Multichain swaps New

Swap anything, anywhere, with NEAR Intents.

NEAR Intents inverts how trading works: instead of scripting an execution path ("bridge X, then swap on Y"), you sign a declarative statement of the outcome you want, and a competitive network of off-chain solvers races to fulfill it. Settlement is anchored in one contract on NEAR — the verifier at intents.near — which holds every participating asset as a NEP-245 multi-token balance and applies each matched trade atomically: the whole bundle settles, or nothing does. @fastnear/intents makes the protocol usable from the browser (IIFE global nearIntents), Node.js, and AI agents.

Canonical facts
  • Verifier: intents.near on NEAR mainnet (there is no public testnet deployment). Internal ledger token ids look like nep141:wrap.near or nep141:eth-0xdac…ec7.omft.near for bridged assets.
  • 30+ chains via three bridge routes (PoA, Omni, HOT) — Bitcoin, Ethereum and its L2s, Solana, TON, Tron, XRP, Stellar, and more — all settling through the same NEAR contract.
  • Signing standards: NEP-413 for NEAR wallets and raw keys, plus erc191 (EVM), raw_ed25519 (Solana), tip191, webauthn, ton_connect, and sep53 — accounts from other ecosystems trade without holding NEAR.
  • Two integration surfaces: the hosted 1Click API (1click.chaindefuser.com) that wraps quoting, bridging, and settlement behind a deposit address, and the solver relay for direct flows.
How a swap flows

Quote, fund, sign, settle — most of it is one REST API.

Each step is a REST call, an on-chain transaction, or an off-chain signature — never a bespoke bridge script.

1 · Discover & quote REST

GET /v0/tokens lists every swappable asset. POST /v0/quote with dry: true previews real pricing for free; dry: false commits and returns a unique depositAddress.

2 · Fund on-chain

Send the input tokens to the deposit address on the origin chain — a plain transfer. Funds already inside intents.near skip this: sign a server-generated intent instead (generate-intentsubmit-intent).

3 · Sign off-chain

Intents are authorized by a NEP-413 signed message — no gas, no transaction. Solvers co-sign the mirror token_diff; the verifier requires all diffs to net to zero per token.

4 · Settle & track REST

The relay submits one execute_intents transaction; output is delivered to your recipient (withdrawing cross-chain via the bridges). Poll GET /v0/status to SUCCESS, REFUNDED, or FAILED — failures auto-refund.

Signing, exactly

NEP-413 under the hood — and the one encoding gotcha.

The signed digest is sha256( borsh(u32 231+413) ‖ borsh({ message, nonce[32], recipient, callbackUrl? }) ), signed by a full-access key (ed25519 for nearly all NEAR accounts; secp256k1 also works). For intents, message is the JSON intent body ({ signer_id, deadline, intents: [...] }), recipient is intents.near, and the 32-byte nonce is the verifier's replay nonce.

Three rules that bite
  • Full-access keys only. NEP-413 forbids FunctionCall access keys — the session keys this demo site uses for silent signing cannot authorize intents. Wallets sign with the account's own key.
  • Re-encode the signature. Wallets return NEP-413 signatures as base64; the verifier wants a curve-prefixed base58 string — ed25519:<base58>, or secp256k1:<base58> for 65-byte signatures. The signers in @fastnear/intents own that conversion — skipping it is the most common integration bug.
  • Zero-sum diffs. A token_diff lists per-token deltas (negative = you pay). Your intent alone is unbalanced; the verifier executes it together with a solver's mirror diff so every token nets to zero.
The signed MultiPayload

What actually gets submitted — to 1Click, the solver relay, or intents.near execute_intents directly.

{
  "standard": "nep413",
  "payload": {
    "message": "{\"signer_id\":\"alice.near\",\"deadline\":\"2026-08-01T00:00:00.000Z\",\"intents\":[{\"intent\":\"token_diff\",\"diff\":{\"nep141:usdc.near\":\"-1000000\",\"nep141:usdt.near\":\"1000000\"}}]}",
    "nonce": "Vij2xgAlKBKzgNPJFViEQRgYyS7p2NEiYTTY4XmT8go=",
    "recipient": "intents.near"
  },
  "public_key": "ed25519:Bn7CwuFHG8XfbcW6RFucMjZsKjnff14oscA57prVuryX",
  "signature": "ed25519:3JT69jSwZL3sv8skfnpy9PnEn5feBHppfgfRakoQs3A5vqJ6gXmW7VJuUnv2n8wounesFi7UwAkaQy1DFEuBWpVm"
}
Quickstarts

Quote in the browser console; swap from a wallet or an agent.

The full set — quote, wallet signing, Node agent swap, deposits and balances — lives in the intents key of recipes.json. Dry quotes are free, keyless, and commitment-free.

Quote a swap with live pricing

IIFE global from intents.js. Works unauthenticated — quotes then carry a 0.2% platform fee; a key from partners.near-intents.org waives it.

<script src="https://js.fastnear.com/intents.js"></script>
<script type="module">
  const oneClick = nearIntents.createOneClickClient();

  const tokens = await oneClick.tokens();
  const quote = await oneClick.quote({
    dry: true, // free preview; dry:false commits and returns a depositAddress
    swapType: "EXACT_INPUT",
    slippageTolerance: 100, // bps
    originAsset: "nep141:wrap.near",
    destinationAsset:
      "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1", // USDC on NEAR
    amount: "1000000000000000000000000", // 1 wNEAR
    depositType: "ORIGIN_CHAIN",
    refundTo: "you.near",
    refundType: "ORIGIN_CHAIN",
    recipient: "you.near",
    recipientType: "DESTINATION_CHAIN",
    deadline: new Date(Date.now() + 10 * 60_000).toISOString(),
  });

  console.log(quote.quote.amountOutFormatted, "USDC for 1 wNEAR");
</script>
Swap intents.near balances from Node.js

The INTENTS deposit type: 1Click builds the payload, the local signer signs it verbatim — no deposit transaction needed. The key stays server-side.

import { createOneClickClient } from "@fastnear/intents";
import { createLocalIntentSigner } from "@fastnear/intents/node";

const { NEAR_ACCOUNT_ID, NEAR_PRIVATE_KEY } = process.env;
const oneClick = createOneClickClient();
const signer = createLocalIntentSigner({
  accountId: NEAR_ACCOUNT_ID,
  privateKey: NEAR_PRIVATE_KEY, // full-access, server-side only
});

// Quote with depositType/refundType/recipientType "INTENTS" — the input
// funds already sit inside intents.near, so no deposit transaction is needed.
const quote = await oneClick.quote({
  dry: false,
  swapType: "EXACT_INPUT",
  slippageTolerance: 100,
  originAsset: "nep141:wrap.near",
  destinationAsset:
    "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1",
  amount: "1000000000000000000000000",
  depositType: "INTENTS",
  refundTo: NEAR_ACCOUNT_ID,
  refundType: "INTENTS",
  recipient: NEAR_ACCOUNT_ID,
  recipientType: "INTENTS",
  deadline: new Date(Date.now() + 10 * 60_000).toISOString(),
});
const { intent } = await oneClick.generateIntent({
  signerId: NEAR_ACCOUNT_ID,
  depositAddress: quote.quote.depositAddress,
});
// signPayload pins the recipient to intents.near and signs verbatim.
const signed = await signer.signPayload(intent);
const { intentHash } = await oneClick.submitIntent({ signedData: signed });
console.log(intentHash);
Wallet path and relay status

Browser signing uses createWalletIntentSigner({ wallet: nearWallet }) over nearWallet.signMessage (NEP-413), which every wallet in the connector's manifest implements. The solver relay (@fastnear/intents/relay) is wired and typed, but its quotes require a partner API key in practice — the 1Click path is the default. The funded end-to-end swap path is verified against the mainnet smoke runbook (packages/intents/MAINNET_QA.md) before being documented further.

Guardrails

Hard constraints and safe defaults.

These rules come from the machine-readable catalog — agents and humans should apply the same ones.

Hard constraints
  • The verifier is intents.near on NEAR mainnet; there is no public testnet deployment of the intents stack.
  • NEP-413 intent signatures require a full-access key, and the verifier checks the key is authorized for signer_id.
  • Submitted signatures use ed25519:<base58> encoding — not the base64 NEAR wallets return; the signers own that conversion.
  • Native NEAR is not a verifier asset: wrap to wNEAR before depositing, and exit native NEAR only via the native_withdraw intent.
  • Amounts are base-unit strings, and token_diff diffs must net to zero per token across the executed batch.
Safe defaults
  • Quote with dry: true first; commit with dry: false only when ready to fund the deposit address before it expires.
  • Keep local signer private keys in server-side secret storage, never browser code.
  • Use a partner API key from partners.near-intents.org to remove the 0.2% keyless platform fee.
  • Poll /v0/status to a terminal state (SUCCESS, REFUNDED, FAILED) and surface swapDetails on non-success.
  • Omit msg on ft_withdraw so failed withdrawals stay refundable.
Go deeper

AI agents: read the intents key in recipes.json and the NEAR Intents section of llms.txt before editing an app. Humans: the package guide is the @fastnear/intents README, the protocol docs live at docs.near-intents.org, and the verifier source is near/intents.