Create transactions to an EVM compatible chain that TrustVault does not support natively

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:

1
2
x-api-key: <YOUR_API_KEY>
Content-Type: application/json

Request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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:

1
2
3
4
5
6
7
8
9
{
"from": "<FROM>",
"to":"<TO>",
"value": "<VALUE>",
"gasPrice": "<GAS_PRICE>",
"gasLimit": "<GAS_LIMIT>",
"nonce": <NONCE>,
"chainId": <CHAIN_ID>
}

Response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"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:

1
2
x-api-key: <YOUR_API_KEY>
Content-Type: application/json

Request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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:

1
2
3
4
5
6
7
8
9
{
"from": "<FROM>",
"contractAddress":"<CONTRACT_ADDRESS>",
"gasPrice": "<GAS_PRICE>",
"gasLimit": "<GAS_LIMIT>",
"nonce": <NONCE>,
"chainId": <CHAIN_ID>,
"data": "<DATA>"
}

Response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"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:

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

1
2
3
4
5
6
7
8
curl --location --request POST '<CHAIN_RPC_URL>' \
--header 'Content-Type: application/json' \
--data-raw '{
"jsonrpc":"2.0",
"method":"eth_gasPrice",
"params":[],
"id":73
}'

Response:

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

convert the hex result "0x70295f4f0" to integer string

1
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:

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

1
2
3
4
5
6
7
8
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:

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

convert the hex result "0x5208" to integer string

1
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:

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

1
2
3
4
5
6
7
8
9
10
11
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:

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

convert the result to an integer

1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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:

1
2
x-api-key: <YOUR_API_KEY>
Content-Type: application/json

Request:

1
2
3
4
5
6
7
8
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

1
2
3
{
"requestId": "<REQUEST_ID>"
}

Response:

1
2
3
4
5
6
7
8
9
10
{
"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:

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

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
{
"id":1,
"jsonrpc": "2.0",
"result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
}

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