gamechanger.wallet

GCScript DSL / Syntax

Syntax

Everything on GCScript are function calls. A main function call, and itโ€™s nested function calls. Recursively.

Functions

In GCScript a function call is a JSON object with a type property. Itโ€™s value is the name of the function. All the other properties are argument names defined function declarations, and their values are the argument values passed to each function.

Example of a function call without arguments:

{
    "type": "getCurrentAddress",
}

The getCurrentAddress function returns current wallet address and has no arguments.

Example of a function call with an argument named value:

{
    "type":"data",
    "value":[
        "foo",
        "bar",
        "baz",
        true,
        null,
        {"nested":{"object":{"inside":"here"}}}
    ]
}

The data function returns the JSON type passed as value argument, and allows you to define a constant.

Blocks of Code

In GCScript a container structure with nested function calls, a block of code, is defined by calling the script function. The body, the nested code, is a key-value map (or list) of function calls passed on the run argument.

Example of a block of code:

{
    "type": "script",
    "run":{
        "result01":{"type":"data", "value":"A"},
        "result02":{"type":"data", "value":"B"},
        "result03":{"type":"data", "value":"C"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: script, data

But this is the result of that code:

{
  "exports": {}
}

Why exports is empty? Have I done something wrong?

Locally, a block of code, (and descendants) has a cache, a local scope memory space where child function calls store their results.

This cache object is isomorphic (unless instructed otherwise) with the code structure itself, it means it keeps the same structure as the block of code that populates it with results during runtime execution.

Globally, the entire script has a unique exports object, and only what gets stored there is going to be returned back to dapp or caller agent.

Each block of code can export itโ€™s cache contents into this exports object.

So in other terms, in order to return data from script execution back to a dapp for example, data needs to be exported from the local caches into the unique global exports object. This can be done by passing an exportAs property with a name for those exports, like this:

Example of a block of code with exports:

{
    "type": "script",
    "exportAs":"myFirstExport",
    "run":{
        "result01":{"type":"data", "value":"A"},
        "result02":{"type":"data", "value":"B"},
        "result03":{"type":"data", "value":"C"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: data, script

and now these are the expected results:

{
  "exports": {
    "myFirstExport":{
        "result01":"A",
        "result02":"B",
        "result03":"C"
    }
  }
}

Finally, at root level there is always a script function call containing all the others functions nested inside, like the usual main() function in C language.

This root script function call has some unique superpowers, for example it can instruct the wallet where to send the results back, this can be done by setting the returnURLPattern property:

Example of a block of code exporting data back to a URL:

{
    "type": "script",
    "exportAs":"myFirstExport",
    "returnURLPattern":"https://google.com/dappCallExecutionResults/{result}/view",  
    "run":{
        "result01":{"type":"data", "value":"A"},
        "result02":{"type":"data", "value":"B"},
        "result03":{"type":"data", "value":"C"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: data, script

and then results will be packed and shared back to that dapp by redirecting the user to the produced URL

"https://google.com/dappCallExecutionResults/1-H4sIAAAAAAAAA6tWSq0oyC8qKVayqlbKrXTLLCoucQWLgASKUotLc0oMDJWslByVdGBcIyDXCcE1BnKdlWprawHx4ftoTAAAAA/view"

Important: this redirection only gets enabled if initial dapp->wallet communication was established from an https origin.

Isomorphism

We can understand isomorphism better by nesting some code blocks and check results.

Example of isomorphism with nested blocks of code:

{
    "type": "script",
    "exportAs":"myNestedExport",
    "run":{
        "groupA":{
            "type": "script",
            "run":{
                "result01":{"type":"data", "value":"A"},
                "result02":{"type":"data", "value":"B"},
                "result03":{"type":"data", "value":"C"}
            }
        },
        "groupB":{
            "type": "script",
            "run":{
                "result01":{"type":"data", "value":"A"},
                "result02":{"type":"data", "value":"B"},
                "result03":{"type":"data", "value":"C"}
            }
        },
        "groupC":{
            "type": "script",
            "run":{
                "result01":{"type":"data", "value":"A"},
                "result02":{"type":"data", "value":"B"},
                "result03":{"type":"data", "value":"C"}
            }
        }             
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: data, script

and the isomorphic results:

{
  "exports": {
    "myNestedExport": {
      "groupA": {
        "result01": "A",
        "result02": "B",
        "result03": "C"
      },
      "groupB": {
        "result01": "A",
        "result02": "B",
        "result03": "C"
      },
      "groupC": {
        "result01": "A",
        "result02": "B",
        "result03": "C"
      }
    }
  }
}

Lists normalization

Usually GCScript turns lists into key-value maps to make lists and maps equally valid.

Example of a block of code as a list:

{
    "type": "script",
    "exportAs":"myWalletData",
    "run":[
        {"type":"getName"},
        {"type":"getCurrentAddress"},
        {"type":"getCurrentSlot"}
    ]
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: script

The getName function returns current wallet name and has no arguments.

The getCurrentAddress function returns current wallet address and has no arguments.

The getCurrentSlot function returns current blockchain slot number and has no arguments.

Now because underneath lists are normalized into key-value maps with item indexes as map keys, we can do this:

Example of a block of code as a key-value map:

{
    "type": "script",
    "exportAs":"myWalletData",
    "run":{
        "0":{"type":"getName"},
        "1":{"type":"getCurrentAddress"},
        "2":{"type":"getCurrentSlot"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: getName, getCurrentAddress, getCurrentSlot, script

And in both cases, results will be exactly the same:

{
  "exports": {
    "myWalletData": {
      "0": "Default",
      "1": "addr_test1qqptd68g4yzkrtn38srqvkk78c3stkvw4wvvsqsmaanny7adqwj2u3djrag0mene2cm9elu5mdqmcz9zc2rzgq7c5g6q5rgrg5",
      "2": "54762728"
    }
  }
}

GCScript normalize lists as key-value map, because among other reasons, key-value maps are the preferred structure on this language. Why you may ask?

Well because of the great self-documenting properties a block of code expressed as key-value map has to offer.

Letโ€™s self document this code better:

Example of a self-documented block of code as key-value map:

{
    "type": "script",
    "exportAs":"myWalletData",
    "run":{
        "name":     {"type":"getName"},
        "address":  {"type":"getCurrentAddress"},
        "slot":     {"type":"getCurrentSlot"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: getName, getCurrentAddress, getCurrentSlot, script

and this time results are, well, self-documented and isomorphic:

{
  "exports": {
    "myWalletData": {
      "name":       "Default",
      "address":    "addr_test1qqptd68g4yzkrtn38srqvkk78c3stkvw4wvvsqsmaanny7adqwj2u3djrag0mene2cm9elu5mdqmcz9zc2rzgq7c5g6q5rgrg5",
      "slot":       "54762728"
    }
  }
}

Anatomy of the interpreter

From last example, we can say that the object properties name,address and slot are variables, and each one will store the result of each of itโ€™s functions being called.

To be more specific GCScript is actually doing underneath something like this javascript code:


let exports={}

function script(exportAs){
    let cache={}
    cache["name"    ]= getName();
    cache["address" ]= getCurrentAddress();
    cache["slot"    ]= getCurrentSlot();

    if(exportAs!==undefined)
        exports[exportAs]=cache;
    
    return cache
}

Results of each function call within a block (or descendants blocks ) will be stored on an internal cache object, or local memory space.

Remember that only when you use the exportAs property you are telling the interpreter to export this local state to the outer world.

Understanding this internal logic is important because this will be the key for you to access these variables/results for later reuse and to break the default isomorphism in order to customize your results.

Breaking the default isomorphism

โ€œToday I woke up and decided that I hate your isomorphism and I want to cherry-pick my exports carefully, leaving behind some data. Can i break the default isomorphism?โ€

Well yes, you can!

The solution is to set the mode in which each script function returns itโ€™s results from itโ€™s own cache memory. This can be done by setting the return property.

The return property can take these options and each one alters the code block results in different ways:

Option Description
{"mode":"all"} Will return all it children code block results. This is the default isomorphic behavior
{"mode":"none"} Will return undefined, and because is not a valid JSON value it will be purged. Like a function():void; in typescript
{"mode":"first"} Will return the result of itโ€™s first child code block.
{"mode":"last"} Will return the result of itโ€™s last child code block.
{"mode":"one", "key":"<CHILD_KEY>"} Will return the result of one child code block, the one in the key name or position argument.
{"mode":"some","keys":[<CHILD_KEY1>,<CHILD_KEY2>,...,<CHILD_KEY_N>]} Will return the result of some children code blocks, the ones in the keys name or position list argument.
{"mode":"macro","exec":"<ISL>"} Will return the result of the execution of an inline scripting language macro. Useful for formatting, debugging results

Lets explore all these on an example, but we will leave explanations for the macro mode for later.

{
    "type": "script",    
    "title": "Return modes example",
    "description": "Learning all the return modes",    
    "return": {
        "mode": "all"
    },
    "exportAs":"starfleetMuseum",
    "run": {
        "none": {
            "type": "script",
            "return": {
                "mode": "none"
            },
            "run": {
                "A": {
                    "type": "data",
                    "value": "USS Enterprise A"
                },
                "B": {
                    "type": "data",
                    "value": "USS Enterprise B"
                },
                "C": {
                    "type": "data",
                    "value": "USS Enterprise C"
                },
                "D": {
                    "type": "data",
                    "value": "USS Enterprise D"
                },
                "E": {
                    "type": "data",
                    "value": "USS Enterprise E"
                }
            }
        },
        "all": {
            "type": "script",
            "return": {
                "mode": "all"
            },
            "run": {
                "A": {
                    "type": "data",
                    "value": "USS Enterprise A"
                },
                "B": {
                    "type": "data",
                    "value": "USS Enterprise B"
                },
                "C": {
                    "type": "data",
                    "value": "USS Enterprise C"
                },
                "D": {
                    "type": "data",
                    "value": "USS Enterprise D"
                },
                "E": {
                    "type": "data",
                    "value": "USS Enterprise E"
                }
            }
        },
        "first": {
            "type": "script",
            "return": {
                "mode": "first"
            },
            "run": {
                "A": {
                    "type": "data",
                    "value": "USS Enterprise A"
                },
                "B": {
                    "type": "data",
                    "value": "USS Enterprise B"
                },
                "C": {
                    "type": "data",
                    "value": "USS Enterprise C"
                },
                "D": {
                    "type": "data",
                    "value": "USS Enterprise D"
                },
                "E": {
                    "type": "data",
                    "value": "USS Enterprise E"
                }
            }
        },
        "last": {
            "type": "script",
            "return": {
                "mode": "last"
            },
            "run": {
                "A": {
                    "type": "data",
                    "value": "USS Enterprise A"
                },
                "B": {
                    "type": "data",
                    "value": "USS Enterprise B"
                },
                "C": {
                    "type": "data",
                    "value": "USS Enterprise C"
                },
                "D": {
                    "type": "data",
                    "value": "USS Enterprise D"
                },
                "E": {
                    "type": "data",
                    "value": "USS Enterprise E"
                }
            }
        },
        "one": {
            "type": "script",
            "return": {
                "mode": "one",
                "key": "C"
            },
            "run": {
                "A": {
                    "type": "data",
                    "value": "USS Enterprise A"
                },
                "B": {
                    "type": "data",
                    "value": "USS Enterprise B"
                },
                "C": {
                    "type": "data",
                    "value": "USS Enterprise C"
                },
                "D": {
                    "type": "data",
                    "value": "USS Enterprise D"
                },
                "E": {
                    "type": "data",
                    "value": "USS Enterprise E"
                }
            }
        },
        "some": {
            "type": "script",
            "return": {
                "mode": "some",
                "keys": [
                    "B",
                    "D"
                ]
            },
            "run": {
                "A": {
                    "type": "data",
                    "value": "USS Enterprise A"
                },
                "B": {
                    "type": "data",
                    "value": "USS Enterprise B"
                },
                "C": {
                    "type": "data",
                    "value": "USS Enterprise C"
                },
                "D": {
                    "type": "data",
                    "value": "USS Enterprise D"
                },
                "E": {
                    "type": "data",
                    "value": "USS Enterprise E"
                }
            }
        },
        "isl": {
            "type": "script",
            "return": {
                "mode": "macro",
                "exec": "{get('cache.isl.C')}"
            },
            "run": {
                "A": {
                    "type": "data",
                    "value": "USS Enterprise A"
                },
                "B": {
                    "type": "data",
                    "value": "USS Enterprise B"
                },
                "C": {
                    "type": "data",
                    "value": "USS Enterprise C"
                },
                "D": {
                    "type": "data",
                    "value": "USS Enterprise D"
                },
                "E": {
                    "type": "data",
                    "value": "USS Enterprise E"
                }
            }
        }
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: data, script

and the results:

{
  "exports": {
    "starfleetMuseum": {
      "all": {
        "A": "USS Enterprise A",
        "B": "USS Enterprise B",
        "C": "USS Enterprise C",
        "D": "USS Enterprise D",
        "E": "USS Enterprise E"
      },
      "first": "USS Enterprise A",
      "last": "USS Enterprise E",
      "one": "USS Enterprise C",
      "some": {
        "B": "USS Enterprise B",
        "D": "USS Enterprise D"
      },
      "isl": "USS Enterprise C"
    }
  }
}

Blocks of Code with user defined arguments

Same like on other languages, you can pass arguments to a user-defined function or block of code and they will exist on that local scope context for the contained code to consume them.

Example of passing a single user-defined argument on a block of code:

{
    "type": "script",
    "args": "Hey, I'm an argument!",
    "exportAs":"myWalletData",
    "run":{
        "name":     {"type":"getName"},
        "address":  {"type":"getCurrentAddress"},
        "slot":     {"type":"getCurrentSlot"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: getName, getCurrentAddress, getCurrentSlot, script

also you can pass arguments differently by passing one argument to each children function call individually

Example of passing a user-defined argument per child function call on a block of code:

{
    "type": "script",
    "argsByKey": {
        "name":     "Hey, I'm name's argument!",
        "address":  "Hey, I'm address's argument!",
        "slot":     "Hey, I'm slot's argument!"
    },
    "exportAs":"myWalletData",
    "run":{
        "name":     {"type":"getName"},
        "address":  {"type":"getCurrentAddress"},
        "slot":     {"type":"getCurrentSlot"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: getName, getCurrentAddress, getCurrentSlot, script

also both techniques work together if combined in such a way that if a child argument has not been provided to argsByKey, args value will be passed by default as a fallback

Example of passing a user-defined argument per child function call and a fallback on a block of code:

{
    "type": "script",
    "args": "Hey, I'm slot's argument!",
    "argsByKey": {
        "name":     "Hey, I'm name's argument!",
        "address":  "Hey, I'm address's argument!"
    },
    "exportAs":"myWalletData",
    "run":{
        "name":     {"type":"getName"},
        "address":  {"type":"getCurrentAddress"},
        "slot":     {"type":"getCurrentSlot"}
    }
}

Run on Cardano Mainnet Run on Cardano Pre-Production Testnet

๐Ÿ” See also: getName, getCurrentAddress, getCurrentSlot, script

How to access and use these arguments, the cache memory, and other topics will be addressed once you get to know how to code on GCScriptโ€™s Inline Scripting Language (ISL).

Full API documentation

โ€œI canโ€™t wait to build something on Cardano, give me all the available functions, now!โ€

Ok, here you have them:

Network API Documentation
Cardano Mainnet HTML Docs
Cardano Pre Production Testnet HTML Docs

Remember that GCScript DSL is a language defined by a JSON schema, so here are the latest auto-generated docs, self-hosted on the wallet itself. Schema between networks are the same but maybe some examples will change based on each one in the future.

Previous: Quick Start Next: GCScript on Steroids: with Inline Scripting Language (ISL)