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 });
usermust be the account that will sign.openthrowsInvalidParamsErrorif the connected wallet is a different account.- A quote requested without
useris indicative only.openrejects it withIndicativeQuoteError. openthrowsQuoteExpiredErrorif the quote runs out before the transaction is sent. Request a new quote and try again.fillDeadlineis optional. It defaults to 5 minutes from now, and must be between 60 seconds and 1 day ahead. If you leave it out,openmoves 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,openkeeps 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
InvalidParamsErrorbefore 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
SwapQuoteErrorbefore 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
rpcUrlsto 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
InsufficientGasErrorfrom the simulation. - The protocol fee is taken from the swapped USDC, and the quoted output already accounts for it.
quote.inputAmountis in the input token's units. The order's USDC escrow is derived atopentime, so a slow user gets a floor priced right before they sign, and the swap is priced again rather than reused fromgetQuote.
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_stakeextrinsic, or you can skip that step by paying from a Substrate account. alphaHotkeymust be the hotkey that holds the stake, because the settler can only unstake from it. A missing or insufficient stake throwsInsufficientBalanceErrorbefore 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 wholeopenif 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_staketo the address's mirror account, fund gas, approve the settler on the staking precompile (skipped if already allowed), then callopen. - In both cases, the unused gas is swept back to the coldkey. For alpha, the sweep also makes the batch atomic: if
openreverts, 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.
recipientis required. The order'suseris 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.inputAmountshows it), because the stake move needs that much headroom to clear. The alpha must sit at the coldkey, onalphaHotkey. - 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 revertedopenis caught from the batch's events and throwsEvmCallFailedError. For a native input, the TAO then stays at that address, available to the next batch. - The result has no EVM transaction:
extrinsicHashandblockHashreplacetxHash, andorder.openTxHashis unset. Progress events aresubstrate-submittedandsubstrate-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
InvalidParamsErrorfor 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 aQuoterErrorif it doesn't match. quote.substrateRecipientholds 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
outputAmountfor the same input. parseSubstrateAddress(input)is exported if you want to validate an address in your form. It returns the AccountId32, orundefinedfor 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.outputAmountis the minimum alpha the stake must yield, in rao (9 decimals). The fill reverts below it.outputAmountFormattedshows the same amount.quote.orderOutputAmountis the native TAO (in wei) written into the order.minOutputAmountandslippageBpsapply to the alpha amount.quote.alphaholds the subnet and validator hotkey.quote.substrateRecipientis the stake owner.recipientis required and must be a Substrate account (an SS58 address or a 32-byte0xAccountId32).alphaHotkeyaccepts the same formats, and is only valid withSN<netuid>outputs.- A quote requires
user(it becomes the order's EVMrecipient), as with any Substrate payout. Withexecute(), the connected wallet provides it automatically. It only matters if you callgetQuote()directly. - The SDK checks that the Quoter's data stakes to the subnet, validator, and coldkey you asked for, and throws a
QuoterErrorotherwise. Unlike a native TAO payout, the data can't be rebuilt locally, because the alpha minimum comes from the Quoter. netuidmust be between 0 and 10,000. Subnet alpha is only available on Subtensor EVM, so other destination chains throwUnsupportedRouteError.
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.