@avalabs/unified-asset-transfer
v0.1.0
Published
<p align="center"> <a href="https://subnets.avax.network/"> <picture> <img alt="Avalanche Logo" src="https://images.ctfassets.net/gcj8jwzm6086/Gse8dqDEnJtT87RsbbEf4/1609daeb09e9db4a6617d44623028356/Avalanche_Horizontal_White.svg" width="auto" heig
Maintainers
Keywords
Readme
What is this?
@avalabs/unified-asset-transfer provides a single, ergonomic interface to move assets within and across chains. It aggregates multiple services (bridges, DEX aggregators, etc.), streams time-sensitive quotes, executes transfers with your custom signers, and tracks progress until completion.
Current integrations include:
- Avalanche EVM transfer service (Avalanche ↔ Ethereum flows, native and ERC-20 flows where supported)
- Lombard BTC ↔ BTC.b bridging services
- Markr (swaps and cross-chain swaps via Markr Orchestrator)
- Wrap/unwrap service for native ↔ wrapped native assets on supported EVM chains
Installation
This package is ESM-first. Install along with peer dependencies you use in your app:
# with pnpm
pnpm add @avalabs/unified-asset-transfer @solana/kit viem zod
# with npm
npm install @avalabs/unified-asset-transfer @solana/kit viem zod
# with yarn
yarn add @avalabs/unified-asset-transfer @solana/kit viem zodQuickstart
Create a TransferManager by specifying an environment and enabling one or more services via initializers. Provide signers that match the services you enable.
import {
createTransferManager,
Environment,
ServiceType,
AVALANCHE_MAINNET_CHAIN,
ETHEREUM_MAINNET_CHAIN,
TokenType,
type EvmSigner,
type Asset,
} from '@avalabs/unified-asset-transfer';
import { parseUnits } from 'viem';
// Minimal EVM signer. You can ignore the provided `dispatch` and
// relay with your wallet directly, returning the tx hash.
const evmSigner: EvmSigner = {
sign: async (tx, _dispatch, step) => {
// Example using an injected EIP-1193 provider
const hash: `0x${string}` = await (window as any).ethereum.request({
method: 'eth_sendTransaction',
params: [
{
from: tx.from,
to: tx.to ?? undefined,
data: tx.data ?? undefined,
value: tx.value ? `0x${tx.value.toString(16)}` : undefined,
maxFeePerGas: tx.maxFeePerGas ? `0x${tx.maxFeePerGas.toString(16)}` : undefined,
maxPriorityFeePerGas: tx.maxPriorityFeePerGas ? `0x${tx.maxPriorityFeePerGas.toString(16)}` : undefined,
},
],
});
return hash;
},
};
// Enable services you want to use
const transferManager = await createTransferManager({
environment: Environment.PROD,
serviceInitializers: [
{ type: ServiceType.AVALANCHE_EVM, evmSigner },
{
type: ServiceType.MARKR,
evmSigner,
markrApiUrl: 'https://orchestrator.markr.io', // Optional, defaults to use proxy endpoint.
markrApiToken: process.env.MARKR_API_TOKEN, // Optional, not needed if using default proxy url.
markrAppId: process.env.MARKR_APP_ID!, // Required.
},
],
});
// Build a quoting intent
const sourceChain = ETHEREUM_MAINNET_CHAIN;
const targetChain = AVALANCHE_MAINNET_CHAIN;
const sourceAsset: Asset = sourceChain.networkToken; // e.g., ETH
const targetAsset: Asset = {
name: 'Wrapped Ether',
symbol: 'WETH.e',
decimals: 18,
type: TokenType.ERC20,
address: '0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab',
};
const from = '0xYourAddress';
const to = from;
const quoter = transferManager.getQuoter({
amount: parseUnits('0.05', sourceAsset.decimals),
fromAddress: from,
slippageBps: 100, // 1%
sourceAsset,
sourceChain,
targetAsset,
targetChain,
toAddress: to,
});
// Subscribe for streaming quotes. Subscription starts immediately.
const unsubscribe = quoter.subscribe((event, payload) => {
if (event === 'quote') {
console.log('New quote', payload.bestQuote);
}
if (event === 'error') {
console.error('Quote error', payload);
}
if (event === 'done') {
console.log('Quote stream ended', payload.reason);
}
});
// ...later, stop streaming and pick the latest best quote
// This just simulates waiting for the user to start a "swap" after seeing quotes
// and selecting one to execute.
await wait(5000);
unsubscribe();
// This is a convenience method to get the latest quotes after streaming has stopped, but you can also maintain the latest quote in your own state from the subscription handler, which is recommended.
const [best, allQuotes] = quoter.getQuotes();
if (!best) throw new Error('No quotes available');
// Estimate the native fee for the transfer
const estimatedNativeFee = await transferManager.estimateNativeFee(best, { feeUnitsMarginBps: 2000 });
console.log('Estimated Native Fee (wei):', estimatedNativeFee.totalFee.toString());
// Execute the transfer
const transfer = await transferManager.transferAsset({
quote: best,
gasSettings: {
estimateGasMarginBps: 1500, // Adds 15% margin to the gas estimate to help prevent gas failures.
// maxFeePerGas, maxPriorityFeePerGas can be provided for EIP-1559 wallets
},
onStepChange: (step) => {
console.log('Signature step', step);
},
});
// Track until finalized (optional)
const { cancel, result } = transferManager.trackTransfer({
transfer,
updateListener: (updated) => console.log('Transfer update', updated),
});
// await cancel(); // to stop tracking early
const finalized = await result;
console.log('Finalized transfer', finalized);API Overview
createTransferManager(options)
Creates and returns a TransferManager that aggregates the services you enable.
type CreateTransferManagerOptions = {
environment: Environment; // dev | production | test
serviceInitializers: readonly [ServiceInitializer, ...(readonly ServiceInitializer[])];
fetch?: Fetch;
};TransferManager
id: string: Unique identifier for the transfer manager instance.status(): TransferManagerStatus: Returns initialization/status details per requested service.getAssetBridgeMap({ sourceAsset, sourceChainId }): Promise<AssetBridgeMap | null>: Given a source asset and chain, get possible bridge target assets per chain.getSupportedChains(): Promise<ReadonlyMap<Caip2ChainId, ReadonlySet<Caip2ChainId>>>: Supported source → destination chain pairs across initialized services.getQuoter(props: QuoterProps, options?: QuoterOptions): QuoterInterface: Create a streaming quote aggregator.estimateNativeFee(quote, options?): Promise<NativeFeeEstimate>: Service-aware native fee estimate.getMinimumTransferAmount(props): Promise<{ [key in ServiceType]?: bigint } | null>: Minimum amount per service for a given transfer intent.transferAsset({ quote, gasSettings?, onStepChange? }): Promise<Transfer>: Execute the selected quote.trackTransfer({ transfer, updateListener }): { cancel, result }: Track a transfer to completion.
Quoter
subscribe(handler): Starts streaming on first subscriber; returns unsubscribe.getQuotes(): [bestQuote: Quote | null, allQuotes: readonly Quote[]]: Snapshot of current active quotes.
Quoter behavior
The Quoter aggregates all eligible initialized services for a single transfer intent and emits a unified stream of events:
quote: emitted whenever a service returns a quote; payload includesbestQuote, the latestquote, and current activequotes.error: emitted when a service attempt errors; this is non-terminal by itself.done: terminal event with one of these reasons:unsubscribed: all subscribers removed.no-eligible-services: no initialized service can serve the requested pair.no-quotes: all eligible services have completed and no quote was produced.
Lifecycle notes:
- Streaming starts lazily on first subscription.
- Quotes are maintained as active (non-expired) quotes and internally re-ranked as updates arrive.
- Services with active quotes are refreshed before quote expiry.
- Services that finish without quotes are only retried after at least one quote has already been observed during the current session.
- If all eligible services finish during first pass with no quotes, quoter completes immediately with
done: no-quotes.
Types and Constants
Key exports are available from the package entry:
- Chains:
AVALANCHE_MAINNET_CHAIN,AVALANCHE_FUJI_CHAIN,ETHEREUM_MAINNET_CHAIN,ETHEREUM_SEPOLIA_CHAIN,BITCOIN_MAINNET_CHAIN,BITCOIN_TESTNET_CHAIN,SOLANA_MAINNET_CHAIN,SOLANA_DEVNET_CHAIN. - Enums:
Environment,ServiceType,TokenType. - Types:
Asset,Chain,Quote,Transfer,EvmSigner,BtcSigner,ChainAssetMap,QuoterProps. - Errors:
SdkError,InvalidParamsError,ServiceInitializationError,ServiceUnavailableError,TimeoutError, helpers likeisSdkError(...). - Utilities: CAIP helpers from
utils/caip.
Assets and chains use CAIP-2 identifiers internally (for example, eip155:43114 for Avalanche C-Chain). See ChainAssetMap for the shape returned by getAssets().
Notes on Signers
- The EVM signer interface gives you full control over signing and broadcasting. You may:
- Submit via your wallet (ignore
dispatch) and return the tx hash, or - Use the provided
dispatch(serializedTx)to broadcast a pre-signed transaction.
- Submit via your wallet (ignore
- Some services require
fromAddressandtoAddressto be identical (e.g., Avalanche EVM service). The SDK enforces these rules per service.
Supported Services
- ✅
ServiceType.AVALANCHE_EVM - ✅
ServiceType.WRAP_UNWRAP - ✅
ServiceType.LOMBARD_BTC_TO_BTCB - ✅
ServiceType.LOMBARD_BTCB_TO_BTC - ✅
ServiceType.MARKR
Environment Support by Service
createTransferManager({ environment, serviceInitializers }) evaluates each configured service against the selected Environment.
| ServiceType | Environment.DEV (dev) | Environment.TEST (test) | Environment.PROD (production) | Notes |
| --------------------------------- | ------------------------- | --------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------- |
| ServiceType.AVALANCHE_EVM | ✅ | ✅ | ✅ | Uses Warden config for the selected environment. |
| ServiceType.WRAP_UNWRAP | ✅ | ✅ | ✅ | test uses testnet chain configs; dev and production currently use mainnet chain configs. |
| ServiceType.LOMBARD_BTC_TO_BTCB | ✅ | ✅ | ✅ | Environment maps to Lombard envs (dev → Env.dev, test → Env.ibc, production → Env.prod). |
| ServiceType.LOMBARD_BTCB_TO_BTC | ✅ | ✅ | ✅ | Environment maps to Lombard envs (dev → Env.dev, test → Env.ibc, production → Env.prod). |
| ServiceType.MARKR | ❌ | ❌ | ✅ | Production-only. In non-production, status is unsupported-environment. |
You can inspect initialization outcomes with transferManager.status() to see per-service states (initialized, error, or unsupported-environment).
License
Licensed under the Limited Ecosystem License. See the LICENSE file in this package for details.
