Skip to content

Intents SDK

@tao.com/intents-sdk is the recommended way to integrate TAO Intents. It calls the Quoter API and the Intents smart contract for you, in the right order, so you don't have to build orders, encode data, or decode reverts yourself.

What it handles:

  • Connects the wallet, and switches (or adds) the origin chain.
  • Checks the balance of the token being paid (USDC by default).
  • Requests a quote before any approval, so a failed quote never costs the user an approval transaction.
  • Approves exactly the input amount if needed (nothing for a native token), and reuses the quote. It requests a new quote only if less than about 20 seconds of the quote are left after the approval, or if the quote expires while the user is in the wallet.
  • Swaps other inputs (ETH, TAO, wTAO, subnet alpha) into USDC inside open. See Paying with something other than USDC.
  • Lets a Substrate coldkey pay with one signature and no EVM wallet. See Paying from a Substrate account.
  • Builds the order, simulates open, sends it, and reads the order ID from the receipt.
  • Tracks the order until it is filled or refunded.
  • Turns failures into typed errors with readable messages.

The SDK needs no API key. It works in the browser and in Node, with any EIP-1193 wallet (window.ethereum, WalletConnect, Coinbase Wallet) or a viem WalletClient.

If you can't use TypeScript, or you want to control every step, see the Integration Guide, which does the same thing by calling the Quoter API and the contract directly.

Install

npm install @tao.com/intents-sdk viem

viem v2 (^2.21.0) is a peer dependency. The package ships as ESM and CommonJS.

Quick start

A user pays 100 USDC on Ethereum and receives native TAO on Subtensor EVM:

import { TaoIntents } from "@tao.com/intents-sdk";

const intents = new TaoIntents({
  rpcUrls: { 1: "https://YOUR-ETHEREUM-RPC" }, // recommended for production
});

const { order, quote } = await intents.execute({
  wallet: window.ethereum,
  originChainId: 1,
  destinationChainId: 964,
  outputToken: "TAO",
  amount: "100", // human units. Use amountRaw for base units.
  onProgress: (e) => console.log(e.step), // drive your UI
});

console.log(`Sending 100 USDC, receiving ${quote.outputAmountFormatted} TAO`);

const result = await order.waitForSettlement(); // resolves when filled or refunded
console.log(result.state); // "filled" | "refunded"

Set an Ethereum RPC URL in rpcUrls for production. The SDK's default public endpoint is rate limited. Subtensor EVM defaults to a public RPC.

execute returns:

Field Description
order The tracked order. See Track the order.
quote The quote the order was opened with.
orderId The order ID from the Open event.
txHash The hash of the open transaction.
approvalTxHash The hash of the approval transaction, if the SDK had to send one.

When paying from a Substrate account, there is no EVM transaction, so extrinsicHash and blockHash replace txHash.

Progress events

onProgress is called as execute and open move through their steps. Use it to show the user what is happening.

step Meaning
switching-chain Asking the wallet to switch to the origin chain. Has chainId.
checking-balance Reading the balance and allowance of the input token.
quoting Requesting a quote.
quoted A quote was received. Has quote.
swap-quoted A non-USDC input was priced into USDC. Has minUsdc, quotedUsdc, poolFee and source (quoter or onchain).
approval-required The input token must be approved. Has amount, and token and kind (erc20 or alpha-stake).
approval-submitted The approval was sent. Has txHash.
approval-confirmed The approval was mined. Has txHash.
quote-refreshing A new quote is being requested. Has reason: expired, or approval when too little of the quote was left after the approval.
awaiting-signature The wallet is asking the user to sign open.
submitted The open transaction was sent. Has txHash.
confirmed The open transaction was mined. Has txHash and orderId.
substrate-submitted The Substrate extrinsic was signed and sent. Has extrinsicHash.
substrate-confirmed The Substrate extrinsic was included. Has extrinsicHash, blockHash and orderId.

Show a quote first

For a "review your swap" screen, split the flow into steps. Request the quote when the user is about to confirm, not on every keystroke (the Quoter is rate limited), and reuse it until validUntil.

const quote = await intents.getQuote({
  originChainId: 1,
  destinationChainId: 964,
  outputToken: "TAO", // symbol or address
  amount: "100", // human units, or amountRaw: 100_000_000n
  user: account, // required to open the intent from this quote
  recipient: otherAddress, // optional, defaults to `user`. Can be a Substrate account for native TAO, see below
});

quote.outputAmountFormatted; // "0.3172..."
quote.validUntil; // unix seconds, about 60 seconds ahead
quote.inputUsd; // "99.991607" (informational)
quote.outputUsd; // "98.036613" (informational)
quote.fees; // cost breakdown (informational)

intents.isQuoteExpired(quote, 15); // true if less than 15 seconds are left to sign

// Optional: approve ahead of time, so the quote window isn't spent on an approval.
await intents.ensureApproval({ wallet, originChainId: 1, amount: "100" });

const { order } = await intents.open({ quote, wallet });
  • user must be the account that will sign. open throws InvalidParamsError if the connected wallet is a different account.
  • A quote requested without user is indicative only. open rejects it with IndicativeQuoteError.
  • open throws QuoteExpiredError if the quote runs out before the transaction is sent. Request a new quote and try again.
  • fillDeadline is optional. It defaults to 5 minutes from now, and must be between 60 seconds and 1 day ahead. If you leave it out, open moves it to a fresh window right before the wallet signs, so a user who lingers on the review screen doesn't end up with an almost-expired order. If you set it yourself, open keeps it and refuses to send an order with less than 60 seconds left.

The quote

Field Description
outputAmount, outputAmountFormatted What the recipient receives, in base units and as a decimal string. The order uses outputAmount unchanged, except for subnet alpha, where it is the minimum alpha. outputAmountFormatted is undefined for output tokens the SDK doesn't list, because their decimals are unknown. Subnet alpha (SN<netuid>) always has known decimals (9), so it is always set.
inputAmount, inputAmountFormatted What the user pays.
validUntil Unix time in seconds until which the quote holds, about 60 seconds.
fillDeadline The deadline the quote was requested with. If you didn't set fillDeadline (fillDeadlineIsExplicit is false), open replaces it with a fresh one right before signing.
user, recipient The paying account and the receiving EVM address. For a Substrate payout, recipient is still the user's EVM address.
substrateRecipient The receiving 32-byte Substrate account (AccountId32) as hex, for a Substrate payout. undefined otherwise.
orderOutputAmount The output amount written into the order. Equals outputAmount, except for subnet alpha, where it is the native TAO (in wei) the solver stakes.
alpha For subnet alpha, { netuid, hotkey }: the subnet and the validator hotkey (32-byte hex) the stake goes to. undefined otherwise.
inputUsd, outputUsd, fees Informational USD values and cost breakdown from the Quoter. Display them, but don't build logic on them.
inputToken, swapRoute The token the user pays. For a non-USDC input, swapRoute is the Uniswap hop the Quoter priced: { poolFeeTier, amountOut }, where amountOut is the expected USDC in base units (6 decimals), before slippage and the protocol fee. open sizes the order's USDC floor from it. undefined if the Quoter sent none, in which case the SDK asks Uniswap itself.
raw The untouched Quoter API response.

Attribute orders to your app

If you have a partner ID, pass it as partnerId so your orders are attributed to you. The SDK appends an ERC-8021 (schema 0) suffix to the calldata of the open transaction. The contract ignores the extra bytes, so the order behaves exactly as it would without one.

await intents.execute({
  wallet: window.ethereum,
  originChainId: 1,
  destinationChainId: 964,
  outputToken: "TAO",
  amount: "100",
  partnerId: "my-app",
});

partnerId works on both execute and open:

const { order } = await intents.open({ quote, wallet, partnerId: "my-app" });
  • The ID must be printable ASCII, with no spaces or commas, and at most 100 characters. Otherwise the SDK throws InvalidParamsError before anything is signed, and before any approval is sent.
  • Without partnerId, no suffix is added.
  • buildAttributionSuffix(partnerId) is exported if you need the raw suffix, for example to append it yourself. See Attribution for the byte layout.

Wallets

Pass wallet as an EIP-1193 provider or as a viem WalletClient. If the wallet is not connected, the SDK asks the user to connect it.

// Browser wallet
await intents.execute({ wallet: window.ethereum, /* ... */ });

// viem WalletClient, for example the one from wagmi's useWalletClient()
await intents.execute({ wallet: walletClient, /* ... */ });

In Node (scripts, backends, tests), use a WalletClient with a local account:

import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
import { TaoIntents } from "@tao.com/intents-sdk";

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({ account, chain: mainnet, transport: http(process.env.ETH_RPC_URL) });
const intents = new TaoIntents({ rpcUrls: { 1: process.env.ETH_RPC_URL! } });

const { order } = await intents.execute({
  wallet,
  originChainId: 1,
  destinationChainId: 964,
  outputToken: "TAO",
  amount: "100",
});

Never put a private key in browser code.

Track the order

execute and open return an Order. Fills usually land within a few minutes. If nobody fills the order before fillDeadline, the protocol refunds the input on the origin chain. Users can't trigger the refund themselves.

order.orderId;
order.explorerUrl; // block explorer link for the open transaction

const status = await order.getStatus(); // one check

const stop = order.watch((s) => render(s.state)); // called right away, then on every state change
stop(); // unsubscribe

const final = await order.waitForSettlement({ signal, timeoutMs: 15 * 60_000 });

getStatus() returns { orderId, state, isFinal, fillDeadline }. state is one of:

state Meaning
open Waiting for a solver to fill the order.
awaiting-refund The deadline passed without a fill. The protocol will refund the user.
filled The output was delivered to the recipient. Final.
refunded The input was returned to the user, in USDC, on the origin chain. Final.

watch stops by itself once the order is final. waitForSettlement resolves with the final status, or rejects with TimeoutError or AbortedError if you pass timeoutMs or signal.

These states match the checks described in Events and order status.

Keep tracking after a reload

order.toJSON() is plain JSON. Store it to keep tracking across page reloads:

localStorage.setItem("order", JSON.stringify(order));

// Later:
const restored = intents.getOrder(JSON.parse(localStorage.getItem("order")!));

// Or, if you only stored the transaction hash:
const byHash = await intents.getOrderByTransaction(1, txHash);

Supported routes

These are the mainnet routes. The input is USDC by default (see Paying with something other than USDC), and the recipient is an EVM address, or a Substrate account when delivering native TAO or subnet alpha on Subtensor (see Deliver TAO to a Substrate wallet and Deliver subnet alpha).

Chain (ID) Input Output tokens
Ethereum (1) USDC, ETH USDC, ETH
Subtensor EVM (964) USDC (on-chain symbol USDC.e), TAO, wTAO, SN<netuid> (subnet alpha, see Paying with subnet alpha) USDC, wTAO, TAO, SN<netuid> (subnet alpha, see Deliver subnet alpha)

Pass tokens by symbol or by address. intents.getChains() and intents.getTokens(chainId) return the built-in lists, which are useful for token pickers. An output address the SDK doesn't list is passed through to the Quoter, which decides whether the route exists.

For the token addresses, see Tokens.

Paying with something other than USDC

inputToken defaults to "USDC". Any other input is swapped into USDC by the settler inside open, through Uniswap V3, before the USDC is escrowed. The order's USDC amount becomes the minimum the swap must return, so the swap can't fill at a worse price than you allowed.

// 0.5 ETH on Ethereum in, native TAO out
await intents.execute({
  wallet,
  originChainId: 1,
  destinationChainId: 964,
  outputToken: "TAO",
  inputToken: "ETH",
  amount: "0.5",
  swapSlippageBps: 50,
});
Input What the user signs
USDC ERC-20 approval (if needed), then open.
Native (ETH, TAO) Only open, with the amount as the transaction value. No approval.
wTAO ERC-20 approval of that token, then open.
Subnet alpha (SN<netuid>) A staking-precompile approval, then open. See Paying with subnet alpha.

The USDC floor is set from the Uniswap hop the Quoter priced (quote.swapRoute: fee tier and expected USDC), minus swapSlippageBps (default 100, which is 1%). If the Quoter sent no route, the SDK asks Uniswap's QuoterV2 on every fee tier and keeps the best output. If the floor is far below what the quote promised, the SDK throws SwapQuoteError instead of signing an order nobody can fill. The swap-quoted progress event reports the result.

  • The supported inputs are the ones in Supported routes: they are the tokens the solver serves on mainnet.
  • The swap is single-hop. A native input is swapped as WETH or wTAO against USDC. Nothing is routed through intermediate pools, so a pair without a direct USDC pool fails with SwapQuoteError before any approval is paid.
  • Pricing through QuoterV2 gives each fee tier a few seconds, so a drained pool that is slow to quote is dropped instead of stalling the rest. Public RPCs rate limit these calls, so set rpcUrls to your own node in production.
  • A native input needs the amount plus gas. The SDK checks that the balance covers the amount. A gas shortfall surfaces as InsufficientGasError from the simulation.
  • The protocol fee is taken from the swapped USDC, and the quoted output already accounts for it.
  • quote.inputAmount is in the input token's units. The order's USDC escrow is derived at open time, so a slow user gets a floor priced right before they sign, and the swap is priced again rather than reused from getQuote.

Paying with subnet alpha

On Subtensor EVM (964), inputToken: "SN<netuid>" ("SN0" is root stake) pays with alpha. The settler unstakes it for the user, swaps the TAO it gets into USDC, and escrows that.

await intents.execute({
  wallet,
  originChainId: 964,
  destinationChainId: 1,
  outputToken: "USDC",
  inputToken: "SN64",
  amount: "1.5", // alpha, 9 decimals
  alphaHotkey: "5HK5...", // the hotkey the alpha is staked to (required)
});
  • The alpha must already be staked by the EVM address that signs, which means the Substrate account that address maps to. Alpha held by a regular Substrate wallet has to be moved there first with a transfer_stake extrinsic, or you can skip that step by paying from a Substrate account.
  • alphaHotkey must be the hotkey that holds the stake, because the settler can only unstake from it. A missing or insufficient stake throws InsufficientBalanceError before anything is approved.
  • The approval is the staking precompile's approve(settler, netuid, amount), for exactly the alpha amount.
  • The minimum TAO the unstake must produce comes from the chain's own swap simulation (root stake is 1:1), less swapSlippageBps. The settler reverts the whole open if the unstake yields less.
  • Alpha can't be both the input and the output of one intent, and the destination can't be Subtensor EVM itself (the Quoter rejects same-chain quotes).

Paying from a Substrate account

A Substrate coldkey can open an intent with one signature and no EVM wallet or gas, paying native TAO or subnet alpha. The SDK builds one utility.batchAll that the coldkey signs. Inside it, the coldkey drives the EVM through evm.call, acting as the EVM address made of the first 20 bytes of its public key (substrateEvmAddress(account)):

  • Native TAO: fund that address with the amount plus gas, then call open.
  • Alpha: transfer_stake to the address's mirror account, fund gas, approve the settler on the staking precompile (skipped if already allowed), then call open.
  • In both cases, the unused gas is swept back to the coldkey. For alpha, the sweep also makes the batch atomic: if open reverts, it burns the gas the sweep needs and the whole batch (stake move included) rolls back.

The SDK has no Substrate dependency. You give it a SubstrateSigner. polkadotSigner adapts a polkadot-js ApiPromise (connected over WebSocket) and an extension signer or keyring pair.

import { TaoIntents, polkadotSigner } from "@tao.com/intents-sdk";

const substrate = polkadotSigner({ api, account: address, signer: injector.signer });

const { order, extrinsicHash } = await intents.execute({
  substrate, // instead of `wallet`
  originChainId: 964,
  destinationChainId: 1,
  inputToken: "TAO", // or inputToken: "SN64" with alphaHotkey
  outputToken: "USDC",
  amount: "1",
  recipient: "0xYourEvmAddress", // required: the account's own EVM address has no key
});

getQuote({ substrateAccount: address, recipient, ... }) followed by open({ quote, substrate }) works like the EVM flow. substrateAccount and user are mutually exclusive. Any other SubstrateSigner only needs signAndSendBatch(calls) (a utility.batchAll of { section, method, args }) and, optionally, spendableAfterFee(calls) so the SDK can refuse before asking for a signature.

  • Only native TAO and alpha, on Subtensor EVM. USDC and wTAO are paid from an EVM wallet as before.
  • recipient is required. The order's user is the account's EVM address, which has no private key.
  • Refunds land at that address. An order nobody fills is refunded in USDC to substrateEvmAddress(account). recoverRefund({ substrate, originChainId: 964, to }) moves it to an address you control, with one signature. Gas left at that address by an earlier batch is reused by the next one.
  • Alpha amounts that are the whole stake lose 2 rao (quote.inputAmount shows it), because the stake move needs that much headroom to clear. The alpha must sit at the coldkey, on alphaHotkey.
  • Native TAO amounts are limited to 9 decimals (whole rao).
  • There is no pre-flight simulation of open. The account's EVM address holds nothing until the batch funds it. A reverted open is caught from the batch's events and throws EvmCallFailedError. For a native input, the TAO then stays at that address, available to the next batch.
  • The result has no EVM transaction: extrinsicHash and blockHash replace txHash, and order.openTxHash is unset. Progress events are substrate-submitted and substrate-confirmed. The extrinsic is mortal for about 51 minutes, and the SDK vetoes a signature that would leave the fill deadline under 60 seconds.
  • The batch's extrinsic fee scales with its gas limits (about 0.0067 TAO for native, about 0.027 TAO with an alpha approval) and is paid by the coldkey on top of the gas it funds.

Deliver TAO to a Substrate wallet

Native TAO (outputToken: "TAO", destination 964) can be paid straight to a Substrate coldkey. Pass an SS58 address or a 32-byte 0x AccountId32 as recipient:

await intents.execute({
  wallet,
  originChainId: 1,
  destinationChainId: 964,
  outputToken: "TAO",
  amount: "100",
  recipient: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
});
  • The SDK verifies the SS58 checksum and throws InvalidParamsError for a mistyped address.
  • The order keeps the user's EVM address as its recipient. The coldkey travels in the order's Substrate payout data, which the Quoter returns with the quote. The SDK checks that this data pays the coldkey you asked for before anything is signed, and throws a QuoterError if it doesn't match.
  • quote.substrateRecipient holds the resolved 32-byte account.
  • Any output token other than native TAO and subnet alpha, or any other destination chain, throws UnsupportedRouteError.
  • Substrate payouts are priced with a higher solver margin than EVM ones, so expect a lower outputAmount for the same input.
  • parseSubstrateAddress(input) is exported if you want to validate an address in your form. It returns the AccountId32, or undefined for an EVM address.

Deliver subnet alpha

Subnet alpha tokens are an output on Subtensor EVM (964). Use "SN<netuid>" as outputToken (for example "SN64"; "SN0" is root stake) and a Substrate recipient, which is the coldkey that will own the stake:

await intents.execute({
  wallet,
  originChainId: 1,
  destinationChainId: 964,
  outputToken: "SN64",
  amount: "100",
  recipient: "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", // required: the coldkey that owns the stake
  alphaHotkey: "5HK5...", // optional: validator to stake to. Defaults to the TAO.com validator.
});

Alpha isn't an ERC-20 that a solver can transfer. The order's output token is native TAO, and when the solver fills the order, the settler stakes that TAO into the subnet for the coldkey. The SDK asks the Quoter for an alpha_stake destination leg, and the order carries the Quoter's stake data. This differs from other outputs:

  • quote.outputAmount is the minimum alpha the stake must yield, in rao (9 decimals). The fill reverts below it. outputAmountFormatted shows the same amount. quote.orderOutputAmount is the native TAO (in wei) written into the order.
  • minOutputAmount and slippageBps apply to the alpha amount.
  • quote.alpha holds the subnet and validator hotkey. quote.substrateRecipient is the stake owner.
  • recipient is required and must be a Substrate account (an SS58 address or a 32-byte 0x AccountId32). alphaHotkey accepts the same formats, and is only valid with SN<netuid> outputs.
  • A quote requires user (it becomes the order's EVM recipient), as with any Substrate payout. With execute(), the connected wallet provides it automatically. It only matters if you call getQuote() directly.
  • The SDK checks that the Quoter's data stakes to the subnet, validator, and coldkey you asked for, and throws a QuoterError otherwise. Unlike a native TAO payout, the data can't be rebuilt locally, because the alpha minimum comes from the Quoter.
  • netuid must be between 0 and 10,000. Subnet alpha is only available on Subtensor EVM, so other destination chains throw UnsupportedRouteError.

Errors

Every failure is a TaoIntentsError with a stable code. Nothing is opened on-chain after a failed quote. If execute fails after the USDC approval was mined, the thrown error has approvalTxHash set. The allowance stays, so calling execute again skips the approval. Error messages are written for users, so you can show them as they are.

Class code Typical handling
InvalidParamsError INVALID_PARAMS Fix the input: a bad address, amount, deadline, or partnerId, a subnet alpha output without a Substrate recipient, alphaHotkey on a non-alpha output, a connected wallet that isn't the quote's user, or a substrate signer whose account isn't the quote's substrateAccount.
UnsupportedRouteError UNSUPPORTED_ROUTE Unknown chain or token, same origin and destination chain, a swap input on a chain with no Uniswap deployment, a Substrate recipient for anything other than native TAO or subnet alpha on Subtensor EVM, a Substrate account paying with anything other than native TAO or subnet alpha, or SN<netuid> on another chain.
SwapQuoteError SWAP_QUOTE_FAILED A non-USDC input can't be priced into USDC: no pool liquidity, an RPC failure, or an absurd floor. Nothing was sent or approved.
QuoterError QUOTER_ERROR The Quoter returned an error or no quote. Has status, apiError, failures, retryable, and paused. See Quoter API errors.
QuoterError QUOTER_FORBIDDEN HTTP 403: the request was refused. Not retryable. blocked is true.
QuoterError QUOTER_UNREACHABLE No response from the quote service: a network problem, or the request was blocked. blocked is true.
QuoteExpiredError QUOTE_EXPIRED Request a new quote.
IndicativeQuoteError INDICATIVE_QUOTE Request the quote again with user set.
InsufficientBalanceError INSUFFICIENT_BALANCE The user doesn't have enough of the token being paid (or the alpha stake). Has required and available.
InsufficientGasError INSUFFICIENT_GAS The user needs the chain's native token for gas.
UserRejectedError USER_REJECTED The user dismissed the wallet prompt.
ContractRevertError CONTRACT_REVERT The contract rejected the order. Has errorName, for example FullSettler__ProtocolFeeExceedsMax. See the revert table.
TransactionRevertedError TRANSACTION_REVERTED The transaction was mined but reverted. Has txHash.
WalletError WALLET_ERROR A wallet or network problem.
SubstrateError SUBSTRATE_ERROR Signing or submitting the Substrate extrinsic failed. Has dispatchError and extrinsicHash when known.
EvmCallFailedError EVM_CALL_FAILED The Substrate batch ran but open didn't: it reverted inside the EVM, or no Open event was found. Nothing was opened.
OrderNotFoundError ORDER_NOT_FOUND Wrong chain, or the hash isn't an intent.
OutputBelowMinimumError OUTPUT_BELOW_MINIMUM The quote is below your minOutputAmount, or a re-quote is worse than slippageBps allows. Nothing was signed. Has quoted and minimum.
TransactionReplacedError TRANSACTION_REPLACED The user cancelled or replaced the transaction in the wallet. No intent was opened.
AbortedError, TimeoutError ABORTED, TIMEOUT Caused by your signal or timeoutMs. If a transaction was already sent, AbortedError.txHash is set. That transaction is not cancelled, and getOrderByTransaction recovers the order once it is mined.
import { QuoterError, UserRejectedError } from "@tao.com/intents-sdk";

try {
  await intents.execute({ /* ... */ });
} catch (e) {
  if (e instanceof UserRejectedError) return; // the user cancelled
  if (e instanceof QuoterError && e.paused) showMaintenance(); // the contract is paused
  else showToast(e instanceof Error ? e.message : String(e));
}

The SDK retries transient Quoter failures once. It never retries a rate limit (429), so request quotes sparingly.

Configuration

All options are optional.

const intents = new TaoIntents({
  rpcUrls: { 1: "...", 964: "..." }, // RPC URLs for reads. Set Ethereum's for production.
  publicClients: { 1: wagmiPublicClient }, // or reuse your own viem public clients
  quoterTimeoutMs: 15_000,
  quoterRetries: 1, // transient failures only
  fillWindowSeconds: 300, // default fill window, 60 seconds to 1 day
});
Option Description
rpcUrls RPC URLs by chain ID, used for reads. Set one for Ethereum in production.
publicClients Your own viem public clients by chain ID. They take precedence over rpcUrls.
quoterUrl Override the Quoter API endpoint. The default is https://www.tao.com/api/v1/intents/quotes.
quoterTimeoutMs Quoter request timeout. Default 15 seconds.
quoterRetries Retries for transient Quoter failures. Default 1.
fillWindowSeconds Default fill window when you don't pass a fillDeadline. Default 300 seconds, between 60 and 86,400. Other values throw InvalidParamsError.
fetch A custom fetch, for example for proxies or tests.
chains Per-chain overrides, such as a different Intents contract address or RPC URL.

Test without real funds

There is no separate test network. Run your code against a local fork of Ethereum, as described in Testing without real funds. With the SDK, point rpcUrls and the wallet's transport at the fork:

const RPC = "http://127.0.0.1:8545"; // anvil --fork-url <your Ethereum RPC URL> --auto-impersonate

const wallet = createWalletClient({ account, chain: mainnet, transport: http(RPC) });
const intents = new TaoIntents({ rpcUrls: { 1: RPC } });

The Quoter API is still the real one, so quotes work with a test wallet. The order is opened on your fork only, so no solver fills it and order.getStatus() stays open.

Methods

Method Use
execute(params) The one-call flow: connect, quote, approve, and open. Re-quotes if the quote expires while the user signs, or if too little of it is left after the approval. Pass minOutputAmount (base units) to refuse quotes below it, slippageBps (default 100) to limit how much worse a re-quote can be than the first quote, inputToken and swapSlippageBps to pay with another token, substrate instead of wallet to pay from a Substrate account, and partnerId to attribute the order to your app. A violated limit throws OutputBelowMinimumError before anything is signed.
getQuote(params) Get a quote.
isQuoteExpired(quote, marginSeconds?) Check whether a quote can still be used.
ensureApproval(params) Approve exactly the input amount, if needed. Pass inputToken (default USDC) when the user will pay with another token: it sends the ERC-20 approval for wTAO, or the staking-precompile approval for subnet alpha (SN<netuid>). It does nothing for a native input, and it takes a wallet, so a Substrate account doesn't use it.
open({ quote, wallet }) Open an intent from a quote. Accepts minOutputAmount, swapSlippageBps and partnerId. Pass substrate instead of wallet for a Substrate account.
recoverRefund({ substrate, originChainId, to }) Move a refund from a Substrate account's EVM address to an address you control.
getOrder(ref) Restore an Order from order.toJSON().
getOrderByTransaction(chainId, txHash) Restore an Order from the hash of its open transaction.
getChains(), getTokens(chainId) The built-in chains and tokens.
buildAttributionSuffix(partnerId) A standalone export: the ERC-8021 calldata suffix for a partner ID.
polkadotSigner(options), substrateEvmAddress(account) Standalone exports for Substrate payments.

execute, open, ensureApproval and recoverRefund accept signal (an AbortSignal) and onProgress. getQuote accepts signal. A signal stops the SDK waiting at any step. It can't close a wallet prompt that is already open or withdraw a transaction that was already sent.