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