@rhinestone/shared-configs 2.0.1 → 2.1.1

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.
@@ -0,0 +1,541 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const viem_1 = require("viem");
4
+ const accounts_1 = require("viem/accounts");
5
+ const index_1 = require("../src/index");
6
+ const probeAbi = (0, viem_1.parseAbi)([
7
+ 'function DOMAIN_SEPARATOR() view returns (bytes32)',
8
+ 'function nonces(address) view returns (uint256)',
9
+ 'function authorizationState(address,bytes32) view returns (bool)',
10
+ 'function implementation() view returns (address)',
11
+ 'function getFacet(bytes4) view returns (address)',
12
+ 'function PERMIT_TYPEHASH() view returns (bytes32)',
13
+ 'function TRANSFER_WITH_AUTHORIZATION_TYPEHASH() view returns (bytes32)',
14
+ 'function RECEIVE_WITH_AUTHORIZATION_TYPEHASH() view returns (bytes32)',
15
+ ]);
16
+ const permitAbi = (0, viem_1.parseAbi)([
17
+ 'function permit(address,address,uint256,uint256,uint8,bytes32,bytes32)',
18
+ 'function permit(address,address,uint256,uint256,bytes)',
19
+ ]);
20
+ const authorizationAbi = (0, viem_1.parseAbi)([
21
+ 'function receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)',
22
+ 'function receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)',
23
+ ]);
24
+ const erc1271Abi = (0, viem_1.parseAbi)([
25
+ 'function isValidSignature(bytes32,bytes) view returns (bytes4)',
26
+ ]);
27
+ const selectors = {
28
+ permit: (0, viem_1.toFunctionSelector)('permit(address,address,uint256,uint256,uint8,bytes32,bytes32)'),
29
+ authorizationState: (0, viem_1.toFunctionSelector)('authorizationState(address,bytes32)'),
30
+ transferWithAuthorization: (0, viem_1.toFunctionSelector)('transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)'),
31
+ receiveWithAuthorization: (0, viem_1.toFunctionSelector)('receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)'),
32
+ permitBytes: (0, viem_1.toFunctionSelector)('permit(address,address,uint256,uint256,bytes)'),
33
+ transferWithAuthorizationBytes: (0, viem_1.toFunctionSelector)('transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)'),
34
+ receiveWithAuthorizationBytes: (0, viem_1.toFunctionSelector)('receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)'),
35
+ };
36
+ const expectedTypehashes = {
37
+ permit: (0, viem_1.keccak256)((0, viem_1.toBytes)('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)')),
38
+ transferWithAuthorization: (0, viem_1.keccak256)((0, viem_1.toBytes)('TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)')),
39
+ receiveWithAuthorization: (0, viem_1.keccak256)((0, viem_1.toBytes)('ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)')),
40
+ };
41
+ const EIP1967_IMPLEMENTATION_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc';
42
+ const LEGACY_ZEPPELIN_IMPLEMENTATION_SLOT = '0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3';
43
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
44
+ const ZERO_BYTES32 = `0x${'00'.repeat(32)}`;
45
+ const MAX_UINT256 = (1n << 256n) - 1n;
46
+ const ERC1271_MAGIC_VALUE = '0x1626ba7e';
47
+ // Runtime that returns the ERC-1271 magic value for every call. The audit
48
+ // installs it at a fresh address through eth_call state overrides, so the
49
+ // token must actually call owner.isValidSignature(...) to accept a signature
50
+ // that cannot recover to the owner address.
51
+ const ERC1271_ALWAYS_VALID_RUNTIME = `0x7f${ERC1271_MAGIC_VALUE.slice(2)}${'00'.repeat(28)}60005260206000f3`;
52
+ // Fresh throwaway signer used only for eth_call. It never sends a transaction
53
+ // or receives funds, so its permit nonce and ERC-3009 authorization are unused.
54
+ const auditPrivateKeyBytes = new Uint8Array(32);
55
+ crypto.getRandomValues(auditPrivateKeyBytes);
56
+ const AUDIT_SIGNER = (0, accounts_1.privateKeyToAccount)((0, viem_1.toHex)(auditPrivateKeyBytes));
57
+ const auditContractBytes = new Uint8Array(32);
58
+ crypto.getRandomValues(auditContractBytes);
59
+ const AUDIT_CONTRACT = (0, accounts_1.privateKeyToAccount)((0, viem_1.toHex)(auditContractBytes)).address;
60
+ // The registry endpoint currently rate-limits this audit's method volume. The
61
+ // fallback is public and read-only; TOKEN_AUTH_AUDIT_RPC_<chainId> can override
62
+ // any chain without changing shared config.
63
+ const rpcFallbacks = {
64
+ '1': ['https://ethereum-rpc.publicnode.com'],
65
+ '56': ['https://bsc-rpc.publicnode.com'],
66
+ '130': ['https://unichain-rpc.publicnode.com'],
67
+ '8453': ['https://base-rpc.publicnode.com'],
68
+ '11155111': ['https://ethereum-sepolia-rpc.publicnode.com'],
69
+ };
70
+ function addressFromWord(word) {
71
+ if (!word || word === ZERO_BYTES32)
72
+ return undefined;
73
+ const candidate = (0, viem_1.getAddress)(`0x${word.slice(-40)}`);
74
+ return candidate === ZERO_ADDRESS ? undefined : candidate;
75
+ }
76
+ function codeContains(code, value) {
77
+ return code.toLowerCase().includes(value.slice(2).toLowerCase());
78
+ }
79
+ async function probeRead(fn) {
80
+ try {
81
+ return await fn();
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ async function callSucceeds(label, fn) {
88
+ try {
89
+ await fn();
90
+ return true;
91
+ }
92
+ catch (error) {
93
+ const detail = error && typeof error === 'object' && 'shortMessage' in error
94
+ ? [
95
+ String(error.shortMessage),
96
+ 'details' in error ? String(error.details) : '',
97
+ 'cause' in error &&
98
+ error.cause &&
99
+ typeof error.cause === 'object' &&
100
+ 'message' in error.cause
101
+ ? String(error.cause.message)
102
+ : '',
103
+ ]
104
+ .filter(Boolean)
105
+ .join(' — ')
106
+ : String(error);
107
+ console.error(`SIMULATION ${label}: ${detail}`);
108
+ return false;
109
+ }
110
+ }
111
+ function erc1271StateOverride() {
112
+ return [
113
+ {
114
+ address: AUDIT_CONTRACT,
115
+ code: ERC1271_ALWAYS_VALID_RUNTIME,
116
+ },
117
+ ];
118
+ }
119
+ async function verifyErc1271StateOverride(client, blockNumber) {
120
+ const data = (0, viem_1.encodeFunctionData)({
121
+ abi: erc1271Abi,
122
+ functionName: 'isValidSignature',
123
+ args: [ZERO_BYTES32, '0x'],
124
+ });
125
+ try {
126
+ const result = await client.call({
127
+ account: AUDIT_SIGNER.address,
128
+ to: AUDIT_CONTRACT,
129
+ data,
130
+ blockNumber,
131
+ stateOverride: erc1271StateOverride(),
132
+ });
133
+ return result.data?.slice(0, 10).toLowerCase() === ERC1271_MAGIC_VALUE;
134
+ }
135
+ catch (error) {
136
+ const detail = error && typeof error === 'object' && 'shortMessage' in error
137
+ ? String(error.shortMessage)
138
+ : String(error);
139
+ console.error(`STATE OVERRIDE ERC-1271 probe failed: ${detail}`);
140
+ return false;
141
+ }
142
+ }
143
+ async function simulatePermit(client, blockNumber, token, authorization, signatureMode) {
144
+ const owner = signatureMode === 'erc1271' ? AUDIT_CONTRACT : AUDIT_SIGNER.address;
145
+ const deadline = MAX_UINT256;
146
+ const signature = await AUDIT_SIGNER.signTypedData({
147
+ domain: authorization.domain,
148
+ types: {
149
+ Permit: [
150
+ { name: 'owner', type: 'address' },
151
+ { name: 'spender', type: 'address' },
152
+ { name: 'value', type: 'uint256' },
153
+ { name: 'nonce', type: 'uint256' },
154
+ { name: 'deadline', type: 'uint256' },
155
+ ],
156
+ },
157
+ primaryType: 'Permit',
158
+ message: {
159
+ owner,
160
+ spender: AUDIT_SIGNER.address,
161
+ value: 0n,
162
+ nonce: 0n,
163
+ deadline,
164
+ },
165
+ });
166
+ const parsed = (0, viem_1.parseSignature)(signature);
167
+ const data = signatureMode === 'erc1271'
168
+ ? (0, viem_1.encodeFunctionData)({
169
+ abi: permitAbi,
170
+ functionName: 'permit',
171
+ args: [
172
+ owner,
173
+ AUDIT_SIGNER.address,
174
+ 0n,
175
+ deadline,
176
+ signature,
177
+ ],
178
+ })
179
+ : (0, viem_1.encodeFunctionData)({
180
+ abi: permitAbi,
181
+ functionName: 'permit',
182
+ args: [
183
+ AUDIT_SIGNER.address,
184
+ AUDIT_SIGNER.address,
185
+ 0n,
186
+ deadline,
187
+ Number(parsed.v),
188
+ parsed.r,
189
+ parsed.s,
190
+ ],
191
+ });
192
+ return callSucceeds(`${token} permit signature=${signatureMode}`, () => client.call({
193
+ account: AUDIT_SIGNER.address,
194
+ to: token,
195
+ data,
196
+ blockNumber,
197
+ ...(signatureMode === 'erc1271'
198
+ ? { stateOverride: erc1271StateOverride() }
199
+ : {}),
200
+ }));
201
+ }
202
+ async function simulateReceiveWithAuthorization(client, blockNumber, token, authorization, signatureMode) {
203
+ const owner = signatureMode === 'erc1271' ? AUDIT_CONTRACT : AUDIT_SIGNER.address;
204
+ const signature = await AUDIT_SIGNER.signTypedData({
205
+ domain: authorization.domain,
206
+ types: {
207
+ ReceiveWithAuthorization: [
208
+ { name: 'from', type: 'address' },
209
+ { name: 'to', type: 'address' },
210
+ { name: 'value', type: 'uint256' },
211
+ { name: 'validAfter', type: 'uint256' },
212
+ { name: 'validBefore', type: 'uint256' },
213
+ { name: 'nonce', type: 'bytes32' },
214
+ ],
215
+ },
216
+ primaryType: 'ReceiveWithAuthorization',
217
+ message: {
218
+ from: owner,
219
+ to: AUDIT_SIGNER.address,
220
+ value: 0n,
221
+ validAfter: 0n,
222
+ validBefore: MAX_UINT256,
223
+ nonce: ZERO_BYTES32,
224
+ },
225
+ });
226
+ const parsed = (0, viem_1.parseSignature)(signature);
227
+ const prefix = [
228
+ owner,
229
+ AUDIT_SIGNER.address,
230
+ 0n,
231
+ 0n,
232
+ MAX_UINT256,
233
+ ZERO_BYTES32,
234
+ ];
235
+ const data = signatureMode === 'erc1271'
236
+ ? (0, viem_1.encodeFunctionData)({
237
+ abi: authorizationAbi,
238
+ functionName: 'receiveWithAuthorization',
239
+ args: [...prefix, signature],
240
+ })
241
+ : (0, viem_1.encodeFunctionData)({
242
+ abi: authorizationAbi,
243
+ functionName: 'receiveWithAuthorization',
244
+ args: [...prefix, Number(parsed.v), parsed.r, parsed.s],
245
+ });
246
+ return callSucceeds(`${token} receiveWithAuthorization signature=${signatureMode}`, () => client.call({
247
+ account: AUDIT_SIGNER.address,
248
+ to: token,
249
+ data,
250
+ blockNumber,
251
+ ...(signatureMode === 'erc1271'
252
+ ? { stateOverride: erc1271StateOverride() }
253
+ : {}),
254
+ }));
255
+ }
256
+ async function auditToken(client, blockNumber, address, configured) {
257
+ const problems = [];
258
+ const rootCode = await probeRead(() => client.getCode({ address, blockNumber }));
259
+ if (!rootCode) {
260
+ return {
261
+ standards: [],
262
+ eoaOnly: undefined,
263
+ liveDomainSeparator: undefined,
264
+ implementation: undefined,
265
+ problems: ['could not read runtime bytecode'],
266
+ };
267
+ }
268
+ const publicImplementation = await probeRead(() => client.readContract({
269
+ address,
270
+ abi: probeAbi,
271
+ functionName: 'implementation',
272
+ blockNumber,
273
+ }));
274
+ const eip1967Implementation = addressFromWord(await probeRead(() => client.getStorageAt({
275
+ address,
276
+ slot: EIP1967_IMPLEMENTATION_SLOT,
277
+ blockNumber,
278
+ })));
279
+ const legacyImplementation = addressFromWord(await probeRead(() => client.getStorageAt({
280
+ address,
281
+ slot: LEGACY_ZEPPELIN_IMPLEMENTATION_SLOT,
282
+ blockNumber,
283
+ })));
284
+ const implementation = typeof publicImplementation === 'string' &&
285
+ publicImplementation !== ZERO_ADDRESS
286
+ ? (0, viem_1.getAddress)(publicImplementation)
287
+ : (eip1967Implementation ?? legacyImplementation);
288
+ const implementationCode = implementation
289
+ ? await probeRead(() => client.getCode({ address: implementation, blockNumber }))
290
+ : undefined;
291
+ const evidenceCodes = [rootCode];
292
+ if (implementationCode)
293
+ evidenceCodes.push(implementationCode);
294
+ const selectorSupport = {};
295
+ for (const [name, selector] of Object.entries(selectors)) {
296
+ if (evidenceCodes.some((code) => codeContains(code, selector))) {
297
+ selectorSupport[name] = true;
298
+ continue;
299
+ }
300
+ const facet = await probeRead(() => client.readContract({
301
+ address,
302
+ abi: probeAbi,
303
+ functionName: 'getFacet',
304
+ args: [selector],
305
+ blockNumber,
306
+ }));
307
+ selectorSupport[name] = facet !== undefined && facet !== ZERO_ADDRESS;
308
+ if (facet && facet !== ZERO_ADDRESS) {
309
+ const facetCode = await probeRead(() => client.getCode({ address: facet, blockNumber }));
310
+ if (facetCode)
311
+ evidenceCodes.push(facetCode);
312
+ }
313
+ }
314
+ const liveDomainSeparator = await probeRead(() => client.readContract({
315
+ address,
316
+ abi: probeAbi,
317
+ functionName: 'DOMAIN_SEPARATOR',
318
+ blockNumber,
319
+ }));
320
+ const nonce = await probeRead(() => client.readContract({
321
+ address,
322
+ abi: probeAbi,
323
+ functionName: 'nonces',
324
+ args: [ZERO_ADDRESS],
325
+ blockNumber,
326
+ }));
327
+ const authorizationState = await probeRead(() => client.readContract({
328
+ address,
329
+ abi: probeAbi,
330
+ functionName: 'authorizationState',
331
+ args: [ZERO_ADDRESS, ZERO_BYTES32],
332
+ blockNumber,
333
+ }));
334
+ const onchainTypehashes = {
335
+ permit: await probeRead(() => client.readContract({
336
+ address,
337
+ abi: probeAbi,
338
+ functionName: 'PERMIT_TYPEHASH',
339
+ blockNumber,
340
+ })),
341
+ transferWithAuthorization: await probeRead(() => client.readContract({
342
+ address,
343
+ abi: probeAbi,
344
+ functionName: 'TRANSFER_WITH_AUTHORIZATION_TYPEHASH',
345
+ blockNumber,
346
+ })),
347
+ receiveWithAuthorization: await probeRead(() => client.readContract({
348
+ address,
349
+ abi: probeAbi,
350
+ functionName: 'RECEIVE_WITH_AUTHORIZATION_TYPEHASH',
351
+ blockNumber,
352
+ })),
353
+ };
354
+ const typehashMatches = (name) => {
355
+ const returned = onchainTypehashes[name];
356
+ if (returned !== undefined)
357
+ return returned === expectedTypehashes[name];
358
+ return evidenceCodes.some((code) => codeContains(code, expectedTypehashes[name]));
359
+ };
360
+ const permitSurface = selectorSupport.permit &&
361
+ liveDomainSeparator !== undefined &&
362
+ nonce !== undefined;
363
+ const transferSurface = selectorSupport.authorizationState &&
364
+ selectorSupport.transferWithAuthorization &&
365
+ selectorSupport.receiveWithAuthorization &&
366
+ liveDomainSeparator !== undefined &&
367
+ authorizationState !== undefined;
368
+ if (selectorSupport.permit !==
369
+ (liveDomainSeparator !== undefined && nonce !== undefined)) {
370
+ problems.push('ERC-2612 selector/read-method evidence is inconsistent');
371
+ }
372
+ const completeTransferSelectors = selectorSupport.authorizationState &&
373
+ selectorSupport.transferWithAuthorization &&
374
+ selectorSupport.receiveWithAuthorization;
375
+ if (completeTransferSelectors !==
376
+ (liveDomainSeparator !== undefined && authorizationState !== undefined)) {
377
+ problems.push('ERC-3009 selector/read-method evidence is inconsistent');
378
+ }
379
+ const permitSimulation = configured?.standards.includes('erc2612')
380
+ ? await simulatePermit(client, blockNumber, address, configured, 'eoa-vrs')
381
+ : undefined;
382
+ const transferSimulation = configured?.standards.includes('erc3009')
383
+ ? await simulateReceiveWithAuthorization(client, blockNumber, address, configured, 'eoa-vrs')
384
+ : undefined;
385
+ const erc2612 = permitSurface && (permitSimulation ?? typehashMatches('permit'));
386
+ const erc3009 = transferSurface &&
387
+ (transferSimulation ??
388
+ (typehashMatches('transferWithAuthorization') &&
389
+ typehashMatches('receiveWithAuthorization')));
390
+ if (configured?.standards.includes('erc2612') && !permitSimulation) {
391
+ problems.push('a correctly typed ERC-2612 permit reverted under eth_call');
392
+ }
393
+ else if (permitSurface && !erc2612) {
394
+ problems.push('unconfigured ERC-2612 surface has an unprovable type hash');
395
+ }
396
+ if (configured?.standards.includes('erc3009') && !transferSimulation) {
397
+ problems.push('a correctly typed ERC-3009 receiveWithAuthorization reverted under eth_call');
398
+ }
399
+ else if (transferSurface && !erc3009) {
400
+ problems.push('unconfigured ERC-3009 surface has unprovable type hashes');
401
+ }
402
+ const standards = [];
403
+ if (erc3009)
404
+ standards.push('erc3009');
405
+ if (erc2612)
406
+ standards.push('erc2612');
407
+ const contractSignerSelectors = [];
408
+ if (erc3009) {
409
+ contractSignerSelectors.push(selectorSupport.transferWithAuthorizationBytes &&
410
+ selectorSupport.receiveWithAuthorizationBytes);
411
+ }
412
+ if (erc2612)
413
+ contractSignerSelectors.push(selectorSupport.permitBytes);
414
+ let eoaOnly;
415
+ if (contractSignerSelectors.length > 0) {
416
+ if (contractSignerSelectors.every((supported) => !supported)) {
417
+ eoaOnly = true;
418
+ }
419
+ else if (!contractSignerSelectors.every(Boolean)) {
420
+ problems.push('standards disagree on contract-wallet signature support; eoaOnly cannot represent this token');
421
+ }
422
+ else if (!configured) {
423
+ problems.push('contract-wallet support requires the configured domain for an ERC-1271 simulation');
424
+ }
425
+ else if (!(await verifyErc1271StateOverride(client, blockNumber))) {
426
+ problems.push('RPC did not apply the ERC-1271 state override; contract-wallet support is unverified');
427
+ }
428
+ else {
429
+ const contractSignerSupport = [];
430
+ if (erc3009) {
431
+ contractSignerSupport.push(await simulateReceiveWithAuthorization(client, blockNumber, address, configured, 'erc1271'));
432
+ }
433
+ if (erc2612) {
434
+ contractSignerSupport.push(await simulatePermit(client, blockNumber, address, configured, 'erc1271'));
435
+ }
436
+ if (contractSignerSupport.every(Boolean))
437
+ eoaOnly = false;
438
+ else if (contractSignerSupport.every((supported) => !supported)) {
439
+ eoaOnly = true;
440
+ }
441
+ else {
442
+ problems.push('standards disagree in ERC-1271 simulation; eoaOnly cannot represent this token');
443
+ }
444
+ }
445
+ }
446
+ return {
447
+ standards,
448
+ eoaOnly,
449
+ liveDomainSeparator,
450
+ implementation,
451
+ problems,
452
+ };
453
+ }
454
+ async function main() {
455
+ const problems = [];
456
+ const requestedChainIds = process.env.TOKEN_AUTH_AUDIT_CHAIN_IDS
457
+ ? new Set(process.env.TOKEN_AUTH_AUDIT_CHAIN_IDS.split(','))
458
+ : undefined;
459
+ let auditedTokens = 0;
460
+ let nativeTokens = 0;
461
+ let configuredTokens = 0;
462
+ for (const [chainId, chain] of Object.entries(index_1.chainRegistry)) {
463
+ if (requestedChainIds && !requestedChainIds.has(chainId))
464
+ continue;
465
+ if (chain.vmType !== 'evm' || chain.virtual) {
466
+ for (const token of chain.tokens) {
467
+ if (token.authorization) {
468
+ problems.push(`${chain.name} (${chainId}) ${token.symbol}: authorization metadata is invalid on a non-direct EVM chain`);
469
+ }
470
+ }
471
+ continue;
472
+ }
473
+ if (!chain.publicRpcUrl) {
474
+ problems.push(`${chain.name} (${chainId}): no public RPC URL`);
475
+ continue;
476
+ }
477
+ const override = process.env[`TOKEN_AUTH_AUDIT_RPC_${chainId}`];
478
+ const rpcUrls = [
479
+ override,
480
+ ...(rpcFallbacks[chainId] ?? []),
481
+ chain.publicRpcUrl,
482
+ ].filter((url, index, urls) => Boolean(url) && urls.indexOf(url) === index);
483
+ const client = (0, viem_1.createPublicClient)({
484
+ transport: (0, viem_1.fallback)(rpcUrls.map((url) => (0, viem_1.http)(url, { retryCount: 2, timeout: 15_000 }))),
485
+ });
486
+ const blockNumber = await client.getBlockNumber();
487
+ for (const token of chain.tokens) {
488
+ const label = `${chain.name} (${chainId}) ${token.symbol} ${token.address}`;
489
+ if (token.address.toLowerCase() === ZERO_ADDRESS) {
490
+ nativeTokens++;
491
+ if (token.authorization) {
492
+ problems.push(`${label}: native tokens cannot use token permits`);
493
+ }
494
+ continue;
495
+ }
496
+ auditedTokens++;
497
+ const address = (0, viem_1.getAddress)(token.address);
498
+ const result = await auditToken(client, blockNumber, address, token.authorization);
499
+ const configuredStandards = token.authorization?.standards ?? [];
500
+ if (JSON.stringify(configuredStandards) !== JSON.stringify(result.standards)) {
501
+ result.problems.push(`configured standards ${JSON.stringify(configuredStandards)} do not match live standards ${JSON.stringify(result.standards)}`);
502
+ }
503
+ if (token.authorization) {
504
+ configuredTokens++;
505
+ const derived = (0, viem_1.domainSeparator)({
506
+ domain: token.authorization.domain,
507
+ });
508
+ if (derived !== token.authorization.domainSeparator) {
509
+ result.problems.push(`declared domain derives ${derived}, not pinned ${token.authorization.domainSeparator}`);
510
+ }
511
+ if (result.liveDomainSeparator !== token.authorization.domainSeparator) {
512
+ result.problems.push(`live DOMAIN_SEPARATOR ${result.liveDomainSeparator ?? '<unavailable>'} does not match pinned ${token.authorization.domainSeparator}`);
513
+ }
514
+ if (result.eoaOnly !== token.authorization.eoaOnly) {
515
+ result.problems.push(`configured eoaOnly=${token.authorization.eoaOnly} does not match verified signature overloads (${result.eoaOnly})`);
516
+ }
517
+ }
518
+ const standards = result.standards.length > 0 ? result.standards.join('+') : 'none';
519
+ const signer = result.eoaOnly === undefined
520
+ ? 'n/a'
521
+ : result.eoaOnly
522
+ ? 'EOA-only'
523
+ : 'EOA+ERC1271';
524
+ console.log(`${result.problems.length === 0 ? 'PASS' : 'FAIL'} ${chainId}@${blockNumber} ${token.symbol} ${address} standards=${standards} signer=${signer} implementation=${result.implementation ?? 'direct'}`);
525
+ for (const problem of result.problems) {
526
+ problems.push(`${label}: ${problem}`);
527
+ }
528
+ }
529
+ }
530
+ console.log(`\nAudited ${auditedTokens} deployed EVM token entries; verified ${configuredTokens} authorization declarations; skipped ${nativeTokens} native-token entries as not applicable.`);
531
+ if (problems.length > 0) {
532
+ console.error('\nAuthorization audit failed:');
533
+ for (const problem of problems)
534
+ console.error(`- ${problem}`);
535
+ process.exitCode = 1;
536
+ }
537
+ }
538
+ main().catch((error) => {
539
+ console.error(error);
540
+ process.exitCode = 1;
541
+ });
@@ -89,7 +89,7 @@ function renderChains(chainRegistry, mainnets, testnets) {
89
89
  }
90
90
  return [
91
91
  "// Auto-generated by scripts/generate.ts. Do not edit manually.",
92
- 'import type { ChainNetwork, ChainStack, PegGroup, ProviderIds, ProviderName, SettlementConfig, SettlementLayer, SupportedChain, SwapQuoter, SwapQuoterConfig, VmType } from "./types";',
92
+ 'import type { ChainNetwork, ChainStack, PegGroup, ProviderIds, ProviderName, SettlementConfig, SettlementLayer, SupportedChain, SwapQuoter, SwapQuoterConfig, TokenAuthorization, VmType } from "./types";',
93
93
  "",
94
94
  "interface Token {",
95
95
  " /** EVM: 0x-prefixed hex. SVM: base58 SPL mint. TVM: base58 (T-prefixed). */",
@@ -100,6 +100,8 @@ function renderChains(chainRegistry, mainnets, testnets) {
100
100
  " pegGroup?: PegGroup;",
101
101
  " balanceSlot: number | null;",
102
102
  " approvalSlot: number | null;",
103
+ " /** Token-native EIP-712 authorization support, when verified. */",
104
+ " authorization?: TokenAuthorization;",
103
105
  " /** Bridge layers that can carry this token on this chain, when known. */",
104
106
  " settlementLayers?: SettlementLayer[];",
105
107
  " /** Rhino's own symbol for this token, where it differs from ours. */",