@profullstack/coinpay 0.3.9 → 0.4.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/src/swap.d.ts ADDED
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Swap Module Type Definitions
3
+ */
4
+
5
+ /**
6
+ * Supported coins for swaps
7
+ */
8
+ export declare const SwapCoins: readonly string[];
9
+
10
+ /**
11
+ * Swap status values
12
+ */
13
+ export declare const SwapStatus: {
14
+ readonly PENDING: 'pending';
15
+ readonly PROCESSING: 'processing';
16
+ readonly SETTLING: 'settling';
17
+ readonly SETTLED: 'settled';
18
+ readonly FAILED: 'failed';
19
+ readonly REFUNDED: 'refunded';
20
+ readonly EXPIRED: 'expired';
21
+ };
22
+
23
+ export type SwapStatusType = (typeof SwapStatus)[keyof typeof SwapStatus];
24
+
25
+ /**
26
+ * Swap client options
27
+ */
28
+ export interface SwapClientOptions {
29
+ /** Wallet ID for tracking swaps */
30
+ walletId?: string;
31
+ /** API base URL */
32
+ baseUrl?: string;
33
+ /** Request timeout in ms */
34
+ timeout?: number;
35
+ }
36
+
37
+ /**
38
+ * Coin information
39
+ */
40
+ export interface CoinInfo {
41
+ symbol: string;
42
+ name: string;
43
+ network: string;
44
+ ticker: string;
45
+ }
46
+
47
+ /**
48
+ * Coins list result
49
+ */
50
+ export interface CoinsResult {
51
+ success: boolean;
52
+ provider: string;
53
+ coins: CoinInfo[];
54
+ count: number;
55
+ }
56
+
57
+ /**
58
+ * Swap quote
59
+ */
60
+ export interface SwapQuote {
61
+ from: string;
62
+ to: string;
63
+ depositAmount: string;
64
+ settleAmount: string;
65
+ rate: string;
66
+ minAmount: number;
67
+ provider: string;
68
+ }
69
+
70
+ /**
71
+ * Quote result
72
+ */
73
+ export interface QuoteResult {
74
+ success: boolean;
75
+ quote: SwapQuote;
76
+ }
77
+
78
+ /**
79
+ * Create swap parameters
80
+ */
81
+ export interface CreateSwapParams {
82
+ /** Source coin (e.g., 'BTC') */
83
+ from: string;
84
+ /** Destination coin (e.g., 'ETH') */
85
+ to: string;
86
+ /** Amount to swap */
87
+ amount: string | number;
88
+ /** Address to receive swapped coins */
89
+ settleAddress: string;
90
+ /** Address for refunds (recommended) */
91
+ refundAddress?: string;
92
+ /** Override wallet ID */
93
+ walletId?: string;
94
+ }
95
+
96
+ /**
97
+ * Swap details
98
+ */
99
+ export interface Swap {
100
+ id: string;
101
+ from: string;
102
+ to: string;
103
+ depositAddress: string;
104
+ depositAmount: string;
105
+ depositCoin?: string;
106
+ settleAddress: string;
107
+ settleAmount?: string;
108
+ settleCoin?: string;
109
+ status: SwapStatusType;
110
+ createdAt: string;
111
+ provider: string;
112
+ }
113
+
114
+ /**
115
+ * Swap result
116
+ */
117
+ export interface SwapResult {
118
+ success: boolean;
119
+ swap: Swap;
120
+ }
121
+
122
+ /**
123
+ * Wait for swap options
124
+ */
125
+ export interface WaitForSwapOptions {
126
+ /** Polling interval in ms (default: 10000) */
127
+ interval?: number;
128
+ /** Maximum wait time in ms (default: 3600000) */
129
+ timeout?: number;
130
+ /** Statuses to wait for */
131
+ targetStatuses?: SwapStatusType[];
132
+ /** Callback when status changes */
133
+ onStatusChange?: (status: SwapStatusType, swap: Swap) => void;
134
+ }
135
+
136
+ /**
137
+ * Swap history options
138
+ */
139
+ export interface SwapHistoryOptions {
140
+ /** Filter by status */
141
+ status?: SwapStatusType;
142
+ /** Number of results (default: 50) */
143
+ limit?: number;
144
+ /** Pagination offset */
145
+ offset?: number;
146
+ }
147
+
148
+ /**
149
+ * Swap history pagination
150
+ */
151
+ export interface SwapHistoryPagination {
152
+ total: number;
153
+ limit: number;
154
+ offset: number;
155
+ hasMore: boolean;
156
+ }
157
+
158
+ /**
159
+ * Swap history result
160
+ */
161
+ export interface SwapHistoryResult {
162
+ success: boolean;
163
+ swaps: Swap[];
164
+ pagination: SwapHistoryPagination;
165
+ }
166
+
167
+ /**
168
+ * SwapClient class for handling cryptocurrency swaps
169
+ */
170
+ export declare class SwapClient {
171
+ /**
172
+ * Create a SwapClient
173
+ */
174
+ constructor(options?: SwapClientOptions);
175
+
176
+ /**
177
+ * Set the wallet ID for tracking swaps
178
+ */
179
+ setWalletId(walletId: string): void;
180
+
181
+ /**
182
+ * Get list of supported coins for swaps
183
+ */
184
+ getSwapCoins(options?: { search?: string }): Promise<CoinsResult>;
185
+
186
+ /**
187
+ * Get a swap quote
188
+ */
189
+ getSwapQuote(from: string, to: string, amount: string | number): Promise<QuoteResult>;
190
+
191
+ /**
192
+ * Create a swap transaction
193
+ */
194
+ createSwap(params: CreateSwapParams): Promise<SwapResult>;
195
+
196
+ /**
197
+ * Get the status of a swap
198
+ */
199
+ getSwapStatus(swapId: string): Promise<SwapResult>;
200
+
201
+ /**
202
+ * Wait for a swap to complete
203
+ */
204
+ waitForSwap(swapId: string, options?: WaitForSwapOptions): Promise<SwapResult>;
205
+
206
+ /**
207
+ * Get swap history for a wallet
208
+ */
209
+ getSwapHistory(walletId?: string, options?: SwapHistoryOptions): Promise<SwapHistoryResult>;
210
+ }
211
+
212
+ /**
213
+ * Get supported swap coins (convenience function)
214
+ */
215
+ export declare function getSwapCoins(options?: {
216
+ baseUrl?: string;
217
+ search?: string;
218
+ }): Promise<CoinsResult>;
219
+
220
+ /**
221
+ * Get a swap quote (convenience function)
222
+ */
223
+ export declare function getSwapQuote(
224
+ from: string,
225
+ to: string,
226
+ amount: string | number,
227
+ options?: { baseUrl?: string }
228
+ ): Promise<QuoteResult>;
229
+
230
+ /**
231
+ * Create a swap (convenience function)
232
+ */
233
+ export declare function createSwap(
234
+ params: CreateSwapParams,
235
+ options?: { baseUrl?: string }
236
+ ): Promise<SwapResult>;
237
+
238
+ /**
239
+ * Get swap status (convenience function)
240
+ */
241
+ export declare function getSwapStatus(
242
+ swapId: string,
243
+ options?: { baseUrl?: string }
244
+ ): Promise<SwapResult>;
245
+
246
+ /**
247
+ * Get swap history (convenience function)
248
+ */
249
+ export declare function getSwapHistory(
250
+ walletId: string,
251
+ options?: SwapHistoryOptions & { baseUrl?: string }
252
+ ): Promise<SwapHistoryResult>;
253
+
254
+ export default SwapClient;
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;