@avail-project/nexus-core

中文文档
3.2.0发布于 2026/11/05MITTypeScriptESM + CJS构建来源已验证近 30 天下载 2405

Nexus headless SDK for cross-chain transactions

原始使用文档
文档目录11 节
  1. Install and connect
  2. Choose an operation
  3. Discover assets and balances
  4. Swap
  5. Review quotes and preview rates
  6. Progress and results
  7. Execute a contract call
  8. Fund and execute
  9. Provider destination execution
  10. History and errors
  11. Migrating an existing integration

@avail-project/nexus-core

Headless TypeScript SDK for cross-chain swaps, same-asset bridging, wallet balances, and EVM contract execution. Bring your own wallet connection and user interface.

Install and connect

npm install @avail-project/nexus-core
import { createNexusClient, type EthereumProvider } from '@avail-project/nexus-core';
import { formatUnits, parseUnits } from '@avail-project/nexus-core/utils';

const client = createNexusClient({ clientId: 'your-app-name', network: 'mainnet' });
await client.initialize();

// Use the EIP-1193 provider supplied by your wallet connector.
declare const provider: EthereumProvider;
await client.setEVMProvider(provider);

clientId is a required, non-empty, stable application identifier. Catalog queries and exact-input quote previews need initialization but no wallet. Execution and wallet balances need a connected provider. Recreate the client after account/provider changes and call client.destroy() on cleanup.

mainnet and canary use mainnet chains. testnet supports standalone execution only; intent operations and async intent catalog helpers reject with ENVIRONMENT_NOT_SUPPORTED.

Choose an operation

NeedMethod
Receive a specified destination amountswapWithExactOut
Spend specified source amountsswapWithExactIn
Bridge the same asset across chainsEither swap method, with that asset's address on each chain
Fund a destination contract call, then execute itswapAndExecute
Execute using funds already on the destinationexecute
Estimate a standalone contract callsimulateExecute

Amounts are raw bigint values. Resolve tokens by chain ID and contract address, then use their own decimals with parseUnits / formatUnits. Native-token addresses come from catalog metadata. Routes, providers, and fees are determined by the quote.

Discover assets and balances

const chains = client.getSupportedChains();
const swapChains = chains.filter((chain) => chain.capabilities.intent);
const executeChains = chains.filter((chain) => chain.capabilities.execute);

const page = await client.getTokensByChain(8453, { symbol: 'USDC', limit: 25 });
const baseUsdc = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const destination = { chainId: 8453, tokenAddress: baseUsdc } as const;
const token = await client.getToken(destination);
const amountRaw = parseUnits('10', token.decimals);

const balances = await client.getBalances();
for (const balance of balances) {
  console.log(balance.symbol, formatUnits(balance.usableBalanceRaw, balance.decimals), balance.usable);
}

Balances expose actualBalanceRaw (full wallet balance) and usableBalanceRaw (available for routing after gas reserves) as bigint. balanceRaw is a deprecated alias of usableBalanceRaw. valueUsd values the usable balance and is null when no price is available.

Catalog methodReturns
getSupportedChains()Cached chain metadata, without token lists
getTokens(query?)Token page across chains
getTokensByChain(chainId, query?)Token page on one chain
getToken({ chainId, tokenAddress })Exact token metadata
getAvailableSourceTokens(destination, selectedSources?, query?)Page of provider groups containing chains and tokens
getAvailableDestinationTokens(sources, query?)Page of chains containing destination tokens
confirmRouteExists(sources, destination)Catalog compatibility as a boolean
getSupportedChainsForRoute(constraints)Chain metadata matching route constraints

Token queries accept chainId, providers, name, symbol, contract, includeUnverified, offset, and limit. Filters combine with AND. Pages default to offset 0 and limit 50; the maximum limit is 1000. Token pages default to verified assets; opt in with includeUnverified: true. Exact lookup can resolve unverified tokens. Balances include both; respect verified and usable.

Advance pagination by page.offset + page.limit while that is less than page.total. Compatibility pages count candidates before filtering, so an empty page does not mean there are no later matches. Reset the offset when filters or selections change. Keep selections outside the displayed page; returned tokens use address, while selections use tokenAddress. Deduplicate provider groups by chain ID plus address.

For exact-input pickers, pass existing sources to getAvailableSourceTokens so all selections share one provider. Exact-output sources are alternatives: use getAvailableSourceTokens(destination, [], query). confirmRouteExists requires a provider shared by all sources and does not guarantee a quote. Lookup failures reject separately from empty pages.

Route constraints accept sources, destinations, providers, and valueUsd. Legs accept chainId?, tokenAddress?, and amountRaw?; amounts require both identity fields. Use the same fields on each leg of one side, and at most one sizing mode: source amounts, destination amounts, or USD value. Amounts must be non-negative. Token queries do not inherit route constraints.

Swap

Exact output specifies what to receive. Omit sources (or pass []) for automatic wallet funding; explicit sources restrict eligible chains and tokens. toNativeAmountRaw optionally requests destination native funds for gas.

const result = await client.swapWithExactOut({
  toChainId: destination.chainId,
  toTokenAddress: destination.tokenAddress,
  toAmountRaw: amountRaw,
  sources: [{ chainId: 1, tokenAddress: '0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48' }],
});
console.log(result.intentId, result.intentExplorerUrl);

Exact input specifies every source's positive raw amount. All sources and the destination must share one provider.

const source = {
  chainId: 1,
  tokenAddress: '0xA0b86991c6218b36c1d19d4a2e9eb0cE3606eB48',
} as const;
const sourceToken = await client.getToken(source);
await client.swapWithExactIn({
  sources: [{ ...source, amountRaw: parseUnits('5', sourceToken.decimals) }],
  toChainId: destination.chainId,
  toTokenAddress: destination.tokenAddress,
});

Review quotes and preview rates

Pass options.hooks.onIntent to display a confirmation UI. The operation waits for allow() or deny(); returning from the callback alone does not accept it. Without a hook, quotes are accepted automatically. ERC-20 approvals use the minimum required amount; wallets may also prompt for approval signatures and source transactions.

import type { IntentHookData, SwapOperationOptions } from '@avail-project/nexus-core';

declare function showReview(hook: IntentHookData): void;
const options: SwapOperationOptions = {
  hooks: { onIntent: showReview },
  slippageBps: 50,
  fillTimeoutMinutes: 2,
  pollingIntervalMs: 2000,
};

Pass options as the second argument to either swap method. These are the default option values. Slippage accepts an integer from 0 to 10000 or 'auto'. The review UI uses hook.quote, hook.execution, hook.allow(), hook.deny(), and await hook.refresh(sources?). Refresh returns a complete replacement quote; render that return value and read hook.execution again. Await refresh before allowing. Exact-input replacement sources must still carry token addresses and raw amounts. The quote is fixed after acceptance.

Quotes expose input, output, output.minAmountRaw, fees, allowances, plan, sourceVerdicts, and expiresAt (Unix seconds). Amounts ending in Raw are bigint; USD quote amounts are decimal strings. quote.isExecutable is a source-balance check; quote.executionWarnings describes any shortfalls.

swapWithExactIn can preview rates before wallet connection. Its hook exposes:

type QuoteExecution =
  | { possible: true }
  | { possible: false; cause: 'not-connected' | 'insufficient-balance' };

A preview remains disconnected throughout that operation. After connecting, start a new swap call and review its fresh quote. allow() on a disconnected preview rejects with WALLET_NOT_CONNECTED; deny() rejects as a user denial. There is no successful preview result. Without a hook, the quote event is emitted and the call rejects. Preview quotes can also fail if source approval gas is missing. Connected quotes with insufficient balances cannot execute; fund the wallet and refresh. Exact-output and composite operations require a wallet.

Progress and results

options.onEvent receives IntentEvent:

  • quote: the current quote and plan.
  • step: step, state (started, completed, or failed), committed, and optional errorDetails.
  • status: intentId, status (created, deposited, fulfilled, or expired), substatus, and per-source legs with transaction links and errors.
import type { IntentEvent } from '@avail-project/nexus-core';

const onEvent = (event: IntentEvent): void => {
  switch (event.type) {
    case 'quote':
      console.log('Quote updated', event.quote.output, event.quote.fees, event.quote.plan.steps);
      break;
    case 'step':
      console.log('Step progress', event.step.type, event.state);
      if (event.state === 'failed') {
        console.error(event.errorDetails?.message, event.errorDetails?.code);
        // A committed attempt may still settle; check its status before retrying.
        console.log('Intent committed', event.committed);
      }
      break;
    case 'status':
      console.log('Intent status', event.intentId, event.status, event.substatus);
      for (const leg of event.legs) {
        console.log('Source progress', leg.sourceIndex, leg.status, leg.txExplorerUrl, leg.error);
      }
      break;
  }
};

const swapOptions: SwapOperationOptions = { ...options, onEvent };

Pass swapOptions as the second argument to either swap method. Replace the logging with your application's progress UI; the error example below uses these same options.

Plan step types are erc20_approval, source_approval_signature, intent_signature, native_transaction, intent_submission, and intent_fulfillment. Event callback failures do not interrupt the operation. Use errorDetails instead of the deprecated step error string.

Swap promises resolve after fulfillment with IntentResult: intentId, intentExplorerUrl, quote, status, approvals, nativeTransactions, and optional attemptId. Track attemptId alongside intentId for support. A rejected promise may follow already-submitted transactions.

Execute a contract call

import type { ExecuteParams } from '@avail-project/nexus-core';

declare const contract: ExecuteParams['to'];
declare const calldata: ExecuteParams['data'];
const executeParams: ExecuteParams = {
  toChainId: destination.chainId,
  to: contract,
  data: calldata,
  value: 0n,
  tokenApproval: {
    toTokenAddress: destination.tokenAddress,
    amount: amountRaw,
    spender: contract,
  },
};
const estimate = await client.simulateExecute(executeParams);
const executed = await client.execute(executeParams);
console.log(estimate.estimatedGasUnits, executed.execute.txExplorerUrl);

Omit tokenApproval when none is required; native-token transfers use value. Simulation requires a wallet and estimates approval gas when needed. Execution returns execute, optional approval, and chainId. By default it waits for a receipt; waitForReceipt: false returns after broadcast. Receipt controls are receiptTimeout (milliseconds, default 300000) and requiredConfirmations (default 1). A submitted transaction can still fail after a broadcast-only result.

Fund and execute

swapAndExecute defaults to tradeType: 'exactOutput': specify toAmountRaw, and the SDK funds the destination shortfall or skips the swap when the selected balances already cover execution.

const composed = await client.swapAndExecute(
  {
    tradeType: 'exactOutput', // Optional; this is the default.
    toChainId: destination.chainId,
    toTokenAddress: destination.tokenAddress,
    toAmountRaw: amountRaw,
    execute: {
      to: contract,
      data: calldata,
      gas: 350_000n,
      tokenApproval: executeParams.tokenApproval,
    },
  },
  {
    hooks: {
      onIntent({ intent, allow }) {
        console.log(intent.executeRequirement, intent.available, intent.shortfall);
        allow(); // In an app, call after the user confirms.
      },
    },
  },
);
console.log(composed.swapSkipped, composed.executeAmountRaw);
if (composed.executionMode === 'wallet') console.log(composed.execute.txExplorerUrl);
if (!composed.swapSkipped) console.log(composed.swapResult.intentExplorerUrl);

Supply a positive execute.gas estimate appropriate to your contract call. The SDK accounts for execution gas and value, funds the destination shortfall, then executes after fulfillment. The composite hook receives intent, not quote; it runs even if funding is already sufficient. intent.quote exists only when intent.swapRequired is true.

For exact input, set tradeType: 'exactInput', supply a positive amountRaw on every source, and omit toAmountRaw. Sources matching the destination chain and token stay in the wallet and are reserved for execution. Other sources enter the swap with their fixed amounts and must share one provider with the destination. If every source is already the destination token, the swap is skipped; ERC-20 execution still needs native gas funds on that chain.

declare function buildDepositData(amountRaw: bigint): NonNullable<ExecuteParams['data']>;

await client.swapAndExecute(
  {
    tradeType: 'exactInput',
    sources: [{ ...source, amountRaw: parseUnits('5', sourceToken.decimals) }],
    toChainId: destination.chainId,
    toTokenAddress: destination.tokenAddress,
    execute: {
      to: contract,
      data: calldata,
      gas: 350_000n,
      tokenApproval: {
        toTokenAddress: destination.tokenAddress,
        amount: 0n, // The SDK sets the final approval amount after funding.
        spender: contract,
      },
    },
  },
  {
    hooks: {
      onIntent({ intent, allow }) {
        console.log(intent.executeRequirement.token.amountRaw, intent.quote?.output);
        allow(); // In an app, call after the user confirms.
      },
    },
    beforeExecute: async ({ receivedAmountRaw, executeAmountRaw }) => {
      console.log('Actual swap proceeds', receivedAmountRaw);
      return { data: buildDepositData(executeAmountRaw) };
    },
  },
);

The review hook shows an estimate based on the quote's minimum output plus selected local tokens. After review, the SDK reads the destination's on-chain balance immediately before funding and again after fulfillment. beforeExecute receives BeforeExecuteContext:

  • receivedAmountRaw: the balance difference across funding, or zero when the swap was skipped.
  • executeAmountRaw: for exact input, actual proceeds plus selected local tokens, after native execution reserves; for exact output, the requested toAmountRaw.

For example, a wallet starting with 10 destination USDT and selecting 5 of it for execution keeps those 5 locally. If funding raises the balance to 140 USDT, the hook receives 130 USDT of proceeds and 135 USDT for execution, leaving the unselected 5 untouched.

Balance differences include unrelated transfers during funding. Missing selected local funds, failed balance reads, non-positive swap proceeds, or insufficient native execution reserves stop execution. Native output reserves execute value and gas; non-native output requests a gas drop when needed. When tokenApproval.toTokenAddress matches the exact-input destination, the SDK reserves approval gas, then rechecks allowance and builds approval for executeAmountRaw after the hook returns. The developer supplies the updated calldata; approval construction stays in the SDK. When every input is local, the amount is already known, so sufficient allowance avoids reserving unneeded approval gas.

Composite refresh(sources?) returns an updated intent with current fee estimates and a replacement funding quote when needed. It reuses the original balance snapshot; start a new operation to reread balances. Omitted sources retain the selection. For exact output, [] clears it; exact-input replacement sources must be non-empty and include token addresses and positive raw amounts. Top-level beforeExecute can return { data?, value?, gas? } after funding, but overrides do not recalculate funding. Use executeAmountRaw to prepare amount-sensitive calldata through this hook. Callbacks that ignore the context remain supported. Intent events cover funding; the composite promise covers the subsequent contract call too.

Provider destination execution

Set useProviderDestinationExecution: true on swapAndExecute to require Relay to approve and execute the destination call. swapWithExactIn and swapWithExactOut remain ordinary swaps. Reuse execute.to, execute.data, execute.value, and execute.tokenApproval; the SDK encodes the approval before the execution call. The destination contract must support acting on behalf of the recipient because its caller is Relay's router.

const deposited = await client.swapAndExecute(
  {
    tradeType: 'exactInput',
    sources: [{ ...source, amountRaw: parseUnits('5', sourceToken.decimals) }],
    toChainId: destination.chainId,
    toTokenAddress: destination.tokenAddress,
    execute: {
      to: contract,
      gas: 350_000n,
      tokenApproval: { toTokenAddress: destination.tokenAddress, amount: 0n, spender: contract },
    },
  },
  {
    useProviderDestinationExecution: true,
    beforeExecute: async ({ executeAmountRaw }) => ({ data: buildDepositData(executeAmountRaw) }),
    hooks: {
      onIntent({ intent, allow }) {
        console.log(intent.executionMode, intent.executeRequirement.token.amountRaw, intent.quote);
        allow(); // In an app, call after the user confirms.
      },
    },
  },
);
if (deposited.executionMode === 'provider') {
  console.log(deposited.executeAmountRaw, deposited.swapResult.intentExplorerUrl);
  // No wallet execution transaction: deposited.execute and deposited.approval are undefined.
}

With this option omitted or false, the wallet flow described above remains the default. With it set to true, an unsupported route rejects before signing and never falls back to wallet execution. The middleware's quote.supportsDestinationCalls flag determines support from the funding legs actually selected: multiple candidates are allowed, but only one chain/token leg may fund the route. Multiple selected legs, or older middleware without the flag, reject.

Provider execution funds the entire call through the quote. Existing destination wallet balances are not subtracted or implicitly added to the deposit. Exact-input sources matching the destination chain/token enter the quote as explicit source inputs too. The SDK never spends additional wallet tokens after fulfillment. Only the output token can be approved; native execution value must come from native output and fit within the quoted execution budget. ERC-20 output with native call value is unsupported in provider mode.

Exact output quotes the requested toAmountRaw without wallet execution gas. Exact input first previews a Relay quote, then fixes the execution amount below its guaranteed minimum. The reserve is 1% of that minimum (rounded up to raw units), capped at $1 using the quote's USD valuation (rounded down to raw units). Unpriced output, or output too small to represent a positive reserve and execution amount, rejects. This reserve is separate from swap slippage.

In provider mode, beforeExecute runs before review to prepare the encoded call, and again on every refresh. receivedAmountRaw is the preview's guaranteed minimum (the requested amount for exact output), not measured proceeds; executeAmountRaw is the fixed execution budget. Use the latter for amount-sensitive calldata or native value. Callbacks should only prepare the call and must be safe to repeat. The SDK quotes those calls and rejects a final minimum below the encoded budget before signing. Refresh commits the new calls, budget, and final quote together; a failed refresh retains the previous intent.

The composite review exposes executionMode: 'provider', the fixed deposit, and zero wallet gas requirements; provider execution costs are included in the quote. Source approvals and intent signatures may still require wallet prompts. Fulfillment completes provider execution, with remaining output going to the recipient. The result exposes executeAmountRaw and swapResult, with no fabricated execute or approval transaction. In wallet mode, executeAmountRaw is the actual execution allocation and the existing transaction results remain available.

History and errors

const history = await client.listIntents({ page: 1, status: 'fulfilled' });
console.log(history.intents, history.total);

All SDK errors extend NexusError and carry category, code, context, and optional details. Use UserActionError for denials and ERROR_CODES for specific recovery actions. getIntentQuoteFailure(error) extracts structured routing, amount, balance, approval-gas, and price diagnostics. Handle codes rather than parsing message text. See the error reference.

Quote error constant (ERROR_CODES.*)Recovery
BACKEND_VALUE_ABOVE_CEILINGReduce the value; getIntentQuoteFailure(error)?.details.maxValueUsd is the USD limit
BACKEND_INPUT_BELOW_DEPOSIT_FEEIncrease the source amount to cover the deposit fee
BACKEND_NO_ROUTE_TO_DESTINATIONChoose a destination supported by the allowed providers
BACKEND_NO_ROUTABLE_SOURCEReview source verdicts and adjust the sources or mixed failure reasons

The middleware reports a specific amount or destination failure when all unroutable sources agree. These failures have retryable: false; change the request before retrying. Source verdicts can include DESTINATION_NOT_SERVED. A destination unsupported by every provider still follows token-not-supported handling.

import {
  ERROR_CODES,
  NexusError,
  UserActionError,
  getIntentQuoteFailure,
} from '@avail-project/nexus-core';

try {
  const swap = await client.swapWithExactOut(
    {
      toChainId: destination.chainId,
      toTokenAddress: destination.tokenAddress,
      toAmountRaw: amountRaw,
    },
    swapOptions,
  );
  console.log('Fulfilled', swap.intentExplorerUrl);
} catch (error) {
  if (error instanceof UserActionError) {
    console.info('Action declined', error.code);
  } else if (error instanceof NexusError) {
    switch (error.code) {
      case ERROR_CODES.WALLET_NOT_CONNECTED:
        console.info('Connect a wallet, then request a fresh quote.');
        break;
      case ERROR_CODES.BACKEND_INSUFFICIENT_BALANCE:
        console.info('Fund the wallet or choose different source assets.');
        break;
      case ERROR_CODES.BACKEND_INSUFFICIENT_APPROVAL_GAS:
        console.info('Add native gas funds on the source chain or choose another source.');
        break;
      default:
        console.error(error.message, error.code, error.context);
    }

    const failure = getIntentQuoteFailure(error);
    if (failure) console.log('Quote diagnostics', failure.subcode, failure.sourceVerdicts);
    if (error.details?.approvals) console.log('Broadcast approvals', error.details.approvals);
  } else {
    throw error;
  }
}

Use the messages to update your UI and retain the error code/context for support. Event handlers show progress; the operation's promise must still be caught to handle the final failure.

After a timeout or connection failure following commitment, check history, the explorer, and known transaction hashes before retrying. The intent may still fulfill. Approval failures can expose error.details.approvals with confirmed, reverted, or unconfirmed states. An unconfirmed transaction may still confirm; use a fresh quote when starting another operation.

Product analytics can be disabled with analytics: { enabled: false } in the client config. Diagnostic logging is configured separately; see the telemetry reference.

Migrating an existing integration

Use address-based swaps for removed bridge / bridgeAndTransfer methods and swapAndExecute for bridgeAndExecute. There is no recipient override or standalone swap simulation method; use quote review through onIntent. getBalancesForSwap is a deprecated alias of getBalances.

Remove local max/routing helpers, Safe and ephemeral-key configuration, onAllowance, and old bridge/swap plan events. Use options.hooks, IntentQuote, IntentEvent, and IntentResult. Utility functions are available from @avail-project/nexus-core/utils and client.utils.

版本与发布记录

@avail-project/nexus-core 共发布 129 个版本,首次发布于 2025/09/26;过去 12 个月发布了 93 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.2.0latest2026/11/052 天前
3.1.02026/10/2117 天前
3.0.12026/10/0830 天前
3.0.02026/10/051 个月前
2.6.02026/09/281 个月前
2.5.02026/09/161 个月前

其他发布通道:dev-0 → 0.0.2-dev.0,stable → 0.0.1,002beta1 → 0.0.2-beta.1,002beta2 → 0.0.2-beta.2,002beta3 → 0.0.2-beta.3,beta-5 → 0.0.2-beta.5。

关于 @avail-project/nexus-core 的常见问题

@avail-project/nexus-core 是什么?

Nexus headless SDK for cross-chain transactions

如何安装 @avail-project/nexus-core?

在项目目录运行 npm install @avail-project/nexus-core,或使用 pnpm add / yarn add / bun add。

@avail-project/nexus-core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@avail-project/nexus-core 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@avail-project/nexus-core 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@avail-project/nexus-core 可以商用吗?

@avail-project/nexus-core 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@avail-project/nexus-core 的下载量有多少?

根据 npm 官方下载统计,@avail-project/nexus-core 在 2026/10/07 – 2026/11/05 这 30 天内被下载 2,405 次,日均约 80 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@avail-project/nexus-core 还在维护吗?

最新版本 3.2.0 发布于 2026/11/05(2 天前);至今共发布 129 个版本,其中过去 12 个月发布了 93 个;首次发布于 2025/09/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。