Skip to content

Executing a route

Every quote has execution_instructions, an ordered list of steps that perform the swap. You don’t need to know anything about the DEX contracts: sign what you get, in order.

Right now there’s only one kind of instruction, NearTransaction:

{
"NearTransaction": {
"receiver_id": "wrap.near",
"actions": [
{
"FunctionCall": {
"method_name": "near_deposit",
"args": "e30=",
"gas": 5000000000000,
"deposit": "1000000000000000000000000"
}
}
]
}
}
Field Type Meaning
receiver_id string The contract the transaction is sent to
actions array Actions of the transaction, in order. Currently always FunctionCall
method_name string Contract method to call
args string Base64-encoded JSON arguments
gas number Gas to attach, in gas units (10^12 = 1 TGas)
deposit string NEAR to attach, in yoctoNEAR

The transaction is signed by the trader, the account you passed as trader_account_id.

Send the transactions one at a time, in the given order, and wait for each to succeed before sending the next. Later steps depend on earlier ones: a storage deposit has to land before a token can be received, NEAR has to be wrapped before wNEAR can be swapped, and so on. If a transaction fails, stop.

Consecutive steps that go to the same contract are already merged into one transaction (for example storage_deposit + near_deposit + ft_transfer_call on wrap.near), so most swaps are one to three transactions.

In the browser, with NEAR Connect:

import { NearConnector } from '@hot-labs/near-connect';
const connector = new NearConnector({ network: 'mainnet' });
await connector.connect();
const wallet = await connector.wallet();
const quote = quotes[0];
for (const { NearTransaction: tx } of quote.execution_instructions) {
await wallet.signAndSendTransaction({
receiverId: tx.receiver_id,
actions: tx.actions.map(({ FunctionCall: call }) => ({
type: 'FunctionCall',
params: {
methodName: call.method_name,
args: JSON.parse(atob(call.args)), // args are base64-encoded JSON
gas: String(call.gas),
deposit: call.deposit,
},
})),
});
}

On a server, with near-api-js:

import { Account, actions } from 'near-api-js';
const account = new Account('alice.near', 'https://free.rpc.fastnear.com', 'ed25519:<private key>');
for (const { NearTransaction: tx } of quote.execution_instructions) {
await account.signAndSendTransaction({
receiverId: tx.receiver_id,
actions: tx.actions.map(({ FunctionCall: call }) =>
actions.functionCall(
call.method_name,
Buffer.from(call.args, 'base64'),
BigInt(call.gas),
BigInt(call.deposit),
),
),
});
}

Each quote tells you what it costs in NEAR, on top of the swap amount:

Field Use it for
estimated_gas_usage The gas fee to show to the user
estimated_max_gas_cost The most gas can cost, if all attached gas is burnt. Make sure the wallet has at least this much
storage_deposits NEAR spent on storage deposits. Usually only non-zero the first time the trader uses a token or DEX

So before swapping, the trader needs at least amount_in (if swapping from NEAR) + storage_deposits + estimated_max_gas_cost NEAR.

These costs are already taken into account when quotes are sorted, so a route with slightly more output but much higher costs ranks lower.

A quote is based on the pool state at the moment it was found. Pools change every block, so:

  • Don’t execute a quote that’s more than a few seconds old. Request a new one right before signing, or use live quotes to always have a fresh one.
  • If deadline is set, the quote stops being valid at that time. Request a new one 2–3 seconds before it, to leave time for signing and block production. Current DEXs don’t use deadlines, so it’s always null for now, but handle it anyway.

If the price moves past your slippage limit, the swap transaction fails and the DEX refunds the input tokens. Steps that already succeeded stay done, so the trader may end up with an intermediate form of the token, like wNEAR from a near_deposit that ran before the failed swap.