@usdctofiat/offramp 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,89 @@ 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
+ return (raw || []).find((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
+ const hasOurVault = d.rateManagerId === DELEGATE_RATE_MANAGER_ID;
449
+ return !hasOurVault;
450
+ }) ?? null;
451
+ } catch {
452
+ return null;
453
+ }
454
+ }
455
+ async function offramp(walletClient, params, onProgress) {
519
456
  const { amount, platform, currency, identifier } = params;
457
+ const platformId = platform.id;
458
+ const currencyCode = currency.code;
459
+ if (!walletClient.account?.address) {
460
+ throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
461
+ }
462
+ const walletAddress = walletClient.account.address;
520
463
  const amt = parseFloat(amount);
521
464
  if (!Number.isFinite(amt) || amt < MIN_DEPOSIT_USDC) {
522
465
  throw new OfframpError(`Minimum deposit is ${MIN_DEPOSIT_USDC} USDC`, "VALIDATION");
523
466
  }
524
- if (!isSupportedCurrency(platform, currency)) {
525
- throw new OfframpError(`${currency} is not supported on ${platform}`, "UNSUPPORTED");
467
+ if (!platform.currencies.includes(currencyCode)) {
468
+ throw new OfframpError(`${currencyCode} is not supported on ${platform.name}`, "UNSUPPORTED");
526
469
  }
527
- const validation = validateIdentifier(platform, identifier);
470
+ const validation = platform.validate(identifier);
528
471
  if (!validation.valid) {
529
472
  throw new OfframpError(validation.error || "Invalid identifier", "VALIDATION");
530
473
  }
531
474
  const normalizedIdentifier = validation.normalized;
532
- const methodHash = getPaymentMethodHash(platform);
475
+ const methodHash = getPaymentMethodHash(platformId);
533
476
  if (!methodHash) {
534
- throw new OfframpError(`${platform} is not currently supported`, "UNSUPPORTED");
477
+ throw new OfframpError(`${platform.name} is not currently supported`, "UNSUPPORTED");
535
478
  }
536
- if (!walletClient.account?.address) {
537
- throw new OfframpError("Wallet client has no account. Connect a wallet first.", "VALIDATION");
479
+ const existing = await findUndelegatedDeposit(walletAddress);
480
+ if (existing) {
481
+ onProgress?.({ step: "resuming", depositId: existing.depositId });
482
+ const client2 = createSdkClient(walletClient);
483
+ const txOverrides2 = { referrer: [REFERRER] };
484
+ try {
485
+ const result = await client2.setRateManager({
486
+ depositId: BigInt(existing.depositId),
487
+ rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
488
+ rateManagerId: DELEGATE_RATE_MANAGER_ID,
489
+ escrowAddress: existing.escrowAddress,
490
+ txOverrides: txOverrides2
491
+ });
492
+ const txHash = extractTxHash2(result);
493
+ onProgress?.({ step: "done", txHash, depositId: existing.depositId });
494
+ return { depositId: existing.depositId, txHash, resumed: true };
495
+ } catch (err) {
496
+ if (isUserCancellation(err)) throw new OfframpError("User cancelled", "USER_CANCELLED", "resuming", err);
497
+ throw new OfframpError(
498
+ "Found undelegated deposit but delegation failed. Try again.",
499
+ "DELEGATION_FAILED",
500
+ "resuming",
501
+ err,
502
+ { depositId: existing.depositId, txHash: existing.txHash }
503
+ );
504
+ }
538
505
  }
539
- const walletAddress = walletClient.account.address;
540
506
  const truncatedAmount = amt.toFixed(6).replace(/\.?0+$/, "");
541
507
  const amountUnits = usdcToUnits(truncatedAmount);
542
508
  const minUnits = usdcToUnits(String(MIN_ORDER_USDC));
543
509
  const maxUnits = usdcToUnits(String(Math.min(amt, 2500)));
544
510
  try {
545
- const publicClient = (0, import_viem.createPublicClient)({ chain: import_chains.base, transport: (0, import_viem.http)(BASE_RPC_URL) });
511
+ const publicClient = (0, import_viem2.createPublicClient)({ chain: import_chains.base, transport: (0, import_viem2.http)(BASE_RPC_URL) });
546
512
  const balance = await publicClient.readContract({
547
513
  address: USDC_ADDRESS,
548
514
  abi: [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }],
@@ -550,11 +516,8 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
550
516
  args: [walletAddress]
551
517
  });
552
518
  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
- );
519
+ const available = Number((0, import_viem2.formatUnits)(balance, 6));
520
+ throw new OfframpError(`Insufficient USDC balance. Have ${available.toFixed(2)}, need ${truncatedAmount}.`, "VALIDATION");
558
521
  }
559
522
  } catch (err) {
560
523
  if (err instanceof OfframpError) throw err;
@@ -578,17 +541,17 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
578
541
  onProgress?.({ step: "registering" });
579
542
  let hashedOnchainId;
580
543
  try {
581
- const canonicalName = platform.startsWith("zelle") ? "zelle" : platform;
582
- const depositData = buildDepositData(platform, normalizedIdentifier);
544
+ const canonicalName = platformId.startsWith("zelle") ? "zelle" : platformId;
545
+ const depositData = buildDepositData(platformId, normalizedIdentifier);
583
546
  hashedOnchainId = await registerPayeeDetails(canonicalName, depositData);
584
547
  } catch (err) {
585
548
  throw new OfframpError("Payee registration failed", "REGISTRATION_FAILED", "registering", err);
586
549
  }
587
550
  onProgress?.({ step: "depositing" });
588
551
  const conversionRates = [
589
- [{ currency, conversionRate: "1" }]
552
+ [{ currency: currencyCode, conversionRate: "1" }]
590
553
  ];
591
- const baseCurrenciesOverride = (0, import_sdk3.mapConversionRatesToOnchainMinRate)(conversionRates, 1);
554
+ const baseCurrenciesOverride = (0, import_sdk4.mapConversionRatesToOnchainMinRate)(conversionRates, 1);
592
555
  const currenciesOverride = attachOracleConfig(baseCurrenciesOverride, conversionRates);
593
556
  let hash;
594
557
  try {
@@ -597,8 +560,8 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
597
560
  amount: amountUnits,
598
561
  retainOnEmpty: false,
599
562
  intentAmountRange: { min: minUnits, max: maxUnits },
600
- processorNames: [platform],
601
- depositData: [buildDepositData(platform, normalizedIdentifier)],
563
+ processorNames: [platformId],
564
+ depositData: [buildDepositData(platformId, normalizedIdentifier)],
602
565
  conversionRates,
603
566
  paymentMethodsOverride: [methodHash],
604
567
  paymentMethodDataOverride: [{
@@ -628,29 +591,27 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
628
591
  }
629
592
  }
630
593
  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 {
594
+ let delay = INDEXER_INITIAL_DELAY_MS;
595
+ for (let attempt = 0; attempt < INDEXER_MAX_ATTEMPTS && !depositId; attempt++) {
596
+ try {
597
+ const deps = await client.indexer.getDepositsWithRelations(
598
+ { depositor: walletAddress },
599
+ { limit: 25 }
600
+ );
601
+ const hit = deps.find((d) => (d?.txHash || "").toLowerCase() === hash.toLowerCase());
602
+ if (hit) {
603
+ depositId = String(hit.depositId);
604
+ break;
645
605
  }
646
- await new Promise((r) => setTimeout(r, delay));
647
- delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
606
+ } catch {
648
607
  }
608
+ await new Promise((r) => setTimeout(r, delay));
609
+ delay = Math.min(INDEXER_MAX_DELAY_MS, Math.floor(delay * 1.7));
649
610
  }
650
611
  }
651
612
  if (!depositId) {
652
613
  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.",
614
+ "Deposit created on-chain but could not confirm deposit ID. Your funds are safe. Call offramp() again to resume delegation.",
654
615
  "CONFIRMATION_FAILED",
655
616
  "confirming",
656
617
  void 0,
@@ -671,7 +632,7 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
671
632
  throw new OfframpError("User cancelled delegation", "USER_CANCELLED", "delegating", delegationError, { txHash: hash, depositId });
672
633
  }
673
634
  throw new OfframpError(
674
- "Deposit created but delegation failed. Visit usdctofiat.xyz to manage your deposit manually.",
635
+ "Deposit created but delegation failed. Call offramp() again to resume delegation.",
675
636
  "DELEGATION_FAILED",
676
637
  "delegating",
677
638
  delegationError,
@@ -679,197 +640,31 @@ async function createOfframpDeposit(walletClient, params, onProgress) {
679
640
  );
680
641
  }
681
642
  onProgress?.({ step: "done", txHash: hash, depositId });
682
- return { depositId, txHash: hash };
643
+ return { depositId, txHash: hash, resumed: false };
683
644
  }
684
645
 
685
- // src/client.ts
686
- var indexerClient = null;
687
- function getIndexerService() {
688
- if (!indexerClient) {
689
- const client = new import_sdk4.IndexerClient(`${API_BASE_URL.replace(/\/+$/, "")}`);
690
- indexerClient = new import_sdk4.IndexerDepositService(client);
691
- }
692
- return indexerClient;
693
- }
694
- function toBigInt(value) {
695
- try {
696
- return BigInt(value || "0");
697
- } catch {
698
- return 0n;
699
- }
700
- }
701
- function toUsdc(value) {
702
- return Number((0, import_viem2.formatUnits)(toBigInt(value), 6));
703
- }
704
- function resolveStatus(deposit) {
705
- if (deposit.status === "CLOSED") return "closed";
706
- if (toBigInt(deposit.remainingDeposits) === 0n) return "empty";
707
- return "active";
708
- }
709
- function resolveMethodNames(hashes) {
710
- const names = /* @__PURE__ */ new Set();
711
- for (const { paymentMethodHash } of hashes) {
712
- const normalized = paymentMethodHash.toLowerCase();
713
- for (const platform of PLATFORMS) {
714
- const platformHashes = getPaymentMethodHashes(platform);
715
- if (platformHashes.some((h) => h.toLowerCase() === normalized)) {
716
- names.add(getPlatformConfig(platform).name);
717
- break;
718
- }
719
- }
720
- }
721
- return Array.from(names);
722
- }
723
- function mapDeposit(d) {
724
- const delegationState = (0, import_sdk4.classifyDelegationState)(
725
- d.rateManagerId ?? void 0,
726
- d.rateManagerAddress ?? void 0,
727
- DELEGATE_RATE_MANAGER_ID,
728
- RATE_MANAGER_REGISTRY_ADDRESS
729
- );
730
- return {
731
- depositId: d.depositId,
732
- compositeId: d.id,
733
- txHash: d.txHash,
734
- status: resolveStatus(d),
735
- remainingUsdc: toUsdc(d.remainingDeposits),
736
- outstandingUsdc: toUsdc(d.outstandingIntentAmount),
737
- totalTakenUsdc: toUsdc(d.totalAmountTaken),
738
- fulfilledIntents: d.fulfilledIntents ?? 0,
739
- paymentMethods: resolveMethodNames(d.paymentMethods),
740
- currencies: d.currencies.map((c) => {
741
- const info = (0, import_sdk4.getCurrencyInfoFromHash)(c.currencyCode);
742
- return info?.currencyCode ?? c.currencyCode;
743
- }),
744
- rateSource: d.currencies[0]?.rateSource || "unknown",
745
- delegated: delegationState === "delegated_here",
746
- escrowAddress: d.escrowAddress
747
- };
748
- }
749
- function extractTxHash(result) {
750
- if (typeof result === "string") return result;
751
- if (result && typeof result === "object" && "hash" in result) return result.hash;
752
- if (result && typeof result === "object" && "transactionHash" in result) return result.transactionHash;
753
- throw new Error("Unexpected transaction result format");
754
- }
755
- var Offramp = class {
756
- /**
757
- * Create an offramp deposit: approve USDC, register payee, create deposit,
758
- * confirm on-chain, and delegate to the vault. All in one call.
759
- *
760
- * @param walletClient - viem WalletClient with an account
761
- * @param params - Deposit parameters (amount, platform, currency, identifier)
762
- * @param onProgress - Optional callback for step-by-step progress updates
763
- */
764
- async createDeposit(walletClient, params, onProgress) {
765
- return createOfframpDeposit(walletClient, params, onProgress);
766
- }
767
- /**
768
- * Fetch all deposits for a wallet address. Read-only, no wallet needed.
769
- * Returns active, empty, and closed deposits sorted by status then recency.
770
- */
771
- async getDeposits(walletAddress) {
772
- const service = getIndexerService();
773
- const raw = await service.fetchDepositsWithRelations(
774
- { depositor: walletAddress },
775
- { limit: 100 }
776
- );
777
- const statusOrder = { active: 0, empty: 1, closed: 2 };
778
- return (raw || []).map(mapDeposit).sort((a, b) => {
779
- const diff = statusOrder[a.status] - statusOrder[b.status];
780
- if (diff !== 0) return diff;
781
- return Number(BigInt(b.depositId) - BigInt(a.depositId));
782
- });
783
- }
784
- /**
785
- * Find a deposit by its transaction hash. Useful for recovering from
786
- * CONFIRMATION_FAILED errors where you have a txHash but no depositId.
787
- *
788
- * @param walletAddress - The depositor's wallet address
789
- * @param txHash - The transaction hash from createDeposit
790
- */
791
- async getDepositByTxHash(walletAddress, txHash) {
792
- const deposits = await this.getDeposits(walletAddress);
793
- const normalized = txHash.toLowerCase();
794
- return deposits.find((d) => d.txHash?.toLowerCase() === normalized) ?? null;
795
- }
796
- /**
797
- * Delegate an existing deposit to the Delegate vault. Use this to:
798
- * - Retry after a DELEGATION_FAILED error
799
- * - Delegate a deposit created outside the SDK
800
- * - Delegate a deposit created via usdctofiat.xyz
801
- *
802
- * @param walletClient - viem WalletClient with the deposit owner's account
803
- * @param depositId - The numeric deposit ID (not the composite escrow_id format)
804
- */
805
- async delegateDeposit(walletClient, depositId, escrowAddress) {
806
- const client = createSdkClient(walletClient);
807
- const result = await client.setRateManager({
808
- depositId: BigInt(depositId),
809
- rateManagerAddress: RATE_MANAGER_REGISTRY_ADDRESS,
810
- rateManagerId: DELEGATE_RATE_MANAGER_ID,
811
- escrowAddress: escrowAddress || ESCROW_ADDRESS,
812
- txOverrides: { referrer: [REFERRER] }
813
- });
814
- return extractTxHash(result);
815
- }
816
- /**
817
- * Withdraw remaining USDC and close a deposit.
818
- *
819
- * @param walletClient - viem WalletClient with the deposit owner's account
820
- * @param depositId - The numeric deposit ID (not the composite escrow_id format)
821
- */
822
- async withdrawDeposit(walletClient, depositId, escrowAddress) {
823
- const client = createSdkClient(walletClient);
824
- const result = await client.withdrawDeposit({
825
- depositId: BigInt(depositId),
826
- escrowAddress: escrowAddress || ESCROW_ADDRESS,
827
- txOverrides: { referrer: [REFERRER] }
828
- });
829
- return extractTxHash(result);
830
- }
831
- /** List available payment platforms with currencies, labels, and format requirements. */
832
- getPlatforms() {
833
- return getPlatforms();
834
- }
835
- /** Get supported currencies for a specific platform. */
836
- getCurrencies(platform) {
837
- return getCurrencies(platform);
838
- }
839
- /**
840
- * Get currency metadata for UI rendering.
841
- * @returns Symbol (€), full name (Euro), and country code (eu), or null if unsupported.
842
- */
843
- getCurrencyInfo(code) {
844
- const info = import_sdk4.currencyInfo[code];
845
- if (!info) return null;
846
- return {
646
+ // src/currencies.ts
647
+ var import_sdk5 = require("@zkp2p/sdk");
648
+ function buildCurrencies() {
649
+ const entries = {};
650
+ for (const [code, info] of Object.entries(import_sdk5.currencyInfo)) {
651
+ entries[code] = {
847
652
  code: info.currencyCode ?? code,
848
653
  name: info.currencyName ?? code,
849
654
  symbol: info.currencySymbol ?? code,
850
655
  countryCode: info.countryCode ?? ""
851
656
  };
852
657
  }
853
- /** Get all supported currencies with metadata. */
854
- getAllCurrencies() {
855
- return Object.keys(import_sdk4.currencyInfo).map((code) => {
856
- return this.getCurrencyInfo(code);
857
- }).filter(Boolean);
858
- }
859
- /**
860
- * Validate and normalize a payment identifier for a platform.
861
- * Strips leading @/$ characters, validates format (email, IBAN, etc).
862
- */
863
- validateIdentifier(platform, identifier) {
864
- return validateIdentifier(platform, identifier);
865
- }
866
- };
658
+ return entries;
659
+ }
660
+ var CURRENCIES = buildCurrencies();
867
661
  // Annotate the CommonJS export names for ESM import in node:
868
662
  0 && (module.exports = {
869
663
  CURRENCIES,
870
- Offramp,
871
664
  OfframpError,
872
- PAYMENT_PLATFORMS,
873
- SUPPORTED_CURRENCIES
665
+ PLATFORMS,
666
+ close,
667
+ deposits,
668
+ offramp
874
669
  });
875
670
  //# sourceMappingURL=index.cjs.map