paybridge 0.1.2 → 0.2.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.
Files changed (48) hide show
  1. package/README.md +87 -7
  2. package/dist/circuit-breaker-store.d.ts +27 -0
  3. package/dist/circuit-breaker-store.js +25 -0
  4. package/dist/circuit-breaker.d.ts +30 -0
  5. package/dist/circuit-breaker.js +86 -0
  6. package/dist/crypto/base.d.ts +15 -0
  7. package/dist/crypto/base.js +24 -0
  8. package/dist/crypto/index.d.ts +35 -0
  9. package/dist/crypto/index.js +95 -0
  10. package/dist/crypto/mock.d.ts +15 -0
  11. package/dist/crypto/mock.js +112 -0
  12. package/dist/crypto/moonpay.d.ts +33 -0
  13. package/dist/crypto/moonpay.js +251 -0
  14. package/dist/crypto/router.d.ts +36 -0
  15. package/dist/crypto/router.js +287 -0
  16. package/dist/crypto/types.d.ts +89 -0
  17. package/dist/crypto/types.js +5 -0
  18. package/dist/crypto/yellowcard.d.ts +56 -0
  19. package/dist/crypto/yellowcard.js +310 -0
  20. package/dist/index.d.ts +9 -1
  21. package/dist/index.js +59 -3
  22. package/dist/providers/base.d.ts +5 -0
  23. package/dist/providers/flutterwave.d.ts +36 -0
  24. package/dist/providers/flutterwave.js +338 -0
  25. package/dist/providers/ozow.d.ts +20 -2
  26. package/dist/providers/ozow.js +161 -114
  27. package/dist/providers/payfast.d.ts +40 -0
  28. package/dist/providers/payfast.js +355 -0
  29. package/dist/providers/paystack.d.ts +37 -0
  30. package/dist/providers/paystack.js +335 -0
  31. package/dist/providers/peach.d.ts +50 -0
  32. package/dist/providers/peach.js +305 -0
  33. package/dist/providers/softycomp.d.ts +106 -0
  34. package/dist/providers/softycomp.js +234 -10
  35. package/dist/providers/stripe.d.ts +38 -0
  36. package/dist/providers/stripe.js +370 -0
  37. package/dist/providers/yoco.d.ts +12 -0
  38. package/dist/providers/yoco.js +159 -61
  39. package/dist/router.d.ts +33 -0
  40. package/dist/router.js +247 -0
  41. package/dist/routing-types.d.ts +39 -0
  42. package/dist/routing-types.js +14 -0
  43. package/dist/stores/redis.d.ts +30 -0
  44. package/dist/stores/redis.js +42 -0
  45. package/dist/strategies.d.ts +18 -0
  46. package/dist/strategies.js +44 -0
  47. package/dist/types.d.ts +4 -2
  48. package/package.json +7 -4
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ /**
3
+ * MoonPay crypto on/off-ramp provider
4
+ * @see https://dev.moonpay.com
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.MoonPayProvider = void 0;
11
+ const crypto_1 = __importDefault(require("crypto"));
12
+ const base_1 = require("./base");
13
+ class MoonPayProvider extends base_1.CryptoRampProvider {
14
+ constructor(config) {
15
+ super();
16
+ this.name = 'moonpay';
17
+ this.apiKey = config.apiKey;
18
+ this.secretKey = config.secretKey;
19
+ this.sandbox = config.sandbox;
20
+ this.webhookSecret = config.webhookSecret;
21
+ // Sandbox vs live is determined by API key prefix (pk_test_* vs pk_live_*)
22
+ this.baseUrl = 'https://api.moonpay.com';
23
+ this.widgetUrl = this.sandbox
24
+ ? 'https://buy-sandbox.moonpay.com'
25
+ : 'https://buy.moonpay.com';
26
+ }
27
+ async getQuote(direction, fiatAmount, fiatCurrency, cryptoAsset, _network) {
28
+ const params = new URLSearchParams({
29
+ apiKey: this.apiKey,
30
+ baseCurrencyCode: fiatCurrency.toLowerCase(),
31
+ quoteCurrencyCode: cryptoAsset.toLowerCase(),
32
+ baseCurrencyAmount: fiatAmount.toString(),
33
+ });
34
+ const url = `${this.baseUrl}/v3/currencies/${cryptoAsset.toLowerCase()}/quote?${params}`;
35
+ const response = await fetch(url);
36
+ if (!response.ok) {
37
+ throw new Error(`MoonPay quote failed: ${response.status}`);
38
+ }
39
+ const data = await response.json();
40
+ const feeTotal = data.feeAmount || 0;
41
+ const rate = data.quoteCurrencyPrice || 0;
42
+ const cryptoAmount = data.quoteCurrencyAmount || 0;
43
+ return {
44
+ fiatAmount,
45
+ cryptoAmount,
46
+ rate,
47
+ feeFixed: 0,
48
+ feePercent: (feeTotal / fiatAmount) * 100,
49
+ feeTotal,
50
+ expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
51
+ };
52
+ }
53
+ async createOnRamp(params) {
54
+ (0, base_1.validateWalletAddress)(params.destinationWallet, params.network);
55
+ const quote = await this.getQuote('on', params.fiatAmount, params.fiatCurrency, params.asset, params.network);
56
+ const widgetParams = new URLSearchParams({
57
+ apiKey: this.apiKey,
58
+ currencyCode: params.asset.toLowerCase(),
59
+ baseCurrencyCode: params.fiatCurrency.toLowerCase(),
60
+ baseCurrencyAmount: params.fiatAmount.toString(),
61
+ walletAddress: params.destinationWallet,
62
+ email: params.customer.email,
63
+ externalTransactionId: params.reference,
64
+ redirectURL: params.urls.success,
65
+ });
66
+ const signature = this.signWidgetUrl(`?${widgetParams.toString()}`);
67
+ widgetParams.append('signature', signature);
68
+ const checkoutUrl = `${this.widgetUrl}?${widgetParams}`;
69
+ return {
70
+ id: `moonpay_on_${params.reference}`,
71
+ direction: 'on',
72
+ status: 'pending',
73
+ quote,
74
+ checkoutUrl,
75
+ createdAt: new Date().toISOString(),
76
+ expiresAt: quote.expiresAt,
77
+ };
78
+ }
79
+ async createOffRamp(params) {
80
+ if (params.sourceWallet) {
81
+ (0, base_1.validateWalletAddress)(params.sourceWallet, params.network);
82
+ }
83
+ // TODO: verify endpoint - use /v3/currencies/{code}/sell_quote?baseCurrencyAmount=...
84
+ // For now, use getQuote with 'off' direction
85
+ const quote = await this.getQuote('off', 0, // Will be calculated from crypto amount
86
+ params.fiatCurrency, params.asset, params.network);
87
+ const sellWidgetUrl = this.sandbox
88
+ ? 'https://sell-sandbox.moonpay.com'
89
+ : 'https://sell.moonpay.com';
90
+ const widgetParams = new URLSearchParams({
91
+ apiKey: this.apiKey,
92
+ baseCurrencyCode: params.asset.toLowerCase(),
93
+ quoteCurrencyCode: params.fiatCurrency.toLowerCase(),
94
+ baseCurrencyAmount: params.cryptoAmount.toString(),
95
+ externalTransactionId: params.reference,
96
+ refundWalletAddress: params.sourceWallet || '',
97
+ redirectURL: params.urls?.success || '',
98
+ });
99
+ const signature = this.signWidgetUrl(`?${widgetParams.toString()}`);
100
+ widgetParams.append('signature', signature);
101
+ const checkoutUrl = `${sellWidgetUrl}?${widgetParams}`;
102
+ return {
103
+ id: `moonpay_off_${params.reference}`,
104
+ direction: 'off',
105
+ status: 'pending',
106
+ quote,
107
+ checkoutUrl,
108
+ createdAt: new Date().toISOString(),
109
+ expiresAt: quote.expiresAt,
110
+ };
111
+ }
112
+ async getRamp(id) {
113
+ const response = await fetch(`${this.baseUrl}/v3/transactions/${id}`, {
114
+ headers: {
115
+ Authorization: `Bearer ${this.secretKey}`,
116
+ },
117
+ });
118
+ if (!response.ok) {
119
+ throw new Error(`MoonPay getRamp failed: ${response.status}`);
120
+ }
121
+ const data = await response.json();
122
+ const direction = data.type === 'buy' ? 'on' : 'off';
123
+ const status = this.mapMoonPayStatus(data.status);
124
+ const quote = {
125
+ fiatAmount: data.baseCurrencyAmount || 0,
126
+ cryptoAmount: data.quoteCurrencyAmount || 0,
127
+ rate: data.quoteCurrencyPrice || 0,
128
+ feeFixed: 0,
129
+ feePercent: 0,
130
+ feeTotal: data.feeAmount || 0,
131
+ expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
132
+ };
133
+ const sanitizedRaw = { ...data };
134
+ if (sanitizedRaw.bankAccount)
135
+ delete sanitizedRaw.bankAccount;
136
+ if (sanitizedRaw.bank_account)
137
+ delete sanitizedRaw.bank_account;
138
+ return {
139
+ id: data.id,
140
+ direction,
141
+ status,
142
+ quote,
143
+ txHash: data.cryptoTransactionId,
144
+ createdAt: data.createdAt,
145
+ raw: sanitizedRaw,
146
+ };
147
+ }
148
+ parseWebhook(body, _headers) {
149
+ const event = typeof body === 'string' ? JSON.parse(body) : body;
150
+ return {
151
+ type: event.type,
152
+ data: event.data,
153
+ raw: event,
154
+ };
155
+ }
156
+ verifyWebhook(body, headers) {
157
+ if (!this.webhookSecret)
158
+ return false;
159
+ // Try V2 signature first (recommended)
160
+ const signatureV2 = headers?.['moonpay-signature-v2'];
161
+ if (signatureV2) {
162
+ return this.verifyWebhookV2(body, signatureV2);
163
+ }
164
+ // Fall back to legacy signature
165
+ const signature = headers?.['moonpay-signature'] || headers?.signature;
166
+ if (!signature)
167
+ return false;
168
+ const expectedSignature = crypto_1.default
169
+ .createHmac('sha256', this.webhookSecret)
170
+ .update(body)
171
+ .digest('hex');
172
+ const sigBuf = Buffer.from(signature);
173
+ const expBuf = Buffer.from(expectedSignature);
174
+ if (sigBuf.length !== expBuf.length)
175
+ return false;
176
+ return crypto_1.default.timingSafeEqual(sigBuf, expBuf);
177
+ }
178
+ verifyWebhookV2(body, signatureHeader) {
179
+ if (!this.webhookSecret)
180
+ return false;
181
+ // Parse V2 format: t=<timestamp>,s=<signature>
182
+ const parts = signatureHeader.split(',');
183
+ let timestamp;
184
+ let signature;
185
+ for (const part of parts) {
186
+ const [key, value] = part.split('=');
187
+ if (key === 't')
188
+ timestamp = value;
189
+ if (key === 's')
190
+ signature = value;
191
+ }
192
+ if (!timestamp || !signature)
193
+ return false;
194
+ // Replay protection: timestamp must be within 5 minutes
195
+ const now = Math.floor(Date.now() / 1000);
196
+ const ts = parseInt(timestamp, 10);
197
+ if (isNaN(ts) || Math.abs(now - ts) > 300)
198
+ return false;
199
+ // Verify signature: HMAC-SHA256 of ${timestamp}.${body}
200
+ const bodyStr = Buffer.isBuffer(body) ? body.toString('utf8') : body;
201
+ const payload = `${timestamp}.${bodyStr}`;
202
+ const expectedSignature = crypto_1.default
203
+ .createHmac('sha256', this.webhookSecret)
204
+ .update(payload)
205
+ .digest('hex');
206
+ const sigBuf = Buffer.from(signature);
207
+ const expBuf = Buffer.from(expectedSignature);
208
+ if (sigBuf.length !== expBuf.length)
209
+ return false;
210
+ return crypto_1.default.timingSafeEqual(sigBuf, expBuf);
211
+ }
212
+ getCapabilities() {
213
+ return {
214
+ supportedAssets: ['BTC', 'ETH', 'USDT', 'USDC', 'BNB', 'SOL'],
215
+ supportedNetworks: ['BTC', 'ETH', 'TRON', 'POLYGON', 'BSC', 'SOLANA'],
216
+ supportedFiat: ['USD', 'EUR', 'GBP', 'ZAR'],
217
+ country: 'GLOBAL',
218
+ kycRequired: true,
219
+ onRampLimits: {
220
+ min: 20,
221
+ max: 50000,
222
+ },
223
+ offRampLimits: {
224
+ min: 30,
225
+ max: 50000,
226
+ },
227
+ fees: {
228
+ onRampPercent: 4.5,
229
+ offRampPercent: 1.0,
230
+ },
231
+ };
232
+ }
233
+ signWidgetUrl(queryString) {
234
+ return crypto_1.default
235
+ .createHmac('sha256', this.secretKey)
236
+ .update(queryString)
237
+ .digest('base64');
238
+ }
239
+ mapMoonPayStatus(moonpayStatus) {
240
+ const statusMap = {
241
+ waitingPayment: 'pending',
242
+ pending: 'pending',
243
+ waitingAuthorization: 'pending',
244
+ completed: 'completed',
245
+ failed: 'failed',
246
+ expired: 'expired',
247
+ };
248
+ return statusMap[moonpayStatus] || 'pending';
249
+ }
250
+ }
251
+ exports.MoonPayProvider = MoonPayProvider;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * CryptoRampRouter — Multi-provider crypto routing
3
+ */
4
+ import { CryptoRamp } from './index';
5
+ import { OnRampParams, OffRampParams, RampQuote, RampResult } from './types';
6
+ export interface CryptoRampRouterConfig {
7
+ providers: Array<{
8
+ provider: CryptoRamp;
9
+ priority?: number;
10
+ }>;
11
+ strategy?: 'cheapest' | 'priority' | 'round-robin';
12
+ fallback?: {
13
+ enabled: boolean;
14
+ maxAttempts?: number;
15
+ retryDelayMs?: number;
16
+ };
17
+ allowExperimental?: boolean;
18
+ circuitBreakerStore?: import('../circuit-breaker-store').CircuitBreakerStore;
19
+ }
20
+ export declare class CryptoRampRouter {
21
+ private providers;
22
+ private strategy;
23
+ private fallback;
24
+ private allowExperimental;
25
+ private circuitBreakers;
26
+ private rrIndex;
27
+ private config;
28
+ constructor(config: CryptoRampRouterConfig);
29
+ getQuote(direction: 'on' | 'off', fiatAmount: number, fiatCurrency: string, cryptoAsset: string, network: string): Promise<RampQuote>;
30
+ createOnRamp(params: OnRampParams): Promise<RampResult>;
31
+ createOffRamp(params: OffRampParams): Promise<RampResult>;
32
+ getRamp(id: string, provider?: string): Promise<RampResult>;
33
+ private filterProviders;
34
+ private orderProviders;
35
+ private sleep;
36
+ }
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ /**
3
+ * CryptoRampRouter — Multi-provider crypto routing
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CryptoRampRouter = void 0;
7
+ const routing_types_1 = require("../routing-types");
8
+ const circuit_breaker_1 = require("../circuit-breaker");
9
+ function sanitizeErrorMessage(msg) {
10
+ if (!msg)
11
+ return 'unknown error';
12
+ return msg
13
+ .replace(/\b[A-Za-z0-9_-]{32,}\b/g, '[REDACTED]')
14
+ .replace(/(api[_-]?key|secret|token|password)["':=\s]+\S+/gi, '$1=[REDACTED]')
15
+ .slice(0, 500);
16
+ }
17
+ class CryptoRampRouter {
18
+ constructor(config) {
19
+ this.rrIndex = 0;
20
+ this.config = config;
21
+ this.providers = config.providers.map(p => ({
22
+ instance: p.provider,
23
+ priority: p.priority,
24
+ }));
25
+ this.strategy = config.strategy ?? 'cheapest';
26
+ this.fallback = {
27
+ enabled: config.fallback?.enabled ?? true,
28
+ maxAttempts: config.fallback?.maxAttempts ?? 3,
29
+ retryDelayMs: config.fallback?.retryDelayMs ?? 250,
30
+ };
31
+ this.allowExperimental = config.allowExperimental ?? false;
32
+ this.circuitBreakers = new Map();
33
+ for (const p of this.providers) {
34
+ const name = p.instance.getProviderName();
35
+ this.circuitBreakers.set(name, new circuit_breaker_1.CircuitBreaker(name, {
36
+ store: config.circuitBreakerStore,
37
+ }));
38
+ }
39
+ }
40
+ async getQuote(direction, fiatAmount, fiatCurrency, cryptoAsset, network) {
41
+ const filtered = this.filterProviders({ fiatCurrency, asset: cryptoAsset, network });
42
+ if (filtered.length === 0) {
43
+ throw new Error(`No providers support ${cryptoAsset} on ${network} with ${fiatCurrency}`);
44
+ }
45
+ const ordered = this.orderProviders(filtered, direction);
46
+ const attempts = [];
47
+ let lastError = null;
48
+ for (const providerMeta of ordered) {
49
+ const providerName = providerMeta.instance.getProviderName();
50
+ const breaker = this.circuitBreakers.get(providerName);
51
+ if (breaker && (await breaker.isOpen())) {
52
+ attempts.push({
53
+ provider: providerName,
54
+ status: 'skipped',
55
+ errorMessage: 'Circuit breaker open',
56
+ latencyMs: 0,
57
+ });
58
+ continue;
59
+ }
60
+ const startTime = Date.now();
61
+ try {
62
+ const result = await providerMeta.instance.getQuote(direction, fiatAmount, fiatCurrency, cryptoAsset, network);
63
+ const latencyMs = Date.now() - startTime;
64
+ if (breaker)
65
+ await breaker.recordSuccess();
66
+ attempts.push({
67
+ provider: providerName,
68
+ status: 'success',
69
+ latencyMs,
70
+ });
71
+ return result;
72
+ }
73
+ catch (error) {
74
+ const latencyMs = Date.now() - startTime;
75
+ lastError = error;
76
+ if (breaker)
77
+ await breaker.recordFailure();
78
+ attempts.push({
79
+ provider: providerName,
80
+ status: 'failed',
81
+ errorCode: error.code,
82
+ errorMessage: sanitizeErrorMessage(error.message),
83
+ latencyMs,
84
+ });
85
+ if (!this.fallback.enabled || attempts.length >= this.fallback.maxAttempts) {
86
+ break;
87
+ }
88
+ await this.sleep(this.fallback.retryDelayMs);
89
+ }
90
+ }
91
+ throw new routing_types_1.RoutingError(`All providers failed: ${lastError?.message || 'Unknown error'}`, attempts);
92
+ }
93
+ async createOnRamp(params) {
94
+ if (!Number.isFinite(params.fiatAmount) || params.fiatAmount <= 0) {
95
+ throw new Error('Invalid amount: must be a positive finite number');
96
+ }
97
+ const filtered = this.filterProviders(params);
98
+ if (filtered.length === 0) {
99
+ throw new Error(`No providers support ${params.asset} on ${params.network} with ${params.fiatCurrency}`);
100
+ }
101
+ const ordered = this.orderProviders(filtered, 'on');
102
+ const attempts = [];
103
+ let lastError = null;
104
+ for (const providerMeta of ordered) {
105
+ const providerName = providerMeta.instance.getProviderName();
106
+ const breaker = this.circuitBreakers.get(providerName);
107
+ if (breaker && (await breaker.isOpen())) {
108
+ attempts.push({
109
+ provider: providerName,
110
+ status: 'skipped',
111
+ errorMessage: 'Circuit breaker open',
112
+ latencyMs: 0,
113
+ });
114
+ continue;
115
+ }
116
+ const startTime = Date.now();
117
+ try {
118
+ const result = await providerMeta.instance.createOnRamp(params);
119
+ const latencyMs = Date.now() - startTime;
120
+ if (breaker)
121
+ await breaker.recordSuccess();
122
+ attempts.push({
123
+ provider: providerName,
124
+ status: 'success',
125
+ latencyMs,
126
+ });
127
+ const routingMeta = {
128
+ attempts,
129
+ chosenProvider: providerName,
130
+ strategy: this.strategy,
131
+ };
132
+ return {
133
+ ...result,
134
+ routingMeta,
135
+ };
136
+ }
137
+ catch (error) {
138
+ const latencyMs = Date.now() - startTime;
139
+ lastError = error;
140
+ if (breaker)
141
+ await breaker.recordFailure();
142
+ attempts.push({
143
+ provider: providerName,
144
+ status: 'failed',
145
+ errorCode: error.code,
146
+ errorMessage: sanitizeErrorMessage(error.message),
147
+ latencyMs,
148
+ });
149
+ if (!this.fallback.enabled || attempts.length >= this.fallback.maxAttempts) {
150
+ break;
151
+ }
152
+ await this.sleep(this.fallback.retryDelayMs);
153
+ }
154
+ }
155
+ throw new routing_types_1.RoutingError(`All providers failed: ${lastError?.message || 'Unknown error'}`, attempts);
156
+ }
157
+ async createOffRamp(params) {
158
+ if (!Number.isFinite(params.cryptoAmount) || params.cryptoAmount <= 0) {
159
+ throw new Error('Invalid amount: must be a positive finite number');
160
+ }
161
+ const filtered = this.filterProviders(params);
162
+ if (filtered.length === 0) {
163
+ throw new Error(`No providers support ${params.asset} on ${params.network} with ${params.fiatCurrency}`);
164
+ }
165
+ const ordered = this.orderProviders(filtered, 'off');
166
+ const attempts = [];
167
+ let lastError = null;
168
+ for (const providerMeta of ordered) {
169
+ const providerName = providerMeta.instance.getProviderName();
170
+ const breaker = this.circuitBreakers.get(providerName);
171
+ if (breaker && (await breaker.isOpen())) {
172
+ attempts.push({
173
+ provider: providerName,
174
+ status: 'skipped',
175
+ errorMessage: 'Circuit breaker open',
176
+ latencyMs: 0,
177
+ });
178
+ continue;
179
+ }
180
+ const startTime = Date.now();
181
+ try {
182
+ const result = await providerMeta.instance.createOffRamp(params);
183
+ const latencyMs = Date.now() - startTime;
184
+ if (breaker)
185
+ await breaker.recordSuccess();
186
+ attempts.push({
187
+ provider: providerName,
188
+ status: 'success',
189
+ latencyMs,
190
+ });
191
+ const routingMeta = {
192
+ attempts,
193
+ chosenProvider: providerName,
194
+ strategy: this.strategy,
195
+ };
196
+ return {
197
+ ...result,
198
+ routingMeta,
199
+ };
200
+ }
201
+ catch (error) {
202
+ const latencyMs = Date.now() - startTime;
203
+ lastError = error;
204
+ if (breaker)
205
+ await breaker.recordFailure();
206
+ attempts.push({
207
+ provider: providerName,
208
+ status: 'failed',
209
+ errorCode: error.code,
210
+ errorMessage: sanitizeErrorMessage(error.message),
211
+ latencyMs,
212
+ });
213
+ if (!this.fallback.enabled || attempts.length >= this.fallback.maxAttempts) {
214
+ break;
215
+ }
216
+ await this.sleep(this.fallback.retryDelayMs);
217
+ }
218
+ }
219
+ throw new routing_types_1.RoutingError(`All providers failed: ${lastError?.message || 'Unknown error'}`, attempts);
220
+ }
221
+ async getRamp(id, provider) {
222
+ if (provider) {
223
+ const providerMeta = this.providers.find(p => p.instance.getProviderName() === provider);
224
+ if (!providerMeta) {
225
+ throw new Error(`Provider ${provider} not found in router`);
226
+ }
227
+ return providerMeta.instance.getRamp(id);
228
+ }
229
+ let lastError = null;
230
+ for (const providerMeta of this.providers) {
231
+ try {
232
+ return await providerMeta.instance.getRamp(id);
233
+ }
234
+ catch (error) {
235
+ lastError = error;
236
+ }
237
+ }
238
+ throw new Error(`Ramp ${id} not found in any provider: ${lastError?.message || 'Unknown error'}`);
239
+ }
240
+ filterProviders(params) {
241
+ return this.providers.filter(p => {
242
+ const caps = p.instance.getCapabilities();
243
+ if (!this.allowExperimental && caps.experimental === true) {
244
+ return false;
245
+ }
246
+ if (!caps.supportedAssets.includes(params.asset)) {
247
+ return false;
248
+ }
249
+ if (!caps.supportedNetworks.includes(params.network)) {
250
+ return false;
251
+ }
252
+ if (!caps.supportedFiat.includes(params.fiatCurrency)) {
253
+ return false;
254
+ }
255
+ return true;
256
+ });
257
+ }
258
+ orderProviders(providers, direction) {
259
+ switch (this.strategy) {
260
+ case 'cheapest':
261
+ return [...providers].sort((a, b) => {
262
+ const capsA = a.instance.getCapabilities();
263
+ const capsB = b.instance.getCapabilities();
264
+ const feeA = direction === 'on' ? capsA.fees.onRampPercent : capsA.fees.offRampPercent;
265
+ const feeB = direction === 'on' ? capsB.fees.onRampPercent : capsB.fees.offRampPercent;
266
+ return feeA - feeB;
267
+ });
268
+ case 'priority':
269
+ return [...providers].sort((a, b) => {
270
+ const priorityA = a.priority ?? 0;
271
+ const priorityB = b.priority ?? 0;
272
+ return priorityB - priorityA;
273
+ });
274
+ case 'round-robin': {
275
+ const idx = this.rrIndex;
276
+ this.rrIndex = (this.rrIndex + 1) % providers.length;
277
+ return [...providers.slice(idx), ...providers.slice(0, idx)];
278
+ }
279
+ default:
280
+ return providers;
281
+ }
282
+ }
283
+ sleep(ms) {
284
+ return new Promise(resolve => setTimeout(resolve, ms));
285
+ }
286
+ }
287
+ exports.CryptoRampRouter = CryptoRampRouter;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Crypto on/off-ramp types
3
+ */
4
+ import { Customer, Currency } from '../types';
5
+ import { RoutingMeta } from '../routing-types';
6
+ export type CryptoAsset = 'BTC' | 'ETH' | 'USDT' | 'USDC' | 'BNB' | 'SOL' | string;
7
+ export type CryptoNetwork = 'BTC' | 'ETH' | 'TRON' | 'POLYGON' | 'BSC' | 'SOLANA' | string;
8
+ export type RampDirection = 'on' | 'off';
9
+ export type RampStatus = 'pending' | 'completed' | 'failed' | 'expired';
10
+ export interface OnRampParams {
11
+ fiatAmount: number;
12
+ fiatCurrency: Currency;
13
+ asset: CryptoAsset;
14
+ network: CryptoNetwork;
15
+ destinationWallet: string;
16
+ customer: Customer;
17
+ urls: {
18
+ success: string;
19
+ cancel: string;
20
+ webhook: string;
21
+ };
22
+ reference: string;
23
+ metadata?: Record<string, any>;
24
+ }
25
+ export interface BankAccount {
26
+ accountNumber: string;
27
+ branchCode: string;
28
+ accountHolder: string;
29
+ bankName: string;
30
+ }
31
+ export interface OffRampParams {
32
+ cryptoAmount: number;
33
+ asset: CryptoAsset;
34
+ network: CryptoNetwork;
35
+ sourceWallet?: string;
36
+ fiatCurrency: Currency;
37
+ bankAccount: BankAccount;
38
+ customer: Customer;
39
+ urls?: {
40
+ success: string;
41
+ cancel: string;
42
+ webhook: string;
43
+ };
44
+ reference: string;
45
+ metadata?: Record<string, any>;
46
+ }
47
+ export interface RampQuote {
48
+ fiatAmount: number;
49
+ cryptoAmount: number;
50
+ rate: number;
51
+ feeFixed: number;
52
+ feePercent: number;
53
+ feeTotal: number;
54
+ expiresAt: string;
55
+ }
56
+ export interface RampResult {
57
+ id: string;
58
+ direction: RampDirection;
59
+ status: RampStatus;
60
+ quote: RampQuote;
61
+ checkoutUrl?: string;
62
+ depositAddress?: string;
63
+ depositTag?: string;
64
+ txHash?: string;
65
+ createdAt: string;
66
+ expiresAt?: string;
67
+ raw?: any;
68
+ routingMeta?: RoutingMeta;
69
+ }
70
+ export interface CryptoRampCapabilities {
71
+ supportedAssets: CryptoAsset[];
72
+ supportedNetworks: CryptoNetwork[];
73
+ supportedFiat: Currency[];
74
+ country: string;
75
+ kycRequired: boolean;
76
+ onRampLimits?: {
77
+ min: number;
78
+ max: number;
79
+ };
80
+ offRampLimits?: {
81
+ min: number;
82
+ max: number;
83
+ };
84
+ fees: {
85
+ onRampPercent: number;
86
+ offRampPercent: number;
87
+ };
88
+ experimental?: boolean;
89
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Crypto on/off-ramp types
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });