@profullstack/coinpay 0.3.10 → 0.4.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/src/swap.js ADDED
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Swap Module for CoinPay SDK
3
+ *
4
+ * Provides cryptocurrency swap functionality using ChangeNow v2 API.
5
+ * Swaps are non-custodial and work in the USA (no KYC required).
6
+ */
7
+
8
+ const DEFAULT_BASE_URL = 'https://coinpayportal.com/api';
9
+
10
+ /**
11
+ * Supported coins for swaps
12
+ */
13
+ export const SwapCoins = [
14
+ 'BTC', 'BCH', 'ETH', 'POL', 'SOL',
15
+ 'BNB', 'DOGE', 'XRP', 'ADA',
16
+ 'USDT', 'USDT_ETH', 'USDT_POL', 'USDT_SOL',
17
+ 'USDC', 'USDC_ETH', 'USDC_POL', 'USDC_SOL',
18
+ ];
19
+
20
+ /**
21
+ * Swap status mapping
22
+ */
23
+ export const SwapStatus = {
24
+ PENDING: 'pending',
25
+ PROCESSING: 'processing',
26
+ SETTLING: 'settling',
27
+ SETTLED: 'settled',
28
+ FAILED: 'failed',
29
+ REFUNDED: 'refunded',
30
+ EXPIRED: 'expired',
31
+ };
32
+
33
+ /**
34
+ * SwapClient - Handles cryptocurrency swaps
35
+ */
36
+ export class SwapClient {
37
+ #baseUrl;
38
+ #timeout;
39
+ #walletId;
40
+
41
+ /**
42
+ * Create a SwapClient
43
+ * @param {Object} options - Client options
44
+ * @param {string} [options.walletId] - Wallet ID for tracking swaps
45
+ * @param {string} [options.baseUrl] - API base URL
46
+ * @param {number} [options.timeout] - Request timeout in ms
47
+ */
48
+ constructor(options = {}) {
49
+ this.#baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, '');
50
+ this.#timeout = options.timeout || 30000;
51
+ this.#walletId = options.walletId || null;
52
+ }
53
+
54
+ /**
55
+ * Set the wallet ID for tracking swaps
56
+ * @param {string} walletId - Wallet ID
57
+ */
58
+ setWalletId(walletId) {
59
+ this.#walletId = walletId;
60
+ }
61
+
62
+ /**
63
+ * Make an API request
64
+ * @private
65
+ */
66
+ async #request(endpoint, options = {}) {
67
+ const url = `${this.#baseUrl}${endpoint}`;
68
+ const controller = new AbortController();
69
+ const timeoutId = setTimeout(() => controller.abort(), this.#timeout);
70
+
71
+ try {
72
+ const response = await fetch(url, {
73
+ ...options,
74
+ signal: controller.signal,
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ ...options.headers,
78
+ },
79
+ });
80
+
81
+ const data = await response.json();
82
+
83
+ if (!response.ok) {
84
+ const error = new Error(data.error || `HTTP ${response.status}`);
85
+ error.status = response.status;
86
+ error.response = data;
87
+ throw error;
88
+ }
89
+
90
+ return data;
91
+ } catch (error) {
92
+ if (error.name === 'AbortError') {
93
+ throw new Error(`Request timeout after ${this.#timeout}ms`);
94
+ }
95
+ throw error;
96
+ } finally {
97
+ clearTimeout(timeoutId);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Get list of supported coins for swaps
103
+ * @param {Object} [options] - Query options
104
+ * @param {string} [options.search] - Search/filter coins by name or symbol
105
+ * @returns {Promise<Object>} List of supported coins
106
+ */
107
+ async getSwapCoins(options = {}) {
108
+ const result = await this.#request('/swap/coins');
109
+
110
+ if (options.search) {
111
+ const search = options.search.toLowerCase();
112
+ result.coins = result.coins.filter(coin =>
113
+ coin.symbol.toLowerCase().includes(search) ||
114
+ coin.name.toLowerCase().includes(search)
115
+ );
116
+ }
117
+
118
+ return result;
119
+ }
120
+
121
+ /**
122
+ * Get a swap quote
123
+ * @param {string} from - Source coin (e.g., 'BTC')
124
+ * @param {string} to - Destination coin (e.g., 'ETH')
125
+ * @param {string|number} amount - Amount to swap
126
+ * @returns {Promise<Object>} Swap quote with rates and estimates
127
+ */
128
+ async getSwapQuote(from, to, amount) {
129
+ if (!from || !to || !amount) {
130
+ throw new Error('from, to, and amount are required');
131
+ }
132
+
133
+ const fromUpper = from.toUpperCase();
134
+ const toUpper = to.toUpperCase();
135
+
136
+ if (!SwapCoins.includes(fromUpper)) {
137
+ throw new Error(`Unsupported source coin: ${fromUpper}. Supported: ${SwapCoins.join(', ')}`);
138
+ }
139
+
140
+ if (!SwapCoins.includes(toUpper)) {
141
+ throw new Error(`Unsupported destination coin: ${toUpper}. Supported: ${SwapCoins.join(', ')}`);
142
+ }
143
+
144
+ if (fromUpper === toUpper) {
145
+ throw new Error('Cannot swap a coin for itself');
146
+ }
147
+
148
+ const params = new URLSearchParams({
149
+ from: fromUpper,
150
+ to: toUpper,
151
+ amount: String(amount),
152
+ });
153
+
154
+ return this.#request(`/swap/quote?${params}`);
155
+ }
156
+
157
+ /**
158
+ * Create a swap transaction
159
+ * @param {Object} params - Swap parameters
160
+ * @param {string} params.from - Source coin
161
+ * @param {string} params.to - Destination coin
162
+ * @param {string|number} params.amount - Amount to swap
163
+ * @param {string} params.settleAddress - Address to receive swapped coins
164
+ * @param {string} [params.refundAddress] - Address for refunds (recommended)
165
+ * @param {string} [params.walletId] - Override wallet ID for this swap
166
+ * @returns {Promise<Object>} Swap transaction with deposit address
167
+ */
168
+ async createSwap(params) {
169
+ const { from, to, amount, settleAddress, refundAddress, walletId } = params;
170
+
171
+ if (!from || !to || !amount || !settleAddress) {
172
+ throw new Error('from, to, amount, and settleAddress are required');
173
+ }
174
+
175
+ const effectiveWalletId = walletId || this.#walletId;
176
+ if (!effectiveWalletId) {
177
+ throw new Error('walletId is required. Set it in constructor or pass it in params.');
178
+ }
179
+
180
+ const fromUpper = from.toUpperCase();
181
+ const toUpper = to.toUpperCase();
182
+
183
+ if (!SwapCoins.includes(fromUpper)) {
184
+ throw new Error(`Unsupported source coin: ${fromUpper}`);
185
+ }
186
+
187
+ if (!SwapCoins.includes(toUpper)) {
188
+ throw new Error(`Unsupported destination coin: ${toUpper}`);
189
+ }
190
+
191
+ if (fromUpper === toUpper) {
192
+ throw new Error('Cannot swap a coin for itself');
193
+ }
194
+
195
+ return this.#request('/swap/create', {
196
+ method: 'POST',
197
+ body: JSON.stringify({
198
+ from: fromUpper,
199
+ to: toUpper,
200
+ amount: String(amount),
201
+ settleAddress,
202
+ refundAddress,
203
+ walletId: effectiveWalletId,
204
+ }),
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Get the status of a swap
210
+ * @param {string} swapId - Swap transaction ID
211
+ * @returns {Promise<Object>} Swap status and details
212
+ */
213
+ async getSwapStatus(swapId) {
214
+ if (!swapId) {
215
+ throw new Error('swapId is required');
216
+ }
217
+
218
+ return this.#request(`/swap/${swapId}`);
219
+ }
220
+
221
+ /**
222
+ * Wait for a swap to complete
223
+ * @param {string} swapId - Swap transaction ID
224
+ * @param {Object} [options] - Polling options
225
+ * @param {number} [options.interval=10000] - Polling interval in ms
226
+ * @param {number} [options.timeout=3600000] - Maximum wait time in ms (default: 1 hour)
227
+ * @param {string[]} [options.targetStatuses] - Statuses to wait for
228
+ * @param {Function} [options.onStatusChange] - Callback when status changes
229
+ * @returns {Promise<Object>} Final swap status
230
+ */
231
+ async waitForSwap(swapId, options = {}) {
232
+ const {
233
+ interval = 10000,
234
+ timeout = 3600000,
235
+ targetStatuses = [SwapStatus.SETTLED, SwapStatus.FAILED, SwapStatus.REFUNDED, SwapStatus.EXPIRED],
236
+ onStatusChange,
237
+ } = options;
238
+
239
+ const startTime = Date.now();
240
+ let lastStatus = null;
241
+
242
+ while (Date.now() - startTime < timeout) {
243
+ const result = await this.getSwapStatus(swapId);
244
+ const currentStatus = result.swap?.status;
245
+
246
+ // Notify on status change
247
+ if (currentStatus !== lastStatus) {
248
+ if (onStatusChange && lastStatus !== null) {
249
+ onStatusChange(currentStatus, result.swap);
250
+ }
251
+ lastStatus = currentStatus;
252
+ }
253
+
254
+ // Check if we've reached a target status
255
+ if (targetStatuses.includes(currentStatus)) {
256
+ return result;
257
+ }
258
+
259
+ // Wait before next poll
260
+ await new Promise(resolve => setTimeout(resolve, interval));
261
+ }
262
+
263
+ throw new Error(`Swap status check timed out after ${timeout}ms`);
264
+ }
265
+
266
+ /**
267
+ * Get swap history for a wallet
268
+ * @param {string} [walletId] - Wallet ID (uses default if not provided)
269
+ * @param {Object} [options] - Query options
270
+ * @param {string} [options.status] - Filter by status
271
+ * @param {number} [options.limit=50] - Number of results
272
+ * @param {number} [options.offset=0] - Pagination offset
273
+ * @returns {Promise<Object>} Swap history
274
+ */
275
+ async getSwapHistory(walletId, options = {}) {
276
+ const effectiveWalletId = walletId || this.#walletId;
277
+ if (!effectiveWalletId) {
278
+ throw new Error('walletId is required');
279
+ }
280
+
281
+ const params = new URLSearchParams({
282
+ walletId: effectiveWalletId,
283
+ });
284
+
285
+ if (options.status) params.set('status', options.status);
286
+ if (options.limit) params.set('limit', String(options.limit));
287
+ if (options.offset) params.set('offset', String(options.offset));
288
+
289
+ return this.#request(`/swap/history?${params}`);
290
+ }
291
+ }
292
+
293
+ // Convenience functions for one-off operations
294
+
295
+ /**
296
+ * Get supported swap coins
297
+ * @param {Object} [options] - Options
298
+ * @param {string} [options.baseUrl] - API base URL
299
+ * @param {string} [options.search] - Search filter
300
+ * @returns {Promise<Object>} Supported coins
301
+ */
302
+ export async function getSwapCoins(options = {}) {
303
+ const client = new SwapClient({ baseUrl: options.baseUrl });
304
+ return client.getSwapCoins(options);
305
+ }
306
+
307
+ /**
308
+ * Get a swap quote
309
+ * @param {string} from - Source coin
310
+ * @param {string} to - Destination coin
311
+ * @param {string|number} amount - Amount
312
+ * @param {Object} [options] - Options
313
+ * @param {string} [options.baseUrl] - API base URL
314
+ * @returns {Promise<Object>} Quote
315
+ */
316
+ export async function getSwapQuote(from, to, amount, options = {}) {
317
+ const client = new SwapClient({ baseUrl: options.baseUrl });
318
+ return client.getSwapQuote(from, to, amount);
319
+ }
320
+
321
+ /**
322
+ * Create a swap
323
+ * @param {Object} params - Swap parameters
324
+ * @param {Object} [options] - Options
325
+ * @param {string} [options.baseUrl] - API base URL
326
+ * @returns {Promise<Object>} Swap details
327
+ */
328
+ export async function createSwap(params, options = {}) {
329
+ const client = new SwapClient({
330
+ baseUrl: options.baseUrl,
331
+ walletId: params.walletId,
332
+ });
333
+ return client.createSwap(params);
334
+ }
335
+
336
+ /**
337
+ * Get swap status
338
+ * @param {string} swapId - Swap ID
339
+ * @param {Object} [options] - Options
340
+ * @param {string} [options.baseUrl] - API base URL
341
+ * @returns {Promise<Object>} Swap status
342
+ */
343
+ export async function getSwapStatus(swapId, options = {}) {
344
+ const client = new SwapClient({ baseUrl: options.baseUrl });
345
+ return client.getSwapStatus(swapId);
346
+ }
347
+
348
+ /**
349
+ * Get swap history
350
+ * @param {string} walletId - Wallet ID
351
+ * @param {Object} [options] - Query options
352
+ * @param {string} [options.baseUrl] - API base URL
353
+ * @returns {Promise<Object>} Swap history
354
+ */
355
+ export async function getSwapHistory(walletId, options = {}) {
356
+ const client = new SwapClient({ baseUrl: options.baseUrl });
357
+ return client.getSwapHistory(walletId, options);
358
+ }
359
+
360
+ export default SwapClient;
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Wallet Module Type Definitions
3
+ */
4
+
5
+ /**
6
+ * Supported blockchain chains
7
+ */
8
+ export declare const WalletChain: {
9
+ readonly BTC: 'BTC';
10
+ readonly BCH: 'BCH';
11
+ readonly ETH: 'ETH';
12
+ readonly POL: 'POL';
13
+ readonly SOL: 'SOL';
14
+ readonly BNB: 'BNB';
15
+ readonly USDC_ETH: 'USDC_ETH';
16
+ readonly USDC_POL: 'USDC_POL';
17
+ readonly USDC_SOL: 'USDC_SOL';
18
+ readonly USDT_ETH: 'USDT_ETH';
19
+ readonly USDT_POL: 'USDT_POL';
20
+ readonly USDT_SOL: 'USDT_SOL';
21
+ };
22
+
23
+ export type WalletChainType = (typeof WalletChain)[keyof typeof WalletChain];
24
+
25
+ /**
26
+ * Default chains to derive on wallet creation
27
+ */
28
+ export declare const DEFAULT_CHAINS: string[];
29
+
30
+ /**
31
+ * Wallet creation options
32
+ */
33
+ export interface WalletCreateOptions {
34
+ /** Number of mnemonic words (12 or 24) */
35
+ words?: 12 | 24;
36
+ /** Chains to derive initial addresses for */
37
+ chains?: string[];
38
+ /** API base URL */
39
+ baseUrl?: string;
40
+ /** Request timeout in ms */
41
+ timeout?: number;
42
+ }
43
+
44
+ /**
45
+ * Wallet import options
46
+ */
47
+ export interface WalletImportOptions {
48
+ /** Chains to derive addresses for */
49
+ chains?: string[];
50
+ /** API base URL */
51
+ baseUrl?: string;
52
+ /** Request timeout in ms */
53
+ timeout?: number;
54
+ }
55
+
56
+ /**
57
+ * Wallet address info
58
+ */
59
+ export interface WalletAddress {
60
+ address_id: string;
61
+ chain: string;
62
+ address: string;
63
+ derivation_index: number;
64
+ is_active: boolean;
65
+ cached_balance?: string;
66
+ balance_updated_at?: string;
67
+ }
68
+
69
+ /**
70
+ * Address list result
71
+ */
72
+ export interface AddressListResult {
73
+ addresses: WalletAddress[];
74
+ total: number;
75
+ }
76
+
77
+ /**
78
+ * Balance info
79
+ */
80
+ export interface WalletBalance {
81
+ chain: string;
82
+ address: string;
83
+ balance: string;
84
+ balance_usd?: string;
85
+ }
86
+
87
+ /**
88
+ * Send transaction options
89
+ */
90
+ export interface SendOptions {
91
+ /** Target blockchain */
92
+ chain: string;
93
+ /** Recipient address */
94
+ to: string;
95
+ /** Amount to send */
96
+ amount: string;
97
+ /** Fee priority */
98
+ priority?: 'low' | 'medium' | 'high';
99
+ }
100
+
101
+ /**
102
+ * Transaction history options
103
+ */
104
+ export interface HistoryOptions {
105
+ /** Filter by chain */
106
+ chain?: string;
107
+ /** Filter by direction */
108
+ direction?: 'incoming' | 'outgoing';
109
+ /** Number of results */
110
+ limit?: number;
111
+ /** Pagination offset */
112
+ offset?: number;
113
+ }
114
+
115
+ /**
116
+ * Transaction record
117
+ */
118
+ export interface Transaction {
119
+ tx_id: string;
120
+ chain: string;
121
+ direction: 'incoming' | 'outgoing';
122
+ amount: string;
123
+ from_address: string;
124
+ to_address: string;
125
+ status: string;
126
+ tx_hash?: string;
127
+ created_at: string;
128
+ }
129
+
130
+ /**
131
+ * Fee estimate
132
+ */
133
+ export interface FeeEstimate {
134
+ priority: 'low' | 'medium' | 'high';
135
+ fee: string;
136
+ fee_usd?: string;
137
+ estimated_time?: string;
138
+ }
139
+
140
+ /**
141
+ * WalletClient class for managing wallets
142
+ */
143
+ export declare class WalletClient {
144
+ private constructor(options?: { baseUrl?: string; timeout?: number });
145
+
146
+ /**
147
+ * Create a new wallet with a fresh mnemonic
148
+ */
149
+ static create(options?: WalletCreateOptions): Promise<WalletClient>;
150
+
151
+ /**
152
+ * Import an existing wallet from a mnemonic
153
+ */
154
+ static fromSeed(mnemonic: string, options?: WalletImportOptions): Promise<WalletClient>;
155
+
156
+ /**
157
+ * Get the mnemonic phrase (for backup)
158
+ */
159
+ getMnemonic(): string | null;
160
+
161
+ /**
162
+ * Get the wallet ID
163
+ */
164
+ getWalletId(): string | null;
165
+
166
+ /**
167
+ * Authenticate with the server
168
+ */
169
+ authenticate(): Promise<void>;
170
+
171
+ /**
172
+ * Get wallet info
173
+ */
174
+ getInfo(): Promise<{
175
+ wallet_id: string;
176
+ status: string;
177
+ created_at: string;
178
+ last_active_at?: string;
179
+ address_count: number;
180
+ }>;
181
+
182
+ /**
183
+ * Get all addresses for this wallet
184
+ */
185
+ getAddresses(options?: { chain?: string; activeOnly?: boolean }): Promise<AddressListResult>;
186
+
187
+ /**
188
+ * Derive a new address for a chain
189
+ */
190
+ deriveAddress(chain: string, index?: number): Promise<WalletAddress>;
191
+
192
+ /**
193
+ * Derive addresses for any missing chains
194
+ */
195
+ deriveMissingChains(targetChains?: string[]): Promise<WalletAddress[]>;
196
+
197
+ /**
198
+ * Get all balances for this wallet
199
+ */
200
+ getBalances(options?: { chain?: string; refresh?: boolean }): Promise<{ balances: WalletBalance[] }>;
201
+
202
+ /**
203
+ * Get balance for a specific chain
204
+ */
205
+ getBalance(chain: string): Promise<{ balances: WalletBalance[] }>;
206
+
207
+ /**
208
+ * Send a transaction
209
+ */
210
+ send(options: SendOptions): Promise<{
211
+ tx_id: string;
212
+ tx_hash: string;
213
+ status: string;
214
+ }>;
215
+
216
+ /**
217
+ * Get transaction history
218
+ */
219
+ getHistory(options?: HistoryOptions): Promise<{
220
+ transactions: Transaction[];
221
+ total: number;
222
+ }>;
223
+
224
+ /**
225
+ * Estimate transaction fee
226
+ */
227
+ estimateFee(chain: string, to?: string, amount?: string): Promise<{
228
+ chain: string;
229
+ estimates: FeeEstimate[];
230
+ }>;
231
+
232
+ /**
233
+ * Encrypt and backup the seed phrase
234
+ */
235
+ backupSeed(password: string): Promise<string>;
236
+ }
237
+
238
+ /**
239
+ * Generate a new mnemonic phrase
240
+ * @param words - Number of words (12 or 24)
241
+ */
242
+ export declare function generateMnemonic(words?: 12 | 24): string;
243
+
244
+ /**
245
+ * Validate a mnemonic phrase
246
+ */
247
+ export declare function validateMnemonic(mnemonic: string): boolean;
248
+
249
+ /**
250
+ * Get derivation path for a chain
251
+ */
252
+ export declare function getDerivationPath(chain: string, index?: number): string;
253
+
254
+ /**
255
+ * Restore a seed from encrypted backup
256
+ */
257
+ export declare function restoreFromBackup(encryptedBackup: string, password: string): Promise<string>;
258
+
259
+ export default WalletClient;