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.
Instruction format
Section titled “Instruction format”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.
Order matters
Section titled “Order matters”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.
Signing
Section titled “Signing”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), ), ), });}With near-kit, using the Quote types from the Quickstart:
use base64::prelude::{BASE64_STANDARD, Engine as _};use near_kit::{Gas, Near, protocol::Action};
let near = Near::mainnet() .credentials(std::env::var("NEAR_PRIVATE_KEY")?, "alice.near")? .build();
let quote = quotes .into_iter() .next() .ok_or_else(|| anyhow::anyhow!("No route found"))?;for ExecutionInstruction::NearTransaction { receiver_id, actions,} in quote.execution_instructions{ let actions = actions .into_iter() .map( |InstructionAction::FunctionCall { method_name, args, gas, deposit, }| { Ok(Action::function_call( method_name, BASE64_STANDARD.decode(args)?, Gas::from_gas(gas), deposit, )) }, ) .collect::<anyhow::Result<Vec<_>>>()?; let outcome = near.transaction(receiver_id).add_actions(actions).await?; if outcome.is_failure() { anyhow::bail!("Transaction failed: {:?}", outcome.failure_message()); }}ExecutionInstruction and InstructionAction are single-variant enums, so a response with a new instruction or action kind fails to deserialize instead of being skipped.
Costs and balance
Section titled “Costs and balance”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.
Keep quotes fresh
Section titled “Keep quotes fresh”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
deadlineis 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 alwaysnullfor now, but handle it anyway.
If something fails
Section titled “If something fails”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.