@usdctofiat/offramp 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/README.md +133 -0
- package/dist/chunk-FQEVQAHM.js +660 -0
- package/dist/chunk-FQEVQAHM.js.map +1 -0
- package/dist/index.cjs +674 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +752 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +24 -0
- package/dist/react.d.ts +24 -0
- package/dist/react.js +89 -0
- package/dist/react.js.map +1 -0
- package/dist/types-BHf4ve-4.d.cts +45 -0
- package/dist/types-BHf4ve-4.d.ts +45 -0
- package/package.json +51 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
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
|
+
Offramp: () => Offramp,
|
|
24
|
+
OfframpError: () => OfframpError
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/client.ts
|
|
29
|
+
var import_viem2 = require("viem");
|
|
30
|
+
var import_sdk4 = require("@zkp2p/sdk");
|
|
31
|
+
|
|
32
|
+
// src/deposit.ts
|
|
33
|
+
var import_viem = require("viem");
|
|
34
|
+
var import_sdk3 = require("@zkp2p/sdk");
|
|
35
|
+
|
|
36
|
+
// src/config.ts
|
|
37
|
+
var import_sdk = require("@zkp2p/sdk");
|
|
38
|
+
var BASE_CHAIN_ID = 8453;
|
|
39
|
+
var RUNTIME_ENV = "production";
|
|
40
|
+
var API_BASE_URL = "https://api.zkp2p.xyz";
|
|
41
|
+
var BASE_RPC_URL = "https://mainnet.base.org";
|
|
42
|
+
var contracts = (0, import_sdk.getContracts)(BASE_CHAIN_ID, RUNTIME_ENV);
|
|
43
|
+
var addresses = contracts.addresses;
|
|
44
|
+
var addrs = addresses;
|
|
45
|
+
var USDC_ADDRESS = addrs.token || "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
46
|
+
var ESCROW_ADDRESS = addrs.escrowV2 || addrs.escrow || "";
|
|
47
|
+
var GATING_SERVICE_ADDRESS = (0, import_sdk.getGatingServiceAddress)(BASE_CHAIN_ID, RUNTIME_ENV);
|
|
48
|
+
var DELEGATE_RATE_MANAGER_ID = "0x8666d6fb0f6797c56e95339fd7ca82fdd348b9db200e10a4c4aa0a0b879fc41c";
|
|
49
|
+
var RATE_MANAGER_REGISTRY_ADDRESS = "0xeed7db23e724ac4590d6db6f78fda6db203535f3";
|
|
50
|
+
var REFERRER = "galleonlabs";
|
|
51
|
+
var MIN_DEPOSIT_USDC = 10;
|
|
52
|
+
var MIN_ORDER_USDC = 5;
|
|
53
|
+
var INDEXER_MAX_ATTEMPTS = 12;
|
|
54
|
+
var INDEXER_INITIAL_DELAY_MS = 1e3;
|
|
55
|
+
var INDEXER_MAX_DELAY_MS = 1e4;
|
|
56
|
+
var PAYEE_REGISTRATION_TIMEOUT_MS = 8e3;
|
|
57
|
+
|
|
58
|
+
// src/platforms.ts
|
|
59
|
+
var import_sdk2 = require("@zkp2p/sdk");
|
|
60
|
+
var import_zod = require("zod");
|
|
61
|
+
|
|
62
|
+
// src/types.ts
|
|
63
|
+
var PLATFORMS = [
|
|
64
|
+
"venmo",
|
|
65
|
+
"cashapp",
|
|
66
|
+
"chime",
|
|
67
|
+
"revolut",
|
|
68
|
+
"wise",
|
|
69
|
+
"mercadopago",
|
|
70
|
+
"zelle",
|
|
71
|
+
"paypal",
|
|
72
|
+
"monzo",
|
|
73
|
+
"n26"
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// src/platforms.ts
|
|
77
|
+
var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
|
|
78
|
+
var PAYMENT_CATALOG = (0, import_sdk2.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
|
|
79
|
+
var FALLBACK_CURRENCIES = {
|
|
80
|
+
venmo: ["USD"],
|
|
81
|
+
cashapp: ["USD"],
|
|
82
|
+
chime: ["USD"],
|
|
83
|
+
revolut: ["USD", "EUR", "GBP", "SGD", "NZD", "AUD", "CAD", "HKD", "MXN", "SAR", "AED", "THB", "TRY", "PLN", "CHF", "ZAR", "CZK", "CNY", "DKK", "HUF", "NOK", "RON", "SEK"],
|
|
84
|
+
wise: ["USD", "CNY", "EUR", "GBP", "AUD", "NZD", "CAD", "AED", "CHF", "ZAR", "SGD", "ILS", "HKD", "JPY", "PLN", "TRY", "IDR", "KES", "MYR", "MXN", "THB", "VND", "UGX", "CZK", "DKK", "HUF", "INR", "NOK", "PHP", "RON", "SEK"],
|
|
85
|
+
mercadopago: ["ARS"],
|
|
86
|
+
zelle: ["USD"],
|
|
87
|
+
paypal: ["USD", "EUR", "GBP", "SGD", "NZD", "AUD", "CAD"],
|
|
88
|
+
monzo: ["GBP"],
|
|
89
|
+
n26: ["EUR"]
|
|
90
|
+
};
|
|
91
|
+
function gatherCatalogHashes(platform) {
|
|
92
|
+
if (platform === "zelle") {
|
|
93
|
+
return Object.entries(PAYMENT_CATALOG).filter(([key]) => key.startsWith("zelle")).flatMap(([, entry]) => entry.currencies ?? []);
|
|
94
|
+
}
|
|
95
|
+
return PAYMENT_CATALOG[platform]?.currencies ?? [];
|
|
96
|
+
}
|
|
97
|
+
function resolveSupportedCurrencies(platform) {
|
|
98
|
+
const codes = /* @__PURE__ */ new Set();
|
|
99
|
+
for (const hash of gatherCatalogHashes(platform)) {
|
|
100
|
+
const info = (0, import_sdk2.getCurrencyInfoFromHash)(hash);
|
|
101
|
+
if (info?.currencyCode && import_sdk2.currencyInfo[info.currencyCode]) {
|
|
102
|
+
codes.add(info.currencyCode);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!codes.size) {
|
|
106
|
+
for (const code of FALLBACK_CURRENCIES[platform] ?? []) codes.add(code);
|
|
107
|
+
}
|
|
108
|
+
return Array.from(codes).sort();
|
|
109
|
+
}
|
|
110
|
+
var BLUEPRINT = {
|
|
111
|
+
venmo: {
|
|
112
|
+
id: "venmo",
|
|
113
|
+
name: "Venmo",
|
|
114
|
+
identifierLabel: "Username",
|
|
115
|
+
placeholder: "venmo username (no @)",
|
|
116
|
+
helperText: "Username without @ (publicly discoverable)",
|
|
117
|
+
validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/),
|
|
118
|
+
transform: (v) => v.replace(/^@+/, "")
|
|
119
|
+
},
|
|
120
|
+
cashapp: {
|
|
121
|
+
id: "cashapp",
|
|
122
|
+
name: "Cash App",
|
|
123
|
+
identifierLabel: "Cashtag",
|
|
124
|
+
placeholder: "cashtag (no $)",
|
|
125
|
+
helperText: "Cashtag without $ (publicly discoverable)",
|
|
126
|
+
validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9]+$/),
|
|
127
|
+
transform: (v) => v.replace(/^\$+/, "")
|
|
128
|
+
},
|
|
129
|
+
chime: {
|
|
130
|
+
id: "chime",
|
|
131
|
+
name: "Chime",
|
|
132
|
+
identifierLabel: "ChimeSign",
|
|
133
|
+
placeholder: "$chimesign",
|
|
134
|
+
helperText: "ChimeSign with $ (must be discoverable)",
|
|
135
|
+
validation: import_zod.z.string().min(2).regex(/^\$[a-zA-Z0-9]+$/),
|
|
136
|
+
transform: (v) => {
|
|
137
|
+
const t = v.trim().toLowerCase();
|
|
138
|
+
return t.startsWith("$") ? t : `$${t}`;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
revolut: {
|
|
142
|
+
id: "revolut",
|
|
143
|
+
name: "Revolut",
|
|
144
|
+
identifierLabel: "Revtag",
|
|
145
|
+
placeholder: "revtag (no @)",
|
|
146
|
+
helperText: "Revtag without @ (must be public)",
|
|
147
|
+
validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9]+$/),
|
|
148
|
+
transform: (v) => v.replace(/^@+/, "").trim()
|
|
149
|
+
},
|
|
150
|
+
wise: {
|
|
151
|
+
id: "wise",
|
|
152
|
+
name: "Wise",
|
|
153
|
+
identifierLabel: "Wisetag",
|
|
154
|
+
placeholder: "wisetag (no @)",
|
|
155
|
+
helperText: "Your Wise @wisetag (no @)",
|
|
156
|
+
validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/),
|
|
157
|
+
transform: (v) => v.replace(/^@+/, "").trim()
|
|
158
|
+
},
|
|
159
|
+
mercadopago: {
|
|
160
|
+
id: "mercadopago",
|
|
161
|
+
name: "Mercado Pago",
|
|
162
|
+
identifierLabel: "CVU",
|
|
163
|
+
placeholder: "22-digit CVU",
|
|
164
|
+
helperText: "CVU must be exactly 22 digits",
|
|
165
|
+
validation: import_zod.z.string().length(22).regex(/^\d{22}$/)
|
|
166
|
+
},
|
|
167
|
+
zelle: {
|
|
168
|
+
id: "zelle",
|
|
169
|
+
name: "Zelle",
|
|
170
|
+
identifierLabel: "Email",
|
|
171
|
+
placeholder: "email",
|
|
172
|
+
helperText: "Registered Zelle email",
|
|
173
|
+
validation: import_zod.z.string().email()
|
|
174
|
+
},
|
|
175
|
+
paypal: {
|
|
176
|
+
id: "paypal",
|
|
177
|
+
name: "PayPal",
|
|
178
|
+
identifierLabel: "Email",
|
|
179
|
+
placeholder: "email",
|
|
180
|
+
helperText: "Email linked to PayPal account",
|
|
181
|
+
validation: import_zod.z.string().email()
|
|
182
|
+
},
|
|
183
|
+
monzo: {
|
|
184
|
+
id: "monzo",
|
|
185
|
+
name: "Monzo",
|
|
186
|
+
identifierLabel: "Username",
|
|
187
|
+
placeholder: "monzo.me username",
|
|
188
|
+
helperText: "Your Monzo.me username",
|
|
189
|
+
validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/)
|
|
190
|
+
},
|
|
191
|
+
n26: {
|
|
192
|
+
id: "n26",
|
|
193
|
+
name: "N26",
|
|
194
|
+
identifierLabel: "IBAN",
|
|
195
|
+
placeholder: "IBAN (e.g. DE89...)",
|
|
196
|
+
helperText: "Your IBAN (spaces will be removed)",
|
|
197
|
+
validation: import_zod.z.string().min(15).max(34).regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/i),
|
|
198
|
+
transform: (v) => v.replace(/\s/g, "").toUpperCase()
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var CONFIGS = Object.fromEntries(
|
|
202
|
+
Object.entries(BLUEPRINT).map(([p, bp]) => {
|
|
203
|
+
const key = p;
|
|
204
|
+
return [key, { ...bp, currencies: resolveSupportedCurrencies(key) }];
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
function getPlatformConfig(platform) {
|
|
208
|
+
return CONFIGS[platform];
|
|
209
|
+
}
|
|
210
|
+
function getPlatforms() {
|
|
211
|
+
return PLATFORMS.map((id) => {
|
|
212
|
+
const cfg = CONFIGS[id];
|
|
213
|
+
return {
|
|
214
|
+
id,
|
|
215
|
+
name: cfg.name,
|
|
216
|
+
currencies: cfg.currencies,
|
|
217
|
+
identifierLabel: cfg.identifierLabel,
|
|
218
|
+
identifierPlaceholder: cfg.placeholder,
|
|
219
|
+
helperText: cfg.helperText
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function getCurrencies(platform) {
|
|
224
|
+
return CONFIGS[platform]?.currencies ?? [];
|
|
225
|
+
}
|
|
226
|
+
function validateIdentifier(platform, value) {
|
|
227
|
+
const cfg = CONFIGS[platform];
|
|
228
|
+
if (!cfg) return { valid: false, normalized: value, error: "Unsupported platform" };
|
|
229
|
+
const transformed = cfg.transform ? cfg.transform(value) : value;
|
|
230
|
+
const result = cfg.validation.safeParse(transformed);
|
|
231
|
+
if (!result.success) {
|
|
232
|
+
return { valid: false, normalized: transformed, error: result.error.issues[0]?.message || "Invalid input" };
|
|
233
|
+
}
|
|
234
|
+
return { valid: true, normalized: transformed };
|
|
235
|
+
}
|
|
236
|
+
function isSupportedCurrency(platform, currency) {
|
|
237
|
+
return CONFIGS[platform]?.currencies.includes(currency) ?? false;
|
|
238
|
+
}
|
|
239
|
+
function normalizePaymentMethodLookupName(platform) {
|
|
240
|
+
const normalized = platform.trim().toLowerCase();
|
|
241
|
+
if (!normalized) return null;
|
|
242
|
+
if (ZELLE_HASH_LOOKUP_NAMES.includes(normalized)) {
|
|
243
|
+
return normalized;
|
|
244
|
+
}
|
|
245
|
+
return PLATFORMS.includes(normalized) ? normalized : null;
|
|
246
|
+
}
|
|
247
|
+
function resolveCanonicalZelleHash() {
|
|
248
|
+
const direct = PAYMENT_CATALOG.zelle?.paymentMethodHash;
|
|
249
|
+
if (direct) return direct;
|
|
250
|
+
const variant = Object.entries(PAYMENT_CATALOG).find(
|
|
251
|
+
([name, entry]) => name.startsWith("zelle") && Boolean(entry.paymentMethodHash)
|
|
252
|
+
);
|
|
253
|
+
return variant?.[1]?.paymentMethodHash ?? null;
|
|
254
|
+
}
|
|
255
|
+
function getPaymentMethodHash(platform) {
|
|
256
|
+
const name = normalizePaymentMethodLookupName(platform);
|
|
257
|
+
if (!name) return null;
|
|
258
|
+
const catalogEntry = PAYMENT_CATALOG[name];
|
|
259
|
+
if (catalogEntry?.paymentMethodHash) return catalogEntry.paymentMethodHash;
|
|
260
|
+
if (name === "zelle" || name.startsWith("zelle-")) return resolveCanonicalZelleHash();
|
|
261
|
+
const sdkHash = (0, import_sdk2.resolvePaymentMethodHash)(name);
|
|
262
|
+
return sdkHash ? sdkHash : null;
|
|
263
|
+
}
|
|
264
|
+
function getPaymentMethodHashes(platform) {
|
|
265
|
+
if (platform !== "zelle") {
|
|
266
|
+
const hash = getPaymentMethodHash(platform);
|
|
267
|
+
return hash ? [hash] : [];
|
|
268
|
+
}
|
|
269
|
+
const hashes = /* @__PURE__ */ new Set();
|
|
270
|
+
const generic = (0, import_sdk2.resolvePaymentMethodHash)("zelle");
|
|
271
|
+
if (generic) hashes.add(generic);
|
|
272
|
+
const canonical = resolveCanonicalZelleHash();
|
|
273
|
+
if (canonical) hashes.add(canonical);
|
|
274
|
+
for (const [name, entry] of Object.entries(PAYMENT_CATALOG)) {
|
|
275
|
+
if (name.startsWith("zelle") && entry.paymentMethodHash) {
|
|
276
|
+
hashes.add(entry.paymentMethodHash);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return Array.from(hashes);
|
|
280
|
+
}
|
|
281
|
+
function buildDepositData(platform, identifier) {
|
|
282
|
+
switch (platform) {
|
|
283
|
+
case "venmo":
|
|
284
|
+
return { venmoUsername: identifier, telegramUsername: "" };
|
|
285
|
+
case "cashapp":
|
|
286
|
+
return { cashtag: identifier, telegramUsername: "" };
|
|
287
|
+
case "chime":
|
|
288
|
+
return { chimesign: identifier.toLowerCase(), telegramUsername: "" };
|
|
289
|
+
case "revolut":
|
|
290
|
+
return { revolutUsername: identifier, telegramUsername: "" };
|
|
291
|
+
case "wise":
|
|
292
|
+
return { wisetag: identifier, telegramUsername: "" };
|
|
293
|
+
case "mercadopago":
|
|
294
|
+
return { cvu: identifier, telegramUsername: "" };
|
|
295
|
+
case "zelle":
|
|
296
|
+
return { zelleEmail: identifier, telegramUsername: "" };
|
|
297
|
+
case "paypal":
|
|
298
|
+
return { paypalEmail: identifier, telegramUsername: "" };
|
|
299
|
+
case "monzo":
|
|
300
|
+
return { monzoMeUsername: identifier, telegramUsername: "" };
|
|
301
|
+
case "n26":
|
|
302
|
+
return { iban: identifier, telegramUsername: "" };
|
|
303
|
+
default:
|
|
304
|
+
return { identifier, telegramUsername: "" };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/errors.ts
|
|
309
|
+
var OfframpError = class extends Error {
|
|
310
|
+
code;
|
|
311
|
+
step;
|
|
312
|
+
cause;
|
|
313
|
+
txHash;
|
|
314
|
+
depositId;
|
|
315
|
+
constructor(message, code, step, cause, details) {
|
|
316
|
+
super(message);
|
|
317
|
+
this.name = "OfframpError";
|
|
318
|
+
this.code = code;
|
|
319
|
+
this.step = step;
|
|
320
|
+
this.cause = cause;
|
|
321
|
+
this.txHash = details?.txHash;
|
|
322
|
+
this.depositId = details?.depositId;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
function isUserCancellation(error) {
|
|
326
|
+
if (!(error instanceof Error)) return false;
|
|
327
|
+
const msg = error.message.toLowerCase();
|
|
328
|
+
return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/deposit.ts
|
|
332
|
+
function usdcToUnits(amount) {
|
|
333
|
+
return (0, import_viem.parseUnits)(amount, 6);
|
|
334
|
+
}
|
|
335
|
+
var DEPOSIT_RECEIVED_ABI = [
|
|
336
|
+
{
|
|
337
|
+
type: "event",
|
|
338
|
+
name: "DepositReceived",
|
|
339
|
+
inputs: [
|
|
340
|
+
{ indexed: true, name: "depositId", type: "uint256" },
|
|
341
|
+
{ indexed: true, name: "depositor", type: "address" },
|
|
342
|
+
{ indexed: true, name: "token", type: "address" },
|
|
343
|
+
{ indexed: false, name: "amount", type: "uint256" },
|
|
344
|
+
{ indexed: false, name: "intentAmountRange", type: "tuple", components: [
|
|
345
|
+
{ name: "min", type: "uint256" },
|
|
346
|
+
{ name: "max", type: "uint256" }
|
|
347
|
+
] },
|
|
348
|
+
{ indexed: false, name: "delegate", type: "address" },
|
|
349
|
+
{ indexed: false, name: "intentGuardian", type: "address" }
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
];
|
|
353
|
+
function extractDepositIdFromLogs(logs) {
|
|
354
|
+
for (const log of logs) {
|
|
355
|
+
if (!log?.topics?.length || !log.data) continue;
|
|
356
|
+
try {
|
|
357
|
+
const decoded = (0, import_viem.decodeEventLog)({
|
|
358
|
+
abi: DEPOSIT_RECEIVED_ABI,
|
|
359
|
+
data: log.data,
|
|
360
|
+
topics: log.topics
|
|
361
|
+
});
|
|
362
|
+
if (decoded.eventName !== "DepositReceived") continue;
|
|
363
|
+
const args = decoded.args;
|
|
364
|
+
if (typeof args.depositId === "bigint") return args.depositId.toString();
|
|
365
|
+
} catch {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
async function registerPayeeDetails(processorName, depositData) {
|
|
372
|
+
const controller = new AbortController();
|
|
373
|
+
const timeout = setTimeout(() => controller.abort(), PAYEE_REGISTRATION_TIMEOUT_MS);
|
|
374
|
+
try {
|
|
375
|
+
const res = await fetch(`${API_BASE_URL}/v1/makers/create`, {
|
|
376
|
+
method: "POST",
|
|
377
|
+
headers: { "Content-Type": "application/json" },
|
|
378
|
+
body: JSON.stringify({ processorName, depositData }),
|
|
379
|
+
signal: controller.signal
|
|
380
|
+
});
|
|
381
|
+
if (!res.ok) {
|
|
382
|
+
const txt = await res.text().catch(() => "");
|
|
383
|
+
throw new Error(`makers/create failed (${res.status}): ${txt || res.statusText}`);
|
|
384
|
+
}
|
|
385
|
+
const json = await res.json();
|
|
386
|
+
if (!json.success || !json.responseObject?.hashedOnchainId) {
|
|
387
|
+
throw new Error(json.message || "makers/create returned no hashedOnchainId");
|
|
388
|
+
}
|
|
389
|
+
return json.responseObject.hashedOnchainId;
|
|
390
|
+
} finally {
|
|
391
|
+
clearTimeout(timeout);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function attachOracleConfig(entries, conversionRates) {
|
|
395
|
+
return entries.map(
|
|
396
|
+
(group, gi) => group.map((entry, ci) => {
|
|
397
|
+
const currency = conversionRates[gi]?.[ci]?.currency;
|
|
398
|
+
if (!currency) return entry;
|
|
399
|
+
const oracleConfig = (0, import_sdk3.getSpreadOracleConfig)(currency);
|
|
400
|
+
if (!oracleConfig) return entry;
|
|
401
|
+
return {
|
|
402
|
+
...entry,
|
|
403
|
+
oracleRateConfig: {
|
|
404
|
+
adapter: oracleConfig.adapter,
|
|
405
|
+
adapterConfig: oracleConfig.adapterConfig,
|
|
406
|
+
spreadBps: 0,
|
|
407
|
+
maxStaleness: oracleConfig.maxStaleness
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
})
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
function createSdkClient(walletClient) {
|
|
414
|
+
return new import_sdk3.OfframpClient({
|
|
415
|
+
walletClient,
|
|
416
|
+
chainId: BASE_CHAIN_ID,
|
|
417
|
+
runtimeEnv: RUNTIME_ENV,
|
|
418
|
+
rpcUrl: BASE_RPC_URL,
|
|
419
|
+
baseApiUrl: API_BASE_URL
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async function createOfframpDeposit(walletClient, params, onProgress) {
|
|
423
|
+
const { amount, platform, currency, identifier } = params;
|
|
424
|
+
const amt = parseFloat(amount);
|
|
425
|
+
if (!Number.isFinite(amt) || amt < MIN_DEPOSIT_USDC) {
|
|
426
|
+
throw new OfframpError(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC`, "VALIDATION");
|
|
427
|
+
}
|
|
428
|
+
if (!isSupportedCurrency(platform, currency)) {
|
|
429
|
+
throw new OfframpError(`${currency} is not supported on ${platform}`, "UNSUPPORTED");
|
|
430
|
+
}
|
|
431
|
+
const validation = validateIdentifier(platform, identifier);
|
|
432
|
+
if (!validation.valid) {
|
|
433
|
+
throw new OfframpError(validation.error || "Invalid identifier", "VALIDATION");
|
|
434
|
+
}
|
|
435
|
+
const normalizedIdentifier = validation.normalized;
|
|
436
|
+
const methodHash = getPaymentMethodHash(platform);
|
|
437
|
+
if (!methodHash) {
|
|
438
|
+
throw new OfframpError(`${platform} is not currently supported`, "UNSUPPORTED");
|
|
439
|
+
}
|
|
440
|
+
const amountUnits = usdcToUnits(amount);
|
|
441
|
+
const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
|
|
442
|
+
const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
|
|
443
|
+
const client = createSdkClient(walletClient);
|
|
444
|
+
const txOverrides = { referrer: [REFERRER] };
|
|
445
|
+
onProgress?.({ step: "approving" });
|
|
446
|
+
try {
|
|
447
|
+
await client.ensureAllowance({
|
|
448
|
+
token: USDC_ADDRESS,
|
|
449
|
+
amount: amountUnits,
|
|
450
|
+
escrowAddress: ESCROW_ADDRESS,
|
|
451
|
+
maxApprove: false,
|
|
452
|
+
txOverrides
|
|
453
|
+
});
|
|
454
|
+
} catch (err) {
|
|
455
|
+
if (isUserCancellation(err)) throw new OfframpError("User cancelled", "USER_CANCELLED", "approving", err);
|
|
456
|
+
throw new OfframpError("USDC approval failed", "APPROVAL_FAILED", "approving", err);
|
|
457
|
+
}
|
|
458
|
+
onProgress?.({ step: "registering" });
|
|
459
|
+
let hashedOnchainId;
|
|
460
|
+
try {
|
|
461
|
+
const canonicalName = platform.startsWith("zelle") ? "zelle" : platform;
|
|
462
|
+
const depositData = buildDepositData(platform, normalizedIdentifier);
|
|
463
|
+
hashedOnchainId = await registerPayeeDetails(canonicalName, depositData);
|
|
464
|
+
} catch (err) {
|
|
465
|
+
throw new OfframpError("Payee registration failed", "REGISTRATION_FAILED", "registering", err);
|
|
466
|
+
}
|
|
467
|
+
onProgress?.({ step: "depositing" });
|
|
468
|
+
const conversionRates = [
|
|
469
|
+
[{ currency, conversionRate: "1" }]
|
|
470
|
+
];
|
|
471
|
+
const baseCurrenciesOverride = (0, import_sdk3.mapConversionRatesToOnchainMinRate)(conversionRates, 1);
|
|
472
|
+
const currenciesOverride = attachOracleConfig(baseCurrenciesOverride, conversionRates);
|
|
473
|
+
let hash;
|
|
474
|
+
try {
|
|
475
|
+
const result = await client.createDeposit({
|
|
476
|
+
token: USDC_ADDRESS,
|
|
477
|
+
amount: amountUnits,
|
|
478
|
+
retainOnEmpty: false,
|
|
479
|
+
intentAmountRange: { min: minUnits, max: maxUnits },
|
|
480
|
+
processorNames: [platform],
|
|
481
|
+
depositData: [buildDepositData(platform, normalizedIdentifier)],
|
|
482
|
+
conversionRates,
|
|
483
|
+
paymentMethodsOverride: [methodHash],
|
|
484
|
+
paymentMethodDataOverride: [{
|
|
485
|
+
intentGatingService: GATING_SERVICE_ADDRESS,
|
|
486
|
+
payeeDetails: hashedOnchainId,
|
|
487
|
+
data: "0x"
|
|
488
|
+
}],
|
|
489
|
+
currenciesOverride,
|
|
490
|
+
escrowAddress: ESCROW_ADDRESS,
|
|
491
|
+
txOverrides
|
|
492
|
+
});
|
|
493
|
+
if (!result?.hash) throw new Error("No transaction hash returned");
|
|
494
|
+
hash = result.hash;
|
|
495
|
+
} catch (err) {
|
|
496
|
+
if (isUserCancellation(err)) throw new OfframpError("User cancelled", "USER_CANCELLED", "depositing", err);
|
|
497
|
+
throw new OfframpError("Deposit transaction failed", "DEPOSIT_FAILED", "depositing", err);
|
|
498
|
+
}
|
|
499
|
+
onProgress?.({ step: "confirming", txHash: hash });
|
|
500
|
+
let depositId = "";
|
|
501
|
+
const receiptClient = client;
|
|
502
|
+
if (typeof receiptClient.waitForTransactionReceipt === "function") {
|
|
503
|
+
try {
|
|
504
|
+
const receipt = await receiptClient.waitForTransactionReceipt({ hash, confirmations: 1 });
|
|
505
|
+
depositId = extractDepositIdFromLogs(receipt.logs) || "";
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (!depositId) {
|
|
510
|
+
const walletAddress = walletClient.account?.address;
|
|
511
|
+
if (walletAddress) {
|
|
512
|
+
let delay = INDEXER_INITIAL_DELAY_MS;
|
|
513
|
+
for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
|
|
514
|
+
try {
|
|
515
|
+
const deposits = await client.indexer.getDepositsWithRelations(
|
|
516
|
+
{ depositor: walletAddress },
|
|
517
|
+
{ limit: 25 }
|
|
518
|
+
);
|
|
519
|
+
const hit = deposits.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
|
|
520
|
+
if (hit) {
|
|
521
|
+
depositId = String(hit.depositId);
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
} catch {
|
|
525
|
+
}
|
|
526
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
527
|
+
delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (!depositId) {
|
|
532
|
+
throw new OfframpError(
|
|
533
|
+
"Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Use the transaction hash to locate your deposit.",
|
|
534
|
+
"CONFIRMATION_FAILED",
|
|
535
|
+
"confirming",
|
|
536
|
+
void 0,
|
|
537
|
+
{ txHash: hash }
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
onProgress?.({ step: "delegating", txHash: hash, depositId });
|
|
541
|
+
try {
|
|
542
|
+
await client.setRateManager({
|
|
543
|
+
depositId: BigInt(depositId),
|
|
544
|
+
rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
|
|
545
|
+
rateManagerId: DELEGATE_RATE_MANAGER_ID,
|
|
546
|
+
escrowAddress: ESCROW_ADDRESS,
|
|
547
|
+
txOverrides
|
|
548
|
+
});
|
|
549
|
+
} catch (delegationError) {
|
|
550
|
+
if (isUserCancellation(delegationError)) {
|
|
551
|
+
throw new OfframpError("User cancelled delegation", "USER_CANCELLED", "delegating", delegationError, { txHash: hash, depositId });
|
|
552
|
+
}
|
|
553
|
+
throw new OfframpError(
|
|
554
|
+
"Deposit created but delegation failed. Visit usdctofiat.xyz to manage your deposit manually.",
|
|
555
|
+
"DELEGATION_FAILED",
|
|
556
|
+
"delegating",
|
|
557
|
+
delegationError,
|
|
558
|
+
{ txHash: hash, depositId }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
onProgress?.({ step: "done", txHash: hash, depositId });
|
|
562
|
+
return { depositId, txHash: hash };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// src/client.ts
|
|
566
|
+
function toBigInt(value) {
|
|
567
|
+
try {
|
|
568
|
+
return BigInt(value || "0");
|
|
569
|
+
} catch {
|
|
570
|
+
return 0n;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function toUsdc(value) {
|
|
574
|
+
return Number((0, import_viem2.formatUnits)(toBigInt(value), 6));
|
|
575
|
+
}
|
|
576
|
+
function resolveStatus(deposit) {
|
|
577
|
+
if (deposit.status === "CLOSED") return "closed";
|
|
578
|
+
if (toBigInt(deposit.remainingDeposits) === 0n) return "empty";
|
|
579
|
+
return "active";
|
|
580
|
+
}
|
|
581
|
+
function resolveMethodNames(hashes) {
|
|
582
|
+
const names = /* @__PURE__ */ new Set();
|
|
583
|
+
for (const { paymentMethodHash } of hashes) {
|
|
584
|
+
const normalized = paymentMethodHash.toLowerCase();
|
|
585
|
+
for (const platform of ["venmo", "cashapp", "chime", "revolut", "wise", "mercadopago", "zelle", "paypal", "monzo", "n26"]) {
|
|
586
|
+
const platformHashes = getPaymentMethodHashes(platform);
|
|
587
|
+
if (platformHashes.some((h) => h.toLowerCase() === normalized)) {
|
|
588
|
+
names.add(getPlatformConfig(platform).name);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return Array.from(names);
|
|
594
|
+
}
|
|
595
|
+
var Offramp = class {
|
|
596
|
+
/**
|
|
597
|
+
* Create an offramp deposit: approve USDC, register payee, create deposit,
|
|
598
|
+
* confirm on-chain, and delegate to the vault. All in one call.
|
|
599
|
+
*/
|
|
600
|
+
async createDeposit(walletClient, params, onProgress) {
|
|
601
|
+
return createOfframpDeposit(walletClient, params, onProgress);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Fetch deposits for a wallet address. Returns active, empty, and closed deposits
|
|
605
|
+
* sorted by status (active first) then recency.
|
|
606
|
+
*/
|
|
607
|
+
async getDeposits(walletAddress) {
|
|
608
|
+
const client = createSdkClient(
|
|
609
|
+
// Read-only client -- no wallet needed for indexer queries
|
|
610
|
+
{ account: void 0, chain: void 0, transport: void 0 }
|
|
611
|
+
);
|
|
612
|
+
const raw = await client.indexer.getDepositsWithRelations(
|
|
613
|
+
{ depositor: walletAddress },
|
|
614
|
+
{ limit: 100 }
|
|
615
|
+
);
|
|
616
|
+
const statusOrder = { active: 0, empty: 1, closed: 2 };
|
|
617
|
+
return (raw || []).map((d) => {
|
|
618
|
+
const delegationState = (0, import_sdk4.classifyDelegationState)(
|
|
619
|
+
d.rateManagerId ?? void 0,
|
|
620
|
+
d.rateManagerAddress ?? void 0,
|
|
621
|
+
DELEGATE_RATE_MANAGER_ID,
|
|
622
|
+
RATE_MANAGER_REGISTRY_ADDRESS
|
|
623
|
+
);
|
|
624
|
+
return {
|
|
625
|
+
depositId: d.depositId,
|
|
626
|
+
compositeId: d.id,
|
|
627
|
+
status: resolveStatus(d),
|
|
628
|
+
remainingUsdc: toUsdc(d.remainingDeposits),
|
|
629
|
+
outstandingUsdc: toUsdc(d.outstandingIntentAmount),
|
|
630
|
+
totalTakenUsdc: toUsdc(d.totalAmountTaken),
|
|
631
|
+
fulfilledIntents: d.fulfilledIntents ?? 0,
|
|
632
|
+
paymentMethods: resolveMethodNames(d.paymentMethods),
|
|
633
|
+
currencies: d.currencies.map((c) => c.currencyCode),
|
|
634
|
+
rateSource: d.currencies[0]?.rateSource || "unknown",
|
|
635
|
+
delegated: delegationState === "delegated_here",
|
|
636
|
+
escrowAddress: d.escrowAddress
|
|
637
|
+
};
|
|
638
|
+
}).sort((a, b) => {
|
|
639
|
+
const diff = statusOrder[a.status] - statusOrder[b.status];
|
|
640
|
+
if (diff !== 0) return diff;
|
|
641
|
+
return Number(BigInt(b.depositId) - BigInt(a.depositId));
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Withdraw and close a deposit, returning remaining USDC to the wallet.
|
|
646
|
+
*/
|
|
647
|
+
async withdrawDeposit(walletClient, depositId, escrowAddress) {
|
|
648
|
+
const client = createSdkClient(walletClient);
|
|
649
|
+
const result = await client.withdrawDeposit({
|
|
650
|
+
depositId: BigInt(depositId),
|
|
651
|
+
escrowAddress: escrowAddress || ESCROW_ADDRESS,
|
|
652
|
+
txOverrides: { referrer: [REFERRER] }
|
|
653
|
+
});
|
|
654
|
+
return typeof result === "string" ? result : result.hash;
|
|
655
|
+
}
|
|
656
|
+
/** List available payment platforms and their supported currencies. */
|
|
657
|
+
getPlatforms() {
|
|
658
|
+
return getPlatforms();
|
|
659
|
+
}
|
|
660
|
+
/** Get supported currencies for a specific platform. */
|
|
661
|
+
getCurrencies(platform) {
|
|
662
|
+
return getCurrencies(platform);
|
|
663
|
+
}
|
|
664
|
+
/** Validate a payment identifier for a platform without creating a deposit. */
|
|
665
|
+
validateIdentifier(platform, identifier) {
|
|
666
|
+
return validateIdentifier(platform, identifier);
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
670
|
+
0 && (module.exports = {
|
|
671
|
+
Offramp,
|
|
672
|
+
OfframpError
|
|
673
|
+
});
|
|
674
|
+
//# sourceMappingURL=index.cjs.map
|