A TypeScript SDK for seamless cross-chain token transfers using the Omni Bridge protocol.
Important
This SDK is in beta and approaching production readiness. While core functionality is stable, some features may still change. We recommend thorough testing before using in production environments.
- 🔄 Cross-chain token transfers between Ethereum, NEAR, Solana, Base, and Arbitrum
- 🤖 Automated transfer finalization through our relayer network
- 🪙 Token deployment and management across chains
- 📖 Comprehensive TypeScript type definitions
- ⚡ Support for native chain-specific features
- 🔍 Transfer status tracking and history
npm install omni-bridge-sdk
# or
yarn add omni-bridge-sdkThe fastest way to get started is using our relayer service for automated transfer finalization:
import { omniTransfer, OmniBridgeAPI } from "omni-bridge-sdk";
// Get fees (includes relayer service fee)
const api = new OmniBridgeAPI();
const fees = await api.getFee("eth:0x123...", "near:bob.near", "eth:0x789...");
// Send tokens
await omniTransfer(wallet, {
tokenAddress: "eth:0x789...", // Token contract
recipient: "near:bob.near", // Destination address
amount: BigInt("1000000"), // Amount to send
fee: BigInt(fees.transferred_token_fee), // Includes relayer fee
nativeFee: BigInt(fees.native_token_fee),
});Here's a more detailed example showing wallet setup, error handling, and status monitoring:
import { setNetwork } from "omni-bridge-sdk";
// Set network type
setNetwork("testnet");
// 1. Setup wallet/provider
const wallet = provider.getSigner(); // for EVM
// or
const account = await near.account("sender.near"); // for NEAR
// or
const provider = new AnchorProvider(connection, wallet); // for Solana
// 2. Get fees (includes relayer service fee)
const api = new OmniBridgeAPI();
const sender = "eth:0x123...";
const recipient = "near:bob.near";
const token = "eth:0x789...";
const fees = await api.getFee(sender, recipient, token);
// 3. Send tokens
try {
const result = await omniTransfer(wallet, {
tokenAddress: token,
recipient,
amount: BigInt("1000000"),
fee: BigInt(fees.transferred_token_fee),
nativeFee: BigInt(fees.native_token_fee),
});
// 4. Monitor status
const status = await api.getTransferStatus(sourceChain, result.nonce);
console.log(`Transfer status: ${status}`);
} catch (error) {
console.error("Transfer failed:", error);
}All addresses in the SDK use the OmniAddress format, which includes the chain prefix:
type OmniAddress =
| `eth:${string}` // Ethereum addresses
| `near:${string}` // NEAR accounts
| `sol:${string}` // Solana public keys
| `arb:${string}` // Arbitrum addresses
| `base:${string}`; // Base addresses
// Helper function
const addr = omniAddress(ChainKind.Near, "account.near");Transfer messages represent cross-chain token transfers:
interface UtxoTransferOptions {
maxFee?: bigint; // Maximum BTC/Zcash network fee (in satoshis)
}
interface OmniTransferMessage {
tokenAddress: OmniAddress; // Source token address
amount: bigint; // Amount to transfer
fee: bigint; // Token fee
nativeFee: bigint; // Gas fee in native token
recipient: OmniAddress; // Destination address
message?: string; // Optional message field (for advanced use, overrides maxFee)
options?: UtxoTransferOptions; // Chain-specific options
}Chain-Specific Options: The options field allows you to specify chain-specific parameters:
- UTXO chains (Bitcoin/Zcash): Use
UtxoTransferOptionsto specify:maxFee: Maximum BTC/Zcash network fee allowed (in satoshis). This protects you from excessive fees.- Automatically converted to the nested message format:
{"MaxGasFee":"5000"}(stringified int) - The contract validates that the actual gas fee doesn't exceed this limit
- Cannot be used together with the
messagefield (use one or the other)
- Automatically converted to the nested message format:
- The protocol fee is automatically calculated by the contract based on the configured
protocol_fee_rate
Example for Bitcoin:
const transfer: OmniTransferMessage = {
tokenAddress: "near:nbtc.near",
recipient: "btc:bc1q...",
amount: BigInt(100000),
fee: BigInt(5000),
nativeFee: BigInt(1000),
options: {
maxFee: BigInt(5000) // Maximum BTC network fee in satoshis
}
}While the SDK provides methods to manually handle the complete transfer lifecycle, we recommend using our relayer service for the best user experience. Benefits include:
- Single transaction for end users
- Automated message signing and finalization
- No need to handle cross-chain message passing
- Optimized gas fees
- Simplified error handling
To use relayers, simply include the relayer fee when initiating the transfer:
const transfer = {
tokenAddress: omniAddress(ChainKind.Near, "usdc.near"),
amount: BigInt("1000000"),
fee: BigInt(feeEstimate.transferred_token_fee), // Relayer fee included
nativeFee: BigInt(feeEstimate.native_token_fee),
recipient: omniAddress(ChainKind.Eth, recipientAddress),
};
// One transaction - relayers handle the rest
const result = await omniTransfer(account, transfer);Track transfer progress using the API:
const api = new OmniBridgeAPI();
const status = await api.getTransferStatus(sourceChain, nonce);
// Status: "pending" | "ready_for_finalize" | "completed" | "failed"
// Get transfer history
const transfers = await api.findOmniTransfers(
"near:sender.near",
0, // offset
10 // limit
);const api = new OmniBridgeAPI();
const fee = await api.getFee(sender, recipient, tokenAddr);
console.log(`Native fee: ${fee.native_token_fee}`); // Includes relayer fee
console.log(`Token fee: ${fee.transferred_token_fee}`);
console.log(`USD fee: ${fee.usd_fee}`);Fee Validation: Using
api.getFee()is recommended to get accurate protocol and relayer fees. The smart contract validates fees on-chain and will reject transfers with insufficient fees. You can provide higher fees than the API suggests if desired (e.g., for priority processing or custom relayers).
Verified Action Approvals (VAAs) are cryptographic proofs used by the Wormhole protocol to verify cross-chain messages. When tokens are transferred from Solana to other chains, Wormhole Guardians observe the transaction and collectively sign a VAA that proves the transfer occurred.
- Solana → NEAR: VAA required for transfer finalization
- Solana → Any Chain: VAA needed for token deployments
- EVM → NEAR: EVM proof required instead of VAA
- NEAR → Any Chain: MPC signature used instead of VAA
import { getVaa } from "omni-bridge-sdk";
// Get VAA after Solana transaction (may take 30-60 seconds)
const vaa = await getVaa(txHash, "Testnet");
// Use VAA for finalization on NEAR
await nearClient.finalizeTransfer(tokenId, recipient, storageDeposit,
ChainKind.Sol, vaa, undefined, ProofKind.InitTransfer);Note: VAAs may take 30-60 seconds to become available after transaction confirmation. Implement retry logic for production applications.
For applications requiring manual control over the transfer process, the SDK provides complete access to underlying bridge functions. Each chain requires specific handling:
// Basic manual flow pattern
const txHash = await sourceClient.initTransfer(transferMessage);
const proof = await getProof(txHash); // VAA for Solana, EVM proof for Ethereum
await destinationClient.finalizeTransfer(tokenId, recipient, proof);Complete Examples: See the
e2e/directory for working end-to-end transfer examples, including Solana→NEAR, Ethereum→NEAR, and NEAR→Ethereum flows with full error handling.
Warning
NEAR Wallet Integration Notes: When using browser-based NEAR wallets through Wallet Selector, transactions involve page redirects. The current SDK doesn't fully support this flow - applications need to handle redirect returns and transaction hash parsing separately. For production applications, consider using near-api-js with a direct key approach or implement custom redirect handling.
Token deployment follows a multi-step process depending on source and destination chains:
// Basic token deployment pattern
await sourceClient.logMetadata(tokenAddress);
// ... wait for proof/signature generation ...
const { tokenAddress } = await destinationClient.deployToken(proof, metadata);
// ... binding step if deploying FROM NEAR ...Key Requirements:
- NEAR→Foreign: 4 steps (logMetadata → wait → deployToken → bindToken)
- Foreign→NEAR: 3 steps (logMetadata → wait → deployToken)
- All cross-chain deployments require the token to first exist on NEAR
Complete Guide: See
docs/token-deployment.mdfor detailed deployment instructions with full code examples for all supported chain combinations.
The SDK provides detailed error messages for common failure scenarios:
try {
const result = await omniTransfer(wallet, transferMessage);
} catch (error) {
if (error.message.includes("Insufficient balance")) {
// Handle balance errors
} else if (error.message.includes("No VAA found")) {
// VAA not ready yet - implement retry logic
} else if (error.message.includes("Token already exists")) {
// Token deployment conflicts
}
// ... handle other error types
}Note: For VAA operations, implement retry logic as VAAs may take 30-60 seconds to become available. For comprehensive error handling patterns, see the examples in
e2e/test files.
Each supported chain has specific requirements:
- Account must exist and be initialized
- Sufficient NEAR for storage and gas
- Token must be registered with account
- Supports both near-api-js and Wallet Selector
- Sufficient ETH/native token for gas
- Token must be approved for bridge
- Valid ERC20 token contract
- Sufficient SOL for rent and fees
- Associated token accounts must exist
- SPL token program requirements
Currently supported chains:
- Ethereum (ETH)
- NEAR
- Solana (SOL)
- Arbitrum (ARB)
- Base
# Install dependencies
bun install
# Build
bun run build
# Run tests
bun run test
# Type checking
bun run typecheck
# Linting
bun run lintMIT