@rhea-finance/cross-chain-aggregation-dex 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +536 -205
  2. package/dist/executors/aptos.d.mts +2 -2
  3. package/dist/executors/aptos.d.ts +2 -2
  4. package/dist/executors/aptos.js.map +1 -1
  5. package/dist/executors/aptos.mjs.map +1 -1
  6. package/dist/executors/bitcoin.d.mts +2 -2
  7. package/dist/executors/bitcoin.d.ts +2 -2
  8. package/dist/executors/bitcoin.js.map +1 -1
  9. package/dist/executors/bitcoin.mjs.map +1 -1
  10. package/dist/executors/evm.d.mts +2 -2
  11. package/dist/executors/evm.d.ts +2 -2
  12. package/dist/executors/evm.js.map +1 -1
  13. package/dist/executors/evm.mjs.map +1 -1
  14. package/dist/executors/near.d.mts +2 -2
  15. package/dist/executors/near.d.ts +2 -2
  16. package/dist/executors/near.js.map +1 -1
  17. package/dist/executors/near.mjs.map +1 -1
  18. package/dist/executors/solana.d.mts +2 -2
  19. package/dist/executors/solana.d.ts +2 -2
  20. package/dist/executors/solana.js.map +1 -1
  21. package/dist/executors/solana.mjs.map +1 -1
  22. package/dist/executors/sui.d.mts +2 -2
  23. package/dist/executors/sui.d.ts +2 -2
  24. package/dist/executors/sui.js.map +1 -1
  25. package/dist/executors/sui.mjs.map +1 -1
  26. package/dist/executors/tron.d.mts +2 -2
  27. package/dist/executors/tron.d.ts +2 -2
  28. package/dist/executors/tron.js.map +1 -1
  29. package/dist/executors/tron.mjs.map +1 -1
  30. package/dist/executors/zcash.d.mts +3 -3
  31. package/dist/executors/zcash.d.ts +3 -3
  32. package/dist/executors/zcash.js +2 -8
  33. package/dist/executors/zcash.js.map +1 -1
  34. package/dist/executors/zcash.mjs +2 -8
  35. package/dist/executors/zcash.mjs.map +1 -1
  36. package/dist/index.d.mts +2 -2
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.js +8 -15
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs +8 -15
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/{registry-DRYUqs7T.d.mts → registry-DjohbPPm.d.mts} +2 -5
  43. package/dist/{registry-DRYUqs7T.d.ts → registry-DjohbPPm.d.ts} +2 -5
  44. package/dist/{shared-BdH3hWuP.d.ts → shared-BbfGrWrJ.d.ts} +2 -7
  45. package/dist/{shared-BqpFeosz.d.mts → shared-BxJ0H20s.d.mts} +2 -7
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -1,88 +1,159 @@
1
1
  # @rhea-finance/cross-chain-aggregation-dex
2
2
 
3
- TypeScript SDK for the unified multi-chain Swap API. It provides raw and normalized quote, build, execution, order status, report, and history interfaces without coupling the core package to a wallet or UI framework.
3
+ 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.
4
4
 
5
- Supported chain families: EVM, Solana, Aptos, NEAR, Tron, Bitcoin, Zcash, and Sui.
5
+ 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](https://github.com/rhea-finance/rhea-sdk-docs/blob/main/CrossChainDexAPI.md).
6
6
 
7
- ## Install
7
+ 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).
8
+
9
+ ## Supported chains and tokens
10
+
11
+ The product support list matches `multi-chain-lending` Trade: **18 mainnets**. Use the HTTP API chain ID for `fromChain` / `toChain` in Swap calls, and the token-query alias when fetching token metadata.
12
+
13
+ | Chain | Type | HTTP / SDK chain ID | Token query alias |
14
+ | --- | --- | --- | --- |
15
+ | Ethereum | EVM | `1` | `eth` |
16
+ | BNB Smart Chain | EVM | `56` | `bsc` |
17
+ | Arbitrum One | EVM | `42161` | `arb` |
18
+ | Base | EVM | `8453` | `base` |
19
+ | Optimism | EVM | `10` | `op` |
20
+ | Berachain | EVM | `1385` | `bera` |
21
+ | Monad | EVM | `143` | `monad` |
22
+ | X Layer | EVM | `196` | `xlayer` |
23
+ | Polygon PoS | EVM | `137` | `pol` |
24
+ | Gnosis Chain | EVM | `100` | `gnosis` |
25
+ | Plasma | EVM | `9745` | `plasma` |
26
+ | Solana | Non-EVM | `solana` | `sol` |
27
+ | Bitcoin | Non-EVM | `btc` | `btc` |
28
+ | NEAR | Non-EVM | `near` | `near` |
29
+ | Zcash | Non-EVM | `zcash` | `zcash` (also accepts `zec`) |
30
+ | Aptos | Non-EVM | `aptos` | `aptos` |
31
+ | Tron | Non-EVM | `tron` | `tron` |
32
+ | Sui | Non-EVM | `sui` | `sui` |
33
+
34
+ "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.
35
+
36
+ ### Token coverage (Unified Swap)
37
+
38
+ Across the 18 product chains above, Unified Swap currently covers **4,000+** tokens (same-chain DEX metadata plus cross-chain Intents tokens, deduplicated per direction). Snapshot: 2026-07-24. Counts change as liquidity providers and Intents listings update; always fetch the live token APIs below for the current list.
39
+
40
+ ### How to fetch supported tokens
41
+
42
+ Token discovery uses HTTP endpoints **outside** `/api/swap/*`. The SDK does not wrap these calls; applications should `fetch` them directly and map results into `AssetRef` when calling `client.quote()`.
43
+
44
+ #### 1. Product token list (recommended for Trade UI)
45
+
46
+ ```http
47
+ GET https://api.rhea.finance/get_multichain_lending_tokens_data?chains=<COMMA_SEPARATED_ALIASES>
48
+ ```
49
+
50
+ Query all currently supported product chains:
8
51
 
9
52
  ```bash
10
- pnpm add @rhea-finance/cross-chain-aggregation-dex
53
+ curl "https://api.rhea.finance/get_multichain_lending_tokens_data?chains=bsc,eth,arb,base,op,bera,monad,xlayer,pol,gnosis,plasma,sol,btc,near,zcash,zec,aptos,tron,sui"
11
54
  ```
12
55
 
13
- ## Quick start
56
+ A successful response is a **JSON array** of token objects (not the `/api/swap` `{ code, data, msg }` envelope). Typical fields:
14
57
 
15
- All token amounts are base-unit decimal strings. Slippage uses basis points (`50` means 0.5%).
58
+ | Field | Description |
59
+ | --- | --- |
60
+ | `assetId` | Multichain / Intents asset ID |
61
+ | `blockchain` | Token query alias (`eth`, `near`, …) |
62
+ | `symbol` | Display symbol |
63
+ | `decimals` | Token decimals |
64
+ | `contractAddress` | On-chain address; may be empty for natives |
65
+ | `price` / `priceUpdatedAt` | Display price only; not a swap quote |
66
+ | `icon` | Optional icon URL |
16
67
 
17
- ```ts
18
- import { SwapClient, type QuoteRequest } from "@rhea-finance/cross-chain-aggregation-dex";
68
+ Integration tips:
19
69
 
20
- const getAccessToken = async () => sessionStorage.getItem("access-token") ?? "";
70
+ - Group by `blockchain`; do not merge tokens by `symbol` alone.
71
+ - For `quote()`, pass the chain-specific token address/ID as `AssetRef.address`, and use the HTTP/SDK chain ID in `fromChain` / `toChain` / `AssetRef.chain` (for example Base `"8453"`, Solana `"solana"`).
72
+ - Presence in this list means the token is discoverable. Confirm a route with `client.quote()` (or `POST /api/swap/quote`) before trading.
73
+ - Prefer short-lived cache; refresh periodically instead of hard-coding the list.
21
74
 
22
- const client = new SwapClient({
23
- baseUrl: "https://api.rhea.finance",
24
- getAccessToken,
25
- });
75
+ #### 2. Per-chain token price metadata
26
76
 
27
- const request: QuoteRequest = {
28
- fromChain: "btc",
29
- toChain: "near",
30
- tokenIn: {
31
- chain: "btc",
32
- address: "btc",
33
- symbol: "BTC",
34
- decimals: 8,
35
- isNative: true,
36
- },
37
- tokenOut: {
38
- chain: "near",
39
- address: "wrap.near",
40
- symbol: "wNEAR",
41
- decimals: 24,
42
- },
43
- amountIn: "100000",
44
- slippageBps: 50,
45
- sender: "bc1...",
46
- recipient: "alice.near",
47
- };
77
+ ```http
78
+ GET https://api.rhea.finance/get_chain_prices?chain=<TOKEN_QUERY_ALIAS>
79
+ ```
48
80
 
49
- const quote = await client.quote(request);
50
- const build = await client.buildSwap({ quote });
81
+ Example:
51
82
 
52
- // build is safe to inspect or send to another process.
53
- // Register a chain executor before calling executeSwap.
83
+ ```bash
84
+ curl "https://api.rhea.finance/get_chain_prices?chain=eth"
85
+ curl "https://api.rhea.finance/get_chain_prices?chain=bsc"
86
+ curl "https://api.rhea.finance/get_chain_prices?chain=base"
54
87
  ```
55
88
 
56
- `buildSwap()` never opens a wallet. To execute a build, inject one or more `ChainExecutor` implementations when creating the client:
89
+ `chain` is required and must be a **token query alias** (`eth`, `bsc`, `base`, …). A successful response uses `{ code, data, msg }`:
57
90
 
58
91
  ```ts
59
- const clientWithExecutor = new SwapClient({
60
- baseUrl: "https://api.rhea.finance",
61
- getAccessToken,
62
- executors: [bitcoinExecutor],
63
- });
92
+ // code === 0 && msg === "success"
93
+ type ChainPricesResponse = {
94
+ code: number;
95
+ msg: string;
96
+ data: Record<
97
+ string,
98
+ {
99
+ address: string;
100
+ chainId: number;
101
+ decimals: number;
102
+ symbol: string;
103
+ name?: string;
104
+ price: string;
105
+ logoURI?: string;
106
+ updated_at?: number;
107
+ }
108
+ >;
109
+ };
110
+ ```
64
111
 
65
- const result = await clientWithExecutor.executeSwap({
66
- build,
67
- waitFor: "submitted",
68
- });
112
+ Use this endpoint for same-chain token metadata and display prices. It is keyed by token address. It does not replace `get_multichain_lending_tokens_data` for the full multi-chain Trade selector, and some non-EVM aliases may return an empty `data` object.
113
+
114
+ ## 1. What the SDK does
115
+
116
+ The recommended flow has only two steps:
117
+
118
+ ```ts
119
+ const quote = await client.quote(quoteRequest);
120
+ const result = await client.swap({ quote });
121
+ ```
122
+
123
+ 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:
124
+
125
+ - whether the swap is same-chain or cross-chain;
126
+ - which router is used;
127
+ - whether execution requires a transaction, a signed order, or a deposit transfer;
128
+ - whether an EVM approval is required;
129
+ - whether the flow is an MCA deposit, NEAR withdrawal, or relayer withdrawal.
130
+
131
+ 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`.
132
+
133
+ ## 2. Installation
134
+
135
+ ```bash
136
+ pnpm add @rhea-finance/cross-chain-aggregation-dex
69
137
  ```
70
138
 
71
- ### Executor adapters
139
+ Node.js 16 or later is required. When running on Node.js 16 without a global `fetch`, inject a compatible implementation through `SwapClientConfig.fetch`.
140
+
141
+ ## 3. Recommended: simple quote → swap flow
142
+
143
+ 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.
144
+
145
+ ### 3.1 Minimal EVM adapter
72
146
 
73
- Each executor is imported from its own subpath and accepts a wallet-neutral adapter. The application decides whether that adapter wraps a browser wallet, server signer, RPC service, or HSM.
147
+ The SDK does not receive private keys and is not coupled to a specific wallet library. Wrap your wallet implementation in an adapter:
74
148
 
75
149
  ```ts
76
- import { SwapClient } from "@rhea-finance/cross-chain-aggregation-dex";
77
150
  import {
78
151
  createEvmExecutor,
79
152
  type EvmWalletAdapter,
80
153
  } from "@rhea-finance/cross-chain-aggregation-dex/executors/evm";
81
154
 
82
- const evmWallet: EvmWalletAdapter = {
83
- getIdentityKey: () => wallet.address,
84
- signMessage: (message) => wallet.signMessage(message),
85
- sendTransaction: async (tx) => {
155
+ const evmAdapter: EvmWalletAdapter = {
156
+ async sendTransaction(tx) {
86
157
  const response = await wallet.sendTransaction({
87
158
  to: tx.to,
88
159
  data: tx.data,
@@ -91,25 +162,271 @@ const evmWallet: EvmWalletAdapter = {
91
162
  });
92
163
  return { txHash: response.hash, raw: response };
93
164
  },
94
- signTypedData: async (request) =>
95
- wallet.signTypedData(
165
+
166
+ async signTypedData(request) {
167
+ return wallet.signTypedData(
96
168
  request.typedData.domain,
97
169
  request.typedData.types,
98
170
  request.typedData.message
99
- ),
100
- waitForTransaction: async (txHash) => provider.waitForTransaction(txHash),
171
+ );
172
+ },
173
+
174
+ async waitForTransaction(txHash) {
175
+ return provider.waitForTransaction(txHash);
176
+ },
101
177
  };
102
178
 
179
+ const evmExecutor = createEvmExecutor(evmAdapter);
180
+ ```
181
+
182
+ 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.
183
+
184
+ ### 3.2 Create a SwapClient
185
+
186
+ ```ts
187
+ import { SwapClient } from "@rhea-finance/cross-chain-aggregation-dex";
188
+
103
189
  const client = new SwapClient({
104
190
  baseUrl: "https://api.rhea.finance",
105
- getAccessToken,
106
- executors: [createEvmExecutor(evmWallet)],
191
+ getAccessToken: () => sessionStorage.getItem("access-token") ?? "",
192
+ executors: [evmExecutor],
193
+ });
194
+ ```
195
+
196
+ ### 3.3 Request a quote
197
+
198
+ ```ts
199
+ import type {
200
+ AssetRef,
201
+ QuoteRequest,
202
+ } from "@rhea-finance/cross-chain-aggregation-dex";
203
+
204
+ const baseUsdc: AssetRef = {
205
+ chain: "8453",
206
+ address: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
207
+ symbol: "USDC",
208
+ decimals: 6,
209
+ };
210
+
211
+ const solanaUsdc: AssetRef = {
212
+ chain: "solana",
213
+ address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
214
+ symbol: "USDC",
215
+ decimals: 6,
216
+ };
217
+
218
+ const quoteRequest: QuoteRequest = {
219
+ fromChain: "8453",
220
+ toChain: "solana",
221
+ tokenIn: baseUsdc,
222
+ tokenOut: solanaUsdc,
223
+ amountIn: "1000000",
224
+ slippageBps: 50,
225
+ quoteWaitingTimeMs: 3000,
226
+ sender: "0xYourBaseAddress",
227
+ recipient: "YourSolanaAddress",
228
+ };
229
+
230
+ const quote = await client.quote(quoteRequest);
231
+ ```
232
+
233
+ `quote()` calls `POST /api/swap/quote`. `quoteWaitingTimeMs` is a quote API request parameter used by routes such as Near Intents to wait for a quote. It is not an on-chain RPC timeout. The SDK sends `3000` when the field is omitted.
234
+
235
+ ### 3.4 Execute the swap directly
236
+
237
+ ```ts
238
+ const result = await client.swap({
239
+ quote,
240
+ waitFor: "submitted",
241
+ beforeSign(preview) {
242
+ console.log("Wallet action requested", preview);
243
+ },
244
+ });
245
+
246
+ console.log(result.status, result.txHash, result.orderId);
247
+ ```
248
+
249
+ `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.
250
+
251
+ 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.
252
+
253
+ To let the SDK continue polling for final delivery:
254
+
255
+ ```ts
256
+ const result = await client.swap({
257
+ quote,
258
+ waitFor: "completed",
259
+ });
260
+
261
+ if (result.status === "completed") {
262
+ console.log("Order completed");
263
+ }
264
+ ```
265
+
266
+ `"completed"` polls the API only when the build response contains a queryable `orderId`. The default polling interval is 5 seconds and the default timeout is 250 seconds. A timeout throws `ORDER_TIMEOUT`, but it does not revert an already submitted on-chain transaction.
267
+
268
+ ### 3.5 Check final delivery status
269
+
270
+ If the swap first returns with `"submitted"`, use the returned `orderId` to poll manually:
271
+
272
+ ```ts
273
+ if (result.orderId) {
274
+ const finalStatus = await client.waitForOrder({
275
+ orderId: result.orderId,
276
+ router: result.router,
277
+ intervalMs: 5000,
278
+ timeoutMs: 250000,
279
+ });
280
+
281
+ console.log(finalStatus.status);
282
+ }
283
+ ```
284
+
285
+ For a single status request:
286
+
287
+ ```ts
288
+ const status = await client.getOrderStatus({
289
+ orderId: result.orderId!,
290
+ router: result.router,
107
291
  });
108
292
  ```
109
293
 
110
- Other executor subpaths follow the same pattern:
294
+ Terminal statuses are `completed`, `failed`, `refunded`, and `expired`.
295
+
296
+ ## 4. Simple-flow field reference
297
+
298
+ ### 4.1 SwapClientConfig
299
+
300
+ | Field | Type | Required | Description and default |
301
+ | --- | --- | --- | --- |
302
+ | `baseUrl` | `string` | Yes | API base URL, for example `https://api.rhea.finance`. A trailing `/` is removed. |
303
+ | `apiKey` | `string` | No | API credential sent with requests. |
304
+ | `getAccessToken` | `() => string \| Promise<string>` | No | Reads an access token before each request. Use this for refreshable sessions. |
305
+ | `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. |
306
+ | `headers` | `Record<string,string>` or function | No | Additional request headers. The function form may return a promise. |
307
+ | `timeoutMs` | `number` | No | Timeout for each HTTP request in milliseconds. Default: `15000`. |
308
+ | `retry` | `Partial<RetryConfig>` | No | Retry policy for retryable quote/read operations. Defaults: 2 retries, 250ms base delay, 2000ms maximum delay, and jitter enabled. |
309
+ | `logger` | `SdkLogger` | No | Receives structured `api.request`, `api.response`, and `api.retry` entries. |
310
+ | `executors` | `readonly ChainExecutor[]` | Required for execution | Wallet executors. May be omitted when only calling `quote()` or `buildSwap()`. |
311
+ | `maxQuoteAgeMs` | `number \| null` | No | Maximum local quote age in milliseconds. Default: `30000`. Set to `null` to disable the local age check; an API-provided `expiresAt` still applies. |
312
+ | `reportMode` | `"auto" \| "manual" \| "disabled"` | No | Reporting policy. Default: `"auto"`. A reporting failure does not turn a submitted swap into a failed swap. |
313
+ | `onEvent` | `(event) => void` | No | Receives all lifecycle events. |
314
+ | `now` | `() => number` | No | Custom millisecond clock, mainly for testing. Default: `Date.now`. |
315
+
316
+ ### 4.2 AssetRef
317
+
318
+ | Field | Type | Required | Format and meaning |
319
+ | --- | --- | --- | --- |
320
+ | `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"`. |
321
+ | `address` | `string` | Yes | Token contract address, mint, coin type, or the API-defined native-token identifier. |
322
+ | `symbol` | `string` | No | Display symbol. It is not used for calculations. |
323
+ | `decimals` | `number` | No | Token precision. Supplying it is recommended so applications can format and convert amounts correctly. |
324
+ | `isNative` | `boolean` | No | Whether the asset is the chain's native token. |
325
+
326
+ `tokenIn.chain` should match `fromChain`, and `tokenOut.chain` should match `toChain`.
327
+
328
+ ### 4.3 QuoteRequest
329
+
330
+ | Field | Type | Required | Format and meaning |
331
+ | --- | --- | --- | --- |
332
+ | `fromChain` | `ChainRef` | Yes | Source chain ID, for example `"8453"`. |
333
+ | `toChain` | `ChainRef` | Yes | Destination chain ID, for example `"solana"`. |
334
+ | `tokenIn` | `AssetRef` | Yes | Asset being spent. |
335
+ | `tokenOut` | `AssetRef` | Yes | Asset being received. |
336
+ | `amountIn` | `string` | Yes | A non-negative base-unit decimal integer string. Do not pass `"1.5"` or scientific notation. |
337
+ | `slippageBps` | `number` | Yes | Slippage in basis points. `50` means 0.5%; `100` means 1%. |
338
+ | `quoteWaitingTimeMs` | `number` | No | Time the quote API may wait for route quotes, in milliseconds. Must be a non-negative integer. Default: `3000`. |
339
+ | `sender` | `string` | Yes | Sender address on the source chain. |
340
+ | `recipient` | `string` | No | Recipient address on the destination chain. Cross-chain requests should normally provide it explicitly. |
341
+ | `extensions` | `Record<string,unknown>` | No | Additional fields forwarded to the API. Regular applications should not use this to replace standard fields. |
342
+
343
+ Important normalized quote fields:
344
+
345
+ | Field | Meaning |
346
+ | --- | --- |
347
+ | `estimatedOut` | Estimated output amount as a base-unit string. |
348
+ | `minAmountOut` | Minimum output after slippage as a base-unit string. |
349
+ | `route.router` | Router selected by the SDK. Use it for display or diagnostics, not application-side execution branching. |
350
+ | `alternatives` | Normalized summaries of other available routes. |
351
+ | `receivedAt` / `expiresAt` | Millisecond timestamps used for quote freshness validation. |
352
+ | `buildContext` | Read-only context used by `swap()` and `buildSwap()`. Do not modify it. |
353
+ | `raw` | Original quote API `data`, useful for diagnostics or fields that are not normalized. |
354
+
355
+ ### 4.4 SwapInput and WaitMode
356
+
357
+ | Field | Type | Required | Description |
358
+ | --- | --- | --- | --- |
359
+ | `quote` | `Quote` | Yes | The value returned by `client.quote()`. Pass it directly without modification. |
360
+ | `waitFor` | `"submitted" \| "source-confirmed" \| "completed"` | No | Default: `"submitted"`. |
361
+ | `signal` | `AbortSignal` | No | Cancels unfinished SDK requests or waits. It cannot withdraw an already broadcast transaction. |
362
+ | `onEvent` | `(event) => void` | No | Receives lifecycle events for this swap only. |
363
+ | `beforeSign` | `(preview) => void \| Promise<void>` | No | Called before each wallet signature or transaction request. It can be used for application confirmation UI. |
364
+ | `idempotencyKey` | `string` | No | Sent with the build request. Duplicate-execution protection is determined by the server. |
365
+
366
+ Wait modes:
367
+
368
+ | Mode | Return condition |
369
+ | --- | --- |
370
+ | `submitted` | Returns after the wallet signs or broadcasts the source action. |
371
+ | `source-confirmed` | Waits for source-chain confirmation when the executor provides a confirmation method; otherwise falls back to `submitted`. |
372
+ | `completed` | After source execution, polls the server until a delivery terminal state when an order reference exists. Without an order reference, it returns the source execution result. |
373
+
374
+ ### 4.5 SwapExecutionResult
375
+
376
+ | Field | Type | Description |
377
+ | --- | --- | --- |
378
+ | `executionId` | `string` | Unique identifier for this SDK execution. |
379
+ | `status` | `string` | `submitted`, `source-confirmed`, `processing`, `completed`, `failed`, `refunded`, or `expired`. |
380
+ | `router` | `string` | Router used for execution. Pass it unchanged when querying order status. |
381
+ | `txHash` | `string?` | Hash of a single source-chain transaction. |
382
+ | `txHashes` | `string[]?` | Hashes of multiple source-chain transactions, such as a NEAR transaction batch. |
383
+ | `orderId` | `string?` | Server order identifier. When present, it can be passed to `waitForOrder()`. |
384
+ | `depositAddress` | `string?` | Cross-chain deposit address. |
385
+ | `report` | `object?` | Reporting state: `reported`, `failed`, or `skipped`. A report warning does not invalidate the source submission. |
386
+ | `raw` | `unknown` | Executor confirmation response or original swap API build data. |
387
+
388
+ `submitted` and `source-confirmed` do not mean that the destination assets have arrived. Use `waitFor: "completed"` or `waitForOrder()` to check final delivery.
389
+
390
+ ## 5. Advanced: buildSwap → executeSwap
391
+
392
+ Use the advanced flow only when you need to inspect the build, pass it between processes, or separate building from wallet execution:
111
393
 
112
394
  ```ts
395
+ const quote = await client.quote(quoteRequest);
396
+
397
+ // Builds and normalizes the execution without opening a wallet.
398
+ const build = await client.buildSwap({
399
+ quote,
400
+ idempotencyKey: crypto.randomUUID(),
401
+ });
402
+
403
+ console.log(build.router, build.execution.kind, build.raw);
404
+
405
+ // Wallet execution can happen later.
406
+ const result = await client.executeSwap({
407
+ build,
408
+ waitFor: "submitted",
409
+ });
410
+ ```
411
+
412
+ The following calls are equivalent:
413
+
414
+ ```ts
415
+ await client.swap({ quote });
416
+
417
+ // Equivalent to:
418
+ const build = await client.buildSwap({ quote });
419
+ await client.executeSwap({ build });
420
+ ```
421
+
422
+ `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.
423
+
424
+ ## 6. Executor adapters
425
+
426
+ Import each chain executor from its dedicated subpath:
427
+
428
+ ```ts
429
+ import { createEvmExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/evm";
113
430
  import { createSolanaExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/solana";
114
431
  import { createAptosExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/aptos";
115
432
  import { createNearExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/near";
@@ -119,227 +436,249 @@ import { createZcashExecutor } from "@rhea-finance/cross-chain-aggregation-dex/e
119
436
  import { createSuiExecutor } from "@rhea-finance/cross-chain-aggregation-dex/executors/sui";
120
437
  ```
121
438
 
122
- Bitcoin requires `feeRate` in the build or a configured fallback:
439
+ | Chain | Supported execution kinds | Core adapter capabilities |
440
+ | --- | --- | --- |
441
+ | EVM | `evm-transaction`, `evm-signature` | `sendTransaction`, `signTypedData`; optional `waitForTransaction`. |
442
+ | Solana | `solana-transaction` | Submit a serialized transaction; optional confirmation wait. |
443
+ | Aptos | `aptos-entry-function` | Submit an entry function; optional confirmation wait. |
444
+ | NEAR | `near-transaction-batch` | Submit NEAR transactions sequentially or as a batch; optional confirmation wait. |
445
+ | Tron | `tron-transfer` | Submit a native-token or token transfer; optional confirmation wait. |
446
+ | Bitcoin | `bitcoin-transfer` | Submit a UTXO transfer. Configure `defaultFeeRate` when the build does not provide a fee rate. |
447
+ | Zcash | `zcash-transfer` | Submit a transparent-address transfer and return a real `txHash`; optional confirmation wait. |
448
+ | Sui | `sui-transfer` | Submit a coin transfer; optional confirmation wait. |
123
449
 
124
- ```ts
125
- const bitcoinExecutor = createBitcoinExecutor(bitcoinWallet, {
126
- defaultFeeRate: 4,
127
- });
128
- ```
450
+ 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.
129
451
 
130
- Zcash adapters may return `{ requiresUserAction: true }` for legacy wallets that complete transfer confirmation in an external interface. The SDK returns `requires-user-action` without creating a fake transaction hash.
452
+ For MCA quotes, the SDK may also read these methods from the registered executor:
131
453
 
132
- `swap({ quote })` is the convenience form of `buildSwap({ quote })` followed by `executeSwap({ build })`. It does not request another quote.
454
+ - `getIdentityKey()` generates `mca.signer.identityKey` and is required for MCA quotes;
455
+ - `signMessage()` signs the exact API-provided message when required by an MCA relayer withdrawal.
133
456
 
134
- ## MCA swaps
457
+ These are adapter capabilities. Application code calling `client.swap()` does not pass a separate signer.
135
458
 
136
- MCA deposit and withdrawal are execution modes of the unified root API. Use the same `client.quote()`, `client.buildSwap()`, `client.swap()`, `client.report()`, and `client.getHistory()` methods as a regular swap. The SDK does not create MCA accounts or query lending positions.
459
+ ## 7. EVM approvals
137
460
 
138
- HTTP endpoint、请求/响应 JSON curl 示例见 [MCA Swap HTTP API 文档](docs/MCA_SWAP_HTTP_API.md)。SDK 调用方式见 [MCA Swap SDK 文档](docs/MCA_SWAP_API.md)。
461
+ When the swap API build response contains an `approval`, the EVM executor performs these steps in order:
139
462
 
140
- ### Deposit into an MCA
463
+ 1. Call the optional `isApprovalRequired(approval)` method. If it is not implemented, approval is assumed to be required.
464
+ 2. Submit the approval transaction.
465
+ 3. If the adapter implements `waitForTransaction`, wait for the approval transaction to confirm.
466
+ 4. Submit the main swap transaction or request the EIP-712 signature.
141
467
 
142
- The destination asset address is the Burrow token id expected by the Swap API. Execution reuses the registered source-chain executor.
468
+ Applications do not need to query allowances or inspect `needsApprove`. To avoid unnecessary approval transactions, implement `isApprovalRequired` in the adapter:
143
469
 
144
470
  ```ts
145
- const quote = await client.quote({
146
- flow: "deposit",
147
- mcaAccountId: "account.near",
148
- fromChain: "1",
149
- toChain: "near",
150
- tokenIn: ethereumUsdc,
151
- tokenOut: mcaUsdc,
152
- amountIn: "1000000",
153
- slippageBps: 50,
154
- sender: "0x...",
155
- recipient: "account.near",
156
- signerChain: "evm",
157
- collateral: {
158
- useAsCollateral: true,
471
+ const evmAdapter: EvmWalletAdapter = {
472
+ // ...sendTransaction, signTypedData, and other methods
473
+ async isApprovalRequired(approval) {
474
+ const allowance = await readAllowance(approval.spender);
475
+ return allowance < requiredAmount;
159
476
  },
160
- });
161
-
162
- const result = await client.swap({ quote, waitFor: "completed" });
477
+ };
163
478
  ```
164
479
 
165
- The report includes `multi_addr`. Its `tx_type` remains `same-chain` or `cross-chain`, matching the unified Swap API.
480
+ The lifecycle emits `approval-requested`, `approval-submitted`, and the subsequent signing or submission events in order.
481
+
482
+ ## 8. MCA deposits and withdrawals
166
483
 
167
- ### Withdraw directly to NEAR
484
+ 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.
168
485
 
169
- Register a NEAR executor and request the NEAR path. The SDK parses `nearMcaWithdrawTx`, builds the MCA `exec` function call, and asks the injected NEAR wallet to send it. This path does not create an additional off-chain MCA message signature.
486
+ Common MCA fields:
487
+
488
+ | Field | Type | Required | Description |
489
+ | --- | --- | --- | --- |
490
+ | `flow` | `"deposit" \| "withdraw"` | Yes | MCA operation. |
491
+ | `mcaAccountId` | `string` | Yes | NEAR account ID of the MCA. |
492
+ | `signerChain` | `McaSignerChain` | Yes | Selects the registered executor that supplies identity and signing capabilities. |
493
+ | `recipientMsgSignatures` | `string[]` | No | Existing recipient-message signatures forwarded in the MCA payload. |
494
+ | `depositSignerProofSignatures` | `string[]` | No | Existing deposit-signer-proof signatures forwarded in the MCA payload. |
495
+
496
+ ### Deposit
170
497
 
171
498
  ```ts
172
499
  const quote = await client.quote({
173
- flow: "withdraw",
500
+ flow: "deposit",
174
501
  mcaAccountId: "account.near",
175
- fromChain: "near",
502
+ signerChain: "evm",
503
+ fromChain: "42161",
176
504
  toChain: "near",
177
- tokenIn: mcaUsdc,
178
- tokenOut: nearUsdc,
505
+ tokenIn: arbitrumUsdc,
506
+ tokenOut: mcaUsdc,
179
507
  amountIn: "1000000",
180
508
  slippageBps: 50,
181
- sender: "account.near",
182
- recipient: "alice.near",
183
- signerChain: "near",
509
+ sender: "0xYourAddress",
510
+ recipient: "account.near",
184
511
  collateral: {
185
- needDecrease: false,
186
- decreaseAmountBurrow: "0",
512
+ useAsCollateral: true,
187
513
  },
188
- executionPreference: "near",
189
- boundNearAccountId: "alice.near",
190
514
  });
191
515
 
192
- await client.swap({ quote, waitFor: "completed" });
516
+ const result = await client.swap({ quote, waitFor: "completed" });
193
517
  ```
194
518
 
195
- With `executionPreference: "auto"`, NEAR direct execution is selected only when the destination chain is NEAR and `recipient` exactly matches `boundNearAccountId`.
196
-
197
- ### Withdraw through the multichain relayer
519
+ `collateral.useAsCollateral` is a required boolean that specifies whether the deposited asset should be used as Burrow collateral.
198
520
 
199
- For other destination chains, expose `getIdentityKey()` and `signMessage()` on the connected wallet adapter used by its registered executor. The SDK signs the exact `messageToSign` returned by the API, then submits `mcaRelayer` through `POST /api/swap/swap`. No source-chain executor broadcasts a transaction for this path.
521
+ ### Withdraw
200
522
 
201
523
  ```ts
202
524
  const quote = await client.quote({
203
525
  flow: "withdraw",
204
526
  mcaAccountId: "account.near",
527
+ signerChain: "evm",
205
528
  fromChain: "near",
206
- toChain: "1",
529
+ toChain: "8453",
207
530
  tokenIn: mcaUsdc,
208
- tokenOut: ethereumUsdc,
531
+ tokenOut: baseUsdc,
209
532
  amountIn: "1000000",
210
533
  slippageBps: 50,
211
534
  sender: "account.near",
212
- recipient: wallet.address,
213
- signerChain: "evm",
535
+ recipient: "0xYourBaseAddress",
214
536
  collateral: {
215
537
  needDecrease: true,
216
- decreaseAmountBurrow: "1000000",
217
- withdrawAll: true,
538
+ decreaseAmountBurrow: "1.0",
539
+ withdrawAll: false,
218
540
  },
219
541
  executionPreference: "relayer",
220
542
  });
221
543
 
222
- await client.swap({
544
+ const result = await client.swap({
223
545
  quote,
224
546
  waitFor: "completed",
225
547
  beforeSign(preview) {
226
- showMcaSignatureConfirmation(preview);
548
+ console.log("MCA message signature preview", preview);
227
549
  },
228
550
  });
229
551
  ```
230
552
 
231
- Supported MCA signer identity formats are EVM, Solana, Bitcoin, NEAR, Aptos, Sui, Zcash, and Tron. Wallet implementations remain application-owned:
553
+ Withdraw-only fields:
232
554
 
233
- ```ts
234
- import {
235
- formatMcaWallet,
236
- selectMcaSigner,
237
- } from "@rhea-finance/cross-chain-aggregation-dex";
238
-
239
- formatMcaWallet("evm", "0xAbC"); // { EVM: "AbC" }
555
+ | Field | Type | Required | Description |
556
+ | --- | --- | --- | --- |
557
+ | `collateral.needDecrease` | `boolean` | Yes | Whether Burrow collateral must be decreased. |
558
+ | `collateral.decreaseAmountBurrow` | `string` | Yes | Human-readable Burrow decimal amount, such as `"1.0"`. This is not a token base-unit amount. |
559
+ | `collateral.withdrawAll` | `boolean` | No | Whether to withdraw the full available amount. |
560
+ | `executionPreference` | `"auto" \| "near" \| "relayer"` | No | Default: `"auto"`. Set explicitly to force direct NEAR or relayer execution. |
561
+ | `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. |
240
562
 
241
- const signer = selectMcaSigner(boundMcaWallets, connectedSignerIdentities);
242
- ```
563
+ 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()`.
243
564
 
244
- The executor registered for the selected chain must expose `signMessage` when the relayer preview requests a message signature. The SDK never receives a private key or recovery phrase.
565
+ ## 9. Lifecycle, errors, and cancellation
245
566
 
246
- ### Collateral policy
247
-
248
- The SDK does not fetch a lending portfolio. Pass collateral decisions explicitly, or calculate the API fields from data already held by the application:
567
+ Lifecycle events can be observed globally on the client or for an individual swap:
249
568
 
250
569
  ```ts
251
- import { resolveMcaWithdrawPolicy } from "@rhea-finance/cross-chain-aggregation-dex";
252
-
253
- const collateral = resolveMcaWithdrawPolicy({
254
- collateralBalance: "12.5",
255
- availableBalance: "1000000",
256
- amountIn: "999999",
257
- isMax: false,
570
+ const client = new SwapClient({
571
+ baseUrl: "https://api.rhea.finance",
572
+ executors: [evmExecutor],
573
+ onEvent(event) {
574
+ console.log("Swap lifecycle", event);
575
+ },
258
576
  });
259
577
  ```
260
578
 
261
- `withdrawAll` becomes true for max selection, exact available balance, or a ratio of at least `0.999999`. The calculation uses decimal strings and `BigInt`, not floating-point arithmetic.
579
+ Events may cover build, approval, signing, submission, source confirmation, order status, completion, warnings, and failures.
262
580
 
263
- MCA history uses the MCA account id as the server-side history search key. The backend matches this value against `sender`, `recipient`, and `multi_addr`:
581
+ SDK errors use the `SwapSdkError` type:
264
582
 
265
583
  ```ts
266
- const history = await client.getHistory({
267
- sender: "account.near",
268
- });
584
+ import { SwapSdkError } from "@rhea-finance/cross-chain-aggregation-dex";
585
+
586
+ try {
587
+ await client.swap({ quote });
588
+ } catch (error) {
589
+ if (error instanceof SwapSdkError) {
590
+ console.error({
591
+ code: error.code,
592
+ stage: error.stage,
593
+ message: error.message,
594
+ retryable: error.retryable,
595
+ cause: error.cause,
596
+ details: error.details,
597
+ });
598
+ }
599
+ }
269
600
  ```
270
601
 
271
- The SDK preserves the server page instead of filtering `record.multi_addr` again. Address fields may be presentation-normalized by the API, so callers should not require exact equality with the MCA account id.
272
-
273
- ## API surfaces
274
-
275
- Normalized methods:
276
-
277
- - `quote()`
278
- - `buildSwap()`
279
- - `executeSwap()` and `swap()`
280
- - `getOrderStatus()` and `waitForOrder()`
281
- - `report()` and `retryReport()`
282
- - `getHistory()`
283
-
284
- Raw methods preserve the unified API `data` shape:
602
+ Common `stage` values are `quote`, `build`, `approve`, `sign`, `broadcast`, `submit`, `report`, `status`, and `history`. Common `code` values include `QUOTE_EXPIRED`, `USER_REJECTED`, `APPROVAL_FAILED`, `SIGNING_FAILED`, `BROADCAST_FAILED`, and `ORDER_TIMEOUT`.
285
603
 
286
- - `quoteRaw()`
287
- - `buildRaw()`
288
- - `submitOrderRaw()`
289
- - `getOrderStatusRaw()`
290
- - `reportRaw()`
291
- - `getHistoryRaw()`
604
+ Pass an `AbortSignal` to stop an unfinished request or wait:
292
605
 
293
- ## Execution kinds
294
-
295
- Build responses are validated and represented as a discriminated union:
296
-
297
- - `evm-transaction`
298
- - `evm-signature`
299
- - `solana-transaction`
300
- - `aptos-entry-function`
301
- - `near-transaction-batch`
302
- - `tron-transfer`
303
- - `bitcoin-transfer`
304
- - `zcash-transfer`
305
- - `sui-transfer`
306
-
307
- The SDK core defines the executor contract and registry. Wallet-specific implementations are injected by the application, so importing the package does not access browser wallet globals.
308
-
309
- ## Runtime and credentials
310
-
311
- - Browsers and Node.js 18+ use the global Fetch API.
312
- - Node.js 16 requires a compatible `fetch` implementation through `new SwapClient({ fetch })`.
313
- - Supply credentials with `apiKey` or `getAccessToken`; when both are present, `getAccessToken` takes precedence.
314
- - The package contains no fixed API credential and never manages private keys or wallet recovery phrases.
315
- - Use `AbortSignal` on network, build, execute, and polling calls when cancellation is required.
606
+ ```ts
607
+ const controller = new AbortController();
608
+ const promise = client.swap({ quote, signal: controller.signal });
609
+ controller.abort();
610
+ await promise;
611
+ ```
316
612
 
317
- ## Retry and logging
613
+ Cancellation only stops the SDK's current work. It cannot revert an approval, signature, or transaction that has already been submitted.
318
614
 
319
- Quote, history, and order-status requests retry network errors, timeouts, HTTP 429, and retryable 5xx responses twice by default. Build, report, order submission, and wallet execution are never retried automatically.
615
+ ## 10. Retries, logging, and credentials
320
616
 
321
617
  ```ts
322
618
  const client = new SwapClient({
323
619
  baseUrl: "https://api.rhea.finance",
620
+ getAccessToken: async () => authStore.getToken(),
324
621
  retry: {
325
622
  maxRetries: 2,
326
623
  baseDelayMs: 250,
327
- maxDelayMs: 2_000,
624
+ maxDelayMs: 2000,
328
625
  jitter: true,
329
626
  },
330
627
  logger: {
331
628
  log(entry) {
332
- telemetry.emit(entry.event, entry);
629
+ console.log("SDK API", entry);
333
630
  },
334
631
  },
335
632
  });
336
633
  ```
337
634
 
338
- Log entries contain only request stage, endpoint path, attempt, response status, timing, and SDK error code. They do not include credentials, query strings, request bodies, signatures, or serialized transactions.
635
+ 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.
636
+
637
+ ## 11. Raw API, reporting, and history
638
+
639
+ Most applications should use normalized methods. Use the Raw API only when the complete original server fields are required:
640
+
641
+ ```ts
642
+ await client.quoteRaw(rawQuoteRequest);
643
+ await client.buildRaw(rawBuildRequest);
644
+ await client.submitOrderRaw(rawSubmitRequest);
645
+ await client.getOrderStatusRaw(rawStatusRequest);
646
+ await client.reportRaw(rawReportRequest);
647
+ await client.getHistoryRaw(rawHistoryRequest);
648
+ ```
649
+
650
+ 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:
651
+
652
+ ```ts
653
+ const client = new SwapClient({
654
+ baseUrl: "https://api.rhea.finance",
655
+ executors: [evmExecutor],
656
+ reportMode: "manual",
657
+ });
658
+
659
+ const result = await client.swap({ quote });
660
+ await client.report(result);
661
+
662
+ // Retry after a reporting failure:
663
+ await client.retryReport(result);
664
+ ```
665
+
666
+ Query history with:
339
667
 
340
- ## Amount conversion
668
+ ```ts
669
+ const history = await client.getHistory({
670
+ sender: "0xYourAddress",
671
+ page: 1,
672
+ pageSize: 20,
673
+ status: ["processing", "completed"],
674
+ });
675
+ ```
341
676
 
342
- `parseUnits` and `formatUnits` use string arithmetic and never pass token values through floating-point numbers:
677
+ The SDK applies `status` filtering locally. The returned page has `filteredLocally: true`.
678
+
679
+ ## 12. Amount utilities
680
+
681
+ Avoid JavaScript floating-point arithmetic for token amounts:
343
682
 
344
683
  ```ts
345
684
  import {
@@ -347,16 +686,8 @@ import {
347
686
  parseUnits,
348
687
  } from "@rhea-finance/cross-chain-aggregation-dex";
349
688
 
350
- parseUnits("1.25", 6); // "1250000"
689
+ parseUnits("1.25", 6); // "1250000"
351
690
  formatUnits("1250000", 6); // "1.25"
352
691
  ```
353
692
 
354
- Both functions reject negative values, scientific notation, malformed input, and unsupported precision. Token decimals must be an integer between 0 and 255.
355
-
356
- ## History filtering
357
-
358
- The service handles sender and pagination. `getHistory({ status })` filters the current returned page locally and sets `filteredLocally: true`; server totals remain unchanged.
359
-
360
- ## License
361
-
362
- MIT
693
+ `parseUnits()` rejects fractional precision beyond the token's decimals. `formatUnits()` accepts only non-negative base-unit decimal integer strings.