@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
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
import { describe, it } from 'node:test';
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import { pulsechain } from 'viem/chains';
|
|
15
|
+
import { findLegacySubProviderAttachments, isProviderDisabled, providerKeyToContractId, subProviders, } from './payment.js';
|
|
16
|
+
process.env.DISABLED_PROVIDERS = 'PayPal, zelle';
|
|
17
|
+
process.env.ENABLED_PROVIDERS = 'venmo,paypal';
|
|
18
|
+
describe('isProviderDisabled', () => {
|
|
19
|
+
it('hides a rail named in the disabled list, whatever the registry says', () => {
|
|
20
|
+
assert.equal(isProviderDisabled('zelle', false), true);
|
|
21
|
+
assert.equal(isProviderDisabled('ZELLE'), true);
|
|
22
|
+
});
|
|
23
|
+
it('lets the disabled list win when a rail is in both lists', () => {
|
|
24
|
+
assert.equal(isProviderDisabled('paypal'), true);
|
|
25
|
+
});
|
|
26
|
+
it('shows a rail named in the enabled list even when the registry disables it', () => {
|
|
27
|
+
assert.equal(isProviderDisabled('venmo', true), false);
|
|
28
|
+
});
|
|
29
|
+
it('follows the registry field for a rail in neither list', () => {
|
|
30
|
+
assert.equal(isProviderDisabled('wise'), false);
|
|
31
|
+
assert.equal(isProviderDisabled('wise', true), true);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('findLegacySubProviderAttachments with a disabled rail', () => {
|
|
35
|
+
it('does not warn about a rail the deployment has switched off', () => {
|
|
36
|
+
// A maker cannot act on a warning about a rail they cannot attach here.
|
|
37
|
+
const bankIds = new Set(Object.values(subProviders).map((key) => providerKeyToContractId(key).toLowerCase()));
|
|
38
|
+
assert.deepEqual(findLegacySubProviderAttachments({ chainId: pulsechain.id, attachedPaymentMethodIds: bankIds }), []);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for where each payment rail sends the buyer.
|
|
3
|
+
*
|
|
4
|
+
* A send link is the buyer's only route from "I must pay" to the rail's own
|
|
5
|
+
* payment screen. A wrong host sends money intent to the wrong place, so each
|
|
6
|
+
* rail is pinned to its exact destination, in both the app-link (QR) and the
|
|
7
|
+
* web form.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for where each payment rail sends the buyer.
|
|
3
|
+
*
|
|
4
|
+
* A send link is the buyer's only route from "I must pay" to the rail's own
|
|
5
|
+
* payment screen. A wrong host sends money intent to the wrong place, so each
|
|
6
|
+
* rail is pinned to its exact destination, in both the app-link (QR) and the
|
|
7
|
+
* web form.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it } from 'node:test';
|
|
10
|
+
import assert from 'node:assert/strict';
|
|
11
|
+
import { pulsechain } from 'viem/chains';
|
|
12
|
+
import { computePayeeDetailsHash, dataEncoding, defaultZelleSendLink, providerConfigs, providers, subProviderConfigs, subProviders, } from './payment.js';
|
|
13
|
+
import { keccak256, stringToHex } from 'viem';
|
|
14
|
+
const webOptions = { username: 'alice', amount: '12.50' };
|
|
15
|
+
const qrOptions = { ...webOptions, qrCode: true };
|
|
16
|
+
const sendLinkOf = (key) => providerConfigs(pulsechain.id)[key].sendLink;
|
|
17
|
+
describe('computePayeeDetailsHash', () => {
|
|
18
|
+
it('is the keccak256 of the identifier text, so maker and taker derive the same hash', () => {
|
|
19
|
+
assert.equal(computePayeeDetailsHash('@myvenmo'), keccak256(stringToHex('@myvenmo')));
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe('Zelle send links', () => {
|
|
23
|
+
it('encodes the username into the enroll URL as a base64 payment payload', () => {
|
|
24
|
+
const decoded = JSON.parse(atob(dataEncoding('alice@example.com')));
|
|
25
|
+
assert.deepEqual(decoded, { name: '', action: 'payment', token: 'alice@example.com' });
|
|
26
|
+
});
|
|
27
|
+
it('returns no default link without a username, because there is no one to pay', () => {
|
|
28
|
+
assert.equal(defaultZelleSendLink({ username: '', amount: '1.00' }), null);
|
|
29
|
+
});
|
|
30
|
+
it('returns the enroll link for a username', () => {
|
|
31
|
+
assert.equal(defaultZelleSendLink(webOptions), `https://enroll.zellepay.com/qr-codes?data=${dataEncoding('alice')}`);
|
|
32
|
+
});
|
|
33
|
+
it('falls back to the generic enroll link when no bank is chosen', () => {
|
|
34
|
+
assert.equal(sendLinkOf(providers.ZELLE)(webOptions), defaultZelleSendLink(webOptions));
|
|
35
|
+
});
|
|
36
|
+
it('delegates to the chosen bank when a sub-provider is named', () => {
|
|
37
|
+
const link = sendLinkOf(providers.ZELLE)({ ...webOptions, subProvider: subProviders.CITI });
|
|
38
|
+
assert.equal(link, 'https://online.citi.com/US/nga/zelle/transfer');
|
|
39
|
+
});
|
|
40
|
+
const bankCases = [
|
|
41
|
+
[subProviders.CHASE, defaultZelleSendLink(qrOptions), 'https://secure.chase.com/web/auth/dashboard#/dashboard/singleDoor/singleDoorController/index'],
|
|
42
|
+
[subProviders.BANK_OF_AMERICA, 'https://www.bankofamerica.com/movemoney', 'https://www.bankofamerica.com/'],
|
|
43
|
+
[subProviders.CITI, defaultZelleSendLink(qrOptions), 'https://online.citi.com/US/nga/zelle/transfer'],
|
|
44
|
+
];
|
|
45
|
+
for (const [bank, qrLink, webLink] of bankCases) {
|
|
46
|
+
it(`${bank}: sends a QR scan to the bank's QR-capable link and a web buyer to its site`, () => {
|
|
47
|
+
const sendLink = subProviderConfigs.zelle[bank].sendLink;
|
|
48
|
+
assert.equal(sendLink(qrOptions), qrLink);
|
|
49
|
+
assert.equal(sendLink(webOptions), webLink);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
describe('rail send links', () => {
|
|
54
|
+
const cases = [
|
|
55
|
+
[
|
|
56
|
+
providers.VENMO,
|
|
57
|
+
'venmo://paycharge?txn=pay&recipients=alice¬e=cash&amount=12.50',
|
|
58
|
+
'https://account.venmo.com/pay?recipients=alice¬e=cash&amount=12.50',
|
|
59
|
+
],
|
|
60
|
+
[providers.REVOLUT, 'https://revolut.me/alice', 'https://revolut.me/alice'],
|
|
61
|
+
[providers.WISE, 'https://wise.com/pay/me/alice', 'https://wise.com/pay/me/alice'],
|
|
62
|
+
[
|
|
63
|
+
providers.MERCADOPAGO,
|
|
64
|
+
'https://www.mercadopago.com.ar/money-out/transfer/',
|
|
65
|
+
'https://www.mercadopago.com.ar/money-out/transfer/',
|
|
66
|
+
],
|
|
67
|
+
[providers.PAYPAL, 'paypal://', 'https://www.paypal.com/myaccount/transfer/homepage'],
|
|
68
|
+
[providers.MONZO, 'https://monzo.me/alice', 'https://monzo.me/alice'],
|
|
69
|
+
[providers.N26, 'number26://', 'https://app.n26.com/'],
|
|
70
|
+
[providers.ALIPAY, 'alipays://', 'https://www.alipay.com/'],
|
|
71
|
+
[providers.CHIME, 'https://app.chime.com/link/qr?u=alice', 'https://app.chime.com/login'],
|
|
72
|
+
[
|
|
73
|
+
providers.ROYALBANKCANADA,
|
|
74
|
+
'https://www.rbcroyalbank.com/ways-to-bank/interac-e-transfer.html',
|
|
75
|
+
'https://www.rbcroyalbank.com/ways-to-bank/interac-e-transfer.html',
|
|
76
|
+
],
|
|
77
|
+
[providers.IDFC, 'https://www.idfcfirstbank.com/', 'https://www.idfcfirstbank.com/'],
|
|
78
|
+
];
|
|
79
|
+
for (const [key, qrLink, webLink] of cases) {
|
|
80
|
+
it(`${key}: pins the QR and web destinations`, () => {
|
|
81
|
+
const sendLink = sendLinkOf(key);
|
|
82
|
+
assert.equal(sendLink(qrOptions), qrLink);
|
|
83
|
+
assert.equal(sendLink(webOptions), webLink);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
it('Cash App prefixes a bare username with $, and leaves a cashtag alone', () => {
|
|
87
|
+
const sendLink = sendLinkOf(providers.CASHAPP);
|
|
88
|
+
assert.equal(sendLink({ username: 'alice', amount: '1.00' }), 'https://cash.app/$alice');
|
|
89
|
+
assert.equal(sendLink({ username: '$alice', amount: '1.00' }), 'https://cash.app/$alice');
|
|
90
|
+
});
|
|
91
|
+
});
|
package/dist/peer-types.d.ts
CHANGED
|
@@ -2,22 +2,19 @@
|
|
|
2
2
|
* @fileoverview Peer protocol types for attestation and proof verification.
|
|
3
3
|
*
|
|
4
4
|
* This module defines the type system for the V3 attestation flow, including:
|
|
5
|
-
* -
|
|
6
|
-
* - Attestation requests/responses from the peer attestation service
|
|
5
|
+
* - Attestation responses from the peer attestation service
|
|
7
6
|
* - EIP-712 typed data structures for on-chain verification
|
|
8
7
|
* - Payment proof encoding for the UnifiedPaymentVerifier contract
|
|
9
8
|
*
|
|
10
9
|
* @remarks
|
|
11
10
|
* **V3 Attestation Flow:**
|
|
12
|
-
* 1. Buyer
|
|
13
|
-
* 2.
|
|
14
|
-
* 3.
|
|
15
|
-
* 4.
|
|
16
|
-
* 5. Buyer calls fulfillIntent() with encoded proof
|
|
11
|
+
* 1. Buyer captures the payment through the PeerAuth extension (buyer trusted-execution capture)
|
|
12
|
+
* 2. Service validates and returns EIP-712 signed attestation (V3AttestationResponse)
|
|
13
|
+
* 3. Attestation encoded as paymentProof bytes (encodeV3PaymentProof)
|
|
14
|
+
* 4. Buyer calls fulfillIntent() with encoded proof
|
|
17
15
|
*
|
|
18
16
|
* **Type Aliases:**
|
|
19
17
|
* For cleaner imports, V3-prefixed types have unprefixed aliases:
|
|
20
|
-
* - AttestationOptions = V3AttestationOptions
|
|
21
18
|
* - AttestationResponse = V3AttestationResponse
|
|
22
19
|
* - IntentData = V3IntentData
|
|
23
20
|
*
|
|
@@ -110,27 +107,6 @@ export type IntentValidation = {
|
|
|
110
107
|
};
|
|
111
108
|
depositData: ValidationDepositData;
|
|
112
109
|
};
|
|
113
|
-
/**
|
|
114
|
-
* Notarized proof from PeerAuth extension.
|
|
115
|
-
* This is the zkTLS proof that proves a payment transaction occurred.
|
|
116
|
-
* Mirrors the NotarizedProof type from @provex/extension-react.
|
|
117
|
-
*/
|
|
118
|
-
export type NotarizedProofData = {
|
|
119
|
-
claim: {
|
|
120
|
-
context: string;
|
|
121
|
-
epoch: number;
|
|
122
|
-
identifier: Hex;
|
|
123
|
-
owner: Hex;
|
|
124
|
-
parameters: string;
|
|
125
|
-
provider: string;
|
|
126
|
-
timestampS: number;
|
|
127
|
-
};
|
|
128
|
-
signatures: {
|
|
129
|
-
attestorAddress: Hex;
|
|
130
|
-
claimSignature: Record<number, number>;
|
|
131
|
-
resultSignature: Record<number, number>;
|
|
132
|
-
};
|
|
133
|
-
};
|
|
134
110
|
/**
|
|
135
111
|
* Intent data required by the peer attestation service.
|
|
136
112
|
* This contains all the on-chain intent parameters needed to verify
|
|
@@ -154,43 +130,6 @@ export type V3IntentData = {
|
|
|
154
130
|
/** Optional timestamp buffer in milliseconds (default: 3000) */
|
|
155
131
|
timestampBufferMs?: string;
|
|
156
132
|
};
|
|
157
|
-
/**
|
|
158
|
-
* V3 attestation request sent to the peer attestation service.
|
|
159
|
-
* The service validates Reclaim proofs and returns an EIP-712 signed attestation.
|
|
160
|
-
*
|
|
161
|
-
* Endpoint: POST /verify/{platform}/{action_type}
|
|
162
|
-
* Example: POST /verify/chase/transfer_zelle
|
|
163
|
-
*/
|
|
164
|
-
export type V3AttestationRequest = {
|
|
165
|
-
/** Type of proof being submitted */
|
|
166
|
-
proofType: 'reclaim';
|
|
167
|
-
/** JSON stringified array of Reclaim proofs */
|
|
168
|
-
proof: string;
|
|
169
|
-
/** Chain ID (e.g., 8453 for Base) */
|
|
170
|
-
chainId: number;
|
|
171
|
-
/** UnifiedPaymentVerifier contract address */
|
|
172
|
-
verifyingContract: Hex;
|
|
173
|
-
/** Intent data from on-chain signalIntent */
|
|
174
|
-
intent: V3IntentData;
|
|
175
|
-
};
|
|
176
|
-
/**
|
|
177
|
-
* Options passed to the v3 getAttestation API.
|
|
178
|
-
* These are transformed into V3AttestationRequest format.
|
|
179
|
-
*/
|
|
180
|
-
export type V3AttestationOptions = {
|
|
181
|
-
/** Platform for the attestation service path (e.g., 'chase') */
|
|
182
|
-
platform: string;
|
|
183
|
-
/** Action type for the attestation service path (e.g., 'transfer_zelle') */
|
|
184
|
-
actionType: string;
|
|
185
|
-
/** Chain ID */
|
|
186
|
-
chainId: number;
|
|
187
|
-
/** UnifiedPaymentVerifier contract address */
|
|
188
|
-
verifyingContract: Hex;
|
|
189
|
-
/** Array of Reclaim proofs from PeerAuth */
|
|
190
|
-
proofs: NotarizedProofData[];
|
|
191
|
-
/** Intent data from on-chain intent */
|
|
192
|
-
intent: V3IntentData;
|
|
193
|
-
};
|
|
194
133
|
/**
|
|
195
134
|
* EIP-712 typed data specification from attestation service.
|
|
196
135
|
*/
|
|
@@ -303,14 +242,6 @@ export type SignedAttestation = {
|
|
|
303
242
|
* @returns ABI-encoded bytes to pass as paymentProof in fulfillIntent
|
|
304
243
|
*/
|
|
305
244
|
export declare function encodeV3PaymentProof(attestation: V3AttestationResponseObject): Hex;
|
|
306
|
-
/**
|
|
307
|
-
* Extracts the Reclaim attestor address from the attestation proofInput.
|
|
308
|
-
* This is needed for the verificationData field in fulfillIntent.
|
|
309
|
-
*
|
|
310
|
-
* @param attestation - The V3AttestationResponseObject from the attestation service
|
|
311
|
-
* @returns The Reclaim attestor address as Hex, or null if not found
|
|
312
|
-
*/
|
|
313
|
-
export declare function extractReclaimAttestorAddress(attestation: V3AttestationResponseObject): Hex | null;
|
|
314
245
|
/**
|
|
315
246
|
* Extracts the nullifier from a V3 attestation response.
|
|
316
247
|
*
|
|
@@ -326,10 +257,6 @@ export declare function extractReclaimAttestorAddress(attestation: V3Attestation
|
|
|
326
257
|
* @returns The derived nullifier as Hex, or null if extraction fails
|
|
327
258
|
*/
|
|
328
259
|
export declare function extractNullifier(attestation: V3AttestationResponseObject): Hex | null;
|
|
329
|
-
/** @alias V3AttestationOptions */
|
|
330
|
-
export type AttestationOptions = V3AttestationOptions;
|
|
331
|
-
/** @alias V3AttestationRequest */
|
|
332
|
-
export type AttestationRequest = V3AttestationRequest;
|
|
333
260
|
/** @alias V3AttestationResponse */
|
|
334
261
|
export type AttestationResponse = V3AttestationResponse;
|
|
335
262
|
/** @alias V3AttestationResponseObject */
|
package/dist/peer-types.js
CHANGED
|
@@ -2,22 +2,19 @@
|
|
|
2
2
|
* @fileoverview Peer protocol types for attestation and proof verification.
|
|
3
3
|
*
|
|
4
4
|
* This module defines the type system for the V3 attestation flow, including:
|
|
5
|
-
* -
|
|
6
|
-
* - Attestation requests/responses from the peer attestation service
|
|
5
|
+
* - Attestation responses from the peer attestation service
|
|
7
6
|
* - EIP-712 typed data structures for on-chain verification
|
|
8
7
|
* - Payment proof encoding for the UnifiedPaymentVerifier contract
|
|
9
8
|
*
|
|
10
9
|
* @remarks
|
|
11
10
|
* **V3 Attestation Flow:**
|
|
12
|
-
* 1. Buyer
|
|
13
|
-
* 2.
|
|
14
|
-
* 3.
|
|
15
|
-
* 4.
|
|
16
|
-
* 5. Buyer calls fulfillIntent() with encoded proof
|
|
11
|
+
* 1. Buyer captures the payment through the PeerAuth extension (buyer trusted-execution capture)
|
|
12
|
+
* 2. Service validates and returns EIP-712 signed attestation (V3AttestationResponse)
|
|
13
|
+
* 3. Attestation encoded as paymentProof bytes (encodeV3PaymentProof)
|
|
14
|
+
* 4. Buyer calls fulfillIntent() with encoded proof
|
|
17
15
|
*
|
|
18
16
|
* **Type Aliases:**
|
|
19
17
|
* For cleaner imports, V3-prefixed types have unprefixed aliases:
|
|
20
|
-
* - AttestationOptions = V3AttestationOptions
|
|
21
18
|
* - AttestationResponse = V3AttestationResponse
|
|
22
19
|
* - IntentData = V3IntentData
|
|
23
20
|
*
|
|
@@ -91,29 +88,6 @@ export function encodeV3PaymentProof(attestation) {
|
|
|
91
88
|
}
|
|
92
89
|
]);
|
|
93
90
|
}
|
|
94
|
-
/**
|
|
95
|
-
* Extracts the Reclaim attestor address from the attestation proofInput.
|
|
96
|
-
* This is needed for the verificationData field in fulfillIntent.
|
|
97
|
-
*
|
|
98
|
-
* @param attestation - The V3AttestationResponseObject from the attestation service
|
|
99
|
-
* @returns The Reclaim attestor address as Hex, or null if not found
|
|
100
|
-
*/
|
|
101
|
-
export function extractReclaimAttestorAddress(attestation) {
|
|
102
|
-
try {
|
|
103
|
-
const proofInput = JSON.parse(attestation.proofInput);
|
|
104
|
-
const proofs = JSON.parse(proofInput.proof);
|
|
105
|
-
if (Array.isArray(proofs) && proofs.length > 0) {
|
|
106
|
-
const attestorAddress = proofs[0]?.signatures?.attestorAddress;
|
|
107
|
-
if (attestorAddress && typeof attestorAddress === 'string') {
|
|
108
|
-
return attestorAddress;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
91
|
/**
|
|
118
92
|
* Extracts the nullifier from a V3 attestation response.
|
|
119
93
|
*
|
package/dist/peer-types.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 { decodeAbiParameters, encodeAbiParameters, keccak256, parseAbiParameters } from 'viem';
|
|
4
|
-
import { encodeV3PaymentProof, extractNullifier,
|
|
4
|
+
import { encodeV3PaymentProof, extractNullifier, } from './peer-types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Where the expected values come from, so these tests can fail:
|
|
7
7
|
* - REAL_ATTESTATION is a live Chase/Zelle response captured from the zkp2p
|
|
@@ -146,33 +146,3 @@ describe('extractNullifier', () => {
|
|
|
146
146
|
assert.equal(extractNullifier({ ...REAL_ATTESTATION, encodedPaymentDetails: '0x' }), null);
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
|
-
describe('extractReclaimAttestorAddress', () => {
|
|
150
|
-
const withProofs = (proofs) => ({
|
|
151
|
-
...REAL_ATTESTATION,
|
|
152
|
-
proofInput: JSON.stringify({ proofType: 'reclaim', proof: JSON.stringify(proofs) }),
|
|
153
|
-
});
|
|
154
|
-
it('reads the attestor address of the live proof', () => {
|
|
155
|
-
assert.equal(extractReclaimAttestorAddress(REAL_ATTESTATION), '0x0636c417755e3ae25c6c166d181c0607f4c572a3');
|
|
156
|
-
});
|
|
157
|
-
it('reads the first proof only', () => {
|
|
158
|
-
const attestation = withProofs([
|
|
159
|
-
{ signatures: { attestorAddress: '0x00000000000000000000000000000000000000f1' } },
|
|
160
|
-
{ signatures: { attestorAddress: '0x00000000000000000000000000000000000000f2' } },
|
|
161
|
-
]);
|
|
162
|
-
assert.equal(extractReclaimAttestorAddress(attestation), '0x00000000000000000000000000000000000000f1');
|
|
163
|
-
});
|
|
164
|
-
it('returns null when there are no proofs', () => {
|
|
165
|
-
assert.equal(extractReclaimAttestorAddress(withProofs([])), null);
|
|
166
|
-
assert.equal(extractReclaimAttestorAddress(withProofs({ not: 'an array' })), null);
|
|
167
|
-
});
|
|
168
|
-
it('returns null when the signatures or the address are missing or not a string', () => {
|
|
169
|
-
assert.equal(extractReclaimAttestorAddress(withProofs([{}])), null);
|
|
170
|
-
assert.equal(extractReclaimAttestorAddress(withProofs([{ signatures: {} }])), null);
|
|
171
|
-
assert.equal(extractReclaimAttestorAddress(withProofs([{ signatures: { attestorAddress: 5 } }])), null);
|
|
172
|
-
assert.equal(extractReclaimAttestorAddress(withProofs([{ signatures: { attestorAddress: '' } }])), null);
|
|
173
|
-
});
|
|
174
|
-
it('returns null for malformed JSON at either level', () => {
|
|
175
|
-
assert.equal(extractReclaimAttestorAddress({ ...REAL_ATTESTATION, proofInput: 'not json' }), null);
|
|
176
|
-
assert.equal(extractReclaimAttestorAddress({ ...REAL_ATTESTATION, proofInput: JSON.stringify({ proof: 'not json' }) }), null);
|
|
177
|
-
});
|
|
178
|
-
});
|
package/dist/profit.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ export interface ProfitCalculationInput {
|
|
|
30
30
|
currency: string;
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Scale a base-unit amount by a difference in decimals.
|
|
35
|
+
*
|
|
36
|
+
* @param amount - Amount in base units.
|
|
37
|
+
* @param decimalDiff - Target decimals minus source decimals.
|
|
38
|
+
* @returns The amount rescaled to the target decimals.
|
|
39
|
+
*/
|
|
40
|
+
export declare const scaleByDecimalDifference: (amount: bigint, decimalDiff: number) => bigint;
|
|
33
41
|
/**
|
|
34
42
|
* Calculate the profit of an intent fulfilled event
|
|
35
43
|
* @param param0.amount - The fiat amount in currency base units (e.g., cents for USD)
|
package/dist/profit.js
CHANGED
|
@@ -11,6 +11,21 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { getCurrencyByContractId } from "./currencies.js";
|
|
13
13
|
import { getDefaultToken } from "./tokens.js";
|
|
14
|
+
/**
|
|
15
|
+
* Scale a base-unit amount by a difference in decimals.
|
|
16
|
+
*
|
|
17
|
+
* @param amount - Amount in base units.
|
|
18
|
+
* @param decimalDiff - Target decimals minus source decimals.
|
|
19
|
+
* @returns The amount rescaled to the target decimals.
|
|
20
|
+
*/
|
|
21
|
+
export const scaleByDecimalDifference = (amount, decimalDiff) => {
|
|
22
|
+
// A negative difference does not occur today (fiat has at most 2 decimals,
|
|
23
|
+
// the settlement token 6), but a future low-decimal token must still scale.
|
|
24
|
+
if (decimalDiff < 0) {
|
|
25
|
+
return amount / 10n ** BigInt(-decimalDiff);
|
|
26
|
+
}
|
|
27
|
+
return amount * 10n ** BigInt(decimalDiff);
|
|
28
|
+
};
|
|
14
29
|
/**
|
|
15
30
|
* Calculate the profit of an intent fulfilled event
|
|
16
31
|
* @param param0.amount - The fiat amount in currency base units (e.g., cents for USD)
|
|
@@ -33,9 +48,7 @@ export const calculateProfit = ({ amount: amountInput, conversionRate, chainId,
|
|
|
33
48
|
// e.g., $100 USD (10000 in 2 decimals) -> 100000000 in 6 decimals for USDC
|
|
34
49
|
// e.g., €100 EUR (10000 in 2 decimals) -> 100000000 in 6 decimals for USDC
|
|
35
50
|
const decimalDiff = inputTokenInfo.decimals - currency.decimals;
|
|
36
|
-
const amountScaled = decimalDiff
|
|
37
|
-
? amount * 10n ** BigInt(decimalDiff)
|
|
38
|
-
: amount / 10n ** BigInt(-decimalDiff);
|
|
51
|
+
const amountScaled = scaleByDecimalDifference(amount, decimalDiff);
|
|
39
52
|
// Profit = what we received - what we expected for 1:1 fiat:token exchange
|
|
40
53
|
// Note: This is most meaningful for USD where $1 ≈ 1 USDC
|
|
41
54
|
// For other currencies, this shows the mathematical difference assuming 1:1
|
package/dist/profit.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 { calculateProfit, calculateProfits } from './profit.js';
|
|
4
|
+
import { calculateProfit, calculateProfits, scaleByDecimalDifference } from './profit.js';
|
|
5
5
|
import { SUPPORTED_CURRENCIES } from './currencies.js';
|
|
6
6
|
describe('profit', () => {
|
|
7
7
|
// Constants for testing
|
|
@@ -312,4 +312,15 @@ describe('profit', () => {
|
|
|
312
312
|
assert.equal(result.profit, 10000000n);
|
|
313
313
|
});
|
|
314
314
|
});
|
|
315
|
+
describe('scaleByDecimalDifference', () => {
|
|
316
|
+
it('multiplies when the target has more decimals, so cents become USDC base units', () => {
|
|
317
|
+
assert.equal(scaleByDecimalDifference(10000n, 4), 100000000n);
|
|
318
|
+
});
|
|
319
|
+
it('divides when the target has fewer decimals, so a low-decimal token is not inflated', () => {
|
|
320
|
+
assert.equal(scaleByDecimalDifference(100000000n, -4), 10000n);
|
|
321
|
+
});
|
|
322
|
+
it('leaves the amount unchanged when decimals match', () => {
|
|
323
|
+
assert.equal(scaleByDecimalDifference(12345n, 0), 12345n);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
315
326
|
});
|
package/dist/reputation.js
CHANGED
|
@@ -241,13 +241,11 @@ export function formatCap(cap) {
|
|
|
241
241
|
* (its threshold is `null`).
|
|
242
242
|
*/
|
|
243
243
|
export function getTierFromVolume(volumeBaseUnits) {
|
|
244
|
-
// Iterate from
|
|
245
|
-
|
|
246
|
-
for (let i =
|
|
247
|
-
const tier =
|
|
244
|
+
// Iterate from the top tier down. `null` is the manual-assignment marker
|
|
245
|
+
// (PRESIDENT) — skipped, since volume alone can never reach it.
|
|
246
|
+
for (let i = TIER_ORDER.length - 1; i >= 0; i--) {
|
|
247
|
+
const tier = TIER_ORDER[i];
|
|
248
248
|
const threshold = TAKER_TIERS[tier].volumeThreshold;
|
|
249
|
-
// `null` is the manual-assignment marker (PRESIDENT) — already
|
|
250
|
-
// filtered out above, defensive check kept for type narrowing.
|
|
251
249
|
if (threshold === null)
|
|
252
250
|
continue;
|
|
253
251
|
if (volumeBaseUnits >= threshold) {
|
package/dist/reputation.test.js
CHANGED
|
@@ -289,3 +289,11 @@ describe('isOverOrderCap: base unit boundaries', () => {
|
|
|
289
289
|
assert.equal(isOverOrderCap({ orderTokenAmount: UNLIMITED_CAP_BASE_UNITS + 1n, cap: UNLIMITED_CAP_BASE_UNITS }), true);
|
|
290
290
|
});
|
|
291
291
|
});
|
|
292
|
+
describe('getTierFromVolume: volume below every threshold', () => {
|
|
293
|
+
it('falls back to peasant for a negative volume instead of returning undefined', () => {
|
|
294
|
+
assert.equal(getTierFromVolume(-1n), 'peasant');
|
|
295
|
+
});
|
|
296
|
+
it('never returns president, whose threshold is null (manual assignment only)', () => {
|
|
297
|
+
assert.notEqual(getTierFromVolume(10n ** 30n), 'president');
|
|
298
|
+
});
|
|
299
|
+
});
|
package/dist/time.test.js
CHANGED
|
@@ -112,11 +112,32 @@ describe('time', () => {
|
|
|
112
112
|
const result = sleep(1);
|
|
113
113
|
assert.ok(result instanceof Promise);
|
|
114
114
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Lets resolved promises run their callbacks. setImmediate is not mocked,
|
|
117
|
+
* so this returns even when sleep is wrong, and the assertion then fails
|
|
118
|
+
* instead of the test hanging.
|
|
119
|
+
*/
|
|
120
|
+
const flushPendingCallbacks = () => new Promise((resolve) => { setImmediate(resolve); });
|
|
121
|
+
// Mocked timers, not the wall clock: a loaded machine made the old
|
|
122
|
+
// "under 10ms" check fail even though sleep itself was correct.
|
|
123
|
+
it('resolves on the zero-delay tick for 0ms', async (context) => {
|
|
124
|
+
context.mock.timers.enable({ apis: ['setTimeout'] });
|
|
125
|
+
let resolved = false;
|
|
126
|
+
void sleep(0).then(() => { resolved = true; });
|
|
127
|
+
context.mock.timers.tick(0);
|
|
128
|
+
await flushPendingCallbacks();
|
|
129
|
+
assert.equal(resolved, true);
|
|
130
|
+
});
|
|
131
|
+
it('does not resolve before its delay has passed', async (context) => {
|
|
132
|
+
context.mock.timers.enable({ apis: ['setTimeout'] });
|
|
133
|
+
let resolved = false;
|
|
134
|
+
void sleep(50).then(() => { resolved = true; });
|
|
135
|
+
context.mock.timers.tick(49);
|
|
136
|
+
await flushPendingCallbacks();
|
|
137
|
+
assert.equal(resolved, false);
|
|
138
|
+
context.mock.timers.tick(1);
|
|
139
|
+
await flushPendingCallbacks();
|
|
140
|
+
assert.equal(resolved, true);
|
|
120
141
|
});
|
|
121
142
|
});
|
|
122
143
|
describe('formatAbsoluteTimestamp', () => {
|
package/dist/tokens.d.ts
CHANGED
|
@@ -8370,9 +8370,9 @@ declare const createClient: {
|
|
|
8370
8370
|
blockTag?: import("viem").BlockTag | undefined;
|
|
8371
8371
|
nonce?: string | undefined | undefined;
|
|
8372
8372
|
address?: `0x${string}` | undefined;
|
|
8373
|
+
time?: Date | undefined;
|
|
8373
8374
|
domain?: string | undefined | undefined;
|
|
8374
8375
|
scheme?: string | undefined | undefined;
|
|
8375
|
-
time?: Date | undefined;
|
|
8376
8376
|
message: string;
|
|
8377
8377
|
signature: import("viem").Hex;
|
|
8378
8378
|
}) => Promise<boolean>;
|
|
@@ -15069,9 +15069,9 @@ declare const createClient: {
|
|
|
15069
15069
|
blockTag?: import("viem").BlockTag | undefined;
|
|
15070
15070
|
nonce?: string | undefined | undefined;
|
|
15071
15071
|
address?: `0x${string}` | undefined;
|
|
15072
|
+
time?: Date | undefined;
|
|
15072
15073
|
domain?: string | undefined | undefined;
|
|
15073
15074
|
scheme?: string | undefined | undefined;
|
|
15074
|
-
time?: Date | undefined;
|
|
15075
15075
|
message: string;
|
|
15076
15076
|
signature: import("viem").Hex;
|
|
15077
15077
|
}) => Promise<boolean>;
|
package/dist/transports.d.ts
CHANGED
|
@@ -31,6 +31,32 @@ import { type ChainId } from './chain.js';
|
|
|
31
31
|
* `eth_*` methods.
|
|
32
32
|
*/
|
|
33
33
|
export declare const VALVE_CITY_PULSECHAIN_RPC = "https://one.valve.city/rpc/vk_demo/evm/369";
|
|
34
|
+
/**
|
|
35
|
+
* Count one outgoing JSON-RPC request for the debug log.
|
|
36
|
+
*
|
|
37
|
+
* Reads the method from a clone, so the real request body stays unread. A body
|
|
38
|
+
* that is not JSON is ignored: a debug counter must never fail a request.
|
|
39
|
+
*
|
|
40
|
+
* @param req - The request viem is about to send.
|
|
41
|
+
*/
|
|
42
|
+
export declare const logRpcRequest: (req: Request) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Latency probe for ranked transports: one `eth_blockNumber` call.
|
|
45
|
+
*
|
|
46
|
+
* It measures real query latency, not just the TCP round trip, and avoids the
|
|
47
|
+
* noisy `net_listening` entries viem's default probe leaves in server logs.
|
|
48
|
+
*
|
|
49
|
+
* @param transport - The transport instance viem is ranking.
|
|
50
|
+
* @returns The block number response, which viem times and discards.
|
|
51
|
+
*/
|
|
52
|
+
export declare const pingBlockNumber: (transport: {
|
|
53
|
+
transport: {
|
|
54
|
+
request: (args: {
|
|
55
|
+
method: string;
|
|
56
|
+
params: unknown[];
|
|
57
|
+
}) => Promise<unknown>;
|
|
58
|
+
};
|
|
59
|
+
}) => Promise<unknown>;
|
|
34
60
|
/**
|
|
35
61
|
* Provex-curated fallback RPC URLs per chain, applied after caller overrides
|
|
36
62
|
* but before the viem chain default.
|
|
@@ -47,7 +73,7 @@ export declare const VALVE_CITY_PULSECHAIN_RPC = "https://one.valve.city/rpc/vk_
|
|
|
47
73
|
* - Base (8453): no built-in fallback — Base's default endpoint is reliable
|
|
48
74
|
* and neither operator runs a Base RPC.
|
|
49
75
|
*/
|
|
50
|
-
export declare const FALLBACK_RPC_URLS:
|
|
76
|
+
export declare const FALLBACK_RPC_URLS: Record<ChainId, readonly string[]>;
|
|
51
77
|
/**
|
|
52
78
|
* Parse a comma-separated RPC URL list into an ordered array of valid URLs.
|
|
53
79
|
*
|
|
@@ -82,6 +108,31 @@ export declare const parseRpcUrlList: (raw: string | undefined | null) => string
|
|
|
82
108
|
* defeat the reason it was placed second.
|
|
83
109
|
*/
|
|
84
110
|
export type ChainTransportStrategy = 'ranked' | 'ordered';
|
|
111
|
+
/**
|
|
112
|
+
* The strategy to use when the caller does not name one.
|
|
113
|
+
*
|
|
114
|
+
* `ranked` in a browser, `ordered` everywhere else.
|
|
115
|
+
*
|
|
116
|
+
* **Why the environment decides.** viem's ranking is a long-lived-client
|
|
117
|
+
* optimisation: it starts a self-recursive `setTimeout` loop that pings every
|
|
118
|
+
* endpoint forever, and it exposes no way to stop it — `rankTransports` takes
|
|
119
|
+
* no abort signal and the client has no teardown. In a browser that is exactly
|
|
120
|
+
* right; the page owns the client for its lifetime and gets the fastest healthy
|
|
121
|
+
* endpoint for free.
|
|
122
|
+
*
|
|
123
|
+
* In Node the same loop means the process can never exit on its own. A one-shot
|
|
124
|
+
* script that reads one value hangs at the end of its work, and a test file that
|
|
125
|
+
* so much as constructs a client hangs for its full timeout with every
|
|
126
|
+
* assertion passing. `packages/scripts/src/lookup-hash.ts` already carries a
|
|
127
|
+
* hand-added `process.exit(0)` for precisely this.
|
|
128
|
+
*
|
|
129
|
+
* Ranking buys a short-lived process nothing anyway — there is no traffic to
|
|
130
|
+
* optimise over — while `ordered` gives it the same fallback coverage with
|
|
131
|
+
* strict priority and no timer. Callers that genuinely want ranking outside a
|
|
132
|
+
* browser (a long-running Node service) can still pass `strategy: 'ranked'`
|
|
133
|
+
* explicitly.
|
|
134
|
+
*/
|
|
135
|
+
export declare const defaultChainTransportStrategy: () => ChainTransportStrategy;
|
|
85
136
|
/**
|
|
86
137
|
* Options accepted by {@link createChainTransport}.
|
|
87
138
|
*/
|
|
@@ -93,7 +144,7 @@ interface CreateChainTransportOptions {
|
|
|
93
144
|
userUrls?: readonly string[];
|
|
94
145
|
/**
|
|
95
146
|
* Traffic-distribution strategy. Defaults per environment — see
|
|
96
|
-
* {@link
|
|
147
|
+
* {@link defaultChainTransportStrategy}: `ranked` in a browser, preserving the
|
|
97
148
|
* latency-optimised behaviour every browser call-site relies on, and
|
|
98
149
|
* `ordered` in Node, where ranking's unstoppable poller would keep the
|
|
99
150
|
* process alive forever and buy a short-lived process nothing.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the RPC call counter, the ranking probe and the
|
|
3
|
+
* environment-dependent default strategy.
|
|
4
|
+
*
|
|
5
|
+
* The counter exists so a developer can open a browser console, set
|
|
6
|
+
* `window.__RPC_DEBUG = true`, and see which JSON-RPC methods a page is
|
|
7
|
+
* hammering. It must stay silent otherwise and must never fail a request.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|