@usdctofiat/offramp 0.2.1 → 1.0.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.
package/dist/index.cjs CHANGED
@@ -21,129 +21,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  CURRENCIES: () => CURRENCIES,
24
- Offramp: () => Offramp,
25
24
  OfframpError: () => OfframpError,
26
- PAYMENT_PLATFORMS: () => PAYMENT_PLATFORMS,
27
- SUPPORTED_CURRENCIES: () => SUPPORTED_CURRENCIES
25
+ PLATFORMS: () => PLATFORMS,
26
+ close: () => close,
27
+ deposits: () => deposits,
28
+ offramp: () => offramp
28
29
  });
29
30
  module.exports = __toCommonJS(index_exports);
30
31
 
31
- // src/client.ts
32
- var import_viem2 = require("viem");
33
- var import_sdk4 = require("@zkp2p/sdk");
34
-
35
- // src/types.ts
36
- var PLATFORMS = [
37
- "venmo",
38
- "cashapp",
39
- "chime",
40
- "revolut",
41
- "wise",
42
- "mercadopago",
43
- "zelle",
44
- "paypal",
45
- "monzo",
46
- "n26"
47
- ];
48
- var PAYMENT_PLATFORMS = {
49
- VENMO: "venmo",
50
- CASHAPP: "cashapp",
51
- CHIME: "chime",
52
- REVOLUT: "revolut",
53
- WISE: "wise",
54
- MERCADO_PAGO: "mercadopago",
55
- ZELLE: "zelle",
56
- PAYPAL: "paypal",
57
- MONZO: "monzo",
58
- N26: "n26"
59
- };
60
- var SUPPORTED_CURRENCIES = [
61
- "AED",
62
- "ARS",
63
- "AUD",
64
- "BRL",
65
- "CAD",
66
- "CHF",
67
- "CLP",
68
- "CNY",
69
- "COP",
70
- "CZK",
71
- "DKK",
72
- "EUR",
73
- "GBP",
74
- "HKD",
75
- "HUF",
76
- "IDR",
77
- "ILS",
78
- "INR",
79
- "JPY",
80
- "KES",
81
- "KRW",
82
- "MXN",
83
- "MYR",
84
- "NOK",
85
- "NZD",
86
- "PEN",
87
- "PHP",
88
- "PLN",
89
- "RON",
90
- "SAR",
91
- "SEK",
92
- "SGD",
93
- "THB",
94
- "TRY",
95
- "TWD",
96
- "UGX",
97
- "USD",
98
- "VND",
99
- "ZAR"
100
- ];
101
- var CURRENCIES = {
102
- AED: "AED",
103
- ARS: "ARS",
104
- AUD: "AUD",
105
- BRL: "BRL",
106
- CAD: "CAD",
107
- CHF: "CHF",
108
- CLP: "CLP",
109
- CNY: "CNY",
110
- COP: "COP",
111
- CZK: "CZK",
112
- DKK: "DKK",
113
- EUR: "EUR",
114
- GBP: "GBP",
115
- HKD: "HKD",
116
- HUF: "HUF",
117
- IDR: "IDR",
118
- ILS: "ILS",
119
- INR: "INR",
120
- JPY: "JPY",
121
- KES: "KES",
122
- KRW: "KRW",
123
- MXN: "MXN",
124
- MYR: "MYR",
125
- NOK: "NOK",
126
- NZD: "NZD",
127
- PEN: "PEN",
128
- PHP: "PHP",
129
- PLN: "PLN",
130
- RON: "RON",
131
- SAR: "SAR",
132
- SEK: "SEK",
133
- SGD: "SGD",
134
- THB: "THB",
135
- TRY: "TRY",
136
- TWD: "TWD",
137
- UGX: "UGX",
138
- USD: "USD",
139
- VND: "VND",
140
- ZAR: "ZAR"
141
- };
142
-
143
32
  // src/deposit.ts
144
- var import_viem = require("viem");
33
+ var import_viem2 = require("viem");
145
34
  var import_chains = require("viem/chains");
146
- var import_sdk3 = require("@zkp2p/sdk");
35
+ var import_sdk4 = require("@zkp2p/sdk");
147
36
 
148
37
  // src/config.ts
149
38
  var import_sdk = require("@zkp2p/sdk");
@@ -170,8 +59,8 @@ var PAYEE_REGISTRATION_TIMEOUT_MS = 8e3;
170
59
  // src/platforms.ts
171
60
  var import_sdk2 = require("@zkp2p/sdk");
172
61
  var import_zod = require("zod");
173
- var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
174
62
  var PAYMENT_CATALOG = (0, import_sdk2.getPaymentMethodsCatalog)(BASE_CHAIN_ID, RUNTIME_ENV);
63
+ var ZELLE_HASH_LOOKUP_NAMES = ["zelle", "zelle-bofa", "zelle-chase", "zelle-citi"];
175
64
  var FALLBACK_CURRENCIES = {
176
65
  venmo: ["USD"],
177
66
  cashapp: ["USD"],
@@ -203,142 +92,62 @@ function resolveSupportedCurrencies(platform) {
203
92
  }
204
93
  return Array.from(codes).sort();
205
94
  }
206
- var BLUEPRINT = {
207
- venmo: {
208
- id: "venmo",
209
- name: "Venmo",
210
- identifierLabel: "Username",
211
- placeholder: "venmo username (no @)",
212
- helperText: "Username without @ (publicly discoverable)",
213
- validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/),
214
- transform: (v) => v.replace(/^@+/, "")
215
- },
216
- cashapp: {
217
- id: "cashapp",
218
- name: "Cash App",
219
- identifierLabel: "Cashtag",
220
- placeholder: "cashtag (no $)",
221
- helperText: "Cashtag without $ (publicly discoverable)",
222
- validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9]+$/),
223
- transform: (v) => v.replace(/^\$+/, "")
224
- },
225
- chime: {
226
- id: "chime",
227
- name: "Chime",
228
- identifierLabel: "ChimeSign",
229
- placeholder: "$chimesign",
230
- helperText: "ChimeSign with $ (must be discoverable)",
231
- validation: import_zod.z.string().min(2).regex(/^\$[a-zA-Z0-9]+$/),
232
- transform: (v) => {
233
- const t = v.trim().toLowerCase();
234
- return t.startsWith("$") ? t : `$${t}`;
235
- }
236
- },
237
- revolut: {
238
- id: "revolut",
239
- name: "Revolut",
240
- identifierLabel: "Revtag",
241
- placeholder: "revtag (no @)",
242
- helperText: "Revtag without @ (must be public)",
243
- validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9]+$/),
244
- transform: (v) => v.replace(/^@+/, "").trim()
245
- },
246
- wise: {
247
- id: "wise",
248
- name: "Wise",
249
- identifierLabel: "Wisetag",
250
- placeholder: "wisetag (no @)",
251
- helperText: "Your Wise @wisetag (no @)",
252
- validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/),
253
- transform: (v) => v.replace(/^@+/, "").trim()
254
- },
255
- mercadopago: {
256
- id: "mercadopago",
257
- name: "Mercado Pago",
258
- identifierLabel: "CVU",
259
- placeholder: "22-digit CVU",
260
- helperText: "CVU must be exactly 22 digits",
261
- validation: import_zod.z.string().length(22).regex(/^\d{22}$/)
262
- },
263
- zelle: {
264
- id: "zelle",
265
- name: "Zelle",
266
- identifierLabel: "Email",
267
- placeholder: "email",
268
- helperText: "Registered Zelle email",
269
- validation: import_zod.z.string().email()
270
- },
271
- paypal: {
272
- id: "paypal",
273
- name: "PayPal",
274
- identifierLabel: "Email",
275
- placeholder: "email",
276
- helperText: "Email linked to PayPal account",
277
- validation: import_zod.z.string().email()
278
- },
279
- monzo: {
280
- id: "monzo",
281
- name: "Monzo",
282
- identifierLabel: "Username",
283
- placeholder: "monzo.me username",
284
- helperText: "Your Monzo.me username",
285
- validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/)
286
- },
287
- n26: {
288
- id: "n26",
289
- name: "N26",
290
- identifierLabel: "IBAN",
291
- placeholder: "IBAN (e.g. DE89...)",
292
- helperText: "Your IBAN (spaces will be removed)",
293
- validation: import_zod.z.string().min(15).max(34).regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/i),
294
- transform: (v) => v.replace(/\s/g, "").toUpperCase()
295
- }
95
+ var BLUEPRINTS = {
96
+ venmo: { id: "venmo", name: "Venmo", identifierLabel: "Username", placeholder: "venmo username (no @)", helperText: "Username without @ (publicly discoverable)", validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/), transform: (v) => v.replace(/^@+/, "") },
97
+ cashapp: { id: "cashapp", name: "Cash App", identifierLabel: "Cashtag", placeholder: "cashtag (no $)", helperText: "Cashtag without $ (publicly discoverable)", validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9]+$/), transform: (v) => v.replace(/^\$+/, "") },
98
+ chime: { id: "chime", name: "Chime", identifierLabel: "ChimeSign", placeholder: "$chimesign", helperText: "ChimeSign with $ (must be discoverable)", validation: import_zod.z.string().min(2).regex(/^\$[a-zA-Z0-9]+$/), transform: (v) => {
99
+ const t = v.trim().toLowerCase();
100
+ return t.startsWith("$") ? t : `$${t}`;
101
+ } },
102
+ revolut: { id: "revolut", name: "Revolut", identifierLabel: "Revtag", placeholder: "revtag (no @)", helperText: "Revtag without @ (must be public)", validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9]+$/), transform: (v) => v.replace(/^@+/, "").trim() },
103
+ wise: { id: "wise", name: "Wise", identifierLabel: "Wisetag", placeholder: "wisetag (no @)", helperText: "Your Wise @wisetag (no @)", validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/), transform: (v) => v.replace(/^@+/, "").trim() },
104
+ mercadopago: { id: "mercadopago", name: "Mercado Pago", identifierLabel: "CVU", placeholder: "22-digit CVU", helperText: "CVU must be exactly 22 digits", validation: import_zod.z.string().length(22).regex(/^\d{22}$/) },
105
+ zelle: { id: "zelle", name: "Zelle", identifierLabel: "Email", placeholder: "email", helperText: "Registered Zelle email", validation: import_zod.z.string().email() },
106
+ paypal: { id: "paypal", name: "PayPal", identifierLabel: "Email", placeholder: "email", helperText: "Email linked to PayPal account", validation: import_zod.z.string().email() },
107
+ monzo: { id: "monzo", name: "Monzo", identifierLabel: "Username", placeholder: "monzo.me username", helperText: "Your Monzo.me username", validation: import_zod.z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/) },
108
+ n26: { id: "n26", name: "N26", identifierLabel: "IBAN", placeholder: "IBAN (e.g. DE89...)", helperText: "Your IBAN (spaces will be removed)", validation: import_zod.z.string().min(15).max(34).regex(/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/i), transform: (v) => v.replace(/\s/g, "").toUpperCase() }
296
109
  };
297
- var CONFIGS = Object.fromEntries(
298
- Object.entries(BLUEPRINT).map(([p, bp]) => {
299
- const key = p;
300
- return [key, { ...bp, currencies: resolveSupportedCurrencies(key) }];
301
- })
302
- );
303
- function getPlatformConfig(platform) {
304
- return CONFIGS[platform];
305
- }
306
- function getPlatforms() {
307
- return PLATFORMS.map((id) => {
308
- const cfg = CONFIGS[id];
309
- return {
310
- id,
311
- name: cfg.name,
312
- currencies: cfg.currencies,
313
- identifierLabel: cfg.identifierLabel,
314
- identifierPlaceholder: cfg.placeholder,
315
- helperText: cfg.helperText
316
- };
317
- });
318
- }
319
- function getCurrencies(platform) {
320
- return CONFIGS[platform]?.currencies ?? [];
321
- }
322
- function validateIdentifier(platform, value) {
323
- const cfg = CONFIGS[platform];
324
- if (!cfg) return { valid: false, normalized: value, error: "Unsupported platform" };
325
- const transformed = cfg.transform ? cfg.transform(value) : value;
326
- const result = cfg.validation.safeParse(transformed);
327
- if (!result.success) {
328
- return { valid: false, normalized: transformed, error: result.error.issues[0]?.message || "Invalid input" };
329
- }
330
- return { valid: true, normalized: transformed };
331
- }
332
- function isSupportedCurrency(platform, currency) {
333
- return CONFIGS[platform]?.currencies.includes(currency) ?? false;
110
+ function buildPlatformEntry(bp) {
111
+ const currencies = resolveSupportedCurrencies(bp.id);
112
+ return {
113
+ id: bp.id,
114
+ name: bp.name,
115
+ currencies,
116
+ identifier: {
117
+ label: bp.identifierLabel,
118
+ placeholder: bp.placeholder,
119
+ help: bp.helperText
120
+ },
121
+ validate(input) {
122
+ const transformed = bp.transform ? bp.transform(input) : input;
123
+ const result = bp.validation.safeParse(transformed);
124
+ if (!result.success) {
125
+ return { valid: false, normalized: transformed, error: result.error.issues[0]?.message || "Invalid input" };
126
+ }
127
+ return { valid: true, normalized: transformed };
128
+ }
129
+ };
334
130
  }
131
+ var PLATFORMS = {
132
+ VENMO: buildPlatformEntry(BLUEPRINTS.venmo),
133
+ CASHAPP: buildPlatformEntry(BLUEPRINTS.cashapp),
134
+ CHIME: buildPlatformEntry(BLUEPRINTS.chime),
135
+ REVOLUT: buildPlatformEntry(BLUEPRINTS.revolut),
136
+ WISE: buildPlatformEntry(BLUEPRINTS.wise),
137
+ MERCADO_PAGO: buildPlatformEntry(BLUEPRINTS.mercadopago),
138
+ ZELLE: buildPlatformEntry(BLUEPRINTS.zelle),
139
+ PAYPAL: buildPlatformEntry(BLUEPRINTS.paypal),
140
+ MONZO: buildPlatformEntry(BLUEPRINTS.monzo),
141
+ N26: buildPlatformEntry(BLUEPRINTS.n26)
142
+ };
335
143
  function normalizePaymentMethodLookupName(platform) {
336
144
  const normalized = platform.trim().toLowerCase();
337
145
  if (!normalized) return null;
338
146
  if (ZELLE_HASH_LOOKUP_NAMES.includes(normalized)) {
339
147
  return normalized;
340
148
  }
341
- return PLATFORMS.includes(normalized) ? normalized : null;
149
+ const ids = Object.values(PLATFORMS).map((p) => p.id);
150
+ return ids.includes(normalized) ? normalized : null;
342
151
  }
343
152
  function resolveCanonicalZelleHash() {
344
153
  const direct = PAYMENT_CATALOG.zelle?.paymentMethodHash;
@@ -424,9 +233,105 @@ function isUserCancellation(error) {
424
233
  return msg.includes("user rejected") || msg.includes("user denied") || msg.includes("user cancelled") || msg.includes("rejected the request") || msg.includes("action_rejected");
425
234
  }
426
235
 
236
+ // src/queries.ts
237
+ var import_viem = require("viem");
238
+ var import_sdk3 = require("@zkp2p/sdk");
239
+ var indexerService = null;
240
+ function getIndexerService() {
241
+ if (!indexerService) {
242
+ const endpoint = (0, import_sdk3.defaultIndexerEndpoint)("PRODUCTION");
243
+ const client = new import_sdk3.IndexerClient(endpoint);
244
+ indexerService = new import_sdk3.IndexerDepositService(client);
245
+ }
246
+ return indexerService;
247
+ }
248
+ function toBigInt(value) {
249
+ try {
250
+ return BigInt(value || "0");
251
+ } catch {
252
+ return 0n;
253
+ }
254
+ }
255
+ function toUsdc(value) {
256
+ return Number((0, import_viem.formatUnits)(toBigInt(value), 6));
257
+ }
258
+ function resolveStatus(deposit) {
259
+ if (deposit.status === "CLOSED") return "closed";
260
+ if (toBigInt(deposit.remainingDeposits) === 0n) return "empty";
261
+ return "active";
262
+ }
263
+ function resolveMethodNames(hashes) {
264
+ const names = /* @__PURE__ */ new Set();
265
+ for (const { paymentMethodHash } of hashes) {
266
+ const normalized = paymentMethodHash.toLowerCase();
267
+ for (const platform of Object.values(PLATFORMS)) {
268
+ const platformHashes = getPaymentMethodHashes(platform.id);
269
+ if (platformHashes.some((h) => h.toLowerCase() === normalized)) {
270
+ names.add(platform.name);
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ return Array.from(names);
276
+ }
277
+ function mapDeposit(d) {
278
+ const delegationState = (0, import_sdk3.classifyDelegationState)(
279
+ d.rateManagerId ?? void 0,
280
+ d.rateManagerAddress ?? void 0,
281
+ DELEGATE_RATE_MANAGER_ID,
282
+ RATE_MANAGER_REGISTRY_ADDRESS
283
+ );
284
+ return {
285
+ depositId: d.depositId,
286
+ compositeId: d.id,
287
+ txHash: d.txHash,
288
+ status: resolveStatus(d),
289
+ remainingUsdc: toUsdc(d.remainingDeposits),
290
+ outstandingUsdc: toUsdc(d.outstandingIntentAmount),
291
+ totalTakenUsdc: toUsdc(d.totalAmountTaken),
292
+ fulfilledIntents: d.fulfilledIntents ?? 0,
293
+ paymentMethods: resolveMethodNames(d.paymentMethods),
294
+ currencies: d.currencies.map((c) => {
295
+ const info = (0, import_sdk3.getCurrencyInfoFromHash)(c.currencyCode);
296
+ return info?.currencyCode ?? c.currencyCode;
297
+ }),
298
+ rateSource: d.currencies[0]?.rateSource || "unknown",
299
+ delegated: delegationState === "delegated_here",
300
+ escrowAddress: d.escrowAddress
301
+ };
302
+ }
303
+ function extractTxHash(result) {
304
+ if (typeof result === "string") return result;
305
+ if (result && typeof result === "object" && "hash" in result) return result.hash;
306
+ if (result && typeof result === "object" && "transactionHash" in result) return result.transactionHash;
307
+ throw new Error("Unexpected transaction result format");
308
+ }
309
+ async function deposits(walletAddress) {
310
+ const service = getIndexerService();
311
+ const raw = await service.fetchDepositsWithRelations(
312
+ { depositor: walletAddress },
313
+ { limit: 100 }
314
+ );
315
+ const statusOrder = { active: 0, empty: 1, closed: 2 };
316
+ return (raw || []).map(mapDeposit).sort((a, b) => {
317
+ const diff = statusOrder[a.status] - statusOrder[b.status];
318
+ if (diff !== 0) return diff;
319
+ return Number(BigInt(b.depositId) - BigInt(a.depositId));
320
+ });
321
+ }
322
+ async function close(walletClient, depositId, escrowAddress) {
323
+ const client = createSdkClient(walletClient);
324
+ const result = await client.withdrawDeposit({
325
+ depositId: BigInt(depositId),
326
+ escrowAddress: escrowAddress || ESCROW_ADDRESS,
327
+ txOverrides: { referrer: [REFERRER] }
328
+ });
329
+ return extractTxHash(result);
330
+ }
331
+
427
332
  // src/deposit.ts
428
333
  function usdcToUnits(amount) {
429
- return (0, import_viem.parseUnits)(amount, 6);
334
+ return (0, import_viem2.parseUnits)(amount, 6);
430
335
  }
431
336
  var DEPOSIT_RECEIVED_ABI = [
432
337
  {
@@ -450,7 +355,7 @@ function extractDepositIdFromLogs(logs) {
450
355
  for (const log of logs) {
451
356
  if (!log?.topics?.length || !log.data) continue;
452
357
  try {
453
- const decoded = (0, import_viem.decodeEventLog)({
358
+ const decoded = (0, import_viem2.decodeEventLog)({
454
359
  abi: DEPOSIT_RECEIVED_ABI,
455
360
  data: log.data,
456
361
  topics: log.topics
@@ -492,7 +397,7 @@ function attachOracleConfig(entries, conversionRates) {
492
397
  (group, gi) => group.map((entry, ci) => {
493
398
  const currency = conversionRates[gi]?.[ci]?.currency;
494
399
  if (!currency) return entry;
495
- const oracleConfig = (0, import_sdk3.getSpreadOracleConfig)(currency);
400
+ const oracleConfig = (0, import_sdk4.getSpreadOracleConfig)(currency);
496
401
  if (!oracleConfig) return entry;
497
402
  return {
498
403
  ...entry,
@@ -506,8 +411,14 @@ function attachOracleConfig(entries, conversionRates) {
506
411
  })
507
412
  );
508
413
  }
414
+ function extractTxHash2(result) {
415
+ if (typeof result === "string") return result;
416
+ if (result && typeof result === "object" && "hash" in result) return result.hash;
417
+ if (result && typeof result === "object" && "transactionHash" in result) return result.transactionHash;
418
+ throw new Error("Unexpected transaction result format");
419
+ }
509
420
  function createSdkClient(walletClient) {
510
- return new import_sdk3.OfframpClient({
421
+ return new import_sdk4.OfframpClient({
511
422
  walletClient,
512
423
  chainId: BASE_CHAIN_ID,
513
424
  runtimeEnv: RUNTIME_ENV,
@@ -515,34 +426,90 @@ function createSdkClient(walletClient) {
515
426
  baseApiUrl: API_BASE_URL
516
427
  });
517
428
  }
518
- async function createOfframpDeposit(walletClient, params, onProgress) {
429
+ async function findUndelegatedDeposit(walletAddress) {
430
+ try {
431
+ const service = getIndexerService();
432
+ const raw = await service.fetchDepositsWithRelations(
433
+ { depositor: walletAddress },
434
+ { limit: 50 }
435
+ );
436
+ const escrowLower = ESCROW_ADDRESS.toLowerCase();
437
+ const undelegated = (raw || []).filter((d) => {
438
+ if (d.status === "CLOSED") return false;
439
+ if (d.escrowAddress.toLowerCase() !== escrowLower) return false;
440
+ const remaining = (() => {
441
+ try {
442
+ return BigInt(d.remainingDeposits || "0");
443
+ } catch {
444
+ return 0n;
445
+ }
446
+ })();
447
+ if (remaining === 0n) return false;
448
+ return d.rateManagerId !== DELEGATE_RATE_MANAGER_ID;
449
+ });
450
+ undelegated.sort((a, b) => Number(BigInt(b.depositId) - BigInt(a.depositId)));
451
+ return undelegated[0] ?? null;
452
+ } catch {
453
+ return null;
454
+ }
455
+ }
456
+ async function offramp(walletClient, params, onProgress) {
519
457
  const { amount, platform, currency, identifier } = params;
458
+ const platformId = platform.id;
459
+ const currencyCode = currency.code;
460
+ if (!walletClient.account?.address) {
461
+ throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
462
+ }
463
+ const walletAddress = walletClient.account.address;
520
464
  const amt = parseFloat(amount);
521
465
  if (!Number.isFinite(amt) || amt < MIN_DEPOSIT_USDC) {
522
466
  throw new OfframpError(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC`, "VALIDATION");
523
467
  }
524
- if (!isSupportedCurrency(platform, currency)) {
525
- throw new OfframpError(`${currency} is not supported on ${platform}`, "UNSUPPORTED");
468
+ if (!platform.currencies.includes(currencyCode)) {
469
+ throw new OfframpError(`${currencyCode} is not supported on ${platform.name}`, "UNSUPPORTED");
526
470
  }
527
- const validation = validateIdentifier(platform, identifier);
471
+ const validation = platform.validate(identifier);
528
472
  if (!validation.valid) {
529
473
  throw new OfframpError(validation.error || "Invalid identifier", "VALIDATION");
530
474
  }
531
475
  const normalizedIdentifier = validation.normalized;
532
- const methodHash = getPaymentMethodHash(platform);
476
+ const methodHash = getPaymentMethodHash(platformId);
533
477
  if (!methodHash) {
534
- throw new OfframpError(`${platform} is not currently supported`, "UNSUPPORTED");
478
+ throw new OfframpError(`${platform.name} is not currently supported`, "UNSUPPORTED");
535
479
  }
536
- if (!walletClient.account?.address) {
537
- throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
480
+ const existing = await findUndelegatedDeposit(walletAddress);
481
+ if (existing) {
482
+ onProgress?.({ step: "resuming", depositId: existing.depositId });
483
+ const client2 = createSdkClient(walletClient);
484
+ const txOverrides2 = { referrer: [REFERRER] };
485
+ try {
486
+ const result = await client2.setRateManager({
487
+ depositId: BigInt(existing.depositId),
488
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
489
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
490
+ escrowAddress: existing.escrowAddress,
491
+ txOverrides: txOverrides2
492
+ });
493
+ const txHash = extractTxHash2(result);
494
+ onProgress?.({ step: "done", txHash, depositId: existing.depositId });
495
+ return { depositId: existing.depositId, txHash, resumed: true };
496
+ } catch (err) {
497
+ if (isUserCancellation(err)) throw new OfframpError("User cancelled", "USER_CANCELLED", "resuming", err);
498
+ throw new OfframpError(
499
+ "Found undelegated deposit but delegation failed. Try again.",
500
+ "DELEGATION_FAILED",
501
+ "resuming",
502
+ err,
503
+ { depositId: existing.depositId, txHash: existing.txHash }
504
+ );
505
+ }
538
506
  }
539
- const walletAddress = walletClient.account.address;
540
507
  const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
541
508
  const amountUnits = usdcToUnits(truncatedAmount);
542
509
  const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
543
510
  const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
544
511
  try {
545
- const publicClient = (0, import_viem.createPublicClient)({ chain: import_chains.base, transport: (0, import_viem.http)(BASE_RPC_URL) });
512
+ const publicClient = (0, import_viem2.createPublicClient)({ chain: import_chains.base, transport: (0, import_viem2.http)(BASE_RPC_URL) });
546
513
  const balance = await publicClient.readContract({
547
514
  address: USDC_ADDRESS,
548
515
  abi: [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }],
@@ -550,11 +517,8 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
550
517
  args: [walletAddress]
551
518
  });
552
519
  if (balance < amountUnits) {
553
- const available = Number((0, import_viem.formatUnits)(balance, 6));
554
- throw new OfframpError(
555
- `Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`,
556
- "VALIDATION"
557
- );
520
+ const available = Number((0, import_viem2.formatUnits)(balance, 6));
521
+ throw new OfframpError(`Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`, "VALIDATION");
558
522
  }
559
523
  } catch (err) {
560
524
  if (err instanceof OfframpError) throw err;
@@ -578,17 +542,17 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
578
542
  onProgress?.({ step: "registering" });
579
543
  let hashedOnchainId;
580
544
  try {
581
- const canonicalName = platform.startsWith("zelle") ? "zelle" : platform;
582
- const depositData = buildDepositData(platform, normalizedIdentifier);
545
+ const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
546
+ const depositData = buildDepositData(platformId, normalizedIdentifier);
583
547
  hashedOnchainId = await registerPayeeDetails(canonicalName, depositData);
584
548
  } catch (err) {
585
549
  throw new OfframpError("Payee registration failed", "REGISTRATION_FAILED", "registering", err);
586
550
  }
587
551
  onProgress?.({ step: "depositing" });
588
552
  const conversionRates = [
589
- [{ currency, conversionRate: "1" }]
553
+ [{ currency: currencyCode, conversionRate: "1" }]
590
554
  ];
591
- const baseCurrenciesOverride = (0, import_sdk3.mapConversionRatesToOnchainMinRate)(conversionRates, 1);
555
+ const baseCurrenciesOverride = (0, import_sdk4.mapConversionRatesToOnchainMinRate)(conversionRates, 1);
592
556
  const currenciesOverride = attachOracleConfig(baseCurrenciesOverride, conversionRates);
593
557
  let hash;
594
558
  try {
@@ -597,8 +561,8 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
597
561
  amount: amountUnits,
598
562
  retainOnEmpty: false,
599
563
  intentAmountRange: { min: minUnits, max: maxUnits },
600
- processorNames: [platform],
601
- depositData: [buildDepositData(platform, normalizedIdentifier)],
564
+ processorNames: [platformId],
565
+ depositData: [buildDepositData(platformId, normalizedIdentifier)],
602
566
  conversionRates,
603
567
  paymentMethodsOverride: [methodHash],
604
568
  paymentMethodDataOverride: [{
@@ -628,29 +592,27 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
628
592
  }
629
593
  }
630
594
  if (!depositId) {
631
- {
632
- let delay = INDEXER_INITIAL_DELAY_MS;
633
- for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
634
- try {
635
- const deposits = await client.indexer.getDepositsWithRelations(
636
- { depositor: walletAddress },
637
- { limit: 25 }
638
- );
639
- const hit = deposits.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
640
- if (hit) {
641
- depositId = String(hit.depositId);
642
- break;
643
- }
644
- } catch {
595
+ let delay = INDEXER_INITIAL_DELAY_MS;
596
+ for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
597
+ try {
598
+ const deps = await client.indexer.getDepositsWithRelations(
599
+ { depositor: walletAddress },
600
+ { limit: 25 }
601
+ );
602
+ const hit = deps.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
603
+ if (hit) {
604
+ depositId = String(hit.depositId);
605
+ break;
645
606
  }
646
- await new Promise((r) => setTimeout(r, delay));
647
- delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
607
+ } catch {
648
608
  }
609
+ await new Promise((r) => setTimeout(r, delay));
610
+ delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
649
611
  }
650
612
  }
651
613
  if (!depositId) {
652
614
  throw new OfframpError(
653
- "Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Use the transaction hash to locate your deposit.",
615
+ "Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Call offramp() again to resume delegation.",
654
616
  "CONFIRMATION_FAILED",
655
617
  "confirming",
656
618
  void 0,
@@ -671,7 +633,7 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
671
633
  throw new OfframpError("User cancelled delegation", "USER_CANCELLED", "delegating", delegationError, { txHash: hash, depositId });
672
634
  }
673
635
  throw new OfframpError(
674
- "Deposit created but delegation failed. Visit usdctofiat.xyz to manage your deposit manually.",
636
+ "Deposit created but delegation failed. Call offramp() again to resume delegation.",
675
637
  "DELEGATION_FAILED",
676
638
  "delegating",
677
639
  delegationError,
@@ -679,198 +641,31 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
679
641
  );
680
642
  }
681
643
  onProgress?.({ step: "done", txHash: hash, depositId });
682
- return { depositId, txHash: hash };
644
+ return { depositId, txHash: hash, resumed: false };
683
645
  }
684
646
 
685
- // src/client.ts
686
- var indexerClient = null;
687
- function getIndexerService() {
688
- if (!indexerClient) {
689
- const endpoint = (0, import_sdk4.defaultIndexerEndpoint)("PRODUCTION");
690
- const client = new import_sdk4.IndexerClient(endpoint);
691
- indexerClient = new import_sdk4.IndexerDepositService(client);
692
- }
693
- return indexerClient;
694
- }
695
- function toBigInt(value) {
696
- try {
697
- return BigInt(value || "0");
698
- } catch {
699
- return 0n;
700
- }
701
- }
702
- function toUsdc(value) {
703
- return Number((0, import_viem2.formatUnits)(toBigInt(value), 6));
704
- }
705
- function resolveStatus(deposit) {
706
- if (deposit.status === "CLOSED") return "closed";
707
- if (toBigInt(deposit.remainingDeposits) === 0n) return "empty";
708
- return "active";
709
- }
710
- function resolveMethodNames(hashes) {
711
- const names = /* @__PURE__ */ new Set();
712
- for (const { paymentMethodHash } of hashes) {
713
- const normalized = paymentMethodHash.toLowerCase();
714
- for (const platform of PLATFORMS) {
715
- const platformHashes = getPaymentMethodHashes(platform);
716
- if (platformHashes.some((h) => h.toLowerCase() === normalized)) {
717
- names.add(getPlatformConfig(platform).name);
718
- break;
719
- }
720
- }
721
- }
722
- return Array.from(names);
723
- }
724
- function mapDeposit(d) {
725
- const delegationState = (0, import_sdk4.classifyDelegationState)(
726
- d.rateManagerId ?? void 0,
727
- d.rateManagerAddress ?? void 0,
728
- DELEGATE_RATE_MANAGER_ID,
729
- RATE_MANAGER_REGISTRY_ADDRESS
730
- );
731
- return {
732
- depositId: d.depositId,
733
- compositeId: d.id,
734
- txHash: d.txHash,
735
- status: resolveStatus(d),
736
- remainingUsdc: toUsdc(d.remainingDeposits),
737
- outstandingUsdc: toUsdc(d.outstandingIntentAmount),
738
- totalTakenUsdc: toUsdc(d.totalAmountTaken),
739
- fulfilledIntents: d.fulfilledIntents ?? 0,
740
- paymentMethods: resolveMethodNames(d.paymentMethods),
741
- currencies: d.currencies.map((c) => {
742
- const info = (0, import_sdk4.getCurrencyInfoFromHash)(c.currencyCode);
743
- return info?.currencyCode ?? c.currencyCode;
744
- }),
745
- rateSource: d.currencies[0]?.rateSource || "unknown",
746
- delegated: delegationState === "delegated_here",
747
- escrowAddress: d.escrowAddress
748
- };
749
- }
750
- function extractTxHash(result) {
751
- if (typeof result === "string") return result;
752
- if (result && typeof result === "object" && "hash" in result) return result.hash;
753
- if (result && typeof result === "object" && "transactionHash" in result) return result.transactionHash;
754
- throw new Error("Unexpected transaction result format");
755
- }
756
- var Offramp = class {
757
- /**
758
- * Create an offramp deposit: approve USDC, register payee, create deposit,
759
- * confirm on-chain, and delegate to the vault. All in one call.
760
- *
761
- * @param walletClient - viem WalletClient with an account
762
- * @param params - Deposit parameters (amount, platform, currency, identifier)
763
- * @param onProgress - Optional callback for step-by-step progress updates
764
- */
765
- async createDeposit(walletClient, params, onProgress) {
766
- return createOfframpDeposit(walletClient, params, onProgress);
767
- }
768
- /**
769
- * Fetch all deposits for a wallet address. Read-only, no wallet needed.
770
- * Returns active, empty, and closed deposits sorted by status then recency.
771
- */
772
- async getDeposits(walletAddress) {
773
- const service = getIndexerService();
774
- const raw = await service.fetchDepositsWithRelations(
775
- { depositor: walletAddress },
776
- { limit: 100 }
777
- );
778
- const statusOrder = { active: 0, empty: 1, closed: 2 };
779
- return (raw || []).map(mapDeposit).sort((a, b) => {
780
- const diff = statusOrder[a.status] - statusOrder[b.status];
781
- if (diff !== 0) return diff;
782
- return Number(BigInt(b.depositId) - BigInt(a.depositId));
783
- });
784
- }
785
- /**
786
- * Find a deposit by its transaction hash. Useful for recovering from
787
- * CONFIRMATION_FAILED errors where you have a txHash but no depositId.
788
- *
789
- * @param walletAddress - The depositor's wallet address
790
- * @param txHash - The transaction hash from createDeposit
791
- */
792
- async getDepositByTxHash(walletAddress, txHash) {
793
- const deposits = await this.getDeposits(walletAddress);
794
- const normalized = txHash.toLowerCase();
795
- return deposits.find((d) => d.txHash?.toLowerCase() === normalized) ?? null;
796
- }
797
- /**
798
- * Delegate an existing deposit to the Delegate vault. Use this to:
799
- * - Retry after a DELEGATION_FAILED error
800
- * - Delegate a deposit created outside the SDK
801
- * - Delegate a deposit created via usdctofiat.xyz
802
- *
803
- * @param walletClient - viem WalletClient with the deposit owner's account
804
- * @param depositId - The numeric deposit ID (not the composite escrow_id format)
805
- */
806
- async delegateDeposit(walletClient, depositId, escrowAddress) {
807
- const client = createSdkClient(walletClient);
808
- const result = await client.setRateManager({
809
- depositId: BigInt(depositId),
810
- rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
811
- rateManagerId: DELEGATE_RATE_MANAGER_ID,
812
- escrowAddress: escrowAddress || ESCROW_ADDRESS,
813
- txOverrides: { referrer: [REFERRER] }
814
- });
815
- return extractTxHash(result);
816
- }
817
- /**
818
- * Withdraw remaining USDC and close a deposit.
819
- *
820
- * @param walletClient - viem WalletClient with the deposit owner's account
821
- * @param depositId - The numeric deposit ID (not the composite escrow_id format)
822
- */
823
- async withdrawDeposit(walletClient, depositId, escrowAddress) {
824
- const client = createSdkClient(walletClient);
825
- const result = await client.withdrawDeposit({
826
- depositId: BigInt(depositId),
827
- escrowAddress: escrowAddress || ESCROW_ADDRESS,
828
- txOverrides: { referrer: [REFERRER] }
829
- });
830
- return extractTxHash(result);
831
- }
832
- /** List available payment platforms with currencies, labels, and format requirements. */
833
- getPlatforms() {
834
- return getPlatforms();
835
- }
836
- /** Get supported currencies for a specific platform. */
837
- getCurrencies(platform) {
838
- return getCurrencies(platform);
839
- }
840
- /**
841
- * Get currency metadata for UI rendering.
842
- * @returns Symbol (€), full name (Euro), and country code (eu), or null if unsupported.
843
- */
844
- getCurrencyInfo(code) {
845
- const info = import_sdk4.currencyInfo[code];
846
- if (!info) return null;
847
- return {
647
+ // src/currencies.ts
648
+ var import_sdk5 = require("@zkp2p/sdk");
649
+ function buildCurrencies() {
650
+ const entries = {};
651
+ for (const [code, info] of Object.entries(import_sdk5.currencyInfo)) {
652
+ entries[code] = {
848
653
  code: info.currencyCode ?? code,
849
654
  name: info.currencyName ?? code,
850
655
  symbol: info.currencySymbol ?? code,
851
656
  countryCode: info.countryCode ?? ""
852
657
  };
853
658
  }
854
- /** Get all supported currencies with metadata. */
855
- getAllCurrencies() {
856
- return Object.keys(import_sdk4.currencyInfo).map((code) => {
857
- return this.getCurrencyInfo(code);
858
- }).filter(Boolean);
859
- }
860
- /**
861
- * Validate and normalize a payment identifier for a platform.
862
- * Strips leading @/$ characters, validates format (email, IBAN, etc).
863
- */
864
- validateIdentifier(platform, identifier) {
865
- return validateIdentifier(platform, identifier);
866
- }
867
- };
659
+ return entries;
660
+ }
661
+ var CURRENCIES = buildCurrencies();
868
662
  // Annotate the CommonJS export names for ESM import in node:
869
663
  0 && (module.exports = {
870
664
  CURRENCIES,
871
- Offramp,
872
665
  OfframpError,
873
- PAYMENT_PLATFORMS,
874
- SUPPORTED_CURRENCIES
666
+ PLATFORMS,
667
+ close,
668
+ deposits,
669
+ offramp
875
670
  });
876
671
  //# sourceMappingURL=index.cjs.map