Infura Transactions (ITX) (1.0.0-alpha)

Infura Support: support@infura.io

Overview

Infura Transactions (ITX) is a simplified way to send Ethereum transactions. ITX handles all edge cases for transaction delivery and takes care of getting transactions mined while removing the need for developers to deal with the complexities around gas management.

Note

This is an early release and will roll out to a small group of beta testers before the official public rollout. If you’d like to be considered for early ITX access, submit your request here.

There are three high-level steps you need to take when integrating ITX in your project:

  1. On-chain deposit. You generate a private key and deposit some ETH with Infura’s on-chain deposit contract. This action credits your ITX gas tank with the corresponding amount of ETH (after 10 block confirmations). You will use your private key any time you authorize spending from your ITX gas tank (i.e. when relaying a transaction).

  2. Send a relay request. You send a transaction relay request to your usual Infura endpoint using the relay_sendTransaction RPC call. ITX will first check if you have sufficient balance, then lock a portion of your funds and relay the transaction on your behalf to the Ethereum network.

  3. Transaction mined & balance deducted. As soon as the transaction is mined and becomes part of the blockchain, the cost of the transaction which includes the network fee (gas price * gas used) + the ITX fee, will be subtracted from your gas tank balance. You can check your new balance using the relay_getBalance RPC call.

Authentication

ITX is fully compatible with all authentication mechanisms used by our Ethereum client interface. Please refer to this section for a full list of authentication scenarios.

Developing with ITX

The ITX API is implemented as a JSON-RPC extension of the standard Ethereum JSON-RPC API - it follows the same design principles and you can use it from your favourite programming language and Web3 framework.

The deposit system is managed by an on-chain contract which is deployed at the same address on all supported Ethereum public networks. The address is referenced by the ENS entry itx.eth.

Network name ITX deposit contract
Mainnet 0x015C7C7A7D65bbdb117C573007219107BD7486f9
Ropsten 0x015C7C7A7D65bbdb117C573007219107BD7486f9
Rinkeby 0x015C7C7A7D65bbdb117C573007219107BD7486f9
Kovan 0x015C7C7A7D65bbdb117C573007219107BD7486f9
Goerli 0x015C7C7A7D65bbdb117C573007219107BD7486f9

The code snippets in this walkthrough are written in JavaScript using the ethers.js library, but you're by no means limited to these programming choices.

You can find a full collection of scripts that showcase an end-to-end interaction with ITX in this Github repository.

Setup the ITX provider

All ITX-specific JSONRPC methods can be accessed via the ethers.js built-in InfuraProvider class, using your dedicated Infura URL.

const { ethers } = require('ethers')

const itx = new ethers.providers.InfuraProvider(
  'mainnet', // or 'ropsten', 'rinkeby', 'kovan', 'goerli'
  'YOUR_INFURA_PROJECT_ID'
)

Create a signer account

This is as simple as generating an Ethereum-compatible private key you have exclusive control over. You will use the private key for authenticating your ITX transaction requests, and its corresponding public address for identifying your ITX gas tank.

const signer = new ethers.Wallet('YOUR_PRIVATE_KEY', itx)

Check your ITX gas tank

Let's check if your signing account has any gas tank balance registered with ITX. You can do this by calling the relay_getBalance method:

async function getBalance() {
  balance = await itx.send('relay_getBalance', [signer.address])
  console.log(`Your current ITX balance is ${balance}`)
}

Full script to read your ITX balance here.

Make a deposit

You can make a deposit by sending Ether to the ITX contract. This contract is deployed at the same address on all public Ethereum networks.

Note: Your deposit will be registered after 10 block confirmations.

async function deposit(signer) {
  const tx = await signer.sendTransaction({
    // ITX deposit contract (same address for all public Ethereum networks)
    to: '0x015C7C7A7D65bbdb117C573007219107BD7486f9',
    // Choose how much ether you want to deposit to your ITX gas tank
    value: ethers.utils.parseUnits('0.1', 'ether')
  })
  // Waiting for the transaction to be mined
  await tx.wait()
}

Full script to make an ITX deposit here.

Signing a relay request

The format of an ITX relay request is:

{
  "to": "<target contract address>",
  "data": "<encoded contract call>",
  "gas": "<upper limit for gas spent>"
}

The relay hash value of this request is given by the following formula, where message is the RLP encoding of [to, data, gas, chainId] and the + operator represents string concatenation:

relayTransactionHash = keccak256(
  "\x19Ethereum Signed Message:\n" +
  keccak256(
    length(message) + message
  )
)

Every ITX relay request needs to be individually authenticated by signing the relay hash value using your private key:

async function signRequest(tx) {
  const relayTransactionHash = ethers.utils.keccak256(
    ethers.utils.defaultAbiCoder.encode(
      ['address', 'bytes', 'uint', 'uint'],
      [tx.to, tx.data, tx.gas, 4] // Rinkeby chainId is 4
    )
  )
  return await signer.signMessage(ethers.utils.arrayify(relayTransactionHash))
}

Sending your first transaction

Now that you're able to generate signatures that authenticate your relay requests, you're ready to call the relay_sendTransaction method and instruct ITX to relay your requests to the Ethereum network.

Note

ITX supports any type of transaction that doesn't directly carry Ether (i.e. whose value field is set to 0). You can use it for deploying your own contracts or calling arbitrary contract methods, and you can also use it for enabling your users to interact with your Dapp, even if they don't hold any Ether. However, you cannot use ITX to send Ether without the intermediation of a wallet contract.

We'll send a transaction that calls the the echo method of a simple demo contract. This contract is deployed at the same address on all supported Ethereum networks.

relay_sendTransaction requires two parameters:

  • The relay request object
  • Your signature
async function callContract() {
  const iface = new ethers.utils.Interface(['function echo(string message)'])
  const data = iface.encodeFunctionData('echo', ['Hello world!'])
  const tx = {
    to: '0x6663184b3521bF1896Ba6e1E776AB94c317204B6',
    data: data,
    gas: '100000'
  }
  const signature = await signRequest(tx)
  const relayTransactionHash = await itx.send('relay_sendTransaction', [
    tx,
    signature
  ])
  console.log(`ITX relay hash: ${relayTransactionHash}`)
  return relayTransactionHash
}

Check if relay request is mined

Unlike eth_sendRawTransaction, the relay request returns a relayTransactionHash and not a single Ethereum transaction hash. The ITX service is responsible for taking your relay request, packing it into an Ethereum transaction, and then gradually increasing the fee until the transaction is mined. The Ethereum Transaction hash is changed every time the fee is bumped and the previous hash is no longer reliable for tracking its status.

We provide a new RPC call relay_getTransactionStatus that returns the list of Ethereum Transaction hashes broadcasted for the supplied relayTransactionHash. You can then check client-side whether any of the transaction hashes were mined.

const wait = (milliseconds) => {
  return new Promise((resolve) => setTimeout(resolve, milliseconds))
}

async function waitTransaction(relayTransactionHash) {
  let mined = false

  while (!mined) {
    const statusResponse = await itx.send('relay_getTransactionStatus', [
      relayTransactionHash
    ])

    for (let i = 0; i < statusResponse.length; i++) {
      const hashes = statusResponse[i]
      const receipt = await itx.getTransactionReceipt(hashes['ethTxHash'])
      if (receipt && receipt.confirmations && receipt.confirmations > 1) {
        mined = true
        return receipt
      }
    }
    await wait(1000)
  }
}

And that is it! You have just sent a relay request via the ITX service. A full code sample is available in our demo repository.

Relay fee structure

When relaying a transaction, ITX will first check if you have sufficient gas tank balance, then lock a portion of your funds and relay the transaction on your behalf to the Ethereum network. As soon as the transaction is mined and becomes part of the blockchain, the total Infura transaction cost will be subtracted from your gas tank balance.

  • Total Infura transaction cost = Network transaction fee + ITX commission
    • Network transaction fee = gas used * gas price
    • ITX commission = gas used * a flat gwei amount
Network ITX commission
Mainnet gas used * 20 gwei
Ropsten gas used * 1 gwei
Rinkeby gas used * 1 gwei
Kovan gas used * 1 gwei
Goerli gas used * 1 gwei

You can check your ITX gas tank balance at any time to monitor your spending.

Limitations

Important

The from address of the final transaction will always be set to an internal ITX wallet address (chosen by the ITX system), whereas the final to and data fields are chosen by you and defined in the original transaction request (as parameters to your relay_sendTransaction call).

While this approach makes it possible for ITX wallets to pay the gas for executing the transaction, you need to pay close attention to the use of msg.sender in the contracts you're interacting with. For every managed transaction, the contracts will see the method call as originating from one of the ITX wallets. The best practice for working around this challenge is to encode a meta transaction in the data field of your initial request.

Meta transactions

In many (if not most) situations, you will need to authenticate your actions with a smart contract. This is necessary if you wish to transfer an ERC20 token or to vote in a DAO. Most smart contracts authenticate the caller using msg.sender which is the immediate caller of the smart contract. More often than not, an Ethereum Transaction is the direct caller of the smart contract and the msg.sender is computed as the from address of the transaction.

This is problematic for third party relayers like ITX as the default Ethereum authentication mechanism (i.e., the built-in transaction signature) is now used by ITX to pay the transaction gas and the from address of the final transaction is not under your direct control. To solve this problem, the community have worked on the concept of a meta-transaction which requires the user to send a signed message to a smart contract before an action is executed.

Meta transaction compatibility with ITX You can use ITX as a building block to implement any meta transaction flow. Your choice of on-chain authentication architecture will determine the to and data fields in your ITX transaction, but it will not impact how you interact with ITX.

At this point ITX is fully agnostic of the to and data parameters it receives from you, so it will relay your transactions regardless of the meta transaction flow you decide to use.

Emerging meta transaction flows can be broken into two solutions:

  • Modify target contract. The smart contract verifies a signed message from the user before executing the command.
  • Wallet contracts. The user has a wallet contract that has possession of their assets. All user actions are sent via the wallet contract.

There are several solutions for modifying the target contract such as EIP-2612's permit(), EIP3009's transferWithAuthorisation() or EIP2771's Forwarder contract..

Generally speaking, the standards focus on how to replace msg.sender with an ecrecover that verifies the user's signed message. If the user only needs to interact with your smart contract, then it is a simple solution. However, if the user needs to interact with ERC20 tokens that are not meta-transaction compatible, then you may run into limitations still.

The wallet contract approach verifies the user's signature before executing their desired action. It is compatible with most smart contracts as the immediate caller of a target contract is the wallet contract and not the Ethereum transaction. Thus, the msg.sender of the target contract is the wallet contract address. There are also other benefits to wallet contracts such as batching two or more transactions together. However it does require a setup phase as the user must transfer assets to their wallet contract. You can pick any wallet contract implementation to work with ITX.


We hope Infura Transactions will help you build more powerful and accessible products. However, it is alpha software and we would appreciate any thoughts about it (good or bad).

relay_sendTransaction

Sends a transaction relay request. Unlike eth_sendRawTransaction, the relay request returns a relayTransactionHash instead of a single Ethereum transaction hash.

Authorizations:
path Parameters
projectId
required
string
Request Body schema: application/json
id
required
number
jsonrpc
required
string
Value: "2.0"
method
required
string
Value: "relay_sendTransaction"
required
Array of objects or strings

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "jsonrpc": "2.0",
  • "method": "relay_sendTransaction",
  • "params": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "id": 0,
  • "jsonrpc": "2.0",
  • "result": "0xff6c3dcf0d4ccc58ab1b0caf2a69812508749cf82eb9b09391cdf7cfbb7cf81e"
}

relay_getTransactionStatus

Retrieves the status of a relayed transaction. Multiple Ethereum transaction hashes can be returned because ITX gradually increases the fee until your transaction is mined. Every time the gas fee is changed, a new Ethereum transaction is broadcasted to the network and its hash gets added to the list.

Authorizations:
path Parameters
projectId
required
string
Request Body schema: application/json
id
required
number
jsonrpc
required
string
Value: "2.0"
method
required
string
Value: "relay_getTransactionStatus"
params
required
Array of strings <= 1 items

A single-element array comprised of the relay transaction hash

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "jsonrpc": "2.0",
  • "method": "relay_getTransactionStatus",
  • "params": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "id": 0,
  • "jsonrpc": "2.0",
  • "result": [
    ]
}

relay_getBalance

Retrieves the ITX balance (gas tank) of a signing address that has previously deposited ether with the ITX contract.

Authorizations:
path Parameters
projectId
required
string
Request Body schema: application/json
id
required
number
jsonrpc
required
string
Value: "2.0"
method
required
string
Value: "relay_getBalance"
params
required
Array of strings <= 1 items

A single-element array comprised of the signer public Ethereum address

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "jsonrpc": "2.0",
  • "method": "relay_getBalance",
  • "params": [
    ]
}

Response samples

Content type
application/json
Example
{
  • "id": 0,
  • "jsonrpc": "2.0",
  • "result": "89154701859431608"
}