@provex/utils 1.0.16 → 1.2.3

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.
Files changed (122) hide show
  1. package/dist/src/chain.d.ts +436 -4
  2. package/dist/src/chain.js +80 -0
  3. package/dist/src/chain.test.d.ts +1 -0
  4. package/dist/src/chain.test.js +112 -0
  5. package/dist/src/contracts.d.ts +196 -20
  6. package/dist/src/contracts.js +245 -17
  7. package/dist/src/contracts.test.d.ts +1 -0
  8. package/dist/src/contracts.test.js +243 -0
  9. package/dist/src/conversionRates.d.ts +99 -8
  10. package/dist/src/conversionRates.js +74 -4
  11. package/dist/src/conversionRates.test.d.ts +1 -0
  12. package/dist/src/conversionRates.test.js +473 -0
  13. package/dist/src/currencies.d.ts +113 -9
  14. package/dist/src/currencies.js +119 -38
  15. package/dist/src/currencies.test.d.ts +1 -0
  16. package/dist/src/currencies.test.js +343 -0
  17. package/dist/src/data/countries.json +166 -166
  18. package/dist/src/deposit.d.ts +69 -1
  19. package/dist/src/deposit.js +52 -1
  20. package/dist/src/depositKeys.d.ts +154 -0
  21. package/dist/src/depositKeys.js +104 -0
  22. package/dist/src/depositKeys.test.d.ts +1 -0
  23. package/dist/src/depositKeys.test.js +231 -0
  24. package/dist/src/endpoints.d.ts +51 -0
  25. package/dist/src/endpoints.js +70 -0
  26. package/dist/src/endpoints.test.d.ts +1 -0
  27. package/dist/src/endpoints.test.js +54 -0
  28. package/dist/src/events.d.ts +40 -2
  29. package/dist/src/events.js +86 -10
  30. package/dist/src/events.test.d.ts +1 -0
  31. package/dist/src/events.test.js +111 -0
  32. package/dist/src/fees.d.ts +136 -0
  33. package/dist/src/fees.js +178 -0
  34. package/dist/src/fees.test.d.ts +1 -0
  35. package/dist/src/fees.test.js +165 -0
  36. package/dist/src/ids.d.ts +125 -22
  37. package/dist/src/ids.js +211 -61
  38. package/dist/src/ids.test.d.ts +1 -0
  39. package/dist/src/ids.test.js +607 -0
  40. package/dist/src/index.d.ts +30 -1
  41. package/dist/src/index.js +30 -1
  42. package/dist/src/input.d.ts +23 -4
  43. package/dist/src/input.js +23 -4
  44. package/dist/src/intent.d.ts +38 -2
  45. package/dist/src/intent.js +28 -1
  46. package/dist/src/intent.test.d.ts +1 -0
  47. package/dist/src/intent.test.js +119 -0
  48. package/dist/src/json.d.ts +38 -1
  49. package/dist/src/json.js +38 -2
  50. package/dist/src/normalization.d.ts +224 -0
  51. package/dist/src/normalization.js +119 -0
  52. package/dist/src/normalization.test.d.ts +1 -0
  53. package/dist/src/normalization.test.js +284 -0
  54. package/dist/src/participants.d.ts +19 -4
  55. package/dist/src/participants.js +19 -4
  56. package/dist/src/payment.d.ts +333 -16
  57. package/dist/src/payment.js +768 -58
  58. package/dist/src/payment.test.d.ts +1 -0
  59. package/dist/src/payment.test.js +272 -0
  60. package/dist/src/peer-types.d.ts +327 -0
  61. package/dist/src/peer-types.js +169 -0
  62. package/dist/src/profit.d.ts +26 -5
  63. package/dist/src/profit.js +31 -8
  64. package/dist/src/profit.test.d.ts +1 -0
  65. package/dist/src/profit.test.js +315 -0
  66. package/dist/src/reputation.d.ts +125 -0
  67. package/dist/src/reputation.js +188 -0
  68. package/dist/src/reputation.test.d.ts +1 -0
  69. package/dist/src/reputation.test.js +156 -0
  70. package/dist/src/sort.d.ts +49 -10
  71. package/dist/src/sort.js +40 -9
  72. package/dist/src/sort.test.d.ts +1 -0
  73. package/dist/src/sort.test.js +216 -0
  74. package/dist/src/stateKey.d.ts +35 -0
  75. package/dist/src/stateKey.js +36 -0
  76. package/dist/src/stats.d.ts +55 -1
  77. package/dist/src/stats.js +46 -1
  78. package/dist/src/stats.test.d.ts +1 -0
  79. package/dist/src/stats.test.js +104 -0
  80. package/dist/src/time.d.ts +59 -9
  81. package/dist/src/time.js +71 -15
  82. package/dist/src/time.test.d.ts +1 -0
  83. package/dist/src/time.test.js +231 -0
  84. package/dist/src/tokens.d.ts +11972 -145
  85. package/dist/src/tokens.js +98 -17
  86. package/dist/src/tokens.test.d.ts +1 -0
  87. package/dist/src/tokens.test.js +193 -0
  88. package/dist/src/units.d.ts +183 -0
  89. package/dist/src/units.js +206 -6
  90. package/dist/src/units.test.d.ts +1 -0
  91. package/dist/src/units.test.js +327 -0
  92. package/dist/src/zkp2p-types.d.ts +273 -4
  93. package/dist/src/zkp2p-types.js +169 -1
  94. package/dist/tsconfig.tsbuildinfo +1 -1
  95. package/package.json +116 -26
  96. package/scripts/convert-svg.ts +0 -22
  97. package/src/chain.ts +0 -27
  98. package/src/contracts.ts +0 -89
  99. package/src/conversionRates.ts +0 -356
  100. package/src/currencies.ts +0 -286
  101. package/src/data/countries.json +0 -1099
  102. package/src/deposit.ts +0 -2
  103. package/src/events.ts +0 -21
  104. package/src/flags/eur.svg +0 -4
  105. package/src/flags/usd.svg +0 -25
  106. package/src/flags.ts +0 -4
  107. package/src/global.d.ts +0 -9
  108. package/src/ids.ts +0 -137
  109. package/src/index.ts +0 -16
  110. package/src/input.ts +0 -35
  111. package/src/intent.ts +0 -15
  112. package/src/json.ts +0 -42
  113. package/src/participants.ts +0 -14
  114. package/src/payment.ts +0 -573
  115. package/src/profit.ts +0 -55
  116. package/src/sort.ts +0 -49
  117. package/src/stats.ts +0 -10
  118. package/src/time.ts +0 -123
  119. package/src/tokens.ts +0 -159
  120. package/src/units.ts +0 -120
  121. package/src/zkp2p-types.ts +0 -55
  122. package/tsconfig.json +0 -18
@@ -1,8 +1,113 @@
1
- import { getAddress, parseUnits, zeroAddress } from "viem";
1
+ /**
2
+ * @fileoverview Payment provider definitions and utilities for Provex protocol.
3
+ *
4
+ * This module is the single source of truth for all supported payment methods,
5
+ * including their contract IDs, icons, risk levels, and tier requirements.
6
+ *
7
+ * @remarks
8
+ * **Supported Payment Providers:**
9
+ * - US: Venmo, Zelle (Chase/BoFA/Citi), CashApp, PayPal, Chime
10
+ * - International: Revolut, Wise, Monzo, N26, Alipay, MercadoPago, RBC
11
+ *
12
+ * **Provider Identification:**
13
+ * - V2: Per-provider verifier contract addresses
14
+ * - V3: keccak256(providerKey) as bytes32 payment method ID
15
+ *
16
+ * **Risk Levels (Base chain only):**
17
+ * - Low risk (Revolut, Wise, Monzo, MercadoPago): 5x cap multiplier, no cooldown
18
+ * - Medium risk (Zelle): 1.5x cap multiplier, cooldown applies
19
+ * - High risk (Venmo, CashApp): 1x cap multiplier, cooldown applies
20
+ * - Highest risk (PayPal): 0.75x cap multiplier, requires Peer Plus tier
21
+ *
22
+ * @see {@link reputation} for tier system
23
+ * @see {@link currencies} for supported currencies per provider
24
+ */
25
+ import { getAddress, parseUnits, zeroAddress, keccak256, stringToHex, isAddress } from "viem";
26
+ /**
27
+ * Read an env var from Vite (browser) or Node, returning empty string if not set.
28
+ */
29
+ function readEnv(name) {
30
+ const viteVal = typeof import.meta !== 'undefined'
31
+ ? import.meta.env?.[`VITE_${name}`]
32
+ : undefined;
33
+ const nodeVal = typeof process !== 'undefined'
34
+ ? process.env?.[name] ?? process.env?.[`VITE_${name}`]
35
+ : undefined;
36
+ return viteVal ?? nodeVal ?? '';
37
+ }
38
+ /**
39
+ * Parse a comma-separated list of provider keys from an env var.
40
+ *
41
+ * VITE_DISABLED_PROVIDERS / DISABLED_PROVIDERS — providers to hide (takes precedence).
42
+ * VITE_ENABLED_PROVIDERS / ENABLED_PROVIDERS — providers to force-show (overrides `disabled: true` in config).
43
+ *
44
+ * Examples:
45
+ * VITE_DISABLED_PROVIDERS=paypal,n26 → hides PayPal and N26
46
+ * VITE_ENABLED_PROVIDERS=paypal → shows PayPal even if config has disabled:true
47
+ * (both set: disabled list wins)
48
+ *
49
+ * If neither is set, falls back to the `disabled` field on the config.
50
+ */
51
+ let _disabledSet = null;
52
+ let _enabledSet = null;
53
+ function parseProviderLists() {
54
+ if (_disabledSet !== null)
55
+ return;
56
+ const disabled = readEnv('DISABLED_PROVIDERS').toLowerCase().split(',').map(s => s.trim()).filter(Boolean);
57
+ const enabled = readEnv('ENABLED_PROVIDERS').toLowerCase().split(',').map(s => s.trim()).filter(Boolean);
58
+ _disabledSet = new Set(disabled);
59
+ _enabledSet = new Set(enabled);
60
+ }
61
+ /** Returns true if a provider should be hidden from user-facing flows. */
62
+ export function isProviderDisabled(key, configDisabled) {
63
+ parseProviderLists();
64
+ const k = key.toLowerCase();
65
+ // Explicit disable list takes priority
66
+ if (_disabledSet.has(k))
67
+ return true;
68
+ // Explicit enable list overrides config
69
+ if (_enabledSet.has(k))
70
+ return false;
71
+ // Fall back to config field
72
+ return configDisabled ?? false;
73
+ }
2
74
  import { base, pulsechain } from "viem/chains";
3
- import { contractsByChainId, getContracts } from "./contracts.js";
75
+ import { contracts } from "./contracts.js";
4
76
  import { formatUnits } from "./units.js";
5
77
  import { SUPPORTED_CURRENCIES } from "./currencies.js";
78
+ /**
79
+ * Generate a contract ID (keccak256 hash) for a provider key.
80
+ * Used to identify payment methods on-chain (e.g., 'venmo' → bytes32).
81
+ *
82
+ * @param key - The provider key (e.g., 'venmo', 'zelle')
83
+ * @returns The contract ID as a hex string
84
+ */
85
+ export const providerKeyToContractId = (key) => {
86
+ return keccak256(stringToHex(key));
87
+ };
88
+ /**
89
+ * Compute the payeeDetailsHash for a plaintext payment identifier.
90
+ * This is the on-chain hash stored in deposit verifier records.
91
+ *
92
+ * The depositor passes this hash when creating a deposit. Buyers look it up
93
+ * via an API (or custom storage) to get the plaintext back.
94
+ *
95
+ * Uses `keccak256(utf8bytes(identifier))` — matches the contract and API convention.
96
+ *
97
+ * @param identifier - Plaintext payment ID (e.g., '@myvenmo', 'user@email.com', '+1234567890')
98
+ * @returns The payeeDetailsHash as a hex string
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { computePayeeDetailsHash } from '@provex/utils/payment'
103
+ *
104
+ * const hash = computePayeeDetailsHash('@myvenmo')
105
+ * // Use in createDeposit({ paymentMethodData: [{ payeeDetails: hash, ... }] })
106
+ * ```
107
+ */
108
+ export const computePayeeDetailsHash = (identifier) => {
109
+ return keccak256(stringToHex(identifier));
110
+ };
6
111
  /**
7
112
  * Payment providers, including manual
8
113
  */
@@ -16,13 +121,76 @@ export const providers = {
16
121
  MANUAL: 'manual',
17
122
  PAYPAL: 'paypal',
18
123
  MONZO: 'monzo',
124
+ N26: 'n26',
125
+ ALIPAY: 'alipay',
126
+ CHIME: 'chime',
127
+ ROYALBANKCANADA: 'royalbankcanada',
128
+ IDFC: 'idfc',
19
129
  };
20
130
  /** Distinct keys for payment sub-providers (currently only used by Zelle) */
21
131
  export const subProviders = {
132
+ CHASE: 'zelle-chase',
133
+ BANK_OF_AMERICA: 'zelle-bofa',
134
+ CITI: 'zelle-citi',
135
+ };
136
+ /**
137
+ * Extension platform keys - the platform identifiers used by the PeerAuth extension.
138
+ * For Zelle sub-providers, these are the bank names without the 'zelle-' prefix.
139
+ * Must match the folder names in @zkp2p/providers package.
140
+ */
141
+ export const extensionPlatforms = {
22
142
  CHASE: 'chase',
23
- BANK_OF_AMERICA: 'bank_of_america',
143
+ BOFA: 'bankofamerica',
24
144
  CITI: 'citi',
25
145
  };
146
+ /**
147
+ * Universal taker tier names for the reputation system.
148
+ * Use this const instead of string literals wherever a tier is referenced.
149
+ */
150
+ export const tiers = {
151
+ PEASANT: 'peasant',
152
+ NEUTRAL: 'neutral',
153
+ PLUS: 'plus',
154
+ PRO: 'pro',
155
+ PLATINUM: 'platinum',
156
+ /** Manually assigned VIP tier — cannot be reached via volume alone */
157
+ PRESIDENT: 'president',
158
+ };
159
+ /**
160
+ * Mapping from universal tier names to peer-specific tier names
161
+ */
162
+ export const tierToPeerTier = {
163
+ [tiers.PEASANT]: 'peer_peasant',
164
+ [tiers.NEUTRAL]: 'peer',
165
+ [tiers.PLUS]: 'peer_plus',
166
+ [tiers.PRO]: 'peer_pro',
167
+ [tiers.PLATINUM]: 'peer_platinum',
168
+ [tiers.PRESIDENT]: 'peer_president',
169
+ };
170
+ /**
171
+ * Mapping from peer tier names to universal tier names
172
+ */
173
+ export const peerTierToTier = {
174
+ peer_peasant: tiers.PEASANT,
175
+ peer: tiers.NEUTRAL,
176
+ peer_plus: tiers.PLUS,
177
+ peer_pro: tiers.PRO,
178
+ peer_platinum: tiers.PLATINUM,
179
+ peer_president: tiers.PRESIDENT,
180
+ };
181
+ /**
182
+ * Convert a peer tier name to universal tier name
183
+ * @param peerTier - peer tier name (e.g., 'peer_peasant')
184
+ * @returns Universal tier name
185
+ * @throws Error if peerTier is not a valid peer tier
186
+ */
187
+ export function fromPeerTier(peerTier) {
188
+ const tier = peerTierToTier[peerTier];
189
+ if (!tier) {
190
+ throw new Error(`Invalid peer tier: ${peerTier}`);
191
+ }
192
+ return tier;
193
+ }
26
194
  /** Provider key to validation key */
27
195
  export const providerKeyToValidationKey = {
28
196
  venmo: 'venmoUsername',
@@ -33,6 +201,11 @@ export const providerKeyToValidationKey = {
33
201
  mercadopago: 'cvu',
34
202
  paypal: 'paypalEmail',
35
203
  monzo: 'monzoMeUsername',
204
+ n26: 'n26Email',
205
+ alipay: 'alipayId',
206
+ chime: 'chimesign',
207
+ royalbankcanada: 'interacEmail',
208
+ idfc: 'idfcAccountNumber',
36
209
  };
37
210
  /** Data encoding for zelle urls */
38
211
  export const dataEncoding = (data) => {
@@ -48,25 +221,28 @@ export const defaultZelleSendLink = (options) => (!options.username ? null
48
221
  /** Global configuration for all supported sub-providers. Other modules can import this directly. */
49
222
  export const subProviderConfigs = {
50
223
  zelle: {
51
- chase: {
224
+ 'zelle-chase': {
52
225
  paymentMethod: 0,
53
226
  key: subProviders.CHASE,
54
227
  name: 'Chase',
55
228
  icon: 'simple-icons:chase',
56
- verifiers: (chain) => getContracts(chain).zelleChaseReclaimVerifier,
229
+ extensionPlatform: extensionPlatforms.CHASE,
230
+ v2Verifiers: (chain) => contracts.v2[chain].zelleChaseReclaimVerifier,
57
231
  sendLink: (opts) => {
58
232
  if (opts.qrCode) {
59
233
  return defaultZelleSendLink(opts);
60
234
  }
235
+ // return 'https://secure.chase.com/web/auth/dashboard#/dashboard/singleDoor/payments/activity;params=qp,sentactivity'
61
236
  return 'https://secure.chase.com/web/auth/dashboard#/dashboard/singleDoor/singleDoorController/index';
62
237
  },
63
238
  },
64
- bank_of_america: {
239
+ 'zelle-bofa': {
65
240
  paymentMethod: 1,
66
241
  key: subProviders.BANK_OF_AMERICA,
67
242
  name: 'Bank of America',
68
243
  icon: 'simple-icons:bankofamerica',
69
- verifiers: (chain) => getContracts(chain).zelleBoAReclaimVerifier,
244
+ extensionPlatform: extensionPlatforms.BOFA,
245
+ v2Verifiers: (chain) => contracts.v2[chain].zelleBoAReclaimVerifier,
70
246
  sendLink: (opts) => {
71
247
  if (opts.qrCode) {
72
248
  return `https://www.bankofamerica.com/movemoney`;
@@ -74,12 +250,13 @@ export const subProviderConfigs = {
74
250
  return 'https://www.bankofamerica.com/';
75
251
  },
76
252
  },
77
- citi: {
253
+ 'zelle-citi': {
78
254
  paymentMethod: 2,
79
255
  key: subProviders.CITI,
80
256
  name: 'Citi',
81
257
  icon: 'arcticons:citi-mobile',
82
- verifiers: (chain) => getContracts(chain).zelleCitiReclaimVerifier,
258
+ extensionPlatform: extensionPlatforms.CITI,
259
+ v2Verifiers: (chain) => contracts.v2[chain].zelleCitiReclaimVerifier,
83
260
  sendLink: (opts) => {
84
261
  if (opts.qrCode) {
85
262
  return defaultZelleSendLink(opts);
@@ -91,16 +268,37 @@ export const subProviderConfigs = {
91
268
  };
92
269
  /** Payment method icon mappings */
93
270
  export const providerConfigs = (chainId) => {
94
- const contracts = getContracts(chainId);
271
+ const v2Contracts = contracts.v2[chainId];
272
+ const v3Contracts = contracts.v3[chainId];
273
+ /**
274
+ * Get v2 verifier addresses for a payment method.
275
+ * Prioritizes v2-specific verifiers over v3 unified verifier.
276
+ * This ensures v2 historical data is correctly categorized even on chains with both v2 and v3 contracts.
277
+ */
278
+ const getV2Verifiers = (verifierKey) => {
279
+ // First check if v2 contracts have specific verifiers for this method
280
+ if (v2Contracts && verifierKey in v2Contracts) {
281
+ const value = v2Contracts[verifierKey];
282
+ return (Array.isArray(value) ? value : []);
283
+ }
284
+ // Fall back to v3 unified verifier (for v3-only chains or v3-only payment methods)
285
+ if (v3Contracts && 'unifiedPaymentVerifier' in v3Contracts) {
286
+ return v3Contracts.unifiedPaymentVerifier || [];
287
+ }
288
+ return [];
289
+ };
95
290
  return {
96
291
  venmo: {
97
292
  key: providers.VENMO,
293
+ contractId: providerKeyToContractId(providers.VENMO),
98
294
  icon: 'ion:logo-venmo',
99
295
  name: 'Venmo',
100
296
  usernameType: 'Venmo Username',
101
297
  validationKey: providerKeyToValidationKey.venmo,
102
298
  color: 'rgb(0, 116, 222)',
103
- verifiers: contracts.venmoReclaimVerifier,
299
+ chartColorLight: '#0074de',
300
+ chartColorDark: '#4da5ff',
301
+ v2Verifiers: getV2Verifiers('venmoReclaimVerifier'),
104
302
  limits: {
105
303
  min: 1.0,
106
304
  },
@@ -114,22 +312,26 @@ export const providerConfigs = (chainId) => {
114
312
  list: [
115
313
  SUPPORTED_CURRENCIES.USD.contractId,
116
314
  ],
315
+ // High risk - 1x multiplier, has cooldown
316
+ riskLevel: 'high',
317
+ capMultiplier: 1,
318
+ hasCooldown: true,
319
+ minimumTier: null,
117
320
  },
118
321
  zelle: {
119
322
  key: providers.ZELLE,
323
+ contractId: providerKeyToContractId(providers.ZELLE),
120
324
  icon: 'simple-icons:zelle',
121
325
  name: 'Zelle',
122
326
  color: '#6d1fd4',
123
327
  usernameType: 'Zelle Email',
124
328
  validationKey: providerKeyToValidationKey.zelle,
125
- verifiers: contracts.zelleBaseVerifier,
329
+ v2Verifiers: getV2Verifiers('zelleBaseVerifier'),
126
330
  limits: {
127
331
  min: 1.0,
128
332
  },
129
333
  subProviders: new Set([
130
- subProviderConfigs.zelle[subProviders.CHASE],
131
- subProviderConfigs.zelle[subProviders.BANK_OF_AMERICA],
132
- subProviderConfigs.zelle[subProviders.CITI],
334
+ ...Object.values(subProviderConfigs.zelle),
133
335
  ]),
134
336
  // Delegate to sub-provider specific link when provided, otherwise fall back to generic QR URL
135
337
  sendLink: (options) => {
@@ -141,15 +343,23 @@ export const providerConfigs = (chainId) => {
141
343
  list: [
142
344
  SUPPORTED_CURRENCIES.USD.contractId,
143
345
  ],
346
+ // Medium risk - 1.5x multiplier, has cooldown
347
+ riskLevel: 'medium',
348
+ capMultiplier: 1.5,
349
+ hasCooldown: true,
350
+ minimumTier: null,
144
351
  },
145
352
  cashapp: {
146
353
  key: providers.CASHAPP,
354
+ contractId: providerKeyToContractId(providers.CASHAPP),
147
355
  icon: 'simple-icons:cashapp',
148
356
  name: 'Cash App',
149
357
  color: 'rgba(0,224,19,1.0)',
358
+ chartColorLight: '#00e013',
359
+ chartColorDark: '#4dff6a',
150
360
  usernameType: 'CashTag',
151
361
  validationKey: providerKeyToValidationKey.cashapp,
152
- verifiers: contracts.cashappReclaimVerifier,
362
+ v2Verifiers: getV2Verifiers('cashappReclaimVerifier'),
153
363
  limits: {
154
364
  min: 1.0,
155
365
  },
@@ -161,15 +371,25 @@ export const providerConfigs = (chainId) => {
161
371
  const u = options.username.startsWith('$') ? options.username : `$${options.username}`;
162
372
  return `https://cash.app/qr/${u}?size=288&margin=0`;
163
373
  },
374
+ // High risk - 1x multiplier, has cooldown
375
+ riskLevel: 'high',
376
+ capMultiplier: 1,
377
+ hasCooldown: true,
378
+ minimumTier: null,
164
379
  },
165
380
  revolut: {
166
381
  key: providers.REVOLUT,
382
+ contractId: providerKeyToContractId(providers.REVOLUT),
167
383
  icon: 'simple-icons:revolut',
168
384
  name: 'Revolut',
169
385
  color: '#191c1f',
386
+ // Chart-friendly color to ensure distinction in stacked bar palette
387
+ chartColorLight: '#8b5cf6',
388
+ chartColorDark: '#c4b5fd',
389
+ backgroundColor: '#ffffff',
170
390
  usernameType: 'RevTag',
171
391
  validationKey: providerKeyToValidationKey.revolut,
172
- verifiers: contracts.revolutReclaimVerifier,
392
+ v2Verifiers: getV2Verifiers('revolutReclaimVerifier'),
173
393
  limits: {
174
394
  min: 1.0,
175
395
  },
@@ -179,29 +399,45 @@ export const providerConfigs = (chainId) => {
179
399
  SUPPORTED_CURRENCIES.USD.contractId,
180
400
  SUPPORTED_CURRENCIES.EUR.contractId,
181
401
  SUPPORTED_CURRENCIES.GBP.contractId,
402
+ SUPPORTED_CURRENCIES.SGD.contractId,
403
+ SUPPORTED_CURRENCIES.NZD.contractId,
182
404
  SUPPORTED_CURRENCIES.AUD.contractId,
183
405
  SUPPORTED_CURRENCIES.CAD.contractId,
184
406
  SUPPORTED_CURRENCIES.HKD.contractId,
185
407
  SUPPORTED_CURRENCIES.MXN.contractId,
186
- SUPPORTED_CURRENCIES.NZD.contractId,
187
- SUPPORTED_CURRENCIES.PLN.contractId,
188
408
  SUPPORTED_CURRENCIES.SAR.contractId,
189
- SUPPORTED_CURRENCIES.SGD.contractId,
190
- SUPPORTED_CURRENCIES.ZAR.contractId,
191
- SUPPORTED_CURRENCIES.CHF.contractId,
409
+ SUPPORTED_CURRENCIES.AED.contractId,
192
410
  SUPPORTED_CURRENCIES.THB.contractId,
193
411
  SUPPORTED_CURRENCIES.TRY.contractId,
194
- SUPPORTED_CURRENCIES.AED.contractId,
412
+ SUPPORTED_CURRENCIES.PLN.contractId,
413
+ SUPPORTED_CURRENCIES.CHF.contractId,
414
+ SUPPORTED_CURRENCIES.ZAR.contractId,
415
+ SUPPORTED_CURRENCIES.JPY.contractId,
416
+ SUPPORTED_CURRENCIES.CZK.contractId,
417
+ SUPPORTED_CURRENCIES.CNY.contractId,
418
+ SUPPORTED_CURRENCIES.DKK.contractId,
419
+ SUPPORTED_CURRENCIES.HUF.contractId,
420
+ SUPPORTED_CURRENCIES.NOK.contractId,
421
+ SUPPORTED_CURRENCIES.RON.contractId,
422
+ SUPPORTED_CURRENCIES.SEK.contractId,
195
423
  ],
424
+ // Low risk - 5x multiplier, no cooldown
425
+ riskLevel: 'low',
426
+ capMultiplier: 5,
427
+ hasCooldown: false,
428
+ minimumTier: null,
429
+ // Revolut sends amounts as integer cents (e.g. 5000 for $50.00)
430
+ amountInCents: true,
196
431
  },
197
432
  wise: {
198
433
  key: providers.WISE,
434
+ contractId: providerKeyToContractId(providers.WISE),
199
435
  icon: 'simple-icons:wise',
200
436
  name: 'Wise',
201
437
  color: '#9fe870',
202
438
  usernameType: 'WiseTag',
203
439
  validationKey: providerKeyToValidationKey.wise,
204
- verifiers: contracts.wiseReclaimVerifier,
440
+ v2Verifiers: getV2Verifiers('wiseReclaimVerifier'),
205
441
  limits: {
206
442
  min: 1.0,
207
443
  },
@@ -209,38 +445,54 @@ export const providerConfigs = (chainId) => {
209
445
  sendLink: (options) => `https://wise.com/pay/me/${options.username}`,
210
446
  list: [
211
447
  SUPPORTED_CURRENCIES.USD.contractId,
448
+ SUPPORTED_CURRENCIES.CNY.contractId,
212
449
  SUPPORTED_CURRENCIES.EUR.contractId,
213
450
  SUPPORTED_CURRENCIES.GBP.contractId,
214
451
  SUPPORTED_CURRENCIES.AUD.contractId,
452
+ SUPPORTED_CURRENCIES.NZD.contractId,
215
453
  SUPPORTED_CURRENCIES.CAD.contractId,
216
- SUPPORTED_CURRENCIES.CNY.contractId,
217
- SUPPORTED_CURRENCIES.HKD.contractId,
218
- SUPPORTED_CURRENCIES.IDR.contractId,
454
+ SUPPORTED_CURRENCIES.AED.contractId,
455
+ SUPPORTED_CURRENCIES.CHF.contractId,
456
+ SUPPORTED_CURRENCIES.ZAR.contractId,
457
+ SUPPORTED_CURRENCIES.SGD.contractId,
219
458
  SUPPORTED_CURRENCIES.ILS.contractId,
459
+ SUPPORTED_CURRENCIES.HKD.contractId,
220
460
  SUPPORTED_CURRENCIES.JPY.contractId,
461
+ SUPPORTED_CURRENCIES.PLN.contractId,
462
+ SUPPORTED_CURRENCIES.TRY.contractId,
463
+ SUPPORTED_CURRENCIES.IDR.contractId,
221
464
  SUPPORTED_CURRENCIES.KES.contractId,
222
465
  SUPPORTED_CURRENCIES.MYR.contractId,
223
466
  SUPPORTED_CURRENCIES.MXN.contractId,
224
- SUPPORTED_CURRENCIES.NZD.contractId,
225
- SUPPORTED_CURRENCIES.PLN.contractId,
226
- SUPPORTED_CURRENCIES.SGD.contractId,
227
- SUPPORTED_CURRENCIES.ZAR.contractId,
228
- SUPPORTED_CURRENCIES.CHF.contractId,
229
467
  SUPPORTED_CURRENCIES.THB.contractId,
230
- SUPPORTED_CURRENCIES.TRY.contractId,
231
- SUPPORTED_CURRENCIES.UGX.contractId,
232
- SUPPORTED_CURRENCIES.AED.contractId,
233
468
  SUPPORTED_CURRENCIES.VND.contractId,
469
+ SUPPORTED_CURRENCIES.UGX.contractId,
470
+ SUPPORTED_CURRENCIES.CZK.contractId,
471
+ SUPPORTED_CURRENCIES.DKK.contractId,
472
+ SUPPORTED_CURRENCIES.HUF.contractId,
473
+ SUPPORTED_CURRENCIES.INR.contractId,
474
+ SUPPORTED_CURRENCIES.NOK.contractId,
475
+ SUPPORTED_CURRENCIES.PHP.contractId,
476
+ SUPPORTED_CURRENCIES.RON.contractId,
477
+ SUPPORTED_CURRENCIES.SEK.contractId,
234
478
  ],
479
+ // Low risk - 5x multiplier, no cooldown
480
+ riskLevel: 'low',
481
+ capMultiplier: 5,
482
+ hasCooldown: false,
483
+ minimumTier: null,
484
+ depositRequiresApproval: true,
485
+ approvalFormUrl: 'https://forms.gle/9N36Wd4iKS8PwLyFA',
235
486
  },
236
487
  mercadopago: {
237
488
  key: providers.MERCADOPAGO,
489
+ contractId: providerKeyToContractId(providers.MERCADOPAGO),
238
490
  icon: 'simple-icons:mercadopago',
239
491
  name: 'Mercado Pago',
240
492
  usernameType: 'Mercado Pago / Bank CVU',
241
493
  color: '#ffe600',
242
494
  validationKey: providerKeyToValidationKey.mercadopago,
243
- verifiers: contracts.mercadopagoReclaimVerifier,
495
+ v2Verifiers: getV2Verifiers('mercadopagoReclaimVerifier'),
244
496
  limits: {
245
497
  min: 1.0,
246
498
  },
@@ -249,19 +501,26 @@ export const providerConfigs = (chainId) => {
249
501
  list: [
250
502
  SUPPORTED_CURRENCIES.ARS.contractId,
251
503
  ],
504
+ // Low risk - instant settlement, no chargebacks (per peer.xyz docs)
505
+ riskLevel: 'low',
506
+ capMultiplier: 5,
507
+ hasCooldown: false,
508
+ minimumTier: null,
252
509
  },
253
510
  paypal: {
254
511
  key: providers.PAYPAL,
512
+ contractId: providerKeyToContractId(providers.PAYPAL),
255
513
  icon: 'simple-icons:paypal',
256
514
  name: 'PayPal',
257
515
  usernameType: 'PayPal Email',
258
516
  validationKey: providerKeyToValidationKey.paypal,
259
- verifiers: contracts.paypalReclaimVerifier,
517
+ v2Verifiers: getV2Verifiers('paypalReclaimVerifier'),
260
518
  limits: {
261
519
  min: 1.0,
262
520
  },
263
521
  subProviders: new Set(),
264
522
  color: '#0070BA',
523
+ chartColor: '#f59e0b',
265
524
  sendLink: (options) => {
266
525
  if (options.qrCode) {
267
526
  return `paypal://`;
@@ -277,14 +536,20 @@ export const providerConfigs = (chainId) => {
277
536
  SUPPORTED_CURRENCIES.CAD.contractId,
278
537
  SUPPORTED_CURRENCIES.SGD.contractId,
279
538
  ],
539
+ riskLevel: 'highest',
540
+ capMultiplier: 0.75,
541
+ hasCooldown: true,
542
+ minimumTier: tiers.PLUS,
543
+ disabled: true,
280
544
  },
281
545
  monzo: {
282
546
  key: providers.MONZO,
547
+ contractId: providerKeyToContractId(providers.MONZO),
283
548
  icon: 'simple-icons:monzo',
284
549
  name: 'Monzo',
285
550
  usernameType: 'Monzo Me Username',
286
551
  validationKey: providerKeyToValidationKey.monzo,
287
- verifiers: contracts.monzoReclaimVerifier,
552
+ v2Verifiers: getV2Verifiers('monzoReclaimVerifier'),
288
553
  limits: {
289
554
  min: 1.0,
290
555
  },
@@ -294,35 +559,281 @@ export const providerConfigs = (chainId) => {
294
559
  SUPPORTED_CURRENCIES.GBP.contractId,
295
560
  ],
296
561
  color: '#FC427B',
562
+ // Low risk - instant settlement, no chargebacks (per peer.xyz docs)
563
+ riskLevel: 'low',
564
+ capMultiplier: 5,
565
+ hasCooldown: false,
566
+ minimumTier: null,
567
+ },
568
+ n26: {
569
+ key: providers.N26,
570
+ contractId: providerKeyToContractId(providers.N26),
571
+ icon: 'simple-icons:n26',
572
+ name: 'N26',
573
+ usernameType: 'N26 Email',
574
+ validationKey: providerKeyToValidationKey.n26,
575
+ v2Verifiers: [],
576
+ limits: {
577
+ min: 1.0,
578
+ },
579
+ subProviders: new Set(),
580
+ sendLink: (options) => {
581
+ if (options.qrCode) {
582
+ return 'number26://';
583
+ }
584
+ return 'https://app.n26.com/';
585
+ },
586
+ list: [
587
+ SUPPORTED_CURRENCIES.EUR.contractId,
588
+ ],
589
+ color: '#088177',
590
+ riskLevel: 'medium',
591
+ capMultiplier: 1,
592
+ hasCooldown: true,
593
+ minimumTier: null,
594
+ disabled: true,
595
+ },
596
+ alipay: {
597
+ key: providers.ALIPAY,
598
+ contractId: providerKeyToContractId(providers.ALIPAY),
599
+ icon: 'simple-icons:alipay',
600
+ name: 'Alipay',
601
+ usernameType: 'Alipay ID',
602
+ validationKey: providerKeyToValidationKey.alipay,
603
+ v2Verifiers: [],
604
+ limits: {
605
+ min: 1.0,
606
+ },
607
+ subProviders: new Set(),
608
+ sendLink: (options) => {
609
+ if (options.qrCode) {
610
+ return 'alipays://';
611
+ }
612
+ return 'https://www.alipay.com/';
613
+ },
614
+ list: [
615
+ SUPPORTED_CURRENCIES.CNY.contractId,
616
+ ],
617
+ color: '#1677FF',
618
+ riskLevel: 'medium',
619
+ capMultiplier: 1,
620
+ hasCooldown: true,
621
+ minimumTier: null,
622
+ depositRequiresApproval: true,
623
+ approvalFormUrl: 'https://forms.gle/9N36Wd4iKS8PwLyFA?hl=zh-CN',
624
+ disabled: true,
625
+ },
626
+ chime: {
627
+ key: providers.CHIME,
628
+ contractId: providerKeyToContractId(providers.CHIME),
629
+ icon: 'arcticons:chime',
630
+ name: 'Chime',
631
+ usernameType: '$ChimeSign',
632
+ validationKey: providerKeyToValidationKey.chime,
633
+ v2Verifiers: [],
634
+ limits: {
635
+ min: 0.1,
636
+ },
637
+ subProviders: new Set(),
638
+ sendLink: (options) => {
639
+ if (options.qrCode) {
640
+ return `https://app.chime.com/link/qr?u=${options.username}`;
641
+ }
642
+ return 'https://app.chime.com/login';
643
+ },
644
+ list: [
645
+ SUPPORTED_CURRENCIES.USD.contractId,
646
+ ],
647
+ color: '#1EC677',
648
+ riskLevel: 'medium',
649
+ capMultiplier: 1,
650
+ hasCooldown: true,
651
+ minimumTier: null,
652
+ disabled: true,
653
+ },
654
+ royalbankcanada: {
655
+ key: providers.ROYALBANKCANADA,
656
+ contractId: providerKeyToContractId(providers.ROYALBANKCANADA),
657
+ icon: 'arcticons:rbcmobile',
658
+ name: 'Royal Bank of Canada',
659
+ usernameType: 'Interac Email',
660
+ validationKey: providerKeyToValidationKey.royalbankcanada,
661
+ v2Verifiers: [],
662
+ limits: {
663
+ min: 1.0,
664
+ },
665
+ subProviders: new Set(),
666
+ sendLink: () => 'https://www.rbcroyalbank.com/ways-to-bank/interac-e-transfer.html',
667
+ list: [
668
+ SUPPORTED_CURRENCIES.CAD.contractId,
669
+ ],
670
+ color: '#4A5568',
671
+ riskLevel: 'medium',
672
+ capMultiplier: 1,
673
+ hasCooldown: true,
674
+ minimumTier: null,
675
+ disabled: true,
676
+ },
677
+ idfc: {
678
+ key: providers.IDFC,
679
+ contractId: providerKeyToContractId(providers.IDFC),
680
+ icon: 'mdi:bank',
681
+ name: 'IDFC Bank',
682
+ usernameType: 'IDFC Account Number',
683
+ validationKey: providerKeyToValidationKey.idfc,
684
+ v2Verifiers: [],
685
+ limits: {
686
+ min: 1.0,
687
+ },
688
+ subProviders: new Set(),
689
+ sendLink: () => 'https://www.idfcfirstbank.com/',
690
+ list: [
691
+ SUPPORTED_CURRENCIES.INR.contractId,
692
+ ],
693
+ color: '#2D3748',
694
+ riskLevel: 'medium',
695
+ capMultiplier: 1,
696
+ hasCooldown: true,
697
+ minimumTier: null,
698
+ disabled: true,
297
699
  },
298
700
  manual: {
299
701
  key: providers.MANUAL,
702
+ contractId: providerKeyToContractId(providers.MANUAL),
300
703
  icon: 'mdi:help-circle',
301
- name: 'Manual Release',
704
+ name: 'Manual',
302
705
  color: 'gray',
303
706
  usernameType: '',
304
707
  validationKey: null,
305
- verifiers: [zeroAddress],
708
+ v2Verifiers: [zeroAddress],
306
709
  limits: {
307
710
  min: 0.0,
308
711
  },
309
712
  subProviders: new Set(),
310
713
  list: [],
714
+ // Manual verification - no reputation restrictions
715
+ riskLevel: 'low',
716
+ capMultiplier: 5,
717
+ hasCooldown: false,
718
+ minimumTier: null,
311
719
  },
312
720
  };
313
721
  };
314
- /** Payment verifier to key map */
315
- export const paymentVerifierToKey = (chainId) => (new Map(Object.values(chainId ? [chainId] : [...contractsByChainId.keys()]).flatMap(chainId => (Object.values(providerConfigs(chainId)).flatMap(config => {
316
- return config.verifiers.map(verifier => [getAddress(verifier), config.key]);
317
- })))));
318
- /** Provider key to verifiers map */
319
- export const providerKeyToVerifiersMap = (chainId) => (new Map(Object.entries(providerConfigs(chainId)).map(([key, config]) => [key, config.verifiers])));
722
+ /**
723
+ * Default platform risk configuration for providers not explicitly configured
724
+ * Assumes medium risk as a safe default
725
+ */
726
+ export const DEFAULT_PLATFORM_RISK = {
727
+ riskLevel: 'medium',
728
+ capMultiplier: 1,
729
+ hasCooldown: true,
730
+ minimumTier: null,
731
+ };
732
+ /**
733
+ * Get the risk configuration for a payment platform
734
+ * @param chainId - The chain ID
735
+ * @param providerKey - The provider key
736
+ * @returns Platform risk configuration
737
+ */
738
+ export function getPlatformRisk(chainId, providerKey) {
739
+ const config = providerConfigs(chainId)[providerKey];
740
+ return {
741
+ riskLevel: config?.riskLevel ?? DEFAULT_PLATFORM_RISK.riskLevel,
742
+ capMultiplier: config?.capMultiplier ?? DEFAULT_PLATFORM_RISK.capMultiplier,
743
+ hasCooldown: config?.hasCooldown ?? DEFAULT_PLATFORM_RISK.hasCooldown,
744
+ minimumTier: config?.minimumTier ?? DEFAULT_PLATFORM_RISK.minimumTier,
745
+ };
746
+ }
747
+ /**
748
+ * Payment verifier to key map
749
+ * Includes both main providers and flattened sub-providers
750
+ */
751
+ export const paymentVerifierToKey = (chainId) => {
752
+ const chainIds = chainId ? [chainId] : Object.keys(contracts.v2).map(Number);
753
+ const entries = chainIds.flatMap(cId => Object.values(providerConfigs(cId)).flatMap(config => [
754
+ // Main provider: V2 verifier addresses -> key
755
+ ...config.v2Verifiers.map(verifier => [getAddress(verifier), config.key]),
756
+ // Main provider: contract ID -> key
757
+ [config.contractId.toLowerCase(), config.key],
758
+ // Sub-providers
759
+ ...Array.from(config.subProviders).flatMap(subProvider => [
760
+ ...subProvider.v2Verifiers(cId).map(verifier => [getAddress(verifier), subProvider.key]),
761
+ [providerKeyToContractId(subProvider.key).toLowerCase(), subProvider.key]
762
+ ])
763
+ ]));
764
+ return new Map(entries);
765
+ };
766
+ /**
767
+ * Provider contractId to config map (reverse lookup by keccak256 hash)
768
+ * Includes both main providers and flattened sub-providers with their own contract IDs
769
+ * Note: Keys are stored as lowercase for case-insensitive lookup
770
+ */
771
+ export const providerByContractId = (chainId) => {
772
+ const configs = Object.values(providerConfigs(chainId));
773
+ const entries = [
774
+ // Main providers
775
+ ...configs.map(config => [config.contractId.toLowerCase(), config]),
776
+ // Sub-providers with synthetic configs
777
+ ...configs.flatMap(config => Array.from(config.subProviders).map(subProvider => {
778
+ const contractId = providerKeyToContractId(subProvider.key).toLowerCase();
779
+ const subConfig = {
780
+ ...config,
781
+ key: subProvider.key,
782
+ contractId: providerKeyToContractId(subProvider.key),
783
+ name: subProvider.name,
784
+ icon: subProvider.icon || config.icon,
785
+ color: subProvider.color || config.color,
786
+ usernameType: subProvider.usernameType || config.usernameType,
787
+ v2Verifiers: subProvider.v2Verifiers(chainId),
788
+ subProviders: new Set(),
789
+ };
790
+ return [contractId, subConfig];
791
+ }))
792
+ ];
793
+ return new Map(entries);
794
+ };
795
+ /**
796
+ * Get provider config by contractId (keccak256 hash)
797
+ * Supports both main providers and sub-providers (e.g., zelle-chase, zelle-bofa, zelle-citi)
798
+ * @param chainId - The chain ID
799
+ * @param contractId - The contract ID (keccak256 hash) to lookup
800
+ * @returns PaymentConfig or null if not found
801
+ */
802
+ export const getProviderByContractId = (chainId, contractId) => {
803
+ const normalizedId = contractId.toLowerCase();
804
+ return providerByContractId(chainId).get(normalizedId) ?? null;
805
+ };
806
+ /**
807
+ * Get provider name by contractId (keccak256 hash)
808
+ * Supports both main providers and sub-providers
809
+ * @param chainId - The chain ID
810
+ * @param contractId - The contract ID (keccak256 hash) to lookup
811
+ * @returns Provider name or the contract ID if not found
812
+ */
813
+ export const getProviderNameByContractId = (chainId, contractId) => {
814
+ return getProviderByContractId(chainId, contractId)?.name ?? contractId;
815
+ };
816
+ /** Provider key to V2 verifiers map (legacy, for backward compatibility) */
817
+ export const providerKeyToVerifiersMap = (chainId) => (new Map(Object.entries(providerConfigs(chainId)).map(([key, config]) => [key, config.v2Verifiers])));
320
818
  /** Provider key to verifiers */
321
819
  export const providerKeyToVerifiers = (chainId, providerKey) => {
322
820
  return providerKeyToVerifiersMap(chainId).get(providerKey) ?? null;
323
821
  };
324
822
  /** Verifier gets key */
325
823
  export const verifierGetsKey = (chainId, verifierAddress) => {
824
+ // If this is a 32-byte hash (66 chars including 0x), look it up directly in the map
825
+ if (verifierAddress?.length === 66) {
826
+ const key = paymentVerifierToKey(chainId).get(verifierAddress.toLowerCase());
827
+ if (key)
828
+ return key;
829
+ // Fallback: try looking it up via contractId
830
+ const contractProvider = getProviderByContractId(chainId ?? base.id, verifierAddress);
831
+ if (contractProvider) {
832
+ return contractProvider.key;
833
+ }
834
+ return providers.MANUAL;
835
+ }
836
+ // Otherwise treat it as an address
326
837
  try {
327
838
  return paymentVerifierToKey(chainId).get(getAddress(verifierAddress)) ?? providers.MANUAL;
328
839
  }
@@ -332,12 +843,21 @@ export const verifierGetsKey = (chainId, verifierAddress) => {
332
843
  };
333
844
  /**
334
845
  * Utility function to get payment method icon
335
- * @param key - The provider key
846
+ * @param key - The provider key (main provider or sub-provider like 'zelle-chase')
336
847
  * @returns The icon string used for iconify
337
848
  */
338
849
  export const getPaymentIcon = (chainId, key) => {
339
850
  const config = providerConfigs(chainId ?? pulsechain.id);
340
- return config[key]?.icon || config[providers.MANUAL].icon;
851
+ // Check main providers first
852
+ if (config[key]?.icon) {
853
+ return config[key].icon;
854
+ }
855
+ // Check sub-providers directly (e.g., 'zelle-chase', 'zelle-bofa', 'zelle-citi')
856
+ const zelleSubConfig = subProviderConfigs.zelle?.[key];
857
+ if (zelleSubConfig?.icon) {
858
+ return zelleSubConfig.icon;
859
+ }
860
+ return config[providers.MANUAL].icon;
341
861
  };
342
862
  /**
343
863
  * Utility function to get payment method configuration by verifier address
@@ -345,7 +865,16 @@ export const getPaymentIcon = (chainId, key) => {
345
865
  * @returns The payment method configuration
346
866
  */
347
867
  export const getPaymentMethodConfig = (chainId, verifierAddress) => {
348
- return (chainId ? [chainId] : [...contractsByChainId.keys()]).reduce((result, chainId) => (result ?? Object.values(providerConfigs(chainId)).reduce((r, config) => (r ?? (config.verifiers.includes(getAddress(verifierAddress)) ? config : null)), result)), null);
868
+ // When verifierAddress is a payment-method hash (bytes32), compare via contractId
869
+ if (!isAddress(verifierAddress)) {
870
+ if (verifierAddress.length === 66) {
871
+ return getProviderByContractId(chainId ?? base.id, verifierAddress);
872
+ }
873
+ return null;
874
+ }
875
+ const normalized = getAddress(verifierAddress);
876
+ const chainIdsToCheck = chainId ? [chainId] : [...Object.keys(contracts.v2).map((id) => Number(id))];
877
+ return chainIdsToCheck.reduce((result, chain) => (result ?? Object.values(providerConfigs(chain)).reduce((r, config) => (r ?? (config.v2Verifiers.some((addr) => getAddress(addr) === normalized) ? config : null)), result)), null);
349
878
  };
350
879
  /**
351
880
  * Utility function to get payment method minimum limit by verifier address
@@ -357,7 +886,8 @@ export const getPaymentMethodMinimum = (chainId, key) => {
357
886
  return config?.limits?.min ?? 0;
358
887
  };
359
888
  /**
360
- * Utility function to get payment method name by verifier address
889
+ * Utility function to get payment method name by verifier address or provider key
890
+ * Handles both main providers and sub-providers (e.g., 'zelle-chase')
361
891
  */
362
892
  export const getPaymentMethodName = (chainId, verifierAddress) => {
363
893
  const providerKey = verifierAddress;
@@ -365,7 +895,23 @@ export const getPaymentMethodName = (chainId, verifierAddress) => {
365
895
  if (providerConfig) {
366
896
  return providerConfig.name;
367
897
  }
368
- const config = providerConfigs(chainId ?? base.id)[verifierAddress] ?? getPaymentMethodConfig(chainId ?? base.id, verifierAddress);
898
+ // Check sub-providers directly (e.g., 'zelle-chase', 'zelle-bofa', 'zelle-citi')
899
+ // Sub-providers are nested under their parent provider key (e.g., subProviderConfigs.zelle['zelle-chase'])
900
+ const zelleSubConfig = subProviderConfigs.zelle?.[providerKey];
901
+ if (zelleSubConfig?.name) {
902
+ return zelleSubConfig.name;
903
+ }
904
+ if (typeof verifierAddress === 'string' && verifierAddress.length === 66) {
905
+ const contractProvider = getProviderByContractId(chainId ?? base.id, verifierAddress);
906
+ if (contractProvider) {
907
+ return contractProvider.name;
908
+ }
909
+ return 'Unknown';
910
+ }
911
+ const config = providerConfigs(chainId ?? base.id)[verifierAddress]
912
+ ?? (typeof verifierAddress === 'string' && verifierAddress.length === 42
913
+ ? getPaymentMethodConfig(chainId ?? base.id, verifierAddress)
914
+ : null);
369
915
  return config?.name || 'Unknown';
370
916
  };
371
917
  /**
@@ -415,7 +961,37 @@ export function getPaymentProviderMinimum(chainId, key) {
415
961
  return getPaymentMethodMinimum(chainId, key) ?? 0;
416
962
  }
417
963
  /**
418
- * Check if a payment method is known
964
+ * External/unregistered payment methods that are known but not in providerConfigs.
965
+ * These are payment methods registered by external parties (e.g., other protocols).
966
+ * Maps payment method ID to metadata to prevent error logging.
967
+ */
968
+ export const externalPaymentMethods = {
969
+ // Pendle - registered on Base
970
+ '0x5908bb0c9b87763ac6171d4104847667e7f02b4c47b574fe890c1f439ed128bb': {
971
+ name: 'pendle',
972
+ originTxHash: '0x3ba1e1ae196ff5e29e09b934966e25faa34b7ec7edc371e6ef275916a9ce1f39',
973
+ chainId: 8453,
974
+ description: 'Pendle Finance payment method',
975
+ },
976
+ };
977
+ /**
978
+ * Check if a payment method is an external/unregistered payment method
979
+ * @param paymentMethodId - The payment method ID
980
+ * @returns True if the payment method is external, false otherwise
981
+ */
982
+ export const isExternalPaymentMethod = (paymentMethodId) => {
983
+ return paymentMethodId in externalPaymentMethods;
984
+ };
985
+ /**
986
+ * Get external payment method info
987
+ * @param paymentMethodId - The payment method ID
988
+ * @returns The external payment method info or undefined
989
+ */
990
+ export const getExternalPaymentMethod = (paymentMethodId) => {
991
+ return externalPaymentMethods[paymentMethodId];
992
+ };
993
+ /**
994
+ * Check if a payment method is known (either in providerConfigs or external)
419
995
  * @param chainId - The chain ID
420
996
  * @param verifierAddress - The verifier address
421
997
  * @returns True if the payment method is known, false otherwise
@@ -424,16 +1000,150 @@ export const isPaymentMethodKnown = (chainId, verifierAddress) => {
424
1000
  return !!getPaymentMethodConfig(chainId, verifierAddress);
425
1001
  };
426
1002
  /**
427
- * Get all payment methods
1003
+ * Check if a payment method ID is known (either in providerConfigs or external)
1004
+ * @param chainId - The chain ID
1005
+ * @param paymentMethodId - The payment method ID
1006
+ * @param verifierAddress - Optional verifier address for checking providerConfigs
1007
+ * @returns True if the payment method is known, false otherwise
1008
+ */
1009
+ export const isPaymentMethodIdKnown = (chainId, paymentMethodId, verifierAddress) => {
1010
+ // Check if it's an external payment method first
1011
+ if (isExternalPaymentMethod(paymentMethodId)) {
1012
+ return true;
1013
+ }
1014
+ // Fall back to checking providerConfigs if verifierAddress is provided
1015
+ if (verifierAddress) {
1016
+ return isPaymentMethodKnown(chainId, verifierAddress);
1017
+ }
1018
+ return false;
1019
+ };
1020
+ /**
1021
+ * Get all payment methods including sub-providers
1022
+ * @param includeSubProviders - Whether to include sub-providers as separate entries (default: false)
428
1023
  * @returns All payment methods
429
1024
  */
430
- export const getAllPaymentMethods = (chainId) => {
431
- return Object.keys(providerConfigs(chainId));
1025
+ export const getAllPaymentMethods = (chainId, includeSubProviders = false) => {
1026
+ const configs = providerConfigs(chainId);
1027
+ const mainProviders = Object.keys(configs)
1028
+ .filter(key => !isProviderDisabled(key, configs[key].disabled));
1029
+ if (!includeSubProviders) {
1030
+ return mainProviders;
1031
+ }
1032
+ const allProviders = [...mainProviders];
1033
+ for (const key of mainProviders) {
1034
+ const config = configs[key];
1035
+ if (config.subProviders.size > 0) {
1036
+ config.subProviders.forEach(subProvider => {
1037
+ allProviders.push(subProvider.key);
1038
+ });
1039
+ }
1040
+ }
1041
+ return allProviders;
432
1042
  };
433
1043
  /**
434
- * Get all verifiable payment methods
1044
+ * Get all verifiable payment methods including sub-providers
1045
+ * @param includeSubProviders - Whether to include sub-providers as separate entries (default: false)
435
1046
  * @returns All verifiable payment methods
436
1047
  */
437
- export const verifiablePaymentMethods = (chainId) => {
438
- return getAllPaymentMethods(chainId).filter(key => key !== providers.MANUAL);
1048
+ export const verifiablePaymentMethods = (chainId, includeSubProviders = false) => {
1049
+ return getAllPaymentMethods(chainId, includeSubProviders).filter(key => key !== providers.MANUAL);
439
1050
  };
1051
+ /**
1052
+ * Get supported currencies for a payment provider.
1053
+ * Returns only the currencies that are in the provider's supported list.
1054
+ *
1055
+ * @param config - The payment provider configuration
1056
+ * @returns Array of Currency objects supported by this provider
1057
+ *
1058
+ * @example
1059
+ * ```ts
1060
+ * const zelle = providerConfigs(base.id).zelle
1061
+ * const currencies = getCurrenciesForProvider(zelle)
1062
+ * // Returns [Currency(USD)] - Zelle only supports USD
1063
+ *
1064
+ * const revolut = providerConfigs(base.id).revolut
1065
+ * const currencies = getCurrenciesForProvider(revolut)
1066
+ * // Returns [Currency(USD), Currency(EUR), Currency(GBP), ...] - Revolut supports many
1067
+ * ```
1068
+ */
1069
+ export function getCurrenciesForProvider(config) {
1070
+ const supportedIds = new Set(config.list);
1071
+ return Object.values(SUPPORTED_CURRENCIES).filter((currency) => supportedIds.has(currency.contractId));
1072
+ }
1073
+ /** Set of all sub-provider keys for fast lookup */
1074
+ const subProviderKeySet = new Set(Object.values(subProviders));
1075
+ /** Set of all main provider keys for fast lookup */
1076
+ const providerKeySet = new Set(Object.values(providers));
1077
+ /**
1078
+ * Check if a provider key is a sub-provider (e.g., 'zelle-chase', 'zelle-bofa')
1079
+ * @param key - The provider key to check
1080
+ * @returns True if the key is a sub-provider
1081
+ */
1082
+ export function isSubProviderKey(key) {
1083
+ return subProviderKeySet.has(key);
1084
+ }
1085
+ /**
1086
+ * Get the parent provider key for a sub-provider.
1087
+ * Returns null if the key is not a sub-provider.
1088
+ * @param key - The sub-provider key (e.g., 'zelle-chase')
1089
+ * @returns The parent provider key (e.g., 'zelle') or null
1090
+ */
1091
+ export function getParentProviderKey(key) {
1092
+ if (!isSubProviderKey(key)) {
1093
+ return null;
1094
+ }
1095
+ // Sub-provider keys follow the pattern '{parent}-{subName}'
1096
+ const parentKey = key.split('-')[0];
1097
+ if (parentKey && providerKeySet.has(parentKey)) {
1098
+ return parentKey;
1099
+ }
1100
+ return null;
1101
+ }
1102
+ /**
1103
+ * Get the extension platform key for a provider or sub-provider.
1104
+ * For sub-providers like 'zelle-chase', returns the extensionPlatform from config (e.g., 'chase').
1105
+ * For main providers, returns the provider key itself.
1106
+ * @param key - The provider or sub-provider key
1107
+ * @returns The platform key to use with the PeerAuth extension
1108
+ */
1109
+ export function getExtensionPlatform(key) {
1110
+ const parentKey = getParentProviderKey(key);
1111
+ if (parentKey) {
1112
+ // It's a sub-provider - look up the extensionPlatform from config
1113
+ const subConfig = subProviderConfigs[parentKey]?.[key];
1114
+ if (subConfig?.extensionPlatform) {
1115
+ return subConfig.extensionPlatform;
1116
+ }
1117
+ }
1118
+ // Main provider - use the key as the platform
1119
+ return key;
1120
+ }
1121
+ /**
1122
+ * Get icon and color info for both parent and sub-provider.
1123
+ * Returns array of icon configs: [parent, subProvider] for sub-providers, [provider] for main providers.
1124
+ * @param chainId - The chain ID for config lookup
1125
+ * @param key - The provider or sub-provider key
1126
+ * @returns Array of icon configs with icon string and optional color
1127
+ */
1128
+ export function getProviderIconStack(chainId, key) {
1129
+ const configs = providerConfigs(chainId);
1130
+ // Check if this is a sub-provider
1131
+ const parentKey = getParentProviderKey(key);
1132
+ if (parentKey) {
1133
+ const parentConfig = configs[parentKey];
1134
+ const subConfig = subProviderConfigs[parentKey]?.[key];
1135
+ if (parentConfig && subConfig) {
1136
+ return [
1137
+ { icon: parentConfig.icon, color: parentConfig.color, name: parentConfig.name },
1138
+ { icon: subConfig.icon ?? parentConfig.icon, color: subConfig.color ?? parentConfig.color, name: subConfig.name },
1139
+ ];
1140
+ }
1141
+ }
1142
+ // Main provider - return single icon
1143
+ const config = configs[key];
1144
+ if (config) {
1145
+ return [{ icon: config.icon, color: config.color, name: config.name }];
1146
+ }
1147
+ // Fallback to manual
1148
+ return [{ icon: configs.manual.icon, color: configs.manual.color, name: 'Unknown' }];
1149
+ }