@provex/utils 1.9.2-rc.20260722211737.69a2b06 → 1.9.2-rc.20260723142825.d415ded
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/payment.d.ts +29 -11
- package/dist/payment.js +32 -12
- package/dist/payment.test.js +33 -1
- package/dist/transports.d.ts +24 -3
- package/dist/transports.js +25 -4
- package/dist/transports.test.js +25 -5
- package/package.json +1 -1
package/dist/payment.d.ts
CHANGED
|
@@ -542,19 +542,37 @@ export declare function isSubProviderKey(key: string): key is SubProviderKey;
|
|
|
542
542
|
*/
|
|
543
543
|
export declare function getParentProviderKey(key: string): ProviderKey | null;
|
|
544
544
|
/**
|
|
545
|
-
*
|
|
545
|
+
* Collapse a sub-provider key to the rail it belongs to.
|
|
546
|
+
*
|
|
547
|
+
* A sub-provider names a bank the buyer captures from — it is a routing detail
|
|
548
|
+
* of the capture step, not an identity of its own. Everything keyed by rail
|
|
549
|
+
* rather than by bank MUST collapse through here:
|
|
550
|
+
*
|
|
551
|
+
* - **The on-chain payment method.** A Zelle attestation is issued for the
|
|
552
|
+
* rail: every bank template in `@zkp2p/providers` declares
|
|
553
|
+
* `metadata.platform: "zelle"`, and the enclave verify route is
|
|
554
|
+
* `zelle/transfer_zelle_{bank}` — the bank rides in the ACTION type, never
|
|
555
|
+
* the platform. So `keccak256("zelle")` is the only payment method a Zelle
|
|
556
|
+
* payment can ever satisfy. Deposits that attached `zelle-chase` and friends
|
|
557
|
+
* accept intents no attestation can fulfil.
|
|
558
|
+
* - **Off-chain maker records.** Makers register once at rail level, so a
|
|
559
|
+
* lookup keyed by `(provider, payeeDetailsHash)` misses the row unless the
|
|
560
|
+
* bank is collapsed away. Skipping this in verify-intent once produced a 400
|
|
561
|
+
* "maker contact could not be verified" for every buy on such a deposit.
|
|
562
|
+
*
|
|
563
|
+
* Non-sub-provider keys pass through unchanged, so this is safe to apply
|
|
564
|
+
* unconditionally.
|
|
546
565
|
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
566
|
+
* @param key - Provider or sub-provider key, e.g. `'zelle-chase'`.
|
|
567
|
+
* @returns The rail key, e.g. `'zelle'`.
|
|
568
|
+
*/
|
|
569
|
+
export declare function resolveRailProviderKey(key: string): string;
|
|
570
|
+
/**
|
|
571
|
+
* Canonicalize a provider key for off-chain maker-record lookups.
|
|
552
572
|
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
* by skipping it and produced 400 "maker contact could not be verified"
|
|
557
|
-
* for every buy on a sub-provider deposit.
|
|
573
|
+
* @deprecated Prefer {@link resolveRailProviderKey}, which names the concept
|
|
574
|
+
* both call-site families share. Retained because the off-chain lookup sites
|
|
575
|
+
* read better with an API-flavoured name.
|
|
558
576
|
*/
|
|
559
577
|
export declare function resolveApiProviderKey(key: string): string;
|
|
560
578
|
/**
|
package/dist/payment.js
CHANGED
|
@@ -1200,25 +1200,45 @@ export function getParentProviderKey(key) {
|
|
|
1200
1200
|
return null;
|
|
1201
1201
|
}
|
|
1202
1202
|
/**
|
|
1203
|
-
*
|
|
1203
|
+
* Collapse a sub-provider key to the rail it belongs to.
|
|
1204
|
+
*
|
|
1205
|
+
* A sub-provider names a bank the buyer captures from — it is a routing detail
|
|
1206
|
+
* of the capture step, not an identity of its own. Everything keyed by rail
|
|
1207
|
+
* rather than by bank MUST collapse through here:
|
|
1208
|
+
*
|
|
1209
|
+
* - **The on-chain payment method.** A Zelle attestation is issued for the
|
|
1210
|
+
* rail: every bank template in `@zkp2p/providers` declares
|
|
1211
|
+
* `metadata.platform: "zelle"`, and the enclave verify route is
|
|
1212
|
+
* `zelle/transfer_zelle_{bank}` — the bank rides in the ACTION type, never
|
|
1213
|
+
* the platform. So `keccak256("zelle")` is the only payment method a Zelle
|
|
1214
|
+
* payment can ever satisfy. Deposits that attached `zelle-chase` and friends
|
|
1215
|
+
* accept intents no attestation can fulfil.
|
|
1216
|
+
* - **Off-chain maker records.** Makers register once at rail level, so a
|
|
1217
|
+
* lookup keyed by `(provider, payeeDetailsHash)` misses the row unless the
|
|
1218
|
+
* bank is collapsed away. Skipping this in verify-intent once produced a 400
|
|
1219
|
+
* "maker contact could not be verified" for every buy on such a deposit.
|
|
1204
1220
|
*
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1207
|
-
* (`zelle-chase`, `zelle-bofa`, …). Every off-chain lookup keyed by
|
|
1208
|
-
* `(provider, payeeDetailsHash)` MUST collapse the sub-provider back to
|
|
1209
|
-
* its parent or it will miss the row.
|
|
1221
|
+
* Non-sub-provider keys pass through unchanged, so this is safe to apply
|
|
1222
|
+
* unconditionally.
|
|
1210
1223
|
*
|
|
1211
|
-
*
|
|
1212
|
-
*
|
|
1213
|
-
* normalization can't drift between call sites — verify-intent regressed
|
|
1214
|
-
* by skipping it and produced 400 "maker contact could not be verified"
|
|
1215
|
-
* for every buy on a sub-provider deposit.
|
|
1224
|
+
* @param key - Provider or sub-provider key, e.g. `'zelle-chase'`.
|
|
1225
|
+
* @returns The rail key, e.g. `'zelle'`.
|
|
1216
1226
|
*/
|
|
1217
|
-
export function
|
|
1227
|
+
export function resolveRailProviderKey(key) {
|
|
1218
1228
|
if (!isSubProviderKey(key))
|
|
1219
1229
|
return key;
|
|
1220
1230
|
return getParentProviderKey(key) ?? key;
|
|
1221
1231
|
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Canonicalize a provider key for off-chain maker-record lookups.
|
|
1234
|
+
*
|
|
1235
|
+
* @deprecated Prefer {@link resolveRailProviderKey}, which names the concept
|
|
1236
|
+
* both call-site families share. Retained because the off-chain lookup sites
|
|
1237
|
+
* read better with an API-flavoured name.
|
|
1238
|
+
*/
|
|
1239
|
+
export function resolveApiProviderKey(key) {
|
|
1240
|
+
return resolveRailProviderKey(key);
|
|
1241
|
+
}
|
|
1222
1242
|
/**
|
|
1223
1243
|
* Get the extension platform key for a provider or sub-provider.
|
|
1224
1244
|
* For sub-providers like 'zelle-chase', returns the extensionPlatform from config (e.g., 'chase').
|
package/dist/payment.test.js
CHANGED
|
@@ -2,7 +2,7 @@ import { describe, it, test } from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { base } from 'viem/chains';
|
|
4
4
|
import { getAddress } from 'viem';
|
|
5
|
-
import { providerConfigs, getCurrenciesForProvider, providers, verifierGetsKey, providerKeyToContractId, resolveApiProviderKey, getProviderPaymentMethodIds, computeProviderAvailability, availableProviderConfigs, subProviders, isDayResolutionRail } from './payment.js';
|
|
5
|
+
import { providerConfigs, getCurrenciesForProvider, providers, verifierGetsKey, providerKeyToContractId, resolveApiProviderKey, resolveRailProviderKey, getProviderPaymentMethodIds, computeProviderAvailability, availableProviderConfigs, subProviders, isDayResolutionRail } from './payment.js';
|
|
6
6
|
import { paymentVerifierToKey, providerByContractId, getProviderByContractId, getProviderNameByContractId, providerKeyToVerifiers, providerKeyToVerifiersMap, getPaymentMethodConfig, getPaymentMethodName, getPaymentMethodMinimum, getPaymentProviderMinimum, calculateEffectiveMinimum, isPaymentMethodKnown, isPaymentMethodIdKnown, isExternalPaymentMethod, getExternalPaymentMethod, externalPaymentMethods, getExtensionPlatform, getParentProviderKey, isSubProviderKey, providerRequiresMetadataIndex, getProviderIconStack, getPaymentIcon, getPlatformRisk, fromPeerTier, tierToPeerTier, tiers, getAllPaymentMethods, verifiablePaymentMethods, } from './payment.js';
|
|
7
7
|
import { getDefaultToken } from './tokens.js';
|
|
8
8
|
import { SUPPORTED_CURRENCIES } from './currencies.js';
|
|
@@ -206,6 +206,38 @@ describe('resolveApiProviderKey', () => {
|
|
|
206
206
|
assert.equal(resolveApiProviderKey('unknown-sub'), 'unknown-sub');
|
|
207
207
|
});
|
|
208
208
|
});
|
|
209
|
+
/**
|
|
210
|
+
* Tests for resolveRailProviderKey — the same collapse, applied to the
|
|
211
|
+
* ON-CHAIN payment method. A Zelle attestation is issued for the rail: every
|
|
212
|
+
* bank template declares `metadata.platform: "zelle"` and the enclave verify
|
|
213
|
+
* route is `zelle/transfer_zelle_{bank}`, so the bank never reaches the
|
|
214
|
+
* payment method. Deposits that attached bank-level methods accepted intents
|
|
215
|
+
* that no attestation could fulfil — they sat until they expired.
|
|
216
|
+
*/
|
|
217
|
+
describe('resolveRailProviderKey', () => {
|
|
218
|
+
it('collapses every Zelle bank to the rail', () => {
|
|
219
|
+
// The full set, not a sample: one bank left un-collapsed is one bank whose
|
|
220
|
+
// deposits silently strand every intent signalled against them.
|
|
221
|
+
for (const bank of Object.values(subProviders)) {
|
|
222
|
+
assert.equal(resolveRailProviderKey(bank), providers.ZELLE);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
it('leaves rails that have no banks untouched', () => {
|
|
226
|
+
assert.equal(resolveRailProviderKey(providers.VENMO), 'venmo');
|
|
227
|
+
assert.equal(resolveRailProviderKey(providers.CASHAPP), 'cashapp');
|
|
228
|
+
assert.equal(resolveRailProviderKey(providers.ZELLE), 'zelle');
|
|
229
|
+
});
|
|
230
|
+
it('is idempotent, so applying it twice cannot drift', () => {
|
|
231
|
+
// Call sites apply it at the write boundary; a second application (say a
|
|
232
|
+
// caller that already normalized) must be a no-op.
|
|
233
|
+
assert.equal(resolveRailProviderKey(resolveRailProviderKey('zelle-chase')), resolveRailProviderKey('zelle-chase'));
|
|
234
|
+
});
|
|
235
|
+
it('maps the rail key to the payment method id registered on-chain', () => {
|
|
236
|
+
// Pins the migration target: keccak256("zelle"). If this changes, deposits
|
|
237
|
+
// would be attaching a method the registry does not route.
|
|
238
|
+
assert.equal(providerKeyToContractId(resolveRailProviderKey('zelle-bofa')), '0xf752c7d19698ecb0bb8988abf9b9a53a4c3657f3bc8850a6fb59fdf3e3ce8cd3');
|
|
239
|
+
});
|
|
240
|
+
});
|
|
209
241
|
/**
|
|
210
242
|
* Tests for providerConfigs - payment provider configuration
|
|
211
243
|
*/
|
package/dist/transports.d.ts
CHANGED
|
@@ -16,15 +16,36 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { type Transport } from 'viem';
|
|
18
18
|
import { type ChainId } from './chain.js';
|
|
19
|
+
/**
|
|
20
|
+
* valve.city PulseChain endpoint — one URL fronting a load-balanced node pool.
|
|
21
|
+
*
|
|
22
|
+
* `vk_demo` is the PUBLIC, RATE-LIMITED demo key. It is the right default for
|
|
23
|
+
* local development and for anyone building against this package without
|
|
24
|
+
* credentials, but a keyed endpoint (`https://one.valve.city/rpc/<key>/evm/369`)
|
|
25
|
+
* belongs in front of production traffic — a shared demo key is a shared rate
|
|
26
|
+
* limit. Override per-deployment via the `VITE_RPC_URLS_369` env (caller
|
|
27
|
+
* overrides are tried ahead of this list) rather than editing this constant.
|
|
28
|
+
*
|
|
29
|
+
* Kept in sync with `DEFAULT_MSGBOARD_ENDPOINT` in @provex/onramp, which points
|
|
30
|
+
* at the same host: one keyed valve.city endpoint serves both `msgboard_*` and
|
|
31
|
+
* `eth_*` methods.
|
|
32
|
+
*/
|
|
33
|
+
export declare const VALVE_CITY_PULSECHAIN_RPC = "https://one.valve.city/rpc/vk_demo/evm/369";
|
|
19
34
|
/**
|
|
20
35
|
* Provex-curated fallback RPC URLs per chain, applied after caller overrides
|
|
21
36
|
* but before the viem chain default.
|
|
22
37
|
*
|
|
23
38
|
* @remarks
|
|
24
|
-
* - Pulsechain (369):
|
|
25
|
-
*
|
|
39
|
+
* - Pulsechain (369): valve.city is listed first — it load balances across a
|
|
40
|
+
* pool of PulseChain nodes behind one endpoint, so it absorbs a single node
|
|
41
|
+
* going slow or stale without the client noticing. g4mm4 is retained behind
|
|
42
|
+
* it as an independent operator: valve.city's internal balancing cannot help
|
|
43
|
+
* if the endpoint ITSELF is unreachable, so a second operator is what makes
|
|
44
|
+
* this list a real fallback rather than one host with extra steps.
|
|
45
|
+
* Order is a hint, not a guarantee — {@link createChainTransport} ranks by
|
|
46
|
+
* measured latency, so the fastest healthy endpoint serves traffic either way.
|
|
26
47
|
* - Base (8453): no built-in fallback — Base's default endpoint is reliable
|
|
27
|
-
* and
|
|
48
|
+
* and neither operator runs a Base RPC.
|
|
28
49
|
*/
|
|
29
50
|
export declare const FALLBACK_RPC_URLS: Partial<Record<ChainId, readonly string[]>>;
|
|
30
51
|
/**
|
package/dist/transports.js
CHANGED
|
@@ -16,6 +16,21 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { fallback, http } from 'viem';
|
|
18
18
|
import { chainIdToChain } from './chain.js';
|
|
19
|
+
/**
|
|
20
|
+
* valve.city PulseChain endpoint — one URL fronting a load-balanced node pool.
|
|
21
|
+
*
|
|
22
|
+
* `vk_demo` is the PUBLIC, RATE-LIMITED demo key. It is the right default for
|
|
23
|
+
* local development and for anyone building against this package without
|
|
24
|
+
* credentials, but a keyed endpoint (`https://one.valve.city/rpc/<key>/evm/369`)
|
|
25
|
+
* belongs in front of production traffic — a shared demo key is a shared rate
|
|
26
|
+
* limit. Override per-deployment via the `VITE_RPC_URLS_369` env (caller
|
|
27
|
+
* overrides are tried ahead of this list) rather than editing this constant.
|
|
28
|
+
*
|
|
29
|
+
* Kept in sync with `DEFAULT_MSGBOARD_ENDPOINT` in @provex/onramp, which points
|
|
30
|
+
* at the same host: one keyed valve.city endpoint serves both `msgboard_*` and
|
|
31
|
+
* `eth_*` methods.
|
|
32
|
+
*/
|
|
33
|
+
export const VALVE_CITY_PULSECHAIN_RPC = 'https://one.valve.city/rpc/vk_demo/evm/369';
|
|
19
34
|
// Per-method call counter. Enabled at runtime by setting window.__RPC_DEBUG = true
|
|
20
35
|
// in the browser console (works in pre-built dist — no Vite env injection needed).
|
|
21
36
|
const _rpcCounts = {};
|
|
@@ -36,13 +51,19 @@ const _rpcLog = (method) => {
|
|
|
36
51
|
* but before the viem chain default.
|
|
37
52
|
*
|
|
38
53
|
* @remarks
|
|
39
|
-
* - Pulsechain (369):
|
|
40
|
-
*
|
|
54
|
+
* - Pulsechain (369): valve.city is listed first — it load balances across a
|
|
55
|
+
* pool of PulseChain nodes behind one endpoint, so it absorbs a single node
|
|
56
|
+
* going slow or stale without the client noticing. g4mm4 is retained behind
|
|
57
|
+
* it as an independent operator: valve.city's internal balancing cannot help
|
|
58
|
+
* if the endpoint ITSELF is unreachable, so a second operator is what makes
|
|
59
|
+
* this list a real fallback rather than one host with extra steps.
|
|
60
|
+
* Order is a hint, not a guarantee — {@link createChainTransport} ranks by
|
|
61
|
+
* measured latency, so the fastest healthy endpoint serves traffic either way.
|
|
41
62
|
* - Base (8453): no built-in fallback — Base's default endpoint is reliable
|
|
42
|
-
* and
|
|
63
|
+
* and neither operator runs a Base RPC.
|
|
43
64
|
*/
|
|
44
65
|
export const FALLBACK_RPC_URLS = {
|
|
45
|
-
369: ['https://rpc-pulsechain.g4mm4.io'],
|
|
66
|
+
369: [VALVE_CITY_PULSECHAIN_RPC, 'https://rpc-pulsechain.g4mm4.io'],
|
|
46
67
|
8453: [],
|
|
47
68
|
};
|
|
48
69
|
/**
|
package/dist/transports.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { base, pulsechain } from 'viem/chains';
|
|
4
|
-
import { createChainTransport, parseRpcUrlList, resolveChainRpcUrls, FALLBACK_RPC_URLS, } from './transports.js';
|
|
4
|
+
import { createChainTransport, parseRpcUrlList, resolveChainRpcUrls, FALLBACK_RPC_URLS, VALVE_CITY_PULSECHAIN_RPC, } from './transports.js';
|
|
5
5
|
/**
|
|
6
6
|
* Read the ordered list of RPC URLs a built transport will actually dial.
|
|
7
7
|
*
|
|
@@ -19,10 +19,26 @@ const dialledUrls = (transport) => {
|
|
|
19
19
|
};
|
|
20
20
|
describe('transports', () => {
|
|
21
21
|
describe('FALLBACK_RPC_URLS', () => {
|
|
22
|
-
it('lists g4mm4 for PulseChain (369)', () => {
|
|
23
|
-
|
|
22
|
+
it('lists valve.city ahead of g4mm4 for PulseChain (369)', () => {
|
|
23
|
+
// valve.city load balances across a node pool, so it leads. g4mm4 stays
|
|
24
|
+
// behind it as an INDEPENDENT operator: valve.city's internal balancing
|
|
25
|
+
// cannot save us if that endpoint itself is down, which is the only
|
|
26
|
+
// failure a fallback list exists to survive.
|
|
27
|
+
assert.deepEqual(FALLBACK_RPC_URLS[369], [
|
|
28
|
+
VALVE_CITY_PULSECHAIN_RPC,
|
|
29
|
+
'https://rpc-pulsechain.g4mm4.io',
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
it('keeps two independent operators for PulseChain', () => {
|
|
33
|
+
// Guard against someone "simplifying" the list down to a single host.
|
|
34
|
+
// One entry means fallback() short-circuits to a bare transport and a
|
|
35
|
+
// single operator outage takes the app down.
|
|
36
|
+
const urls = FALLBACK_RPC_URLS[369] ?? [];
|
|
37
|
+
assert.ok(urls.length >= 2, 'PulseChain must keep a second, independent RPC operator');
|
|
38
|
+
const hosts = new Set(urls.map((u) => new URL(u).host));
|
|
39
|
+
assert.equal(hosts.size, urls.length, 'fallback URLs must not share a host');
|
|
24
40
|
});
|
|
25
|
-
it('has an empty list for Base (8453) —
|
|
41
|
+
it('has an empty list for Base (8453) — neither operator runs a Base RPC', () => {
|
|
26
42
|
assert.deepEqual(FALLBACK_RPC_URLS[8453], []);
|
|
27
43
|
});
|
|
28
44
|
});
|
|
@@ -47,9 +63,10 @@ describe('transports', () => {
|
|
|
47
63
|
});
|
|
48
64
|
});
|
|
49
65
|
describe('resolveChainRpcUrls', () => {
|
|
50
|
-
it('returns g4mm4 then chain default for PulseChain', () => {
|
|
66
|
+
it('returns valve.city, then g4mm4, then chain default for PulseChain', () => {
|
|
51
67
|
const urls = resolveChainRpcUrls(369);
|
|
52
68
|
assert.deepEqual(urls, [
|
|
69
|
+
VALVE_CITY_PULSECHAIN_RPC,
|
|
53
70
|
'https://rpc-pulsechain.g4mm4.io',
|
|
54
71
|
pulsechain.rpcUrls.default.http[0],
|
|
55
72
|
]);
|
|
@@ -61,9 +78,12 @@ describe('transports', () => {
|
|
|
61
78
|
it('places caller-supplied userUrls ahead of curated fallbacks and the default', () => {
|
|
62
79
|
const userUrls = ['https://my-private-rpc.example', 'https://another.example'];
|
|
63
80
|
const urls = resolveChainRpcUrls(369, { userUrls });
|
|
81
|
+
// A deployment-supplied keyed endpoint MUST outrank the shared vk_demo
|
|
82
|
+
// default — this is how production escapes the demo key's rate limit.
|
|
64
83
|
assert.deepEqual(urls, [
|
|
65
84
|
'https://my-private-rpc.example',
|
|
66
85
|
'https://another.example',
|
|
86
|
+
VALVE_CITY_PULSECHAIN_RPC,
|
|
67
87
|
'https://rpc-pulsechain.g4mm4.io',
|
|
68
88
|
pulsechain.rpcUrls.default.http[0],
|
|
69
89
|
]);
|