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