API Reference

Web3

The web3 class is an abstraction for all blockchain networks. You can use this class to obtain instances for interacting with different networks.

Example

var web3 = Web3.new()
var op = web3.get_op_instance()

Optimism

Optimism is a class used for interacting with the OP network. It encapsulates the JSON-RPC interfaces and core protocols needed for interacting with the OP network.

Example

const NODE_RPC_URL := "https://snowy-capable-wave.optimism-sepolia.quiknode.pro/360d0830d495913ed76393730e16efb929d0f652"
var op = Optimism.new()

# Set the node's RPC URL
op.set_rpc_url(NODE_RPC_URL)
var call_msg = {
    "from": "0x0000000000000000000000000000000000000000",
    "to": CONTRACT_ADDRESS,
    "input": "0x" + packed.hex_encode(),
}
# Request a method of the smart contract.
# The process of constructing packed variable is not expanded here.
# Refer to the documentation at ABIHelper for this part of the construction process.
var rpc_resp = op.call_contract(call_msg, "")

How to get Node’s RPC URL

TODO

RPC methods

For RPC requests, we offer two processing methods:

  • Synchronous call

  • Asynchronous call

For synchronous calls, the interface directly returns the final execution result. For asynchronous calls, the interface returns related request parameters, requiring developers to use Godot engine capabilities for asynchronous processing. Asynchronous call interfaces are uniformly prefixed with “async_” before the synchronous interface name.

1. chain_id

chain_id() retrieves the current chain ID for transaction replay protection.

Example

2. network_id

Example

3. block_by_hash

block_by_hash() returns the given full block information.

Note

Note that loading full blocks requires two requests. Use header_by_hash() if you don’t need all transactions or uncle headers.

Example

4. header_by_hash

header_by_hash() returns the block header with the given hash.

Example

5. block_by_number

block_by_number() returns a block from the current canonical chain. If number is null, the latest known block is returned.

Note

Note that loading full blocks requires two requests. Use header_by_number() if you don’t need all transactions or uncle headers.

Example

6. header_by_number

HeaderByNumber returns a block header from the current canonical chain. If number is null, the latest known header is returned.

Example

7. block_number

block_number() returns the most recent block number

Example

8. block_receipts_by_number

block_receipts_by_number() returns the receipts of a given block number. The block number can be specified as follows:

  • “earliest”: 0

  • “pending”: -1

  • “latest”: -2

  • “finalized”: -3

  • “safe”: -4

  • “default”: any positive integer, representing the block number

Example

9. block_receipts_by_hash

block_receipts_by_hash() returns the receipts of a given block hash.

Example

10. transaction_by_hash

transaction_by_hash() returns the transaction with the given hash.

Example

11. transaction_receipt_by_hash

transaction_receipt_by_hash() returns the receipt of a given transaction hash.

Note

Note that the receipt is not available for pending transactions.

Example

12. balance_at

balance_at() returns the wei balance of the given account. The block number can be nil, in which case the balance is taken from the latest known block.

Example

13. nonce_at

nonce_at() returns the nonce of the given account.

Example

14. send_transaction

send_transaction() injects a signed transaction into the pending pool for execution.

If the transaction was a contract creation use the TransactionReceipt method to get the contract address after the transaction has been mined.

Example

15. call_contract

call_contract() executes a message call transaction, which is directly executed in the VM of the node, but never mined into the blockchain.

blockNumber selects the block height at which the call runs. It can be nil, in which case the code is taken from the latest known block. Note that state from very old blocks might not be available.

Example

16. suggest_gas_price

suggest_gas_price() retrieves the currently suggested gas price to allow a timely execution of a transaction.

Example

17. estimate_gas

estimate_gas() tries to estimate the gas needed to execute a specific transaction based on the current pending state of the backend blockchain. There is no guarantee that this is the true gas limit requirement as other transactions may be added or removed by miners, but it should provide a basis for setting a reasonable default.

Example

LegacyTx

When interacting with a blockchain network, we usually need to send a transaction to it. Legacy is a basic transaction type defined in the ETH protocol. The LegacyTx class is a wrapper designed to support this transaction type. It includes the variables and methods needed to create a transaction.

The following example demonstrates how to construct a LegacyTx object and set its various properties.

# create a legacyTx
var legacyTx = LegacyTx.new()
legacyTx.set_nonce(get_nonce)
var gas_price = op.suggest_gas_price()
legacyTx.set_gas_price(gas_price)
legacyTx.set_gas_limit(828516)
var value = BigInt.new()
value.from_string("0")
legacyTx.set_value(value)
legacyTx.set_data(packed)

var chain_id = BigInt.new()
chain_id.from_string("11155420")
legacyTx.set_chain_id(chain_id)
legacyTx.set_to_address(CONTRACT_ADDRESS)

1. set_chain_id

Sets the chain ID for the transaction.

# chain_id: BigInt
# return: void
legacyTx.set_chain_id(chain_id)
Parameters:

chain_id: BigInt

Returns:

void

2. set_nonce

Sets the nonce for the transaction.

todo: explain what is nonce.

# nonce: int
# return: void
legacyTx.set_nonce(nonce)
Parameters:

nonce: int

Returns:

void

3. set_gas_price

Sets the gas price for the transaction.

# gas_price: BigInt
# return: void
legacyTx.set_gas_price(gas_price)
Parameters:

gas_price: BigInt

Returns:

void

4. set_gas_limit

Sets the gas limit for the transaction.

# gas_limit: int
# return: void
legacyTx.set_gas_limit(gas_limit)
Parameters:

gas_limit: int

Returns:

void

5. set_to_address

Sets the address for the transaction.

legacyTx.set_to_address("0xE85f5c8053C1fcdf2b7b517D0DC7C3cb36c81ABF")
Parameters:

address: string; ETH address with 0x prefix

Returns:

void

6. set_value

Sets the value for the transaction.

# value: BigInt
# return: void
legacyTx.set_value(value)
Parameters:

value: BigInt

Returns:

void

7. set_data

Sets the data to be sent to the blockchain network, such as ABI-serialized data for calling a smart contract or custom data.

# data: PackedByteArray
# return: void
legacyTx.set_data(data)
Parameters:

data: PackedByteArray

Returns:

void

8. rlp_hash

Can obtain the keccak256 hash value of LegacyTx after RLP encoding.

# return: PackedByteArray
var hash = legacyTx.rlp_hash()
Parameters:

None

Returns:

PackedByteArray

9. hash

Call this function to get the hash of the transaction. It nceessary to call after sign the transaction.

# return: PackedByteArray
var hash = legacyTx.hash()
Parameters:

None

Returns:

PackedByteArray

10. sign_tx

Sign the LegacyTx using a signer constructed from a private key.

# signer: Ref<Secp256k1Wrapper>
# return: void
legacyTx.sign_tx(signer)
Parameters:

signer: Ref<Secp256k1Wrapper>

Returns:

int: 0 if success, -1 if failed

11. sign_tx_by_account

Sign the LegacyTx by use EthAccount.

# account: Ref<EthAccount>
# return: void
legacyTx.sign_tx_by_account(account)
Parameters:

account: Ref<EthAccount>

Returns:

int: 0 if success, -1 if failed

12. signedtx_marshal_binary

Marshal the signed transaction into binary data and encode to hex string.

# return: PackedByteArray
var res = legacyTx.signedtx_marshal_binary()
Parameters:

None

Returns:

String

BigInt

The BigInt class implements some basic operations related to large numbers, utilizing the GMP library to fulfill its requirements.

1. add

Add two BigInt objects.

# other: BigInt
# return: BigInt
var res = bigInt.add(other)

2. sub

Subtract two BigInt objects.

# other: BigInt
# return: BigInt
var res = bigInt.sub(other)

3. mul

Multiply two BigInt objects.

# other: BigInt
# return: BigInt
var res = bigInt.mul(other)

4. div

Divide two BigInt objects.

# other: BigInt
# return: BigInt
var res = bigInt.div(other)

5. mod

Get the remainder of the division of two BigInt objects.

# other: BigInt
# return: BigInt
var res = bigInt.mod(other)

6. abs

Get the absolute value of a BigInt object.

# return: BigInt
var res = bigInt.abs()

7. cmp

Compare two BigInt objects.

# other: BigInt
# return: int
var res = bigInt.cmp(other)
Returns:

int: 1 if bigInt > other, 0 if bigInt == other, -1 if bigInt < other

8. sgn

Returns the sign of m_number as an integer:

# return: int
var res = bigInt.sgn()
Returns:

int: 1 if bigInt > 0, 0 if bigInt == 0, -1 if bigInt < 0

8. is_zero

Check if the BigInt object is zero.

# return: bool
var res = bigInt.is_zero()

9. from_string

Create a BigInt object from a string.

# str: string
# return: void
bigInt.from_string(str)

10. from_hex

Create a BigInt object from a hex string.

# str: string
# return: void
bigInt.from_hex(str)

11. get_string

Get the string representation of the BigInt object.

# return: string
var res = bigInt.get_string()

12. to_hex

Get the hex string with 0x prefix representation of the BigInt object.

# return: string
var res = bigInt.to_hex()

JsonrpcHelper

Note

This is an internal class and is not intended for direct user use at the moment. We will provide relevant documentation when needed.

ABIHelper

The Ethereum ABI (Application Binary Interface) is a standardized interface between Ethereum smart contracts and external applications. The ABI defines how to encode and decode the functions and events of a smart contract, allowing external applications to interact with the smart contract.

For more details on ABI, you can refer to here.

The ABIHelper class is a utility class for handling ABI encoding and decoding. It provides user-friendly methods for processing ABI encoding and decoding.

Unit test code file: abihelper_unit_test.gd

1. unmarshal_from_json

Parse the ABI JSON string of the smart contract into the ABIHelper object. This sets a series of properties required for ABI encoding and decoding, including function lists, parameter types, etc., for the ABIHelper object.

# json: string
# return: bool
const CONTRACT_ABI := """
[{"inputs":[{"components":[{"internalType":"uint32","name":"a","type":"uint32"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"components":[{"internalType":"string[]","name":"t","type":"string[]"}],"internalType":"struct Test.C[]","name":"x","type":"tuple[]"},{"internalType":"bytes10","name":"y","type":"bytes10"}],"internalType":"struct Test.T[]","name":"c","type":"tuple[]"},{"internalType":"int256[3]","name":"d","type":"int256[3]"}],"internalType":"struct Test.S","name":"s","type":"tuple"},{"components":[{"components":[{"internalType":"string[]","name":"t","type":"string[]"}],"internalType":"struct Test.C[]","name":"x","type":"tuple[]"},{"internalType":"bytes10","name":"y","type":"bytes10"}],"internalType":"struct Test.T","name":"t","type":"tuple"},{"internalType":"uint256","name":"u","type":"uint256"},{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes10","name":"b10","type":"bytes10"}],"name":"f","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"g","outputs":[{"components":[{"internalType":"uint32","name":"a","type":"uint32"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"components":[{"internalType":"string[]","name":"t","type":"string[]"}],"internalType":"struct Test.C[]","name":"x","type":"tuple[]"},{"internalType":"bytes10","name":"y","type":"bytes10"}],"internalType":"struct Test.T[]","name":"c","type":"tuple[]"},{"internalType":"int256[3]","name":"d","type":"int256[3]"}],"internalType":"struct Test.S","name":"","type":"tuple"},{"components":[{"components":[{"internalType":"string[]","name":"t","type":"string[]"}],"internalType":"struct Test.C[]","name":"x","type":"tuple[]"},{"internalType":"bytes10","name":"y","type":"bytes10"}],"internalType":"struct Test.T","name":"","type":"tuple"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"h","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]
"""
var h = ABIHelper.new()
var res = h.unmarshal_from_json(CONTRACT_ABI)
Parameters:

json: string; ABI JSON string. TODO: how to get abi string.

Returns:

bool: true if success, false if failed

2. pack

Pack the parameters into a byte array according to the ABI encoding rules.

Here we need to use two examples to illustrate the usage of the pack function. A simple example is used to explain the basic usage, and a complex example is used to explain how to handle complex data structures.

Example 1: ERC20 transfer

For an ERC20 contract, the definition of its transfer function is as follows:

function transfer(address recipient, uint256 amount) public returns (bool) {
    _transfer(msg.sender, recipient, amount);
    return true;
}

You can see that the transfer function accepts two parameters: a recipient of type address and an amount of type uint256. Its interface ABI definition is similar to:

[
    {
        "constant": false,
        "inputs": [
        {
            "name": "recipient",
            "type": "address"
        },
        {
            "name": "amount",
            "type": "uint256"
        }
        ],
        "name": "transfer",
        "outputs": [
        {
            "name": "",
            "type": "bool"
        }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    }
]

So how do we call this function? The following code demonstrates the process of calling this function using GDScript.

const CONTRACT_ABI := ... # ABI definition of the ERC20 contract
var h = ABIHelper.new()
var res = h.unmarshal_from_json(CONTRACT_ABI)

var params = [
    # receipient
    "0xeB98753449AD50d30561a66CA48BF69EEcaD4bC3",
    # amount
    "123456"
]

var packed = h.pack("transfer", params)
Parameters:

name: string; method name

params: Array; parameters for calling the method

Returns:

PackedByteArray: packed data

Let’s explain this code in detail. First, we create an ABIHelper object h, and then call the unmarshal_from_json function, passing in the ABI definition of the ERC20 contract. This allows the h object to understand the ABI definition of the ERC20 contract.

Next, we define a params array containing two elements. The first element is the recipient’s address, and the second element is the amount value. These correspond to the two parameters of the transfer function.

You can understand it as: params[0] => recipient, params[1] => amount.

Therefore, you can easily deduce that the order of elements in the params array must match the order of parameters in the contract function. This is very important; otherwise, you will get incorrect results.

Finally, we call the pack function, passing in the function name transfer and the parameters array, to get the packed result.

We will use the packed result as the data for the LegacyTx and send it to the blockchain to call the transfer function of the ERC20 contract.

Example 2: Complex Structure Call Example

For some complex business scenarios, people often define custom data structures and pass them when calling functions, such as arrays, structs, nested structs, etc.

Here, we use a complex nested struct as an example to illustrate how to use the pack function for complex data structures. Through this example, you will fully understand the behavior of the pack function.

Now we have a contract that defines many complex data structures, with structs nested within other structs. The contract is defined as follows:

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
pragma experimental ABIEncoderV2;

contract Test {
    struct S {
        uint32 a;
        uint[] b;
        T[] c;
        int256[3] d;
    }

    struct C {
        string[] t;
    }

    struct T {
        C[] x;
        bytes10 y;
    }

    mapping (address=>uint256) balance;

    function f(S memory s, T memory t, uint u, address user, bytes10 b10) public pure {
        // This is a pure function, it does not modify the state nor read the state
    }

    function g() public pure returns (S memory, T memory, address, uint) {
        // Initialize S struct
        S memory s;
        s.a = 1;
        s.b = new uint[](2);
        s.b[0] = 2;
        s.b[1] = 3;
        s.c = new T[](1);
        s.c[0].x = new C[](1);
        s.c[0].x[0].t = new string[](2);
        s.c[0].x[0].t[0] = "STRING_TEST"; // Uppercase string
        s.c[0].x[0].t[1] = "string_test"; // Lowercase string
        s.c[0].y = bytes10(0x04000000000000000000); // 4 in bytes10
        s.d[0] = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // Large number 1
        s.d[1] = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE; // Large number 2
        s.d[2] = ~int256(0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); // Large number 3 (negative number)

        // Initialize T struct
        T memory t;
        t.x = new C[](1);
        t.x[0].t = new string[](2);
        t.x[0].t[0] = "STRING_test"; // Mixed case string
        t.x[0].t[1] = "STRING_TEST_MORE_THAN_32_BYTES_abcdefghijklmnopqrstuvwxyz_0000000111111222222"; // String longer than 32 bytes
        t.y = bytes10(0x08000000000000000000); // 8 in bytes10

        // Initialize address
        address addr = address(0x8eee12Bd33Ec72a277ffA9ddF246759878589D3b);

        // Initialize uint
        uint u = 9;

        return (s, t, addr, u);
    }

    function h(address user) public returns (uint256) {
        balance[user] += 1;
        return balance[user];
    }
}

Note

This Test contract will also be used when introducing the unpack function.

We will use the f() function as an example to illustrate how to use the pack function.

First, we need to obtain the ABI definition of the contract. For the above contract code, its ABI definition is as follows:

const CONTRACT_ABI := """
[{"inputs":[{"components":[{"internalType":"uint32","name":"a","type":"uint32"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"components":[{"internalType":"string[]","name":"t","type":"string[]"}],"internalType":"struct Test.C[]","name":"x","type":"tuple[]"},{"internalType":"bytes10","name":"y","type":"bytes10"}],"internalType":"struct Test.T[]","name":"c","type":"tuple[]"},{"internalType":"int256[3]","name":"d","type":"int256[3]"}],"internalType":"struct Test.S","name":"s","type":"tuple"},{"components":[{"components":[{"internalType":"string[]","name":"t","type":"string[]"}],"internalType":"struct Test.C[]","name":"x","type":"tuple[]"},{"internalType":"bytes10","name":"y","type":"bytes10"}],"internalType":"struct Test.T","name":"t","type":"tuple"},{"internalType":"uint256","name":"u","type":"uint256"},{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes10","name":"b10","type":"bytes10"}],"name":"f","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"g","outputs":[{"components":[{"internalType":"uint32","name":"a","type":"uint32"},{"internalType":"uint256[]","name":"b","type":"uint256[]"},{"components":[{"components":[{"internalType":"string[]","name":"t","type":"string[]"}],"internalType":"struct Test.C[]","name":"x","type":"tuple[]"},{"internalType":"bytes10","name":"y","type":"bytes10"}],"internalType":"struct Test.T[]","name":"c","type":"tuple[]"},{"internalType":"int256[3]","name":"d","type":"int256[3]"}],"internalType":"struct Test.S","name":"","type":"tuple"},{"components":[{"components":[{"internalType":"string[]","name":"t","type":"string[]"}],"internalType":"struct Test.C[]","name":"x","type":"tuple[]"},{"internalType":"bytes10","name":"y","type":"bytes10"}],"internalType":"struct Test.T","name":"","type":"tuple"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"h","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]
"""

Now, let’s explain in detail how to pack the f() function of this contract. The following is an example code in GDScript that demonstrates how to encode the f() function call using the pack function:

var h = ABIHelper.new()
var res = h.unmarshal_from_json(CONTRACT_ABI)

var params = [
    [
        1, # s.a
        [2, 3], # s.b
        [
            [
                [
                    [
                        ["STRING_TEST", "string_test"], # s.c[0].x[0].t
                    ], # s.c[0].x[0]
                ], # s.c[0].x
                [4, 0, 0, 0, 0, 0, 0, 0, 0, 0], # s.c[0].y
            ], # s.c[0]
        ], # s.c
        [
            # 7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
            "57896044618658097711785492504343953926634992332820282019728792003956564819967", # s.d[0]
            # 7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe
            "57896044618658097711785492504343953926634992332820282019728792003956564819966", # s.d[1]
            146 # s.d[2]
        ], # s.d
    ], # s
    [
        [
            [
                ["STRING_test", "STRING_TEST_MORE_THAN_32_BYTES_abcdefghijklmnopqrstuvwxyz_0000000111111222222"], # t.x[0].t
            ], # t.x[0]
        ], # t.x
        [8, 0, 0, 0, 0, 0, 0, 0, 0, 0], # t.y
    ], # t
    9, # u
    "0x8eee12Bd33Ec72a277ffA9ddF246759878589D3b", # user
    [116, 119, 111, 101, 102, 103, 104, 105, 106, 107] # b10
]
var packed = h.pack("f", params)

We can see that the f() function accepts 5 parameters, which are:

  • s: struct S

  • t: struct T

  • u: uint

  • user: address

  • b10: bytes10

The struct S and struct T are composite structures containing multiple fields, with nested structures within them. We need to fill these fields into the params array in the order defined by the contract.

  • params[0] => s

  • params[1] => t

  • params[2] => u

  • params[3] => user

  • params[4] => b10

In the above code, comments are used to explain the value of each field. This should help you clearly understand how to fill the params array.

Additionally, this example contract includes common data types and some special data, such as large numbers, mixed-case strings, and strings longer than 32 bytes. This should help you better understand how to use the pack function.

Note

Now, you should be able to infer the following logical relationship:

For fields within a struct, we use an array to represent them, where each element in the array corresponds to a field in the struct. The order of the elements in the array must match the order of the fields defined in the struct; otherwise, it will result in incorrect outcomes.

3. unpack

First, it is important to note that we do not provide a direct unpack function. Instead, we offer the following two methods to achieve the unpack functionality:

  • unpack_into_dictionary: Decodes the ABI-encoded data into a dictionary object.

  • unpack_into_array: Decodes the ABI-encoded data into an array.

Both methods decode the ABI-encoded data into specific data structures based on the ABI definition, but the resulting data structures provided to the developer for manipulation are different.

We will continue using the smart contract introduced in the pack function section. However, this time we will use the g() function for illustration.

The g() function returns 4 parameters, which are:

  • 0: struct S

  • 1: struct T

  • 2: address

  • 3: uint

Note

These parameters are defined as anonymous in the ABI definition, where the name is empty. Therefore, we use numerical indices to represent these parameters.

Additionally, the return value of the g() function is designed to include complex data and data structures, such as large numbers, structs, arrays, etc. This helps you better understand how to use the unpack function.

Note

For large numbers, we uniformly use strings for wrapping instead of directly using the BigInt class. This approach avoids some unnecessary issues. If you need to perform operations on these numbers, you can use the BigInt class for processing.

Example 1: unpack_into_dictionary

var h = ABIHelper.new()
var res = h.unmarshal_from_json(CONTRACT_ABI)

var callret = "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000003600000000000000000000000008eee12bd33ec72a277ffa9ddf246759878589d3b0000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b535452494e475f54455354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b737472696e675f7465737400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b535452494e475f74657374000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004d535452494e475f544553545f4d4f52455f5448414e5f33325f42595445535f6162636465666768696a6b6c6d6e6f707172737475767778797a5f3030303030303031313131313132323232323200000000000000000000000000000000000000"
var result = {}
var err = h.unpack_into_dictionary("g", callret.hex_decode(), result)
if err != OK:
    assert(false, "unpack_into_dictionary failed!")
# example correct output:
# { "0": { "a": 1, "b": ["2", "3"], "c": [{ "x": [{ "t": ["STRING_TEST", "string_test"] }], "y": [4, 0, 0, 0, 0, 0, 0, 0, 0, 0] }], "d": ["57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819966", "-57896044618658097711785492504343953926634992332820282019728792003956564819968"] }, "1": { "x": [{ "t": ["STRING_test", "STRING_TEST_MORE_THAN_32_BYTES_abcdefghijklmnopqrstuvwxyz_0000000111111222222"] }], "y": [8, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, "2": "8eee12bd33ec72a277ffa9ddf246759878589d3b", "3": "9" }

Example 2: unpack_into_array

var h = ABIHelper.new()
var res = h.unmarshal_from_json(CONTRACT_ABI)

var callret = "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000003600000000000000000000000008eee12bd33ec72a277ffa9ddf246759878589d3b0000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b535452494e475f54455354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b737472696e675f7465737400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b535452494e475f74657374000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004d535452494e475f544553545f4d4f52455f5448414e5f33325f42595445535f6162636465666768696a6b6c6d6e6f707172737475767778797a5f3030303030303031313131313132323232323200000000000000000000000000000000000000"
var result = []
err = h.unpack_into_array("g", callret.hex_decode(), result)
if err != OK:
    assert(false, "unpack_into_array failed!")

# example correct output:
# [[1, ["2", "3"], [[[[["STRING_TEST", "string_test"]]], [4, 0, 0, 0, 0, 0, 0, 0, 0, 0]]], ["57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819966", "-57896044618658097711785492504343953926634992332820282019728792003956564819968"]], [[[["STRING_test", "STRING_TEST_MORE_THAN_32_BYTES_abcdefghijklmnopqrstuvwxyz_0000000111111222222"]]], [8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], "8eee12bd33ec72a277ffa9ddf246759878589d3b", "9"]

The above two examples correspond to using unpack_into_dictionary and unpack_into_array to decode ABI-encoded data. Their usage is as follows:

  1. Initialize an ABIHelper object.

  2. Call the unmarshal_from_json function, passing in the ABI definition of the contract.

  3. Define callret. Here, a test return value after calling the g() function is directly provided. Typically, this value is obtained by calling a function of the smart contract, usually by calling the call_contract() method.

  4. Define a result variable to store the decoded data. Depending on the chosen function, result needs to be either a dictionary object or an array object.

  5. Call the unpack_into_dictionary or unpack_into_array function to decode callret into result.

For unpack_into_dictionary, the decoded value is a dictionary where the keys are the indices of the parameters or the variable names of the struct members, and the values are the parameter values.

Note

Solidity allows naming the return values of functions. If the return values are named, the keys in the returned dictionary will be those names. If they are not named, the keys will be the indices of the parameters.

function getDetails() public pure returns (uint256 id, string memory name, bool isActive);

For unpack_into_array, the decoded value is an array where each element corresponds to a parameter value.


EthAccountManager

EthAccountManager is a class for managing Ethereum accounts. It provides functionality to create new Ethereum accounts and import accounts from private keys.

Warning

The APIs provided by this class have not been audited and may have security vulnerabilities. Please take precautions, properly clear memory, securely store private keys, and thoroughly test transaction receiving and sending functions before using in production!

1. create

Creates a new Ethereum account.

# entropy: PackedByteArray (optional)
# return: EthAccount
var account_manager = EthAccountManager.new()
var account = account_manager.create()
Parameters:
  1. entropy: PackedByteArray (optional); Entropy used to generate a random private key (must be 32 bytes). If empty, the internal system random number is used.

Returns:

EthAccount: An instance of the created Ethereum account object.

Note

To ensure the security of the generated account, although a certain degree of entropy is provided internally, we still recommend that you provide a higher entropy as a parameter.


2. privateKeyToAccount

Creates an Ethereum account based on the provided private key.

# privkey: PackedByteArray
# return: EthAccount
var account_manager = EthAccountManager.new()
var account = account_manager.privateKeyToAccount(private_key)
Parameters:
  1. privkey: PackedByteArray; The private key bytes of the account. Must be in a valid Ethereum private key format.

Returns:

EthAccount: An instance of the Ethereum account created from the private key.


Example

The following example demonstrates how to use EthAccountManager to create a new account and import an account from a private key:

# Create an instance of the account manager
var account_manager = EthAccountManager.new()

# Create a new account
var new_account = account_manager.create(1)
print("New account address: ", new_account.get_address())

# Import an account from a private key
var private_key = PackedByteArray([...]) # 32-byte private key
var imported_account = account_manager.privateKeyToAccount(private_key)
print("Imported account address: ", imported_account.get_address())

EthAccount

EthAccount is a class that handles basic operations for Ethereum accounts. It provides access to account information and signing data. Through EthAccount, users can obtain the private key, public key, and address of the account, and use the account’s private key to sign data.

Note

Please note that instances of EthAccount must be generated through EthAccountManager to ensure proper initialization and management of the account.

Warning

The APIs provided by this class have not been audited and may have security vulnerabilities. Please take precautions, properly clear memory, securely store private keys, and thoroughly test transaction receiving and sending functions before using in production!

1. get_private_key

Gets the private key of the account.

# return: PackedByteArray
var private_key = account.get_private_key()
Returns:

PackedByteArray: The private key bytes of the account.


2. get_public_key

Gets the public key of the account.

# return: PackedByteArray
var public_key = account.get_public_key()
Returns:

PackedByteArray: The public key bytes of the account.


3. get_address

Gets the address of the account (in byte format).

# return: PackedByteArray
var address = account.get_address()
Returns:

PackedByteArray: The address bytes of the account.


4. get_hex_address

Gets the address of the account in hexadecimal format.

# return: String
var hex_address = account.get_hex_address()
Returns:

String: The hexadecimal string address of the account.


5. sign_data

Signs the provided data using the account’s private key.

# data: PackedByteArray
# return: PackedByteArray
var signature = account.sign_data(data)
Parameters:
  1. data: PackedByteArray; The data to be signed.

Returns:

PackedByteArray: The signed data.


6. sign_data_with_prefix

Signs the data after computing the keccak256 hash of the Ethereum signed message prefix.

# data: PackedByteArray
# return: PackedByteArray
var signature = account.sign_data_with_prefix(data)
Parameters:
  1. data: PackedByteArray; The data to be signed.

Returns:

PackedByteArray: The signed data.

Note

  • This method follows the Ethereum signed message standard:

  • keccak256(”\x19Ethereum Signed Message:\n” + len(message) + message)


Example

The following example demonstrates how to perform basic operations using EthAccount:

# Assume we have an account instance
var account = eth_account_manager.create()

# Get account information
print("Address: ", account.get_hex_address())
print("Public Key: ", account.get_public_key().hex_encode())

# Sign some data
var data = "Hello, Ethereum!".to_utf8_buffer()
var signature = account.sign_data(data)
print("Signature: ", signature.hex_encode())

# Sign with Ethereum message prefix
var prefixed_signature = account.sign_data_with_prefix(data)
print("Prefixed Signature: ", prefixed_signature.hex_encode())

EthWalletManager

EthWalletManager is a class for managing multiple Ethereum HD wallets. It provides functionality to create, load, and save wallets. Through this manager, new wallet instances can be generated or existing wallets can be restored from mnemonic phrases.

Warning

The APIs provided by this class have not been audited and may have security vulnerabilities. Please take precautions, properly clear memory, securely store private keys, and thoroughly test transaction receiving and sending functions before using in production!

1. create

Creates a new Ethereum wallet.

# strength: int (optional)
# entropy: PackedByteArray (optional)
# passphrase: String (optional)
# return: EthWallet
var wallet = wallet_manager.create()
var wallet = wallet_manager.create(1, entropy, "my passphrase")
Parameters:
  1. strength: int (optional); create number of accounts.

  2. entropy: PackedByteArray (optional); entropy used when generating the wallet.

  3. passphrase: String (optional); passphrase for additional security.

Returns:

EthWallet: instance of the created wallet.


2. from_mnemonic

Restore a wallet from mnemonic phrase.

# mnemonic: PackedStringArray
# passphrase: String (optional)
# return: EthWallet
var wallet = wallet_manager.from_mnemonic(mnemonic, "optional passphrase")
Parameters:
  1. mnemonic: PackedStringArray;The mnemonic phrase, currently only supports a mnemonic phrase consisting of 12 English words.

  2. passphrase: String (optional); passphrase.

Returns:

EthWallet: instance of the created wallet.


3. load

Load an wallet.

# return: EthWallet
var wallet = wallet_manager.load()
Returns:

EthWallet: instance of the loaded wallet.


4. save

Save the wallet.

# hd_wallet: EthWallet
# return: bool
var success = wallet_manager.save(wallet)
Parameters:
  1. hd_wallet: EthWallet; The wallet to be saved.

Returns:

bool: true was successful, false was failed


Example

A complete example code for reference.

# Create a wallet manager
var wallet_manager = EthWalletManager.new()

# Create a new wallet with 1 account
var wallet = wallet_manager.create(1)

# Save the wallet
wallet_manager.save(wallet)

# Load the wallet later
var loaded_wallet = wallet_manager.load()

EthWallet

EthWallet is a class that implements hierarchical deterministic (HD) wallet functionality. It allows users to manage multiple Ethereum accounts and provides functions for adding, removing accounts, and handling mnemonic phrases.

Warning

The APIs provided by this class have not been audited and may have security vulnerabilities. Please take precautions, properly clear memory, securely store private keys, and thoroughly test transaction receiving and sending functions before using in production!

Note

Instances of EthWallet must be generated through EthWalletManager to ensure proper initialization and management of the wallet.

1. add

Adds a new Ethereum account to the wallet.

# privateKey: PackedByteArray (optional)
# return: bool
# Create a wallet manager
var wallet_manager = EthWalletManager.new()

var wallet = wallet_manager.create()

var success = wallet.add()   # Generate a new account
var success = wallet.add(private_key)  # Add an account with an existing private key
Parameters:
  1. privateKey: PackedByteArray (optional); The private key of the new account. If empty, a new account compliant with BIP32 will be generated.

Returns:

bool: true was successful, false was failed


2. remove_address

Removes an account by address.

# address: PackedByteArray
# return: bool
var success = wallet.remove_address(address)
Parameters:
  1. address: PackedByteArray; The address to be removed.

Returns:

bool: true was successful, false was failed


3. remove

Removes the account by index. .. code-block:: gdscript

# index: int # return: bool var success = wallet.remove(0) # Remove the first account

Parameters:
  1. index: uint64_t; The index of the account to be removed.

Returns:

bool: true was successful, false was failed


4. clear

Safely clears all wallet data, including accounts and mnemonic phrases.

# return: bool
var success = wallet.clear()
Returns:

bool: true was successful, false was failed


5. get_accounts

Gets all accounts in the wallet.

# return: Array[EthAccount]
var accounts = wallet.get_accounts()
Returns:

Array[EthAccount]: An array of Ethereum accounts.


6. get_mnemonic

Gets the wallet’s mnemonic phrase following the BIP39 protocol, which can be used for export to other BIP32 standard HD wallets.

# return: PackedStringArray
var mnemonic = wallet.get_mnemonic()
Returns:

PackedStringArray: The wallet mnemonic phrase.


7. save

Save Ethereum wallet.

# return: bool
var success = wallet.save()
Returns:

bool: true was successful, false was failed


8. load

Loads an Ethereum wallet.

# return: EthWallet
var wallet = wallet.load()
Returns:

EthWallet: The loaded EthWallet instance.


Example

A complete example code for reference.

var address_const = [
    "c75185ff30635988d4ae44ab544fc66130932568",
    "fce4b4e710f93dbbb219555f71a62b4cebcfa907",
    "f85ccde06eab0391d976efff4d9de02eca09c566",
    "fb30288ec7c57819547b9b0f3cdf3941cad66790",
    "dee895839eb69fe79336c76b20045f6ca2f37f6a"
]
    var ethMgr = EthWalletManager.new()
    var m = ["oil", "bamboo", "reject", "omit", "gentle", "boss", "useless", "fog", "genuine", "primary", "divorce", "abstract"]
    var wallet = ethMgr.from_mnemonic(m)
    var counts = 5
    for index in counts:
        wallet.add()

    var accounts = wallet.get_accounts()

    if accounts.size() > 0:
        for i in range(accounts.size()):
            var account = accounts[i]
            # Ensure the account is of type EthAccount
            if account is EthAccount:
                var eth_account = account as EthAccount  # Cast to EthAccount
                var address = eth_account.get_address()  # Call the method of EthAccount
                assert(address.hex_encode() == address_const[i])  # Check if it matches using the index
                print("Account Address:", address.hex_encode())
            else:
                print("Object at index", i, "is not of type EthAccount")
    else:
        print("No accounts found in the wallet.")

    pass

Secp256k1Wrapper

KeccakWrapper