@moonpay/wdk-protocol-swidge-moonpay-trade 0.0.0 → 0.1.1

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.1] - 2026-07-09
11
+
12
+ ### Fixed
13
+ - Default `baseUrl` changed from testing URL `box-v2.api.decent.xyz` to public `api-v2.swaps.xyz`
14
+ - Renamed env vars: `BOX_API_KEY` → `SWAPS_XYZ_KEY`, `BOX_API_BASE_URL` → `SWAPS_XYZ_BASE_URL`
15
+
16
+ ## [0.1.0] - 2026-07-07
17
+
18
+ ### Added
19
+ - `MoonPayTradeSwidgeProtocol` — implements `ISwidgeProtocol` from `@tetherto/wdk-wallet`
20
+ - `quoteSwidge` — non-binding quote via `GET /api/getQuote`
21
+ - `swidge` — deposit-based swap via `POST /api/postQuote` + `POST /api/postSwap`
22
+ - `getSwidgeStatus` — status polling via `GET /api/getStatus`
23
+ - `getSupportedChains` — chain discovery via `GET /api/getChainList`
24
+ - `getSupportedTokens` — token discovery via `GET /api/getTokenList` with optional `fromChain` filter
25
+ - Fee guards (`maxProtocolFeeBps`, `maxNetworkFeeBps`) at constructor and per-call level
26
+ - Module-level TTL cache (default 10 min) shared across instances pointing to the same base URL
27
+ - Typed errors: `AccountRequiredError`, `FeeLimitExceededError`, `ProviderApiError`, `UnknownStatusError`, `InvalidTokenIdError`
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # @moonpay/wdk-protocol-swidge-moonpay-trade
2
+
3
+ [![Powered by WDK](https://img.shields.io/badge/Powered%20by-WDK-purple)](https://docs.wdk.tether.io)
4
+ [![npm](https://img.shields.io/npm/v/@moonpay/wdk-protocol-swidge-moonpay-trade)](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade)
5
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
6
+
7
+ WDK Swidge protocol module for MoonPay Trade. Implements [`ISwidgeProtocol`](https://github.com/tetherto/wdk-wallet/blob/main/src/protocols/swidge-protocol.js) from `@tetherto/wdk-wallet` v1.0.0-beta.9, wrapping MoonPay's SwapsXYZ API swap and bridge infrastructure.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @moonpay/wdk-protocol-swidge-moonpay-trade
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```js
18
+ import MoonPayTradeSwidgeProtocol from '@moonpay/wdk-protocol-swidge-moonpay-trade';
19
+
20
+ const protocol = new MoonPayTradeSwidgeProtocol(account, {
21
+ apiKey: process.env.SWAPS_XYZ_KEY,
22
+ });
23
+
24
+ // Discover supported tokens
25
+ const chains = await protocol.getSupportedChains();
26
+ const tokens = await protocol.getSupportedTokens({ fromChain: 1 }); // Ethereum
27
+
28
+ // Quote
29
+ const quote = await protocol.quoteSwidge({
30
+ fromToken: '1:0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT on Ethereum
31
+ toToken: '8453:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC on Base
32
+ fromTokenAmount: 10_000_000n, // 10 USDT (6 decimals)
33
+ });
34
+
35
+ // Execute (requires full IWalletAccount)
36
+ const result = await protocol.swidge({ /* same options */ });
37
+
38
+ // Poll status
39
+ const status = await protocol.getSwidgeStatus(result.id);
40
+ ```
41
+
42
+ See [`examples/e2e.mjs`](examples/e2e.mjs) for a complete runnable example.
43
+
44
+ ## Token identifiers
45
+
46
+ Tokens are identified as `"<chainId>:<tokenAddress>"`. Use `getSupportedTokens()` to discover valid identifiers:
47
+
48
+ ```js
49
+ const tokens = await protocol.getSupportedTokens({ fromChain: 1 });
50
+ // e.g. { token: '1:0xA0b8...', chain: 1, symbol: 'USDC', decimals: 6 }
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ | Option | Type | Required | Default | Description |
56
+ |---|---|---|---|---|
57
+ | `apiKey` | `string` | ✅ | — | `x-api-key` credential for SwapsXYZ API |
58
+ | `baseUrl` | `string` | | `https://api-v2.swaps.xyz` | SwapsXYZ API base URL |
59
+ | `maxProtocolFeeBps` | `number \| bigint` | | — | Reject swidge if protocol fee exceeds this bps |
60
+ | `maxNetworkFeeBps` | `number \| bigint` | | — | Reject swidge if network fee exceeds this bps |
61
+ | `cacheTtlMs` | `number` | | `600000` (10 min) | Token/chain list cache TTL. Set `0` to disable. |
62
+
63
+ ## Status mapping
64
+
65
+ | SwapsXYZ API status | `SwidgeStatus` |
66
+ |---|---|
67
+ | `pending`, `submitted`, `approval_*`, `payout_created` | `pending` |
68
+ | `success`, `completed` | `completed` |
69
+ | `failed` | `failed` |
70
+ | `requires refund`, `refund_requested` | `refund-pending` |
71
+ | `refunded` | `refunded` |
72
+ | `expired` | `expired` |
73
+ | `cancelled` | `cancelled` |
74
+
75
+ ## Fee mapping
76
+
77
+ | SwapsXYZ API field | `SwidgeFeeType` | Notes |
78
+ |---|---|---|
79
+ | `applicationFee` + `protocolFee` | `protocol` | MoonPay spread + underlying protocol fee (both summed) |
80
+ | `bridgeFee` | `network` | Gas / bridge infrastructure cost |
81
+
82
+ ## Error types
83
+
84
+ | Class | Thrown when |
85
+ |---|---|
86
+ | `AccountRequiredError` | `swidge()` called without a full `IWalletAccount` |
87
+ | `FeeLimitExceededError` | Fee guard (`maxProtocolFeeBps` / `maxNetworkFeeBps`) exceeded; `.feeType`, `.actualBps`, `.limitBps` available |
88
+ | `ProviderApiError` | SwapsXYZ API returns a non-2xx response; `.endpoint`, `.status`, `.responseBody` available |
89
+ | `UnknownStatusError` | `getSwidgeStatus()` receives an unrecognised status string; `.rawStatus` available |
90
+ | `InvalidTokenIdError` | Token identifier is not in `"<chainId>:<address>"` format; `.tokenId` available |
91
+
92
+ ## Supported chains and tokens
93
+
94
+ Chains and tokens are dynamically discovered from the SwapsXYZ API at runtime:
95
+
96
+ ```js
97
+ const chains = await protocol.getSupportedChains(); // all chains
98
+ const tokens = await protocol.getSupportedTokens(); // all tokens
99
+ const ethTokens = await protocol.getSupportedTokens({ fromChain: 1 }); // Ethereum only
100
+ ```
101
+
102
+ Results are cached for 10 minutes by default (configurable via `cacheTtlMs`).
103
+
104
+ ## Support
105
+
106
+ - Security vulnerabilities: see [SECURITY.md](SECURITY.md)
107
+ - General support: [moonpay.com](https://www.moonpay.com)
package/SECURITY.md ADDED
@@ -0,0 +1,9 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ To report a security vulnerability, please email security@moonpay.com.
6
+
7
+ Do not open a public GitHub issue for security vulnerabilities.
8
+
9
+ We will respond within 5 business days and aim to publish a fix within 30 days.
@@ -0,0 +1,124 @@
1
+ import type { IWalletAccount, IWalletAccountReadOnly } from '@tetherto/wdk-wallet';
2
+ import type { SwidgeOptions, SwidgeProtocolConfig, SwidgeQuote, SwidgeResult, SwidgeStatusOptions, SwidgeStatusResult, SwidgeSupportedChain, SwidgeSupportedToken } from '@tetherto/wdk-wallet/protocols';
3
+ import { SwidgeProtocol } from '@tetherto/wdk-wallet/protocols';
4
+ /** Configuration passed to the MoonPayTradeSwidgeProtocol constructor. */
5
+ export interface MoonPayTradeConfig {
6
+ /** Required. The `x-api-key` credential for SwapsXYZ API. */
7
+ apiKey: string;
8
+ /**
9
+ * Base URL for SwapsXYZ API requests.
10
+ * @default https://api-v2.swaps.xyz
11
+ */
12
+ baseUrl?: string;
13
+ /**
14
+ * Maximum acceptable protocol fee in basis points of the input amount.
15
+ * Overrides the per-call `config.maxProtocolFeeBps` when not set per call.
16
+ */
17
+ maxProtocolFeeBps?: number | bigint;
18
+ /**
19
+ * Maximum acceptable network fee in basis points of the input amount.
20
+ * Overrides the per-call `config.maxNetworkFeeBps` when not set per call.
21
+ */
22
+ maxNetworkFeeBps?: number | bigint;
23
+ /**
24
+ * TTL for the token/chain list cache in milliseconds.
25
+ * Set to 0 to disable caching.
26
+ * @default 600000 (10 minutes)
27
+ */
28
+ cacheTtlMs?: number;
29
+ }
30
+ /**
31
+ * WDK Swidge protocol implementation for MoonPay Trade (SwapsXYZ API).
32
+ *
33
+ * Token identifiers use the format `"<chainId>:<tokenAddress>"`,
34
+ * e.g. `"1:0xdAC17F958D2ee523a2206206994597C13D831ec7"` for USDT on Ethereum.
35
+ *
36
+ * @example
37
+ * ```js
38
+ * import MoonPayTradeSwidgeProtocol from '@moonpay/wdk-protocol-swidge-moonpay-trade';
39
+ *
40
+ * const protocol = new MoonPayTradeSwidgeProtocol(account, { apiKey: process.env.SWAPS_XYZ_KEY });
41
+ * const quote = await protocol.quoteSwidge({ fromToken: '1:0x...', toToken: '10:0x...', fromTokenAmount: 1000000n });
42
+ * const result = await protocol.swidge({ fromToken: '1:0x...', toToken: '10:0x...', fromTokenAmount: 1000000n });
43
+ * const status = await protocol.getSwidgeStatus(result.id);
44
+ * ```
45
+ */
46
+ export default class MoonPayTradeSwidgeProtocol extends SwidgeProtocol {
47
+ #private;
48
+ /**
49
+ * @param account - Wallet account. Pass `IWalletAccount` for full access (required by
50
+ * `swidge`), `IWalletAccountReadOnly` for quote/status/discovery only, or `undefined`
51
+ * for quote and discovery only.
52
+ * @param config - Provider configuration including the required SwapsXYZ API key.
53
+ */
54
+ constructor(account: IWalletAccount | IWalletAccountReadOnly | undefined, config: MoonPayTradeConfig);
55
+ /** Clear the shared module-level cache. Useful in tests or to force a refresh. */
56
+ static clearCache(): void;
57
+ /**
58
+ * Returns a non-binding quote for a swap or bridge operation.
59
+ *
60
+ * Can be called without a wallet account.
61
+ *
62
+ * @param options - Source/destination tokens, amount, and optional routing hints.
63
+ * @returns Quote including `fromTokenAmount`, `toTokenAmount`, `toTokenAmountMin`, and
64
+ * a non-empty `fees` array.
65
+ * @throws {InvalidTokenIdError} If `fromToken` or `toToken` is not in `"<chainId>:<address>"` format.
66
+ * @throws {ProviderApiError} If the SwapsXYZ API request fails.
67
+ */
68
+ quoteSwidge(options: SwidgeOptions): Promise<SwidgeQuote>;
69
+ /**
70
+ * Executes a swap or bridge operation using the deposit-address flow.
71
+ *
72
+ * Internally calls POST /api/postQuote (expects decimal amounts) then POST /api/postSwap.
73
+ * Token decimals are fetched from GET /api/getTokenList and cached to convert
74
+ * WDK base-unit bigint amounts ↔ the decimal strings postQuote requires.
75
+ *
76
+ * @param options - Same shape as `quoteSwidge`. No quote object is accepted.
77
+ * @param config - Optional per-call fee overrides.
78
+ * @returns Execution result including `id` (swapId) to pass to `getSwidgeStatus`.
79
+ * @throws {AccountRequiredError} If no account or a read-only account was given at construction.
80
+ * @throws {FeeLimitExceededError} If a fee guard is exceeded.
81
+ * @throws {InvalidTokenIdError} If `fromToken` or `toToken` is not in `"<chainId>:<address>"` format.
82
+ * @throws {ProviderApiError} If a SwapsXYZ API request fails.
83
+ */
84
+ swidge(options: SwidgeOptions, config?: SwidgeProtocolConfig): Promise<SwidgeResult>;
85
+ /**
86
+ * Returns the current status of an in-flight swap or bridge.
87
+ *
88
+ * Poll until the returned `status` is terminal:
89
+ * `completed`, `failed`, `refunded`, `cancelled`, `expired`, or `partial`.
90
+ *
91
+ * @param id - The `id` returned by `swidge`.
92
+ * @param _options - Optional chain hints (currently unused).
93
+ * @returns Current status and any associated on-chain transaction hashes.
94
+ * @throws {UnknownStatusError} If the provider returns an unrecognised status.
95
+ * @throws {ProviderApiError} If the SwapsXYZ API request fails or the id is not found.
96
+ */
97
+ getSwidgeStatus(id: string, _options?: SwidgeStatusOptions): Promise<SwidgeStatusResult>;
98
+ /**
99
+ * Returns the chains supported by MoonPay Trade for swidge operations.
100
+ *
101
+ * Calls GET /api/getChainList and enriches each entry with the native token symbol
102
+ * sourced from box-common static data (no extra API call needed).
103
+ * Results are cached with TTL (default 10 min).
104
+ *
105
+ * @throws {ProviderApiError} If the getChainList request fails.
106
+ */
107
+ getSupportedChains(): Promise<SwidgeSupportedChain[]>;
108
+ /**
109
+ * Returns the tokens supported by MoonPay Trade for swidge operations.
110
+ *
111
+ * When `options.fromChain` is provided, fetches only that chain's tokens.
112
+ * Otherwise fetches all chains in a single request.
113
+ * Results are cached with TTL (default 10 min).
114
+ *
115
+ * @param options - Optional `fromChain` filter (other fields are not used by this provider).
116
+ * @throws {ProviderApiError} If the getTokenList request fails.
117
+ */
118
+ getSupportedTokens(options?: {
119
+ fromChain?: string | number;
120
+ fromToken?: string;
121
+ toChain?: string | number;
122
+ }): Promise<SwidgeSupportedToken[]>;
123
+ }
124
+ //# sourceMappingURL=MoonPayTradeSwidgeProtocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MoonPayTradeSwidgeProtocol.d.ts","sourceRoot":"","sources":["../src/MoonPayTradeSwidgeProtocol.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EACpB,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AA6BhE,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,OAAO,0BAA2B,SAAQ,cAAc;;IAOpE;;;;;OAKG;gBAED,OAAO,EAAE,cAAc,GAAG,sBAAsB,GAAG,SAAS,EAC5D,MAAM,EAAE,kBAAkB;IAkB5B,kFAAkF;IAClF,MAAM,CAAC,UAAU,IAAI,IAAI;IA0DzB;;;;;;;;;;OAUG;IACY,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC;IAoBxE;;;;;;;;;;;;;;OAcG;IACY,MAAM,CACnB,OAAO,EAAE,aAAa,EACtB,MAAM,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,YAAY,CAAC;IAoFxB;;;;;;;;;;;OAWG;IACY,eAAe,CAC5B,EAAE,EAAE,MAAM,EACV,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,kBAAkB,CAAC;IAM9B;;;;;;;;OAQG;IACY,kBAAkB,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAkBpE;;;;;;;;;OASG;IACY,kBAAkB,CAAC,OAAO,CAAC,EAAE;QAC1C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KAC3B,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;CA4BpC"}
@@ -0,0 +1,35 @@
1
+ /** Thrown when swidge() is called without a full (signing-capable) wallet account. */
2
+ export declare class AccountRequiredError extends Error {
3
+ readonly name = "AccountRequiredError";
4
+ constructor();
5
+ }
6
+ /** Thrown when a fee guard (maxProtocolFeeBps or maxNetworkFeeBps) is exceeded. */
7
+ export declare class FeeLimitExceededError extends Error {
8
+ readonly feeType: 'protocol' | 'network';
9
+ readonly actualBps: bigint;
10
+ readonly limitBps: bigint;
11
+ readonly name = "FeeLimitExceededError";
12
+ constructor(feeType: 'protocol' | 'network', actualBps: bigint, limitBps: bigint);
13
+ }
14
+ /** Thrown when the provider returns a status value that cannot be mapped to SwidgeStatus. */
15
+ export declare class UnknownStatusError extends Error {
16
+ readonly rawStatus: string;
17
+ readonly name = "UnknownStatusError";
18
+ constructor(rawStatus: string);
19
+ }
20
+ /** Thrown when a token identifier does not match the expected "<chainId>:<address>" format. */
21
+ export declare class InvalidTokenIdError extends Error {
22
+ readonly tokenId: string;
23
+ readonly name = "InvalidTokenIdError";
24
+ constructor(tokenId: string);
25
+ }
26
+ /** Thrown when the provider API returns a non-2xx response. */
27
+ export declare class ProviderApiError extends Error {
28
+ readonly endpoint: string;
29
+ readonly status: number;
30
+ readonly name = "ProviderApiError";
31
+ /** Truncated response body — inspect this field for debugging, not error.message. */
32
+ readonly responseBody: string;
33
+ constructor(endpoint: string, status: number, body: string);
34
+ }
35
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAcA,sFAAsF;AACtF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,SAAkB,IAAI,0BAA0B;;CAIjD;AAED,mFAAmF;AACnF,qBAAa,qBAAsB,SAAQ,KAAK;aAG5B,OAAO,EAAE,UAAU,GAAG,SAAS;aAC/B,SAAS,EAAE,MAAM;aACjB,QAAQ,EAAE,MAAM;IAJlC,SAAkB,IAAI,2BAA2B;gBAE/B,OAAO,EAAE,UAAU,GAAG,SAAS,EAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM;CAInC;AAED,6FAA6F;AAC7F,qBAAa,kBAAmB,SAAQ,KAAK;aAEf,SAAS,EAAE,MAAM;IAD7C,SAAkB,IAAI,wBAAwB;gBAClB,SAAS,EAAE,MAAM;CAG9C;AAED,+FAA+F;AAC/F,qBAAa,mBAAoB,SAAQ,KAAK;aAEhB,OAAO,EAAE,MAAM;IAD3C,SAAkB,IAAI,yBAAyB;gBACnB,OAAO,EAAE,MAAM;CAG5C;AAED,+DAA+D;AAC/D,qBAAa,gBAAiB,SAAQ,KAAK;aAKvB,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,MAAM;IALhC,SAAkB,IAAI,sBAAsB;IAC5C,qFAAqF;IACrF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAEZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAC9B,IAAI,EAAE,MAAM;CAMf"}
@@ -0,0 +1,5 @@
1
+ export type { ISwidgeProtocol } from '@tetherto/wdk-wallet/protocols';
2
+ export { AccountRequiredError, FeeLimitExceededError, InvalidTokenIdError, ProviderApiError, UnknownStatusError, } from './errors.js';
3
+ export type { MoonPayTradeConfig } from './MoonPayTradeSwidgeProtocol.js';
4
+ export { default, default as MoonPayTradeSwidgeProtocol } from './MoonPayTradeSwidgeProtocol.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,YAAY,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,iCAAiC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,448 @@
1
+ import { getNativeTokenInfo } from '@decent.xyz/box-common';
2
+ import { formatUnits, parseUnits } from 'viem';
3
+ import { SwidgeProtocol } from '@tetherto/wdk-wallet/protocols';
4
+
5
+ // src/errors.ts
6
+ var AccountRequiredError = class extends Error {
7
+ name = "AccountRequiredError";
8
+ constructor() {
9
+ super("swidge requires a full wallet account \u2014 pass an IWalletAccount at construction");
10
+ }
11
+ };
12
+ var FeeLimitExceededError = class extends Error {
13
+ constructor(feeType, actualBps, limitBps) {
14
+ super(`${feeType} fee ${actualBps}bps exceeds configured maximum ${limitBps}bps`);
15
+ this.feeType = feeType;
16
+ this.actualBps = actualBps;
17
+ this.limitBps = limitBps;
18
+ }
19
+ feeType;
20
+ actualBps;
21
+ limitBps;
22
+ name = "FeeLimitExceededError";
23
+ };
24
+ var UnknownStatusError = class extends Error {
25
+ constructor(rawStatus) {
26
+ super(`Unknown swap status: "${rawStatus}"`);
27
+ this.rawStatus = rawStatus;
28
+ }
29
+ rawStatus;
30
+ name = "UnknownStatusError";
31
+ };
32
+ var InvalidTokenIdError = class extends Error {
33
+ constructor(tokenId) {
34
+ super(`Invalid SwapsXYZ token identifier: "${tokenId}" (expected "<chainId>:<address>")`);
35
+ this.tokenId = tokenId;
36
+ }
37
+ tokenId;
38
+ name = "InvalidTokenIdError";
39
+ };
40
+ var ProviderApiError = class extends Error {
41
+ constructor(endpoint, status, body) {
42
+ super(`SwapsXYZ ${endpoint} failed [${status}]`);
43
+ this.endpoint = endpoint;
44
+ this.status = status;
45
+ this.responseBody = body.slice(0, 500);
46
+ }
47
+ endpoint;
48
+ status;
49
+ name = "ProviderApiError";
50
+ /** Truncated response body — inspect this field for debugging, not error.message. */
51
+ responseBody;
52
+ };
53
+ function formatBaseUnitsToDecimal(baseUnits, decimals) {
54
+ const value = typeof baseUnits === "string" ? BigInt(baseUnits) : baseUnits;
55
+ return formatUnits(value, decimals);
56
+ }
57
+ function parseDecimalFeeToBaseUnits(decimalAmount, decimals) {
58
+ const trimmed = decimalAmount.trim();
59
+ if (!trimmed || !/^[0-9.]+$/.test(trimmed)) {
60
+ throw new Error(`Invalid decimal amount: "${decimalAmount}"`);
61
+ }
62
+ return parseUnits(trimmed, decimals);
63
+ }
64
+ function parseTokenId(tokenId) {
65
+ const colon = tokenId.indexOf(":");
66
+ if (colon < 1) throw new InvalidTokenIdError(tokenId);
67
+ const chainId = Number(tokenId.slice(0, colon));
68
+ if (!Number.isInteger(chainId) || chainId <= 0) throw new InvalidTokenIdError(tokenId);
69
+ return { chainId, address: tokenId.slice(colon + 1) };
70
+ }
71
+ function paymentAmountToBigInt(amount) {
72
+ const dot = amount.indexOf(".");
73
+ return BigInt(dot >= 0 ? amount.slice(0, dot) : amount);
74
+ }
75
+ function paymentToFeeFromQuote(payment, type) {
76
+ return {
77
+ type,
78
+ amount: paymentAmountToBigInt(payment.amount),
79
+ token: `${payment.chainId}:${payment.address}`,
80
+ chain: payment.chainId
81
+ };
82
+ }
83
+ function toSwidgeQuote(response) {
84
+ const fees = [
85
+ paymentToFeeFromQuote(response.applicationFee, "protocol")
86
+ // our spread
87
+ ];
88
+ if (response.bridgeFee) {
89
+ fees.push(paymentToFeeFromQuote(response.bridgeFee, "network"));
90
+ }
91
+ const protocolFeeAmount = paymentAmountToBigInt(response.protocolFee.amount);
92
+ if (protocolFeeAmount > 0n) {
93
+ fees.push(paymentToFeeFromQuote(response.protocolFee, "other"));
94
+ }
95
+ return {
96
+ fromTokenAmount: paymentAmountToBigInt(response.amountIn.amount),
97
+ toTokenAmount: paymentAmountToBigInt(response.amountOut.amount),
98
+ toTokenAmountMin: paymentAmountToBigInt(response.amountOutMin.amount),
99
+ fees,
100
+ estimatedDuration: response.estimatedTxTime,
101
+ priceImpact: response.estimatedPriceImpact,
102
+ expiry: response.expiryUnix
103
+ };
104
+ }
105
+ function toSwidgeResult(quote, swap, srcChainId, srcTokenAddress, fromDecimals, toDecimals) {
106
+ const fromToken = `${srcChainId}:${srcTokenAddress}`;
107
+ const fees = [
108
+ {
109
+ type: "protocol",
110
+ amount: parseDecimalFeeToBaseUnits(quote.applicationFee, fromDecimals),
111
+ token: fromToken,
112
+ chain: srcChainId
113
+ },
114
+ {
115
+ type: "network",
116
+ amount: parseDecimalFeeToBaseUnits(quote.bridgeFee, fromDecimals),
117
+ token: fromToken,
118
+ chain: srcChainId
119
+ }
120
+ ];
121
+ const protocolFeeAmount = parseDecimalFeeToBaseUnits(quote.protocolFee, fromDecimals);
122
+ if (protocolFeeAmount > 0n) {
123
+ fees.push({ type: "other", amount: protocolFeeAmount, token: fromToken, chain: srcChainId });
124
+ }
125
+ return {
126
+ id: swap.swapId,
127
+ fees,
128
+ fromTokenAmount: parseDecimalFeeToBaseUnits(quote.amount, fromDecimals),
129
+ toTokenAmount: parseDecimalFeeToBaseUnits(quote.amountTo, toDecimals),
130
+ transactions: [{ hash: swap.swapId, type: "source", chain: srcChainId }]
131
+ };
132
+ }
133
+ var STATUS_MAP = {
134
+ // box-backend StatusMessage values
135
+ pending: "pending",
136
+ submitted: "pending",
137
+ rent_gas_submitted: "pending",
138
+ rent_gas_completed: "pending",
139
+ approval_submitted: "pending",
140
+ approval_success: "pending",
141
+ success: "completed",
142
+ completed: "completed",
143
+ failed: "failed",
144
+ "requires refund": "refund-pending",
145
+ refunded: "refunded",
146
+ expired: "expired",
147
+ // altvm TransactionStatus values
148
+ payout_created: "pending",
149
+ refund_requested: "refund-pending",
150
+ cancelled: "cancelled"
151
+ };
152
+ function toSwidgeSupportedChain(chain, nativeSymbol) {
153
+ return {
154
+ id: chain.chainId,
155
+ name: chain.name,
156
+ type: chain.vmId,
157
+ nativeToken: nativeSymbol
158
+ };
159
+ }
160
+ function toSwidgeSupportedToken(token, chainId) {
161
+ if (!token.symbol) return null;
162
+ return {
163
+ token: `${chainId}:${token.address}`,
164
+ chain: chainId,
165
+ symbol: token.symbol,
166
+ decimals: token.decimals,
167
+ address: token.address,
168
+ ...token.name ? { name: token.name } : {}
169
+ };
170
+ }
171
+ function toSwidgeStatusResult(response) {
172
+ const rawStatus = response.status?.toLowerCase?.() ?? "";
173
+ const mapped = STATUS_MAP[rawStatus];
174
+ if (!mapped) throw new UnknownStatusError(response.status ?? "");
175
+ const transactions = [];
176
+ if (response.srcTxHash) {
177
+ transactions.push({ hash: response.srcTxHash, type: "source", chain: response.srcChainId });
178
+ }
179
+ if (response.dstTxHash) {
180
+ transactions.push({
181
+ hash: response.dstTxHash,
182
+ type: "destination",
183
+ chain: response.dstChainId
184
+ });
185
+ }
186
+ return {
187
+ status: mapped,
188
+ transactions: transactions.length > 0 ? transactions : void 0
189
+ };
190
+ }
191
+ var DEFAULT_BASE_URL = "https://api-v2.swaps.xyz";
192
+ var DEFAULT_SLIPPAGE = 5e-3;
193
+ var DEFAULT_DECIMALS = 18;
194
+ var DEFAULT_CACHE_TTL_MS = 10 * 60 * 1e3;
195
+ var _tokenCache = /* @__PURE__ */ new Map();
196
+ var _chainCache = /* @__PURE__ */ new Map();
197
+ var MoonPayTradeSwidgeProtocol = class extends SwidgeProtocol {
198
+ #apiKey;
199
+ #baseUrl;
200
+ #maxProtocolFeeBps;
201
+ #maxNetworkFeeBps;
202
+ #cacheTtlMs;
203
+ /**
204
+ * @param account - Wallet account. Pass `IWalletAccount` for full access (required by
205
+ * `swidge`), `IWalletAccountReadOnly` for quote/status/discovery only, or `undefined`
206
+ * for quote and discovery only.
207
+ * @param config - Provider configuration including the required SwapsXYZ API key.
208
+ */
209
+ constructor(account, config) {
210
+ super(void 0, {
211
+ maxNetworkFeeBps: config.maxNetworkFeeBps,
212
+ maxProtocolFeeBps: config.maxProtocolFeeBps
213
+ });
214
+ this._account = account;
215
+ this.#apiKey = config.apiKey;
216
+ this.#baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
217
+ this.#maxProtocolFeeBps = config.maxProtocolFeeBps !== void 0 ? BigInt(config.maxProtocolFeeBps) : void 0;
218
+ this.#maxNetworkFeeBps = config.maxNetworkFeeBps !== void 0 ? BigInt(config.maxNetworkFeeBps) : void 0;
219
+ this.#cacheTtlMs = config.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS;
220
+ }
221
+ /** Clear the shared module-level cache. Useful in tests or to force a refresh. */
222
+ static clearCache() {
223
+ _tokenCache.clear();
224
+ _chainCache.clear();
225
+ }
226
+ async #get(path, params) {
227
+ const url = new URL(path, this.#baseUrl);
228
+ for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
229
+ const res = await fetch(url.toString(), {
230
+ headers: { "x-api-key": this.#apiKey }
231
+ });
232
+ if (!res.ok) {
233
+ const body = await res.text().catch(() => "");
234
+ throw new ProviderApiError(path, res.status, body);
235
+ }
236
+ return res.json();
237
+ }
238
+ async #post(path, body) {
239
+ const res = await fetch(`${this.#baseUrl}${path}`, {
240
+ method: "POST",
241
+ headers: {
242
+ "x-api-key": this.#apiKey,
243
+ "Content-Type": "application/json"
244
+ },
245
+ body: JSON.stringify(body)
246
+ });
247
+ if (!res.ok) {
248
+ const text = await res.text().catch(() => "");
249
+ throw new ProviderApiError(path, res.status, text);
250
+ }
251
+ return res.json();
252
+ }
253
+ async #getTokenList(chainId) {
254
+ const key = `${this.#baseUrl}:${chainId}`;
255
+ const entry = _tokenCache.get(key);
256
+ if (entry && (this.#cacheTtlMs === 0 || entry.exp > Date.now())) return entry.data;
257
+ const response = await this.#get("/api/getTokenList", {
258
+ chainId: String(chainId)
259
+ });
260
+ if (this.#cacheTtlMs > 0) {
261
+ _tokenCache.set(key, { data: response.tokens, exp: Date.now() + this.#cacheTtlMs });
262
+ }
263
+ return response.tokens;
264
+ }
265
+ /**
266
+ * Returns the decimal precision for a token on a given chain.
267
+ * Falls back to 18 decimals if the token is not found.
268
+ */
269
+ async #fetchTokenDecimals(chainId, address) {
270
+ const tokens = await this.#getTokenList(chainId);
271
+ const normalizedAddress = address.toLowerCase();
272
+ const token = tokens.find((t) => t.address.toLowerCase() === normalizedAddress);
273
+ return token?.decimals ?? DEFAULT_DECIMALS;
274
+ }
275
+ /**
276
+ * Returns a non-binding quote for a swap or bridge operation.
277
+ *
278
+ * Can be called without a wallet account.
279
+ *
280
+ * @param options - Source/destination tokens, amount, and optional routing hints.
281
+ * @returns Quote including `fromTokenAmount`, `toTokenAmount`, `toTokenAmountMin`, and
282
+ * a non-empty `fees` array.
283
+ * @throws {InvalidTokenIdError} If `fromToken` or `toToken` is not in `"<chainId>:<address>"` format.
284
+ * @throws {ProviderApiError} If the SwapsXYZ API request fails.
285
+ */
286
+ async quoteSwidge(options) {
287
+ const { chainId: srcChainId, address: srcToken } = parseTokenId(options.fromToken);
288
+ const { chainId: dstChainId, address: dstToken } = parseTokenId(options.toToken);
289
+ const exactIn = options.fromTokenAmount !== void 0;
290
+ const amount = String(exactIn ? options.fromTokenAmount : options.toTokenAmount);
291
+ const swapDirection = exactIn ? "exact-amount-in" : "exact-amount-out";
292
+ const response = await this.#get("/api/getQuote", {
293
+ srcToken,
294
+ srcChainId: String(srcChainId),
295
+ dstToken,
296
+ dstChainId: String(dstChainId),
297
+ amount,
298
+ swapDirection
299
+ });
300
+ return toSwidgeQuote(response);
301
+ }
302
+ /**
303
+ * Executes a swap or bridge operation using the deposit-address flow.
304
+ *
305
+ * Internally calls POST /api/postQuote (expects decimal amounts) then POST /api/postSwap.
306
+ * Token decimals are fetched from GET /api/getTokenList and cached to convert
307
+ * WDK base-unit bigint amounts ↔ the decimal strings postQuote requires.
308
+ *
309
+ * @param options - Same shape as `quoteSwidge`. No quote object is accepted.
310
+ * @param config - Optional per-call fee overrides.
311
+ * @returns Execution result including `id` (swapId) to pass to `getSwidgeStatus`.
312
+ * @throws {AccountRequiredError} If no account or a read-only account was given at construction.
313
+ * @throws {FeeLimitExceededError} If a fee guard is exceeded.
314
+ * @throws {InvalidTokenIdError} If `fromToken` or `toToken` is not in `"<chainId>:<address>"` format.
315
+ * @throws {ProviderApiError} If a SwapsXYZ API request fails.
316
+ */
317
+ async swidge(options, config) {
318
+ if (!this._account || !("sendTransaction" in this._account)) {
319
+ throw new AccountRequiredError();
320
+ }
321
+ const { chainId: srcChainId, address: srcToken } = parseTokenId(options.fromToken);
322
+ const { chainId: dstChainId, address: dstToken } = parseTokenId(options.toToken);
323
+ const senderAddress = await this._account.getAddress();
324
+ const recipient = options.recipient ?? senderAddress;
325
+ const [fromDecimals, toDecimals] = await Promise.all([
326
+ this.#fetchTokenDecimals(srcChainId, srcToken),
327
+ this.#fetchTokenDecimals(dstChainId, dstToken)
328
+ ]);
329
+ const exactIn = options.fromTokenAmount !== void 0;
330
+ const rawAmount = exactIn ? options.fromTokenAmount ?? 0n : options.toTokenAmount ?? 0n;
331
+ const decimalAmount = formatBaseUnitsToDecimal(
332
+ BigInt(rawAmount),
333
+ exactIn ? fromDecimals : toDecimals
334
+ );
335
+ const quote = await this.#post("/api/postQuote", {
336
+ fromChainId: srcChainId,
337
+ fromToken: srcToken,
338
+ toChainId: dstChainId,
339
+ toToken: dstToken,
340
+ amount: decimalAmount,
341
+ tradeMethod: "float",
342
+ slippage: options.slippage ?? DEFAULT_SLIPPAGE,
343
+ senderAddress,
344
+ receiverAddress: recipient,
345
+ ...options.refundAddress ? { refundAddress: options.refundAddress } : {}
346
+ });
347
+ const maxProtocolBps = config?.maxProtocolFeeBps !== void 0 ? BigInt(config.maxProtocolFeeBps) : this.#maxProtocolFeeBps;
348
+ const maxNetworkBps = config?.maxNetworkFeeBps !== void 0 ? BigInt(config.maxNetworkFeeBps) : this.#maxNetworkFeeBps;
349
+ if (maxProtocolBps !== void 0 || maxNetworkBps !== void 0) {
350
+ const inputBigInt = exactIn ? BigInt(rawAmount) : parseDecimalFeeToBaseUnits(quote.amount, fromDecimals);
351
+ const bps = (fee) => parseDecimalFeeToBaseUnits(fee, fromDecimals) * 10000n / inputBigInt;
352
+ if (maxProtocolBps !== void 0) {
353
+ const actual = bps(quote.applicationFee);
354
+ if (actual > maxProtocolBps)
355
+ throw new FeeLimitExceededError("protocol", actual, maxProtocolBps);
356
+ }
357
+ if (maxNetworkBps !== void 0) {
358
+ const actual = bps(quote.bridgeFee);
359
+ if (actual > maxNetworkBps)
360
+ throw new FeeLimitExceededError("network", actual, maxNetworkBps);
361
+ }
362
+ }
363
+ const swap = await this.#post("/api/postSwap", {
364
+ quoteId: quote.quoteId,
365
+ senderAddress,
366
+ receiverAddress: recipient,
367
+ ...options.refundAddress ? { refundAddress: options.refundAddress } : {},
368
+ slippage: options.slippage ?? DEFAULT_SLIPPAGE
369
+ });
370
+ return toSwidgeResult(quote, swap, srcChainId, srcToken, fromDecimals, toDecimals);
371
+ }
372
+ /**
373
+ * Returns the current status of an in-flight swap or bridge.
374
+ *
375
+ * Poll until the returned `status` is terminal:
376
+ * `completed`, `failed`, `refunded`, `cancelled`, `expired`, or `partial`.
377
+ *
378
+ * @param id - The `id` returned by `swidge`.
379
+ * @param _options - Optional chain hints (currently unused).
380
+ * @returns Current status and any associated on-chain transaction hashes.
381
+ * @throws {UnknownStatusError} If the provider returns an unrecognised status.
382
+ * @throws {ProviderApiError} If the SwapsXYZ API request fails or the id is not found.
383
+ */
384
+ async getSwidgeStatus(id, _options) {
385
+ if (!id) throw new Error("getSwidgeStatus: id is required");
386
+ const response = await this.#get("/api/getStatus", { txId: id });
387
+ return toSwidgeStatusResult(response);
388
+ }
389
+ /**
390
+ * Returns the chains supported by MoonPay Trade for swidge operations.
391
+ *
392
+ * Calls GET /api/getChainList and enriches each entry with the native token symbol
393
+ * sourced from box-common static data (no extra API call needed).
394
+ * Results are cached with TTL (default 10 min).
395
+ *
396
+ * @throws {ProviderApiError} If the getChainList request fails.
397
+ */
398
+ async getSupportedChains() {
399
+ const key = `${this.#baseUrl}:chains`;
400
+ const entry = _chainCache.get(key);
401
+ const chains = entry && (this.#cacheTtlMs === 0 || entry.exp > Date.now()) ? entry.data : await this.#get("/api/getChainList", {}).then((data) => {
402
+ if (this.#cacheTtlMs > 0) {
403
+ _chainCache.set(key, { data, exp: Date.now() + this.#cacheTtlMs });
404
+ }
405
+ return data;
406
+ });
407
+ return chains.map((chain) => {
408
+ const native = getNativeTokenInfo(chain.chainId);
409
+ return toSwidgeSupportedChain(chain, native?.symbol ?? "");
410
+ });
411
+ }
412
+ /**
413
+ * Returns the tokens supported by MoonPay Trade for swidge operations.
414
+ *
415
+ * When `options.fromChain` is provided, fetches only that chain's tokens.
416
+ * Otherwise fetches all chains in a single request.
417
+ * Results are cached with TTL (default 10 min).
418
+ *
419
+ * @param options - Optional `fromChain` filter (other fields are not used by this provider).
420
+ * @throws {ProviderApiError} If the getTokenList request fails.
421
+ */
422
+ async getSupportedTokens(options) {
423
+ if (options?.fromChain !== void 0) {
424
+ const chainId = Number(options.fromChain);
425
+ const tokens = await this.#getTokenList(chainId);
426
+ return tokens.flatMap((token) => {
427
+ const mapped = toSwidgeSupportedToken(token, chainId);
428
+ return mapped ? [mapped] : [];
429
+ });
430
+ }
431
+ const allTokens = await this.#get("/api/getTokenList", {});
432
+ const exp = Date.now() + this.#cacheTtlMs;
433
+ const result = [];
434
+ for (const [chainIdStr, { tokens }] of Object.entries(allTokens)) {
435
+ const chainId = Number(chainIdStr);
436
+ if (this.#cacheTtlMs > 0) {
437
+ _tokenCache.set(`${this.#baseUrl}:${chainId}`, { data: tokens, exp });
438
+ }
439
+ for (const token of tokens) {
440
+ const mapped = toSwidgeSupportedToken(token, chainId);
441
+ if (mapped) result.push(mapped);
442
+ }
443
+ }
444
+ return result;
445
+ }
446
+ };
447
+
448
+ export { AccountRequiredError, FeeLimitExceededError, InvalidTokenIdError, MoonPayTradeSwidgeProtocol, ProviderApiError, UnknownStatusError, MoonPayTradeSwidgeProtocol as default };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Convert a base-unit bigint to a human-readable decimal string.
3
+ * e.g. 1500000n with 6 decimals → "1.5"
4
+ */
5
+ export declare function formatBaseUnitsToDecimal(baseUnits: bigint | string, decimals: number): string;
6
+ /**
7
+ * Convert a decimal fee string to base-unit bigint, accepting zero.
8
+ * e.g. "0.003" with 6 decimals → 3000n, "0" → 0n
9
+ */
10
+ export declare function parseDecimalFeeToBaseUnits(decimalAmount: string, decimals: number): bigint;
11
+ import type { SwidgeQuote, SwidgeResult, SwidgeStatusResult, SwidgeSupportedChain, SwidgeSupportedToken } from '@tetherto/wdk-wallet/protocols';
12
+ /** Parse our provider-specific token identifier into chainId + address components. */
13
+ export declare function parseTokenId(tokenId: string): {
14
+ chainId: number;
15
+ address: string;
16
+ };
17
+ /**
18
+ * Serialised Payment field from ActionResponse (bigints become strings after JSON).
19
+ * Used by GET /api/getQuote response.
20
+ */
21
+ interface SerializedPayment {
22
+ amount: string;
23
+ address: string;
24
+ chainId: number;
25
+ symbol?: string;
26
+ [key: string]: unknown;
27
+ }
28
+ /** Shape returned by GET /api/getQuote (ActionResponse minus tx/txId). */
29
+ interface GetQuoteResponse {
30
+ amountIn: SerializedPayment;
31
+ amountOut: SerializedPayment;
32
+ amountOutMin: SerializedPayment;
33
+ applicationFee: SerializedPayment;
34
+ protocolFee: SerializedPayment;
35
+ bridgeFee?: SerializedPayment;
36
+ estimatedTxTime: number;
37
+ estimatedPriceImpact: number;
38
+ expiryUnix?: number;
39
+ requiresTokenApproval?: boolean;
40
+ }
41
+ /**
42
+ * Decimal-format response from POST /api/postQuote.
43
+ * All amounts are human-readable decimal strings (e.g. "1.5"), not base-unit integers.
44
+ * Use parseDecimalFeeToBaseUnits with the appropriate token decimals to convert.
45
+ */
46
+ interface PostQuoteResponse {
47
+ quoteId: string;
48
+ amount: string;
49
+ amountTo: string;
50
+ applicationFee: string;
51
+ protocolFee: string;
52
+ bridgeFee: string;
53
+ expiryUnix: number;
54
+ }
55
+ /** Shape returned by POST /api/postSwap. */
56
+ interface PostSwapResponse {
57
+ swapId: string;
58
+ depositAddress: string;
59
+ depositAddressMemo?: string;
60
+ protocolFee: string;
61
+ applicationFee: string;
62
+ bridgeFee: string;
63
+ expiryUnix: number;
64
+ }
65
+ /** Shape returned by GET /api/getStatus. */
66
+ interface GetStatusResponse {
67
+ status: string;
68
+ srcTxHash?: string;
69
+ dstTxHash?: string;
70
+ srcChainId?: number;
71
+ dstChainId?: number;
72
+ [key: string]: unknown;
73
+ }
74
+ /** A single token entry from GET /api/getTokenList?chainId=<id>. */
75
+ export interface GetTokenListItem {
76
+ address: string;
77
+ decimals: number;
78
+ isNative: boolean;
79
+ symbol?: string;
80
+ name?: string;
81
+ }
82
+ /** Shape returned by GET /api/getChainList. */
83
+ export interface GetChainListItem {
84
+ chainId: number;
85
+ name: string;
86
+ vmId: string;
87
+ }
88
+ /** Shape returned by GET /api/getTokenList (no chainId filter): keyed by chainId string. */
89
+ export type GetAllTokensResponse = Record<string, {
90
+ tokens: GetTokenListItem[];
91
+ }>;
92
+ export type { GetQuoteResponse, GetStatusResponse, PostQuoteResponse, PostSwapResponse };
93
+ export declare function toSwidgeQuote(response: GetQuoteResponse): SwidgeQuote;
94
+ /**
95
+ * Map POST /api/postQuote + POST /api/postSwap responses to SwidgeResult.
96
+ *
97
+ * postQuote returns decimal strings for all amounts; decimalStringToBigInt converts
98
+ * them back to base-unit bigints using the token's decimals.
99
+ *
100
+ * Note: transactions[].hash is set to swapId (the deposit-flow tracking ID), not an
101
+ * on-chain transaction hash. Confirmed on-chain hashes are available via getSwidgeStatus
102
+ * after the wallet submits the transfer.
103
+ *
104
+ * @param fromDecimals - Decimals of the source token (used to parse fee strings).
105
+ * @param toDecimals - Decimals of the destination token (used to parse amountTo).
106
+ */
107
+ export declare function toSwidgeResult(quote: PostQuoteResponse, swap: PostSwapResponse, srcChainId: number, srcTokenAddress: string, fromDecimals: number, toDecimals: number): SwidgeResult;
108
+ /**
109
+ * Map a getChainList entry + native token symbol to SwidgeSupportedChain.
110
+ * `nativeSymbol` comes from getNativeTokenInfo(chainId).symbol in box-common (static data).
111
+ */
112
+ export declare function toSwidgeSupportedChain(chain: GetChainListItem, nativeSymbol: string): SwidgeSupportedChain;
113
+ /**
114
+ * Map a getTokenList entry to SwidgeSupportedToken.
115
+ * Returns null for tokens without a symbol (invalid/incomplete entries).
116
+ *
117
+ * Token identifier format: "<chainId>:<address>" — consistent with quoteSwidge/swidge.
118
+ */
119
+ export declare function toSwidgeSupportedToken(token: GetTokenListItem, chainId: number): SwidgeSupportedToken | null;
120
+ export declare function toSwidgeStatusResult(response: GetStatusResponse): SwidgeStatusResult;
121
+ //# sourceMappingURL=mappers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mappers.d.ts","sourceRoot":"","sources":["../src/mappers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * End-to-end example: discover tokens → quote → swidge → poll status.
3
+ *
4
+ * Prerequisites:
5
+ * 1. npm run build
6
+ * 2. Set env vars in .env (or export them):
7
+ * SWAPS_XYZ_KEY=<your-key>
8
+ * SWAPS_XYZ_BASE_URL=http://localhost:4000 # or omit for production
9
+ * TEST_WALLET_ADDRESS=0x<address> # required for swidge
10
+ *
11
+ * Run:
12
+ * node examples/e2e.mjs
13
+ */
14
+
15
+ import { existsSync, readFileSync } from 'node:fs';
16
+ import { dirname, join } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ import MoonPayTradeSwidgeProtocol from '../dist/index.js';
19
+
20
+ // Load .env from the package root if present
21
+ const envPath = join(dirname(fileURLToPath(import.meta.url)), '..', '.env');
22
+ if (existsSync(envPath)) {
23
+ for (const line of readFileSync(envPath, 'utf8').split('\n')) {
24
+ const trimmed = line.trim();
25
+ if (!trimmed || trimmed.startsWith('#')) continue;
26
+ const eq = trimmed.indexOf('=');
27
+ if (eq < 1) continue;
28
+ const key = trimmed.slice(0, eq).trim();
29
+ const value = trimmed.slice(eq + 1).trim();
30
+ if (process.env[key] === undefined) process.env[key] = value;
31
+ }
32
+ }
33
+
34
+ if (!process.env.SWAPS_XYZ_KEY) {
35
+ console.error('SWAPS_XYZ_KEY is required');
36
+ process.exit(1);
37
+ }
38
+
39
+ // ─── Discovery-only protocol (no account required for quotes) ─────────────────
40
+
41
+ const protocol = new MoonPayTradeSwidgeProtocol(undefined, {
42
+ apiKey: process.env.SWAPS_XYZ_KEY,
43
+ baseUrl: process.env.SWAPS_XYZ_BASE_URL,
44
+ });
45
+
46
+ console.log('\n── Supported chains (sample) ────────────────────────────────');
47
+ const chains = await protocol.getSupportedChains();
48
+ console.log(`${chains.length} chains`);
49
+ console.log(chains.slice(0, 3).map((c) => ` ${c.id} ${c.name} (${c.nativeToken})`).join('\n'));
50
+
51
+ console.log('\n── Supported tokens on Ethereum (sample) ────────────────────');
52
+ const ethTokens = await protocol.getSupportedTokens({ fromChain: 1 });
53
+ console.log(`${ethTokens.length} tokens`);
54
+ console.log(ethTokens.slice(0, 3).map((t) => ` ${t.token} ${t.symbol}`).join('\n'));
55
+
56
+ // Use USDT on Ethereum → USDC on Base as the example pair
57
+ const fromToken = '1:0xdAC17F958D2ee523a2206206994597C13D831ec7'; // USDT on Ethereum
58
+ const toToken = '8453:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base
59
+ const fromTokenAmount = 10_000_000n; // 10 USDT (6 decimals)
60
+
61
+ console.log(`\n── Quote: ${fromToken} → ${toToken} ─────────────────────────`);
62
+ const quote = await protocol.quoteSwidge({ fromToken, toToken, fromTokenAmount });
63
+ console.log(` from: ${quote.fromTokenAmount}`);
64
+ console.log(` to: ${quote.toTokenAmount}`);
65
+ console.log(` toMin: ${quote.toTokenAmountMin}`);
66
+ console.log(` fees: [${quote.fees.map((f) => `${f.type}=${f.amount}`).join(', ')}]`);
67
+ console.log(` duration: ${quote.estimatedDuration}s`);
68
+
69
+ // ─── E2E: swidge + poll status (requires TEST_WALLET_ADDRESS) ─────────────────
70
+
71
+ if (!process.env.TEST_WALLET_ADDRESS) {
72
+ console.log('\n── Skipping swidge (set TEST_WALLET_ADDRESS to run) ─────────');
73
+ process.exit(0);
74
+ }
75
+
76
+ // Minimal account — provides address only; sendTransaction not called in deposit flow
77
+ const account = {
78
+ getAddress: async () => process.env.TEST_WALLET_ADDRESS,
79
+ sendTransaction: async () => { throw new Error('not required for deposit-based swaps'); },
80
+ };
81
+
82
+ const e2eProtocol = new MoonPayTradeSwidgeProtocol(account, {
83
+ apiKey: process.env.SWAPS_XYZ_KEY,
84
+ baseUrl: process.env.SWAPS_XYZ_BASE_URL,
85
+ });
86
+
87
+ console.log(`\n── Swidge (sender: ${process.env.TEST_WALLET_ADDRESS.slice(0, 10)}…) ────────────────────`);
88
+ const result = await e2eProtocol.swidge({ fromToken, toToken, fromTokenAmount });
89
+ console.log(` swapId: ${result.id}`);
90
+ console.log(` fromAmount: ${result.fromTokenAmount}`);
91
+ console.log(` toAmount: ${result.toTokenAmount}`);
92
+
93
+ console.log('\n── Polling getSwidgeStatus ───────────────────────────────────');
94
+ let statusResult;
95
+ for (let i = 1; i <= 5; i++) {
96
+ try {
97
+ statusResult = await e2eProtocol.getSwidgeStatus(result.id);
98
+ break;
99
+ } catch {
100
+ if (i < 5) {
101
+ console.log(` not ready yet, retrying in 3s... (${i}/5)`);
102
+ await new Promise((r) => setTimeout(r, 3000));
103
+ }
104
+ }
105
+ }
106
+ console.log(` status: ${statusResult?.status ?? 'unavailable after 5 retries'}`);
package/package.json CHANGED
@@ -1,8 +1,55 @@
1
1
  {
2
2
  "name": "@moonpay/wdk-protocol-swidge-moonpay-trade",
3
- "version": "0.0.0",
4
- "description": "",
5
- "publishConfig": {
6
- "access": "public"
3
+ "version": "0.1.1",
4
+ "description": "WDK Swidge protocol implementation for MoonPay Trade",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.mjs"
11
+ }
12
+ },
13
+ "main": "./dist/index.mjs",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "CHANGELOG.md",
19
+ "SECURITY.md",
20
+ "examples"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/swaps-xyz/wdk-protocol-swidge-moonpay-trade"
25
+ },
26
+ "homepage": "https://github.com/swaps-xyz/wdk-protocol-swidge-moonpay-trade",
27
+ "keywords": [
28
+ "wdk",
29
+ "swidge",
30
+ "moonpay",
31
+ "swap",
32
+ "bridge",
33
+ "tether"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsup && tsc --project tsconfig.build.json",
37
+ "dev": "tsup --watch",
38
+ "check-types": "tsc --noEmit",
39
+ "test": "vitest run",
40
+ "test:coverage": "vitest run --coverage",
41
+ "test:watch": "vitest"
42
+ },
43
+ "dependencies": {
44
+ "@decent.xyz/box-common": "5.0.130",
45
+ "@tetherto/wdk-wallet": "1.0.0-beta.9",
46
+ "viem": "2.54.6"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "24.13.3",
50
+ "@vitest/coverage-v8": "4.1.10",
51
+ "tsup": "8.5.1",
52
+ "typescript": "5.9.3",
53
+ "vitest": "4.1.10"
7
54
  }
8
55
  }