Get Started with web3 Development Easily Based on Ethereum Using FMZ

Author: Lydia, Created: 2023-06-25 09:17:53, Updated: 2023-09-18 20:28:19

[TOC]

img

Get Started with web3 Development Easily Based on Ethereum Using FMZ

EtherEaseWithFMZ Tutorial

Get started with web3 development based on Ethereum using FMZ easily

Ethereum is a smart contract platform based on blockchain technology, which provides a decentralized way to write and deploy smart contracts. Smart contracts are a special type of computer program that can automatically execute on the blockchain and implement various business logic without the need for trusting third parties.

FMZ Quant Trading Platform (FMZ.COM) provides an easy-to-use API, allowing developers to interact more easily with the Ethereum blockchain and its ecosystem. It achieves access to decentralized exchanges (DEX), obtains on-chain data, sends transactions, and other functions.

In this tutorial, the examples are written in JavaScript language, the testing environment uses both Ethereum mainnet and Goerli testnet. And you can also view the API interfaces and related descriptions, code examples used in the tutorial in FMZ platform’s API documentation.


FMZ Getting Started

Before learning to use the FMZ Quant Trading Platform, we need to familiarize ourselves with a few basic concepts:

1. FMZ Quant Trading Platform Architecture

After registering and logging in on the FMZ Quant Trading Platform official website (https://www.fmz.com), you can use various functions of the platform. The FMZ website is the management end of the whole system, and user-written programs run on the docker actually. The docker software program can be deployed on various devices, such as servers, computers, etc. When a user writes a program and creates a running instance on the FMZ website, the FMZ platform will communicate with the docker and start a program instance on it.

2. Docker

If you want to run a program instance, you must deploy a docker. The deployment of the docker is also very simple, and there are deployment tutorials on the platform. You can also use the ‘One-click Deployment’ provided by FMZ to deploy automatically on servers rented on behalf of FMZ.

  • Deploy docker on personal devices

You can deploy and run the docker program on servers, personal computers and other devices, as long as the network is normal (need to be able to access the corresponding target, such as a certain exchange interface, node address, etc.). The main steps of deployment are:

  1. Log in or open the device where the docker program is to be deployed, such as logging into a server or turning on a computer to enter the operating system.
  2. Download the corresponding version of the docker program (depending on the device operating system), download page: https://www.fmz.com/m/add-node

img

  1. What you downloaded is a compressed package, need to decompress.
  2. Run the docker program, the docker program is an executable file called robot. Configure the docker communication address, which is unique to each FMZ account, after logging in to FMZ, you can view your own address at https://www.fmz.com/m/add-node page (i.e., ./robot -s node.fmz.com/xxxxx this string of addresses, where the content at xxxxx position is different for each FMZ account). Finally, you need to enter the password of your FMZ account. After configuring these settings, run the docker program.
  • Use FMZ platform’s “One-Click Deployment” function

    Add a docker page on the FMZ platform, address: https://www.fmz.com/m/add-node

    img

3. Debugging Tool

FMZ Quant Trading Platform provides a free debugging tool that supports JavaScript, TypeScript, and the page is: https://www.fmz.com/m/debug. Because creating instances to run is billed. During the initial learning period, you can use this debugging tool for testing and learning. Except for the maximum running time limit of 3 minutes, there is no difference between using the debugging tool and creating an instance to run.

When using the TypeScript language, you need to write // @ts-check on the first line of code to switch to TypeScript mode; if not switched, the default is JavaScript language.

4. Platforms

On FMZ, “Platform” is a general concept. For CEX exchanges, it refers to a specific exchange account configuration. For web3, this exchange refers to a configuration information that includes node address and private key configuration.

In the logged-in state of FMZ platform, at https://www.fmz.com/m/add-platform page, you can configure exchange information, where the exchange is a general concept.

img

Select Web3, configure RPC node address, configure private key, you can click the lower right corner “Sensitive information will be stored encrypted” to view the security mechanism.

Nodes can be self-built nodes or nodes provided by node service providers. There are many node service providers, such as: Infura. After registration, you can view the node address of your own account. Both mainnet and testnet are available, which is quite convenient. Configure this node address in the Rpc Address control shown in the above figure. The label can be named by yourself to distinguish between configured exchange objects.

img

In the picture, https://mainnet.infura.io/v3/xxxxxxxxxxxxx is the private Infura ETH mainnet RPC node address.


Interact with Ethereum by using FMZ

After deploying the docker program and configuring the exchange object, you can use FMZ.COM’s “Debugging Tool” for testing. Call Ethereum RPC methods and interact with Ethereum, in addition to the several RPC methods listed and introduced in this chapter, other RPC methods can be found by consulting materials, such as https://www.quicknode.com/docs.

We list a few simple examples, starting from the basics. For various languages ​​and tools, there are ways to access web3, as shown in the picture:

img

On FMZ, RPC method calls are also encapsulated, and these functions are encapsulated in the FMZ API function exchange.IO. The calling method is exchange.IO("api", "eth", ...). The first parameter is fixed to "api", the second parameter is fixed to "eth", and other parameters depend on the specific RPC method being called.

For output information, we will use the Log function of the FMZ platform. The Log function can accept multiple parameters and then output them in the log area of ​​the “Debug Tool” or “Bot” page on the FMZ platform. The “Debug Tool” page will be our main testing tool.

eth_getBalance

The eth_getBalance method of Ethereum is used to query the ETH balance of an address on Ethereum, and this method requires two parameters.

  • Address to be queried.
  • Label, we usually use “latest”. Let’s check the Ethereum founder Vitalik Buterin's ETH wallet address, the known address is: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045.
function main() {
    let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest")
    Log("ethBalance:", ethBalance)
}

Already deployed the docker (in the picture: linux/amd64 …) and configured the exchange object (in the picture: Web3 test), testing code in debugging tool:

img

Click the “Execute”’ button to run the code and display the results:

ethBalance: 0x117296558f185bbc4c6

The log function prints out the ethBalance variable value as: 0x117296558f185bbc4c6, which is a string type. It is the hexadecimal value of the ETH balance in wei units, with 1e18 wei being equal to 1ETH. Therefore, it needs to be converted to become a readable decimal ETH balance.

Converting ethBalance into readable data:

function main() {
    let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest")
    Log("ethBalance:", ethBalance)
    
    // Converting ethBalance into readable data
    let vitalikEthBalance = parseInt(ethBalance.substring(2), 16) / 1e18
    Log("vitalikEthBalance:", vitalikEthBalance)
}

Search on https://etherscan.io/:

img

However, due to the precision problem of the language itself, there will be deviations in this way of processing. Therefore, FMZ platform has built-in two functions for data processing:

  • BigInt : Convert hexadecimal string to BigInt object.
  • BigDecimal : Convert numeric type objects into computable BigDecimal objects.

Adjust the code again:

function main() {
    let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest")

    // The precision unit of ETH is 1e18
    let ethDecimal = 18
    Log("vitalikEthBalance:", Number((BigDecimal(BigInt(ethBalance)) / BigDecimal(Math.pow(10, ethDecimal))).toString()))
}

vitalikEthBalance: 5149.6244846875215

eth_chainId

eth_chainId and net_version have similar purposes, so they are tested together. Both of these functions return the Id of the blockchain that the current RPC node is connected to, with the difference being that net_version returns a decimal Id and eth_chainId returns a hexadecimal Id.

Network name corresponding to chainId

1 - ethereum mainnet
2 - morden testnet (deprecated)
3 - ropsten testnet
4 - rinkeby testnet
5 - goerli testnet
11155111 - sepolia testnet
10 - optimism mainnet
69 - optimism kovan testnet
42 - kovan testnet
137 - matic/polygon mainnet
80001 - matic/polygon mumbai testnet
250 - fantom mainnet
100 - xdai mainnet
56 - bsc mainnet

Test with the configured Ethereum testnet goerli node:

function main() {
    let netVersionId = exchange.IO("api", "eth", "net_version")
    let ethChainId = exchange.IO("api", "eth", "eth_chainId")

    Log("netVersionId:", netVersionId)
    Log("ethChainId:", ethChainId, " , conversion:", parseInt(ethChainId.substring(2), 16))
}

eth_gasPrice

Call the eth_gasPrice method to query the current gas price on the chain.

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function main() {
    let gasPrice = exchange.IO("api", "eth", "eth_gasPrice")
    Log("gasPrice:", gasPrice, " , conversion:", toAmount(gasPrice, 0))
}

Here we write a function to convert the hexadecimal string into a readable numerical value: toAmount. In addition, note that the unit of gasPrice is wei, so pass the value 0 to the real parameter corresponding to the formal parameter decimals.

eth_blockNumber

"eth_blockNumber is used to query the block height.

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function main() {
    let blockNumber = exchange.IO("api", "eth", "eth_blockNumber")
    Log(toAmount(blockNumber, 0))
}

Run in debugging tool:

img

Search on https://etherscan.io/:

img

eth_getBlockByNumber

Query block information.

function main() {
    let blockNumber = exchange.IO("api", "eth", "eth_blockNumber")    
    Log(blockNumber)
    let blockMsg = exchange.IO("api", "eth", "eth_getBlockByNumber", blockNumber, true)
    Log(typeof(blockMsg), blockMsg)
    
    // Due to the excessive content of Log output, it will be truncated automatically, so traverse each field of the returned block information and print them one by one
    for (let key in blockMsg) {
        Log("key:", key, ", val:", blockMsg[key])
    }
}

Executing in the “Debugging Tool” can obtain the following information:

img

Read contract information

Many smart contract applications run on Ethereum, and ENS is one of them. ENS, or Ethereum Name Service, is a decentralized domain name resolution service based on the Ethereum blockchain. Do you remember the example in the tutorial where we checked the balance of Ethereum founder Vitalik Buterin’s wallet? One of Vitalik Buterin’s wallet addresses is: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045. So how do we know the address? In fact, it can be queried through the ENS smart contract using an intuitive name vitalik.eth.

The following content in this chapter uses Ethereum mainnet environment, according to the ENS documentation, Hashing Names are required for querying Ethereum domain names. Use the following code to process vitalik.eth.

function nameHash(name) {
    if (name == "") {
        return "0000000000000000000000000000000000000000000000000000000000000000"
    } else {
        let arr = name.split(".")
        let label = arr[0]
        
        arr.shift()
        let remainder = arr.join(".")
        return Encode("sha3.keccak256", "hex", "hex", nameHash(remainder) + Encode("sha3.keccak256", "raw", "hex", label))
    }
}

In the above code example, we saw another unfamiliar function Encode. This function is an API function of the FMZ platform and is specifically used for encoding operations on the FMZ platform. The function supports multiple encoding methods and various hash algorithms.

Encode(algo, inputFormat, outputFormat, data, keyFormat, key string)

According to the description in the ENS document, use the sha3.keccak256 algorithm to process data.

Call the nameHash function, for example: Log(nameHash("vitalik.eth")), you can get: ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835, and you need to add the “0x” prefix. 0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835 is used as the parameter of the resolver method in ENS smart contract.

let ensNode = "0x" + nameHash("vitalik.eth")    // Prepare the parameters ensNode for calling the resolver method

According to the ENS documentation, the contract address for ENS smart contract applications is: 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e. Before calling the resolver method of the smart contract, we also need to prepare the ABI of the contract.

Register ABI

Upon learning this, you may ask: what is the ABI of a smart contract?

ABI, or Application Binary Interface, is the interface standard for smart contracts to communicate with the external world.
The ABI of a smart contract defines the contract's function interfaces, parameter types, return values, and other information, as well as specifications for calling the contract and passing parameters.

The ABI of a smart contract is usually stored in JSON format and contains the following information:

Contract function interfaces: function names, parameter lists, return values, etc.
Function parameter types: such as uint256, bool, string etc.
Encoding methods for input and output parameters of functions: Smart contracts use an encoding method called Solidity ABI to encode input and output parameters of functions so that they can interact with Ethereum network.
In Ethereum network ,the ABI of a smart contract is used to call its functions. When you need to call a contract function, you need to provide the name of the function, its parameters, and bytecode encoded according to ABI encoding method.
Ethereum nodes will package this information into transactions and send them out on Ethereum network for execution.

In Solidity language,the keyword 'interface' can be used define ABIs for smart contracts. Ethereum development tools like Remix IDE ,Truffle also provide editing & generation tools making it easier developers create & use ABIs.

Extract the resolver method part from ENS’s ABI, or you can use the complete ABI. You can query the contract’s ABI on https://etherscan.io/ or obtain the ABI through other channels (e.g., relevant project documentation).

img

let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]`

Here we are going to learn a new invocation method on the FMZ platform, exchange.IO("abi", address, abiContent), which is used to register ABI. The address parameter is the smart contract address and the abiContent parameter is the corresponding smart contract ABI (string).

let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]`
exchange.IO("abi", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", abiENS_resolver)  // 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e is the address of the ENS smart contract deployed on the Ethereum mainnet

Methods for Calling Smart Contracts

Next, you can call the resolver method of the ENS smart contract, which returns the address of the ENS: Public Resolver contract.

img

let resolverAddress = exchange.IO("api", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "resolver", ensNode)

Use the ENS: Public Resolver contract’s addr method to obtain Vitalik Buterin’s wallet address. To call the ENS: Public Resolver contract, you still need to register the ABI first. The ABI information for this smart contract can still be obtained from https://etherscan.io/.

let abiENSPublicResolver = `[{"inputs":[{"internalType":"contract ENS","name":"_ens","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"coinType","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newAddress","type":"bytes"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"AuthorisationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"},{"indexed":false,"internalType":"bytes","name":"record","type":"bytes"}],"name":"DNSRecordChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"}],"name":"DNSRecordDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"DNSZoneCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"indexed":false,"internalType":"address","name":"implementer","type":"address"}],"name":"InterfaceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"string","name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"x","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"string","name":"indexedKey","type":"string"},{"indexed":false,"internalType":"string","name":"key","type":"string"}],"name":"TextChanged","type":"event"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"addr","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"}],"name":"addr","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"authorisations","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"clearDNSZone","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"contenthash","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint16","name":"resource","type":"uint16"}],"name":"dnsRecord","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"}],"name":"hasDNSRecords","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"interfaceImplementer","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentType","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"},{"internalType":"bytes","name":"a","type":"bytes"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"a","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"setAuthorisation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"hash","type":"bytes"}],"name":"setContenthash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setDNSRecords","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"internalType":"address","name":"implementer","type":"address"}],"name":"setInterface","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"},{"internalType":"string","name":"value","type":"string"}],"name":"setText","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"}],"name":"text","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]`
exchange.IO("abi", resolverAddress, abiENSPublicResolver)

img

Finally, call the ENS: Public Resolver contract’s addr method, with the parameter still being ensNode.

let vitalikAddress = exchange.IO("api", resolverAddress, "addr", ensNode)
Log("vitalikAddress:", vitalikAddress)

Log function output:

vitalikAddress: 0xd8da6bf26964af9d7eed9e03e53415d37aa96045

Call the Complete Code of ENS

function nameHash(name) {
    if (name == "") {
        return "0000000000000000000000000000000000000000000000000000000000000000"
    } else {
        let arr = name.split(".")
        let label = arr[0]
        
        arr.shift()
        let remainder = arr.join(".")
        return Encode("sha3.keccak256", "hex", "hex", nameHash(remainder) + Encode("sha3.keccak256", "raw", "hex", label))
    }
}

function main() {
    // Calculate the name
    let ensNode = "0x" + nameHash("vitalik.eth")    

    // Register ENS contract
    let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]`
    exchange.IO("abi", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", abiENS_resolver)
    let resolverAddress = exchange.IO("api", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "resolver", ensNode)
    
    // Register ENS Public Resolver contract
    let abiENSPublicResolver = `[{"inputs":[{"internalType":"contract ENS","name":"_ens","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"coinType","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newAddress","type":"bytes"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"AuthorisationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"},{"indexed":false,"internalType":"bytes","name":"record","type":"bytes"}],"name":"DNSRecordChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"}],"name":"DNSRecordDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"DNSZoneCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"indexed":false,"internalType":"address","name":"implementer","type":"address"}],"name":"InterfaceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"string","name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"x","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"string","name":"indexedKey","type":"string"},{"indexed":false,"internalType":"string","name":"key","type":"string"}],"name":"TextChanged","type":"event"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"addr","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"}],"name":"addr","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"authorisations","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"clearDNSZone","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"contenthash","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint16","name":"resource","type":"uint16"}],"name":"dnsRecord","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"}],"name":"hasDNSRecords","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"interfaceImplementer","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentType","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"},{"internalType":"bytes","name":"a","type":"bytes"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"a","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"setAuthorisation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"hash","type":"bytes"}],"name":"setContenthash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setDNSRecords","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"internalType":"address","name":"implementer","type":"address"}],"name":"setInterface","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"},{"internalType":"string","name":"value","type":"string"}],"name":"setText","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"}],"name":"text","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]`
    exchange.IO("abi", resolverAddress, abiENSPublicResolver)
    let vitalikAddress = exchange.IO("api", resolverAddress, "addr", ensNode)
    Log("vitalikAddress:", vitalikAddress)
}

Send ETH

In the previous course chapters, we have learned how to configure private keys. How do we know the wallet address corresponding to this private key for the configured exchange object? On FMZ, you can use the exchange.IO("address") function to obtain the wallet address corresponding to the configured private key.

The following content in this chapter uses Goerli testnet environment, so the node I am using is: https://goerli.infura.io/v3/*******, and Infura assigns different node addresses for each registered user. Here the ******* hides specific content.

function main() {
    let walletAddress = exchange.IO("address")
    Log("Testnet goerli wallet address:", walletAddress)
}

After knowing your wallet address, you can use Ethereum’s RPC method eth_getTransactionCount to query the transaction count of the wallet address. In Ethereum, this count is very common, and it’s actually the nonce parameter that needs to be passed in during transfer operations. In Ethereum, nonce is a unique number used to ensure that each transaction is unique. It is an increasing number, and it will automatically increase every time a new transaction is sent. Therefore, when you send a transaction to a smart contract, you need to provide a nonce to ensure that the transaction is unique and in the correct order. We can find this information in some materials and documents:

https://goethereumbook.org/en/

img

Here, the PendingNonceAt function in the Ethereum library of Go language is actually calling the eth_getTransactionCount method. In previous courses, we have also learned how to call RPC methods. Here we use the exchange.IO("api", "eth", ...) function again.

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function main() {
    let walletAddress = exchange.IO("address")
    Log("Testnet goerli wallet address:", walletAddress)

    /**
    * eth_getTransactionCount
    * @param address - string - The address from which the transaction count to be checked.
    * @param blockNumber - string - The block number as a string in hexadecimal format or tags.
    * @returns The integer of the number of transactions sent from an address encoded as hexadecimal.
    */
    let nonce: string = exchange.IO("api", "eth", "eth_getTransactionCount", walletAddress, "pending")
    Log("wallet address:", walletAddress, "current nonce:", nonce, ", convert to decimal:", toAmount(nonce, 0))
}

Before explaining the transfer operation, let’s briefly understand some concepts. When transferring on Ethereum, a certain amount of ETH tokens will be consumed (as gas fees). The gas fee is determined by two parameters:

  • gasPrice

    However, the gas fees on the Ethereum network always fluctuate according to market demand and the fees users are willing to pay, so writing a fixed gas fee in the code is sometimes not an ideal choice. We can use the eth_gasPrice method we learned before, which can obtain the average gas price.

  • gasLimit

    A standard Ether transfer has a gas limit of 21,000 units.

After understanding the concepts of nonce, gasPrice, and gasLimit, you can test the transfer. FMZ provides a very simple and easy-to-use transfer function.

exchange.IO("api", "eth", "send", toAddress, toAmount)

When it’s used for transfers, the third parameter of exchange.IO is fixed as “send”, the toAddress parameter is the address receiving ETH during the transfer, and toAmount is the amount of ETH transferred.

The parameters nonce, gasPrice, and gasLimit can all use system default values automatically obtained on FMZ. They can also be specified:

exchange.IO("api", "eth", "send", toAddress, toAmount, {gasPrice: 5000000000, gasLimit: 21000, nonce: 100})

Next, we will transfer a certain amount of ETH to a specific address on the goerli test network:

function toInnerAmount(s, decimals) {
    return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function main() {
    let walletAddress = exchange.IO("address")
    Log("Testnet goerli wallet address:", walletAddress)

    let ret = exchange.IO("api", "eth", "send", "0x4D75a08E870674E68cAE611f329A27f446A66813", toInnerAmount(0.01, 18))
    return ret // return Transaction Hash : 0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e
}

Because the unit of Ethereum transfer amount is wei, a custom function toInnerAmount needs to be used to process the value in wei units.

Query Transaction Hash: 0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e on https://etherscan.io/.

img

You can also write code to query transfer hash 0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e, using the eth_getTransactionReceipt method for queries.

function main() {
    let transHash = "0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e"
    let info = exchange.IO("api", "eth", "eth_getTransactionReceipt", transHash)
    return info
}

Query result:

{
	"cumulativeGasUsed": "0x200850",
	"effectiveGasPrice": "0x1748774421",
	"transactionHash": "0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e",
	"type": "0x0",
	"blockHash": "0x6bdde8b0f0453ecd24eecf7c634d65306f05511e0e8f09f9ed3f59eee2d06ac7",
	"contractAddress": null,
	"blockNumber": "0x868a50",
	"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
	"gasUsed": "0x5208",
	"to": "0x4d75a08e870674e68cae611f329a27f446a66813",
	"status": "0x1",
	"transactionIndex": "0x23",
	"from": "0x6b3f11d807809b0b1e5e3243df04a280d9f94bf4",
	"logs": []
}

Description corresponding to each field:

blockHash - The hash value of the block where the transaction is located.
blockNumber - The block number of the block where the transaction is located, encoded in hexadecimal.
contractAddress - If it's a contract creation, the address of the contract; otherwise null.
cumulativeGasUsed - The total gas used when executing this transaction in the block.
effectiveGasPrice - Total base fee plus tip per unit of gas.
from - Sender's address.
gasUsed - Gas used by this specific transaction.
logs - Array of log objects generated by this transaction.
  address - Address that generated this log.
  topics - Data array with 0 to 4 indexed log parameters, each with 32 bytes. In Solidity, first topic is event signature hash (e.g., Deposit(address,bytes32,uint256)), unless you declare an event using anonymous specifier.
  data - Non-indexed parameters for logs with length of 32 bytes.
  blockNumber - The block number of the block where this log is located.
  transactionHash - Transaction hash at time when log was created. Null if pending state.
  transactionIndex - Index position during creation. Null if pending state.
  blockHash - The hash value for containing block.
  logIndex - Hexadecimal-encoded integer index position within containing block. Null if pending state.
  removed - True if deleted due to chain reorganization; false for valid logs.
logsBloom - Bloom filter for retrieving related logs.
status - Hexadecimal-encoded value either being '1' (success) or '0' (failure).
to - Receiving party's address; null for contract creation transactions.
transactionHash - The hash value associated with given transaction.
transactionIndex - Hexadecimal-encoded index position within its respective containing-block.
type - Type value.

Call Ethereum Smart Contract

In the chapter on “Reading Contract Information”, we used a complete example to call the method of ENS contract deployed on Ethereum to obtain Vitalik Buterin’s wallet address. These methods belong to Read methods, and calling these methods does not require gas (remember what we talked about gas before?). In this chapter, we will call some Write methods of smart contracts on Ethereum and pay for gas. These operations will be verified by each node and miner in the entire network and change the blockchain state.

ERC20

For the ERC20 contract (ERC20 token contract), FMZ platform lists the ABI of the ERC20 contract ABI as a common ABI built directly into the system, eliminating the step of registering the ABI. We have also learned about ABI in previous tutorials when we registered ENS contract’s ABI before calling ENS contract methods.

To better understand ABI, you can check it out before using it. Here is the ABI for ERC20 contracts:

[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"tran

Related

More