@provex/utils 1.11.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buy/browsePanelDerivations.d.ts +212 -0
- package/dist/buy/browsePanelDerivations.js +269 -0
- package/dist/buy/browsePanelDerivations.test.d.ts +9 -0
- package/dist/buy/browsePanelDerivations.test.js +552 -0
- package/dist/buy/fillabilityNotice.d.ts +214 -0
- package/dist/buy/fillabilityNotice.js +162 -0
- package/dist/buy/fillabilityNotice.test.d.ts +8 -0
- package/dist/buy/fillabilityNotice.test.js +246 -0
- package/dist/buy/formatCurrencyBaseUnits.d.ts +30 -0
- package/dist/buy/formatCurrencyBaseUnits.js +34 -0
- package/dist/buy/formatCurrencyBaseUnits.test.d.ts +7 -0
- package/dist/buy/formatCurrencyBaseUnits.test.js +34 -0
- package/dist/buy/fulfillTransactionHash.d.ts +29 -0
- package/dist/buy/fulfillTransactionHash.js +27 -0
- package/dist/buy/fulfillTransactionHash.test.d.ts +7 -0
- package/dist/buy/fulfillTransactionHash.test.js +46 -0
- package/dist/buy/index.d.ts +17 -0
- package/dist/buy/index.js +17 -0
- package/dist/buy/orderCap.d.ts +29 -0
- package/dist/buy/orderCap.js +35 -0
- package/dist/buy/orderCap.test.d.ts +8 -0
- package/dist/buy/orderCap.test.js +35 -0
- package/dist/buy/postFeeAmount.d.ts +39 -0
- package/dist/buy/postFeeAmount.js +36 -0
- package/dist/buy/postFeeAmount.test.d.ts +9 -0
- package/dist/buy/postFeeAmount.test.js +82 -0
- package/dist/buy/providerSelectionMode.d.ts +5 -0
- package/dist/buy/providerSelectionMode.js +4 -0
- package/dist/buy/settlementReconciliation.d.ts +48 -0
- package/dist/buy/settlementReconciliation.js +65 -0
- package/dist/buy/settlementReconciliation.test.d.ts +9 -0
- package/dist/buy/settlementReconciliation.test.js +164 -0
- package/dist/buy/submitStepDerivations.d.ts +122 -0
- package/dist/buy/submitStepDerivations.js +140 -0
- package/dist/buy/submitStepDerivations.test.d.ts +1 -0
- package/dist/buy/submitStepDerivations.test.js +305 -0
- package/dist/buy/verifiableBanks.d.ts +29 -0
- package/dist/buy/verifiableBanks.js +28 -0
- package/dist/buy/verifiableBanks.test.d.ts +4 -0
- package/dist/buy/verifiableBanks.test.js +31 -0
- package/dist/consent.js +0 -2
- package/dist/consent.storage.test.d.ts +8 -0
- package/dist/consent.storage.test.js +111 -0
- package/dist/consent.test.js +60 -1
- package/dist/contracts.js +3 -1
- package/dist/contracts.test.js +17 -1
- package/dist/conversionRates.test.js +18 -0
- package/dist/currencies.d.ts +6 -0
- package/dist/currencies.js +1 -1
- package/dist/currencies.test.js +29 -2
- package/dist/fees.js +0 -3
- package/dist/gating.test.js +16 -1
- package/dist/ids.test.js +48 -0
- package/dist/payment.d.ts +50 -0
- package/dist/payment.js +72 -51
- package/dist/payment.lookup.test.d.ts +5 -0
- package/dist/payment.lookup.test.js +169 -0
- package/dist/payment.providerLists.test.d.ts +12 -0
- package/dist/payment.providerLists.test.js +40 -0
- package/dist/payment.sendLinks.test.d.ts +9 -0
- package/dist/payment.sendLinks.test.js +91 -0
- package/dist/peer-types.d.ts +5 -78
- package/dist/peer-types.js +5 -31
- package/dist/peer-types.test.js +1 -31
- package/dist/profit.d.ts +8 -0
- package/dist/profit.js +16 -3
- package/dist/profit.test.js +12 -1
- package/dist/reputation.js +4 -6
- package/dist/reputation.test.js +8 -0
- package/dist/time.test.js +26 -5
- package/dist/tokens.d.ts +2 -2
- package/dist/transports.d.ts +53 -2
- package/dist/transports.debug.test.d.ts +9 -0
- package/dist/transports.debug.test.js +95 -0
- package/dist/transports.js +26 -10
- package/dist/transports.test.js +5 -1
- package/dist/units.d.ts +19 -0
- package/dist/units.js +21 -0
- package/dist/units.test.js +31 -1
- package/package.json +6 -2
|
@@ -56,6 +56,13 @@ describe('conversionRates', () => {
|
|
|
56
56
|
assert.ok(usdRates, 'Should have USD rates');
|
|
57
57
|
assert.equal(usdRates.length, 1);
|
|
58
58
|
});
|
|
59
|
+
it('keeps an event whose rate value is missing, with an empty value, instead of dropping the deposit', () => {
|
|
60
|
+
const events = [
|
|
61
|
+
createMockEvent({ currentDepositConversionRateUpdated: { value: null } }),
|
|
62
|
+
];
|
|
63
|
+
const result = processConversionRates({ chainId: base.id, events });
|
|
64
|
+
assert.equal(result.get('venmo')?.get(USD_CONTRACT_ID)?.[0]?.value, '');
|
|
65
|
+
});
|
|
59
66
|
it('should skip events without currentDepositConversionRateUpdated', () => {
|
|
60
67
|
const events = [
|
|
61
68
|
createMockEvent({ currentDepositConversionRateUpdated: undefined }),
|
|
@@ -327,6 +334,17 @@ describe('conversionRates', () => {
|
|
|
327
334
|
const result = getPrimaryConversionRate(new Map());
|
|
328
335
|
assert.equal(result, null);
|
|
329
336
|
});
|
|
337
|
+
it('skips a provider that holds no rates and finds one under a later provider', () => {
|
|
338
|
+
const eurRate = createMockEvent({ currency: EUR_CONTRACT_ID });
|
|
339
|
+
const providerRates = new Map([
|
|
340
|
+
['venmo', new Map()],
|
|
341
|
+
['zelle', new Map([[EUR_CONTRACT_ID, eurRate]])],
|
|
342
|
+
]);
|
|
343
|
+
assert.equal(getPrimaryConversionRate(providerRates)?.currency, EUR_CONTRACT_ID);
|
|
344
|
+
});
|
|
345
|
+
it('returns null when every provider holds no rates', () => {
|
|
346
|
+
assert.equal(getPrimaryConversionRate(new Map([['venmo', new Map()]])), null);
|
|
347
|
+
});
|
|
330
348
|
it('should prioritize USD over other currencies', () => {
|
|
331
349
|
const usdRate = createMockEvent({ currency: USD_CONTRACT_ID });
|
|
332
350
|
const eurRate = createMockEvent({ currency: EUR_CONTRACT_ID });
|
package/dist/currencies.d.ts
CHANGED
|
@@ -113,6 +113,12 @@ export declare class Currency implements CurrencyInfo {
|
|
|
113
113
|
* @param currencyCode - The currency code
|
|
114
114
|
*/
|
|
115
115
|
export declare function generateCurrencyId(currencyCode: string): string;
|
|
116
|
+
/**
|
|
117
|
+
* Get the flag for a country
|
|
118
|
+
* @param countryCode - The country code
|
|
119
|
+
* @returns The flag image path
|
|
120
|
+
*/
|
|
121
|
+
export declare const flagFromCountries: (countryCode: string) => string;
|
|
116
122
|
/**
|
|
117
123
|
* Registry of all supported fiat currencies.
|
|
118
124
|
*
|
package/dist/currencies.js
CHANGED
|
@@ -132,7 +132,7 @@ export function generateCurrencyId(currencyCode) {
|
|
|
132
132
|
* @param countryCode - The country code
|
|
133
133
|
* @returns The flag image path
|
|
134
134
|
*/
|
|
135
|
-
const flagFromCountries = (countryCode) => {
|
|
135
|
+
export const flagFromCountries = (countryCode) => {
|
|
136
136
|
return countries.find((country) => country.code === countryCode)?.flag ?? '';
|
|
137
137
|
};
|
|
138
138
|
/**
|
package/dist/currencies.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
1
|
+
import { describe, it, mock } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { SUPPORTED_CURRENCIES, getCurrencyInfo, getCurrencyByContractId, getCurrencyNameByContractId, getAllSupportedCurrencies, isSupportedCurrency, formatCurrencyAmount, tickerToContractId, generateCurrencyId, formatDefaultRate, formatCurrencyTotal, formatTokenTotal, Currency, } from './currencies.js';
|
|
3
|
+
import { SUPPORTED_CURRENCIES, getCurrencyInfo, getCurrencyByContractId, getCurrencyNameByContractId, getAllSupportedCurrencies, isSupportedCurrency, formatCurrencyAmount, tickerToContractId, generateCurrencyId, formatDefaultRate, formatCurrencyTotal, formatTokenTotal, flagFromCountries, Currency, } from './currencies.js';
|
|
4
4
|
describe('currencies', () => {
|
|
5
5
|
describe('SUPPORTED_CURRENCIES', () => {
|
|
6
6
|
it('should have exactly 36 supported currencies', () => {
|
|
@@ -459,3 +459,30 @@ describe('currencies', () => {
|
|
|
459
459
|
});
|
|
460
460
|
});
|
|
461
461
|
});
|
|
462
|
+
describe('flagFromCountries', () => {
|
|
463
|
+
it('returns the flag of a known country code', () => {
|
|
464
|
+
assert.equal(flagFromCountries('GBP'), SUPPORTED_CURRENCIES.GBP.flag);
|
|
465
|
+
});
|
|
466
|
+
it('returns an empty string for an unknown code, so a currency without a flag still renders', () => {
|
|
467
|
+
assert.equal(flagFromCountries('NOT-A-CODE'), '');
|
|
468
|
+
});
|
|
469
|
+
it('leaves no supported currency without a flag', () => {
|
|
470
|
+
for (const currency of Object.values(SUPPORTED_CURRENCIES)) {
|
|
471
|
+
assert.ok(currency.flag, `${currency.ticker} has no flag`);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
describe('formatCurrencyTotal: an unexpected Intl failure', () => {
|
|
476
|
+
it('rethrows an error that is not the unknown-ticker RangeError, so real faults are not hidden', () => {
|
|
477
|
+
const unseenCurrency = { ...SUPPORTED_CURRENCIES.USD, ticker: 'ZZZ', symbol: 'Z' };
|
|
478
|
+
mock.method(Intl, 'NumberFormat', () => {
|
|
479
|
+
throw new TypeError('boom');
|
|
480
|
+
});
|
|
481
|
+
try {
|
|
482
|
+
assert.throws(() => formatCurrencyTotal({ amount: 100n, currency: unseenCurrency, locale: 'fr-FR' }), TypeError);
|
|
483
|
+
}
|
|
484
|
+
finally {
|
|
485
|
+
mock.restoreAll();
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
});
|
package/dist/fees.js
CHANGED
package/dist/gating.test.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import assert from 'node:assert/strict';
|
|
15
15
|
import { test } from 'node:test';
|
|
16
16
|
import { keccak256, toHex } from 'viem';
|
|
17
|
-
import { buildGatingDigest, hashReferralFees, } from './gating.js';
|
|
17
|
+
import { buildGatingDigest, hashReferralFees, signGatingApproval, } from './gating.js';
|
|
18
18
|
// Shared inputs used to generate every golden vector below.
|
|
19
19
|
const ORCHESTRATOR = '0x888888359E981B5225CA48fbCdCeff702FC3b888';
|
|
20
20
|
const ESCROW = '0x777777779d229cdF3110e9de47943791c26300Ef';
|
|
@@ -104,3 +104,18 @@ test('v3 digest omits caller + referral hash and differs from v4', () => {
|
|
|
104
104
|
const v4 = buildGatingDigest({ ...commonV4, referralFees: [] });
|
|
105
105
|
assert.notEqual(v3, v4);
|
|
106
106
|
});
|
|
107
|
+
test('signGatingApproval hands the signer the raw digest, so the contract recovers the same hash', async () => {
|
|
108
|
+
const input = { ...commonV4, referralFees: [] };
|
|
109
|
+
const received = [];
|
|
110
|
+
const signature = await signGatingApproval({
|
|
111
|
+
signer: {
|
|
112
|
+
signMessage: async ({ message }) => {
|
|
113
|
+
received.push(message.raw);
|
|
114
|
+
return '0xabcd';
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
digest: input,
|
|
118
|
+
});
|
|
119
|
+
assert.deepEqual(received, [buildGatingDigest(input)]);
|
|
120
|
+
assert.equal(signature, '0xabcd');
|
|
121
|
+
});
|
package/dist/ids.test.js
CHANGED
|
@@ -604,4 +604,52 @@ describe('ids', () => {
|
|
|
604
604
|
}, /Unknown chainId|Hex length/);
|
|
605
605
|
});
|
|
606
606
|
});
|
|
607
|
+
describe('orderId.order.parse: unknown chain', () => {
|
|
608
|
+
it('rejects an id whose chain is not supported rather than returning a chain the app cannot serve', () => {
|
|
609
|
+
const id = orderId.order.serialize({
|
|
610
|
+
timestamp: 1000n,
|
|
611
|
+
chainId: 8453n,
|
|
612
|
+
transactionIndex: 5n,
|
|
613
|
+
logIndex: 3n,
|
|
614
|
+
name: 'deposit_received',
|
|
615
|
+
});
|
|
616
|
+
// Overwrite the final 4 bytes (chain id) with an unsupported chain.
|
|
617
|
+
const unsupported = `${id.slice(0, -8)}0000270f`;
|
|
618
|
+
assert.throws(() => orderId.order.parse(unsupported), /Unknown chainId parsed from order id: 9999/);
|
|
619
|
+
});
|
|
620
|
+
it('leaves the name undefined for an event type id that has no registered name', () => {
|
|
621
|
+
const id = orderId.order.serialize({
|
|
622
|
+
timestamp: 1000n,
|
|
623
|
+
chainId: 8453n,
|
|
624
|
+
transactionIndex: 5n,
|
|
625
|
+
logIndex: 3n,
|
|
626
|
+
name: 'deposit_received',
|
|
627
|
+
});
|
|
628
|
+
// The event type is the single byte after timestamp (8) and tx index (4).
|
|
629
|
+
const offset = 2 + (8 + 4) * 2;
|
|
630
|
+
const unnamed = `${id.slice(0, offset)}ff${id.slice(offset + 2)}`;
|
|
631
|
+
const parsed = orderId.order.parse(unnamed);
|
|
632
|
+
assert.equal(parsed.eventTypeId, 255);
|
|
633
|
+
assert.equal(parsed.name, undefined);
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
describe('orderId.stat.parse', () => {
|
|
637
|
+
const stat = {
|
|
638
|
+
type: 'hour',
|
|
639
|
+
event: 'deposit',
|
|
640
|
+
currency: '0x1234567890123456789012345678901234567890123456789012345678901234',
|
|
641
|
+
token: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
|
|
642
|
+
verifier: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
|
|
643
|
+
version: 'v2',
|
|
644
|
+
};
|
|
645
|
+
it('recovers the hour-bucketed timestamp and the chain from a serialized stat id', () => {
|
|
646
|
+
const id = orderId.stat.serialize({ ...stat, timestamp: 3700n, chainId: 8453n });
|
|
647
|
+
assert.deepEqual(orderId.stat.parse(id), { timestamp: 3600n, chainId: 8453 });
|
|
648
|
+
});
|
|
649
|
+
it('rejects an id whose chain is not supported', () => {
|
|
650
|
+
const id = orderId.stat.serialize({ ...stat, timestamp: 3700n, chainId: 8453n });
|
|
651
|
+
const unsupported = `${id.slice(0, -8)}0000270f`;
|
|
652
|
+
assert.throws(() => orderId.stat.parse(unsupported), /Unknown chainId parsed from stat id: 9999/);
|
|
653
|
+
});
|
|
654
|
+
});
|
|
607
655
|
});
|
package/dist/payment.d.ts
CHANGED
|
@@ -23,6 +23,21 @@
|
|
|
23
23
|
* @see {@link currencies} for supported currencies per provider
|
|
24
24
|
*/
|
|
25
25
|
import { type Hex } from "viem";
|
|
26
|
+
/**
|
|
27
|
+
* Pick a value from the Vite (browser) and Node environments.
|
|
28
|
+
*
|
|
29
|
+
* Vite wins over Node. Within Node, the bare name wins over its `VITE_` twin.
|
|
30
|
+
*
|
|
31
|
+
* @param params.name - Variable name without the `VITE_` prefix.
|
|
32
|
+
* @param params.viteEnv - `import.meta.env`, or undefined outside Vite.
|
|
33
|
+
* @param params.nodeEnv - `process.env`, or undefined outside Node.
|
|
34
|
+
* @returns The value, or an empty string when none is set.
|
|
35
|
+
*/
|
|
36
|
+
export declare function readEnvFrom({ name, viteEnv, nodeEnv, }: {
|
|
37
|
+
name: string;
|
|
38
|
+
viteEnv?: Record<string, string | undefined>;
|
|
39
|
+
nodeEnv?: Record<string, string | undefined>;
|
|
40
|
+
}): string;
|
|
26
41
|
/** Returns true if a provider should be hidden from user-facing flows. */
|
|
27
42
|
export declare function isProviderDisabled(key: string, configDisabled?: boolean): boolean;
|
|
28
43
|
import type { TokenInfo } from "./tokens.js";
|
|
@@ -299,6 +314,41 @@ export declare const dataEncoding: (data: string) => string;
|
|
|
299
314
|
export declare const defaultZelleSendLink: (options: SendLinkOptions) => string | null;
|
|
300
315
|
/** Global configuration for all supported sub-providers. Other modules can import this directly. */
|
|
301
316
|
export declare const subProviderConfigs: Partial<Record<ProviderKey, Record<SubProviderKey, PaymentSubProviderConfig>>>;
|
|
317
|
+
/**
|
|
318
|
+
* Verifier addresses for one payment method on one chain.
|
|
319
|
+
*
|
|
320
|
+
* Prefers the v2-specific verifier, so v2 history stays correctly categorized
|
|
321
|
+
* on chains that also run v3. Falls back to the v3 unified verifier for v3-only
|
|
322
|
+
* chains and v3-only payment methods.
|
|
323
|
+
*
|
|
324
|
+
* @param params.verifierKey - Key of the v2 verifier, such as `venmoReclaimVerifier`.
|
|
325
|
+
* @param params.v2Contracts - The chain's v2 contract set, if any.
|
|
326
|
+
* @param params.v3Contracts - The chain's v3 contract set, if any.
|
|
327
|
+
* @returns Verifier addresses; empty when neither generation names one.
|
|
328
|
+
*/
|
|
329
|
+
export declare function resolveVerifierAddresses({ verifierKey, v2Contracts, v3Contracts, }: {
|
|
330
|
+
verifierKey: string;
|
|
331
|
+
v2Contracts?: Partial<Record<string, unknown>>;
|
|
332
|
+
v3Contracts?: {
|
|
333
|
+
unifiedPaymentVerifier?: readonly string[];
|
|
334
|
+
};
|
|
335
|
+
}): string[];
|
|
336
|
+
/**
|
|
337
|
+
* The icon, color and username label a sub-provider shows. Each falls back to
|
|
338
|
+
* the parent rail's value when the sub-provider does not set its own.
|
|
339
|
+
*
|
|
340
|
+
* @param params.subProvider - The sub-provider config.
|
|
341
|
+
* @param params.parent - The parent rail's config.
|
|
342
|
+
* @returns The resolved icon, color and username label.
|
|
343
|
+
*/
|
|
344
|
+
export declare function inheritSubProviderLook({ subProvider, parent, }: {
|
|
345
|
+
subProvider: Pick<PaymentSubProviderConfig, 'icon' | 'color' | 'usernameType'>;
|
|
346
|
+
parent: Pick<PaymentConfig, 'icon' | 'color' | 'usernameType'>;
|
|
347
|
+
}): {
|
|
348
|
+
icon: string;
|
|
349
|
+
color: string;
|
|
350
|
+
usernameType: string;
|
|
351
|
+
};
|
|
302
352
|
/** Payment method icon mappings */
|
|
303
353
|
export declare const providerConfigs: (chainId: ChainId) => Record<ProviderKey, PaymentConfig>;
|
|
304
354
|
/**
|
package/dist/payment.js
CHANGED
|
@@ -27,13 +27,26 @@ import { getAddress, parseUnits, zeroAddress, keccak256, stringToHex, isAddress
|
|
|
27
27
|
* Read an env var from Vite (browser) or Node, returning empty string if not set.
|
|
28
28
|
*/
|
|
29
29
|
function readEnv(name) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
return readEnvFrom({
|
|
31
|
+
name,
|
|
32
|
+
viteEnv: import.meta.env,
|
|
33
|
+
nodeEnv: globalThis.process?.env,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Pick a value from the Vite (browser) and Node environments.
|
|
38
|
+
*
|
|
39
|
+
* Vite wins over Node. Within Node, the bare name wins over its `VITE_` twin.
|
|
40
|
+
*
|
|
41
|
+
* @param params.name - Variable name without the `VITE_` prefix.
|
|
42
|
+
* @param params.viteEnv - `import.meta.env`, or undefined outside Vite.
|
|
43
|
+
* @param params.nodeEnv - `process.env`, or undefined outside Node.
|
|
44
|
+
* @returns The value, or an empty string when none is set.
|
|
45
|
+
*/
|
|
46
|
+
export function readEnvFrom({ name, viteEnv, nodeEnv, }) {
|
|
47
|
+
const viteValue = viteEnv?.[`VITE_${name}`];
|
|
48
|
+
const nodeValue = nodeEnv?.[name] ?? nodeEnv?.[`VITE_${name}`];
|
|
49
|
+
return viteValue ?? nodeValue ?? '';
|
|
37
50
|
}
|
|
38
51
|
/**
|
|
39
52
|
* Parse a comma-separated list of provider keys from an env var.
|
|
@@ -287,6 +300,40 @@ export const subProviderConfigs = {
|
|
|
287
300
|
},
|
|
288
301
|
},
|
|
289
302
|
};
|
|
303
|
+
/**
|
|
304
|
+
* Verifier addresses for one payment method on one chain.
|
|
305
|
+
*
|
|
306
|
+
* Prefers the v2-specific verifier, so v2 history stays correctly categorized
|
|
307
|
+
* on chains that also run v3. Falls back to the v3 unified verifier for v3-only
|
|
308
|
+
* chains and v3-only payment methods.
|
|
309
|
+
*
|
|
310
|
+
* @param params.verifierKey - Key of the v2 verifier, such as `venmoReclaimVerifier`.
|
|
311
|
+
* @param params.v2Contracts - The chain's v2 contract set, if any.
|
|
312
|
+
* @param params.v3Contracts - The chain's v3 contract set, if any.
|
|
313
|
+
* @returns Verifier addresses; empty when neither generation names one.
|
|
314
|
+
*/
|
|
315
|
+
export function resolveVerifierAddresses({ verifierKey, v2Contracts, v3Contracts, }) {
|
|
316
|
+
if (v2Contracts && verifierKey in v2Contracts) {
|
|
317
|
+
const value = v2Contracts[verifierKey];
|
|
318
|
+
return Array.isArray(value) ? value : [];
|
|
319
|
+
}
|
|
320
|
+
return [...(v3Contracts?.unifiedPaymentVerifier ?? [])];
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* The icon, color and username label a sub-provider shows. Each falls back to
|
|
324
|
+
* the parent rail's value when the sub-provider does not set its own.
|
|
325
|
+
*
|
|
326
|
+
* @param params.subProvider - The sub-provider config.
|
|
327
|
+
* @param params.parent - The parent rail's config.
|
|
328
|
+
* @returns The resolved icon, color and username label.
|
|
329
|
+
*/
|
|
330
|
+
export function inheritSubProviderLook({ subProvider, parent, }) {
|
|
331
|
+
return {
|
|
332
|
+
icon: subProvider.icon || parent.icon,
|
|
333
|
+
color: subProvider.color || parent.color,
|
|
334
|
+
usernameType: subProvider.usernameType || parent.usernameType,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
290
337
|
/** Payment method icon mappings */
|
|
291
338
|
export const providerConfigs = (chainId) => {
|
|
292
339
|
const v2Contracts = contracts.v2[chainId];
|
|
@@ -296,18 +343,7 @@ export const providerConfigs = (chainId) => {
|
|
|
296
343
|
* Prioritizes v2-specific verifiers over v3 unified verifier.
|
|
297
344
|
* This ensures v2 historical data is correctly categorized even on chains with both v2 and v3 contracts.
|
|
298
345
|
*/
|
|
299
|
-
const getV2Verifiers = (verifierKey) => {
|
|
300
|
-
// First check if v2 contracts have specific verifiers for this method
|
|
301
|
-
if (v2Contracts && verifierKey in v2Contracts) {
|
|
302
|
-
const value = v2Contracts[verifierKey];
|
|
303
|
-
return (Array.isArray(value) ? value : []);
|
|
304
|
-
}
|
|
305
|
-
// Fall back to v3 unified verifier (for v3-only chains or v3-only payment methods)
|
|
306
|
-
if (v3Contracts && 'unifiedPaymentVerifier' in v3Contracts) {
|
|
307
|
-
return v3Contracts.unifiedPaymentVerifier || [];
|
|
308
|
-
}
|
|
309
|
-
return [];
|
|
310
|
-
};
|
|
346
|
+
const getV2Verifiers = (verifierKey) => resolveVerifierAddresses({ verifierKey, v2Contracts, v3Contracts });
|
|
311
347
|
return {
|
|
312
348
|
venmo: {
|
|
313
349
|
key: providers.VENMO,
|
|
@@ -821,9 +857,7 @@ export const providerByContractId = (chainId) => {
|
|
|
821
857
|
key: subProvider.key,
|
|
822
858
|
contractId: providerKeyToContractId(subProvider.key),
|
|
823
859
|
name: subProvider.name,
|
|
824
|
-
|
|
825
|
-
color: subProvider.color || config.color,
|
|
826
|
-
usernameType: subProvider.usernameType || config.usernameType,
|
|
860
|
+
...inheritSubProviderLook({ subProvider, parent: config }),
|
|
827
861
|
v2Verifiers: subProvider.v2Verifiers(chainId),
|
|
828
862
|
subProviders: new Set(),
|
|
829
863
|
};
|
|
@@ -864,14 +898,8 @@ export const verifierGetsKey = (chainId, verifierAddress) => {
|
|
|
864
898
|
// If this is a 32-byte hash (66 chars including 0x), look it up directly in the map
|
|
865
899
|
if (verifierAddress?.length === 66) {
|
|
866
900
|
const key = paymentVerifierToKey(chainId).get(verifierAddress.toLowerCase());
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
// Fallback: try looking it up via contractId
|
|
870
|
-
const contractProvider = getProviderByContractId(chainId ?? base.id, verifierAddress);
|
|
871
|
-
if (contractProvider) {
|
|
872
|
-
return contractProvider.key;
|
|
873
|
-
}
|
|
874
|
-
return providers.MANUAL;
|
|
901
|
+
// The map holds every provider and sub-provider contract id, so a miss is unknown.
|
|
902
|
+
return key ?? providers.MANUAL;
|
|
875
903
|
}
|
|
876
904
|
// Otherwise treat it as an address
|
|
877
905
|
try {
|
|
@@ -941,7 +969,7 @@ export const getPaymentMethodName = (chainId, verifierAddress) => {
|
|
|
941
969
|
if (zelleSubConfig?.name) {
|
|
942
970
|
return zelleSubConfig.name;
|
|
943
971
|
}
|
|
944
|
-
if (
|
|
972
|
+
if (verifierAddress.length === 66) {
|
|
945
973
|
const contractProvider = getProviderByContractId(chainId ?? base.id, verifierAddress);
|
|
946
974
|
if (contractProvider) {
|
|
947
975
|
return contractProvider.name;
|
|
@@ -949,7 +977,7 @@ export const getPaymentMethodName = (chainId, verifierAddress) => {
|
|
|
949
977
|
return 'Unknown';
|
|
950
978
|
}
|
|
951
979
|
const config = providerConfigs(chainId ?? base.id)[verifierAddress]
|
|
952
|
-
?? (
|
|
980
|
+
?? (verifierAddress.length === 42
|
|
953
981
|
? getPaymentMethodConfig(chainId ?? base.id, verifierAddress)
|
|
954
982
|
: null);
|
|
955
983
|
return config?.name || 'Unknown';
|
|
@@ -998,7 +1026,7 @@ export function calculateEffectiveMinimum(chainId, userMinAmountWei, tokenInfo,
|
|
|
998
1026
|
* @returns The minimum limit
|
|
999
1027
|
*/
|
|
1000
1028
|
export function getPaymentProviderMinimum(chainId, key) {
|
|
1001
|
-
return getPaymentMethodMinimum(chainId, key)
|
|
1029
|
+
return getPaymentMethodMinimum(chainId, key);
|
|
1002
1030
|
}
|
|
1003
1031
|
/**
|
|
1004
1032
|
* External/unregistered payment methods that are known but not in providerConfigs.
|
|
@@ -1253,7 +1281,7 @@ export function getCurrenciesForProvider(config) {
|
|
|
1253
1281
|
*/
|
|
1254
1282
|
export function currencyCapableProviders(chainId, currency) {
|
|
1255
1283
|
const configs = providerConfigs(chainId);
|
|
1256
|
-
return verifiablePaymentMethods(chainId).filter((key) => configs[key]
|
|
1284
|
+
return verifiablePaymentMethods(chainId).filter((key) => configs[key].list.includes(currency.contractId));
|
|
1257
1285
|
}
|
|
1258
1286
|
/**
|
|
1259
1287
|
* Whether a provider can actually serve a buyer paying in `currency` on this
|
|
@@ -1292,12 +1320,10 @@ export function selectVolumeDefaultProvider(args) {
|
|
|
1292
1320
|
if (capableKeys.has(key))
|
|
1293
1321
|
return key;
|
|
1294
1322
|
}
|
|
1295
|
-
return capable[0]
|
|
1323
|
+
return capable[0];
|
|
1296
1324
|
}
|
|
1297
1325
|
/** Set of all sub-provider keys for fast lookup */
|
|
1298
1326
|
const subProviderKeySet = new Set(Object.values(subProviders));
|
|
1299
|
-
/** Set of all main provider keys for fast lookup */
|
|
1300
|
-
const providerKeySet = new Set(Object.values(providers));
|
|
1301
1327
|
/**
|
|
1302
1328
|
* Check if a provider key is a sub-provider (e.g., 'zelle-chase', 'zelle-bofa')
|
|
1303
1329
|
* @param key - The provider key to check
|
|
@@ -1317,11 +1343,8 @@ export function getParentProviderKey(key) {
|
|
|
1317
1343
|
return null;
|
|
1318
1344
|
}
|
|
1319
1345
|
// Sub-provider keys follow the pattern '{parent}-{subName}'
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
return parentKey;
|
|
1323
|
-
}
|
|
1324
|
-
return null;
|
|
1346
|
+
// Every sub-provider's parent is a provider key (pinned by a test).
|
|
1347
|
+
return key.split('-')[0];
|
|
1325
1348
|
}
|
|
1326
1349
|
/**
|
|
1327
1350
|
* Collapse a sub-provider key to the rail it belongs to.
|
|
@@ -1349,8 +1372,6 @@ export function getParentProviderKey(key) {
|
|
|
1349
1372
|
* @returns The rail key, e.g. `'zelle'`.
|
|
1350
1373
|
*/
|
|
1351
1374
|
export function resolveRailProviderKey(key) {
|
|
1352
|
-
if (!isSubProviderKey(key))
|
|
1353
|
-
return key;
|
|
1354
1375
|
return getParentProviderKey(key) ?? key;
|
|
1355
1376
|
}
|
|
1356
1377
|
/**
|
|
@@ -1420,14 +1441,14 @@ export function getProviderIconStack(chainId, key) {
|
|
|
1420
1441
|
// Check if this is a sub-provider
|
|
1421
1442
|
const parentKey = getParentProviderKey(key);
|
|
1422
1443
|
if (parentKey) {
|
|
1444
|
+
// A sub-provider key always has a parent config and its own entry.
|
|
1423
1445
|
const parentConfig = configs[parentKey];
|
|
1424
|
-
const subConfig = subProviderConfigs[parentKey]
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
}
|
|
1446
|
+
const subConfig = subProviderConfigs[parentKey][key];
|
|
1447
|
+
const { icon, color } = inheritSubProviderLook({ subProvider: subConfig, parent: parentConfig });
|
|
1448
|
+
return [
|
|
1449
|
+
{ icon: parentConfig.icon, color: parentConfig.color, name: parentConfig.name },
|
|
1450
|
+
{ icon, color, name: subConfig.name },
|
|
1451
|
+
];
|
|
1431
1452
|
}
|
|
1432
1453
|
// Main provider - return single icon
|
|
1433
1454
|
const config = configs[key];
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for payment lookups by key, hash and address, and for
|
|
3
|
+
* the pure helpers that resolve verifiers, environment values and sub-provider looks.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { base, pulsechain } from 'viem/chains';
|
|
8
|
+
import { getParentProviderKey, getPaymentMethodConfig, getPaymentMethodMinimum, getPaymentMethodName, getPaymentProviderMinimum, getProviderIconStack, inheritSubProviderLook, providerConfigs, providerKeyToContractId, providers, readEnvFrom, resolveVerifierAddresses, subProviders, verifierGetsKey, } from './payment.js';
|
|
9
|
+
const A = '0x1111111111111111111111111111111111111111';
|
|
10
|
+
const B = '0x2222222222222222222222222222222222222222';
|
|
11
|
+
describe('readEnvFrom', () => {
|
|
12
|
+
it('prefers the Vite value over Node, because the browser build is the deployed one', () => {
|
|
13
|
+
const value = readEnvFrom({
|
|
14
|
+
name: 'X',
|
|
15
|
+
viteEnv: { VITE_X: 'vite' },
|
|
16
|
+
nodeEnv: { X: 'node' },
|
|
17
|
+
});
|
|
18
|
+
assert.equal(value, 'vite');
|
|
19
|
+
});
|
|
20
|
+
it('prefers the bare Node name over its VITE_ twin', () => {
|
|
21
|
+
assert.equal(readEnvFrom({ name: 'X', nodeEnv: { X: 'bare', VITE_X: 'twin' } }), 'bare');
|
|
22
|
+
});
|
|
23
|
+
it('falls back to the VITE_ twin in Node', () => {
|
|
24
|
+
assert.equal(readEnvFrom({ name: 'X', nodeEnv: { VITE_X: 'twin' } }), 'twin');
|
|
25
|
+
});
|
|
26
|
+
it('returns an empty string when nothing is set or no environment exists', () => {
|
|
27
|
+
assert.equal(readEnvFrom({ name: 'X', viteEnv: {}, nodeEnv: {} }), '');
|
|
28
|
+
assert.equal(readEnvFrom({ name: 'X' }), '');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('resolveVerifierAddresses', () => {
|
|
32
|
+
it('returns the v2-specific verifiers when the v2 set names the method', () => {
|
|
33
|
+
const result = resolveVerifierAddresses({
|
|
34
|
+
verifierKey: 'venmoReclaimVerifier',
|
|
35
|
+
v2Contracts: { venmoReclaimVerifier: [A] },
|
|
36
|
+
v3Contracts: { unifiedPaymentVerifier: [B] },
|
|
37
|
+
});
|
|
38
|
+
assert.deepEqual(result, [A]);
|
|
39
|
+
});
|
|
40
|
+
it('returns nothing, not the v3 verifier, when the v2 entry is not a list', () => {
|
|
41
|
+
// A v2 key that holds a single address (like `usdc`) is not a verifier list.
|
|
42
|
+
// Falling to v3 there would mislabel v2 history as v3.
|
|
43
|
+
const result = resolveVerifierAddresses({
|
|
44
|
+
verifierKey: 'usdc',
|
|
45
|
+
v2Contracts: { usdc: A },
|
|
46
|
+
v3Contracts: { unifiedPaymentVerifier: [B] },
|
|
47
|
+
});
|
|
48
|
+
assert.deepEqual(result, []);
|
|
49
|
+
});
|
|
50
|
+
it('falls back to the v3 unified verifier when v2 does not name the method', () => {
|
|
51
|
+
const result = resolveVerifierAddresses({
|
|
52
|
+
verifierKey: 'paypalReclaimVerifier',
|
|
53
|
+
v2Contracts: { venmoReclaimVerifier: [A] },
|
|
54
|
+
v3Contracts: { unifiedPaymentVerifier: [B] },
|
|
55
|
+
});
|
|
56
|
+
assert.deepEqual(result, [B]);
|
|
57
|
+
});
|
|
58
|
+
it('falls back to the v3 unified verifier on a chain with no v2 set', () => {
|
|
59
|
+
assert.deepEqual(resolveVerifierAddresses({ verifierKey: 'k', v3Contracts: { unifiedPaymentVerifier: [B] } }), [B]);
|
|
60
|
+
});
|
|
61
|
+
it('returns an empty list when neither generation names a verifier', () => {
|
|
62
|
+
assert.deepEqual(resolveVerifierAddresses({ verifierKey: 'k' }), []);
|
|
63
|
+
assert.deepEqual(resolveVerifierAddresses({ verifierKey: 'k', v3Contracts: {} }), []);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe('inheritSubProviderLook', () => {
|
|
67
|
+
const parent = { icon: 'parent-icon', color: 'parent-color', usernameType: 'Parent Label' };
|
|
68
|
+
it('uses the sub-provider values when it sets them', () => {
|
|
69
|
+
const look = inheritSubProviderLook({
|
|
70
|
+
subProvider: { icon: 'sub-icon', color: 'sub-color', usernameType: 'Sub Label' },
|
|
71
|
+
parent,
|
|
72
|
+
});
|
|
73
|
+
assert.deepEqual(look, { icon: 'sub-icon', color: 'sub-color', usernameType: 'Sub Label' });
|
|
74
|
+
});
|
|
75
|
+
it('inherits each value from the parent rail when the sub-provider omits it', () => {
|
|
76
|
+
assert.deepEqual(inheritSubProviderLook({ subProvider: {}, parent }), parent);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('getParentProviderKey', () => {
|
|
80
|
+
it('names a real rail for every sub-provider, so the rail can always be looked up', () => {
|
|
81
|
+
const railKeys = new Set(Object.values(providers));
|
|
82
|
+
for (const subProviderKey of Object.values(subProviders)) {
|
|
83
|
+
const parent = getParentProviderKey(subProviderKey);
|
|
84
|
+
assert.ok(parent !== null && railKeys.has(parent), `${subProviderKey} has no parent rail`);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
it('returns null for a key that is not a sub-provider', () => {
|
|
88
|
+
assert.equal(getParentProviderKey('venmo'), null);
|
|
89
|
+
assert.equal(getParentProviderKey('zelle-unknown'), null);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('getProviderIconStack', () => {
|
|
93
|
+
it('stacks the parent rail icon under the bank icon for a sub-provider', () => {
|
|
94
|
+
const configs = providerConfigs(base.id);
|
|
95
|
+
const stack = getProviderIconStack(base.id, subProviders.CHASE);
|
|
96
|
+
assert.equal(stack.length, 2);
|
|
97
|
+
assert.equal(stack[0].icon, configs.zelle.icon);
|
|
98
|
+
assert.equal(stack[1].name, 'Chase');
|
|
99
|
+
assert.equal(stack[1].icon, 'simple-icons:chase');
|
|
100
|
+
// Chase sets no color of its own, so it takes the rail's.
|
|
101
|
+
assert.equal(stack[1].color, configs.zelle.color);
|
|
102
|
+
});
|
|
103
|
+
it('returns a single entry for a main rail', () => {
|
|
104
|
+
const stack = getProviderIconStack(base.id, providers.VENMO);
|
|
105
|
+
assert.equal(stack.length, 1);
|
|
106
|
+
assert.equal(stack[0].name, 'Venmo');
|
|
107
|
+
});
|
|
108
|
+
it('names an unrecognised key Unknown, with the manual icon, instead of throwing', () => {
|
|
109
|
+
const stack = getProviderIconStack(base.id, 'nonsense');
|
|
110
|
+
assert.deepEqual(stack, [
|
|
111
|
+
{ icon: providerConfigs(base.id).manual.icon, color: providerConfigs(base.id).manual.color, name: 'Unknown' },
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('minimum limits', () => {
|
|
116
|
+
it('reads the minimum for a provider on the default chain when no chain is given', () => {
|
|
117
|
+
assert.equal(getPaymentMethodMinimum(null, providers.VENMO), providerConfigs(base.id).venmo.limits.min);
|
|
118
|
+
});
|
|
119
|
+
it('returns the same minimum through the provider-level helper', () => {
|
|
120
|
+
assert.equal(getPaymentProviderMinimum(pulsechain.id, providers.VENMO), getPaymentMethodMinimum(pulsechain.id, providers.VENMO));
|
|
121
|
+
});
|
|
122
|
+
it('returns zero for an unknown provider rather than NaN', () => {
|
|
123
|
+
assert.equal(getPaymentMethodMinimum(base.id, 'nonsense'), 0);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('lookup by payment-method hash', () => {
|
|
127
|
+
const zelleChaseHash = providerKeyToContractId(subProviders.CHASE);
|
|
128
|
+
const unknownHash = `0x${'ab'.repeat(32)}`;
|
|
129
|
+
it('verifierGetsKey resolves a sub-provider hash through the contract id map', () => {
|
|
130
|
+
assert.equal(verifierGetsKey(base.id, zelleChaseHash), subProviders.CHASE);
|
|
131
|
+
});
|
|
132
|
+
it('verifierGetsKey defaults to the base chain when none is given', () => {
|
|
133
|
+
assert.equal(verifierGetsKey(null, providerKeyToContractId(providers.VENMO)), providers.VENMO);
|
|
134
|
+
});
|
|
135
|
+
it('getPaymentMethodConfig returns the config for a known hash and null for an unknown one', () => {
|
|
136
|
+
assert.equal(getPaymentMethodConfig(base.id, providerKeyToContractId(providers.WISE))?.key, providers.WISE);
|
|
137
|
+
assert.equal(getPaymentMethodConfig(null, providerKeyToContractId(providers.WISE))?.key, providers.WISE);
|
|
138
|
+
assert.equal(getPaymentMethodConfig(base.id, unknownHash), null);
|
|
139
|
+
});
|
|
140
|
+
it('getPaymentMethodConfig returns null for text that is neither an address nor a hash', () => {
|
|
141
|
+
assert.equal(getPaymentMethodConfig(base.id, 'venmo'), null);
|
|
142
|
+
});
|
|
143
|
+
it('getPaymentMethodConfig finds a v2 verifier address on every chain when none is given', () => {
|
|
144
|
+
const venmoVerifier = providerConfigs(base.id).venmo.v2Verifiers[0];
|
|
145
|
+
assert.equal(getPaymentMethodConfig(null, venmoVerifier)?.key, providers.VENMO);
|
|
146
|
+
});
|
|
147
|
+
it('getPaymentMethodName names a hash, falls back to Unknown for a strange one', () => {
|
|
148
|
+
assert.equal(getPaymentMethodName(base.id, zelleChaseHash), 'Chase');
|
|
149
|
+
assert.equal(getPaymentMethodName(base.id, unknownHash), 'Unknown');
|
|
150
|
+
});
|
|
151
|
+
it('getPaymentMethodName searches the default chain for every kind of input when none is given', () => {
|
|
152
|
+
const venmoVerifier = providerConfigs(base.id).venmo.v2Verifiers[0];
|
|
153
|
+
assert.equal(getPaymentMethodName(null, zelleChaseHash), 'Chase');
|
|
154
|
+
assert.equal(getPaymentMethodName(null, venmoVerifier), 'Venmo');
|
|
155
|
+
assert.equal(getPaymentMethodName(null, 'nonsense'), 'Unknown');
|
|
156
|
+
});
|
|
157
|
+
it('getPaymentMethodName names a provider key, on the default chain when none is given', () => {
|
|
158
|
+
assert.equal(getPaymentMethodName(null, providers.VENMO), 'Venmo');
|
|
159
|
+
});
|
|
160
|
+
it('getPaymentMethodName names a sub-provider key', () => {
|
|
161
|
+
assert.equal(getPaymentMethodName(base.id, subProviders.CITI), 'Citi');
|
|
162
|
+
});
|
|
163
|
+
it('getPaymentMethodName names a verifier address, and says Unknown for a stranger', () => {
|
|
164
|
+
const venmoVerifier = providerConfigs(base.id).venmo.v2Verifiers[0];
|
|
165
|
+
assert.equal(getPaymentMethodName(base.id, venmoVerifier), 'Venmo');
|
|
166
|
+
assert.equal(getPaymentMethodName(base.id, '0x0000000000000000000000000000000000000dEaD'.slice(0, 42)), 'Unknown');
|
|
167
|
+
assert.equal(getPaymentMethodName(base.id, 'nonsense'), 'Unknown');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the deployment switches that hide or force-show a
|
|
3
|
+
* payment rail.
|
|
4
|
+
*
|
|
5
|
+
* `DISABLED_PROVIDERS` and `ENABLED_PROVIDERS` are read once, on the first call,
|
|
6
|
+
* and cached in module state. Each test file runs in its own process, so this
|
|
7
|
+
* file sets both variables before any call and observes one fixed deployment:
|
|
8
|
+
*
|
|
9
|
+
* - disabled: `PayPal` and `zelle` (mixed case and padded, as a human writes it)
|
|
10
|
+
* - enabled: `venmo` and `paypal`
|
|
11
|
+
*/
|
|
12
|
+
export {};
|