Script Blockchain SRC20 Token Integration Guide
Introduction
Script latest release provides support for the Ethereum RPC API similar to Binance Smart Chain and Polygon. With it, Script now supports the entire Etherum DApp dev stack including Metamask, Hardhat, Remix, Ethers.js, Web3.js, and Truffle Suite. Ethereum DApps that are ported over to Script can use the same API calls to interact with Script blockchain. This means Ethereum DApps can be deployed to Script with no or minor modifications and tap into the growing Script user and capital base.
Hosted ETH RPC API for Integration Tests
To facilitate DApp development, we provide hosted ETH RPC API services for integration test purposes:
Script Testnet
ETH RPC URL: https://testeth-rpc-api.script.tv/rpc
Explorer: https://explorer.script.tv/
Chain ID: 742
Through these hosted API services, you can interact with the Script blockchain like you would with the Ethereum blockchain. Below are a few examples demonstrating querying the chain ID, synchronization status, block number, and SPAY balances against the Script Testnet.
Query Chain ID
curl -X POST -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":67}' https://testeth-rpc-api.script.tv/rpc
Query synchronization status
curl -X POST -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' https://testeth-rpc-api.script.tv/rpc
Query block number
curl -X POST -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}' https://testeth-rpc-api.script.tv/rpc
Query account SPAY balance (should return an integer which represents the current SPAY balance in wei)
curl -X POST -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xc15149236229bd13f0aec783a9cc8e8059fb28da", "latest"],"id":1}' https://testeth-rpc-api.script.tv/rpc
In House ETH RPC API Deployment
For production deployments, we highly recommend setting up your own in-house Script Ethereum RPC API, rather than using the above public endpoints. To enable the Script ETH RPC API service, you’d need to run two software on the same machine, namely
the Script Node, and
the Script/Ethereum RPC adapter. The adapter is a middleware which translates the Script native RPC APIs to the Ethereum RPC API.
SRC-20 Token
With the full-EVM compatibility comes the SRC20 token, which is an ERC-20 like token standard on the Script blockchain. SRC stands for Script Network Token.
Here is OGN, an SRC20 Token on the Script Testnet. Please click on the "Contract" tab see the verified Solidity source code: https://explorer.script.tv/account/0x7b8652a24a8c708c26c5f172e21590fdc5f941f3
Below is a Javascript code snippet which reads the total supply, name, symbol of the OGN token using web3.js. The code snippet should look familiar to an Ethereum developer, as it is exactly the same code for querying the corresponding attributes of an ERC20 token on Ethereum. These queries can also be done using ethers.js.
Example 1: Read SRC20 Token
const Web3 = require('web3')
const web3 = new Web3('https://testeth-rpc-api.script.tv/rpc')
const chainID = 742 // for the Script Testnet
const abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}] const address = '0x7b8652a24a8c708c26c5f172e21590fdc5f941f3' // The OGN SRC20 token (similar to ERC20) smart contract
const contract = new web3.eth.Contract(abi, address)
contract.methods.totalSupply().call((err, result) => { console.log("totalSupply:", result, "OGN (wei)") })
contract.methods.name().call((err, result) => { console.log("name:", result) })
contract.methods.symbol().call((err, result) => { console.log("symbol:", result) })
contract.methods.balanceOf('0x49706694aE934EF885b4391414003ff43786207a').call((err, result) => { console.log("balance of 0x49706694aE934EF885b4391414003ff43786207a:", result, "OGN (wei)") })
Transferring SRC20 is also similar to transferring ERC20 tokens. Moreover, same as an ERC20 token on Ethereum, whenever there is a SRC20 token transfer between two wallets, the Transfer event will be triggered. This could be useful for detecting SRC20 token deposits. Here is an SRC20 token transfer example on the Script explorer. Below we also provide a Javascript code snippet showing how to transfer the OGN token between two wallets:
Example 2: Transfer SRC20 Tokens
const Web3 = require('web3');
const web3 = new Web3('https://testeth-rpc-api.script.tv/rpc')
const chainID = 742 // for the Script Testnet
// Variables definition
const senderPrivKey = '1111111111111111111111111111111111111111111111111111111111111111'
const senderAddr = "0x49706694aE934EF885b4391414003ff43786207a"
const recipientAddr = "0x8285E6BdFeB1413CD3a1CF733cA567A1B826e947"
const ognContractAddress = "0x7b8652a24a8c708c26c5f172e21590fdc5f941f3"; const abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}] const contract = new web3.eth.Contract(abi, ognContractAddress)
// Create transaction const sendOGN = async(ognAmountInWei) => { console.log(Attempting to send ${ognAmountInWei} wei of OGN from ${senderAddr} to ${recipientAddr}
);
const count = await web3.eth.getTransactionCount(senderAddr); const createTransaction = await web3.eth.accounts.signTransaction({ "from": senderAddr, "nonce": web3.utils.toHex(count), "gas": web3.utils.toHex(150000), "to": ognContractAddress, "data": contract.methods.transfer(recipientAddr, ognAmountInWei).encodeABI() }, senderPrivKey );
// Deploy transaction const createReceipt = await web3.eth.sendSignedTransaction( createTransaction.rawTransaction );
console.log(""); console.log("Transaction successful with hash:", createReceipt.transactionHash); console.log(""); console.log("Transaction details:", JSON.stringify(createReceipt, null, " ")); };
const ognAmountInWei = "100" // Sending 100 wei of OGN (OGN has 18 decimals) sendOGN(ognAmountInWei)
The following example script shows how to list all the events emitted by the OGN contract in the specified block range (please avoid large block ranges, best below 200):
Example 3: List SRC20 Token Events
const Web3 = require('web3')
const web3 = new Web3('https://testeth-rpc-api.script.tv/rpc')
const chainID = 742 // for the Script Testnet
const ognContractAddress = '0x7b8652a24a8c708c26c5f172e21590fdc5f941f3' // The OGN SRC20 token (similar to ERC20) smart contract const abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}] const contract = new web3.eth.Contract(abi, ognContractAddress)
let fromBlock = 1721045 let toBlock = 1721045
// Get all the events emitted by the OGN contract in the specified block range contract.getPastEvents("allEvents", {
fromBlock: fromBlock,
toBlock: toBlock}
).then(console.log);
Metamask Support
End users can send and receive SRC20 tokens using Metamask.
Explorer Support
The Script explorer provides extensive details for SRC20 tokens and their transactions similar to EtherScan. Below are examples including a SRC20 token page, a transaction details page, and the SRC20 transaction list of an account:
SRC20 Token page https://explorer.script.tv/account/0x49706694aE934EF885b4391414003ff43786207a#Contract-Code
SRC20 Transaction detail page: https://explorer.script.tv/txs/0xdffb7e32e0281dbb1dcf08d6adb6ab3180d606beba09498db2afe7e2709966ee
SRC20 Token transactions of an account (please click on the “SRC20 Token Txns” tab): https://explorer.script.tv/account/0x49706694ae934ef885b4391414003ff43786207a
Smart Contract Transaction On-Chain Confirmation
The Script blockchain supports the ETH-style transactions (those sent through Metamask, Web3.js, Ethers.js, etc) by translating them into the native smart contract format. This means the ETH-style transactions have two valid transaction hashes. Here is an example. As shown on the explorer, it has two hashes. You can query the Script native RPC with either one to get the transaction details:
curl -X POST -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"script.GetTransaction","params":[{"hash":"0xbc09eef26b1b0d914f5d6bbf09e77320ee2968c631736776570ecc0daf83ac9e"}],"id":1}' http://localhost:16888/rpc
curl -X POST -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"script.GetTransaction","params":[{"hash":" 0x86671a7323192fd273bf40c7a277a00d26ecc9dd3ec939195398a180d6fa6c7b"}],"id":1}' http://localhost:16888/rpc
Moreover, the script.GetBlockByHeight and script.GetBlock RPC endpoint can return both hashes when its query parameter include_eth_tx_hashes is set to true. As the following example shows, the transaction details include both the hash and eth_tx_hash:
curl -X POST -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","method":"script.GetBlockByHeight","params":[{"height":"14158920", "include_eth_tx_hashes":true}],"id":1}' http://localhost:16888/rpc
The RPC API response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"chain_id": "scriptnet",
"epoch": "14244042",
"height": "14158920",
...,
"status": 4,
"transactions": [
...,
{
"raw": {...},
"type": 7,
"hash": "0xbc09eef26b1b0d914f5d6bbf09e77320ee2968c631736776570ecc0daf83ac9e",
"eth_tx_hash": "0x86671a7323192fd273bf40c7a277a00d26ecc9dd3ec939195398a180d6fa6c7b",
"receipt": {...}
}]
}
}
Last updated