diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5b894a..4a77a6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,15 @@ jobs: with: submodules: recursive + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Install npm dependencies + run: | + npm ci + - name: Setup Foundry uses: foundry-rs/foundry-toolchain@v1 with: @@ -52,4 +61,4 @@ jobs: - name: Test run: | forge test -vvv - id: test \ No newline at end of file + id: test diff --git a/.gitignore b/.gitignore index 85198aa..d4dfc4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Compiler files cache/ out/ +node_modules/ # Ignores development broadcast logs !/broadcast diff --git a/README.md b/README.md index d549578..6c3e47b 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ ffi = true ### Third-party integrations -The following blockchains are integrated via third party APIs and not the official safe.global tx service: +The following blockchains are integrated via third-party APIs and not the official `safe.global` tx service: | Blockchain | Provider | | --- | --- | diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..79c71fa --- /dev/null +++ b/package-lock.json @@ -0,0 +1,440 @@ +{ + "name": "safe-utils", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "safe-utils", + "devDependencies": { + "@safe-global/api-kit": "4.1.0", + "@safe-global/safe-deployments": "1.37.53" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@safe-global/protocol-kit": "^7.0.0", + "@safe-global/types-kit": "^3.1.0", + "node-fetch": "^2.7.0", + "viem": "^2.21.8" + } + }, + "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==", + "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" + }, + "optionalDependencies": { + "@noble/curves": "^1.6.0", + "@peculiar/asn1-schema": "^2.3.13" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.2" + } + }, + "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==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "abitype": "^1.0.2" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.2.3", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/viem": { + "version": "2.47.6", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.47.6.tgz", + "integrity": "sha512-zExmbI99NGvMdYa7fmqSTLgkwh48dmhgEqFrUgkpL4kfG4XkVefZ8dZqIKVUhZo6Uhf0FrrEXOsHm9LUyIvI2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.2.3", + "isows": "1.0.7", + "ox": "0.14.7", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2416c3f --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "safe-utils", + "devDependencies": { + "@safe-global/api-kit": "4.1.0", + "@safe-global/safe-deployments": "1.37.53" + } +} diff --git a/src/Safe.sol b/src/Safe.sol index b57de9b..53d843a 100644 --- a/src/Safe.sol +++ b/src/Safe.sol @@ -12,14 +12,18 @@ library Safe { /// forge-lint: disable-next-line(screaming-snake-case-const) Vm constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + string constant SAFE_TRANSACTION_SERVICE_BASE_URL = "https://api.safe.global/tx-service"; + string constant PLUME_TRANSACTION_SERVICE_URL = "https://safe-transaction-plume.onchainden.com/api"; // https://github.com/safe-global/safe-smart-account/blob/release/v1.4.1/contracts/libraries/SafeStorage.sol bytes32 constant SAFE_THRESHOLD_STORAGE_SLOT = bytes32(uint256(4)); - // https://github.com/safe-global/safe-deployments/blob/v1.37.32/src/assets/v1.3.0/multi_send_call_only.json - address constant MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL = 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D; - address constant MULTI_SEND_CALL_ONLY_ADDRESS_EIP155 = 0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B; - address constant MULTI_SEND_CALL_ONLY_ADDRESS_ZKSYNC = 0xf220D3b4DFb23C4ade8C88E526C1353AbAcbC38F; + // https://github.com/safe-global/safe-deployments/blob/c6a2025fca317b629d73d24b472c266418e2a4d6/src/assets/v1.3.0/multi_send_call_only.json + address constant MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL = 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D; + address constant MULTI_SEND_CALL_ONLY_ADDRESS_V130_ZKSYNC = 0xf220D3b4DFb23C4ade8C88E526C1353AbAcbC38F; + // https://github.com/safe-global/safe-deployments/blob/c6a2025fca317b629d73d24b472c266418e2a4d6/src/assets/v1.4.1/multi_send_call_only.json + address constant MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL = 0x9641d764fc13c8B624c04430C7356C1C7C8102e2; + address constant MULTI_SEND_CALL_ONLY_ADDRESS_V141_ZKSYNC = 0x0408EF011960d02349d50286D20531229BCef773; error ApiKitUrlNotFound(uint256 chainId); error MultiSendCallOnlyNotFound(uint256 chainId); @@ -29,8 +33,6 @@ library Safe { struct Instance { address safe; HTTP.Client http; - mapping(uint256 chainId => string) urls; - mapping(uint256 chainId => MultiSendCallOnly) multiSendCallOnly; string requestBody; } @@ -58,54 +60,6 @@ library Safe { self.instances.push(); Instance storage i = self.instances[self.instances.length - 1]; i.safe = safe; - // https://github.com/safe-global/safe-core-sdk/blob/4d89cb9b1559e4349c323a48a10caf685f7f8c88/packages/api-kit/src/utils/config.ts - i.urls[1] = "https://api.safe.global/tx-service/eth/api"; - i.urls[10] = "https://api.safe.global/tx-service/oeth/api"; - i.urls[56] = "https://api.safe.global/tx-service/bnb/api"; - i.urls[100] = "https://api.safe.global/tx-service/gno/api"; - i.urls[130] = "https://api.safe.global/tx-service/unichain/api"; - i.urls[137] = "https://api.safe.global/tx-service/pol/api"; - i.urls[196] = "https://api.safe.global/tx-service/okb/api"; - i.urls[324] = "https://api.safe.global/tx-service/zksync/api"; - i.urls[480] = "https://api.safe.global/tx-service/wc/api"; - i.urls[999] = "https://api.safe.global/tx-service/hyper/api"; - i.urls[1101] = "https://api.safe.global/tx-service/zkevm/api"; - i.urls[5000] = "https://api.safe.global/tx-service/mantle/api"; - i.urls[8453] = "https://api.safe.global/tx-service/base/api"; - i.urls[42161] = "https://api.safe.global/tx-service/arb1/api"; - i.urls[42220] = "https://api.safe.global/tx-service/celo/api"; - i.urls[43114] = "https://api.safe.global/tx-service/avax/api"; - i.urls[59144] = "https://api.safe.global/tx-service/linea/api"; - i.urls[84532] = "https://api.safe.global/tx-service/basesep/api"; - i.urls[98866] = "https://safe-transaction-plume.onchainden.com/api"; - i.urls[534352] = "https://api.safe.global/tx-service/scr/api"; - i.urls[11155111] = "https://api.safe.global/tx-service/sep/api"; - i.urls[1313161554] = "https://api.safe.global/tx-service/aurora/api"; - - // https://github.com/safe-global/safe-deployments/blob/v1.37.32/src/assets/v1.3.0/multi_send_call_only.json - i.multiSendCallOnly[1] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[10] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[56] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[100] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[130] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[137] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[196] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[324] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_ZKSYNC); - i.multiSendCallOnly[480] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[999] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[1101] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[5000] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[8453] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[42161] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[42220] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[43114] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[59144] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[84532] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[98866] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[534352] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[11155111] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.multiSendCallOnly[1313161554] = MultiSendCallOnly(MULTI_SEND_CALL_ONLY_ADDRESS_CANONICAL); - i.http.initialize().withHeader("Content-Type", "application/json").withFollowRedirects(true); return self; } @@ -114,20 +68,95 @@ library Safe { return self.instances[self.instances.length - 1]; } - function getApiKitUrl(Client storage self, uint256 chainId) internal view returns (string memory) { - string memory url = instance(self).urls[chainId]; - if (bytes(url).length == 0) { - revert ApiKitUrlNotFound(chainId); + // Keep the first Client parameter so existing `using Safe for *` call sites remain unchanged. + function getApiKitUrl(Client storage, uint256 chainId) internal pure returns (string memory) { + if (chainId == 98866) { + return PLUME_TRANSACTION_SERVICE_URL; } - return url; + 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) { + if (chainId == 1) return "eth"; + if (chainId == 10) return "oeth"; + if (chainId == 50) return "xdc"; + if (chainId == 56) return "bnb"; + if (chainId == 100) return "gno"; + if (chainId == 130) return "unichain"; + if (chainId == 137) return "pol"; + if (chainId == 143) return "monad"; + if (chainId == 146) return "sonic"; + if (chainId == 196) return "okb"; + if (chainId == 204) return "opbnb"; + if (chainId == 232) return "lens"; + if (chainId == 324) return "zksync"; + if (chainId == 480) return "wc"; + if (chainId == 988) return "stable"; + if (chainId == 999) return "hyper"; + if (chainId == 1101) return "zkevm"; + if (chainId == 3338) return "peaq"; + if (chainId == 3637) return "btc"; + if (chainId == 5000) return "mantle"; + 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 == 42161) return "arb1"; + if (chainId == 42220) return "celo"; + if (chainId == 43111) return "hemi"; + if (chainId == 43114) return "avax"; + 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 == 534352) return "scr"; + if (chainId == 747474) return "katana"; + if (chainId == 11155111) return "sep"; + if (chainId == 1313161554) return "aurora"; + revert ApiKitUrlNotFound(chainId); } - function getMultiSendCallOnly(Client storage self, uint256 chainId) internal view returns (MultiSendCallOnly) { - MultiSendCallOnly multiSendCallOnly = instance(self).multiSendCallOnly[chainId]; - if (address(multiSendCallOnly) == address(0)) { - revert MultiSendCallOnlyNotFound(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); } - return multiSendCallOnly; + 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); } function getNonce(Client storage self) internal view returns (uint256) { diff --git a/test/Safe.t.sol b/test/Safe.t.sol index d26409c..54fa427 100644 --- a/test/Safe.t.sol +++ b/test/Safe.t.sol @@ -3,13 +3,12 @@ pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {Safe} from "../src/Safe.sol"; -import {strings} from "solidity-stringutils/strings.sol"; import {IWETH} from "./interfaces/IWETH.sol"; import {Enum} from "safe-smart-account/common/Enum.sol"; +import {SafeConfigFixtures} from "./helpers/SafeConfigFixtures.sol"; contract SafeTest is Test { using Safe for *; - using strings for *; Safe.Client safe; address safeAddress = 0xF3a292Dda3F524EA20b5faF2EE0A1c4abA665e4F; @@ -65,3 +64,58 @@ contract SafeTest is Test { safe.proposeTransactionsWithSignature(targets, datas, foundrySigner1, signature); } } + +contract SafeConfigTest is Test { + using Safe for *; + + string constant SAFE_TRANSACTION_SERVICE_BASE_URL = "https://api.safe.global/tx-service"; + + Safe.Client safe; + + function setUp() public { + safe.initialize(address(0xBEEF)); + } + + function test_Safe_getTransactionServiceUrl_matchesLatestOfficialSdkConfig() public pure { + (uint256[] memory chainIds, string[] memory shortNames) = SafeConfigFixtures.officialChains(); + + 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_getApiKitUrl_prefersThirdPartyOverrides() public view { + assertEq(safe.getApiKitUrl(98866), "https://safe-transaction-plume.onchainden.com/api"); + } + + function test_Safe_getApiKitUrl_revertsForUnknownChain() public { + vm.expectRevert(abi.encodeWithSelector(Safe.ApiKitUrlNotFound.selector, 31337)); + this.exposedGetApiKitUrl(31337); + } + + function test_Safe_getMultiSendCallOnly_resolvesLegacyAndNewDeployments() public view { + (uint256[] memory chainIds, address[] memory expected) = SafeConfigFixtures.multiSendChains(); + + assertEq(chainIds.length, expected.length); + for (uint256 i = 0; i < chainIds.length; i++) { + assertEq(address(safe.getMultiSendCallOnly(chainIds[i])), expected[i]); + } + } + + function test_Safe_getMultiSendCallOnly_revertsForUnknownChain() public { + vm.expectRevert(abi.encodeWithSelector(Safe.MultiSendCallOnlyNotFound.selector, 31337)); + this.exposedGetMultiSendCallOnly(31337); + } + + function exposedGetApiKitUrl(uint256 chainId) external view returns (string memory) { + return safe.getApiKitUrl(chainId); + } + + 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 new file mode 100644 index 0000000..d70ecb9 --- /dev/null +++ b/test/SafeDifferential.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {stdJson} from "forge-std/StdJson.sol"; +import {Safe} from "../src/Safe.sol"; +import {SafeConfigFixtures} from "./helpers/SafeConfigFixtures.sol"; + +contract SafeDifferentialTest is Test { + using Safe for *; + using stdJson for string; + + Safe.Client safe; + + function setUp() public { + safe.initialize(address(0xBEEF)); + } + + function test_Safe_transactionServiceConfig_matchesTypeScriptSdk() public { + (uint256[] memory expectedChainIds,) = SafeConfigFixtures.officialChains(); + (uint256[] memory chainIds, string[] memory urls) = _readTypeScriptSdkConfig(); + + assertEq(chainIds.length, urls.length); + assertEq(chainIds.length, expectedChainIds.length); + + for (uint256 i = 0; i < chainIds.length; i++) { + assertEq(chainIds[i], expectedChainIds[i]); + assertEq(Safe.getTransactionServiceUrl(chainIds[i]), urls[i]); + assertEq(safe.getApiKitUrl(chainIds[i]), urls[i]); + } + } + + function test_Safe_multiSendConfig_matchesSafeDeployments() public { + (uint256[] memory chainIds, uint256[] memory counts, address[] memory addresses) = + _readSafeDeploymentsMultiSendConfig(); + + assertEq(chainIds.length, counts.length); + + uint256 cursor; + for (uint256 i = 0; i < chainIds.length; i++) { + address resolved = address(safe.getMultiSendCallOnly(chainIds[i])); + bool found; + + for (uint256 j = 0; j < counts[i]; j++) { + if (resolved == addresses[cursor + j]) { + found = true; + break; + } + } + + assertTrue(found, string.concat("unexpected multisend address for chain ", vm.toString(chainIds[i]))); + cursor += counts[i]; + } + + assertEq(cursor, addresses.length); + } + + function _readTypeScriptSdkConfig() private returns (uint256[] memory chainIds, string[] memory urls) { + string[] memory inputs = new string[](2); + inputs[0] = "node"; + inputs[1] = "test/ffi/safe-api-kit-config.cjs"; + + string memory json = string(vm.ffi(inputs)); + chainIds = json.readUintArray(".chainIds"); + urls = json.readStringArray(".urls"); + } + + function _readSafeDeploymentsMultiSendConfig() + private + returns (uint256[] memory chainIds, uint256[] memory counts, address[] memory addresses) + { + string[] memory inputs = new string[](2); + inputs[0] = "node"; + inputs[1] = "test/ffi/safe-multisend-config.cjs"; + + string memory json = string(vm.ffi(inputs)); + chainIds = json.readUintArray(".chainIds"); + counts = json.readUintArray(".counts"); + addresses = json.readAddressArray(".addresses"); + } +} diff --git a/test/ffi/safe-api-kit-config.cjs b/test/ffi/safe-api-kit-config.cjs new file mode 100644 index 0000000..0e7dbcb --- /dev/null +++ b/test/ffi/safe-api-kit-config.cjs @@ -0,0 +1,44 @@ +const fs = require("fs"); +const vm = require("vm"); + +const MIN_EXPECTED_NETWORKS = 39; +const bundlePath = require.resolve("@safe-global/api-kit"); +const bundle = fs.readFileSync(bundlePath, "utf8"); + +const baseUrlMatch = bundle.match(/var TRANSACTION_SERVICE_URL = "([^"]+)";/); +if (!baseUrlMatch) { + throw new Error(`Unable to locate TRANSACTION_SERVICE_URL in ${bundlePath}`); +} + +const networksMatch = bundle.match(/var networks = (\[[\s\S]*?\n\]);\nvar getNetworkShortName =/); +if (!networksMatch) { + throw new Error(`Unable to locate networks config in ${bundlePath}`); +} + +const networks = vm.runInNewContext(networksMatch[1]); +if (!Array.isArray(networks) || networks.length < MIN_EXPECTED_NETWORKS) { + throw new Error(`Unexpected networks config shape in ${bundlePath}`); +} + +const chainIds = []; +const urls = []; +const seenChainIds = new Set(); + +for (const network of networks) { + const chainId = Number(network.chainId); + if (!Number.isInteger(chainId)) { + throw new Error(`Invalid chainId in ${bundlePath}: ${network.chainId}`); + } + if (typeof network.shortName !== "string" || network.shortName.length === 0) { + throw new Error(`Invalid shortName in ${bundlePath}: ${String(network.shortName)}`); + } + if (seenChainIds.has(chainId)) { + throw new Error(`Duplicate chainId in ${bundlePath}: ${chainId}`); + } + + seenChainIds.add(chainId); + chainIds.push(chainId); + urls.push(`${baseUrlMatch[1]}/${network.shortName}/api`); +} + +process.stdout.write(JSON.stringify({ chainIds, urls })); diff --git a/test/ffi/safe-multisend-config.cjs b/test/ffi/safe-multisend-config.cjs new file mode 100644 index 0000000..56ac3cf --- /dev/null +++ b/test/ffi/safe-multisend-config.cjs @@ -0,0 +1,87 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +const MIN_EXPECTED_NETWORKS = 39; +const SUPPORTED_THIRD_PARTY_CHAIN_IDS = [98866]; +const MULTI_SEND_VERSIONS = ["1.5.0", "1.4.1", "1.3.0"]; + +const apiKitBundlePath = require.resolve("@safe-global/api-kit"); +const apiKitBundle = fs.readFileSync(apiKitBundlePath, "utf8"); + +const networksMatch = apiKitBundle.match(/var networks = (\[[\s\S]*?\n\]);\nvar getNetworkShortName =/); +if (!networksMatch) { + throw new Error(`Unable to locate networks config in ${apiKitBundlePath}`); +} + +const networks = vm.runInNewContext(networksMatch[1]); +if (!Array.isArray(networks) || networks.length < MIN_EXPECTED_NETWORKS) { + throw new Error(`Unexpected networks config shape in ${apiKitBundlePath}`); +} + +const chainIds = []; +const seenChainIds = new Set(); + +for (const network of networks) { + const chainId = Number(network.chainId); + if (!Number.isInteger(chainId)) { + throw new Error(`Invalid chainId in ${apiKitBundlePath}: ${network.chainId}`); + } + if (seenChainIds.has(chainId)) { + throw new Error(`Duplicate chainId in ${apiKitBundlePath}: ${chainId}`); + } + + seenChainIds.add(chainId); + chainIds.push(chainId); +} + +for (const chainId of SUPPORTED_THIRD_PARTY_CHAIN_IDS) { + if (seenChainIds.has(chainId)) { + throw new Error(`Duplicate supported chainId: ${chainId}`); + } + + seenChainIds.add(chainId); + chainIds.push(chainId); +} + +const safeDeploymentsRoot = path.dirname(require.resolve("@safe-global/safe-deployments/package.json")); +const multiSendDeployments = MULTI_SEND_VERSIONS.map((version) => { + const deploymentPath = path.join( + safeDeploymentsRoot, + `src/assets/v${version}/multi_send_call_only.json`, + ); + return JSON.parse(fs.readFileSync(deploymentPath, "utf8")); +}); + +const counts = []; +const addresses = []; + +for (const chainId of chainIds) { + const validAddresses = new Set(); + + for (const deployment of multiSendDeployments) { + const addressTypes = deployment.networkAddresses[String(chainId)]; + if (!addressTypes) { + continue; + } + + const normalizedAddressTypes = Array.isArray(addressTypes) ? addressTypes : [addressTypes]; + for (const addressType of normalizedAddressTypes) { + const address = deployment.deployments[addressType]?.address; + if (typeof address !== "string" || address.length === 0) { + throw new Error(`Invalid ${addressType} deployment for chain ${chainId}`); + } + + validAddresses.add(address); + } + } + + if (validAddresses.size === 0) { + throw new Error(`No MultiSendCallOnly deployment found for chain ${chainId}`); + } + + counts.push(validAddresses.size); + addresses.push(...validAddresses); +} + +process.stdout.write(JSON.stringify({ chainIds, counts, addresses })); diff --git a/test/helpers/SafeConfigFixtures.sol b/test/helpers/SafeConfigFixtures.sol new file mode 100644 index 0000000..da19a38 --- /dev/null +++ b/test/helpers/SafeConfigFixtures.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +library SafeConfigFixtures { + address constant MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL = 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D; + address constant MULTI_SEND_CALL_ONLY_ADDRESS_V130_ZKSYNC = 0xf220D3b4DFb23C4ade8C88E526C1353AbAcbC38F; + address constant MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL = 0x9641d764fc13c8B624c04430C7356C1C7C8102e2; + 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); + uint256 index; + + index = _pushOfficial(chainIds, shortNames, index, 1, "eth"); + index = _pushOfficial(chainIds, shortNames, index, 10, "oeth"); + index = _pushOfficial(chainIds, shortNames, index, 50, "xdc"); + index = _pushOfficial(chainIds, shortNames, index, 56, "bnb"); + index = _pushOfficial(chainIds, shortNames, index, 100, "gno"); + index = _pushOfficial(chainIds, shortNames, index, 130, "unichain"); + index = _pushOfficial(chainIds, shortNames, index, 137, "pol"); + index = _pushOfficial(chainIds, shortNames, index, 143, "monad"); + index = _pushOfficial(chainIds, shortNames, index, 146, "sonic"); + index = _pushOfficial(chainIds, shortNames, index, 196, "okb"); + index = _pushOfficial(chainIds, shortNames, index, 204, "opbnb"); + 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, 988, "stable"); + index = _pushOfficial(chainIds, shortNames, index, 999, "hyper"); + index = _pushOfficial(chainIds, shortNames, index, 1101, "zkevm"); + index = _pushOfficial(chainIds, shortNames, index, 3338, "peaq"); + index = _pushOfficial(chainIds, shortNames, index, 3637, "btc"); + index = _pushOfficial(chainIds, shortNames, index, 5000, "mantle"); + 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, 42161, "arb1"); + index = _pushOfficial(chainIds, shortNames, index, 42220, "celo"); + index = _pushOfficial(chainIds, shortNames, index, 43111, "hemi"); + index = _pushOfficial(chainIds, shortNames, index, 43114, "avax"); + 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, 534352, "scr"); + index = _pushOfficial(chainIds, shortNames, index, 747474, "katana"); + 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); + uint256 index; + + index = _pushMultiSend(chainIds, expected, index, 1, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 10, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 50, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 56, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 100, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 130, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 137, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 143, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 146, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 196, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 204, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + 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, 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, 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, 5000, MULTI_SEND_CALL_ONLY_ADDRESS_V130_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, 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, 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, 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, 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, 11155111, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + index = _pushMultiSend(chainIds, expected, index, 1313161554, MULTI_SEND_CALL_ONLY_ADDRESS_V130_CANONICAL); + _pushMultiSend(chainIds, expected, index, 98866, MULTI_SEND_CALL_ONLY_ADDRESS_V141_CANONICAL); + } + + function _pushOfficial( + uint256[] memory chainIds, + string[] memory shortNames, + uint256 index, + uint256 chainId, + string memory shortName + ) private pure returns (uint256) { + chainIds[index] = chainId; + shortNames[index] = shortName; + return index + 1; + } + + function _pushMultiSend( + uint256[] memory chainIds, + address[] memory expected, + uint256 index, + uint256 chainId, + address multiSendCallOnly + ) private pure returns (uint256) { + chainIds[index] = chainId; + expected[index] = multiSendCallOnly; + return index + 1; + } +}