gamechanger.wallet

GCScript DSL / GCScript on Steroids: with Inline Scripting Language (ISL)

Overview

On this chapter you have learned how every element on GCScript is a function call, which involves a permissioned wallet operation that may have input arguments and may return some results after it’s execution, but…

If I ask you to code a simple dapp connection to calculate and return only the SHA512 hash of user’s address and nothing more for privacy reasons, how would you solve it?

So far you can ask for and return back the user address which is a not private enough solution for this exercise:

{
    "type": "script",
    "exportAs":"myWalletData",
    "return":{
        "mode":"last"
    },
    "run":{
        "address":  {"type":"getCurrentAddress"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: getCurrentAddress, script

My results:

{
  "exports": {
    "myWalletData": "addr1q8aw6dzpw3cld828cqywp0sql4sxfw6syhzyh2kfcfccakddqwj2u3djrag0mene2cm9elu5mdqmcz9zc2rzgq7c5g6qshxn7l"
  }
}

Inline Scripting Language (ISL): is the scripting language you can use to execute minor and permission-less operations to give flexibility, privacy and general purpose features to the permissioned and static GCScript. It is the available addon language to enhance GCScript and it is only callable in-lined on most GCScript function arguments. ISL syntax is a non-turing complete subset of Javascript. You can also provide non-ISL or invalid-ISL strings to GCScript function arguments and will be treated as string constants instead.


Macro: Is a special GCScript function that acts like a pipe, it returns the isomorphic JSON results of the JSON with (or without) ISL strings passed on the run argument. In other terms, it is very handy because you can use it to run ISL code while also for declaring constants like in the data function. It’s only difference with data is that macro supports ISL.


How to know if a specific function argument accepts ISL? ISL is supported on most arguments, still you can check in [GCScript API Documentation](https://beta-wallet.gamechanger.finance/doc/api/v2/index.html) if it accepts **ISL** as some may not for diverse reasons.


Now let’s use the macro function with a single line of ISL for

{
    "type": "script",
    "exportAs":"myPrivateWalletData",
    "return":{
        "mode":"last"
    },
    "run":{
        "address":  {"type":"getCurrentAddress"},
        "hash":     {
            "type":"macro",
            "run" :"{sha512(get('cache.address'))}"
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: getCurrentAddress, macro, get, sha512, script

My -private- results:

{
  "exports": {
    "myPrivateWalletData": "776887253df0b91d752b997c5cc08dcbcdf19e7f01f9475c3e14e8899e2979b097d3c5cca3251bdbbf83493560d84e946f82f4d021bf0fd0a4faf124725048ea"
  }
}


In summary, ISL is to GCScript as:

Syntax and behavior

ISL syntax is a simple subset of Javascript syntax. It is meant mainly for calling a pre-defined set of functions. The ISL interpreter understands other basic types such as strings and numbers but once again, it is mainly meant for calling functions.

Rules:

Currently, like in GCScript, execution flow lacks conditional branching and iteration support to keep the execution outcome as deterministic as possible without introducing wallet-side unknown behaviors. Simplicity as a security feature. If you need conditionals, iteration support and other turing complete language features, try solving that part of your code at dapp-side, outside the wallet, or redesign your system to only delegate wallet-side tasks that are well defined and are not prone to scale randomly nor take unknown actions.

“The wallet is your smart and auditable employee, only you must take the critical decisions”.

Valid ISL or Invalid ISL String Constants?

Valid ISL or Invalid ISL String Constants? That is the question. Sometimes you need to pass ISL code, sometimes you only want to pass a string constant, and sometimes well.. you forget to wrap your ISL code between { and }

Let’s explore interpreter behavior around this language feature using 2 ISL functions,console() for logging values on the Dapp Connector Console and the return() function to return values into cache object in script context:

{
    "type": "script",
    "title":"ISL Validity Test",
    "description":"Check Advanced-Console as well as script results",
    "exportAs":"ISLValidityTest",
    "run":{
        "valid":{
            "type":"macro",
            "run" :"{console('log','✓ This is valid ISL'); return('OK')}"
        },
        "invalid":{
            "type":"macro",
            "run" :"console('log','✗ This is invalid ISL'); return('NOT OK')"
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: macro, console, return, script

Export results:

{
  "exports": {
    "ISLValidityTest": {
      "valid": "OK",
      "invalid": "console('log','✗ This is invalid ISL'); return('NOT OK')"
    }
  }
}

and if we check logs and warnings on Advanced -> Console, we get:

[
  {
    "path": "/valid",
    "type": "log",
    "message": "✓ This is valid ISL",
    "time": 1717100675363
  },
  {
    "path": "/invalid",
    "type": "warn",
    "message": "{\n  \"code\": \"inline-macro-maybe-text\",\n  \"message\": \"just letting you know that this text will be used as value because differs too much from inline macro syntax. Maybe this is what you wanted and is ok.\",\n  \"more\": {\n    \"error\": \"Syntax error. Block of code between '{' and '}' not found in 'console('log','✗ This is invalid ISL'); return('NOT OK')'\",\n    \"text\": \"console('log','✗ This is invalid ISL'); return('NOT OK')\"\n  }\n}",
    "time": 1717100675534
  }
]

This is how running this script looks like on the Dapp Connector Live Debugger:

In summary: Review your ISL code! Invalid syntax make code lines behave as string constants. Also with the console(<'log'|'info'|'warn'|'error'>,<value>) ISL function and the Dapp Connector Console you can debug any constant or variable of your code.

ISL on GCScript function arguments

Remember that almost all GCScript function arguments support ISL?, let’s explore ISL on other functions than macro.

Let’s pass the result of the address function into the signDataWithAddress function to sign an hexadecimal encoded message. To do so we will use the get() function, that is a getter function for accessing script context variables like the cache global object, the memory space where all function results are stored. The get() function accepts the JSON path of the memory space we want to read as argument, in this case get('cache.myAddress').

Also let’s use the ISL strToHex() function to encode on-the-fly a text message as an hexadecimal string. Message will be provided through script arguments and once again accessed by calling get('args.myMessage') function. Get ready to use get() a lot.

{
    "type": "script",
    "title":"Message Signing",
    "description":"Let's use your wallet private key to sign an arbitrary message",
    "args":{
        "myMessage":"✉ this is the input message ✉"
    },
    "return":{
        "mode":"last"
    },
    "exportAs":"messageSignature",
    "run":{
        "myAddress":  {"type":"getCurrentAddress"},
        "mySignature": {
            "type": "signDataWithAddress",
            "address": "{get('cache.myAddress')}",
            "dataHex": "{strToHex(get('args.myMessage'))}"
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: getCurrentAddress, signDataWithAddress, get, strToHex, script

My export results:

{
  "exports": {
    "messageSignature": {
      "signature": "845846a201276761646472657373583901faed34417471f69d47c008e0be00fd6064bb5025c44baac9c2718ed9ad03a4ae45b21f50fde67956365cff94db41bc08a2c2862403d8a234a166686173686564f45821e29c8920746869732069732074686520696e707574206d65737361676520e29c895840681d92bec9b500d1999acd60499bd9e6e4f7c2ea22052ee38e56fde0b9d96e188a45c9a9a18cb6375c5e65558809ea3b0c9d1e8a61a913f3a0d2cc0f7c676c05",
      "key": "a401010327200621582048362707efe478336740139127d8468aca10bf1358d2ab903d2d58876c99733b"
    }
  }
}

Variables and ISL: the global object

So far you have learned how the get() ISL function can be used to access the cache global object, which is a read-only space where GCScript function results are stored as constants.

“What if I need to create my own variables?”

Welcome the global object, the memory space you can read and write through ISL.

For reading any memory space, like cache, args, and global objects we use the already known get(<path>) function providing the JSON path we want to access for reading, and for writing into global object we use the set(<name>,<value>) function providing the name of the variable we want to create or update and the value we want to set on that memory space.

Let’s do some arithmetic operations involving user-defined variables.

The addBigNum() and subBigNum() ISL functions are math helpers that allow you to add and subtract from small to very large numbers, this numeric type is called BigNum and is a safer type to deal with blockchain values. These numbers are handled as numeric strings.

What number do you think we will get as result?

{
    "type": "script",
    "title":"My Calcs",
    "description":"Let's use variables to do some arithmetic operations",
    "exportAs":"myCalc",
    "return":{
        "mode":"last"
    },
    "run":{
        "create": {
            "type": "macro",
            "run":"{set('myVariable','1')}"
        },
        "operation1": {
            "type": "macro",
            "run":"{set('myVariable',addBigNum(get('global.myVariable'),'10'))}"
        },
        "operation2": {
            "type": "macro",
            "run":"{set('myVariable',subBigNum(get('global.myVariable'),'10'))}"
        },
        "operation3": {
            "type": "macro",
            "run":"{set('myVariable',addBigNum(get('global.myVariable'),'10'))}"
        },                        
        "finally":{
            "type": "macro",
            "run":"{get('global.myVariable')}"
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: macro, set, get, addBigNum, subBigNum, script

Export results:

{
  "exports": {
    "myCalc": "11"
  }
}

This is how it looks like on the *Dapp Connector Live Debugger, running the dapp connection in Step By Step Mode for pausing the execution and analyzing the script context memory on each line of the code.*

The Dapp Connector Live Debugger allows you to audit in real time every line of code and the execution impact of every single instruction a dapp is asking your wallet to do. No matter what dapp brand it is, where the code came from, never trust, always verify, you as a Web3 user or developer can review, fork, edit and re-deploy any dapp connection code, any GCScript. Welcome to the wallet where you are in control.

On the Dapp Connector User Interface you can choose in which mode to run your dapp connection by clicking/tapping on the Continue Dropdown Button and selecting the mode, for example Step By Step, Slowed Down or Normal mode among other options like Edit On Playground IDE which allows you to edit and fork any dapp connection.

Go to Advanced -> Status for watching live a snapshot of the script memory context on any moment of the script execution.

Execution flow

The macro GCScript function accepts as argument any JSON type with/without ISL strings embedded at any level of it. This means that with macro we have a high degree of freedom on how to design and distribute the in-lined code.

Let’s solve the exact same math operation sequence from last example but in different ways:

One-liner

By separating function calls with ; and by using the return(<value>) ISL function at the end of the sequence we can run more than one instruction on the same ISL line, on the same block of code:

One ISL code block (with line breaks for cleaner readability)

{
    set('myVariable','1');    
    set('myVariable',addBigNum(get('global.myVariable'),'10'));
    set('myVariable',subBigNum(get('global.myVariable'),'10'));
    set('myVariable',addBigNum(get('global.myVariable'),'10'));
    return(get('global.myVariable'));
}

Now same ISL code block but now embedded on GCScript (with scaped line breaks as JSON specification requires)

{
    "type": "script",
    "title":"My Calcs",
    "description":"Let's use variables to do some arithmetic operations",
    "exportAs":"myCalc",
    "return":{
        "mode":"last"
    },
    "run":{
        "finally":{
            "type": "macro",
            "run":"{\nset('myVariable','1');\n set('myVariable',addBigNum(get('global.myVariable'),'10'));\n set('myVariable',subBigNum(get('global.myVariable'),'10'));\n set('myVariable',addBigNum(get('global.myVariable'),'10'));\n return(get('global.myVariable'));\n }"
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: macro, set, get, addBigNum, subBigNum, return, script

Array of ISL strings

As macro allow us for passing JSON lists or arrays of strings, we can split the code in ISL code blocks items for cleaner readability:

{
    "type": "script",
    "title":"My Calcs",
    "description":"Let's use variables to do some arithmetic operations",
    "exportAs":"myCalc",
    "return":{
        "mode":"last"
    },
    "run":{
        "myCalcs": {
            "type": "macro",
            "run": [
                "{set('myVariable','1')}",
                "{set('myVariable',addBigNum(get('global.myVariable'),'10'))}",
                "{set('myVariable',subBigNum(get('global.myVariable'),'10'))}",
                "{set('myVariable',addBigNum(get('global.myVariable'),'10'))}"                
            ]
        },
        "finally":{
            "type": "macro",
            "run":"{get('global.myVariable')}"
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: macro, set, get, addBigNum, subBigNum, script

Key-Value Map of ISL strings

As macro allow us for passing JSON key-value maps of strings, we can split the code in ISL code blocks values for cleaner readability and also for a self-documented code using map keys for this matter:

{
    "type": "script",
    "title":"My Calcs",
    "description":"Let's use variables to do some arithmetic operations",
    "exportAs":"myCalc",
    "return":{
        "mode":"last"
    },
    "run":{
        "myCalcs": {
            "type": "macro",
            "run": {
                "create":"{set('myVariable','1')}",
                "operation1":"{set('myVariable',addBigNum(get('global.myVariable'),'10'))}",
                "operation2":"{set('myVariable',subBigNum(get('global.myVariable'),'10'))}",
                "operation3":"{set('myVariable',addBigNum(get('global.myVariable'),'10'))}"                
            }
        },
        "finally":{
            "type": "macro",
            "run":"{get('global.myVariable')}"
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: macro, set, get, addBigNum, subBigNum, script

Isomorphism

Some GCScript function arguments only accepts a string, a number or a boolean value.

Some GCScript function arguments accepts more complex JSON structures like key-value maps or arrays.

The macro GCScript function accepts as argument any JSON type with/without ISL strings embedded at any level of it. It is an isomorphic function. It can handle these types:

In this example we can explore isomorphism with ISL nested at deep levels of a JSON structure:

{
    "type": "script",
    "exportAs":"isomorphicResult",
    "return":{
        "mode":"last"
    },
    "run":{
        "name":     {"type":"getName"},
        "address":  {"type":"getCurrentAddress"},
        "slotNo":   {"type":"getCurrentSlot"},
        "isomorphicTest":{
            "type":"macro",
            "run" :{
                "myList":[
                    "{get('cache.name')}",
                    "{get('cache.address')}",
                    "{get('cache.slotNo')}",
                    false,
                    14,
                    "this is not ISL, so is a string constant"
                ],
                "my":{
                    "nested":{
                        "map":{
                            "myName":"{get('cache.name')}",
                            "myAddress":"{get('cache.address')}",
                            "mySlotNo":"{get('cache.slotNo')}",
                            "foo":true,
                            "bar":15,
                            "baz":"this is also a non-ISL string, so is a string constant"
                        }
                    }
                },
                "even this is possible":{
                    "{get('cache.name')}":{
                        "{get('cache.address')}":{
                            "{get('cache.slotNo')}":"voilà (another string constant)"
                        }
                    }
                }
            }
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

🔍 See also: getName, getCurrentAddress, getCurrentSlot, macro, get, script

My isomorphic results:

{
  "exports": {
    "isomorphicResult": {
      "myList": [
        "Default",
        "addr1q8aw6dzpw3cld828cqywp0sql4sxfw6syhzyh2kfcfccakddqwj2u3djrag0mene2cm9elu5mdqmcz9zc2rzgq7c5g6qshxn7l",
        "125512966",
        false,
        14,
        "this is not ISL, so is a string constant"
      ],
      "my": {
        "nested": {
          "map": {
            "myName": "Default",
            "myAddress": "addr1q8aw6dzpw3cld828cqywp0sql4sxfw6syhzyh2kfcfccakddqwj2u3djrag0mene2cm9elu5mdqmcz9zc2rzgq7c5g6qshxn7l",
            "mySlotNo": "125512966",
            "foo": true,
            "bar": 15,
            "baz": "this is also a non-ISL string, so is a string constant"
          }
        }
      },
      "even this is possible": {
        "Default": {
          "addr1q8aw6dzpw3cld828cqywp0sql4sxfw6syhzyh2kfcfccakddqwj2u3djrag0mene2cm9elu5mdqmcz9zc2rzgq7c5g6qshxn7l": {
            "125512966": "voilà (another string constant)"
          }
        }
      }
    }
  }
}

The isomorphic macro function with it’s ISL support is very handy for creating complex JSON templates, for example you may find it useful for dealing with


Congratulations! You have learned all the basic knowledge on how to combine GCScript with ISL to solve all kind of wallet-related operations on client side in a way users can audit and developers can fork and contribute back, fostering an entire Web3 open source ecosystem for Cardano.

Now it’s a matter of learning the specific GCScript and ISL functions that will allow you to build, sign and submit transactions, derive keys, sign and encrypt messages and even create and deploy code on GCFS, the Cardano on-chain Filesystem Protocol

Previous: Syntax Home: General Documentation