@moonpay/wdk-protocol-swidge-moonpay-trade 0.0.0 → 0.1.0
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 +21 -0
- package/README.md +107 -0
- package/SECURITY.md +9 -0
- package/dist/MoonPayTradeSwidgeProtocol.d.ts +124 -0
- package/dist/MoonPayTradeSwidgeProtocol.d.ts.map +1 -0
- package/dist/errors.d.ts +35 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +448 -0
- package/dist/mappers.d.ts +121 -0
- package/dist/mappers.d.ts.map +1 -0
- package/examples/e2e.mjs +106 -0
- package/package.json +51 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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.0] - 2026-07-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `MoonPayTradeSwidgeProtocol` — implements `ISwidgeProtocol` from `@tetherto/wdk-wallet`
|
|
14
|
+
- `quoteSwidge` — non-binding quote via `GET /api/getQuote`
|
|
15
|
+
- `swidge` — deposit-based swap via `POST /api/postQuote` + `POST /api/postSwap`
|
|
16
|
+
- `getSwidgeStatus` — status polling via `GET /api/getStatus`
|
|
17
|
+
- `getSupportedChains` — chain discovery via `GET /api/getChainList`
|
|
18
|
+
- `getSupportedTokens` — token discovery via `GET /api/getTokenList` with optional `fromChain` filter
|
|
19
|
+
- Fee guards (`maxProtocolFeeBps`, `maxNetworkFeeBps`) at constructor and per-call level
|
|
20
|
+
- Module-level TTL cache (default 10 min) shared across instances pointing to the same base URL
|
|
21
|
+
- Typed errors: `AccountRequiredError`, `FeeLimitExceededError`, `ProviderApiError`, `UnknownStatusError`, `InvalidTokenIdError`
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @moonpay/wdk-protocol-swidge-moonpay-trade
|
|
2
|
+
|
|
3
|
+
[](https://docs.wdk.tether.io)
|
|
4
|
+
[](https://www.npmjs.com/package/@moonpay/wdk-protocol-swidge-moonpay-trade)
|
|
5
|
+
[](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://box-v2.api.decent.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://box-v2.api.decent.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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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://box-v2.api.decent.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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"}
|
package/examples/e2e.mjs
ADDED
|
@@ -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.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
}
|