@provex/utils 1.0.16 → 1.2.4

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 (149) hide show
  1. package/dist/chain.d.ts +446 -0
  2. package/dist/chain.js +98 -0
  3. package/dist/chain.test.js +112 -0
  4. package/dist/contracts.d.ts +206 -0
  5. package/dist/contracts.js +292 -0
  6. package/dist/contracts.test.js +243 -0
  7. package/dist/conversionRates.d.ts +215 -0
  8. package/dist/{src/conversionRates.js → conversionRates.js} +74 -4
  9. package/dist/conversionRates.test.js +473 -0
  10. package/dist/currencies.d.ts +248 -0
  11. package/dist/{src/currencies.js → currencies.js} +119 -38
  12. package/dist/currencies.test.js +343 -0
  13. package/dist/data/countries.json +1099 -0
  14. package/dist/deposit.d.ts +69 -0
  15. package/dist/deposit.js +52 -0
  16. package/dist/depositKeys.d.ts +154 -0
  17. package/dist/depositKeys.js +104 -0
  18. package/dist/depositKeys.test.d.ts +1 -0
  19. package/dist/depositKeys.test.js +231 -0
  20. package/dist/events.d.ts +41 -0
  21. package/dist/events.js +92 -0
  22. package/dist/events.test.d.ts +1 -0
  23. package/dist/events.test.js +111 -0
  24. package/dist/fees.d.ts +136 -0
  25. package/dist/fees.js +178 -0
  26. package/dist/fees.test.d.ts +1 -0
  27. package/dist/fees.test.js +165 -0
  28. package/dist/ids.d.ts +175 -0
  29. package/dist/ids.js +231 -0
  30. package/dist/ids.test.d.ts +1 -0
  31. package/dist/ids.test.js +607 -0
  32. package/dist/index.d.ts +45 -0
  33. package/dist/index.js +45 -0
  34. package/dist/input.d.ts +26 -0
  35. package/dist/{src/input.js → input.js} +23 -4
  36. package/dist/intent.d.ts +50 -0
  37. package/dist/intent.js +28 -0
  38. package/dist/intent.test.d.ts +1 -0
  39. package/dist/intent.test.js +119 -0
  40. package/dist/json.d.ts +55 -0
  41. package/dist/json.js +80 -0
  42. package/dist/participants.d.ts +22 -0
  43. package/dist/participants.js +28 -0
  44. package/dist/payment.d.ts +481 -0
  45. package/dist/payment.js +1149 -0
  46. package/dist/payment.test.d.ts +1 -0
  47. package/dist/payment.test.js +272 -0
  48. package/dist/peer-types.d.ts +327 -0
  49. package/dist/peer-types.js +169 -0
  50. package/dist/profit.d.ts +49 -0
  51. package/dist/profit.js +57 -0
  52. package/dist/profit.test.d.ts +1 -0
  53. package/dist/profit.test.js +315 -0
  54. package/dist/reputation.d.ts +125 -0
  55. package/dist/reputation.js +188 -0
  56. package/dist/reputation.test.d.ts +1 -0
  57. package/dist/reputation.test.js +156 -0
  58. package/dist/sort.d.ts +75 -0
  59. package/dist/sort.js +70 -0
  60. package/dist/sort.test.d.ts +1 -0
  61. package/dist/sort.test.js +216 -0
  62. package/dist/stateKey.d.ts +35 -0
  63. package/dist/stateKey.js +36 -0
  64. package/dist/stats.d.ts +61 -0
  65. package/dist/stats.js +53 -0
  66. package/dist/stats.test.d.ts +1 -0
  67. package/dist/stats.test.js +104 -0
  68. package/dist/time.d.ts +104 -0
  69. package/dist/time.js +162 -0
  70. package/dist/time.test.d.ts +1 -0
  71. package/dist/time.test.js +231 -0
  72. package/dist/tokens.d.ts +15502 -0
  73. package/dist/{src/tokens.js → tokens.js} +98 -17
  74. package/dist/tokens.test.d.ts +1 -0
  75. package/dist/tokens.test.js +193 -0
  76. package/dist/units.d.ts +200 -0
  77. package/dist/units.js +279 -0
  78. package/dist/units.test.d.ts +1 -0
  79. package/dist/units.test.js +327 -0
  80. package/package.json +119 -29
  81. package/dist/scripts/convert-svg.js +0 -17
  82. package/dist/src/chain.d.ts +0 -14
  83. package/dist/src/chain.js +0 -18
  84. package/dist/src/contracts.d.ts +0 -30
  85. package/dist/src/contracts.js +0 -64
  86. package/dist/src/conversionRates.d.ts +0 -124
  87. package/dist/src/currencies.d.ts +0 -144
  88. package/dist/src/data/countries.json +0 -1099
  89. package/dist/src/deposit.d.ts +0 -1
  90. package/dist/src/events.d.ts +0 -3
  91. package/dist/src/events.js +0 -16
  92. package/dist/src/ids.d.ts +0 -72
  93. package/dist/src/ids.js +0 -81
  94. package/dist/src/index.d.ts +0 -16
  95. package/dist/src/index.js +0 -16
  96. package/dist/src/input.d.ts +0 -7
  97. package/dist/src/intent.d.ts +0 -14
  98. package/dist/src/json.d.ts +0 -18
  99. package/dist/src/json.js +0 -44
  100. package/dist/src/participants.d.ts +0 -7
  101. package/dist/src/participants.js +0 -13
  102. package/dist/src/payment.d.ts +0 -164
  103. package/dist/src/payment.js +0 -439
  104. package/dist/src/profit.d.ts +0 -28
  105. package/dist/src/profit.js +0 -34
  106. package/dist/src/sort.d.ts +0 -36
  107. package/dist/src/sort.js +0 -39
  108. package/dist/src/stats.d.ts +0 -7
  109. package/dist/src/stats.js +0 -8
  110. package/dist/src/time.d.ts +0 -54
  111. package/dist/src/time.js +0 -106
  112. package/dist/src/tokens.d.ts +0 -3675
  113. package/dist/src/units.d.ts +0 -17
  114. package/dist/src/units.js +0 -79
  115. package/dist/src/zkp2p-types.d.ts +0 -48
  116. package/dist/tsconfig.tsbuildinfo +0 -1
  117. package/scripts/convert-svg.ts +0 -22
  118. package/src/chain.ts +0 -27
  119. package/src/contracts.ts +0 -89
  120. package/src/conversionRates.ts +0 -356
  121. package/src/currencies.ts +0 -286
  122. package/src/data/countries.json +0 -1099
  123. package/src/deposit.ts +0 -2
  124. package/src/events.ts +0 -21
  125. package/src/flags/eur.svg +0 -4
  126. package/src/flags/usd.svg +0 -25
  127. package/src/flags.ts +0 -4
  128. package/src/global.d.ts +0 -9
  129. package/src/ids.ts +0 -137
  130. package/src/index.ts +0 -16
  131. package/src/input.ts +0 -35
  132. package/src/intent.ts +0 -15
  133. package/src/json.ts +0 -42
  134. package/src/participants.ts +0 -14
  135. package/src/payment.ts +0 -573
  136. package/src/profit.ts +0 -55
  137. package/src/sort.ts +0 -49
  138. package/src/stats.ts +0 -10
  139. package/src/time.ts +0 -123
  140. package/src/tokens.ts +0 -159
  141. package/src/units.ts +0 -120
  142. package/src/zkp2p-types.ts +0 -55
  143. package/tsconfig.json +0 -18
  144. /package/dist/{scripts/convert-svg.d.ts → chain.test.d.ts} +0 -0
  145. /package/dist/{src/deposit.js → contracts.test.d.ts} +0 -0
  146. /package/dist/{src/intent.js → conversionRates.test.d.ts} +0 -0
  147. /package/dist/{src/zkp2p-types.js → currencies.test.d.ts} +0 -0
  148. /package/dist/{src/flags.d.ts → flags.d.ts} +0 -0
  149. /package/dist/{src/flags.js → flags.js} +0 -0
@@ -0,0 +1,1149 @@
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
+ }
74
+ import { base, pulsechain } from "viem/chains";
75
+ import { contracts } from "./contracts.js";
76
+ import { formatUnits } from "./units.js";
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
+ };
111
+ /**
112
+ * Payment providers, including manual
113
+ */
114
+ export const providers = {
115
+ VENMO: 'venmo',
116
+ ZELLE: 'zelle',
117
+ CASHAPP: 'cashapp',
118
+ REVOLUT: 'revolut',
119
+ WISE: 'wise',
120
+ MERCADOPAGO: 'mercadopago',
121
+ MANUAL: 'manual',
122
+ PAYPAL: 'paypal',
123
+ MONZO: 'monzo',
124
+ N26: 'n26',
125
+ ALIPAY: 'alipay',
126
+ CHIME: 'chime',
127
+ ROYALBANKCANADA: 'royalbankcanada',
128
+ IDFC: 'idfc',
129
+ };
130
+ /** Distinct keys for payment sub-providers (currently only used by Zelle) */
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 = {
142
+ CHASE: 'chase',
143
+ BOFA: 'bankofamerica',
144
+ CITI: 'citi',
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
+ }
194
+ /** Provider key to validation key */
195
+ export const providerKeyToValidationKey = {
196
+ venmo: 'venmoUsername',
197
+ zelle: 'zelleEmail',
198
+ cashapp: 'cashtag',
199
+ revolut: 'revolutUsername',
200
+ wise: 'wisetag',
201
+ mercadopago: 'cvu',
202
+ paypal: 'paypalEmail',
203
+ monzo: 'monzoMeUsername',
204
+ n26: 'n26Email',
205
+ alipay: 'alipayId',
206
+ chime: 'chimesign',
207
+ royalbankcanada: 'interacEmail',
208
+ idfc: 'idfcAccountNumber',
209
+ };
210
+ /** Data encoding for zelle urls */
211
+ export const dataEncoding = (data) => {
212
+ return btoa(JSON.stringify({
213
+ name: '',
214
+ action: 'payment',
215
+ token: data,
216
+ }));
217
+ };
218
+ /** Default Zelle send link */
219
+ export const defaultZelleSendLink = (options) => (!options.username ? null
220
+ : `https://enroll.zellepay.com/qr-codes?data=${dataEncoding(options.username)}`);
221
+ /** Global configuration for all supported sub-providers. Other modules can import this directly. */
222
+ export const subProviderConfigs = {
223
+ zelle: {
224
+ 'zelle-chase': {
225
+ paymentMethod: 0,
226
+ key: subProviders.CHASE,
227
+ name: 'Chase',
228
+ icon: 'simple-icons:chase',
229
+ extensionPlatform: extensionPlatforms.CHASE,
230
+ v2Verifiers: (chain) => contracts.v2[chain].zelleChaseReclaimVerifier,
231
+ sendLink: (opts) => {
232
+ if (opts.qrCode) {
233
+ return defaultZelleSendLink(opts);
234
+ }
235
+ // return 'https://secure.chase.com/web/auth/dashboard#/dashboard/singleDoor/payments/activity;params=qp,sentactivity'
236
+ return 'https://secure.chase.com/web/auth/dashboard#/dashboard/singleDoor/singleDoorController/index';
237
+ },
238
+ },
239
+ 'zelle-bofa': {
240
+ paymentMethod: 1,
241
+ key: subProviders.BANK_OF_AMERICA,
242
+ name: 'Bank of America',
243
+ icon: 'simple-icons:bankofamerica',
244
+ extensionPlatform: extensionPlatforms.BOFA,
245
+ v2Verifiers: (chain) => contracts.v2[chain].zelleBoAReclaimVerifier,
246
+ sendLink: (opts) => {
247
+ if (opts.qrCode) {
248
+ return `https://www.bankofamerica.com/movemoney`;
249
+ }
250
+ return 'https://www.bankofamerica.com/';
251
+ },
252
+ },
253
+ 'zelle-citi': {
254
+ paymentMethod: 2,
255
+ key: subProviders.CITI,
256
+ name: 'Citi',
257
+ icon: 'arcticons:citi-mobile',
258
+ extensionPlatform: extensionPlatforms.CITI,
259
+ v2Verifiers: (chain) => contracts.v2[chain].zelleCitiReclaimVerifier,
260
+ sendLink: (opts) => {
261
+ if (opts.qrCode) {
262
+ return defaultZelleSendLink(opts);
263
+ }
264
+ return 'https://online.citi.com/US/nga/zelle/transfer';
265
+ },
266
+ },
267
+ },
268
+ };
269
+ /** Payment method icon mappings */
270
+ export const providerConfigs = (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
+ };
290
+ return {
291
+ venmo: {
292
+ key: providers.VENMO,
293
+ contractId: providerKeyToContractId(providers.VENMO),
294
+ icon: 'ion:logo-venmo',
295
+ name: 'Venmo',
296
+ usernameType: 'Venmo Username',
297
+ validationKey: providerKeyToValidationKey.venmo,
298
+ color: 'rgb(0, 116, 222)',
299
+ chartColorLight: '#0074de',
300
+ chartColorDark: '#4da5ff',
301
+ v2Verifiers: getV2Verifiers('venmoReclaimVerifier'),
302
+ limits: {
303
+ min: 1.0,
304
+ },
305
+ subProviders: new Set(),
306
+ sendLink: (options) => {
307
+ if (options.qrCode) {
308
+ return `venmo://paycharge?txn=pay&recipients=${options.username}&note=cash&amount=${options.amount}`;
309
+ }
310
+ return `https://account.venmo.com/pay?recipients=${options.username}&note=cash&amount=${options.amount}`;
311
+ },
312
+ list: [
313
+ SUPPORTED_CURRENCIES.USD.contractId,
314
+ ],
315
+ // High risk - 1x multiplier, has cooldown
316
+ riskLevel: 'high',
317
+ capMultiplier: 1,
318
+ hasCooldown: true,
319
+ minimumTier: null,
320
+ },
321
+ zelle: {
322
+ key: providers.ZELLE,
323
+ contractId: providerKeyToContractId(providers.ZELLE),
324
+ icon: 'simple-icons:zelle',
325
+ name: 'Zelle',
326
+ color: '#6d1fd4',
327
+ usernameType: 'Zelle Email',
328
+ validationKey: providerKeyToValidationKey.zelle,
329
+ v2Verifiers: getV2Verifiers('zelleBaseVerifier'),
330
+ limits: {
331
+ min: 1.0,
332
+ },
333
+ subProviders: new Set([
334
+ ...Object.values(subProviderConfigs.zelle),
335
+ ]),
336
+ // Delegate to sub-provider specific link when provided, otherwise fall back to generic QR URL
337
+ sendLink: (options) => {
338
+ if (options.subProvider) {
339
+ return subProviderConfigs.zelle[options.subProvider].sendLink(options);
340
+ }
341
+ return defaultZelleSendLink(options);
342
+ },
343
+ list: [
344
+ SUPPORTED_CURRENCIES.USD.contractId,
345
+ ],
346
+ // Medium risk - 1.5x multiplier, has cooldown
347
+ riskLevel: 'medium',
348
+ capMultiplier: 1.5,
349
+ hasCooldown: true,
350
+ minimumTier: null,
351
+ },
352
+ cashapp: {
353
+ key: providers.CASHAPP,
354
+ contractId: providerKeyToContractId(providers.CASHAPP),
355
+ icon: 'simple-icons:cashapp',
356
+ name: 'Cash App',
357
+ color: 'rgba(0,224,19,1.0)',
358
+ chartColorLight: '#00e013',
359
+ chartColorDark: '#4dff6a',
360
+ usernameType: 'CashTag',
361
+ validationKey: providerKeyToValidationKey.cashapp,
362
+ v2Verifiers: getV2Verifiers('cashappReclaimVerifier'),
363
+ limits: {
364
+ min: 1.0,
365
+ },
366
+ subProviders: new Set(),
367
+ list: [
368
+ SUPPORTED_CURRENCIES.USD.contractId,
369
+ ],
370
+ sendLink: (options) => {
371
+ const u = options.username.startsWith('$') ? options.username : `$${options.username}`;
372
+ return `https://cash.app/qr/${u}?size=288&margin=0`;
373
+ },
374
+ // High risk - 1x multiplier, has cooldown
375
+ riskLevel: 'high',
376
+ capMultiplier: 1,
377
+ hasCooldown: true,
378
+ minimumTier: null,
379
+ },
380
+ revolut: {
381
+ key: providers.REVOLUT,
382
+ contractId: providerKeyToContractId(providers.REVOLUT),
383
+ icon: 'simple-icons:revolut',
384
+ name: 'Revolut',
385
+ color: '#191c1f',
386
+ // Chart-friendly color to ensure distinction in stacked bar palette
387
+ chartColorLight: '#8b5cf6',
388
+ chartColorDark: '#c4b5fd',
389
+ backgroundColor: '#ffffff',
390
+ usernameType: 'RevTag',
391
+ validationKey: providerKeyToValidationKey.revolut,
392
+ v2Verifiers: getV2Verifiers('revolutReclaimVerifier'),
393
+ limits: {
394
+ min: 1.0,
395
+ },
396
+ subProviders: new Set(),
397
+ sendLink: (options) => `https://revolut.me/${options.username}`,
398
+ list: [
399
+ SUPPORTED_CURRENCIES.USD.contractId,
400
+ SUPPORTED_CURRENCIES.EUR.contractId,
401
+ SUPPORTED_CURRENCIES.GBP.contractId,
402
+ SUPPORTED_CURRENCIES.SGD.contractId,
403
+ SUPPORTED_CURRENCIES.NZD.contractId,
404
+ SUPPORTED_CURRENCIES.AUD.contractId,
405
+ SUPPORTED_CURRENCIES.CAD.contractId,
406
+ SUPPORTED_CURRENCIES.HKD.contractId,
407
+ SUPPORTED_CURRENCIES.MXN.contractId,
408
+ SUPPORTED_CURRENCIES.SAR.contractId,
409
+ SUPPORTED_CURRENCIES.AED.contractId,
410
+ SUPPORTED_CURRENCIES.THB.contractId,
411
+ SUPPORTED_CURRENCIES.TRY.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,
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,
431
+ },
432
+ wise: {
433
+ key: providers.WISE,
434
+ contractId: providerKeyToContractId(providers.WISE),
435
+ icon: 'simple-icons:wise',
436
+ name: 'Wise',
437
+ color: '#9fe870',
438
+ usernameType: 'WiseTag',
439
+ validationKey: providerKeyToValidationKey.wise,
440
+ v2Verifiers: getV2Verifiers('wiseReclaimVerifier'),
441
+ limits: {
442
+ min: 1.0,
443
+ },
444
+ subProviders: new Set(),
445
+ sendLink: (options) => `https://wise.com/pay/me/${options.username}`,
446
+ list: [
447
+ SUPPORTED_CURRENCIES.USD.contractId,
448
+ SUPPORTED_CURRENCIES.CNY.contractId,
449
+ SUPPORTED_CURRENCIES.EUR.contractId,
450
+ SUPPORTED_CURRENCIES.GBP.contractId,
451
+ SUPPORTED_CURRENCIES.AUD.contractId,
452
+ SUPPORTED_CURRENCIES.NZD.contractId,
453
+ SUPPORTED_CURRENCIES.CAD.contractId,
454
+ SUPPORTED_CURRENCIES.AED.contractId,
455
+ SUPPORTED_CURRENCIES.CHF.contractId,
456
+ SUPPORTED_CURRENCIES.ZAR.contractId,
457
+ SUPPORTED_CURRENCIES.SGD.contractId,
458
+ SUPPORTED_CURRENCIES.ILS.contractId,
459
+ SUPPORTED_CURRENCIES.HKD.contractId,
460
+ SUPPORTED_CURRENCIES.JPY.contractId,
461
+ SUPPORTED_CURRENCIES.PLN.contractId,
462
+ SUPPORTED_CURRENCIES.TRY.contractId,
463
+ SUPPORTED_CURRENCIES.IDR.contractId,
464
+ SUPPORTED_CURRENCIES.KES.contractId,
465
+ SUPPORTED_CURRENCIES.MYR.contractId,
466
+ SUPPORTED_CURRENCIES.MXN.contractId,
467
+ SUPPORTED_CURRENCIES.THB.contractId,
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,
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',
486
+ },
487
+ mercadopago: {
488
+ key: providers.MERCADOPAGO,
489
+ contractId: providerKeyToContractId(providers.MERCADOPAGO),
490
+ icon: 'simple-icons:mercadopago',
491
+ name: 'Mercado Pago',
492
+ usernameType: 'Mercado Pago / Bank CVU',
493
+ color: '#ffe600',
494
+ validationKey: providerKeyToValidationKey.mercadopago,
495
+ v2Verifiers: getV2Verifiers('mercadopagoReclaimVerifier'),
496
+ limits: {
497
+ min: 1.0,
498
+ },
499
+ subProviders: new Set(),
500
+ sendLink: () => `https://www.mercadopago.com.ar/money-out/transfer/`,
501
+ list: [
502
+ SUPPORTED_CURRENCIES.ARS.contractId,
503
+ ],
504
+ // Low risk - instant settlement, no chargebacks (per peer.xyz docs)
505
+ riskLevel: 'low',
506
+ capMultiplier: 5,
507
+ hasCooldown: false,
508
+ minimumTier: null,
509
+ },
510
+ paypal: {
511
+ key: providers.PAYPAL,
512
+ contractId: providerKeyToContractId(providers.PAYPAL),
513
+ icon: 'simple-icons:paypal',
514
+ name: 'PayPal',
515
+ usernameType: 'PayPal Email',
516
+ validationKey: providerKeyToValidationKey.paypal,
517
+ v2Verifiers: getV2Verifiers('paypalReclaimVerifier'),
518
+ limits: {
519
+ min: 1.0,
520
+ },
521
+ subProviders: new Set(),
522
+ color: '#0070BA',
523
+ chartColor: '#f59e0b',
524
+ sendLink: (options) => {
525
+ if (options.qrCode) {
526
+ return `paypal://`;
527
+ }
528
+ return `https://www.paypal.com/myaccount/transfer/homepage`;
529
+ },
530
+ list: [
531
+ SUPPORTED_CURRENCIES.USD.contractId,
532
+ SUPPORTED_CURRENCIES.EUR.contractId,
533
+ SUPPORTED_CURRENCIES.GBP.contractId,
534
+ SUPPORTED_CURRENCIES.AUD.contractId,
535
+ SUPPORTED_CURRENCIES.NZD.contractId,
536
+ SUPPORTED_CURRENCIES.CAD.contractId,
537
+ SUPPORTED_CURRENCIES.SGD.contractId,
538
+ ],
539
+ riskLevel: 'highest',
540
+ capMultiplier: 0.75,
541
+ hasCooldown: true,
542
+ minimumTier: tiers.PLUS,
543
+ disabled: true,
544
+ },
545
+ monzo: {
546
+ key: providers.MONZO,
547
+ contractId: providerKeyToContractId(providers.MONZO),
548
+ icon: 'simple-icons:monzo',
549
+ name: 'Monzo',
550
+ usernameType: 'Monzo Me Username',
551
+ validationKey: providerKeyToValidationKey.monzo,
552
+ v2Verifiers: getV2Verifiers('monzoReclaimVerifier'),
553
+ limits: {
554
+ min: 1.0,
555
+ },
556
+ subProviders: new Set(),
557
+ sendLink: (options) => `https://monzo.me/${options.username}`,
558
+ list: [
559
+ SUPPORTED_CURRENCIES.GBP.contractId,
560
+ ],
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,
699
+ },
700
+ manual: {
701
+ key: providers.MANUAL,
702
+ contractId: providerKeyToContractId(providers.MANUAL),
703
+ icon: 'mdi:help-circle',
704
+ name: 'Manual',
705
+ color: 'gray',
706
+ usernameType: '',
707
+ validationKey: null,
708
+ v2Verifiers: [zeroAddress],
709
+ limits: {
710
+ min: 0.0,
711
+ },
712
+ subProviders: new Set(),
713
+ list: [],
714
+ // Manual verification - no reputation restrictions
715
+ riskLevel: 'low',
716
+ capMultiplier: 5,
717
+ hasCooldown: false,
718
+ minimumTier: null,
719
+ },
720
+ };
721
+ };
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])));
818
+ /** Provider key to verifiers */
819
+ export const providerKeyToVerifiers = (chainId, providerKey) => {
820
+ return providerKeyToVerifiersMap(chainId).get(providerKey) ?? null;
821
+ };
822
+ /** Verifier gets key */
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
837
+ try {
838
+ return paymentVerifierToKey(chainId).get(getAddress(verifierAddress)) ?? providers.MANUAL;
839
+ }
840
+ catch (error) {
841
+ return providers.MANUAL;
842
+ }
843
+ };
844
+ /**
845
+ * Utility function to get payment method icon
846
+ * @param key - The provider key (main provider or sub-provider like 'zelle-chase')
847
+ * @returns The icon string used for iconify
848
+ */
849
+ export const getPaymentIcon = (chainId, key) => {
850
+ const config = providerConfigs(chainId ?? pulsechain.id);
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;
861
+ };
862
+ /**
863
+ * Utility function to get payment method configuration by verifier address
864
+ * @param verifierAddress - The verifier address
865
+ * @returns The payment method configuration
866
+ */
867
+ export const getPaymentMethodConfig = (chainId, verifierAddress) => {
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);
878
+ };
879
+ /**
880
+ * Utility function to get payment method minimum limit by verifier address
881
+ * @param key - The provider key
882
+ * @returns The minimum limit
883
+ */
884
+ export const getPaymentMethodMinimum = (chainId, key) => {
885
+ const config = providerConfigs(chainId ?? base.id)[key];
886
+ return config?.limits?.min ?? 0;
887
+ };
888
+ /**
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')
891
+ */
892
+ export const getPaymentMethodName = (chainId, verifierAddress) => {
893
+ const providerKey = verifierAddress;
894
+ const providerConfig = providerConfigs(chainId ?? base.id)[providerKey];
895
+ if (providerConfig) {
896
+ return providerConfig.name;
897
+ }
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);
915
+ return config?.name || 'Unknown';
916
+ };
917
+ /**
918
+ * Calculate effective minimum amount (higher of user minimum or payment provider minimum)
919
+ * @param userMinAmountWei - The user minimum amount
920
+ * @param tokenInfo - The token info
921
+ * @param providerRates - The provider rates
922
+ * @returns The effective minimum amount
923
+ */
924
+ export function calculateEffectiveMinimum(chainId, userMinAmountWei, tokenInfo, providerRates) {
925
+ const userMinAmountWeiBigInt = BigInt(userMinAmountWei);
926
+ let providerMinAmountWei = 0n;
927
+ if (providerRates) {
928
+ // Get unique payment providers from the rates
929
+ const paymentProviders = new Set();
930
+ for (const [providerKey] of providerRates.entries()) {
931
+ paymentProviders.add(providerKey);
932
+ }
933
+ // Calculate the highest provider minimum limit
934
+ for (const providerKey of paymentProviders) {
935
+ const providerConfig = providerConfigs(chainId)[providerKey];
936
+ if (providerConfig?.limits?.min) {
937
+ const providerMinWei = parseUnits(providerConfig.limits.min.toString(), tokenInfo.decimals);
938
+ if (providerMinWei > providerMinAmountWei) {
939
+ providerMinAmountWei = providerMinWei;
940
+ }
941
+ }
942
+ }
943
+ }
944
+ const effectiveMinAmountWei = userMinAmountWeiBigInt > providerMinAmountWei
945
+ ? userMinAmountWeiBigInt
946
+ : providerMinAmountWei;
947
+ const formattedEffectiveMin = formatUnits(effectiveMinAmountWei, { decimals: tokenInfo.decimals });
948
+ return {
949
+ userMinAmountWei: userMinAmountWeiBigInt,
950
+ providerMinAmountWei,
951
+ effectiveMinAmountWei,
952
+ formattedEffectiveMin
953
+ };
954
+ }
955
+ /**
956
+ * Helper function to get payment provider minimum limit (returns decimal number for config)
957
+ * @param key - The provider key
958
+ * @returns The minimum limit
959
+ */
960
+ export function getPaymentProviderMinimum(chainId, key) {
961
+ return getPaymentMethodMinimum(chainId, key) ?? 0;
962
+ }
963
+ /**
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)
995
+ * @param chainId - The chain ID
996
+ * @param verifierAddress - The verifier address
997
+ * @returns True if the payment method is known, false otherwise
998
+ */
999
+ export const isPaymentMethodKnown = (chainId, verifierAddress) => {
1000
+ return !!getPaymentMethodConfig(chainId, verifierAddress);
1001
+ };
1002
+ /**
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)
1023
+ * @returns All payment methods
1024
+ */
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;
1042
+ };
1043
+ /**
1044
+ * Get all verifiable payment methods including sub-providers
1045
+ * @param includeSubProviders - Whether to include sub-providers as separate entries (default: false)
1046
+ * @returns All verifiable payment methods
1047
+ */
1048
+ export const verifiablePaymentMethods = (chainId, includeSubProviders = false) => {
1049
+ return getAllPaymentMethods(chainId, includeSubProviders).filter(key => key !== providers.MANUAL);
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
+ }