@provex/utils 1.9.2 → 1.10.0-rc.20260912192040.fdbb8fc
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/chain.d.ts +13 -0
- package/dist/chain.js +13 -0
- package/dist/contracts.config.test.d.ts +18 -0
- package/dist/contracts.config.test.js +220 -0
- package/dist/contracts.d.ts +169 -29
- package/dist/contracts.js +188 -31
- package/dist/contracts.test.js +134 -7
- package/dist/deposit.d.ts +50 -0
- package/dist/deposit.js +45 -0
- package/dist/events.d.ts +2 -2
- package/dist/events.js +20 -0
- package/dist/payment.currency.test.d.ts +11 -0
- package/dist/payment.currency.test.js +134 -0
- package/dist/payment.d.ts +149 -23
- package/dist/payment.js +170 -29
- package/dist/payment.rails.test.d.ts +25 -0
- package/dist/payment.rails.test.js +213 -0
- package/dist/payment.test.js +73 -51
- package/dist/rateInput.d.ts +163 -0
- package/dist/rateInput.js +173 -0
- package/dist/rateInput.test.d.ts +1 -0
- package/dist/rateInput.test.js +318 -0
- package/dist/tokens.d.ts +15068 -15037
- package/dist/tokens.js +37 -8
- package/dist/tokens.test.js +11 -0
- package/dist/transports.d.ts +86 -8
- package/dist/transports.js +126 -10
- package/dist/transports.test.js +131 -5
- package/dist/units.d.ts +9 -0
- package/dist/units.js +9 -0
- package/package.json +5 -1
package/dist/contracts.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { getAddress } from 'viem';
|
|
26
26
|
import { base, pulsechain } from 'viem/chains';
|
|
27
|
+
import { isChainId } from './chain.js';
|
|
27
28
|
export const reclaimSigners = {
|
|
28
29
|
peerWitnessSigner: '0x0636c417755E3ae25C6c166D181c0607F4C572A3',
|
|
29
30
|
reclaimWitnessSigner: '0x244897572368eadf65bfbc5aec98d8e5443a9072',
|
|
@@ -97,29 +98,29 @@ export const v4BaseContracts = {
|
|
|
97
98
|
orchestratorRegistry: getAddress('0xBe9fED15ED7A4B915C03EFcEcb9662739C3382A9'),
|
|
98
99
|
};
|
|
99
100
|
/**
|
|
100
|
-
* V4 contracts on PulseChain (Chain ID 369) —
|
|
101
|
-
*
|
|
102
|
-
* EscrowV2 + OrchestratorV2
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* `
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* export const v4PulsechainContracts = {
|
|
115
|
-
* usdc: getAddress('0x15D38573d2feeb82e7ad5187aB8c1D52810B1f07'), // same PLSD/USDC as v3
|
|
116
|
-
* gatingService: getAddress('0x…'),
|
|
117
|
-
* escrow: [getAddress('0x…')], // EscrowV2
|
|
118
|
-
* unifiedPaymentVerifier: [getAddress('0x…')], // UnifiedPaymentVerifierV2
|
|
119
|
-
* orchestrator: [getAddress('0x…')], // OrchestratorV2
|
|
120
|
-
* orchestratorRegistry: getAddress('0x…'),
|
|
121
|
-
* } as V4Contracts
|
|
101
|
+
* V4 contracts on PulseChain (Chain ID 369) — LIVE as of 2026-07-24.
|
|
102
|
+
*
|
|
103
|
+
* EscrowV2 + OrchestratorV2 landed on PulseChain mainnet at blocks
|
|
104
|
+
* 27112395–27112403 (deployer 0x948eB6D3A08BEb29Dc1D08d09753862573A94122).
|
|
105
|
+
* Bytecode verified on chain 369. Addresses taken from
|
|
106
|
+
* `zkp2p-contracts/deployments/pulsechain/{EscrowV2,OrchestratorV2,
|
|
107
|
+
* OrchestratorRegistry,UnifiedPaymentVerifierV2}.json`.
|
|
108
|
+
*
|
|
109
|
+
* Registered in the `contracts.v4` map below, so `getLatestConfiguredVersion(369)`
|
|
110
|
+
* now routes new PulseChain deposits to v4. This must ship alongside the indexer
|
|
111
|
+
* wiring in `packages/indexer/ponder.config.ts` (PulseChain v4 streams +
|
|
112
|
+
* `startBlocks.pulsechain.v4Escrow` = 27112398): deploy the indexer first so a
|
|
113
|
+
* fresh reindex covers the EscrowV2 events before the UI starts writing v4
|
|
114
|
+
* deposits it would otherwise fail to read.
|
|
122
115
|
*/
|
|
116
|
+
export const v4PulsechainContracts = {
|
|
117
|
+
usdc: getAddress('0x15D38573d2feeb82e7ad5187aB8c1D52810B1f07'), // same PLSD/USDC as v3
|
|
118
|
+
gatingService: getAddress('0x4Bd6364FCC4D8A537aBfee33B16E959794fDf9Cf'), // same off-chain signer as v3
|
|
119
|
+
escrow: [getAddress('0xD48184389e7FE8EFb4f16AE62d8eFad9145099e3')], // EscrowV2
|
|
120
|
+
unifiedPaymentVerifier: [getAddress('0x8966f5428CD8893722BF5EbF74D44BE93F600796')], // UnifiedPaymentVerifierV2
|
|
121
|
+
orchestrator: [getAddress('0xc5CcE8D7AAcFDa82111bC8896Eb0F8f49888d171')], // OrchestratorV2
|
|
122
|
+
orchestratorRegistry: getAddress('0x18c0CE7814895327518ae625F7c72cA1315dAb89'),
|
|
123
|
+
};
|
|
123
124
|
/**
|
|
124
125
|
* V3 contracts deployed to Pulsechain (Chain ID 369)
|
|
125
126
|
* Deployed: 2026-01-12
|
|
@@ -149,10 +150,36 @@ export const V3_PULSECHAIN_DEPLOYMENT_REFERENCE = {
|
|
|
149
150
|
nullifierRegistry: '0x4AeA2288ec542963256b66D70EfddF4cB8754f82',
|
|
150
151
|
simpleAttestationVerifier: '0x49DAd1f29a3FE2E4D90a59e6687d9f7A2836B4a8',
|
|
151
152
|
};
|
|
153
|
+
/**
|
|
154
|
+
* V4 (EscrowV2 / OrchestratorV2) Pulsechain ancillary contracts - FOR REFERENCE ONLY
|
|
155
|
+
*
|
|
156
|
+
* Deployed on chain 369 on 2026-07-24 but NOT read by any runtime code path today
|
|
157
|
+
* (unlike V3_PULSECHAIN_DEPLOYMENT_REFERENCE, whose entries the escrow exposes
|
|
158
|
+
* dynamically). Recorded purely as an ops reference — a documented home for the
|
|
159
|
+
* addresses you need when making owner calls or wiring a future feature. Do NOT
|
|
160
|
+
* import these into runtime paths; if one ever gets called, add a typed entry to
|
|
161
|
+
* the version contract sets above instead.
|
|
162
|
+
*/
|
|
163
|
+
export const V4_PULSECHAIN_DEPLOYMENT_REFERENCE = {
|
|
164
|
+
// N-of-M witness attestation verifier (EnumerableSet witnesses + configurable
|
|
165
|
+
// threshold; defaults to 1 = "any witness"). Swap target for the multi-
|
|
166
|
+
// attestation cutover via UnifiedPaymentVerifier.setAttestationVerifier();
|
|
167
|
+
// drop-in replacement for the single-witness simpleAttestationVerifier.
|
|
168
|
+
multiAttestationVerifier: '0xd98bdf683a8b218Ea45Cc8A258ef1A662ef8e9ae',
|
|
169
|
+
// Whitelist-groups system — only live on PulseChain (Base has neither yet).
|
|
170
|
+
// WhitelistPreIntentHookV2 admits a taker who is directly whitelisted OR a
|
|
171
|
+
// member of any attached AddressGroupRegistry group; supersedes the V1 hook.
|
|
172
|
+
addressGroupRegistry: '0x2e6E054eca4EC24149516AC8036Dd30c937237AF',
|
|
173
|
+
whitelistPreIntentHookV2: '0x4F7f6691f8c362d31d19567133Ce33CC3EEd6d51',
|
|
174
|
+
// Read-only v4 aggregator: every external fn is `view`, holds no state, and
|
|
175
|
+
// takes the escrow/orchestrator as call params (so it works against any v4
|
|
176
|
+
// deployment). Pairs with the V3 protocolViewer above.
|
|
177
|
+
protocolViewerV2: '0x02CC2997B73A1d1E1D0151319F25125B11A55565',
|
|
178
|
+
};
|
|
152
179
|
export const versions = {
|
|
153
180
|
V2: 'v2', // Deprecated: Historical data only
|
|
154
181
|
V3: 'v3', // Active: Primary version for Base buy/sell UI
|
|
155
|
-
V4: 'v4', // OrchestratorV2 + EscrowV2 (Base
|
|
182
|
+
V4: 'v4', // OrchestratorV2 + EscrowV2 (live on Base and PulseChain)
|
|
156
183
|
};
|
|
157
184
|
export const versionKeys = Object.values(versions);
|
|
158
185
|
/**
|
|
@@ -173,9 +200,11 @@ export const contracts = {
|
|
|
173
200
|
[pulsechain.id]: v3PulsechainContracts,
|
|
174
201
|
},
|
|
175
202
|
v4: {
|
|
176
|
-
// OrchestratorV2 + EscrowV2.
|
|
177
|
-
//
|
|
203
|
+
// OrchestratorV2 + EscrowV2. Live on both Base and PulseChain (369 deploy
|
|
204
|
+
// landed 2026-07-24). getLatestConfiguredVersion routes new deposits on both
|
|
205
|
+
// chains to v4.
|
|
178
206
|
[base.id]: v4BaseContracts,
|
|
207
|
+
[pulsechain.id]: v4PulsechainContracts,
|
|
179
208
|
},
|
|
180
209
|
};
|
|
181
210
|
/**
|
|
@@ -216,9 +245,9 @@ const VERSION_PRECEDENCE = ['v4', 'v3', 'v2'];
|
|
|
216
245
|
*
|
|
217
246
|
* This is the routing decision for **new deposits**: a chain automatically
|
|
218
247
|
* moves to v4 the moment its v4 contracts are registered in
|
|
219
|
-
* `contracts.v4[chainId]` — no code change, no flag flip. Base
|
|
220
|
-
* today (EscrowV2/OrchestratorV2 are live
|
|
221
|
-
*
|
|
248
|
+
* `contracts.v4[chainId]` — no code change, no flag flip. Both Base and
|
|
249
|
+
* PulseChain return `'v4'` today (EscrowV2/OrchestratorV2 are live on both; the
|
|
250
|
+
* PulseChain deploy landed 2026-07-24).
|
|
222
251
|
*
|
|
223
252
|
* Distinct from {@link getLatestContract}'s `getDefaultVersion` fallback, which
|
|
224
253
|
* stays on v3 for read paths that aggregate across generations. Only deposit
|
|
@@ -228,10 +257,9 @@ const VERSION_PRECEDENCE = ['v4', 'v3', 'v2'];
|
|
|
228
257
|
* @returns The newest version with a configured escrow, or `'v3'` as a safe fallback
|
|
229
258
|
*
|
|
230
259
|
* @example
|
|
231
|
-
* //
|
|
260
|
+
* // Both chains have v4 contracts registered → new deposits go to v4
|
|
232
261
|
* getLatestConfiguredVersion(base.id) // 'v4'
|
|
233
|
-
* //
|
|
234
|
-
* getLatestConfiguredVersion(pulsechain.id) // 'v3'
|
|
262
|
+
* getLatestConfiguredVersion(pulsechain.id) // 'v4'
|
|
235
263
|
*/
|
|
236
264
|
export const getLatestConfiguredVersion = (chainId) => {
|
|
237
265
|
for (const version of VERSION_PRECEDENCE) {
|
|
@@ -274,6 +302,83 @@ export const getLatestContract = (chainId, key, version) => {
|
|
|
274
302
|
return null;
|
|
275
303
|
return Array.isArray(target) ? target.at(-1) : target;
|
|
276
304
|
};
|
|
305
|
+
/**
|
|
306
|
+
* The UnifiedPaymentVerifier whose address belongs in the EIP-712 domain of a
|
|
307
|
+
* payment attestation on this chain.
|
|
308
|
+
*
|
|
309
|
+
* **Why this is not keyed on the deposit's generation.** A v3 deposit and a v4
|
|
310
|
+
* deposit do not get different verifiers. Both escrow generations point at the
|
|
311
|
+
* *same* `PaymentVerifierRegistry`, and the registry resolves a payment method
|
|
312
|
+
* to a verifier — so the verifier follows the registry's current mapping, never
|
|
313
|
+
* the escrow the deposit happens to live in. Verified on chain 369 on
|
|
314
|
+
* 2026-07-24: `escrow.paymentVerifierRegistry()` returns
|
|
315
|
+
* `0x6a4fF1950Be05995dEe75920cf27CD8FEbB6597A` for both the v3 escrow
|
|
316
|
+
* (`0x0B42…f11a`) and the v4 escrow (`0xD481…99e3`), and `getVerifier(...)` on
|
|
317
|
+
* that registry returns the v4 UnifiedPaymentVerifierV2
|
|
318
|
+
* (`0x8966f5428CD8893722BF5EbF74D44BE93F600796`) for **all 15** registered
|
|
319
|
+
* payment methods — including every method a v3 deposit uses.
|
|
320
|
+
*
|
|
321
|
+
* **Why it matters.** `UnifiedPaymentVerifier` builds its domain separator with
|
|
322
|
+
* `verifyingContract = address(this)`, so an attestation signed against the
|
|
323
|
+
* wrong verifier address produces a digest the on-chain check rejects. Pinning
|
|
324
|
+
* this to `'v3'` (as every call site used to) hands the signer the retired
|
|
325
|
+
* `0x0e73181a…` and every submission fails signature verification.
|
|
326
|
+
*
|
|
327
|
+
* Tracking the latest configured version keeps this correct through the next
|
|
328
|
+
* verifier generation too, for the same reason
|
|
329
|
+
* {@link getLatestConfiguredVersion} keeps deposit routing correct: registering
|
|
330
|
+
* the new addresses is the only step.
|
|
331
|
+
*
|
|
332
|
+
* @param chainId - The chain the intent lives on (use `deposit.chainId`).
|
|
333
|
+
* @returns The verifier address, or null when the chain has none configured.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* const verifyingContract = getPaymentAttestationVerifier(deposit.chainId)
|
|
338
|
+
* // chain 369 → 0x8966f5428CD8893722BF5EbF74D44BE93F600796
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
export const getPaymentAttestationVerifier = (chainId) => getLatestContract(chainId, 'unifiedPaymentVerifier', getLatestConfiguredVersion(chainId));
|
|
342
|
+
/**
|
|
343
|
+
* Check a client-supplied `verifyingContract` against the verifier this chain
|
|
344
|
+
* actually settles against, and refuse anything else.
|
|
345
|
+
*
|
|
346
|
+
* **Why a server-side check exists at all.** The attestation signers take
|
|
347
|
+
* `verifyingContract` from the request body and sign the EIP-712 digest built
|
|
348
|
+
* around it. Whatever address the caller names is what our key attests to. A
|
|
349
|
+
* stale browser bundle still holding the retired v3 verifier therefore gets a
|
|
350
|
+
* perfectly valid signature over the wrong domain — and the buyer discovers it
|
|
351
|
+
* only when `fulfillIntent` reverts on signature verification, *after* the
|
|
352
|
+
* fiat has left their bank. Nobody can refund that from our side.
|
|
353
|
+
*
|
|
354
|
+
* Fail closed: signing is the irreversible step, so an unrecognised chain or an
|
|
355
|
+
* unparseable address is a refusal, never a pass-through.
|
|
356
|
+
*
|
|
357
|
+
* Comparison is case-insensitive because the address arrives as JSON from a
|
|
358
|
+
* browser and may be checksummed, lowercased, or neither.
|
|
359
|
+
*
|
|
360
|
+
* @param chainId - The chain the attestation will settle on
|
|
361
|
+
* @param verifyingContract - The address the caller asked us to sign for
|
|
362
|
+
* @returns The configured address on success, or the reason for refusing
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* const check = checkPaymentAttestationVerifier({ chainId, verifyingContract })
|
|
367
|
+
* if (!check.ok) {
|
|
368
|
+
* res.status(400).json({ error: 'VERIFYING_CONTRACT_REJECTED', reason: check.reason })
|
|
369
|
+
* return
|
|
370
|
+
* }
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
export const checkPaymentAttestationVerifier = ({ chainId, verifyingContract, }) => {
|
|
374
|
+
const expected = isChainId(chainId) ? getPaymentAttestationVerifier(chainId) : null;
|
|
375
|
+
if (!expected)
|
|
376
|
+
return { ok: false, reason: 'unconfigured', expected: null, received: verifyingContract };
|
|
377
|
+
if (!verifyingContract || verifyingContract.toLowerCase() !== expected.toLowerCase()) {
|
|
378
|
+
return { ok: false, reason: 'mismatch', expected, received: verifyingContract };
|
|
379
|
+
}
|
|
380
|
+
return { ok: true, verifyingContract: expected };
|
|
381
|
+
};
|
|
277
382
|
/**
|
|
278
383
|
* Get all escrow contracts across all versions and chains.
|
|
279
384
|
* Returns a flat list of all escrow locations for querying deposits.
|
|
@@ -308,6 +413,58 @@ export function getAllEscrowContracts() {
|
|
|
308
413
|
}
|
|
309
414
|
return escrows;
|
|
310
415
|
}
|
|
416
|
+
/** Lazily-built escrow address (lowercased) → location index. */
|
|
417
|
+
let escrowLocationIndex = null;
|
|
418
|
+
/**
|
|
419
|
+
* Locate a known escrow by address: which generation and which chain it is.
|
|
420
|
+
*
|
|
421
|
+
* The address is the only thing a deposit row reliably carries, so this is how
|
|
422
|
+
* a call site with a `deposit` in hand answers "is this v3 or v4?" without a
|
|
423
|
+
* network read.
|
|
424
|
+
*
|
|
425
|
+
* @param escrow - The escrow contract address (case-insensitive).
|
|
426
|
+
* @returns Its location, or null when the address is not a configured escrow.
|
|
427
|
+
*/
|
|
428
|
+
export function getEscrowLocation(escrow) {
|
|
429
|
+
if (!escrowLocationIndex) {
|
|
430
|
+
escrowLocationIndex = new Map(getAllEscrowContracts().map((location) => [location.escrow.toLowerCase(), location]));
|
|
431
|
+
}
|
|
432
|
+
return escrowLocationIndex.get(escrow.toLowerCase()) ?? null;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* The protocol generation an escrow address belongs to.
|
|
436
|
+
*
|
|
437
|
+
* @param escrow - The escrow contract address (case-insensitive).
|
|
438
|
+
* @returns The version, or null when the address is not a configured escrow.
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* detectVersionFromEscrow('0xD48184389e7FE8EFb4f16AE62d8eFad9145099e3') // 'v4'
|
|
442
|
+
*/
|
|
443
|
+
export function detectVersionFromEscrow(escrow) {
|
|
444
|
+
return getEscrowLocation(escrow)?.version ?? null;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* The Orchestrator that governs intents for a given escrow.
|
|
448
|
+
*
|
|
449
|
+
* **Only v4 can be answered from configuration, and only v4 has to be.** A v2
|
|
450
|
+
* or v3 escrow exposes `orchestrator()` and callers read it from the chain.
|
|
451
|
+
* EscrowV2 (v4) does not: it authorizes through an `OrchestratorRegistry` and
|
|
452
|
+
* has no `orchestrator()` function at all, so that call reverts. The v4
|
|
453
|
+
* orchestrator address is therefore held explicitly in the v4 contract sets and
|
|
454
|
+
* returned from here.
|
|
455
|
+
*
|
|
456
|
+
* @param escrow - The escrow contract address (case-insensitive).
|
|
457
|
+
* @returns The orchestrator address for a v4 escrow; null for any other
|
|
458
|
+
* generation (the caller should read `orchestrator()` on-chain instead) and
|
|
459
|
+
* for unknown addresses.
|
|
460
|
+
*/
|
|
461
|
+
export function getConfiguredOrchestratorForEscrow(escrow) {
|
|
462
|
+
const location = getEscrowLocation(escrow);
|
|
463
|
+
if (!location || location.version !== 'v4')
|
|
464
|
+
return null;
|
|
465
|
+
const v4ForChain = contracts.v4[location.chainId];
|
|
466
|
+
return v4ForChain?.orchestrator?.at(-1) ?? null;
|
|
467
|
+
}
|
|
311
468
|
/**
|
|
312
469
|
* Get all V3 escrow contracts across all chains.
|
|
313
470
|
* Used for cross-chain nullifier checking to prevent double-spending.
|
package/dist/contracts.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 { getContracts, getLatestContract, getLatestConfiguredVersion, getAllEscrowContracts, getAllV3EscrowContracts, shortenEscrowAddress, getEscrowVersion, isV3Escrow, supportsOrchestrator, supportsOrchestratorEscrow, v2BaseContracts, v3BaseContracts, v4BaseContracts, v2PulsechainContracts, v3PulsechainContracts, V3_PULSECHAIN_DEPLOYMENT_REFERENCE, contracts, } from './contracts.js';
|
|
4
|
+
import { getContracts, getLatestContract, getLatestConfiguredVersion, getPaymentAttestationVerifier, checkPaymentAttestationVerifier, detectVersionFromEscrow, getConfiguredOrchestratorForEscrow, getAllEscrowContracts, getAllV3EscrowContracts, shortenEscrowAddress, getEscrowVersion, isV3Escrow, supportsOrchestrator, supportsOrchestratorEscrow, v2BaseContracts, v3BaseContracts, v4BaseContracts, v2PulsechainContracts, v3PulsechainContracts, v4PulsechainContracts, V3_PULSECHAIN_DEPLOYMENT_REFERENCE, contracts, } from './contracts.js';
|
|
5
5
|
describe('contracts', () => {
|
|
6
6
|
describe('getDefaultVersion', () => {
|
|
7
7
|
it('returns v3 for Base', async () => {
|
|
@@ -24,12 +24,14 @@ describe('contracts', () => {
|
|
|
24
24
|
it('routes Base to v4 because EscrowV2/OrchestratorV2 are registered', () => {
|
|
25
25
|
assert.strictEqual(getLatestConfiguredVersion(base.id), 'v4');
|
|
26
26
|
});
|
|
27
|
-
it('
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
it('routes PulseChain to v4 now that its EscrowV2 is registered', () => {
|
|
28
|
+
// The PulseChain v4 deploy landed 2026-07-24 and its addresses went into
|
|
29
|
+
// contracts.v4[369], which is the entire flip: registering the escrow is
|
|
30
|
+
// what moves new deposits onto it. Every read path keyed on the
|
|
31
|
+
// generation has to follow — see `buyEligibleDepositVersionsCondition`
|
|
32
|
+
// and `getPaymentAttestationVerifier`.
|
|
33
|
+
assert.ok(getContracts('v4', pulsechain.id)?.escrow?.length);
|
|
34
|
+
assert.strictEqual(getLatestConfiguredVersion(pulsechain.id), 'v4');
|
|
33
35
|
});
|
|
34
36
|
it('resolves to a version that has an escrow, with nothing newer configured', () => {
|
|
35
37
|
// The invariant that makes the auto-flip safe: the resolved version is
|
|
@@ -336,4 +338,129 @@ describe('contracts', () => {
|
|
|
336
338
|
assert.strictEqual(supportsOrchestratorEscrow('0x0000000000000000000000000000000000000000', base.id), false);
|
|
337
339
|
});
|
|
338
340
|
});
|
|
341
|
+
describe('getPaymentAttestationVerifier', () => {
|
|
342
|
+
// The EIP-712 domain of a payment attestation carries the verifier's own
|
|
343
|
+
// address (`verifyingContract = address(this)` in UnifiedPaymentVerifier),
|
|
344
|
+
// so signing against the wrong one yields a digest the on-chain check
|
|
345
|
+
// rejects and every submission fails.
|
|
346
|
+
it('follows the latest configured generation, not a pinned v3', () => {
|
|
347
|
+
// Both escrow generations share one PaymentVerifierRegistry, and that
|
|
348
|
+
// registry resolves every payment method to the newest verifier — so the
|
|
349
|
+
// answer tracks the newest configured version rather than the deposit's
|
|
350
|
+
// own escrow. Verified on chain 369 on 2026-07-24: all 15 registered
|
|
351
|
+
// methods, v3 deposits included, resolve to the v4 verifier.
|
|
352
|
+
for (const chainId of [base.id, pulsechain.id]) {
|
|
353
|
+
assert.strictEqual(getPaymentAttestationVerifier(chainId), getLatestContract(chainId, 'unifiedPaymentVerifier', getLatestConfiguredVersion(chainId)));
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
it('does not return the retired v3 verifier on PulseChain', () => {
|
|
357
|
+
assert.notStrictEqual(getPaymentAttestationVerifier(pulsechain.id), v3PulsechainContracts.unifiedPaymentVerifier.at(-1));
|
|
358
|
+
assert.strictEqual(getPaymentAttestationVerifier(pulsechain.id), v4PulsechainContracts.unifiedPaymentVerifier.at(-1));
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
describe('detectVersionFromEscrow', () => {
|
|
362
|
+
it('names the generation an escrow address belongs to', () => {
|
|
363
|
+
assert.strictEqual(detectVersionFromEscrow(v2BaseContracts.escrow[0]), 'v2');
|
|
364
|
+
assert.strictEqual(detectVersionFromEscrow(v3PulsechainContracts.escrow[0]), 'v3');
|
|
365
|
+
assert.strictEqual(detectVersionFromEscrow(v4PulsechainContracts.escrow[0]), 'v4');
|
|
366
|
+
});
|
|
367
|
+
it('is case-insensitive — indexer rows store lowercased addresses', () => {
|
|
368
|
+
assert.strictEqual(detectVersionFromEscrow(v4PulsechainContracts.escrow[0].toLowerCase()), 'v4');
|
|
369
|
+
});
|
|
370
|
+
it('returns null for an address that is not a configured escrow', () => {
|
|
371
|
+
assert.strictEqual(detectVersionFromEscrow('0x0000000000000000000000000000000000000000'), null);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
describe('getConfiguredOrchestratorForEscrow', () => {
|
|
375
|
+
it('answers for v4, whose escrow has no orchestrator() to read', () => {
|
|
376
|
+
// EscrowV2 authorizes through an OrchestratorRegistry and exposes no
|
|
377
|
+
// `orchestrator()`, so that call reverts and the address has to come
|
|
378
|
+
// from configuration. Without this, every dependent step (fulfill,
|
|
379
|
+
// payee details, fee display) waits on an address that never arrives.
|
|
380
|
+
assert.strictEqual(getConfiguredOrchestratorForEscrow(v4PulsechainContracts.escrow[0]), v4PulsechainContracts.orchestrator.at(-1));
|
|
381
|
+
});
|
|
382
|
+
it('returns null for v2/v3, which expose orchestrator() and are read on-chain', () => {
|
|
383
|
+
assert.strictEqual(getConfiguredOrchestratorForEscrow(v3PulsechainContracts.escrow[0]), null);
|
|
384
|
+
assert.strictEqual(getConfiguredOrchestratorForEscrow(v2BaseContracts.escrow[0]), null);
|
|
385
|
+
});
|
|
386
|
+
it('returns null for an unknown escrow address', () => {
|
|
387
|
+
assert.strictEqual(getConfiguredOrchestratorForEscrow('0x0000000000000000000000000000000000000000'), null);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
describe('checkPaymentAttestationVerifier', () => {
|
|
391
|
+
// These are not "does the comparison work" tests. The signer takes
|
|
392
|
+
// `verifyingContract` from the request body and signs the EIP-712 digest
|
|
393
|
+
// built around it, so whatever passes this check is what our key attests
|
|
394
|
+
// to. A pass that should have been a refusal produces a valid signature
|
|
395
|
+
// over the wrong domain, and the buyer learns about it when
|
|
396
|
+
// `fulfillIntent` reverts AFTER their fiat has already left their bank.
|
|
397
|
+
// Every assertion below is about that one failure.
|
|
398
|
+
it('accepts the verifier the chain actually settles against', () => {
|
|
399
|
+
const expected = getPaymentAttestationVerifier(pulsechain.id);
|
|
400
|
+
const result = checkPaymentAttestationVerifier({
|
|
401
|
+
chainId: pulsechain.id,
|
|
402
|
+
verifyingContract: expected,
|
|
403
|
+
});
|
|
404
|
+
assert.strictEqual(result.ok, true);
|
|
405
|
+
assert.strictEqual(result.ok && result.verifyingContract, expected);
|
|
406
|
+
});
|
|
407
|
+
it('accepts regardless of address casing', () => {
|
|
408
|
+
// The address arrives as JSON from a browser and may be checksummed,
|
|
409
|
+
// lowercased, or neither. Rejecting on casing alone would break every
|
|
410
|
+
// honest client.
|
|
411
|
+
const expected = getPaymentAttestationVerifier(pulsechain.id);
|
|
412
|
+
assert.strictEqual(checkPaymentAttestationVerifier({
|
|
413
|
+
chainId: pulsechain.id,
|
|
414
|
+
verifyingContract: expected.toLowerCase(),
|
|
415
|
+
}).ok, true);
|
|
416
|
+
assert.strictEqual(checkPaymentAttestationVerifier({
|
|
417
|
+
chainId: pulsechain.id,
|
|
418
|
+
verifyingContract: expected.toUpperCase().replace('0X', '0x'),
|
|
419
|
+
}).ok, true);
|
|
420
|
+
});
|
|
421
|
+
it('refuses the retired verifier a stale bundle would ask for', () => {
|
|
422
|
+
// The concrete regression: a browser still running the pre-v4 bundle
|
|
423
|
+
// names the v3 UnifiedPaymentVerifier. It is a real, once-correct
|
|
424
|
+
// address — nothing about it looks malformed.
|
|
425
|
+
const retired = getLatestContract(pulsechain.id, 'unifiedPaymentVerifier', 'v3');
|
|
426
|
+
const current = getPaymentAttestationVerifier(pulsechain.id);
|
|
427
|
+
assert.notStrictEqual(retired, current, 'fixture is meaningless if these match');
|
|
428
|
+
const result = checkPaymentAttestationVerifier({
|
|
429
|
+
chainId: pulsechain.id,
|
|
430
|
+
verifyingContract: retired,
|
|
431
|
+
});
|
|
432
|
+
assert.strictEqual(result.ok, false);
|
|
433
|
+
assert.strictEqual(!result.ok && result.reason, 'mismatch');
|
|
434
|
+
assert.strictEqual(!result.ok && result.expected, current);
|
|
435
|
+
});
|
|
436
|
+
it('refuses an arbitrary address', () => {
|
|
437
|
+
const result = checkPaymentAttestationVerifier({
|
|
438
|
+
chainId: pulsechain.id,
|
|
439
|
+
verifyingContract: '0x000000000000000000000000000000000000dEaD',
|
|
440
|
+
});
|
|
441
|
+
assert.strictEqual(result.ok, false);
|
|
442
|
+
assert.strictEqual(!result.ok && result.reason, 'mismatch');
|
|
443
|
+
});
|
|
444
|
+
it('refuses a missing verifyingContract rather than defaulting to the configured one', () => {
|
|
445
|
+
// Fail closed. Silently substituting the configured address would sign
|
|
446
|
+
// for a caller who never named a domain at all.
|
|
447
|
+
const result = checkPaymentAttestationVerifier({
|
|
448
|
+
chainId: pulsechain.id,
|
|
449
|
+
verifyingContract: undefined,
|
|
450
|
+
});
|
|
451
|
+
assert.strictEqual(result.ok, false);
|
|
452
|
+
assert.strictEqual(!result.ok && result.reason, 'mismatch');
|
|
453
|
+
});
|
|
454
|
+
it('refuses an unsupported chain instead of trusting the caller', () => {
|
|
455
|
+
// chainId reaches the handler as untyped JSON. An unknown chain has no
|
|
456
|
+
// verifier to compare against, so there is nothing to validate — the
|
|
457
|
+
// only safe answer is no.
|
|
458
|
+
const result = checkPaymentAttestationVerifier({
|
|
459
|
+
chainId: 1,
|
|
460
|
+
verifyingContract: getPaymentAttestationVerifier(pulsechain.id),
|
|
461
|
+
});
|
|
462
|
+
assert.strictEqual(result.ok, false);
|
|
463
|
+
assert.strictEqual(!result.ok && result.reason, 'unconfigured');
|
|
464
|
+
});
|
|
465
|
+
});
|
|
339
466
|
});
|
package/dist/deposit.d.ts
CHANGED
|
@@ -68,3 +68,53 @@ export type EscrowVersion = (typeof escrowVersions)[keyof typeof escrowVersions]
|
|
|
68
68
|
* Array of escrow version values for use with onchainEnum in ponder.schema.ts.
|
|
69
69
|
*/
|
|
70
70
|
export declare const ESCROW_VERSIONS: readonly EscrowVersion[];
|
|
71
|
+
/**
|
|
72
|
+
* The escrow generations that make up the protocol as it runs today.
|
|
73
|
+
*
|
|
74
|
+
* Public surfaces — the activity chart, the deposits table, the analytics
|
|
75
|
+
* dashboard — deliberately exclude v2 on PulseChain: those deposits predate the
|
|
76
|
+
* v3 launch entirely and are abandoned, so counting them makes today's numbers
|
|
77
|
+
* unreadable. That exclusion used to be written as `version === 'v3'` at each
|
|
78
|
+
* call site, which quietly became "everything except the current generation"
|
|
79
|
+
* the day v4 went live and flat-lined every one of those surfaces.
|
|
80
|
+
*
|
|
81
|
+
* Name the set once so shipping a generation is one edit here, not a hunt for
|
|
82
|
+
* every hard-coded `'v3'`.
|
|
83
|
+
*/
|
|
84
|
+
export declare const CURRENT_ESCROW_VERSIONS: readonly ["v3", "v4"];
|
|
85
|
+
/**
|
|
86
|
+
* A currency entry as the escrow's `createDeposit` / `addPaymentMethods` /
|
|
87
|
+
* `addCurrencies` calls expect it, before generation-specific fields.
|
|
88
|
+
*/
|
|
89
|
+
export type EscrowCurrency = {
|
|
90
|
+
code: `0x${string}`;
|
|
91
|
+
minConversionRate: bigint;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* v4's currency struct carries an `oracleRateConfig`; a zeroed config means
|
|
95
|
+
* "no oracle — settle at the fixed `minConversionRate`", which reproduces v3
|
|
96
|
+
* behaviour exactly. Deposits that do not price off an oracle use this.
|
|
97
|
+
*/
|
|
98
|
+
export declare const EMPTY_ORACLE_RATE_CONFIG: {
|
|
99
|
+
readonly adapter: `0x${string}`;
|
|
100
|
+
readonly adapterConfig: `0x${string}`;
|
|
101
|
+
readonly spreadBps: 0;
|
|
102
|
+
readonly maxStaleness: 0;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Shape currency entries for a given escrow generation.
|
|
106
|
+
*
|
|
107
|
+
* Three escrow writes changed their currency tuple in v4 — `createDeposit`,
|
|
108
|
+
* `addPaymentMethods` and `addCurrencies` all gained the oracle rate config,
|
|
109
|
+
* which changes their selector. Sending v3-shaped currencies to EscrowV2
|
|
110
|
+
* produces a call it cannot decode; sending v4-shaped currencies to a v3
|
|
111
|
+
* escrow does the same. Route every currency list through here so the two
|
|
112
|
+
* stay paired with the interface the caller selected.
|
|
113
|
+
*
|
|
114
|
+
* @param currencies - The currency entries to shape
|
|
115
|
+
* @param version - The escrow generation being written to
|
|
116
|
+
* @returns The entries, with the zeroed oracle config attached for v4
|
|
117
|
+
*/
|
|
118
|
+
export declare const toEscrowCurrencies: <T extends EscrowCurrency>(currencies: readonly T[], version: EscrowVersion) => readonly (T | (T & {
|
|
119
|
+
oracleRateConfig: typeof EMPTY_ORACLE_RATE_CONFIG;
|
|
120
|
+
}))[];
|
package/dist/deposit.js
CHANGED
|
@@ -51,3 +51,48 @@ export const escrowVersions = {
|
|
|
51
51
|
* Array of escrow version values for use with onchainEnum in ponder.schema.ts.
|
|
52
52
|
*/
|
|
53
53
|
export const ESCROW_VERSIONS = Object.values(escrowVersions);
|
|
54
|
+
/**
|
|
55
|
+
* The escrow generations that make up the protocol as it runs today.
|
|
56
|
+
*
|
|
57
|
+
* Public surfaces — the activity chart, the deposits table, the analytics
|
|
58
|
+
* dashboard — deliberately exclude v2 on PulseChain: those deposits predate the
|
|
59
|
+
* v3 launch entirely and are abandoned, so counting them makes today's numbers
|
|
60
|
+
* unreadable. That exclusion used to be written as `version === 'v3'` at each
|
|
61
|
+
* call site, which quietly became "everything except the current generation"
|
|
62
|
+
* the day v4 went live and flat-lined every one of those surfaces.
|
|
63
|
+
*
|
|
64
|
+
* Name the set once so shipping a generation is one edit here, not a hunt for
|
|
65
|
+
* every hard-coded `'v3'`.
|
|
66
|
+
*/
|
|
67
|
+
export const CURRENT_ESCROW_VERSIONS = [
|
|
68
|
+
escrowVersions.V3,
|
|
69
|
+
escrowVersions.V4,
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* v4's currency struct carries an `oracleRateConfig`; a zeroed config means
|
|
73
|
+
* "no oracle — settle at the fixed `minConversionRate`", which reproduces v3
|
|
74
|
+
* behaviour exactly. Deposits that do not price off an oracle use this.
|
|
75
|
+
*/
|
|
76
|
+
export const EMPTY_ORACLE_RATE_CONFIG = {
|
|
77
|
+
adapter: '0x0000000000000000000000000000000000000000',
|
|
78
|
+
adapterConfig: '0x',
|
|
79
|
+
spreadBps: 0,
|
|
80
|
+
maxStaleness: 0,
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Shape currency entries for a given escrow generation.
|
|
84
|
+
*
|
|
85
|
+
* Three escrow writes changed their currency tuple in v4 — `createDeposit`,
|
|
86
|
+
* `addPaymentMethods` and `addCurrencies` all gained the oracle rate config,
|
|
87
|
+
* which changes their selector. Sending v3-shaped currencies to EscrowV2
|
|
88
|
+
* produces a call it cannot decode; sending v4-shaped currencies to a v3
|
|
89
|
+
* escrow does the same. Route every currency list through here so the two
|
|
90
|
+
* stay paired with the interface the caller selected.
|
|
91
|
+
*
|
|
92
|
+
* @param currencies - The currency entries to shape
|
|
93
|
+
* @param version - The escrow generation being written to
|
|
94
|
+
* @returns The entries, with the zeroed oracle config attached for v4
|
|
95
|
+
*/
|
|
96
|
+
export const toEscrowCurrencies = (currencies, version) => version === 'v4'
|
|
97
|
+
? currencies.map((currency) => ({ ...currency, oracleRateConfig: EMPTY_ORACLE_RATE_CONFIG }))
|
|
98
|
+
: currencies;
|
package/dist/events.d.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* 3. Deposit close events (withdrawal, close)
|
|
24
24
|
* 4. Protocol configuration events (dust, fees, registries)
|
|
25
25
|
*/
|
|
26
|
-
export declare const eventOrder: readonly ["deposit_received", "deposit_payment_method_added", "deposit_payment_method_active_updated", "deposit_currency_added", "deposit_delegate_set", "deposit_delegate_removed", "deposit_conversion_rate_updated", "deposit_accepting_intents_updated", "deposit_intent_amount_range_updated", "deposit_funds_added", "intent_signaled", "funds_locked", "payment_verified", "funds_unlocked_and_transferred", "intent_fulfilled", "intent_pruned", "funds_unlocked", "intent_expiry_extended", "funds_removed", "deposit_withdrawn", "deposit_closed", "dust_collected", "dust_recipient_updated", "dust_threshold_updated", "deposit_retain_on_empty_updated", "payment_method_currency_added", "payment_method_currency_removed", "payment_method_added", "payment_method_removed", "allow_multiple_intents_updated", "escrow_registry_updated", "post_intent_hook_registry_updated", "protocol_fee_recipient_updated", "protocol_fee_updated", "relayer_registry_updated", "unpaused", "paused", "intent_expiration_period_updated", "max_intents_per_deposit_updated", "min_deposit_amount_set", "orchestrator_updated", "ownership_transferred", "payment_verifier_registry_updated", "provider_hash_added", "provider_hash_removed"];
|
|
26
|
+
export declare const eventOrder: readonly ["deposit_received", "deposit_payment_method_added", "deposit_payment_method_active_updated", "deposit_currency_added", "deposit_delegate_set", "deposit_delegate_removed", "deposit_rate_manager_set", "deposit_rate_manager_cleared", "deposit_pre_intent_hook_set", "deposit_whitelist_hook_set", "deposit_conversion_rate_updated", "deposit_oracle_rate_config_set", "deposit_oracle_rate_config_removed", "deposit_accepting_intents_updated", "deposit_intent_amount_range_updated", "deposit_funds_added", "intent_signaled", "funds_locked", "payment_verified", "funds_unlocked_and_transferred", "intent_fulfilled", "intent_pruned", "funds_unlocked", "intent_expiry_extended", "intent_manager_fee_snapshotted", "intent_referral_fee_distributed", "funds_removed", "deposit_withdrawn", "deposit_closed", "dust_collected", "dust_recipient_updated", "dust_threshold_updated", "deposit_retain_on_empty_updated", "payment_method_currency_added", "payment_method_currency_removed", "payment_method_added", "payment_method_removed", "allow_multiple_intents_updated", "escrow_registry_updated", "orchestrator_registry_updated", "partial_manual_release_delay_updated", "post_intent_hook_registry_updated", "protocol_fee_recipient_updated", "protocol_fee_updated", "relayer_registry_updated", "unpaused", "paused", "intent_expiration_period_updated", "max_intents_per_deposit_updated", "min_deposit_amount_set", "orchestrator_updated", "ownership_transferred", "payment_verifier_registry_updated", "provider_hash_added", "provider_hash_removed"];
|
|
27
27
|
/**
|
|
28
28
|
* Union type of all valid event type names.
|
|
29
29
|
* Use this when filtering or handling specific event types.
|
|
@@ -38,4 +38,4 @@ export type EventType = (typeof eventOrder)[number];
|
|
|
38
38
|
* const id = eventOrderId.get('intent_signaled') // 9
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
|
-
export declare const eventOrderId: Map<"paused" | "deposit_received" | "deposit_payment_method_added" | "deposit_payment_method_active_updated" | "deposit_currency_added" | "deposit_delegate_set" | "deposit_delegate_removed" | "deposit_conversion_rate_updated" | "deposit_accepting_intents_updated" | "deposit_intent_amount_range_updated" | "deposit_funds_added" | "intent_signaled" | "funds_locked" | "payment_verified" | "funds_unlocked_and_transferred" | "intent_fulfilled" | "intent_pruned" | "funds_unlocked" | "intent_expiry_extended" | "funds_removed" | "deposit_withdrawn" | "deposit_closed" | "dust_collected" | "dust_recipient_updated" | "dust_threshold_updated" | "deposit_retain_on_empty_updated" | "payment_method_currency_added" | "payment_method_currency_removed" | "payment_method_added" | "payment_method_removed" | "allow_multiple_intents_updated" | "escrow_registry_updated" | "post_intent_hook_registry_updated" | "protocol_fee_recipient_updated" | "protocol_fee_updated" | "relayer_registry_updated" | "unpaused" | "intent_expiration_period_updated" | "max_intents_per_deposit_updated" | "min_deposit_amount_set" | "orchestrator_updated" | "ownership_transferred" | "payment_verifier_registry_updated" | "provider_hash_added" | "provider_hash_removed", number>;
|
|
41
|
+
export declare const eventOrderId: Map<"paused" | "deposit_received" | "deposit_payment_method_added" | "deposit_payment_method_active_updated" | "deposit_currency_added" | "deposit_delegate_set" | "deposit_delegate_removed" | "deposit_rate_manager_set" | "deposit_rate_manager_cleared" | "deposit_pre_intent_hook_set" | "deposit_whitelist_hook_set" | "deposit_conversion_rate_updated" | "deposit_oracle_rate_config_set" | "deposit_oracle_rate_config_removed" | "deposit_accepting_intents_updated" | "deposit_intent_amount_range_updated" | "deposit_funds_added" | "intent_signaled" | "funds_locked" | "payment_verified" | "funds_unlocked_and_transferred" | "intent_fulfilled" | "intent_pruned" | "funds_unlocked" | "intent_expiry_extended" | "intent_manager_fee_snapshotted" | "intent_referral_fee_distributed" | "funds_removed" | "deposit_withdrawn" | "deposit_closed" | "dust_collected" | "dust_recipient_updated" | "dust_threshold_updated" | "deposit_retain_on_empty_updated" | "payment_method_currency_added" | "payment_method_currency_removed" | "payment_method_added" | "payment_method_removed" | "allow_multiple_intents_updated" | "escrow_registry_updated" | "orchestrator_registry_updated" | "partial_manual_release_delay_updated" | "post_intent_hook_registry_updated" | "protocol_fee_recipient_updated" | "protocol_fee_updated" | "relayer_registry_updated" | "unpaused" | "intent_expiration_period_updated" | "max_intents_per_deposit_updated" | "min_deposit_amount_set" | "orchestrator_updated" | "ownership_transferred" | "payment_verifier_registry_updated" | "provider_hash_added" | "provider_hash_removed", number>;
|
package/dist/events.js
CHANGED
|
@@ -31,8 +31,23 @@ export const eventOrder = [
|
|
|
31
31
|
'deposit_currency_added',
|
|
32
32
|
'deposit_delegate_set',
|
|
33
33
|
'deposit_delegate_removed',
|
|
34
|
+
// v4: rate-management authority, delegated separately from the deposit
|
|
35
|
+
// delegate. Grouped with the delegate events because it is the same kind of
|
|
36
|
+
// fact — who, other than the owner, may act on this deposit.
|
|
37
|
+
'deposit_rate_manager_set',
|
|
38
|
+
'deposit_rate_manager_cleared',
|
|
39
|
+
// v4: per-deposit hooks held on the orchestrator. A whitelist hook makes a
|
|
40
|
+
// deposit reservable only by addresses it approves, so a reader that ignores
|
|
41
|
+
// this offers the deposit to buyers whose `signalIntent` will revert.
|
|
42
|
+
'deposit_pre_intent_hook_set',
|
|
43
|
+
'deposit_whitelist_hook_set',
|
|
34
44
|
// configuration events (these happen before funds_added in deposit creation)
|
|
35
45
|
'deposit_conversion_rate_updated',
|
|
46
|
+
// v4: an oracle config makes `deposit_conversion_rate_updated` a FLOOR rather
|
|
47
|
+
// than the settlement rate, so it belongs immediately beside it in a
|
|
48
|
+
// deposit's history — reading one without the other misstates the price.
|
|
49
|
+
'deposit_oracle_rate_config_set',
|
|
50
|
+
'deposit_oracle_rate_config_removed',
|
|
36
51
|
'deposit_accepting_intents_updated',
|
|
37
52
|
'deposit_intent_amount_range_updated',
|
|
38
53
|
// funds added comes after configuration so it appears first in UI (newest-first)
|
|
@@ -46,6 +61,9 @@ export const eventOrder = [
|
|
|
46
61
|
'intent_pruned',
|
|
47
62
|
'funds_unlocked',
|
|
48
63
|
'intent_expiry_extended',
|
|
64
|
+
// v4: fee accounting recorded against a settled intent.
|
|
65
|
+
'intent_manager_fee_snapshotted',
|
|
66
|
+
'intent_referral_fee_distributed',
|
|
49
67
|
// deposit close events
|
|
50
68
|
'funds_removed', // synthetic UI event for partial withdrawal (remaining > 0)
|
|
51
69
|
'deposit_withdrawn', // full withdrawal that closes the deposit (remaining = 0)
|
|
@@ -63,6 +81,8 @@ export const eventOrder = [
|
|
|
63
81
|
'payment_method_removed',
|
|
64
82
|
'allow_multiple_intents_updated',
|
|
65
83
|
'escrow_registry_updated',
|
|
84
|
+
'orchestrator_registry_updated',
|
|
85
|
+
'partial_manual_release_delay_updated',
|
|
66
86
|
'post_intent_hook_registry_updated',
|
|
67
87
|
'protocol_fee_recipient_updated',
|
|
68
88
|
'protocol_fee_updated',
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for currency-aware payment-provider selection.
|
|
3
|
+
*
|
|
4
|
+
* These exist because the Buy page auto-picked a payment method without ever
|
|
5
|
+
* consulting the buyer's currency. The volume leaders on every chain are
|
|
6
|
+
* United States dollar rails (Venmo, Zelle, Cash App), so a buyer who chose
|
|
7
|
+
* pounds or euros was seeded onto a rail that structurally cannot serve them
|
|
8
|
+
* and shown an empty order book. Each test below names the behaviour that
|
|
9
|
+
* failure would break, not just the value it returns.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|