@rhinestone/shared-configs 2.0.0 → 2.1.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.
@@ -42,6 +42,7 @@ exports.classifyChainStacks = classifyChainStacks;
42
42
  exports.validateChainConfig = validateChainConfig;
43
43
  exports.validateOftCoverage = validateOftCoverage;
44
44
  exports.formatValidationIssues = formatValidationIssues;
45
+ const viem_1 = require("viem");
45
46
  const celo_1 = require("viem/celo");
46
47
  const viemChains = __importStar(require("viem/chains"));
47
48
  const op_stack_1 = require("viem/op-stack");
@@ -72,12 +73,17 @@ const SWAP_QUOTERS = new Set([
72
73
  ]);
73
74
  const VM_TYPES = new Set(['evm', 'svm', 'tvm']);
74
75
  const PEG_GROUPS = new Set(['USD', 'ETH']);
76
+ const TOKEN_AUTHORIZATION_STANDARDS = new Set([
77
+ 'erc3009',
78
+ 'erc2612',
79
+ ]);
75
80
  const NETWORKS = new Set(['mainnet', 'testnet']);
76
81
  const STACKS = new Set(['op-stack', 'zksync', 'celo', 'vanilla']);
77
82
  // Registry keys are chain ids consumers index by directly, so they must be
78
83
  // canonical positive integers — no leading zeros, which would let "01" and "1"
79
84
  // both address chain 1.
80
85
  const CHAIN_ID_KEY = /^[1-9][0-9]*$/;
86
+ const BYTES32 = /^0x[0-9a-fA-F]{64}$/;
81
87
  // CAIP-2 (https://chainagnostic.org/CAIPs/caip-2): `<namespace>:<reference>`.
82
88
  //
83
89
  // Deliberately shape-only — the namespace set is not ours to close, and a chain
@@ -183,7 +189,7 @@ function settlementConfigProblems(chain) {
183
189
  * only sanity-check the envelope, so this is the single place a malformed
184
190
  * registry gets caught before it reaches anyone.
185
191
  */
186
- function structuralProblems(chain) {
192
+ function structuralProblems(registryChainId, chain) {
187
193
  const problems = [];
188
194
  if (!isNonEmptyString(chain.name))
189
195
  problems.push('name is not a non-empty string');
@@ -241,6 +247,28 @@ function structuralProblems(chain) {
241
247
  if (!isAbsentOr(chain.virtual, (v) => typeof v === 'boolean')) {
242
248
  problems.push('virtual is not a boolean');
243
249
  }
250
+ // Required, and each vendor's key required within it: `null` says the vendor
251
+ // was checked and does not serve this chain, which is the answer a consumer
252
+ // acts on when it decides the chain cannot take deposits. An absent key is a
253
+ // different claim — an artifact older than the field — and letting the two
254
+ // look alike is exactly the silent gap this field exists to close.
255
+ if (!isObject(chain.providerIds)) {
256
+ problems.push('providerIds is missing (use null per vendor for a chain it does not serve)');
257
+ }
258
+ else {
259
+ for (const vendor of ['alchemy', 'zerion']) {
260
+ const id = chain.providerIds[vendor];
261
+ if (!(vendor in chain.providerIds)) {
262
+ problems.push(`providerIds.${vendor} is missing (use null if the vendor does not serve this chain)`);
263
+ }
264
+ else if (id !== null && !isNonEmptyString(id)) {
265
+ problems.push(`providerIds.${vendor} ${JSON.stringify(id)} is not a non-empty string or null`);
266
+ }
267
+ }
268
+ }
269
+ if (typeof chain.sessionCapable !== 'boolean') {
270
+ problems.push('sessionCapable is not a boolean');
271
+ }
244
272
  for (const field of ['nativeToken', 'wrappedNativeToken']) {
245
273
  const token = chain[field];
246
274
  if (!isObject(token)) {
@@ -287,6 +315,95 @@ function structuralProblems(chain) {
287
315
  if (!isAbsentOr(token.pegGroup, (v) => PEG_GROUPS.has(v))) {
288
316
  problems.push(`${at}.pegGroup ${JSON.stringify(token.pegGroup)} is not a known peg group`);
289
317
  }
318
+ const authorization = token.authorization;
319
+ if (authorization !== undefined) {
320
+ if (chain.vmType !== 'evm') {
321
+ problems.push(`${at}.authorization is only valid for EVM tokens`);
322
+ }
323
+ if (chain.virtual === true) {
324
+ problems.push(`${at}.authorization is not valid on a virtual chain; use the settlement chain's token`);
325
+ }
326
+ if (!isObject(authorization)) {
327
+ problems.push(`${at}.authorization is not an object`);
328
+ }
329
+ else {
330
+ const standards = authorization.standards;
331
+ if (!Array.isArray(standards) || standards.length === 0) {
332
+ problems.push(`${at}.authorization.standards is not a non-empty array`);
333
+ }
334
+ else {
335
+ const seen = new Set();
336
+ for (const standard of standards) {
337
+ if (!TOKEN_AUTHORIZATION_STANDARDS.has(standard)) {
338
+ problems.push(`${at}.authorization.standards contains unknown value ${JSON.stringify(standard)}`);
339
+ }
340
+ else if (seen.has(standard)) {
341
+ problems.push(`${at}.authorization.standards contains duplicate value ${JSON.stringify(standard)}`);
342
+ }
343
+ if (typeof standard === 'string')
344
+ seen.add(standard);
345
+ }
346
+ }
347
+ const domain = authorization.domain;
348
+ if (!isObject(domain)) {
349
+ problems.push(`${at}.authorization.domain is not an object`);
350
+ }
351
+ else {
352
+ const verifyingContract = domain.verifyingContract;
353
+ if (!isNonEmptyString(domain.name)) {
354
+ problems.push(`${at}.authorization.domain.name is not a non-empty string`);
355
+ }
356
+ if (!isNonEmptyString(domain.version)) {
357
+ problems.push(`${at}.authorization.domain.version is not a non-empty string`);
358
+ }
359
+ if (typeof verifyingContract !== 'string' ||
360
+ !(0, viem_1.isAddress)(verifyingContract)) {
361
+ problems.push(`${at}.authorization.domain.verifyingContract is not an EVM address`);
362
+ }
363
+ else if (typeof token.address === 'string' &&
364
+ verifyingContract.toLowerCase() !== token.address.toLowerCase()) {
365
+ problems.push(`${at}.authorization.domain.verifyingContract does not match the token address`);
366
+ }
367
+ const hasChainId = domain.chainId !== undefined;
368
+ const hasSalt = domain.salt !== undefined;
369
+ if (hasChainId === hasSalt) {
370
+ problems.push(`${at}.authorization.domain must contain exactly one of chainId or salt`);
371
+ }
372
+ if (hasChainId) {
373
+ if (!isNonNegativeInt(domain.chainId)) {
374
+ problems.push(`${at}.authorization.domain.chainId is not a non-negative integer`);
375
+ }
376
+ else if (domain.chainId !== Number(registryChainId)) {
377
+ problems.push(`${at}.authorization.domain.chainId does not match registry chain ${registryChainId}`);
378
+ }
379
+ }
380
+ if (hasSalt && !BYTES32.test(String(domain.salt))) {
381
+ problems.push(`${at}.authorization.domain.salt is not a bytes32 hex value`);
382
+ }
383
+ const pinnedSeparator = String(authorization.domainSeparator);
384
+ if (!BYTES32.test(pinnedSeparator)) {
385
+ problems.push(`${at}.authorization.domainSeparator is not a bytes32 hex value`);
386
+ }
387
+ else if (isNonEmptyString(domain.name) &&
388
+ isNonEmptyString(domain.version) &&
389
+ typeof verifyingContract === 'string' &&
390
+ (0, viem_1.isAddress)(verifyingContract) &&
391
+ hasChainId !== hasSalt &&
392
+ (!hasChainId || isNonNegativeInt(domain.chainId)) &&
393
+ (!hasSalt || BYTES32.test(String(domain.salt)))) {
394
+ const derived = (0, viem_1.domainSeparator)({
395
+ domain: domain,
396
+ });
397
+ if (derived.toLowerCase() !== pinnedSeparator.toLowerCase()) {
398
+ problems.push(`${at}.authorization.domainSeparator does not match the declared EIP-712 domain`);
399
+ }
400
+ }
401
+ }
402
+ if (typeof authorization.eoaOnly !== 'boolean') {
403
+ problems.push(`${at}.authorization.eoaOnly is not a boolean`);
404
+ }
405
+ }
406
+ }
290
407
  });
291
408
  }
292
409
  for (const [field, allowed] of [
@@ -936,7 +1053,7 @@ function validateChainConfig(chains) {
936
1053
  });
937
1054
  continue;
938
1055
  }
939
- for (const problem of structuralProblems(chain)) {
1056
+ for (const problem of structuralProblems(chainId, chain)) {
940
1057
  issues.push({ severity: 'error', chainId, chainName, message: problem });
941
1058
  }
942
1059
  // Virtual chains (HyperCore) are reachable via their settlement chain, not
@@ -1,4 +1,4 @@
1
- import type { ChainNetwork, ChainStack, PegGroup, ProviderName, SettlementConfig, SettlementLayer, SupportedChain, SwapQuoter, SwapQuoterConfig, VmType } from "./types";
1
+ import type { ChainNetwork, ChainStack, PegGroup, ProviderIds, ProviderName, SettlementConfig, SettlementLayer, SupportedChain, SwapQuoter, SwapQuoterConfig, TokenAuthorization, VmType } from "./types";
2
2
  interface Token {
3
3
  /** EVM: 0x-prefixed hex. SVM: base58 SPL mint. TVM: base58 (T-prefixed). */
4
4
  address: string;
@@ -8,6 +8,8 @@ interface Token {
8
8
  pegGroup?: PegGroup;
9
9
  balanceSlot: number | null;
10
10
  approvalSlot: number | null;
11
+ /** Token-native EIP-712 authorization support, when verified. */
12
+ authorization?: TokenAuthorization;
11
13
  /** Bridge layers that can carry this token on this chain, when known. */
12
14
  settlementLayers?: SettlementLayer[];
13
15
  /** Rhino's own symbol for this token, where it differs from ours. */
@@ -67,6 +69,13 @@ interface Chain {
67
69
  * name, CCTP domain, NEAR prefix, OFT adapter). Third-party addresses —
68
70
  * never the contracts block, which is read as Rhinestone-deployed. */
69
71
  settlementConfig?: SettlementConfig;
72
+ /** What the deposit-ingestion vendors call this chain. `null` means the
73
+ * vendor was checked and does not serve it; both null means the chain
74
+ * cannot take deposits. */
75
+ providerIds: ProviderIds;
76
+ /** Whether we open smart sessions here — a declared policy, not a
77
+ * statement about what is deployed. */
78
+ sessionCapable: boolean;
70
79
  providers: ProviderName[];
71
80
  swapQuoters: SwapQuoter[];
72
81
  swapQuoterConfig?: Partial<Record<SwapQuoter, SwapQuoterConfig>>;