Skip to main content

Building a Relaying Server

With Sequence, you can create a smart contract wallet your server can use to dispatch transactions for your users without you having to be worried about transaction speed, throughput and re-orgs.

Sequentual Transactions

By default, Sequence transactions will be executed sequentially.

Nodejs Server

Your server will need an EOA wallet that will be able to sign messages. It will be the owner of your server-side Sequence wallet which will be used to dispatch transactions.

This Sequence wallet should have the correct ownership at your contract level, not the EOA. Also, the Sequence wallet should be sufficiently funded in order to pay fees needed by the relayer to dispatch your transactions.

Using this approach - versus the next section (with sponsoring) - includes the ability to pay for gas in any available currency by our relayer (e.g. 'MATIC', 'DAI', 'USDC', 'WETH').

import { Session } from '@0xsequence/auth'

// Create your server EOA
const walletEOA = new ethers.Wallet(serverPrivateKey, provider)

// Open a Sequence session, this will find or create
// a Sequence wallet controlled by your server EOA
const session = await Session.singleSigner({
signer: walletEOA,
projectAccessKey: '<access_key>'
})

// Get the Sequence wallet address
console.log(session.account.address)

// Get a signer for a specific network
// - 1: Ethereum Mainnet
// - 137: Polygon Mainnet
// - 42161: Arbitrum One
// See https://chainid.network/ for more
const signer = session.account.getSigner(137)

// Craft your transaction
const erc721Interface = new ethers.utils.Interface([
'function safeTransferFrom(address _from, address _to, uint256 _tokenId)'
])

const data = erc721Interface.encodeFunctionData(
'safeTransferFrom', [senderAddress, recipientAddress, id]
)

const txn = {
to: erc721TokenAddress,
data
}

// Send the transaction
const txnResponse = await signer.sendTransaction(txn)

// Check if transaction was successful
if (txnReceipt.status != 1) {
console.log(`Unexpected status: ${txnReceipt.status}`)
}

You can also enforce a specific way to pay for gas fees, or the openning of a specific Sequence wallet.

import { Session } from '@0xsequence/auth'

// Create your server EOA
const walletEOA = new ethers.Wallet(serverPrivateKey, provider)

// Open a Sequence session, this will find or create
// a Sequence wallet controlled by your server EOA
const session = await Session.singleSigner({
signer: walletEOA,
projectAccessKey: '<access_key>'
// OPTIONAL: Multiple wallets could be found for the same EOA
// to enforce a specific wallet you can use the following callback
selectWallet: async (wallets: string[]) => {
const found = wallets.find(w => w === EXPECTED_WALLET_ADDRESS)
if (!found) throw Error('wallet not found')
// Returning the wallet address will make the session use it
// returning undefined will make the session create a new wallet
return found
}
})

const signer = session.account.getSigner(137, {
// OPTIONAL: You can also enforce a specific way to pay for gas fees
// if not provided the sdk will select one for you
selectFee: async (
_txs: any,
options: FeeOption[]
) => {
// Find the option to pay with native tokens
const found = options.find(o => !o.token.contractAddress)
if (!found) throw Error('fee option not found')
return found
}
})

// Initialize the contract
const usdc = new ethers.Contract(
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174', // USDC on Polygon
ERC_20_ABI,
signer
)

// Send the transaction
const txnResponse = await usdc.transfer(recipient, 1)

// Check if transaction was successful
if (txnReceipt.status != 1) {
console.log(`Unexpected status: ${txnReceipt.status}`)
}
Triggers migration

Openning a session may trigger a migration of your Sequence wallet to a new version, this could be v1 to v2 or v2 to future versions.

Migration is a one-way process, once your wallet is migrated it cannot be reverted to a previous version.

To catch any unwanted migration, you can use the onMigration callback.

Nodejs Server with Gas Sponsoring using Sequence Builder

If you want to have your transactions sponsored & paid for with a credit card, you can follow the below steps before beginning your code, at the following link: https://sequence.build/

By sponsoring your transaction, you can now just send the transaction without a fee object and not have to fund the smart contract wallet before relaying any transactions.

Already Deployed Contract

For this example, we assume you have a smart contract deployed with a contract address to include in the last step.

A. Create Dapp

Sequence builder create app

B. New Dapp

Sequence builder new dapp

C.1 Gas Tank

Sequence builder gas tank

C.2 Add Gas

Sequence builder add gas

C.3 Add Sponsored Address

Sequence builder add sponsored address

The following is example code that implements a relayed transaction, same as the above example (i.e. Nodejs Server) but without fees, taken care of by the Sequence Builder.

import { Session } from '@0xsequence/auth'

// Create your server EOA
const walletEOA = new ethers.Wallet(serverPrivateKey, provider)

// Open a Sequence session, this will find or create
// a Sequence wallet controlled by your server EOA
const session = await Session.singleSigner({
signer: walletEOA,
projectAccessKey: '<access_key>'
})

// Get the Sequence wallet address
console.log(session.account.address)

// Get a signer for a specific network
// - 1: Ethereum Mainnet
// - 137: Polygon Mainnet
// - 42161: Arbitrum One
// See https://chainid.network/ for more
const signer = session.account.getSigner(137, {
// OPTIONAL: This ensures that the transaction is paid for by the gas tank
// but if not provided, the gas tank will be used anyway
selectFee: async (
_txs: any,
_options: FeeOption[]
) => {
return undefined
}
})

// Craft your transaction
const erc721Interface = new ethers.utils.Interface([
'function safeTransferFrom(address _from, address _to, uint256 _tokenId)'
])

const data = erc721Interface.encodeFunctionData(
'safeTransferFrom', [senderAddress, recipientAddress, id]
)

const txn = {
to: erc721TokenAddress,
data
}

// Send the transaction
const txnResponse = await signer.sendTransaction(txn)

// Check if transaction was successful
if (txnReceipt.status != 1) {
console.log(`Unexpected status: ${txnReceipt.status}`)
}

Parallel Transactions

If you want to send multiple independent transactions without needing to batch them, you can also send them in distinct nonce spaces. Using distinct nonce spaces for your transactions signals to the relayer that there's no dependency between them and that they can be executed on-chain in any order.

This allows the transactions to be dispatched immediately in an unbuffered way without having to wait for a full batch. Here is an example of how to do that:

// Generate random nonce spaces with ~0% probability of collision
const randomNonceSpace1 = ethers.BigNumber.from(ethers.utils.hexlify(ethers.utils.randomBytes(20)))
const randomNonceSpace2 = ethers.BigNumber.from(ethers.utils.hexlify(ethers.utils.randomBytes(20)))

// Create signers for each nonce space
const signer1 = session.account.getSigner(137, {
nonceSpace: randomNonceSpace1
})

const signer2 = session.account.getSigner(137, {
nonceSpace: randomNonceSpace2
})

// Generate transactions
const txn1 = {
to: tokenContract.address,
data: erc20Interface.encodeFunctionData(
'transfer', [recipient1, amount1]
)
}

const txn2 = {
to: tokenContract.address,
data: erc20Interface.encodeFunctionData(
'transfer', [recipient2, amount2]
)
}

// Dispatch transactions, which can now be executed in parallel
await Promise.all([
signer1.sendTransaction(txn1),
signer2.sendTransaction(txn2)
])

If batching transactions is not a problem for your use-case, you can call await wallet.sendTransaction(txns). You can read more about batch transactions in Sending Batched Transactions.