@provex/utils 1.7.0 → 1.8.0-rc.20260607065921.1177ada
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/input.js +10 -1
- package/dist/input.test.d.ts +1 -0
- package/dist/input.test.js +94 -0
- package/dist/json.test.d.ts +1 -0
- package/dist/json.test.js +103 -0
- package/dist/participants.test.d.ts +1 -0
- package/dist/participants.test.js +56 -0
- package/dist/payment.d.ts +16 -0
- package/dist/payment.js +21 -0
- package/dist/payment.test.js +14 -2
- package/package.json +2 -2
package/dist/input.js
CHANGED
|
@@ -48,7 +48,16 @@ export const adjustLowestDecimal = (inputVal, direction) => {
|
|
|
48
48
|
// Convert back to string, pad with zeros if needed
|
|
49
49
|
let valueStr = value.toString().padStart(whole.length + 18, '0');
|
|
50
50
|
let newWhole = BigInt(valueStr.slice(0, valueStr.length - 18) || '0').toString();
|
|
51
|
-
|
|
51
|
+
// Take the last 18 chars (the decimal portion of the padded value),
|
|
52
|
+
// then trim back down to the original precision.
|
|
53
|
+
//
|
|
54
|
+
// Two-step slice instead of `valueStr.slice(-18, -18 + originalDecimals)`
|
|
55
|
+
// — that single slice returns an empty string when
|
|
56
|
+
// originalDecimals === 18 because end (0) ≤ begin (-18 wraps to a
|
|
57
|
+
// positive index), which silently drops every decimal digit at the
|
|
58
|
+
// 18-decimal cap. The two-step path is correct for every
|
|
59
|
+
// originalDecimals in [1, 18].
|
|
60
|
+
let newDecimal = valueStr.slice(-18).slice(0, originalDecimals);
|
|
52
61
|
// If originalDecimals was 0, don't add a decimal point
|
|
53
62
|
return originalDecimals > 0 ? `${newWhole}.${newDecimal}` : newWhole;
|
|
54
63
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { adjustLowestDecimal } from './input.js';
|
|
4
|
+
describe('adjustLowestDecimal', () => {
|
|
5
|
+
describe('whole numbers (no decimal point)', () => {
|
|
6
|
+
it('increments by 1', () => {
|
|
7
|
+
assert.equal(adjustLowestDecimal('100', 1), '101');
|
|
8
|
+
assert.equal(adjustLowestDecimal('0', 1), '1');
|
|
9
|
+
assert.equal(adjustLowestDecimal('9', 1), '10');
|
|
10
|
+
});
|
|
11
|
+
it('decrements by 1', () => {
|
|
12
|
+
assert.equal(adjustLowestDecimal('100', -1), '99');
|
|
13
|
+
assert.equal(adjustLowestDecimal('10', -1), '9');
|
|
14
|
+
assert.equal(adjustLowestDecimal('1', -1), '0');
|
|
15
|
+
});
|
|
16
|
+
it('does not produce a decimal point on a whole-number input', () => {
|
|
17
|
+
const result = adjustLowestDecimal('100', 1);
|
|
18
|
+
assert.ok(!result.includes('.'), `expected no decimal point, got "${result}"`);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('two decimal places', () => {
|
|
22
|
+
it('increments the hundredths place and preserves precision', () => {
|
|
23
|
+
assert.equal(adjustLowestDecimal('1.50', 1), '1.51');
|
|
24
|
+
assert.equal(adjustLowestDecimal('0.00', 1), '0.01');
|
|
25
|
+
});
|
|
26
|
+
it('decrements the hundredths place and preserves precision', () => {
|
|
27
|
+
assert.equal(adjustLowestDecimal('1.50', -1), '1.49');
|
|
28
|
+
assert.equal(adjustLowestDecimal('1.00', -1), '0.99');
|
|
29
|
+
});
|
|
30
|
+
it('keeps trailing zeros (precision is sticky)', () => {
|
|
31
|
+
// 1.50 - 1 step at the hundredths → 1.49 (NOT "1.5"), because
|
|
32
|
+
// the original input declared 2 decimals of precision.
|
|
33
|
+
assert.equal(adjustLowestDecimal('1.50', -1), '1.49');
|
|
34
|
+
// 1.00 + 1 step → 1.01 (NOT "1.01" collapsed to "1.01")
|
|
35
|
+
assert.equal(adjustLowestDecimal('1.00', 1), '1.01');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('three+ decimal places', () => {
|
|
39
|
+
it('operates at the deepest entered precision', () => {
|
|
40
|
+
assert.equal(adjustLowestDecimal('0.001', 1), '0.002');
|
|
41
|
+
assert.equal(adjustLowestDecimal('0.001', -1), '0.000');
|
|
42
|
+
assert.equal(adjustLowestDecimal('0.0001', 1), '0.0002');
|
|
43
|
+
});
|
|
44
|
+
it('preserves precision when crossing decimal carry', () => {
|
|
45
|
+
assert.equal(adjustLowestDecimal('0.009', 1), '0.010');
|
|
46
|
+
assert.equal(adjustLowestDecimal('0.099', 1), '0.100');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('cannot decrement below zero', () => {
|
|
50
|
+
// The function's contract (line 44-47) is: "if direction is -1
|
|
51
|
+
// and value < delta, do nothing." This protects against negative
|
|
52
|
+
// outputs in numeric input controls.
|
|
53
|
+
it('clamps at zero for whole numbers', () => {
|
|
54
|
+
assert.equal(adjustLowestDecimal('0', -1), '0');
|
|
55
|
+
});
|
|
56
|
+
it('clamps at zero for decimals', () => {
|
|
57
|
+
assert.equal(adjustLowestDecimal('0.00', -1), '0.00');
|
|
58
|
+
assert.equal(adjustLowestDecimal('0.001', -1), '0.000');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('decimal precision cap (18 places)', () => {
|
|
62
|
+
// Internally the function pads to 18 decimals for the bigint math.
|
|
63
|
+
// The boundary case (input AT 18 decimals) used to drop every
|
|
64
|
+
// decimal digit because the final single-slice math hit
|
|
65
|
+
// `slice(-18, 0)` and returned empty. Fixed by switching to a
|
|
66
|
+
// two-step `slice(-18).slice(0, originalDecimals)` — pin that
|
|
67
|
+
// here so a regression to the single-slice path would surface.
|
|
68
|
+
it('preserves precision at 17 decimals after adjust', () => {
|
|
69
|
+
const result = adjustLowestDecimal('0.12345678901234567', 1);
|
|
70
|
+
const [, decimal = ''] = result.split('.');
|
|
71
|
+
assert.equal(decimal.length, 17, `expected 17 decimal places, got ${decimal.length}`);
|
|
72
|
+
});
|
|
73
|
+
it('preserves precision at exactly 18 decimals after adjust', () => {
|
|
74
|
+
const result = adjustLowestDecimal('0.123456789012345678', 1);
|
|
75
|
+
// Adjusting at the 18th decimal place (idx 17) by +1 flips the
|
|
76
|
+
// last digit: ...678 → ...679. Result must still be 18-decimal.
|
|
77
|
+
assert.equal(result, '0.123456789012345679');
|
|
78
|
+
});
|
|
79
|
+
it('truncates inputs beyond 18 decimals before adjusting', () => {
|
|
80
|
+
// Input with 30 decimal characters gets truncated to 18 at
|
|
81
|
+
// parse time, then incremented at the 18th decimal place.
|
|
82
|
+
const result = adjustLowestDecimal('0.123456789012345678901234567890', 1);
|
|
83
|
+
assert.equal(result, '0.123456789012345679');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('large whole-number values', () => {
|
|
87
|
+
it('handles values larger than Number.MAX_SAFE_INTEGER via bigint', () => {
|
|
88
|
+
// 2^53 = 9007199254740992 — anything above this loses precision
|
|
89
|
+
// in plain JS numbers. The function uses bigint internally so
|
|
90
|
+
// the result must be exact.
|
|
91
|
+
assert.equal(adjustLowestDecimal('9007199254740992', 1), '9007199254740993');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { jsonSerializer, mapBigIntFields } from './json.js';
|
|
4
|
+
describe('jsonSerializer.stringify', () => {
|
|
5
|
+
it('serializes a plain object without bigints (matches JSON.stringify)', () => {
|
|
6
|
+
const obj = { name: 'test', count: 42, active: true };
|
|
7
|
+
assert.equal(jsonSerializer.stringify(obj), JSON.stringify(obj));
|
|
8
|
+
});
|
|
9
|
+
it('converts a top-level bigint to a string', () => {
|
|
10
|
+
const json = jsonSerializer.stringify({ amount: 100000000n });
|
|
11
|
+
assert.equal(json, '{"amount":"100000000"}');
|
|
12
|
+
});
|
|
13
|
+
it('converts nested bigints to strings', () => {
|
|
14
|
+
const json = jsonSerializer.stringify({
|
|
15
|
+
outer: { inner: 999n, deeper: { evenDeeper: 1n } },
|
|
16
|
+
});
|
|
17
|
+
assert.equal(json, '{"outer":{"inner":"999","deeper":{"evenDeeper":"1"}}}');
|
|
18
|
+
});
|
|
19
|
+
it('handles arrays of bigints', () => {
|
|
20
|
+
const json = jsonSerializer.stringify([1n, 2n, 3n]);
|
|
21
|
+
assert.equal(json, '["1","2","3"]');
|
|
22
|
+
});
|
|
23
|
+
it('handles mixed arrays', () => {
|
|
24
|
+
const json = jsonSerializer.stringify([1n, 'two', 3]);
|
|
25
|
+
assert.equal(json, '["1","two",3]');
|
|
26
|
+
});
|
|
27
|
+
it('handles a negative bigint', () => {
|
|
28
|
+
const json = jsonSerializer.stringify({ delta: -123n });
|
|
29
|
+
assert.equal(json, '{"delta":"-123"}');
|
|
30
|
+
});
|
|
31
|
+
it('handles zero bigint', () => {
|
|
32
|
+
const json = jsonSerializer.stringify({ zero: 0n });
|
|
33
|
+
assert.equal(json, '{"zero":"0"}');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('jsonSerializer.parse', () => {
|
|
37
|
+
it('is JSON.parse by reference', () => {
|
|
38
|
+
// Documented behavior — parse does NOT convert string-shaped numbers
|
|
39
|
+
// back to bigints. That's mapBigIntFields' job.
|
|
40
|
+
assert.equal(jsonSerializer.parse, JSON.parse);
|
|
41
|
+
});
|
|
42
|
+
it('returns parsed values as plain objects (no auto-bigint)', () => {
|
|
43
|
+
const parsed = jsonSerializer.parse('{"amount":"100000000"}');
|
|
44
|
+
assert.deepEqual(parsed, { amount: '100000000' });
|
|
45
|
+
assert.equal(typeof parsed.amount, 'string');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('mapBigIntFields', () => {
|
|
49
|
+
it('converts a named field to bigint at the top level', () => {
|
|
50
|
+
const result = mapBigIntFields({ amount: '100', name: 'x' }, ['amount']);
|
|
51
|
+
assert.equal(result.amount, 100n);
|
|
52
|
+
assert.equal(result.name, 'x');
|
|
53
|
+
});
|
|
54
|
+
it('leaves fields not in the list untouched', () => {
|
|
55
|
+
const result = mapBigIntFields({ amount: '100', count: '50' }, ['amount']);
|
|
56
|
+
assert.equal(result.amount, 100n);
|
|
57
|
+
assert.equal(result.count, '50'); // string, not bigint
|
|
58
|
+
});
|
|
59
|
+
it('recurses into nested objects', () => {
|
|
60
|
+
const result = mapBigIntFields({ outer: { inner: '50', label: 'x' } }, ['inner']);
|
|
61
|
+
assert.equal(result.outer.inner, 50n);
|
|
62
|
+
assert.equal(result.outer.label, 'x');
|
|
63
|
+
});
|
|
64
|
+
it('recurses into arrays', () => {
|
|
65
|
+
const result = mapBigIntFields([{ amount: '10' }, { amount: '20' }], ['amount']);
|
|
66
|
+
assert.equal(result[0].amount, 10n);
|
|
67
|
+
assert.equal(result[1].amount, 20n);
|
|
68
|
+
});
|
|
69
|
+
it('handles array-of-array nesting', () => {
|
|
70
|
+
const result = mapBigIntFields([[{ amount: '1' }], [{ amount: '2' }]], ['amount']);
|
|
71
|
+
assert.equal(result[0][0].amount, 1n);
|
|
72
|
+
assert.equal(result[1][0].amount, 2n);
|
|
73
|
+
});
|
|
74
|
+
it('only converts strings that match the digit regex', () => {
|
|
75
|
+
// The implementation requires /^-?\d+$/ to coerce — strings with
|
|
76
|
+
// non-digit characters stay as strings to avoid BigInt() throws.
|
|
77
|
+
const result = mapBigIntFields({ amount: 'not-a-number', count: '42abc' }, ['amount', 'count']);
|
|
78
|
+
assert.equal(result.amount, 'not-a-number');
|
|
79
|
+
assert.equal(result.count, '42abc');
|
|
80
|
+
});
|
|
81
|
+
it('handles negative number strings', () => {
|
|
82
|
+
const result = mapBigIntFields({ delta: '-100' }, ['delta']);
|
|
83
|
+
assert.equal(result.delta, -100n);
|
|
84
|
+
});
|
|
85
|
+
it('leaves non-string values in matched fields alone', () => {
|
|
86
|
+
// The implementation gates on `typeof value === 'string'`, so a
|
|
87
|
+
// number or bigint at a matched key stays as-is.
|
|
88
|
+
const result = mapBigIntFields({ amount: 42 }, ['amount']);
|
|
89
|
+
assert.equal(result.amount, 42);
|
|
90
|
+
});
|
|
91
|
+
it('handles a roundtrip: stringify → parse → mapBigIntFields', () => {
|
|
92
|
+
const original = { amount: 100000000n, name: 'test', nested: { value: 50n } };
|
|
93
|
+
const json = jsonSerializer.stringify(original);
|
|
94
|
+
const parsed = jsonSerializer.parse(json);
|
|
95
|
+
const restored = mapBigIntFields(parsed, ['amount', 'value']);
|
|
96
|
+
assert.deepEqual(restored, original);
|
|
97
|
+
});
|
|
98
|
+
it('returns primitives unchanged', () => {
|
|
99
|
+
assert.equal(mapBigIntFields(42, ['amount']), 42);
|
|
100
|
+
assert.equal(mapBigIntFields('hello', ['amount']), 'hello');
|
|
101
|
+
assert.equal(mapBigIntFields(null, ['amount']), null);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { generateParticipantId } from './participants.js';
|
|
4
|
+
const DEAD_ADDRESS = '0xdead000000000000000000000000000000000000';
|
|
5
|
+
const BEEF_ADDRESS = '0xbeef000000000000000000000000000000000000';
|
|
6
|
+
describe('generateParticipantId', () => {
|
|
7
|
+
it('returns a 0x-prefixed 66-character hex string (32 bytes)', () => {
|
|
8
|
+
const id = generateParticipantId(1, DEAD_ADDRESS);
|
|
9
|
+
assert.match(id, /^0x[0-9a-f]{64}$/);
|
|
10
|
+
});
|
|
11
|
+
it('is deterministic for the same inputs', () => {
|
|
12
|
+
const a = generateParticipantId(8453, DEAD_ADDRESS);
|
|
13
|
+
const b = generateParticipantId(8453, DEAD_ADDRESS);
|
|
14
|
+
assert.equal(a, b);
|
|
15
|
+
});
|
|
16
|
+
it('produces different IDs for different chains with the same address', () => {
|
|
17
|
+
// The whole point of this fn: cross-chain uniqueness. The same
|
|
18
|
+
// address on chain 1 and chain 8453 must hash to different ids,
|
|
19
|
+
// otherwise indexer counterparty tracking collapses across chains.
|
|
20
|
+
const onMainnet = generateParticipantId(1, DEAD_ADDRESS);
|
|
21
|
+
const onBase = generateParticipantId(8453, DEAD_ADDRESS);
|
|
22
|
+
assert.notEqual(onMainnet, onBase);
|
|
23
|
+
});
|
|
24
|
+
it('produces different IDs for different addresses on the same chain', () => {
|
|
25
|
+
const dead = generateParticipantId(8453, DEAD_ADDRESS);
|
|
26
|
+
const beef = generateParticipantId(8453, BEEF_ADDRESS);
|
|
27
|
+
assert.notEqual(dead, beef);
|
|
28
|
+
});
|
|
29
|
+
it('accepts bigint chainId equivalent to numeric', () => {
|
|
30
|
+
// Documented signature is `number | bigint`. Confirm the two
|
|
31
|
+
// shapes produce the same hash (otherwise an indexer that uses
|
|
32
|
+
// bigint and a UI that uses number would silently disagree).
|
|
33
|
+
const fromNumber = generateParticipantId(8453, DEAD_ADDRESS);
|
|
34
|
+
const fromBigint = generateParticipantId(8453n, DEAD_ADDRESS);
|
|
35
|
+
assert.equal(fromNumber, fromBigint);
|
|
36
|
+
});
|
|
37
|
+
it('handles a large chainId (PulseChain = 369)', () => {
|
|
38
|
+
const id = generateParticipantId(369, DEAD_ADDRESS);
|
|
39
|
+
assert.match(id, /^0x[0-9a-f]{64}$/);
|
|
40
|
+
});
|
|
41
|
+
it('handles chainId 0 (zero is valid as a numeric chain identifier)', () => {
|
|
42
|
+
// Zero is a meaningful sentinel chainId in some test/dev contexts;
|
|
43
|
+
// the function should still produce a valid hash, not throw.
|
|
44
|
+
const id = generateParticipantId(0, DEAD_ADDRESS);
|
|
45
|
+
assert.match(id, /^0x[0-9a-f]{64}$/);
|
|
46
|
+
});
|
|
47
|
+
it('produces an ID independent of address case', () => {
|
|
48
|
+
// viem's keccak256 should treat the hex content equivalently
|
|
49
|
+
// regardless of input case (concatHex normalizes). Documenting
|
|
50
|
+
// this so an upstream change to viem's case handling would surface
|
|
51
|
+
// here, not silently in production.
|
|
52
|
+
const lower = generateParticipantId(8453, '0xdead000000000000000000000000000000000000');
|
|
53
|
+
const upper = generateParticipantId(8453, '0xDEAD000000000000000000000000000000000000');
|
|
54
|
+
assert.equal(lower, upper);
|
|
55
|
+
});
|
|
56
|
+
});
|
package/dist/payment.d.ts
CHANGED
|
@@ -86,6 +86,22 @@ export declare const subProviders: {
|
|
|
86
86
|
};
|
|
87
87
|
/** Sub-provider key (providers within providers) */
|
|
88
88
|
export type SubProviderKey = typeof subProviders[keyof typeof subProviders];
|
|
89
|
+
/**
|
|
90
|
+
* Rails whose zkTLS proof carries only a calendar DATE (no time of day), so the
|
|
91
|
+
* gate applies the day-resolution window (latestPlausiblePaymentMs) and the buy
|
|
92
|
+
* UI must disclose the wider timing window.
|
|
93
|
+
*
|
|
94
|
+
* - `providers.ZELLE` — parent key used by deposit rate cards (covers all Zelle).
|
|
95
|
+
* - `subProviders.CHASE` — confirmed via real proof: timestamp is 8-digit YYYYMMDD.
|
|
96
|
+
*
|
|
97
|
+
* Other Zelle sub-banks (BofA, Citi) use `dateStringUtcParameter` in the gate
|
|
98
|
+
* until real proofs confirm their timestamp format. Add them here once confirmed
|
|
99
|
+
* and the gate is updated to `dayParameterYyyymmdd`. Keep in sync with
|
|
100
|
+
* `payment-extraction.ts` — every key here must use `dayParameterYyyymmdd` there.
|
|
101
|
+
*/
|
|
102
|
+
export declare const DAY_RESOLUTION_PROVIDER_KEYS: ReadonlySet<ProviderKey | SubProviderKey>;
|
|
103
|
+
/** True if a provider's proof only resolves payment time to the calendar date. */
|
|
104
|
+
export declare function isDayResolutionRail(key: ProviderKey | SubProviderKey): boolean;
|
|
89
105
|
/**
|
|
90
106
|
* Extension platform keys - the platform identifiers used by the PeerAuth extension.
|
|
91
107
|
* For Zelle sub-providers, these are the bank names without the 'zelle-' prefix.
|
package/dist/payment.js
CHANGED
|
@@ -133,6 +133,27 @@ export const subProviders = {
|
|
|
133
133
|
BANK_OF_AMERICA: 'zelle-bofa',
|
|
134
134
|
CITI: 'zelle-citi',
|
|
135
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* Rails whose zkTLS proof carries only a calendar DATE (no time of day), so the
|
|
138
|
+
* gate applies the day-resolution window (latestPlausiblePaymentMs) and the buy
|
|
139
|
+
* UI must disclose the wider timing window.
|
|
140
|
+
*
|
|
141
|
+
* - `providers.ZELLE` — parent key used by deposit rate cards (covers all Zelle).
|
|
142
|
+
* - `subProviders.CHASE` — confirmed via real proof: timestamp is 8-digit YYYYMMDD.
|
|
143
|
+
*
|
|
144
|
+
* Other Zelle sub-banks (BofA, Citi) use `dateStringUtcParameter` in the gate
|
|
145
|
+
* until real proofs confirm their timestamp format. Add them here once confirmed
|
|
146
|
+
* and the gate is updated to `dayParameterYyyymmdd`. Keep in sync with
|
|
147
|
+
* `payment-extraction.ts` — every key here must use `dayParameterYyyymmdd` there.
|
|
148
|
+
*/
|
|
149
|
+
export const DAY_RESOLUTION_PROVIDER_KEYS = new Set([
|
|
150
|
+
providers.ZELLE,
|
|
151
|
+
subProviders.CHASE,
|
|
152
|
+
]);
|
|
153
|
+
/** True if a provider's proof only resolves payment time to the calendar date. */
|
|
154
|
+
export function isDayResolutionRail(key) {
|
|
155
|
+
return DAY_RESOLUTION_PROVIDER_KEYS.has(key);
|
|
156
|
+
}
|
|
136
157
|
/**
|
|
137
158
|
* Extension platform keys - the platform identifiers used by the PeerAuth extension.
|
|
138
159
|
* For Zelle sub-providers, these are the bank names without the 'zelle-' prefix.
|
package/dist/payment.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { describe, it } from 'node:test';
|
|
1
|
+
import { describe, it, test } from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { base } from 'viem/chains';
|
|
4
|
-
import { providerConfigs, getCurrenciesForProvider, providers, verifierGetsKey, providerKeyToContractId, resolveApiProviderKey, getProviderPaymentMethodIds, computeProviderAvailability, availableProviderConfigs, subProviders } from './payment.js';
|
|
4
|
+
import { providerConfigs, getCurrenciesForProvider, providers, verifierGetsKey, providerKeyToContractId, resolveApiProviderKey, getProviderPaymentMethodIds, computeProviderAvailability, availableProviderConfigs, subProviders, isDayResolutionRail } from './payment.js';
|
|
5
5
|
import { SUPPORTED_CURRENCIES } from './currencies.js';
|
|
6
6
|
describe('getCurrenciesForProvider', () => {
|
|
7
7
|
const configs = providerConfigs(base.id);
|
|
@@ -474,6 +474,18 @@ describe('computeProviderAvailability', () => {
|
|
|
474
474
|
// unavailable should contain at least the visible non-disabled set
|
|
475
475
|
assert.ok(result.unavailable.length > 0);
|
|
476
476
|
});
|
|
477
|
+
test('isDayResolutionRail flags Zelle rails (date-only proof timestamp), not millisecond rails', () => {
|
|
478
|
+
// Chase proof (parity-seed.json) confirmed: timestamp is 8-digit YYYYMMDD.
|
|
479
|
+
// Parent 'zelle' key covers deposit rate cards that show all Zelle sub-banks.
|
|
480
|
+
assert.equal(isDayResolutionRail(providers.ZELLE), true);
|
|
481
|
+
assert.equal(isDayResolutionRail(subProviders.CHASE), true);
|
|
482
|
+
// BofA and Citi use dateStringUtcParameter until real proofs confirm date-only
|
|
483
|
+
// format — must NOT be flagged until the gate is updated to dayParameterYyyymmdd.
|
|
484
|
+
assert.equal(isDayResolutionRail(subProviders.BANK_OF_AMERICA), false);
|
|
485
|
+
assert.equal(isDayResolutionRail(subProviders.CITI), false);
|
|
486
|
+
// Millisecond-resolution rails must never be flagged.
|
|
487
|
+
assert.equal(isDayResolutionRail(providers.REVOLUT), false);
|
|
488
|
+
});
|
|
477
489
|
it('availableProviderConfigs is a thin wrapper that returns only the available list', () => {
|
|
478
490
|
const both = computeProviderAvailability({
|
|
479
491
|
chainId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@provex/utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0-rc.20260607065921.1177ada",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"README.md"
|
|
131
131
|
],
|
|
132
132
|
"dependencies": {
|
|
133
|
-
"@zkp2p/providers": "
|
|
133
|
+
"@zkp2p/providers": "github:zkp2p/providers#7751475b73b533b342d8518628578b33ea191a19"
|
|
134
134
|
},
|
|
135
135
|
"peerDependencies": {
|
|
136
136
|
"viem": "^2.0.0"
|