Who is this tutorial for:

  • API users that want to interact to an EVM compatible chain that TrustVault does not support natively
  • API users that want to send native currency transactions to an EVM compatible chain that TrustVault does not support natively
  • API users that want to invoke smart contract methods (including ERC-20 transfers) to an EVM compatible chain that TrustVault does not support natively

Brief Outline

Supported EVM Compatible Chains

Support Chains has the benefit of:

  • The balance and transactions of an addresses in the chain can be viewed in the iOS TrustVault app and TrustVault Web
  • The ability to construct a transaction for the chain in the iOS TrustVault app and TrustVault Web
  • The API supports creating a transaction for a supported asset in the chain by passing just the assetSymbol (e.g. LINK)
  • Webhook users can get a webhook when ERC-20s are received in the chain with full ERC-20 data payload including a valuation

Unsupported EVM Compatible Chains

All EVM compatible chains are unsupported by default which means:

  • They can only be interacted via TrustVault API or MetaMask x TrustVault chrome extension

In order to interact via TrustVault API with an EVM compatible chain that TrustVault does not support natively there are a few steps that needs to be done manually (these are done automatically for natively supported chains). The steps are:

  1. Decide the EVM compatible chain you want to interact with and get the chain information
    1. see EVM chain info (RPC URL and Chain ID) section
  2. Decide if you are sending a native currency (i.e. MATIC on Polygon) or invoking a smart contract (including ERC-20 transfers) and follow the correct instructions:
    1. Sending native currency transaction
    2. Invoking a smart contract method
  3. Construct the raw transaction by calculating the transaction details
  4. Submit the raw transaction to TrustVault setting the sendToNetworkWhenSigned: false (the signed transaction must be sent to network manually)
  5. Workflow / Sign the transaction as normal using your iOS device
  6. Poll the transaction request for the correct state
  7. Grab the rawTransactionBytes and submit to network

Sending native currency transaction (EVM compatible chain)

To send a native currency transaction to an EVM compatible chain that TrustVault does not support natively:

  1. Create a transaction using the steps in Native Currency Transaction Mutation
    1. Take note of the requestId from the response (required for step 2 and 3)
  2. Add signatures using the TrustVault IOS app or Add Signature Mutation ( requestId required) for externally held device keys.
  3. Poll for the rawTransactionBytes using the steps in Get rawTransactionBytes of the transaction request
  4. Broadcast the rawTransactionBytes to the EVM compatible chain using the steps in Broadcast the rawTransactionBytes

Invoking a smart contract method (EVM compatible chain)

When sending a smart contract transaction we can invoke any method available in the ABI of a smart contract. For transferring ERC-20 use the transfer method of the ERC-20 smart contract.

See the steps in Generate data field on how to select the available methods of a smart contract.

To invoke a smart contract method in an EVM compatible chain that TrustVault does not support natively:

  1. Create a transaction using the steps in Smart contract transaction mutation
    1. Take note of the requestId from the response (required for step 2 and 3)
  2. Add signatures using the TrustVault IOS app or Add Signature Mutation ( requestId required) for externally held device keys.
  3. Poll for the rawTransactionBytes using the steps in Get rawTransactionBytes of the transaction request
  4. Broadcast the rawTransactionBytes to the EVM compatible chain using the steps in Broadcast the rawTransactionBytes

Native Currency Transaction Mutation

The following fields are needed in order to send a native currency transaction to an EVM compatible chain that TrustVault does not support natively:

Headers:

Copy
Copied
x-api-key: <YOUR_API_KEY>
Content-Type: application/json

Request:

Copy
Copied
mutation (
  $from: String!
  $to: String!
  $value: String!
  $gasPrice: String
  $gasLimit: String
  $nonce: Int
  $chainId: Int
) {
  createEthereumTransaction(
    createTransactionInput: {
      ethereumTransaction: {
        fromAddress: $from
        to: $to
        value: $value
        gasPrice: $gasPrice
        gasLimit: $gasLimit
        nonce: $nonce
        chainId: $chainId
      }
      source: "API"
      sendToNetworkWhenSigned: false
    }
  ) {
    ... on CreateEthereumTransactionResponse {
      requestId
    }
    signData {
      transaction {
        fromAddress
        to
        value
        gasPrice
        gasLimit
        nonce
        chainId
        data
      }
      hdWalletPath {
        hdWalletPurpose
        hdWalletCoinType
        hdWalletAccount
        hdWalletUsage
        hdWalletAddressIndex
      }
      unverifiedDigestData {
        transactionDigest
        signData
        shaSignData
      }
    }
  }
}

Variables:

Copy
Copied
{
 "from": "<FROM>",
 "to":"<TO>",
 "value": "<VALUE>",
 "gasPrice": "<GAS_PRICE>",
 "gasLimit": "<GAS_LIMIT>",
 "nonce": <NONCE>,
 "chainId": <CHAIN_ID>
}

Response:

Copy
Copied
{
    "data": {
        "createEthereumTransaction": {
            "requestId": "93b99bf0-22a4-adcf-cef7-06c1b23409c4",
            "signData": {
                "transaction": {
                    "fromAddress": "0xB96966D32f4654b823eaa3844EB381932c04C18D",
                    "to": "0x61Df7eAb4f740AFCeB8e468cb16d323f262e3970",
                    "value": "100000000000000",
                    "gasPrice": "30000000000",
                    "gasLimit": "21000",
                    "nonce": 0,
                    "chainId": 137,
                    "data": null
                },
                "hdWalletPath": {
                    "hdWalletPurpose": "0x80000044",
                    "hdWalletCoinType": "0x80000060",
                    "hdWalletAccount": "0x80000000",
                    "hdWalletUsage": "0x0",
                    "hdWalletAddressIndex": "0x0"
                },
                "unverifiedDigestData": {
                    "transactionDigest": "733a5d51394fef258dc2245c928ad92d82c3eee530cb4615e8a71d4ea2eafb28",
                    "signData": "303f0420733a5d51394fef258dc2245c928ad92d82c3eee530cb4615e8a71d4ea2eafb28301b020500800000440205008000006002050080000000020100020100",
                    "shaSignData": "d7d20a63d9353f3fd967803ed26ce6774432a852745c7f935ccb75e37f6c7409"
                }
            }
        }
    }
}

Smart contract transaction mutation

The following fields are needed in order to invoke a method of a smart contract in an EVM compatible chain that TrustVault does not support natively:

Headers:

Copy
Copied
x-api-key: <YOUR_API_KEY>
Content-Type: application/json

Request:

Copy
Copied
mutation (
  $from: String!
  $contractAddress: String!
  $gasPrice: String
  $gasLimit: String
  $nonce: Int
  $chainId: Int
  $data: String
) {
  createEthereumTransaction(
    createTransactionInput: {
      ethereumTransaction: {
        fromAddress: $from
        to: $contractAddress
        value: "0"
        gasPrice: $gasPrice
        gasLimit: $gasLimit
        nonce: $nonce
        chainId: $chainId
        data: $data
      }
      source: "API"
      sendToNetworkWhenSigned: false
    }
  ) {
    ... on CreateEthereumTransactionResponse {
      requestId
    }
    signData {
      transaction {
        fromAddress
        to
        value
        gasPrice
        gasLimit
        nonce
        chainId
        data
      }
      hdWalletPath {
        hdWalletPurpose
        hdWalletCoinType
        hdWalletAccount
        hdWalletUsage
        hdWalletAddressIndex
      }
      unverifiedDigestData {
        transactionDigest
        signData
        shaSignData
      }
    }
  }
}

Variables:

Copy
Copied
{
 "from": "<FROM>",
 "contractAddress":"<CONTRACT_ADDRESS>",
 "gasPrice": "<GAS_PRICE>",
 "gasLimit": "<GAS_LIMIT>",
 "nonce": <NONCE>,
 "chainId": <CHAIN_ID>,
 "data": "<DATA>"
}

Response:

Copy
Copied
{
    "data": {
        "createEthereumTransaction": {
            "requestId": "be970f58-6fec-f0f6-5880-c68342f5e768",
            "signData": {
                "transaction": {
                    "fromAddress": "0xB96966D32f4654b823eaa3844EB381932c04C18D",
                    "to": "0x61Df7eAb4f740AFCeB8e468cb16d323f262e3970",
                    "value": "0",
                    "gasPrice": "30000000000",
                    "gasLimit": "51473",
                    "nonce": 0,
                    "chainId": 137,
                    "data": "0xa9059cbb000000000000000000000000528880b3eb9b5c6ba9cf5215a777ed3d983c6c9e00000000000000000000000000000000000000000000000000005af3107a4000"
                },
                "hdWalletPath": {
                    "hdWalletPurpose": "0x80000044",
                    "hdWalletCoinType": "0x80000060",
                    "hdWalletAccount": "0x80000000",
                    "hdWalletUsage": "0x0",
                    "hdWalletAddressIndex": "0x0"
                },
                "unverifiedDigestData": {
                    "transactionDigest": "e57f9e8b6721571c1b4860a952c26a3a5c63a8016b725117bdf635767c639eb1",
                    "signData": "303f0420e57f9e8b6721571c1b4860a952c26a3a5c63a8016b725117bdf635767c639eb1301b020500800000440205008000006002050080000000020100020100",
                    "shaSignData": "0625e966997ca5741d838a4eb3ab59afe94769f094f4e46359a6f07645952872"
                }
            }
        }
    }
}

EVM chain info (RPC URL and Chain ID)

RPC URL - this value is needed to get necessary information from the EVM compatible chain. Chain ID - chain identifier where this transaction is going to be sent to

Please refer to rpc.info for the RPC URL and Chain ID of a particular EVM compatible chain

Get value

The value of the native currency (i.e. MATIC on Polygon) sent with this transaction (integer string in wei units)

Use this ethereum unit converter to convert the Ether value (i.e native currency) to wei units.

Get gasPrice

The price per unit of gas in wei units (integer string). The eth_gasPrice JSON RPC method will be used to grab the gasLimit for the transaction.

Request:

CHAINRPCURL - EVM chain info (RPC URL and Chain ID)

Copy
Copied
curl --location --request POST '<CHAIN_RPC_URL>' \
--header 'Content-Type: application/json' \
--data-raw '{
 "jsonrpc":"2.0",
 "method":"eth_gasPrice",
 "params":[],
 "id":73
}'

Response:

Copy
Copied
{"jsonrpc":"2.0","id":73,"result":"0x70295f4f0"}

convert the hex result "0x70295f4f0" to integer string

Copy
Copied
const gasPrice = parseInt("0x70295f4f0").toString(); // "30108153072"

Get gasLimit

The maximum units of gas the transaction is allowed to use (integer string). The eth_estimateGas JSON RPC method will be used to grab the gasLimit for the transaction.

Request:

CHAINRPCURL - EVM chain info (RPC URL and Chain ID) TRANSACTIONTOFIELD - for native currency transactions this is the recipient address, for smart contract invocations (including ERC-20 transfers) this is the smart contract address

Copy
Copied
curl --location --request POST '<CHAIN_RPC_URL>' \
--header 'Content-Type: application/json' \
--data-raw '{
 "jsonrpc":"2.0",
 "method":"eth_estimateGas",
 "params":[{"to": "<TRANSACTION_TO_FIELD>"}],
 "id":1
}'

Response:

Copy
Copied
{"jsonrpc":"2.0","id":1,"result":"0x5208"}

convert the hex result "0x5208" to integer string

Copy
Copied
const gasLimit = parseInt("0x5208").toString(); // "21000"

Get nonce

The number of transactions made by the sender prior to this one (integer). The eth_getTransactionCount JSON RPC method will be used to grab the current nonce value.

Request:

CHAINRPCURL - EVM chain info (RPC URL and Chain ID) TRANSACTIONFROMFIELD - the Ethereum TrustVault address where the transaction will come from

Copy
Copied
curl --location --request POST '<CHAIN_RPC_URL>' \
--header 'Content-Type: application/json' \
--data-raw '{
  "jsonrpc":"2.0",
  "method":"eth_getTransactionCount",
  "params":[
    "<TRANSACTION_FROM_FIELD>",
    "latest"
  ],
  "id":1
}'

Response:

Copy
Copied
{"jsonrpc":"2.0","id":1,"result":"0x0"}

convert the result to an integer

Copy
Copied
const nonce = parseInt("0x0"); // 0

Generate data field

The data field is optional and only required if you want to invoke methods of a smart contract (this includes ERC-20 transfers).

Follow this guide to get the ABI of the smart contract you want to invoke. Please check the ABI of the method you want to invoke and the parameters it accepts.

The example below calls the transfer method which takes in 2 arguments:

  • address - type address
  • value - type uint256
Copy
Copied
const web3EthAbi = require("web3-eth-abi");

const encode = (contractAbi, functionName, functionArgs) => {
  for (const methodAbi of contractAbi) {
    if (methodAbi.name === functionName) {
      return web3EthAbi.encodeFunctionCall(methodAbi, functionArgs);
    }
  }
  throw new Error(`function ${functionName} does not exist`);
}

const abi = [...] // contract ABI
const methodName = "transfer";
const methodArgs = [
  "0x528880b3Eb9B5C6bA9Cf5215A777ED3D983C6C9e", // recipient address
  "100000000000000", // value to be transferred to the recipient address (in wei) - see Get Value section
];
const data = encode(abi, methodName, methodArgs); // 0xa9059cbb000000000000000000000000528880b3eb9b5c6ba9cf5215a777ed3d983c6c9e00000000000000000000000000000000000000000000000000005af3107a4000

This is an example ABI of the WETH contract in Polygon: WETH ABI

Get rawTransactionBytes of the transaction request

The rawTransactionBytes will be only be populated once the transaction request have enough signature to satisfy the wallet policy, otherwise it will be null.

Headers:

Copy
Copied
x-api-key: <YOUR_API_KEY>
Content-Type: application/json

Request:

Copy
Copied
query ($requestId: String!) {
  getRequest(requestId: $requestId) {
    requestId
    status
    type
    rawTransactionBytes
  }
}

Variables:

REQUEST_ID - the requestId of the transaction request from either Native Currency Transaction Mutation or Smart contract transaction mutation response

Copy
Copied
{
    "requestId": "<REQUEST_ID>"
}

Response:

Copy
Copied
{
    "data": {
        "getRequest": {
            "requestId": "93b99bf0-22a4-adcf-cef7-06c1b23409c4",
            "status": "SIGNED",
            "type": "EXTERNAL_ETH_TRANSACTION",
            "rawTransactionBytes": "0xf8ae82027985400746fe00830493e094c7dad2e953dc7b11474151134737a007049f576e80b844e2bbb158000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000003dff70c63f22be43820217a03ef69f68465b19274f2fa71636b053f7daf7bcdde7b3f9e18662937231f10417a02b0ae881ad04fc9d006c173e86f9cb1b226c95a90c0a2f921a249b25e1574c0b"
        }
    }
}

Broadcast the rawTransactionBytes

Once the transaction request has collected enough signatures to satisfy the policy the rawTransactionBytes will be populated and can now be broadcasted to the EVM compatible network.

The eth_sendRawTransaction JSON RPC method will used to broadcast the rawTransactionBytes.

Request:

CHAINRPCURL - EVM chain info (RPC URL and Chain ID) RAWTRANSACTIONBYTES - Get rawTransactionBytes of the transaction request

Copy
Copied
curl --location --request POST '<CHAIN_RPC_URL>' \
--header 'Content-Type: application/json' \
--data-raw '{
 "jsonrpc":"2.0",
 "method":"eth_sendRawTransaction",
 "params":["<RAW_TRANSACTION_BYTES>"],
 "id":1
}'

Response:

Copy
Copied
{
  "id":1,
  "jsonrpc": "2.0",
  "result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
}

The response.result is the transactionHash which you can query on the EVM compatible chain blockchain explorer.