Quickstart
This page is also Markdown, for agents: /dex-aggregator/quickstart.md, and /llms.txt lists every page.
This page goes through swapping 1 NEAR to USDt. It takes one HTTP request to get the route and a few signed transactions to execute it.
The examples on this page use fetch and NEAR Connect (@hot-labs/near-connect). For signing on a server, see Executing a route.
The examples on this page use reqwest and near-kit:
anyhow = "1"base64 = "0.23"near-kit = "0.19"reqwest = { version = "0.13", features = ["json"] }serde = { version = "1", features = ["derive"] }tokio = { version = "1", features = ["full"] }-
Request a route.
All amounts are raw integers in the token’s smallest unit. 1 NEAR is
10^24yoctoNEAR.const params = new URLSearchParams({token_in: 'near',token_out: 'nep141:usdt.tether-token.near',amount_in: '1000000000000000000000000', // 1 NEARslippage_type: 'Fixed',slippage: '0.01', // 1%trader_account_id: 'alice.near',});const response = await fetch(`https://router.intear.tech/route?${params}`);if (!response.ok) throw new Error(await response.text());const quotes = await response.json();use near_kit::{AccountId, NearToken};use serde::Deserialize;#[derive(Debug, Deserialize)]struct Quote {dex_id: String,estimated_amount: Amount,worst_case_amount: Amount,execution_instructions: Vec<ExecutionInstruction>,token_output: String,fee_amount: String,}#[derive(Debug, Deserialize)]#[serde(rename_all = "snake_case")]enum Amount {AmountIn(String),AmountOut(String),}#[derive(Debug, Deserialize)]enum ExecutionInstruction {NearTransaction {receiver_id: AccountId,actions: Vec<InstructionAction>,is_fee_transaction: bool,},}#[derive(Debug, Deserialize)]enum InstructionAction {FunctionCall {method_name: String,args: String, // base64gas: u64,deposit: NearToken,},}let url = reqwest::Url::parse_with_params("https://router.intear.tech/route",[("token_in", "near"),("token_out", "nep141:usdt.tether-token.near"),("amount_in", "1000000000000000000000000"), // 1 NEAR("slippage_type", "Fixed"),("slippage", "0.01"), // 1%("trader_account_id", "alice.near"),],)?;let response = reqwest::get(url).await?;if !response.status().is_success() {anyhow::bail!(response.text().await?);}let quotes: Vec<Quote> = response.json().await?;Always pass
trader_account_idfor real quotes. Without it, the route can’t include the storage deposits and registrations the trader is missing, and the swap may fail or lose tokens. It’s an optional field, since you might want to still show the rates even when user’s wallet is not connected yet. -
Pick a quote.
The response is an array of quotes, one per DEX that found a route, sorted best first. It’s already ranked by what the trader gets after gas and storage costs, so in most cases you take the first one. An empty array means no DEX found a route.
[{"dex_id": "RheaDcl","estimated_amount": { "amount_out": "4880007" },"worst_case_amount": { "amount_out": "4831206" },"token_output": "nep141:usdt.tether-token.near","execution_instructions": [ ... ],"estimated_gas_usage": "8231794617168100000000","estimated_max_gas_cost": "12521020071713600000000","storage_deposits": "1250000000000000000000","fee_amount": "0","needs_unwrap": false}]Show the user
estimated_amountas the expected result andworst_case_amountas the minimum they will receive.estimated_gas_usageis the amount of NEAR that will likely be spent on gas and actually burnt, whileestimated_max_gas_costis all the gas the transactions attach, at NEAR’s minimum gas price: the most they can burn. The user needs at leaststorage_deposits + estimated_max_gas_costNEAR, plus the input when swapping from NEAR, to execute the transactions. Keepingestimated_max_gas_cost * 10and another 0.05 NEAR for future gas costs to swap back gives a safe margin. -
Sign and send the transactions in order.
Each item in
execution_instructionsis a NEAR transaction. Send them one by one, in order, and wait for each one to succeed before sending the next. With app fees, stop before the ones withis_fee_transaction: true, and send them only if/route/outcomesayscharge_fee: true(Fee transactions).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];if (!quote) throw new Error('No route found');for (const { NearTransaction: tx } of quote.execution_instructions) {// App fees: send these only once /route/outcome says charge_fee is trueif (tx.is_fee_transaction) break;const { status } = 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 JSONgas: String(call.gas),deposit: call.deposit,},})),});if (typeof status === 'object' && status.Failure) {throw new Error(`Transaction failed: ${JSON.stringify(status.Failure)}`);}// ft_transfer_call returns the amount the DEX used: "0" if it refunded everythingconst transferCall = tx.actions.at(-1)?.FunctionCall.method_name === 'ft_transfer_call';if (transferCall && typeof status === 'object' && status.SuccessValue === btoa('"0"')) {throw new Error('Swap refunded');}}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,is_fee_transaction,} in quote.execution_instructions{// App fees: send these only once /route/outcome says charge_fee is trueif is_fee_transaction {break;}let transfer_call = actions.last().is_some_and(|InstructionAction::FunctionCall { method_name, .. }| method_name == "ft_transfer_call",);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());}// ft_transfer_call returns the amount the DEX used: "0" if it refunded everythingif transfer_call && outcome.result()? == b"\"0\"" {anyhow::bail!("Swap refunded");}}See Executing a route for the instruction format and edge cases.
-
Check
token_output.If
token_outputis the same as yourtoken_out, you’re done. If it’s different, the tokens landed in another form of the same token (for example NEAR instead of wNEAR, or in your wallet instead of a DEX’s inner balance) and need one more conversion step. See Tokens and locations. You don’t need to handle this if you use Outcomes, it already sends you the next steps, including output conversion, and tells you whether to send the fee transactions.
What’s next
Section titled “What’s next”- Try different parameters in the Playground.
- Keep quotes fresh while the user looks at them with live quotes.
- Earn from the volume you bring with referral fees.