@sardis/ramp 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/dist/index.js ADDED
@@ -0,0 +1,643 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/errors.ts
9
+ var PolicyViolation = class extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "PolicyViolation";
13
+ }
14
+ };
15
+ var RampError = class extends Error {
16
+ constructor(message, code, details) {
17
+ super(message);
18
+ this.name = "RampError";
19
+ this.code = code;
20
+ this.details = details;
21
+ }
22
+ };
23
+
24
+ // src/ramp.ts
25
+ var DEFAULT_BRIDGE_API_URL = "https://api.bridge.xyz/v0";
26
+ var DEFAULT_BRIDGE_SANDBOX_URL = "https://api.sandbox.bridge.xyz/v0";
27
+ var DEFAULT_SARDIS_API_URL = "https://api.sardis.sh/v2";
28
+ var SardisFiatRamp = class {
29
+ constructor(config) {
30
+ if (!config.sardisKey || config.sardisKey.trim() === "") {
31
+ throw new RampError("Sardis API key is required", "INVALID_CONFIG");
32
+ }
33
+ if (!config.bridgeKey || config.bridgeKey.trim() === "") {
34
+ throw new RampError("Bridge API key is required", "INVALID_CONFIG");
35
+ }
36
+ this.sardisKey = config.sardisKey;
37
+ this.bridgeKey = config.bridgeKey;
38
+ this.bridgeUrl = config.bridgeUrl ?? (config.environment === "production" ? DEFAULT_BRIDGE_API_URL : DEFAULT_BRIDGE_SANDBOX_URL);
39
+ this.sardisUrl = config.sardisUrl ?? DEFAULT_SARDIS_API_URL;
40
+ }
41
+ async sardisRequest(method, path, body) {
42
+ const response = await fetch(`${this.sardisUrl}${path}`, {
43
+ method,
44
+ headers: {
45
+ Authorization: `Bearer ${this.sardisKey}`,
46
+ "Content-Type": "application/json"
47
+ },
48
+ body: body ? JSON.stringify(body) : void 0
49
+ });
50
+ if (!response.ok) {
51
+ const error = await response.json().catch(() => ({}));
52
+ throw new RampError(
53
+ error.message || "Sardis API error",
54
+ error.code || "SARDIS_ERROR",
55
+ error
56
+ );
57
+ }
58
+ return response.json();
59
+ }
60
+ async bridgeRequest(method, path, body) {
61
+ const response = await fetch(`${this.bridgeUrl}${path}`, {
62
+ method,
63
+ headers: {
64
+ "Api-Key": this.bridgeKey,
65
+ "Content-Type": "application/json"
66
+ },
67
+ body: body ? JSON.stringify(body) : void 0
68
+ });
69
+ if (!response.ok) {
70
+ const error = await response.json().catch(() => ({}));
71
+ throw new RampError(
72
+ error.message || "Bridge API error",
73
+ error.code || "BRIDGE_ERROR",
74
+ error
75
+ );
76
+ }
77
+ return response.json();
78
+ }
79
+ async getWallet(walletId) {
80
+ return this.sardisRequest("GET", `/wallets/${walletId}`);
81
+ }
82
+ /**
83
+ * Fund a Sardis wallet from fiat sources.
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const result = await ramp.fundWallet({
88
+ * walletId: 'wallet_123',
89
+ * amountUsd: 100,
90
+ * method: 'bank'
91
+ * })
92
+ * console.log(result.achInstructions?.routingNumber)
93
+ * ```
94
+ */
95
+ async fundWallet(params) {
96
+ const wallet = await this.getWallet(params.walletId);
97
+ if (params.method === "crypto") {
98
+ return {
99
+ type: "crypto",
100
+ depositAddress: wallet.address,
101
+ chain: wallet.chain,
102
+ token: "USDC"
103
+ };
104
+ }
105
+ const transfer = await this.bridgeRequest("POST", "/transfers", {
106
+ amount: params.amountUsd.toString(),
107
+ on_behalf_of: params.walletId,
108
+ source: {
109
+ payment_rail: params.method === "bank" ? "ach" : "card",
110
+ currency: "usd"
111
+ },
112
+ destination: {
113
+ payment_rail: "ethereum",
114
+ currency: "usdc",
115
+ to_address: wallet.address,
116
+ chain: this.chainToBridge(wallet.chain)
117
+ }
118
+ });
119
+ let achInstructions;
120
+ let wireInstructions;
121
+ const instr = transfer.source_deposit_instructions;
122
+ if (instr?.payment_rail === "ach") {
123
+ achInstructions = {
124
+ accountNumber: instr.account_number,
125
+ routingNumber: instr.routing_number,
126
+ bankName: instr.bank_name,
127
+ accountHolder: instr.account_holder,
128
+ reference: instr.reference
129
+ };
130
+ } else if (instr?.payment_rail === "wire") {
131
+ wireInstructions = {
132
+ accountNumber: instr.account_number,
133
+ routingNumber: instr.routing_number,
134
+ swiftCode: instr.swift_code || "",
135
+ bankName: instr.bank_name,
136
+ bankAddress: instr.bank_address || "",
137
+ accountHolder: instr.account_holder,
138
+ reference: instr.reference
139
+ };
140
+ }
141
+ return {
142
+ type: "fiat",
143
+ paymentLink: transfer.hosted_url,
144
+ achInstructions,
145
+ wireInstructions,
146
+ estimatedArrival: transfer.estimated_completion_at ? new Date(transfer.estimated_completion_at) : void 0,
147
+ feePercent: transfer.fee ? parseFloat(transfer.fee.percent) : void 0,
148
+ transferId: transfer.id
149
+ };
150
+ }
151
+ /**
152
+ * Withdraw from Sardis wallet to bank account.
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const result = await ramp.withdrawToBank({
157
+ * walletId: 'wallet_123',
158
+ * amountUsd: 50,
159
+ * bankAccount: {
160
+ * accountHolderName: 'John Doe',
161
+ * accountNumber: '1234567890',
162
+ * routingNumber: '021000021'
163
+ * }
164
+ * })
165
+ * ```
166
+ */
167
+ async withdrawToBank(params) {
168
+ const wallet = await this.getWallet(params.walletId);
169
+ const policyCheck = await this.sardisRequest("POST", `/wallets/${params.walletId}/check-policy`, {
170
+ amount: params.amountUsd.toString(),
171
+ action: "withdrawal"
172
+ });
173
+ if (!policyCheck.allowed) {
174
+ throw new PolicyViolation(policyCheck.reason || "Policy violation");
175
+ }
176
+ const bridgeDeposit = await this.bridgeRequest(
177
+ "POST",
178
+ "/deposit-addresses",
179
+ {
180
+ chain: this.chainToBridge(wallet.chain),
181
+ currency: "usdc"
182
+ }
183
+ );
184
+ const tx = await this.sardisRequest(
185
+ "POST",
186
+ "/transactions",
187
+ {
188
+ wallet_id: params.walletId,
189
+ to: bridgeDeposit.address,
190
+ amount: params.amountUsd.toString(),
191
+ token: "USDC",
192
+ memo: "Withdrawal to bank"
193
+ }
194
+ );
195
+ const payout = await this.bridgeRequest("POST", "/payouts", {
196
+ amount: params.amountUsd.toString(),
197
+ currency: "usd",
198
+ source: {
199
+ tx_hash: tx.tx_hash,
200
+ chain: this.chainToBridge(wallet.chain)
201
+ },
202
+ destination: {
203
+ payment_rail: "ach",
204
+ account_holder_name: params.bankAccount.accountHolderName,
205
+ account_number: params.bankAccount.accountNumber,
206
+ routing_number: params.bankAccount.routingNumber,
207
+ account_type: params.bankAccount.accountType || "checking"
208
+ }
209
+ });
210
+ return {
211
+ txHash: tx.tx_hash,
212
+ payoutId: payout.id,
213
+ estimatedArrival: new Date(payout.estimated_completion_at),
214
+ fee: payout.fee ? parseFloat(payout.fee.amount) : 0,
215
+ status: "pending"
216
+ };
217
+ }
218
+ /**
219
+ * Pay merchant in USD from crypto wallet.
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * const result = await ramp.payMerchantFiat({
224
+ * walletId: 'wallet_123',
225
+ * amountUsd: 99.99,
226
+ * merchant: {
227
+ * name: 'ACME Corp',
228
+ * bankAccount: { ... }
229
+ * }
230
+ * })
231
+ * ```
232
+ */
233
+ async payMerchantFiat(params) {
234
+ const wallet = await this.getWallet(params.walletId);
235
+ const policyCheck = await this.sardisRequest("POST", `/wallets/${params.walletId}/check-policy`, {
236
+ amount: params.amountUsd.toString(),
237
+ merchant: params.merchant.name,
238
+ category: params.merchant.category
239
+ });
240
+ if (!policyCheck.allowed) {
241
+ throw new PolicyViolation(policyCheck.reason || "Policy violation");
242
+ }
243
+ if (policyCheck.requires_approval) {
244
+ const approval = await this.sardisRequest(
245
+ "POST",
246
+ `/wallets/${params.walletId}/request-approval`,
247
+ {
248
+ amount: params.amountUsd.toString(),
249
+ reason: `Payment to ${params.merchant.name}`
250
+ }
251
+ );
252
+ return {
253
+ status: "pending_approval",
254
+ approvalRequest: approval
255
+ };
256
+ }
257
+ const payment = await this.bridgeRequest("POST", "/payments", {
258
+ amount: params.amountUsd.toString(),
259
+ source: {
260
+ wallet_address: wallet.address,
261
+ chain: this.chainToBridge(wallet.chain),
262
+ currency: "usdc"
263
+ },
264
+ destination: {
265
+ payment_rail: "ach",
266
+ currency: "usd",
267
+ account_holder_name: params.merchant.bankAccount.accountHolderName,
268
+ account_number: params.merchant.bankAccount.accountNumber,
269
+ routing_number: params.merchant.bankAccount.routingNumber
270
+ }
271
+ });
272
+ return {
273
+ status: "completed",
274
+ paymentId: payment.id,
275
+ merchantReceived: params.amountUsd,
276
+ fee: payment.fee ? parseFloat(payment.fee.amount) : 0,
277
+ txHash: payment.source_tx_hash
278
+ };
279
+ }
280
+ /**
281
+ * Get status of a funding transfer.
282
+ */
283
+ async getFundingStatus(transferId) {
284
+ return this.bridgeRequest("GET", `/transfers/${transferId}`);
285
+ }
286
+ /**
287
+ * Get status of a bank withdrawal.
288
+ */
289
+ async getWithdrawalStatus(payoutId) {
290
+ return this.bridgeRequest("GET", `/payouts/${payoutId}`);
291
+ }
292
+ chainToBridge(chain) {
293
+ const mapping = {
294
+ base: "base",
295
+ polygon: "polygon",
296
+ ethereum: "ethereum",
297
+ arbitrum: "arbitrum",
298
+ optimism: "optimism"
299
+ };
300
+ return mapping[chain.toLowerCase()] || chain;
301
+ }
302
+ };
303
+
304
+ // src/onramper.ts
305
+ var ONRAMPER_API_URL = "https://api.onramper.com";
306
+ var ONRAMPER_WIDGET_URL = "https://buy.onramper.com";
307
+ var DEFAULT_SARDIS_API_URL2 = "https://api.sardis.sh/v2";
308
+ var SardisOnramper = class {
309
+ constructor(config) {
310
+ if (!config.apiKey || config.apiKey.trim() === "") {
311
+ throw new RampError("Onramper API key is required", "INVALID_CONFIG");
312
+ }
313
+ if (!config.sardisKey || config.sardisKey.trim() === "") {
314
+ throw new RampError("Sardis API key is required", "INVALID_CONFIG");
315
+ }
316
+ this.apiKey = config.apiKey;
317
+ this.sardisKey = config.sardisKey;
318
+ this.sardisUrl = config.sardisUrl ?? DEFAULT_SARDIS_API_URL2;
319
+ this.mode = config.mode ?? "sandbox";
320
+ this.defaultFiat = config.defaultFiat ?? "USD";
321
+ this.defaultCrypto = config.defaultCrypto ?? "USDC";
322
+ }
323
+ // ===========================================================================
324
+ // API Requests
325
+ // ===========================================================================
326
+ async onramperRequest(method, path, body) {
327
+ const response = await fetch(`${ONRAMPER_API_URL}${path}`, {
328
+ method,
329
+ headers: {
330
+ Authorization: `Bearer ${this.apiKey}`,
331
+ "Content-Type": "application/json"
332
+ },
333
+ body: body ? JSON.stringify(body) : void 0
334
+ });
335
+ if (!response.ok) {
336
+ const error = await response.json().catch(() => ({}));
337
+ throw new RampError(
338
+ error.message || "Onramper API error",
339
+ error.code || "ONRAMPER_ERROR",
340
+ error
341
+ );
342
+ }
343
+ return response.json();
344
+ }
345
+ async sardisRequest(method, path, body) {
346
+ const response = await fetch(`${this.sardisUrl}${path}`, {
347
+ method,
348
+ headers: {
349
+ Authorization: `Bearer ${this.sardisKey}`,
350
+ "Content-Type": "application/json"
351
+ },
352
+ body: body ? JSON.stringify(body) : void 0
353
+ });
354
+ if (!response.ok) {
355
+ const error = await response.json().catch(() => ({}));
356
+ throw new RampError(
357
+ error.message || "Sardis API error",
358
+ error.code || "SARDIS_ERROR",
359
+ error
360
+ );
361
+ }
362
+ return response.json();
363
+ }
364
+ // ===========================================================================
365
+ // Supported Assets
366
+ // ===========================================================================
367
+ /**
368
+ * Get supported fiat currencies
369
+ */
370
+ async getSupportedFiats() {
371
+ const response = await this.onramperRequest("GET", "/supported/fiats");
372
+ return response.fiats.map((f) => ({
373
+ code: f.code,
374
+ name: f.name,
375
+ symbol: f.symbol,
376
+ minAmount: f.limits.min,
377
+ maxAmount: f.limits.max
378
+ }));
379
+ }
380
+ /**
381
+ * Get supported crypto assets
382
+ */
383
+ async getSupportedCryptos(network) {
384
+ const path = network ? `/supported/crypto?network=${encodeURIComponent(network)}` : "/supported/crypto";
385
+ const response = await this.onramperRequest("GET", path);
386
+ return response.crypto.map((c) => ({
387
+ code: c.code,
388
+ name: c.name,
389
+ network: c.network,
390
+ symbol: c.symbol,
391
+ decimals: c.decimals,
392
+ minAmount: c.limits.min,
393
+ maxAmount: c.limits.max
394
+ }));
395
+ }
396
+ /**
397
+ * Get supported payment methods for a country
398
+ */
399
+ async getSupportedPaymentMethods(country) {
400
+ const response = await this.onramperRequest("GET", `/supported/payment-methods?country=${encodeURIComponent(country)}`);
401
+ return response.paymentMethods;
402
+ }
403
+ // ===========================================================================
404
+ // Quotes
405
+ // ===========================================================================
406
+ /**
407
+ * Get quotes from all available providers
408
+ */
409
+ async getQuotes(params) {
410
+ const queryParams = new URLSearchParams({
411
+ source: params.sourceCurrency,
412
+ destination: params.destinationCurrency,
413
+ amount: params.amount.toString(),
414
+ type: params.type || "fiat"
415
+ });
416
+ if (params.paymentMethod) {
417
+ queryParams.set("paymentMethod", params.paymentMethod);
418
+ }
419
+ if (params.country) {
420
+ queryParams.set("country", params.country);
421
+ }
422
+ if (params.network) {
423
+ queryParams.set("network", params.network);
424
+ }
425
+ const response = await this.onramperRequest("GET", `/quotes?${queryParams.toString()}`);
426
+ return response.quotes;
427
+ }
428
+ /**
429
+ * Get the best quote (lowest total cost)
430
+ */
431
+ async getBestQuote(params) {
432
+ const quotes = await this.getQuotes(params);
433
+ if (quotes.length === 0) {
434
+ return null;
435
+ }
436
+ return quotes.sort((a, b) => b.destinationAmount - a.destinationAmount)[0] ?? null;
437
+ }
438
+ // ===========================================================================
439
+ // Widget Integration
440
+ // ===========================================================================
441
+ /**
442
+ * Generate widget URL for embedding or redirect
443
+ *
444
+ * @example
445
+ * ```typescript
446
+ * const url = onramper.getWidgetUrl({
447
+ * walletAddress: '0x...',
448
+ * fiatCurrency: 'USD',
449
+ * cryptoCurrency: 'USDC',
450
+ * fiatAmount: 100,
451
+ * network: 'base',
452
+ * })
453
+ * window.open(url, '_blank')
454
+ * ```
455
+ */
456
+ getWidgetUrl(options) {
457
+ const params = new URLSearchParams();
458
+ params.set("apiKey", this.apiKey);
459
+ params.set("walletAddress", options.walletAddress);
460
+ params.set("defaultFiat", options.fiatCurrency || this.defaultFiat);
461
+ params.set("defaultCrypto", options.cryptoCurrency || this.defaultCrypto);
462
+ if (options.fiatAmount) {
463
+ params.set("fiatAmount", options.fiatAmount.toString());
464
+ }
465
+ if (options.cryptoAmount) {
466
+ params.set("cryptoAmount", options.cryptoAmount.toString());
467
+ }
468
+ if (options.network) {
469
+ params.set("network", this.mapNetwork(options.network));
470
+ }
471
+ if (options.paymentMethod) {
472
+ params.set("paymentMethod", options.paymentMethod);
473
+ }
474
+ if (options.color) {
475
+ params.set("color", options.color.replace("#", ""));
476
+ }
477
+ if (options.darkMode) {
478
+ params.set("darkMode", "true");
479
+ }
480
+ if (options.onlyProviders?.length) {
481
+ params.set("onlyProviders", options.onlyProviders.join(","));
482
+ }
483
+ if (options.excludeProviders?.length) {
484
+ params.set("excludeProviders", options.excludeProviders.join(","));
485
+ }
486
+ if (options.country) {
487
+ params.set("country", options.country);
488
+ }
489
+ if (options.language) {
490
+ params.set("language", options.language);
491
+ }
492
+ if (options.skipIntro) {
493
+ params.set("skipIntro", "true");
494
+ }
495
+ if (options.redirectUrl) {
496
+ params.set("redirectUrl", options.redirectUrl);
497
+ }
498
+ if (options.partnerContext) {
499
+ params.set("partnerContext", options.partnerContext);
500
+ }
501
+ if (this.mode === "sandbox") {
502
+ params.set("isTestMode", "true");
503
+ }
504
+ return `${ONRAMPER_WIDGET_URL}?${params.toString()}`;
505
+ }
506
+ /**
507
+ * Generate widget HTML for iframe embedding
508
+ */
509
+ getWidgetIframe(options) {
510
+ const url = this.getWidgetUrl(options);
511
+ const width = options.width || "400px";
512
+ const height = options.height || "600px";
513
+ return `<iframe
514
+ src="${url}"
515
+ width="${width}"
516
+ height="${height}"
517
+ frameborder="0"
518
+ allow="accelerometer; autoplay; camera; gyroscope; payment"
519
+ style="border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1);"
520
+ ></iframe>`;
521
+ }
522
+ // ===========================================================================
523
+ // Transaction Management
524
+ // ===========================================================================
525
+ /**
526
+ * Get transaction status by Onramper transaction ID
527
+ */
528
+ async getTransaction(transactionId) {
529
+ return this.onramperRequest(
530
+ "GET",
531
+ `/transactions/${transactionId}`
532
+ );
533
+ }
534
+ /**
535
+ * List transactions for a wallet address
536
+ */
537
+ async listTransactions(walletAddress, options) {
538
+ const params = new URLSearchParams({
539
+ walletAddress
540
+ });
541
+ if (options?.limit) {
542
+ params.set("limit", options.limit.toString());
543
+ }
544
+ if (options?.offset) {
545
+ params.set("offset", options.offset.toString());
546
+ }
547
+ if (options?.status) {
548
+ params.set("status", options.status);
549
+ }
550
+ const response = await this.onramperRequest("GET", `/transactions?${params.toString()}`);
551
+ return response.transactions;
552
+ }
553
+ // ===========================================================================
554
+ // Sardis Integration
555
+ // ===========================================================================
556
+ /**
557
+ * Fund a Sardis wallet via Onramper widget
558
+ *
559
+ * Returns a widget URL that, when used, will deposit crypto
560
+ * directly to the Sardis wallet.
561
+ */
562
+ async fundWallet(params) {
563
+ const wallet = await this.sardisRequest("GET", `/wallets/${params.walletId}`);
564
+ const widgetUrl = this.getWidgetUrl({
565
+ walletAddress: wallet.address,
566
+ network: wallet.chain,
567
+ fiatAmount: params.fiatAmount,
568
+ fiatCurrency: params.fiatCurrency,
569
+ cryptoCurrency: params.cryptoCurrency || "USDC",
570
+ paymentMethod: params.paymentMethod,
571
+ redirectUrl: params.redirectUrl,
572
+ partnerContext: `sardis:${wallet.id}`,
573
+ darkMode: true,
574
+ color: "FF6B35"
575
+ // Sardis orange
576
+ });
577
+ return {
578
+ widgetUrl,
579
+ walletAddress: wallet.address,
580
+ network: wallet.chain
581
+ };
582
+ }
583
+ /**
584
+ * Get best quote for funding a Sardis wallet
585
+ */
586
+ async getWalletFundingQuote(params) {
587
+ const wallet = await this.sardisRequest("GET", `/wallets/${params.walletId}`);
588
+ return this.getBestQuote({
589
+ sourceCurrency: params.fiatCurrency || this.defaultFiat,
590
+ destinationCurrency: "USDC",
591
+ amount: params.fiatAmount,
592
+ type: "fiat",
593
+ paymentMethod: params.paymentMethod,
594
+ network: wallet.chain
595
+ });
596
+ }
597
+ // ===========================================================================
598
+ // Webhooks
599
+ // ===========================================================================
600
+ /**
601
+ * Verify webhook signature from Onramper
602
+ */
603
+ verifyWebhookSignature(payload, signature, secret) {
604
+ const crypto = __require("crypto");
605
+ const expectedSignature = crypto.createHmac("sha256", secret).update(payload).digest("hex");
606
+ return crypto.timingSafeEqual(
607
+ Buffer.from(signature),
608
+ Buffer.from(expectedSignature)
609
+ );
610
+ }
611
+ /**
612
+ * Parse webhook payload
613
+ */
614
+ parseWebhookPayload(payload) {
615
+ return JSON.parse(payload);
616
+ }
617
+ // ===========================================================================
618
+ // Helpers
619
+ // ===========================================================================
620
+ mapNetwork(chain) {
621
+ const mapping = {
622
+ base: "base",
623
+ polygon: "polygon",
624
+ ethereum: "ethereum",
625
+ arbitrum: "arbitrum",
626
+ optimism: "optimism",
627
+ avalanche: "avalanche",
628
+ bsc: "bsc",
629
+ solana: "solana"
630
+ };
631
+ return mapping[chain.toLowerCase()] || chain;
632
+ }
633
+ };
634
+ function createOnramper(config) {
635
+ return new SardisOnramper(config);
636
+ }
637
+ export {
638
+ PolicyViolation,
639
+ RampError,
640
+ SardisFiatRamp,
641
+ SardisOnramper,
642
+ createOnramper
643
+ };