From fccbd179d32d89d482e5ddb246bbbc97117e9b4d Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 13 Oct 2026 17:43:33 +0200 Subject: [PATCH 1/3] Sync Safe config with api-kit 5.0.3 and add ADI Chain Mirrors the api-kit 5.0.3 network list, which adds Pharos, Robinhood, Arc and their testnets among others, and drops zkEVM, BTC and Codex. ADI Chain (36900) is served by the official transaction service under `adi` but is not yet in the api-kit config, so it is added beside the mirrored list. --- package-lock.json | 152 ++++++++++++++++++---------- package.json | 4 +- src/Safe.sol | 38 +++++-- test/Safe.t.sol | 5 + test/ffi/safe-multisend-config.cjs | 4 +- test/helpers/SafeConfigFixtures.sol | 47 +++++++-- 6 files changed, 171 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79c71fa..3afb709 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,8 +6,8 @@ "": { "name": "safe-utils", "devDependencies": { - "@safe-global/api-kit": "4.1.0", - "@safe-global/safe-deployments": "1.37.53" + "@safe-global/api-kit": "5.0.3", + "@safe-global/safe-deployments": "1.37.63" } }, "node_modules/@adraffy/ens-normalize": { @@ -60,44 +60,58 @@ } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", - "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.11.0.tgz", + "integrity": "sha512-8xh4avo53/vXO6UEv9yaoJ83hY581geaLTygf61NPIXP5AeIZCPjJhpTaYU23IZkG+NpLN/C9FYzWp7hphIT9A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "node_modules/@safe-global/api-kit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@safe-global/api-kit/-/api-kit-4.1.0.tgz", - "integrity": "sha512-UlLG5bY1dR3zKKJxv0/d4bsR0zkFiC52aXJDg4/BTXGge51CZdG/d/5Z+V/IQX6fC09QiLPT1D3VquO0oc9Ecg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@safe-global/api-kit/-/api-kit-5.0.3.tgz", + "integrity": "sha512-EDEtnc7XD3C2AUVRiKKK8hZVlErznWbHmKGAkoprTICiyIdyBTKOx6kZwkuS6guBlVgBwA7Hk3msVaSTmIOkKw==", "dev": true, "license": "MIT", "dependencies": { - "@safe-global/protocol-kit": "^7.0.0", - "@safe-global/types-kit": "^3.1.0", + "@safe-global/protocol-kit": "^8.0.6", + "@safe-global/types-kit": "^4.0.1", "node-fetch": "^2.7.0", - "viem": "^2.21.8" + "viem": "^2.52.2" } }, "node_modules/@safe-global/protocol-kit": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@safe-global/protocol-kit/-/protocol-kit-7.0.0.tgz", - "integrity": "sha512-h2Aw+o13gfZng/ou2vt+Yzq/SNI6d98ScEJCCn6hZHvNlrFjEzKNuXJcmDuG/nGdsMq9vAnm0SvnTm9W2Qjf4A==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@safe-global/protocol-kit/-/protocol-kit-8.0.7.tgz", + "integrity": "sha512-QKbRQmaQWHxsKB3/cogQ0JBzsnewSo2yoMvTvepicBy5N1C3FPRjiRGJBfQ/H9v881BvsLZXggbIh7e4qrjidg==", "dev": true, "license": "MIT", "dependencies": { - "@safe-global/safe-deployments": "^1.37.52", - "@safe-global/safe-modules-deployments": "^3.0.0", - "@safe-global/types-kit": "^3.1.0", - "abitype": "^1.0.2", - "semver": "^7.7.2", - "viem": "^2.21.8" + "@safe-global/safe-deployments": "^1.37.63", + "@safe-global/safe-modules-deployments": "^3.0.10", + "@safe-global/types-kit": "^4.0.1", + "abitype": "^1.2.3", + "semver": "^7.8.0", + "viem": "^2.52.2" }, "optionalDependencies": { "@noble/curves": "^1.6.0", @@ -105,30 +119,34 @@ } }, "node_modules/@safe-global/safe-deployments": { - "version": "1.37.53", - "resolved": "https://registry.npmjs.org/@safe-global/safe-deployments/-/safe-deployments-1.37.53.tgz", - "integrity": "sha512-3XihirwKqcCi6jsipCiW3lYXra9i4pC9nlhHTdUyi7Yx38nBYIkXeLZN2Nmf2UPcQBeHGnW1T3DgzY4VnuF/FQ==", + "version": "1.37.63", + "resolved": "https://registry.npmjs.org/@safe-global/safe-deployments/-/safe-deployments-1.37.63.tgz", + "integrity": "sha512-dH4V3iHH5tZdxx3DBvvBzmnlOZpKMXUx6CBFGUwlARnUa9MEVVyg8Sbpc3DplsaPB5fLh0J97+WB5ylxhLWCRA==", "dev": true, "license": "MIT", "dependencies": { "semver": "^7.6.2" + }, + "engines": { + "node": ">=22.0.0", + "pnpm": ">=10.16.0" } }, "node_modules/@safe-global/safe-modules-deployments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@safe-global/safe-modules-deployments/-/safe-modules-deployments-3.0.1.tgz", - "integrity": "sha512-APySCnHzu5KOVqlIRJq8mg1oVKPPvoWp+Yq3GAtwSUp8QJ5OFBGBxlB8Sc+68bcr15YArAxXpCHU68R1Kpq1vw==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@safe-global/safe-modules-deployments/-/safe-modules-deployments-3.0.10.tgz", + "integrity": "sha512-6TWp62j4MxQxTttL7WehrQMI3irMeQo3/mKUuSWb0+oE7fzioQWZ+QYT3sD92cfsKITFIWJ2rHyeyXIbSq0qMg==", "dev": true, "license": "MIT" }, "node_modules/@safe-global/types-kit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@safe-global/types-kit/-/types-kit-3.1.0.tgz", - "integrity": "sha512-uI6lFV8wOji4rb7juu0/LRMND0rq2NWaqH4zFzE2FUjO8lTvbCT+/5W/+flUUfkcaqyLDlJ1OPjasa2bEakrbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@safe-global/types-kit/-/types-kit-4.0.1.tgz", + "integrity": "sha512-zmIYyAH9mcBcqHszPgcfNjOJYuPSvWCcc/f8zeznh7N1HSA7jEoFErO06O4QDfwAAS4aEyoyjhPyfHbVYRheZg==", "dev": true, "license": "MIT", "dependencies": { - "abitype": "^1.0.2" + "abitype": "^1.2.3" } }, "node_modules/@scure/base": { @@ -171,9 +189,9 @@ } }, "node_modules/abitype": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", - "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.3.0.tgz", + "integrity": "sha512-fk6Te+bojIFrMvMZrnOO+SxCB+RUksTGOzq/60ZRvs1L+BVzvi2bqt9L3W/17ZLdZsyM1FuYf65P5nlmoiH1Bg==", "dev": true, "license": "MIT", "funding": { @@ -193,15 +211,15 @@ } }, "node_modules/asn1js": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", - "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", "dev": true, "license": "BSD-3-Clause", "optional": true, "dependencies": { "pvtsutils": "^1.3.6", - "pvutils": "^1.1.3", + "pvutils": "^1.1.5", "tslib": "^2.8.1" }, "engines": { @@ -253,9 +271,9 @@ } }, "node_modules/ox": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.7.tgz", - "integrity": "sha512-zSQ/cfBdolj7U4++NAvH7sI+VG0T3pEohITCgcQj8KlawvTDY4vGVhDT64Atsm0d6adWfIYHDpu88iUBMMp+AQ==", + "version": "0.14.56", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.14.56.tgz", + "integrity": "sha512-Feinlj+deWEam6RIwurRE+eHZ2N3hoUb3AAjQ10BmxRCYIymfqNkv0eZ+4ln4GkgDmSGLD2rGtTMqsFYSVDC6w==", "dev": true, "funding": [ { @@ -311,9 +329,9 @@ } }, "node_modules/pvutils": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", - "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", "dev": true, "license": "MIT", "optional": true, @@ -322,9 +340,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -350,9 +368,9 @@ "optional": true }, "node_modules/viem": { - "version": "2.47.6", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.47.6.tgz", - "integrity": "sha512-zExmbI99NGvMdYa7fmqSTLgkwh48dmhgEqFrUgkpL4kfG4XkVefZ8dZqIKVUhZo6Uhf0FrrEXOsHm9LUyIvI2Q==", + "version": "2.57.5", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.57.5.tgz", + "integrity": "sha512-BaUdS5lJJTxr6hJX17K0AjHbgUhsolsqH8CCbTq1WMunW/I/Siwc9VrTwccCcNPppgqa/gY4TqK8Zl8LLmlTlw==", "dev": true, "funding": [ { @@ -368,8 +386,8 @@ "@scure/bip39": "1.6.0", "abitype": "1.2.3", "isows": "1.0.7", - "ox": "0.14.7", - "ws": "8.18.3" + "ox": "0.14.56", + "ws": "8.21.0" }, "peerDependencies": { "typescript": ">=5.0.4" @@ -396,6 +414,28 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/viem/node_modules/abitype": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.3.tgz", + "integrity": "sha512-Ofer5QUnuUdTFsBRwARMoWKOH1ND5ehwYhJ3OJ/BQO+StkwQjHw0XyVh4vDttzHB7QOFhPHa/o413PJ82gU/Tg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -415,9 +455,9 @@ } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 2416c3f..0716d9e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "safe-utils", "devDependencies": { - "@safe-global/api-kit": "4.1.0", - "@safe-global/safe-deployments": "1.37.53" + "@safe-global/api-kit": "5.0.3", + "@safe-global/safe-deployments": "1.37.63" } } diff --git a/src/Safe.sol b/src/Safe.sol index e6b164e..59ebd4c 100644 --- a/src/Safe.sol +++ b/src/Safe.sol @@ -96,29 +96,44 @@ library Safe { if (chainId == 232) return "lens"; if (chainId == 324) return "zksync"; if (chainId == 480) return "wc"; + if (chainId == 677) return "bot"; if (chainId == 988) return "stable"; if (chainId == 999) return "hyper"; - if (chainId == 1101) return "zkevm"; + if (chainId == 1001) return "kairos"; + if (chainId == 1672) return "pharos"; + if (chainId == 1874) return "wch-sepolia"; if (chainId == 3338) return "peaq"; - if (chainId == 3637) return "btc"; + if (chainId == 4217) return "tempo"; + if (chainId == 4326) return "mega"; + if (chainId == 4663) return "robinhood"; if (chainId == 5000) return "mantle"; + if (chainId == 5003) return "mnt-sep"; + if (chainId == 5042) return "arc"; + if (chainId == 8217) return "kaia"; if (chainId == 8453) return "base"; if (chainId == 9745) return "plasma"; if (chainId == 10143) return "monad-testnet"; if (chainId == 10200) return "chi"; if (chainId == 16661) return "0g"; + if (chainId == 25363) return "fluent"; + // Served by the official transaction service, not yet listed in the api-kit config. + if (chainId == 36900) return "adi"; if (chainId == 42161) return "arb1"; if (chainId == 42220) return "celo"; + if (chainId == 42431) return "tempo-moderato"; if (chainId == 43111) return "hemi"; if (chainId == 43114) return "avax"; + if (chainId == 46630) return "robinhood-testnet"; if (chainId == 57073) return "ink"; if (chainId == 59144) return "linea"; if (chainId == 80069) return "bep"; if (chainId == 80094) return "berachain"; - if (chainId == 81224) return "codex"; if (chainId == 84532) return "basesep"; + if (chainId == 102030) return "ctc"; if (chainId == 534352) return "scr"; if (chainId == 747474) return "katana"; + if (chainId == 5042002) return "arc-testnet"; + if (chainId == 11142220) return "celo-sep"; if (chainId == 11155111) return "sep"; if (chainId == 1313161554) return "aurora"; revert ApiKitUrlNotFound(chainId); @@ -151,16 +166,19 @@ library Safe { function _usesV130CanonicalMultiSend(uint256 chainId) private pure returns (bool) { return (chainId == 1 || chainId == 10 || chainId == 56 || chainId == 100 || chainId == 130 || chainId == 137 - || chainId == 196 || chainId == 480 || chainId == 999 || chainId == 1101 || chainId == 5000 - || chainId == 8453 || chainId == 10200 || chainId == 42161 || chainId == 42220 || chainId == 43114 - || chainId == 59144 || chainId == 84532 || chainId == 534352 || chainId == 11155111 - || chainId == 1313161554); + || chainId == 196 || chainId == 480 || chainId == 999 || chainId == 5000 || chainId == 8453 + || chainId == 10200 || chainId == 42161 || chainId == 42220 || chainId == 43114 || chainId == 59144 + || chainId == 84532 || chainId == 534352 || chainId == 11155111 || chainId == 1313161554); } function _usesV141CanonicalMultiSend(uint256 chainId) private pure returns (bool) { - return (chainId == 50 || chainId == 143 || chainId == 146 || chainId == 204 || chainId == 988 || chainId == 3338 - || chainId == 3637 || chainId == 9745 || chainId == 10143 || chainId == 16661 || chainId == 43111 - || chainId == 57073 || chainId == 80069 || chainId == 80094 || chainId == 81224 || chainId == 747474); + return (chainId == 50 || chainId == 143 || chainId == 146 || chainId == 204 || chainId == 677 || chainId == 988 + || chainId == 1001 || chainId == 1672 || chainId == 1874 || chainId == 3338 || chainId == 4217 + || chainId == 4326 || chainId == 4663 || chainId == 5003 || chainId == 5042 || chainId == 8217 + || chainId == 9745 || chainId == 10143 || chainId == 16661 || chainId == 25363 || chainId == 36900 + || chainId == 42431 || chainId == 43111 || chainId == 46630 || chainId == 57073 || chainId == 80069 + || chainId == 80094 || chainId == 102030 || chainId == 747474 || chainId == 5042002 + || chainId == 11142220); } function getNonce(Client storage self) internal view returns (uint256) { diff --git a/test/Safe.t.sol b/test/Safe.t.sol index 54fa427..366ee91 100644 --- a/test/Safe.t.sol +++ b/test/Safe.t.sol @@ -92,6 +92,11 @@ contract SafeConfigTest is Test { assertEq(safe.getApiKitUrl(98866), "https://safe-transaction-plume.onchainden.com/api"); } + function test_Safe_getApiKitUrl_resolvesUnlistedOfficialChains() public view { + assertEq(safe.getApiKitUrl(36900), "https://api.safe.global/tx-service/adi/api"); + assertEq(Safe.getTransactionServiceUrl(36900), "https://api.safe.global/tx-service/adi/api"); + } + function test_Safe_getApiKitUrl_revertsForUnknownChain() public { vm.expectRevert(abi.encodeWithSelector(Safe.ApiKitUrlNotFound.selector, 31337)); this.exposedGetApiKitUrl(31337); diff --git a/test/ffi/safe-multisend-config.cjs b/test/ffi/safe-multisend-config.cjs index 56ac3cf..f5d6c19 100644 --- a/test/ffi/safe-multisend-config.cjs +++ b/test/ffi/safe-multisend-config.cjs @@ -4,6 +4,8 @@ const vm = require("vm"); const MIN_EXPECTED_NETWORKS = 39; const SUPPORTED_THIRD_PARTY_CHAIN_IDS = [98866]; +// Served by the official transaction service but not yet listed in the api-kit config. +const SUPPORTED_UNLISTED_OFFICIAL_CHAIN_IDS = [36900]; const MULTI_SEND_VERSIONS = ["1.5.0", "1.4.1", "1.3.0"]; const apiKitBundlePath = require.resolve("@safe-global/api-kit"); @@ -35,7 +37,7 @@ for (const network of networks) { chainIds.push(chainId); } -for (const chainId of SUPPORTED_THIRD_PARTY_CHAIN_IDS) { +for (const chainId of [...SUPPORTED_UNLISTED_OFFICIAL_CHAIN_IDS, ...SUPPORTED_THIRD_PARTY_CHAIN_IDS]) { if (seenChainIds.has(chainId)) { throw new Error(`Duplicate supported chainId: ${chainId}`); } diff --git a/test/helpers/SafeConfigFixtures.sol b/test/helpers/SafeConfigFixtures.sol index da19a38..5dd842c 100644 --- a/test/helpers/SafeConfigFixtures.sol +++ b/test/helpers/SafeConfigFixtures.sol @@ -8,8 +8,8 @@ library SafeConfigFixtures { address constant MULTI_SEND_CALL_ONLY_ADDRESS_V141_ZKSYNC = 0x0408EF011960d02349d50286D20531229BCef773; function officialChains() internal pure returns (uint256[] memory chainIds, string[] memory shortNames) { - chainIds = new uint256[](39); - shortNames = new string[](39); + chainIds = new uint256[](52); + shortNames = new string[](52); uint256 index; index = _pushOfficial(chainIds, shortNames, index, 1, "eth"); @@ -26,36 +26,49 @@ library SafeConfigFixtures { index = _pushOfficial(chainIds, shortNames, index, 232, "lens"); index = _pushOfficial(chainIds, shortNames, index, 324, "zksync"); index = _pushOfficial(chainIds, shortNames, index, 480, "wc"); + index = _pushOfficial(chainIds, shortNames, index, 677, "bot"); index = _pushOfficial(chainIds, shortNames, index, 988, "stable"); index = _pushOfficial(chainIds, shortNames, index, 999, "hyper"); - index = _pushOfficial(chainIds, shortNames, index, 1101, "zkevm"); + index = _pushOfficial(chainIds, shortNames, index, 1001, "kairos"); + index = _pushOfficial(chainIds, shortNames, index, 1672, "pharos"); + index = _pushOfficial(chainIds, shortNames, index, 1874, "wch-sepolia"); index = _pushOfficial(chainIds, shortNames, index, 3338, "peaq"); - index = _pushOfficial(chainIds, shortNames, index, 3637, "btc"); + index = _pushOfficial(chainIds, shortNames, index, 4217, "tempo"); + index = _pushOfficial(chainIds, shortNames, index, 4326, "mega"); + index = _pushOfficial(chainIds, shortNames, index, 4663, "robinhood"); index = _pushOfficial(chainIds, shortNames, index, 5000, "mantle"); + index = _pushOfficial(chainIds, shortNames, index, 5003, "mnt-sep"); + index = _pushOfficial(chainIds, shortNames, index, 5042, "arc"); + index = _pushOfficial(chainIds, shortNames, index, 8217, "kaia"); index = _pushOfficial(chainIds, shortNames, index, 8453, "base"); index = _pushOfficial(chainIds, shortNames, index, 9745, "plasma"); index = _pushOfficial(chainIds, shortNames, index, 10143, "monad-testnet"); index = _pushOfficial(chainIds, shortNames, index, 10200, "chi"); index = _pushOfficial(chainIds, shortNames, index, 16661, "0g"); + index = _pushOfficial(chainIds, shortNames, index, 25363, "fluent"); index = _pushOfficial(chainIds, shortNames, index, 42161, "arb1"); index = _pushOfficial(chainIds, shortNames, index, 42220, "celo"); + index = _pushOfficial(chainIds, shortNames, index, 42431, "tempo-moderato"); index = _pushOfficial(chainIds, shortNames, index, 43111, "hemi"); index = _pushOfficial(chainIds, shortNames, index, 43114, "avax"); + index = _pushOfficial(chainIds, shortNames, index, 46630, "robinhood-testnet"); index = _pushOfficial(chainIds, shortNames, index, 57073, "ink"); index = _pushOfficial(chainIds, shortNames, index, 59144, "linea"); index = _pushOfficial(chainIds, shortNames, index, 80069, "bep"); index = _pushOfficial(chainIds, shortNames, index, 80094, "berachain"); - index = _pushOfficial(chainIds, shortNames, index, 81224, "codex"); index = _pushOfficial(chainIds, shortNames, index, 84532, "basesep"); + index = _pushOfficial(chainIds, shortNames, index, 102030, "ctc"); index = _pushOfficial(chainIds, shortNames, index, 534352, "scr"); index = _pushOfficial(chainIds, shortNames, index, 747474, "katana"); + index = _pushOfficial(chainIds, shortNames, index, 5042002, "arc-testnet"); + index = _pushOfficial(chainIds, shortNames, index, 11142220, "celo-sep"); index = _pushOfficial(chainIds, shortNames, index, 11155111, "sep"); _pushOfficial(chainIds, shortNames, index, 1313161554, "aurora"); } function multiSendChains() internal pure returns (uint256[] memory chainIds, address[] memory expected) { - chainIds = new uint256[](40); - expected = new address[](40); + chainIds = new uint256[](54); + expected = new address[](54); uint256 index; index = _pushMultiSend(chainIds, expected, index, 1, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); @@ -72,31 +85,45 @@ library SafeConfigFixtures { index = _pushMultiSend(chainIds, expected, index, 232, MULTI_SEND_CALL_ONLY_ADDRESS_V141_ZKSYNC); index = _pushMultiSend(chainIds, expected, index, 324, MULTI_SEND_CALL_ONLY_ADDRESS_V130_ZKSYNC); index = _pushMultiSend(chainIds, expected, index, 480, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 677, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 988, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 999, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); - index = _pushMultiSend(chainIds, expected, index, 1101, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 1001, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 1672, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 1874, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 3338, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); - index = _pushMultiSend(chainIds, expected, index, 3637, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 4217, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 4326, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 4663, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 5000, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 5003, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 5042, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 8217, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 8453, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 9745, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 10143, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 10200, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 16661, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 25363, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 42161, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 42220, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 42431, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 43111, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 43114, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 46630, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 57073, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 59144, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 80069, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 80094, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); - index = _pushMultiSend(chainIds, expected, index, 81224, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 84532, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 102030, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 534352, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 747474, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 5042002, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 11142220, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 11155111, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 1313161554, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 36900, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); _pushMultiSend(chainIds, expected, index, 98866, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); } From a3de4f706643101284a02afe4c3a5c9717e42bbc Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 13 Oct 2026 17:43:33 +0200 Subject: [PATCH 2/3] Format for forge 1.8.5, the current stable CI installs --- src/Safe.sol | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Safe.sol b/src/Safe.sol index 59ebd4c..f763eff 100644 --- a/src/Safe.sol +++ b/src/Safe.sol @@ -440,7 +440,9 @@ library Safe { instance(self).requestBody = vm.serializeUint(".proposeTransaction", "gasPrice", 0); instance(self).requestBody = vm.serializeUint(".proposeTransaction", "nonce", params.nonce); - HTTP.Response memory response = instance(self).http.instance() + HTTP.Response memory response = instance(self) + .http + .instance() .POST( string.concat( getApiKitUrl(self, block.chainid), @@ -448,7 +450,9 @@ library Safe { vm.toString(instance(self).safe), "/multisig-transactions/" ) - ).withBody(instance(self).requestBody).request(); + ) + .withBody(instance(self).requestBody) + .request(); // The response status should be 2xx, otherwise there was an issue if (response.status < 200 || response.status >= 300) { From d8197da4d5efb10699c4001d915583af1d81b244 Mon Sep 17 00:00:00 2001 From: lemunozm Date: Tue, 13 Oct 2026 20:02:51 +0200 Subject: [PATCH 3/3] Keep MultiSendCallOnly for chains whose transaction service was retired Polygon zkEVM, BTC and Codex left api-kit because the official transaction service dropped them, but safe-deployments still lists the contract there, so only the service lookup forgets them. Every other chain with a service now defaults to the canonical v1.4.1 contract, with the v1.3.0 and zkSync chains as the explicit lists, and ADI sits in an override above an exact api-kit mirror. The multisend differential test sweeps the union of api-kit's list and the fixture, so a chain api-kit drops stays checked for as long as the library resolves it. --- src/Safe.sol | 101 +++++++++++++++------------- test/Safe.t.sol | 28 ++++---- test/SafeDifferential.t.sol | 12 +++- test/ffi/safe-api-kit-config.cjs | 6 +- test/ffi/safe-multisend-config.cjs | 21 ++++-- test/helpers/SafeConfigFixtures.sol | 19 +++++- 6 files changed, 115 insertions(+), 72 deletions(-) diff --git a/src/Safe.sol b/src/Safe.sol index f763eff..45cd152 100644 --- a/src/Safe.sol +++ b/src/Safe.sol @@ -80,8 +80,59 @@ library Safe { return getTransactionServiceUrl(chainId); } - // Mirrors safe-global/safe-core-sdk/packages/api-kit/src/utils/config.ts on main. - function getNetworkShortName(uint256 chainId) internal pure returns (string memory) { + function getNetworkShortName(uint256 chainId) internal pure returns (string memory shortName) { + shortName = _officialNetworkShortName(chainId); + if (bytes(shortName).length == 0) revert ApiKitUrlNotFound(chainId); + } + + function getTransactionServiceUrl(uint256 chainId) internal pure returns (string memory) { + return string.concat(SAFE_TRANSACTION_SERVICE_BASE_URL, "/", getNetworkShortName(chainId), "/api"); + } + + // Keep the first Client parameter so existing `using Safe for *` call sites remain unchanged. + function getMultiSendCallOnly(Client storage, uint256 chainId) internal pure returns (MultiSendCallOnly) { + if (chainId == 324) { + return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V130_ZKSYNC); + } + // safe-deployments registers Lens against the zkSync deployment in both v1.3.0 and v1.4.1. + if (chainId == 232) { + return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V141_ZKSYNC); + } + if (_usesV130CanonicalMultiSend(chainId)) { + return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + } + if (_usesV141CanonicalMultiSend(chainId)) { + return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + } + revert MultiSendCallOnlyNotFound(chainId); + } + + // Chains the official transaction service serves: the api-kit config plus the ones it does not list yet. + function _officialNetworkShortName(uint256 chainId) private pure returns (string memory) { + if (chainId == 36900) return "adi"; + return _apiKitNetworkShortName(chainId); + } + + function _usesV130CanonicalMultiSend(uint256 chainId) private pure returns (bool) { + // Polygon zkEVM (1101) lost its transaction service, but safe-deployments still lists the contract. + return (chainId == 1 || chainId == 10 || chainId == 56 || chainId == 100 || chainId == 130 || chainId == 137 + || chainId == 196 || chainId == 480 || chainId == 999 || chainId == 1101 || chainId == 5000 + || chainId == 8453 || chainId == 10200 || chainId == 42161 || chainId == 42220 || chainId == 43114 + || chainId == 59144 || chainId == 84532 || chainId == 534352 || chainId == 11155111 + || chainId == 1313161554); + } + + // Every chain the official transaction service serves, except the v1.3.0 and zkSync ones matched before, + // deploys the canonical v1.4.1 contract. So do Plume (98866), served by a third-party transaction service, and + // BTC (3637) and Codex (81224), which lost their transaction service while safe-deployments still lists them. + function _usesV141CanonicalMultiSend(uint256 chainId) private pure returns (bool) { + if (chainId == 98866 || chainId == 3637 || chainId == 81224) return true; + return bytes(_officialNetworkShortName(chainId)).length != 0; + } + + // Mirrors safe-global/safe-core-sdk/packages/api-kit/src/utils/config.ts on main; empty for a chain it does + // not list. + function _apiKitNetworkShortName(uint256 chainId) private pure returns (string memory) { if (chainId == 1) return "eth"; if (chainId == 10) return "oeth"; if (chainId == 50) return "xdc"; @@ -116,8 +167,6 @@ library Safe { if (chainId == 10200) return "chi"; if (chainId == 16661) return "0g"; if (chainId == 25363) return "fluent"; - // Served by the official transaction service, not yet listed in the api-kit config. - if (chainId == 36900) return "adi"; if (chainId == 42161) return "arb1"; if (chainId == 42220) return "celo"; if (chainId == 42431) return "tempo-moderato"; @@ -136,49 +185,7 @@ library Safe { if (chainId == 11142220) return "celo-sep"; if (chainId == 11155111) return "sep"; if (chainId == 1313161554) return "aurora"; - revert ApiKitUrlNotFound(chainId); - } - - function getTransactionServiceUrl(uint256 chainId) internal pure returns (string memory) { - return string.concat(SAFE_TRANSACTION_SERVICE_BASE_URL, "/", getNetworkShortName(chainId), "/api"); - } - - // Keep the first Client parameter so existing `using Safe for *` call sites remain unchanged. - function getMultiSendCallOnly(Client storage, uint256 chainId) internal pure returns (MultiSendCallOnly) { - if (chainId == 98866) { - return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); - } - if (chainId == 324) { - return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V130_ZKSYNC); - } - // safe-deployments registers Lens against the zkSync deployment in both v1.3.0 and v1.4.1. - if (chainId == 232) { - return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V141_ZKSYNC); - } - if (_usesV141CanonicalMultiSend(chainId)) { - return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); - } - if (_usesV130CanonicalMultiSend(chainId)) { - return MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); - } - revert MultiSendCallOnlyNotFound(chainId); - } - - function _usesV130CanonicalMultiSend(uint256 chainId) private pure returns (bool) { - return (chainId == 1 || chainId == 10 || chainId == 56 || chainId == 100 || chainId == 130 || chainId == 137 - || chainId == 196 || chainId == 480 || chainId == 999 || chainId == 5000 || chainId == 8453 - || chainId == 10200 || chainId == 42161 || chainId == 42220 || chainId == 43114 || chainId == 59144 - || chainId == 84532 || chainId == 534352 || chainId == 11155111 || chainId == 1313161554); - } - - function _usesV141CanonicalMultiSend(uint256 chainId) private pure returns (bool) { - return (chainId == 50 || chainId == 143 || chainId == 146 || chainId == 204 || chainId == 677 || chainId == 988 - || chainId == 1001 || chainId == 1672 || chainId == 1874 || chainId == 3338 || chainId == 4217 - || chainId == 4326 || chainId == 4663 || chainId == 5003 || chainId == 5042 || chainId == 8217 - || chainId == 9745 || chainId == 10143 || chainId == 16661 || chainId == 25363 || chainId == 36900 - || chainId == 42431 || chainId == 43111 || chainId == 46630 || chainId == 57073 || chainId == 80069 - || chainId == 80094 || chainId == 102030 || chainId == 747474 || chainId == 5042002 - || chainId == 11142220); + return ""; } function getNonce(Client storage self) internal view returns (uint256) { diff --git a/test/Safe.t.sol b/test/Safe.t.sol index 366ee91..159088b 100644 --- a/test/Safe.t.sol +++ b/test/Safe.t.sol @@ -76,27 +76,20 @@ contract SafeConfigTest is Test { safe.initialize(address(0xBEEF)); } - function test_Safe_getTransactionServiceUrl_matchesLatestOfficialSdkConfig() public pure { + function test_Safe_getTransactionServiceUrl_matchesLatestOfficialSdkConfig() public view { (uint256[] memory chainIds, string[] memory shortNames) = SafeConfigFixtures.officialChains(); + _assertOfficialTransactionServiceUrls(chainIds, shortNames); + } - assertEq(chainIds.length, shortNames.length); - for (uint256 i = 0; i < chainIds.length; i++) { - assertEq( - Safe.getTransactionServiceUrl(chainIds[i]), - string.concat(SAFE_TRANSACTION_SERVICE_BASE_URL, "/", shortNames[i], "/api") - ); - } + function test_Safe_getTransactionServiceUrl_resolvesUnlistedOfficialChains() public view { + (uint256[] memory chainIds, string[] memory shortNames) = SafeConfigFixtures.unlistedOfficialChains(); + _assertOfficialTransactionServiceUrls(chainIds, shortNames); } function test_Safe_getApiKitUrl_prefersThirdPartyOverrides() public view { assertEq(safe.getApiKitUrl(98866), "https://safe-transaction-plume.onchainden.com/api"); } - function test_Safe_getApiKitUrl_resolvesUnlistedOfficialChains() public view { - assertEq(safe.getApiKitUrl(36900), "https://api.safe.global/tx-service/adi/api"); - assertEq(Safe.getTransactionServiceUrl(36900), "https://api.safe.global/tx-service/adi/api"); - } - function test_Safe_getApiKitUrl_revertsForUnknownChain() public { vm.expectRevert(abi.encodeWithSelector(Safe.ApiKitUrlNotFound.selector, 31337)); this.exposedGetApiKitUrl(31337); @@ -120,6 +113,15 @@ contract SafeConfigTest is Test { return safe.getApiKitUrl(chainId); } + function _assertOfficialTransactionServiceUrls(uint256[] memory chainIds, string[] memory shortNames) private view { + assertEq(chainIds.length, shortNames.length); + for (uint256 i = 0; i < chainIds.length; i++) { + string memory expected = string.concat(SAFE_TRANSACTION_SERVICE_BASE_URL, "/", shortNames[i], "/api"); + assertEq(Safe.getTransactionServiceUrl(chainIds[i]), expected); + assertEq(safe.getApiKitUrl(chainIds[i]), expected); + } + } + function exposedGetMultiSendCallOnly(uint256 chainId) external view returns (address) { return address(safe.getMultiSendCallOnly(chainId)); } diff --git a/test/SafeDifferential.t.sol b/test/SafeDifferential.t.sol index d70ecb9..df536ab 100644 --- a/test/SafeDifferential.t.sol +++ b/test/SafeDifferential.t.sol @@ -30,9 +30,12 @@ contract SafeDifferentialTest is Test { } } + // Sweeps every chain api-kit lists and every chain the fixture claims, so a chain api-kit drops stays checked + // against safe-deployments for as long as the library resolves it. function test_Safe_multiSendConfig_matchesSafeDeployments() public { + (uint256[] memory supportedChainIds,) = SafeConfigFixtures.multiSendChains(); (uint256[] memory chainIds, uint256[] memory counts, address[] memory addresses) = - _readSafeDeploymentsMultiSendConfig(); + _readSafeDeploymentsMultiSendConfig(supportedChainIds); assertEq(chainIds.length, counts.length); @@ -65,13 +68,16 @@ contract SafeDifferentialTest is Test { urls = json.readStringArray(".urls"); } - function _readSafeDeploymentsMultiSendConfig() + function _readSafeDeploymentsMultiSendConfig(uint256[] memory supportedChainIds) private returns (uint256[] memory chainIds, uint256[] memory counts, address[] memory addresses) { - string[] memory inputs = new string[](2); + string[] memory inputs = new string[](2 + supportedChainIds.length); inputs[0] = "node"; inputs[1] = "test/ffi/safe-multisend-config.cjs"; + for (uint256 i = 0; i < supportedChainIds.length; i++) { + inputs[2 + i] = vm.toString(supportedChainIds[i]); + } string memory json = string(vm.ffi(inputs)); chainIds = json.readUintArray(".chainIds"); diff --git a/test/ffi/safe-api-kit-config.cjs b/test/ffi/safe-api-kit-config.cjs index 0e7dbcb..60104d4 100644 --- a/test/ffi/safe-api-kit-config.cjs +++ b/test/ffi/safe-api-kit-config.cjs @@ -1,7 +1,11 @@ const fs = require("fs"); const vm = require("vm"); -const MIN_EXPECTED_NETWORKS = 39; +// api-kit's network count at the pinned version; a lower count means the bundle scrape matched only part of it. +const MIN_EXPECTED_NETWORKS = 52; + +// api-kit exports no network list, so it is scraped from the built bundle. package.json pins the exact version +// because the bundle layout may change between releases. const bundlePath = require.resolve("@safe-global/api-kit"); const bundle = fs.readFileSync(bundlePath, "utf8"); diff --git a/test/ffi/safe-multisend-config.cjs b/test/ffi/safe-multisend-config.cjs index f5d6c19..17cbbe9 100644 --- a/test/ffi/safe-multisend-config.cjs +++ b/test/ffi/safe-multisend-config.cjs @@ -2,12 +2,21 @@ const fs = require("fs"); const path = require("path"); const vm = require("vm"); -const MIN_EXPECTED_NETWORKS = 39; -const SUPPORTED_THIRD_PARTY_CHAIN_IDS = [98866]; -// Served by the official transaction service but not yet listed in the api-kit config. -const SUPPORTED_UNLISTED_OFFICIAL_CHAIN_IDS = [36900]; +// api-kit's network count at the pinned version; a lower count means the bundle scrape matched only part of it. +const MIN_EXPECTED_NETWORKS = 52; const MULTI_SEND_VERSIONS = ["1.5.0", "1.4.1", "1.3.0"]; +// Chains to check beyond the ones api-kit lists, passed by the test from SafeConfigFixtures.multiSendChains(). +const fixtureChainIds = process.argv.slice(2).map((arg) => { + const chainId = Number(arg); + if (!Number.isInteger(chainId)) { + throw new Error(`Invalid chainId argument: ${arg}`); + } + return chainId; +}); + +// api-kit exports no network list, so it is scraped from the built bundle. package.json pins the exact version +// because the bundle layout may change between releases. const apiKitBundlePath = require.resolve("@safe-global/api-kit"); const apiKitBundle = fs.readFileSync(apiKitBundlePath, "utf8"); @@ -37,9 +46,9 @@ for (const network of networks) { chainIds.push(chainId); } -for (const chainId of [...SUPPORTED_UNLISTED_OFFICIAL_CHAIN_IDS, ...SUPPORTED_THIRD_PARTY_CHAIN_IDS]) { +for (const chainId of fixtureChainIds) { if (seenChainIds.has(chainId)) { - throw new Error(`Duplicate supported chainId: ${chainId}`); + continue; } seenChainIds.add(chainId); diff --git a/test/helpers/SafeConfigFixtures.sol b/test/helpers/SafeConfigFixtures.sol index 5dd842c..ef69e26 100644 --- a/test/helpers/SafeConfigFixtures.sol +++ b/test/helpers/SafeConfigFixtures.sol @@ -7,6 +7,7 @@ library SafeConfigFixtures { address constant MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL = 0x9641d764fc13c8B624c04430C7356C1C7C8102e2; address constant MULTI_SEND_CALL_ONLY_ADDRESS_V141_ZKSYNC = 0x0408EF011960d02349d50286D20531229BCef773; + // The chains api-kit lists, in its order. function officialChains() internal pure returns (uint256[] memory chainIds, string[] memory shortNames) { chainIds = new uint256[](52); shortNames = new string[](52); @@ -66,9 +67,20 @@ library SafeConfigFixtures { _pushOfficial(chainIds, shortNames, index, 1313161554, "aurora"); } + // Chains the official transaction service serves before api-kit lists them. Once api-kit lists one, move it to + // officialChains(): the differential test fails until then. + function unlistedOfficialChains() internal pure returns (uint256[] memory chainIds, string[] memory shortNames) { + chainIds = new uint256[](1); + shortNames = new string[](1); + _pushOfficial(chainIds, shortNames, 0, 36900, "adi"); + } + + // Every chain getMultiSendCallOnly resolves: the official and unlisted official chains, Plume (98866) on its + // third-party transaction service, and Polygon zkEVM (1101), BTC (3637) and Codex (81224), whose transaction + // service was retired while safe-deployments still lists the contract. function multiSendChains() internal pure returns (uint256[] memory chainIds, address[] memory expected) { - chainIds = new uint256[](54); - expected = new address[](54); + chainIds = new uint256[](57); + expected = new address[](57); uint256 index; index = _pushMultiSend(chainIds, expected, index, 1, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); @@ -89,9 +101,11 @@ library SafeConfigFixtures { index = _pushMultiSend(chainIds, expected, index, 988, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 999, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 1001, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 1101, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 1672, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 1874, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 3338, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 3637, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 4217, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 4326, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 4663, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); @@ -115,6 +129,7 @@ library SafeConfigFixtures { index = _pushMultiSend(chainIds, expected, index, 59144, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 80069, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 80094, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 81224, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 84532, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 102030, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); index = _pushMultiSend(chainIds, expected, index, 534352, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL);