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..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"; @@ -96,71 +147,45 @@ 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"; 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); - } - - 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 == 1101 || 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 ""; } function getNonce(Client storage self) internal view returns (uint256) { @@ -422,7 +447,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), @@ -430,7 +457,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) { diff --git a/test/Safe.t.sol b/test/Safe.t.sol index 54fa427..159088b 100644 --- a/test/Safe.t.sol +++ b/test/Safe.t.sol @@ -76,16 +76,14 @@ 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 { @@ -115,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 56ac3cf..17cbbe9 100644 --- a/test/ffi/safe-multisend-config.cjs +++ b/test/ffi/safe-multisend-config.cjs @@ -2,10 +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]; +// 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"); @@ -35,9 +46,9 @@ for (const network of networks) { chainIds.push(chainId); } -for (const chainId of 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 da19a38..ef69e26 100644 --- a/test/helpers/SafeConfigFixtures.sol +++ b/test/helpers/SafeConfigFixtures.sol @@ -7,9 +7,10 @@ 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[](39); - shortNames = new string[](39); + chainIds = new uint256[](52); + shortNames = new string[](52); uint256 index; index = _pushOfficial(chainIds, shortNames, index, 1, "eth"); @@ -26,36 +27,60 @@ 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"); } + // 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[](40); - expected = new address[](40); + chainIds = new uint256[](57); + expected = new address[](57); uint256 index; index = _pushMultiSend(chainIds, expected, index, 1, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); @@ -72,31 +97,48 @@ 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, 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); 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); }