@rhea-finance/cross-chain-aggregation-dex
v2.0.7
Published
TypeScript SDK for a unified multi-chain Swap API with quote, transaction building, execution adapters, status, reporting, and history.
Readme
@rhea-finance/cross-chain-aggregation-dex
A TypeScript SDK for the RHEA unified multi-chain Swap API. The SDK handles quote normalization, swap building, wallet execution, EVM approvals, order submission, status polling, reporting, and history. Applications do not need to branch on routers, cross-chain status, or transaction types.
This package is a client SDK for the RHEA Cross-Chain Swap API. For endpoint contracts, request/response fields, and protocol details, see the Cross-Chain Swap API documentation.
Supported chain families: EVM, Solana, Aptos, NEAR, Tron, Bitcoin, Zcash, and Sui. How to load the product token list and chain coverage is described in Supported chains and tokens.
Supported chains and tokens
The product support list matches multi-chain-lending Trade: 25 mainnets. Use the HTTP API chain ID for fromChain / toChain in Swap calls. Token-list endpoints use the separate numeric ID shown below; for EVM chains the two IDs are the same.
| Chain | Type | HTTP / SDK chain ID | Token-list chain ID | blockchain alias |
| --- | --- | --- | --- | --- |
| Ethereum | EVM | 1 | 1 | eth |
| Optimism | EVM | 10 | 10 | op |
| Avalanche | EVM | 43114 | 43114 | avax |
| Robinhood Chain | EVM | 4663 | 4663 | robinhood |
| Katana | EVM | 747474 | 747474 | katana |
| Sonic | EVM | 146 | 146 | sonic |
| Unichain | EVM | 130 | 130 | unichain |
| Pharos | EVM | 1672 | 1672 | pharos |
| Tempo | EVM | 4217 | 4217 | tempo |
| BNB Smart Chain | EVM | 56 | 56 | bsc |
| Gnosis Chain | EVM | 100 | 100 | gnosis |
| Polygon PoS | EVM | 137 | 137 | pol |
| Monad | EVM | 143 | 143 | monad |
| X Layer | EVM | 196 | 196 | xlayer |
| Base | EVM | 8453 | 8453 | base |
| Plasma | EVM | 9745 | 9745 | plasma |
| Arbitrum One | EVM | 42161 | 42161 | arb |
| Berachain | EVM | 80094 | 80094 | bera |
| Tron | Non-EVM | tron | 195 | tron |
| Solana | Non-EVM | solana | 501 | sol |
| Sui | Non-EVM | sui | 784 | sui |
| NEAR | Non-EVM | near | 900001 | near |
| Bitcoin | Non-EVM | btc | 900002 | btc |
| Zcash | Non-EVM | zcash | 900010 | zec |
| Aptos | Non-EVM | aptos | 900012 | aptos |
"Supported" means the product can load tokens for that chain and send them into the unified quote flow. It does not guarantee a route for every pair. Live liquidity, routers, amount, and service status still decide whether a quote succeeds.
Token coverage (Unified Swap)
Token coverage is dynamic and direction-specific. The from-token list combines same-chain routing metadata with cross-chain-capable assets, while the cross-chain to-token list is maintained independently. Do not hard-code a token count or assume every from token can also be selected as a cross-chain destination; fetch both live endpoints described below.
How to load supported tokens
Use the SDK methods for application selectors. Both methods return the same normalized SwapTokenListItem[] shape, even though the two HTTP endpoints have different raw response formats:
const fromTokens = await client.getFromTokens({ chainId: 8453 });
const crossChainToTokens = await client.getCrossChainToTokens({ chainId: 501 });
const tokenIn = fromTokens.find((token) => token.symbol === "USDC")!;
const tokenOut = crossChainToTokens.find((token) => token.symbol === "USDC")!;
const quote = await client.quote({
fromChain: tokenIn.chain,
toChain: tokenOut.chain,
tokenIn,
tokenOut,
amountIn: "1000000",
slippageBps: 50,
sender: "0x...",
recipient: "...",
});chainId is the numeric token-list chain ID from the table above. For same-chain tokenOut, reuse getFromTokens; call getCrossChainToTokens only for a destination on another chain.
The normalized item extends AssetRef:
interface SwapTokenListItem extends AssetRef {
chain: ChainRef;
address: string;
symbol: string;
decimals: number;
isNative: boolean;
tokenListChainId: number;
blockchain: string;
assetId: string;
contractAddress: string | null;
coinType: string | null;
name: string | null;
logoURI: string | null;
price: string | number | null;
priceUpdatedAt: number | null;
sources: string[];
raw: Record<string, unknown>;
}address and assetId contain the identifier accepted by the unified quote API. contractAddress separately preserves the on-chain contract/mint when available and is null for native assets. Unknown backend fields remain available in raw.
The methods cache successful lists for 10 minutes per direction and chain, and merge concurrent identical loads. Failures are never cached. Configure tokenListCacheTtlMs, or set it to 0 to disable caching. Requests with an AbortSignal are not shared with another caller.
The corresponding HTTP endpoints are documented below for server integrations that do not use the SDK.
1. From-token list
GET https://api.rhea.finance/get_chain_prices?chain=<NUMERIC_CHAIN_ID>Example:
curl "https://api.rhea.finance/get_chain_prices?chain=1"
curl "https://api.rhea.finance/get_chain_prices?chain=56"
curl "https://api.rhea.finance/get_chain_prices?chain=8453"chain is required and uses the token-list registry's numeric ID. EVM chains use their normal chain ID. Common non-EVM IDs are Solana 501, Tron 195, Sui 784, NEAR 900001, Bitcoin 900002, Zcash 900010, and Aptos 900012.
A successful response uses { code, data, msg }:
// code === 0 && msg === "success"
type ChainPricesResponse = {
code: number;
msg: string;
data: Record<
string,
{
address: string;
chainId: number;
decimals: number;
symbol: string;
name?: string;
price: string;
logoURI?: string;
updated_at?: number;
}
>;
};Use every returned row as the chain's from-token list. The same list is also used for tokenOut when the source and destination chains are the same.
2. Cross-chain to-token list
GET https://api.rhea.finance/api/swap/supported_to_tokens?chain=<NUMERIC_CHAIN_ID>Example:
curl "https://api.rhea.finance/api/swap/supported_to_tokens?chain=8453"The response uses the standard envelope and returns the final destination list in data.tokens:
type SupportedToTokensResponse = {
code: number;
msg: string;
data: {
tokens: Array<{
address?: string;
assetId?: string;
symbol: string;
decimals: number;
logoURI?: string;
price?: string | number;
isNative?: boolean;
coinType?: string;
sources?: string[];
}>;
};
};Use this endpoint only when tokenOut is on a different chain from tokenIn. The returned rows are already the final supported tokenOut list; do not filter them again with legacy crossChainTo or sameChain flags.
Integration rules:
- Never build both selectors from one combined token list.
- Do not merge tokens by symbol alone; use chain plus address, asset ID, or coin type.
- Pass the selected normalized token to
client.quote()and still confirm route availability with the quote API. - Invalidate a selected cross-chain tokenOut if a refreshed destination list no longer contains its chain and
addresspair.
1. What the SDK does
The recommended flow has only two steps:
const quote = await client.quote(quoteRequest);
const result = await client.swap({ quote });The normalized object returned by quote() can be passed directly to swap(). Internally, swap() continues with buildSwap() and executeSwap(), then selects the registered executor that matches the execution type returned by the API. Applications do not need to determine:
- whether the swap is same-chain or cross-chain;
- which router is used;
- whether execution requires a transaction, a signed order, or a deposit transfer;
- whether an EVM approval is required;
- whether the flow is an MCA deposit, NEAR withdrawal, or relayer withdrawal.
The application only provides the request fields and the relevant wallet adapters. Do not modify quote.buildContext or rebuild a swap request from quote.raw.
2. Installation
pnpm add @rhea-finance/cross-chain-aggregation-dexNode.js 16 or later is required. When running on Node.js 16 without a global fetch, inject a compatible implementation through SwapClientConfig.fetch.
3. Recommended: simple quote → swap flow
The example below swaps USDC on Base for USDC on Solana. All token amounts use base-unit decimal strings. For example, USDC has 6 decimals, so "1000000" represents 1 USDC.
3.1 Minimal EVM adapter
The SDK does not receive private keys and is not coupled to a specific wallet library. Wrap your wallet implementation in an adapter:
import {
createEvmExecutor,
type EvmWalletAdapter,
} from "@rhea-finance/cross-chain-aggregation-dex/executors/evm";
const evmAdapter: EvmWalletAdapter = {
async sendTransaction(tx) {
const response = await wallet.sendTransaction({
to: tx.to,
data: tx.data,
value: tx.value,
gasLimit: tx.gasLimit,
gasPrice: tx.gasPrice,
maxFeePerGas: tx.maxFeePerGas,
maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
});
return { txHash: response.hash, raw: response };
},
async signTypedData(request) {
return wallet.signTypedData(
request.typedData.domain,
request.typedData.types,
request.typedData.message
);
},
async waitForTransaction(txHash) {
const receipt = await provider.waitForTransaction(txHash);
return {
status: receipt?.status === 1 ? "confirmed" : "failed",
raw: receipt,
};
},
};
const evmExecutor = createEvmExecutor(evmAdapter);The EVM executor does not read the currently connected chain. It uses tx.chainId from the API build response. The wallet should prompt the user or switch networks when sending the transaction.
The build normalizer accepts EVM chain IDs as numbers, decimal strings, or JSON-RPC hexadecimal strings and exposes them to the adapter as numbers. It also preserves from, gasPrice, maxFeePerGas, and maxPriorityFeePerGas. For standard ERC-20 approval calldata, the SDK treats the encoded allowance spender as authoritative when the API's separate approve.spender field is inconsistent.
3.2 Create a SwapClient
To request API access and an API key, sign in to the RHEA Boss portal. Provide the issued credential through apiKey for a static credential or getAccessToken when your application refreshes access tokens.
import { SwapClient } from "@rhea-finance/cross-chain-aggregation-dex";
const client = new SwapClient({
baseUrl: "https://api.rhea.finance",
getAccessToken: () => sessionStorage.getItem("access-token") ?? "",
executors: [evmExecutor],
});3.3 Request a quote
import type {
AssetRef,
QuoteRequest,
} from "@rhea-finance/cross-chain-aggregation-dex";
const baseUsdc: AssetRef = {
chain: "8453",
address: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
symbol: "USDC",
decimals: 6,
};
const solanaUsdc: AssetRef = {
chain: "solana",
address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
symbol: "USDC",
decimals: 6,
};
const quoteRequest: QuoteRequest = {
fromChain: "8453",
toChain: "solana",
tokenIn: baseUsdc,
tokenOut: solanaUsdc,
amountIn: "1000000",
slippageBps: 50,
quoteWaitingTimeMs: 3000,
sameChainTimeoutMs: 500,
crossChainTimeoutMs: 3000,
sender: "0xYourBaseAddress",
recipient: "YourSolanaAddress",
};
const quote = await client.quote(quoteRequest);quote() calls POST /api/v2/swap/quote. The frontend can configure the Near Intents wait plus the same-chain and cross-chain route timeouts on every QuoteRequest. The SDK sends the defaults shown above when fields are omitted.
Set confidentiality: "basic" to use the confidential 1Click route. The SDK preserves it through quote and build, and includes it in automatic or manual report payloads. Omit the field for public swaps:
const confidentialQuote = await client.quote({
...quoteRequest,
confidentiality: "basic",
});3.3.1 Configure quote timing
quoteWaitingTimeMs, sameChainTimeoutMs, and crossChainTimeoutMs are first-class SDK parameters. Pass them directly to client.quote(); do not put them in extensions or executor configuration.
// Frontend: allow Near Intents up to 5 seconds to return a route quote.
const quote = await client.quote({
...quoteRequest,
quoteWaitingTimeMs: 5000,
sameChainTimeoutMs: 750,
crossChainTimeoutMs: 6000,
});| Field | SDK default | Scope |
| --- | --- | --- |
| quoteWaitingTimeMs | 3000 | Wait window for Near Intents and similar intent-based route quotes, including MCA previews produced by the quote call. |
| sameChainTimeoutMs | 500 | Timeout budget supplied to same-chain quote routing. |
| crossChainTimeoutMs | 3000 | Timeout budget supplied to cross-chain quote routing. |
All three values use milliseconds and must be non-negative integers. They affect only POST /api/v2/swap/quote; the SDK removes them from the subsequent build request. They do not control the SDK HTTP timeout, wallet signing, source-chain confirmation, bridge settlement, or order polling. Keep the client's timeoutMs above the configured quote budget plus network overhead.
3.4 Execute the swap directly
const result = await client.swap({
quote,
waitFor: "submitted",
beforeSign(preview) {
console.log("Wallet action requested", preview);
},
});
console.log(result.status, result.txHash, result.orderId);swap() does not request another quote. It uses the buildContext stored in the quote to call the swap API, normalizes the build response, and invokes the matching executor.
The default waitFor mode is "submitted". The method returns after the wallet successfully signs or submits the transaction. This does not mean the assets have arrived on the destination chain.
To let the SDK continue polling for final delivery:
const result = await client.swap({
quote,
waitFor: "completed",
orderPolling: {
intervalMs: 5000,
timeoutMs: 600000,
},
});
if (result.status === "completed") {
console.log("Order completed");
}"completed" polls the order-status API whenever the swap has a queryable order reference. Confidential swaps also require order-status polling when they are same-chain. For confidential Near Intents builds, the SDK uses deposit.orderId when present and otherwise uses deposit.depositAddress as the status key. If a cross-chain or confidential swap does not provide a usable status key, the SDK throws INVALID_API_RESPONSE at the status stage instead of treating source-chain confirmation as completion.
The default polling interval is 5 seconds. There is no default polling timeout, so polling continues until the order reaches a terminal state or the supplied AbortSignal is aborted.
Set orderPolling.timeoutMs only when the application needs a time limit. An explicit timeout throws ORDER_TIMEOUT, but it does not revert an already submitted on-chain transaction.
3.5 Check final delivery status
If the swap first returns with "submitted", use the returned orderId to poll manually:
if (result.orderId) {
const finalStatus = await client.waitForOrder({
orderId: result.orderId,
router: result.router,
intervalMs: 5000,
timeoutMs: 600000,
});
console.log(finalStatus.status);
}For a single status request:
const status = await client.getOrderStatus({
orderId: result.orderId!,
router: result.router,
});Terminal statuses are completed, failed, refunded, and expired.
4. Simple-flow field reference
4.1 SwapClientConfig
| Field | Type | Required | Description and default |
| --- | --- | --- | --- |
| baseUrl | string | Yes | API base URL, for example https://api.rhea.finance. A trailing / is removed. |
| apiKey | string | No | Static API credential sent with requests. Request one through the RHEA Boss portal. |
| getAccessToken | () => string \| Promise<string> | No | Reads an access token before each request. Use this for refreshable sessions. |
| fetch | typeof globalThis.fetch | No | Custom fetch implementation. The SDK binds its invocation context to avoid browser Illegal invocation errors. It is normally required on Node.js 16. |
| headers | Record<string,string> or function | No | Additional request headers. The function form may return a promise. |
| timeoutMs | number | No | Timeout for each HTTP request in milliseconds. Default: 15000. |
| retry | Partial<RetryConfig> | No | Retry policy for retryable quote/read operations. Defaults: 2 retries, 250ms base delay, 2000ms maximum delay, and jitter enabled. |
| logger | SdkLogger | No | Receives structured api.request, api.response, and api.retry entries. |
| executors | readonly ChainExecutor[] | Required for execution | Wallet executors. May be omitted when only calling quote() or buildSwap(). |
| tokenListCacheTtlMs | number | No | Successful token-list cache lifetime in milliseconds. Default: 600000 (10 minutes). Set to 0 to disable. |
| reportMode | "auto" \| "manual" \| "disabled" | No | Reporting policy. Default: "auto". A reporting failure does not turn a submitted swap into a failed swap. |
| onEvent | (event) => void | No | Receives all lifecycle events. |
| now | () => number | No | Custom millisecond clock, mainly for testing. Default: Date.now. |
4.2 AssetRef
| Field | Type | Required | Format and meaning |
| --- | --- | --- | --- |
| chain | ChainRef | Yes | The SDK uses one chain ID format everywhere. EVM chains use decimal strings, such as Base "8453". Other values are "solana", "aptos", "near", "tron", "btc", "zcash", and "sui". |
| address | string | Yes | Token contract address, mint, coin type, or the API-defined native-token identifier. |
| symbol | string | No | Display symbol. It is not used for calculations. |
| decimals | number | No | Token precision. Supplying it is recommended so applications can format and convert amounts correctly. |
| isNative | boolean | No | Whether the asset is the chain's native token. |
tokenIn.chain should match fromChain, and tokenOut.chain should match toChain.
4.3 QuoteRequest
| Field | Type | Required | Format and meaning |
| --- | --- | --- | --- |
| fromChain | ChainRef | Yes | Source chain ID, for example "8453". |
| toChain | ChainRef | Yes | Destination chain ID, for example "solana". |
| tokenIn | AssetRef | Yes | Asset being spent. |
| tokenOut | AssetRef | Yes | Asset being received. |
| amountIn | string | Yes | A non-negative base-unit decimal integer string. Do not pass "1.5" or scientific notation. |
| slippageBps | number | Yes | Slippage in basis points. 50 means 0.5%; 100 means 1%. |
| quoteWaitingTimeMs | number | No | Near Intents quote wait in milliseconds. Must be a non-negative integer. Default: 3000. See Configure quote timing. |
| sameChainTimeoutMs | number | No | Same-chain route quote timeout in milliseconds. Must be a non-negative integer. Default: 500. |
| crossChainTimeoutMs | number | No | Cross-chain route quote timeout in milliseconds. Must be a non-negative integer. Default: 3000. |
| confidentiality | "basic" | No | Enables the confidential 1Click route and marks the resulting report. Omit for public swaps. |
| sender | string | Yes | Sender address on the source chain. |
| recipient | string | No | Recipient address on the destination chain. Cross-chain requests should normally provide it explicitly. |
| extensions | Record<string,unknown> | No | Additional fields forwarded to the API. Regular applications should not use this to replace standard fields. |
Important normalized quote fields:
| Field | Meaning |
| --- | --- |
| estimatedOut | Estimated output amount as a base-unit string. |
| minAmountOut | Minimum output after slippage as a base-unit string. |
| route.router | Router selected by the SDK. Use it for display or diagnostics, not application-side execution branching. |
| alternatives | Normalized summaries of other available routes. |
| receivedAt / expiresAt | Millisecond timestamps used for quote freshness validation. |
| buildContext | Read-only context used by swap() and buildSwap(). Do not modify it. |
| raw | Original quote API data, useful for diagnostics or fields that are not normalized. |
4.4 SwapInput and WaitMode
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| quote | Quote | Yes | The value returned by client.quote(). Pass it directly without modification. |
| waitFor | "submitted" \| "source-confirmed" \| "completed" | No | Default: "submitted". |
| orderPolling | OrderPollingOptions | No | Used with waitFor: "completed". intervalMs defaults to 5000; omit timeoutMs to poll indefinitely. |
| signal | AbortSignal | No | Cancels unfinished SDK requests or waits. It cannot withdraw an already broadcast transaction. |
| onEvent | (event) => void | No | Receives lifecycle events for this swap only. |
| beforeSign | (preview) => void \| Promise<void> | No | Called before each wallet signature or transaction request. It can be used for application confirmation UI. |
| idempotencyKey | string | No | Sent with the build request. Duplicate-execution protection is determined by the server. |
Wait modes:
| Mode | Return condition |
| --- | --- |
| submitted | Returns after the wallet signs or broadcasts the source action. |
| source-confirmed | Calls the required wallet confirmation method and returns only when it reports confirmed. A failed or malformed status throws BROADCAST_FAILED. |
| completed | After source execution, polls the server until a delivery terminal state. Same-chain confidential swaps are included. A cross-chain or confidential swap without a usable status key fails at the status stage instead of being reported as completed. |
waitForOrder() accepts the same polling values directly:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| intervalMs | number | No | Delay between status requests in milliseconds. Default: 5000. |
| timeoutMs | number | No | Maximum total polling duration in milliseconds. Omit it to poll indefinitely. |
4.5 SwapExecutionResult
| Field | Type | Description |
| --- | --- | --- |
| executionId | string | Unique identifier for this SDK execution. |
| status | string | submitted, source-confirmed, processing, completed, failed, refunded, or expired. |
| router | string | Router used for execution. Pass it unchanged when querying order status. |
| txHash | string? | Hash of a single source-chain transaction. |
| txHashes | string[]? | Hashes of multiple source-chain transactions, such as a NEAR transaction batch. |
| orderId | string? | Status-query key. Usually a server order identifier; confidential Near Intents may use the deposit address. When present, it can be passed to waitForOrder(). |
| depositAddress | string? | Cross-chain deposit address. |
| report | object? | Reporting state: reported, failed, or skipped. A report warning does not invalidate the source submission. |
| raw | unknown | Executor confirmation response or original swap API build data. |
submitted and source-confirmed do not mean that the destination assets have arrived. Use waitFor: "completed" or waitForOrder() to check final delivery.
5. Advanced: buildSwap → executeSwap
Use the advanced flow only when you need to inspect the build, pass it between processes, or separate building from wallet execution:
const quote = await client.quote(quoteRequest);
// Builds and normalizes the execution without opening a wallet.
const build = await client.buildSwap({
quote,
idempotencyKey: crypto.randomUUID(),
});
console.log(build.router, build.execution.kind, build.raw);
// Wallet execution can happen later.
const result = await client.executeSwap({
build,
waitFor: "submitted",
});The following calls are equivalent:
await client.swap({ quote });
// Equivalent to:
const build = await client.buildSwap({ quote });
await client.executeSwap({ build });buildSwap() does not require an executor. executeSwap() requires a registered executor that supports build.execution.kind. MCA relayer withdrawals include an SDK-managed message-signing and submission flow, so use swap() instead of splitting that flow.
6. Executor adapters
Import each chain executor from its dedicated subpath:
import { createEvmExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/evm";
import { createSolanaExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/solana";
import { createAptosExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/aptos";
import { createNearExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/near";
import { createTronExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/tron";
import { createBitcoinExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/bitcoin";
import { createZcashExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/zcash";
import { createSuiExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/sui";| Chain | Supported execution kinds | Core adapter capabilities |
| --- | --- | --- |
| EVM | evm-transaction, evm-signature | sendTransaction, signTypedData, and waitForTransaction. |
| Solana | solana-transaction | Submit a serialized transaction and implement waitForTransaction. |
| Aptos | aptos-entry-function | Submit an entry function and implement waitForTransaction. |
| NEAR | near-transaction-batch | Submit NEAR transactions and implement waitForTransactions. |
| Tron | tron-transfer | Submit a native-token or token transfer and implement waitForTransaction. |
| Bitcoin | bitcoin-transfer | Submit a UTXO transfer and implement waitForTransaction. Configure defaultFeeRate when the build does not provide a fee rate. |
| Zcash | zcash-transfer | Submit a transparent-address transfer and implement waitForTransaction. |
| Sui | sui-transfer | Submit a coin transfer and implement waitForTransaction. |
Zcash follows the same standard as every other chain: a successful wallet submission must return a real transaction hash. The SDK never invents a transaction hash.
Every built-in wallet adapter must implement a confirmation method. It must return this normalized result instead of returning a chain-specific receipt directly:
type TransactionConfirmation = {
status: "confirmed" | "failed";
raw?: unknown;
};The adapter is responsible for interpreting its chain-specific receipt, including EVM receipt.status, Solana meta.err, Aptos execution success, NEAR final execution status, and the confirmation policy used for UTXO chains. The SDK rejects both failed and malformed confirmation statuses.
For MCA quotes, the SDK may also read these methods from the registered executor:
getIdentityKey()generatesmca.signer.identityKeyand is required for MCA quotes;signMessage()signs the exact API-provided message when required by an MCA relayer withdrawal.
These are adapter capabilities. Application code calling client.swap() does not pass a separate signer.
7. EVM approvals
When the swap API build response contains an approval, the EVM executor performs these steps in order:
- Call the optional
isApprovalRequired(approval)method. If it is not implemented, approval is assumed to be required. - Submit the approval transaction.
- Call the required
waitForTransactionmethod and require aconfirmedresult. - Submit the main swap transaction or request the EIP-712 signature.
Applications do not need to query allowances or inspect needsApprove. To avoid unnecessary approval transactions, implement isApprovalRequired in the adapter:
const evmAdapter: EvmWalletAdapter = {
// ...sendTransaction, signTypedData, and other methods
async isApprovalRequired(approval) {
const allowance = await readAllowance(approval.spender);
return allowance < requiredAmount;
},
};The lifecycle emits approval-requested, approval-submitted, and the subsequent signing or submission events in order.
8. MCA deposits and withdrawals
MCA flows use the same client.quote() and client.swap() methods. The flow field distinguishes deposits from withdrawals. Applications do not branch on the returned router.
Common MCA fields:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| flow | "deposit" \| "withdraw" | Yes | MCA operation. |
| mcaAccountId | string | Yes | NEAR account ID of the MCA. |
| signerChain | McaSignerChain | Yes | Selects the registered executor that supplies identity and signing capabilities. |
| recipientMsgSignatures | string[] | No | Existing recipient-message signatures forwarded in the MCA payload. |
| depositSignerProofSignatures | string[] | No | Existing deposit-signer-proof signatures forwarded in the MCA payload. |
Deposit
const quote = await client.quote({
flow: "deposit",
mcaAccountId: "account.near",
signerChain: "evm",
fromChain: "42161",
toChain: "near",
tokenIn: arbitrumUsdc,
tokenOut: mcaUsdc,
amountIn: "1000000",
slippageBps: 50,
sender: "0xYourAddress",
recipient: "account.near",
collateral: {
useAsCollateral: true,
},
});
const result = await client.swap({ quote, waitFor: "completed" });collateral.useAsCollateral is a required boolean that specifies whether the deposited asset should be used as Burrow collateral.
Withdraw
import { resolveMcaWithdrawPolicy } from "@rhea-finance/cross-chain-aggregation-dex";
const collateral = resolveMcaWithdrawPolicy({
amountBurrow, // requested withdraw in Burrow internal decimals
suppliedBalance, // current token supplied balance, same decimals
availableBalance, // human/display precision, matching amountInHuman
amountIn: amountInHuman,
isMax,
});
const quote = await client.quote({
flow: "withdraw",
mcaAccountId: "account.near",
signerChain: "evm",
fromChain: "near",
toChain: "8453",
tokenIn: mcaUsdc,
tokenOut: baseUsdc,
amountIn: "1000000",
slippageBps: 50,
sender: "account.near",
recipient: "0xYourBaseAddress",
collateral,
executionPreference: "relayer",
});
const result = await client.swap({
quote,
waitFor: "completed",
beforeSign(preview) {
console.log("MCA message signature preview", preview);
},
});Withdraw-only fields:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| collateral.needDecrease | boolean | No | Compatibility hint only. The SDK derives the API value from decreaseAmountBurrow and corrects contradictory input. |
| collateral.decreaseAmountBurrow | string | Yes | Required collateral decrease in Burrow decimals: max(amountBurrow - suppliedBalance, 0). The SDK canonicalizes the value and sends "0" when supplied balance covers the withdrawal. |
| collateral.withdrawAll | boolean | No | Whether to withdraw the full available amount. |
| executionPreference | "auto" \| "near" \| "relayer" | No | Default: "auto". Set explicitly to force direct NEAR or relayer execution. |
| boundNearAccountId | string | Required for automatic NEAR selection | In auto mode, direct NEAR execution is selected only when toChain === "near" and recipient exactly matches this field. Otherwise, the relayer is selected. |
For direct NEAR execution, the NEAR executor submits nearMcaWithdrawTx. For relayer execution, the executor selected by signerChain uses signMessage() to sign the API-provided messageToSign, after which the SDK submits the order. Application code only calls swap().
Derive the collateral policy from the same balances displayed by the application, as shown above. This mirrors Lending Withdraw and multi-chain Trade's 2026-08-20 withdraw fix: decreaseCollateralAmountBurrow = max(amountBurrow - suppliedBalance, 0), and needDecreaseCollateral is true exactly when that result is positive. When relayer gas is reserved, use the supplied balance from the gas-adjusted portfolio snapshot, matching the amount used for the quote. withdrawAll remains an independent Max/available-balance decision.
9. Lifecycle, errors, and cancellation
Lifecycle events can be observed globally on the client or for an individual swap:
const client = new SwapClient({
baseUrl: "https://api.rhea.finance",
executors: [evmExecutor],
onEvent(event) {
console.log("Swap lifecycle", event);
},
});Events may cover build, approval, signing, submission, source confirmation, order status, completion, warnings, and failures.
SDK errors use the SwapSdkError type:
import { SwapSdkError } from "@rhea-finance/cross-chain-aggregation-dex";
try {
await client.swap({ quote });
} catch (error) {
if (error instanceof SwapSdkError) {
console.error({
code: error.code,
stage: error.stage,
message: error.message,
retryable: error.retryable,
cause: error.cause,
details: error.details,
});
}
}Common stage values are quote, build, approve, sign, broadcast, submit, report, status, and history. Common code values include USER_REJECTED, APPROVAL_FAILED, SIGNING_FAILED, BROADCAST_FAILED, and ORDER_TIMEOUT.
Pass an AbortSignal to stop an unfinished request or wait:
const controller = new AbortController();
const promise = client.swap({ quote, signal: controller.signal });
controller.abort();
await promise;Cancellation only stops the SDK's current work. It cannot revert an approval, signature, or transaction that has already been submitted.
10. Retries, logging, and credentials
const client = new SwapClient({
baseUrl: "https://api.rhea.finance",
getAccessToken: async () => authStore.getToken(),
retry: {
maxRetries: 2,
baseDelayMs: 250,
maxDelayMs: 2000,
jitter: true,
},
logger: {
log(entry) {
console.log("SDK API", entry);
},
},
});The SDK automatically retries only retryable quote/read operations. It does not automatically retry build, broadcast, or order-submission operations that could execute twice. Network failures preserve the underlying error name and message to help diagnose CORS failures, connection resets, timeouts, or fetch invocation problems.
11. Raw API, reporting, and history
Most applications should use normalized methods. Use the Raw API only when the complete original server fields are required:
await client.quoteRaw(rawQuoteRequest);
await client.buildRaw(rawBuildRequest);
await client.submitOrderRaw(rawSubmitRequest);
await client.getOrderStatusRaw(rawStatusRequest);
await client.reportRaw(rawReportRequest);
await client.createHistoryAuthChallenge(rawChallengeRequest);
await client.verifyHistoryAuthChallenge(rawVerifyRequest);
await client.getHistoryRaw(rawHistoryRequest);The default reporting mode is reportMode: "auto". If a swap succeeds but reporting fails, result.report.status is "failed" and a warning is emitted. The SDK does not throw a misleading swap failure. For manual reporting:
const client = new SwapClient({
baseUrl: "https://api.rhea.finance",
executors: [evmExecutor],
reportMode: "manual",
});
const result = await client.swap({ quote });
await client.report(result);
// Retry after a reporting failure:
await client.retryReport(result);Query history with:
const history = await client.getHistory({
sender: "0xYourAddress",
page: 1,
pageSize: 20,
status: ["processing", "completed"],
});The SDK applies status filtering locally. The returned page has filteredLocally: true.
For confidential history, authorize the connected wallet, then query with the returned short-lived token:
const authorization = await client.authorizeConfidentialHistory(
{
chainFamily: "evm",
chainId: "1",
walletAddress: connectedAddress,
// Include mcaAccountId when authorizing an MCA principal.
},
async (challenge) => {
// Check the wallet/network has not changed, then sign exactly this message.
const signature = await signer.signMessage(
challenge.signingInput.message
);
return { signature };
}
);
const confidentialHistory = await client.getHistory({
sender: authorization.queryAddress,
mode: "confidential",
walletToken: authorization.token,
page: 1,
pageSize: 20,
});The callback returns the wallet-specific proof required by challenge.signingMethod; NEP-413 and other chain families may require additional proof fields. authorizeConfidentialHistory() validates that the challenge and verified token refer to the requested principal before returning them. The SDK sends the normal API credential in Authorization and the wallet token separately in Authentication. Public history omits mode and walletToken.
12. Amount utilities
Avoid JavaScript floating-point arithmetic for token amounts:
import {
formatUnits,
parseUnits,
} from "@rhea-finance/cross-chain-aggregation-dex";
parseUnits("1.25", 6); // "1250000"
formatUnits("1250000", 6); // "1.25"parseUnits() rejects fractional precision beyond the token's decimals. formatUnits() accepts only non-negative base-unit decimal integer strings.
