npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aori/usdm-bridge-sdk

v0.1.0

Published

Headless TypeScript SDK for the LayerZero VT API powering Aori USDM bridging — quote, swap, and status tracking without UI.

Downloads

469

Readme

@aori/usdm-bridge-sdk

Headless TypeScript SDK for the LayerZero VT (Value Transfer) API that powers the @aori/mega-swap-widget. Same quote, sign, submit, and status-tracking flow as the widget — without React, wagmi, or any UI.

Use this when you want to bridge to/from USDM on MegaETH (or any other VT-supported pair) from a backend job, a CLI, a custom UI, or a non-React framework.

Install

npm install @aori/usdm-bridge-sdk viem
# or
bun add @aori/usdm-bridge-sdk viem

viem is a required peer whether or not you use the ethers adapter — see Using ethers v6. ethers is an optional peer; install it only if you want your application code to construct the wallet client through the ethers adapter:

npm install @aori/usdm-bridge-sdk viem ethers

Configure

UsdmBridgeConfig mirrors the widget's aori.config.ts shape, minus the theme/appearance/wallet-modal fields. The example below binds the output side of every pair to USDM on MegaETH (chain 4326), which is the canonical Aori "USDM bridge" setup.

import type { UsdmBridgeConfig } from '@aori/usdm-bridge-sdk';

export const usdmBridgeConfig: UsdmBridgeConfig = {
  vtApiBaseUrl: '/api/vt',
  rpcOverrides: {
    1: '/api/rpc/1',
    10: '/api/rpc/10',
    56: '/api/rpc/56',
    143: '/api/rpc/143',
    4326: '/api/rpc/4326',
    8453: '/api/rpc/8453',
    42161: '/api/rpc/42161',
  },
  tokens: {
    defaultBase: { chainId: 1, address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' }, // USDC Ethereum
    defaultQuote: { chainId: 4326, address: '0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7' }, // USDM MegaETH
    supportedOutputTokens: [
      { chainId: 4326, address: '0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7' },
    ],
    supportedOutputChains: [4326],
  },
  walletScreening: {
    enabled: true,
    useChainalysisOracle: true,
    screeningUrl: '/api/screening',
  },
  settings: {
    defaultSlippage: 0.01,
    pollingIntervalMs: 4000,
    statusTimeoutMs: 300_000,
  },
};

getQuote rejects any pair whose input or output isn't in the configured supported* lists with UnsupportedPairError — that's how you keep one side of every quote bound to USDM.

One-shot bridge (recommended)

sdk.bridge(...) runs the entire flow — executeSwap, the deposit-chain settle delay, and pollStatus — and resolves only after the order reaches a terminal state. Use the onSuccess / onFailure / onSettled hooks to trigger code in your application when the swap completes. All three are awaited before the returned promise resolves, so you can await side effects (analytics, crediting an account, sending a push notification, …) inline:

import { UsdmBridgeSdk } from '@aori/usdm-bridge-sdk';

const sdk = new UsdmBridgeSdk(usdmBridgeConfig);

const quote = await sdk.getQuote({ /* … */ });

const result = await sdk.bridge({
  quote,
  walletClient,
  onStep:         (step)   => console.log('step:', step.kind),
  onStatusChange: (status) => console.log('status:', status.status),

  onSuccess: async (r) => {
    await fetch('/api/credit-user', {
      method: 'POST',
      body: JSON.stringify({ userId, dstTxHash: r.dstTxHash, amount: r.quote.dstAmount }),
    });
  },
  onFailure: (r) => sentry.captureMessage('bridge failed', { extra: r }),
  onSettled: (r) => analytics.track('bridge_settled', { outcome: r.outcome, quoteId: r.quoteId }),
});

if (result.outcome === 'success') {
  console.log('Settled:', result.dstTxHash, result.explorerUrl);
} else {
  console.warn('Did not settle:', result.status.status);
}

bridge() resolves regardless of outcome — result.outcome is 'success' for SUCCEEDED/COMPLETED and 'failure' for FAILED/CANCELLED. The promise only rejects for actual errors: network failures, user-rejected signing, abort, or anything thrown from your hooks. If a hook throws, bridge() propagates the error so you can fail-fast on, say, a downstream API rejecting the credit.

Cancel a bridge in flight with an AbortSignal:

const ac = new AbortController();
const promise = sdk.bridge({ quote, walletClient, abortSignal: ac.signal, onSuccess });
// later…
ac.abort();

If you'd rather drive executeSwap and pollStatus separately (e.g. to render distinct "submitting" vs "settling" UI states), the lower-level flow below still works.

Quote → Swap → Status (low-level)

import { UsdmBridgeSdk } from '@aori/usdm-bridge-sdk';
import { createWalletClient, custom } from 'viem';
import { mainnet } from 'viem/chains';
import { usdmBridgeConfig } from './usdm-bridge.config';

const sdk = new UsdmBridgeSdk(usdmBridgeConfig);

const walletClient = createWalletClient({
  account: '0xYourAddress',
  chain: mainnet,
  transport: custom(window.ethereum!),
});

// 1. Fetch a quote
const quote = await sdk.getQuote({
  srcChainId: 1,
  dstChainId: 4326,
  srcTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC Ethereum
  dstTokenAddress: '0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7', // USDM MegaETH
  amount: '100',           // decimal human amount (100 USDC)
  srcTokenDecimals: 6,     // required for string/number `amount`
  // or: amount: 100_000_000n   // bigint = raw on-chain units (no decimals needed)
  srcWalletAddress: '0xYourAddress',
});

console.log(`Quote ${quote.id}: ${quote.srcAmount} -> ${quote.dstAmount}`);

// 2. Execute (chain-switch + approval + deposit + sign + submit)
const result = await sdk.executeSwap({
  quote,
  walletClient,
  onStep: (step) => console.log('step:', step),
  onTxHash: (hash, kind) => console.log(`${kind} tx: ${hash}`),
});

console.log(`Submitted ${result.quoteId}, tx hashes:`, result.txHashes);

// 3. Track status to terminal state
const finalStatus = await sdk.pollStatus(result.quoteId, {
  txHash: result.txHashes[result.txHashes.length - 1],
  onStatusChange: (s) => console.log('status:', s.status),
});

console.log('done:', finalStatus.status, finalStatus.dstTxHash);

What executeSwap does

For each step in quote.userSteps:

| Step type | Action | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TRANSACTION | Chain-switches the wallet, decodes calldata. If approve(...) it does the allowance/USDT-style reset/maxUint256 dance and waits for receipt. Otherwise it sends as the native deposit. | | SIGNATURE | Chain-switches if needed, signs EIP-712 typed data with the wallet, then POST /submit-signature to the relayer. |

After all steps complete you get back { quoteId, signature?, txHashes, isNativeDeposit, depositChainBlockTimeMs }. If isNativeDeposit, wait 2 * depositChainBlockTimeMs before the first pollStatus call (the widget does the same).

Using ethers v6 as an integrator-side adapter

If your codebase is already on ethers and you don't want to import viem in your wallet-construction code, the SDK ships a small adapter at @aori/usdm-bridge-sdk/ethers:

import { UsdmBridgeSdk } from '@aori/usdm-bridge-sdk';
import { ethersSignerToWalletClient } from '@aori/usdm-bridge-sdk/ethers';
import { BrowserProvider } from 'ethers';

const sdk = new UsdmBridgeSdk(usdmBridgeConfig);

const provider = new BrowserProvider(window.ethereum!, 'any');
const signer = await provider.getSigner();
const walletClient = await ethersSignerToWalletClient(signer);

const quote = await sdk.getQuote(/* ... */);
await sdk.executeSwap({ quote, walletClient });

A working end-to-end React example is in examples/react-6963-ethers/ — same UI as react-6963, but with zero viem imports in user code.

What the adapter actually does (and doesn't)

The adapter is a compatibility shim, not a viem replacement. Specifically:

  • It wraps your ethers Signer in a tiny EIP-1193 transport that translates eth_sendTransaction, eth_signTypedData_v4, eth_chainId, eth_accounts, and wallet_switchEthereumChain into the corresponding ethers calls. Anything else falls through to signer.provider.send.
  • It then calls viem's createWalletClient({ transport: custom(shim) }) and returns the resulting WalletClient (typed as SwapWalletClient) for the SDK to consume.

This means:

| | Reality | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Can my application code be 100% ethers, no viem imports? | ✅ Yes. That's the whole point of the adapter. | | Does using the adapter remove viem from my bundle? | ❌ No. The SDK's swap pipeline (executeSwap, allowance reads, receipt waits, EIP-712 typed-data canonicalization, ABI codec, the Chainalysis sanctions read) is all viem-based internally, and the adapter itself constructs a viem WalletClient. Bundle size is roughly the same as the viem-flavored example. | | Is ethers a hard runtime dependency? | ❌ Optional peer. Don't install it (and don't import from /ethers) and the SDK works fine. | | Is viem a hard runtime dependency? | ✅ Always. It's the SDK's runtime regardless of which adapter you use. | | What changes between viem and ethers integration? | Only the wallet-construction code in your application. The sdk.bridge({ quote, walletClient, ... }) call site is byte-for-byte identical. |

If you want a truly lib-agnostic core (genuinely no viem in the bundle when using ethers, hard peer-dep guarantees per adapter, separate npm packages), that's a larger architectural change — open an issue to discuss.

Status tracking only

If you've already submitted via your own pipeline and just want polling:

import { UsdmBridgeSdk } from '@aori/usdm-bridge-sdk';

const sdk = new UsdmBridgeSdk({ vtApiBaseUrl: '/api/vt' });

const status = await sdk.pollStatus(quoteId, {
  txHash,
  interval: 4000,
  timeout: 300_000,
  onStatusChange: (s) => console.log(s.status),
  onSuccess: (s) => console.log('settled:', s.dstTxHash),
  onFailure: (s) => console.warn('did not settle:', s.status),
  onSettled: (s) => console.log('terminal:', s.status),
  // onComplete: (s) => …  ← legacy alias of onSettled, fires for any terminal state
});

Terminal statuses: SUCCEEDED, COMPLETED (success), FAILED, CANCELLED (failure). The semantic hooks (onSuccess / onFailure / onSettled) are awaited before pollStatus resolves; throws inside them reject the promise. Helpers isSuccessStatus, isFailureStatus, and isTerminalStatus are exported if you want to classify a status string yourself.

Server-side proxying

In production, keep your VT API key and any private RPC URLs off the client.

API proxy (vtApiBaseUrl)

Point vtApiBaseUrl at your own backend route. The SDK sends every quote/submit/status request there instead of directly to LayerZero.

new UsdmBridgeSdk({ vtApiBaseUrl: '/api/vt' /* no apiKey needed */ });

Your backend forwards to https://transfer.layerzero-api.com/v1 with the real x-api-key injected from env vars.

RPC proxy (rpcOverrides)

The SDK uses public RPCs by default for ERC20 allowance reads, receipt waits, and the Chainalysis sanctions oracle. Override per-chain:

new UsdmBridgeSdk({
  rpcOverrides: {
    1: '/api/rpc/1',
    4326: '/api/rpc/4326',
  },
});

This is independent of whatever your wallet provider uses for signing/sending — the SDK only uses these for read-side calls.

Low-level primitives

When you want full control over the orchestration (e.g. interleaving custom UI between approval and deposit), import the standalone helpers and skip executeSwap:

import {
  requestQuote,
  ChainSwitch,
  handleApprovalStep,
  sendTransactionStep,
  signAndSubmit,
  pollOrderStatus,
  SdkEnvironment,
} from '@aori/usdm-bridge-sdk';

const env = new SdkEnvironment({ vtApiBaseUrl: '/api/vt' });
const quote = await requestQuote({ /* ... */ }, { env });

for (const step of quote.userSteps) {
  if (step.type === 'TRANSACTION') {
    await ChainSwitch(walletClient, /* chainId */);
    const wasApproval = await handleApprovalStep({ step, walletClient, ownerAddress, quote, env });
    if (!wasApproval) {
      await sendTransactionStep({ step, walletClient, userAddress: ownerAddress, fallbackChainKey: quote.srcChainKey });
    }
  } else {
    await signAndSubmit({ quote, signatureStep: step, userAddress: ownerAddress, walletClient, env });
  }
}

await pollOrderStatus(quote.id, env, { onStatusChange: (s) => console.log(s.status) });

Supported chains

Built-in chain registry (extend via rpcOverrides):

| Chain ID | Key | | -------- | --------- | | 1 | ethereum | | 10 | optimism | | 30 | rootstock | | 56 | bsc | | 143 | monad | | 988 | stable | | 4326 | megaeth | | 8453 | base | | 9745 | plasma | | 42161 | arbitrum |

License

UNLICENSED — same terms as the rest of the Aori widget stack.