Skip to content

A lightweight scripting language for querying crypto wallets.

Notifications You must be signed in to change notification settings

varunshenoy/unfold-lang

Repository files navigation

Logo


Unfold is a lightweight, domain-specific language for querying crypto wallets. It currently supports various operations, such as checking the ETH, ERC20, or ERC721 balance, on a single wallet address.

The primary benefit of Unfold is its simplicity in usage. You can learn the entire grammar in < 5 minutes, making it a perfect low-code environment for tokengating your storefront or community. Moreover, every runtime is sandboxed and isolated from the rest of the environment. This prevents malicious users from injecting Javascript into your websites.

Sounds interesting? Check out the demo!

Want to try it out? I've set up a playground just for you!

Installation

Unfold is available on npm. Just run the following command in your project.

npm i unfold-lang

You can also manually install Unfold. This is useful if you want to make significant changes to the interpreter architecture. Just clone the repo (or download it) and run npm install in its corresponding directory.

Set Up

After installation, you can get started in <15 lines of code.

// 1. Import the context and runtime
const EthereumWalletContext = require("unfold-lang/lang/EthereumWalletContext");
const UnfoldRuntime = require("unfold-lang/lang/parse");

// Important: execution code must be in an async block because we do not want to return until the entire AST is traversed
async function run() {
    // 2. Gather code and address to query
    const code = `...`;
    const address = "0x...";

    // 3. Instantiate context and runtime
    const walletContext = new EthereumWalletContext(address, YOUR_RPC_KEY);
    const runtime = new UnfoldRuntime(code, walletContext);

    // 4. Run the interpreter
    await runtime.setup();
    console.log(runtime.ast);
    await runtime.execute();

    // 5. Print the result
    console.log(runtime.result());
}

run();

Check out example.js to see a full example.

Under the Hood

Every time any Unfold code is written, a separate runtime instance needs to be created to execute the code.

To maintain generalizability to other L1/L2 chains, Unfold runtimes expect two inputs: the written code and a wallet context. Wallet contexts are responsible for providing methods and attributes that interact with the chain, such as address, queryNativeToken(), queryToken(tokenType, tokenAddress), and setChain(chainId).

The lexing/parsing of Unfold is handled by nearleyjs, which returns an abstract syntax tree in JSON using a grammar specified in grammar.ne. This tree is passed off to simple recursive interpreter written in vanilla Javascript.

An example of an AST (for the first example in example_queries.txt) is shown below.

[
  { operator: 'comment' },
  {
    operator: 'assign',
    name: 'usdc',
    value: {
      operator: 'ERC20',
      address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
    }
  },
  { operator: 'return', value: 'usdc' }
]

A diagram highlighting the architecture of composing Unfold within one of your projects is shown below.

Runtime Overview

Writing Scripts

Every Unfold script is composed of statements. The Unfold runtime expects at most one statement per line. Unfold supports the usual comparators and conditional operations, as well as boolean values.

The syntax for an conditional statement is as follows.

if condition then directive
if condition then directive  else directive

A condition includes all of the usual comparators, logical symbols, and boolean values. Conditions can compare numbers (i.e. amount of USDC in a wallet) or addresses (i.e. for whitelisting).

A directive can be a return statement, a token assignment, a chain change, or another conditional statement.

The only type of variable in Unfold is a token. A token can be defined as follows.

Token TokenName = TokenObject

TokenName can be any string. This identifier is stored a global state that can be reused by future statements.

TokenObject can be created by either specifying ERC20 or ERC721 with a contract address.

Token usdc = ERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
Token bayc = ERC721(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D)

There is also a special TokenObject ETH that queries the amount of Ether in a given wallet address.

Unfold supports a unique address accessor through the $ object. You can check how much of a token the current wallet has by using $.balanceOf(TokenName). You can get the address of the current wallet with $.address, which can be used for whitelisting.

Any condition or value can be returned from the Unfold runtime through the return command.

Comments are prefaced with a pound symbol #. A comment must occupy its own line with no other code.

Roadmap

I'm not actively working on this project, but I'll be adding features as I need them.

I'll happily merge new contexts for other L1s, for example, Solana (with the help of @solana/web3js). I want this project to be as extensible and general-purpose as possible.

Adding new primitives to the grammar would be helpful. I'm currently thinking about lists, dicts, etc. Adding types like these would increase complexity by quite a bit (adding for loops, for example), but maybe the tradeoff is worth it.

A cool idea I've been having is to extend the grammar to call methods on contracts directly. Definitely an interesting exercise in language design!

About

A lightweight scripting language for querying crypto wallets.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published