Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ BLAST_RPC=
MODE_RPC=
MANTLE_RPC=
MEGAETH_RPC=
RISE_RPC=
GNOSIS_RPC=
SONIC_RPC=
UNICHAIN_RPC=
Expand Down Expand Up @@ -81,6 +82,7 @@ WORLDCHAIN_ETHERSCAN_KEY=
SEI_ETHERSCAN_KEY=
SONEIUM_ETHERSCAN_KEY=
MEGAETH_ETHERSCAN_KEY=
RISE_ETHERSCAN_KEY= # optional; RISE Blockscout does not require a key
PLUME_ETHERSCAN_KEY=
ETHEREAL_ETHERSCAN_KEY=
KATANA_ETHERSCAN_KEY=
Expand Down
1 change: 1 addition & 0 deletions deployments.csv
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ chainId,variant,allowanceHolderAddress,allowanceHolderSalt,allowanceHolderSaltTe
999,cancun,0x50c4E75a512F2A14A7b304787Adf79C4531A5909,0xa450483209637d11f92238066a6b1f405ae093536f02dda3b90a36d36f180570,AllowanceHolder50c4e7:5981577,0xe0afa70daed24ec949499638683365cbe4cbf097a1c8417a730e4b497cfb4610,0x0be5794255d21df0f4a10a516428f8d06805779d69b1714297bebcc18971e0b4,0x50cFe7c1938dB66A1a6D2e86D36F39FBef3d5c4a,0x4c57cf418d2865efb93a3c31021f230798eaca0458d188fbb593e329718139ab,OpenRouter50cfe7:4030514,0x0e5ff42eb7810767de756c833a042818f95052b885634a3dc3adb679a4652f48,0xd1dbc2c8ca87939cadf58e4eb91832cbe44bd5d12e3a0a0d56c76928a818e26b,0x8A774c1B73998A54ff09341f3cfF8A0010BbA7f1,0xeb8d08957ffb6da6c84ab1f89e95f4e08331fa81834e21d00754af20ec74d453,BungeeReceiver,0x1e4cf28950cc422f6b2112db77b8c0b0025c176291f984166889b8157de9dfe6,0x3e3fdfa261d7f00f53c22b4ba051f0da1903402669591bcef63d51ebe3085108,0xC914815120FA5A7e05748398C9fDf1d1b2729008,0x7c3bbaa3e26afa737955bd8665389dae8745dd537430122c9545abb09f85e34b,CalldataExecutor,0x2ac2791e955dbd2e9787486e2a4a9ba669d27cb1b642ade54a1221a5ccd53b64,0x037de760dbd500210ed5c68706a16171ec415563c6136e52d6a9930a5f53e32a
1329,cancun,0x50c4E75a512F2A14A7b304787Adf79C4531A5909,0xa450483209637d11f92238066a6b1f405ae093536f02dda3b90a36d36f180570,AllowanceHolder50c4e7:5981577,0xe0afa70daed24ec949499638683365cbe4cbf097a1c8417a730e4b497cfb4610,0x0be5794255d21df0f4a10a516428f8d06805779d69b1714297bebcc18971e0b4,0x50cFe7c1938dB66A1a6D2e86D36F39FBef3d5c4a,0x4c57cf418d2865efb93a3c31021f230798eaca0458d188fbb593e329718139ab,OpenRouter50cfe7:4030514,0x0e5ff42eb7810767de756c833a042818f95052b885634a3dc3adb679a4652f48,0xd1dbc2c8ca87939cadf58e4eb91832cbe44bd5d12e3a0a0d56c76928a818e26b,0x8A774c1B73998A54ff09341f3cfF8A0010BbA7f1,0xeb8d08957ffb6da6c84ab1f89e95f4e08331fa81834e21d00754af20ec74d453,BungeeReceiver,0x1e4cf28950cc422f6b2112db77b8c0b0025c176291f984166889b8157de9dfe6,0x3e3fdfa261d7f00f53c22b4ba051f0da1903402669591bcef63d51ebe3085108,0xC914815120FA5A7e05748398C9fDf1d1b2729008,0x7c3bbaa3e26afa737955bd8665389dae8745dd537430122c9545abb09f85e34b,CalldataExecutor,0x2ac2791e955dbd2e9787486e2a4a9ba669d27cb1b642ade54a1221a5ccd53b64,0x037de760dbd500210ed5c68706a16171ec415563c6136e52d6a9930a5f53e32a
1868,cancun,0x50c4E75a512F2A14A7b304787Adf79C4531A5909,0xa450483209637d11f92238066a6b1f405ae093536f02dda3b90a36d36f180570,AllowanceHolder50c4e7:5981577,0xe0afa70daed24ec949499638683365cbe4cbf097a1c8417a730e4b497cfb4610,0x0be5794255d21df0f4a10a516428f8d06805779d69b1714297bebcc18971e0b4,0x50cFe7c1938dB66A1a6D2e86D36F39FBef3d5c4a,0x4c57cf418d2865efb93a3c31021f230798eaca0458d188fbb593e329718139ab,OpenRouter50cfe7:4030514,0x0e5ff42eb7810767de756c833a042818f95052b885634a3dc3adb679a4652f48,0xd1dbc2c8ca87939cadf58e4eb91832cbe44bd5d12e3a0a0d56c76928a818e26b,0x8A774c1B73998A54ff09341f3cfF8A0010BbA7f1,0xeb8d08957ffb6da6c84ab1f89e95f4e08331fa81834e21d00754af20ec74d453,BungeeReceiver,0x1e4cf28950cc422f6b2112db77b8c0b0025c176291f984166889b8157de9dfe6,0x3e3fdfa261d7f00f53c22b4ba051f0da1903402669591bcef63d51ebe3085108,0xC914815120FA5A7e05748398C9fDf1d1b2729008,0x7c3bbaa3e26afa737955bd8665389dae8745dd537430122c9545abb09f85e34b,CalldataExecutor,0x2ac2791e955dbd2e9787486e2a4a9ba669d27cb1b642ade54a1221a5ccd53b64,0x037de760dbd500210ed5c68706a16171ec415563c6136e52d6a9930a5f53e32a
4153,cancun,0x50c4E75a512F2A14A7b304787Adf79C4531A5909,0xa450483209637d11f92238066a6b1f405ae093536f02dda3b90a36d36f180570,AllowanceHolder50c4e7:5981577,0x7b4c1230f4398f130d0763a180f21f0dbcae086cbd4a52746c4374a8d5620165,0x1919e6d868d2cc2e7de33d97a656149d47f37b87d0eea3f07a6a404663746d4f,0x50cFe7c1938dB66A1a6D2e86D36F39FBef3d5c4a,0x4c57cf418d2865efb93a3c31021f230798eaca0458d188fbb593e329718139ab,OpenRouter50cfe7:4030514,0x0e5ff42eb7810767de756c833a042818f95052b885634a3dc3adb679a4652f48,0xd1dbc2c8ca87939cadf58e4eb91832cbe44bd5d12e3a0a0d56c76928a818e26b,0x8A774c1B73998A54ff09341f3cfF8A0010BbA7f1,0xeb8d08957ffb6da6c84ab1f89e95f4e08331fa81834e21d00754af20ec74d453,BungeeReceiver,0x8c61bf68799b5897057a153c134492e43faa90a4caa0d9ce5d1016e4dec6ae5e,0x3e3fdfa261d7f00f53c22b4ba051f0da1903402669591bcef63d51ebe3085108,0xC914815120FA5A7e05748398C9fDf1d1b2729008,0x7c3bbaa3e26afa737955bd8665389dae8745dd537430122c9545abb09f85e34b,CalldataExecutor,0x2ac2791e955dbd2e9787486e2a4a9ba669d27cb1b642ade54a1221a5ccd53b64,0x037de760dbd500210ed5c68706a16171ec415563c6136e52d6a9930a5f53e32a
4217,cancun,0x50c4E75a512F2A14A7b304787Adf79C4531A5909,0xa450483209637d11f92238066a6b1f405ae093536f02dda3b90a36d36f180570,AllowanceHolder50c4e7:5981577,0xe0afa70daed24ec949499638683365cbe4cbf097a1c8417a730e4b497cfb4610,0x0be5794255d21df0f4a10a516428f8d06805779d69b1714297bebcc18971e0b4,,,,,,,,,,,,,,,
4326,cancun,0x50c4E75a512F2A14A7b304787Adf79C4531A5909,0xa450483209637d11f92238066a6b1f405ae093536f02dda3b90a36d36f180570,AllowanceHolder50c4e7:5981577,0xe0afa70daed24ec949499638683365cbe4cbf097a1c8417a730e4b497cfb4610,0x0be5794255d21df0f4a10a516428f8d06805779d69b1714297bebcc18971e0b4,0x50cFe7c1938dB66A1a6D2e86D36F39FBef3d5c4a,0x4c57cf418d2865efb93a3c31021f230798eaca0458d188fbb593e329718139ab,OpenRouter50cfe7:4030514,0x0e5ff42eb7810767de756c833a042818f95052b885634a3dc3adb679a4652f48,0xd1dbc2c8ca87939cadf58e4eb91832cbe44bd5d12e3a0a0d56c76928a818e26b,0x8A774c1B73998A54ff09341f3cfF8A0010BbA7f1,0xeb8d08957ffb6da6c84ab1f89e95f4e08331fa81834e21d00754af20ec74d453,BungeeReceiver,0x1e4cf28950cc422f6b2112db77b8c0b0025c176291f984166889b8157de9dfe6,0x3e3fdfa261d7f00f53c22b4ba051f0da1903402669591bcef63d51ebe3085108,0xC914815120FA5A7e05748398C9fDf1d1b2729008,0x7c3bbaa3e26afa737955bd8665389dae8745dd537430122c9545abb09f85e34b,CalldataExecutor,0x2ac2791e955dbd2e9787486e2a4a9ba669d27cb1b642ade54a1221a5ccd53b64,0x037de760dbd500210ed5c68706a16171ec415563c6136e52d6a9930a5f53e32a
4663,cancun,0x50c4E75a512F2A14A7b304787Adf79C4531A5909,0xa450483209637d11f92238066a6b1f405ae093536f02dda3b90a36d36f180570,AllowanceHolder50c4e7:5981577,0xe0afa70daed24ec949499638683365cbe4cbf097a1c8417a730e4b497cfb4610,0x0be5794255d21df0f4a10a516428f8d06805779d69b1714297bebcc18971e0b4,0x50cFe7c1938dB66A1a6D2e86D36F39FBef3d5c4a,0x4c57cf418d2865efb93a3c31021f230798eaca0458d188fbb593e329718139ab,OpenRouter50cfe7:4030514,0x0e5ff42eb7810767de756c833a042818f95052b885634a3dc3adb679a4652f48,0xd1dbc2c8ca87939cadf58e4eb91832cbe44bd5d12e3a0a0d56c76928a818e26b,0x8A774c1B73998A54ff09341f3cfF8A0010BbA7f1,0xeb8d08957ffb6da6c84ab1f89e95f4e08331fa81834e21d00754af20ec74d453,BungeeReceiver,0x1e4cf28950cc422f6b2112db77b8c0b0025c176291f984166889b8157de9dfe6,0x3e3fdfa261d7f00f53c22b4ba051f0da1903402669591bcef63d51ebe3085108,0xC914815120FA5A7e05748398C9fDf1d1b2729008,0x7c3bbaa3e26afa737955bd8665389dae8745dd537430122c9545abb09f85e34b,CalldataExecutor,0x2ac2791e955dbd2e9787486e2a4a9ba669d27cb1b642ade54a1221a5ccd53b64,0x037de760dbd500210ed5c68706a16171ec415563c6136e52d6a9930a5f53e32a
Expand Down
5 changes: 5 additions & 0 deletions deployments/prod/addresses/rise.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"RFQVaultExecutor": "0x97caca78ac2a94c67643d07843f85afaa44a3ea5",
"AcrossERC20AmountManipulator": "0x05481b7163c376ab4cb0ebc7d17f2cf7651042ee",
"MathManipulator": "0x3c3d4F3D636C06bcEb3bCc71EafE36486Ef40581"
}
14 changes: 14 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ const config: HardhatUserConfig = {
chainId: 4326,
accounts,
},
rise: {
url: process.env.RISE_RPC ?? 'https://rpc.risechain.com/',
chainId: 4153,
accounts,
},
Comment on lines +149 to +153

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 2 'RISE_RPC|RISE_ETHERSCAN_KEY|process\.env\[' \
  .env.example hardhat.config.ts scripts/deploy/networks.ts

Repository: SocketDotTech/openrouter

Length of output: 2499


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- hardhat.config.ts relevant definitions ---'
sed -n '1,45p;135,160p;245,285p' hardhat.config.ts

printf '%s\n' '--- environment and package metadata ---'
sed -n '35,48p;78,90p' .env.example
rg -n -C 3 'hardhat|`@nomicfoundation/hardhat-verify`|explorerApiKey|RISE_ETHERSCAN_KEY' package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true

printf '%s\n' '--- all RISE references ---'
rg -n -C 2 'RISE_RPC|RISE_ETHERSCAN_KEY' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' .

Repository: SocketDotTech/openrouter

Length of output: 30993


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const fallbackRpc = 'https://rpc.risechain.com/';
const fallbackExplorer = 'blockscout';
const samples = [undefined, '', '   ', 'https://custom.example/rpc'];

for (const value of samples) {
  const currentRpc = value ?? fallbackRpc;
  const currentExplorer = value ?? fallbackExplorer;
  const trimmed = value?.trim();
  const correctedRpc = trimmed || fallbackRpc;
  const correctedExplorer = trimmed || fallbackExplorer;

  console.log(JSON.stringify({
    input: value,
    currentRpc,
    currentExplorer,
    correctedRpc,
    correctedExplorer,
  }));
}
JS

python3 - <<'PY'
from pathlib import Path

text = Path("hardhat.config.ts").read_text()
assert "url: process.env.RISE_RPC ?? 'https://rpc.risechain.com/'" in text
assert "rise: process.env.RISE_ETHERSCAN_KEY ?? 'blockscout'" in text
assert "explorerApiKey(process.env.RISE_ETHERSCAN_KEY)" not in text
print("Both RISE sites use nullish coalescing directly, and the explorer site does not use the non-empty-key helper.")
PY

Repository: SocketDotTech/openrouter

Length of output: 908


Treat blank RISE variables as unset.

Use trimmed, non-empty values before applying the fallbacks at hardhat.config.ts#L149-L153 and hardhat.config.ts#L275-L275. Otherwise, a blank RISE_RPC produces an invalid RPC URL, and a blank RISE_ETHERSCAN_KEY bypasses the intended blockscout fallback.

📍 Affects 1 file
  • hardhat.config.ts#L149-L153 (this comment)
  • hardhat.config.ts#L275-L275
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hardhat.config.ts` around lines 149 - 153, Normalize the RISE environment
variables before applying fallbacks: in the rise network configuration, use a
trimmed, non-empty RISE_RPC value or the default RPC URL; likewise, use a
trimmed, non-empty RISE_ETHERSCAN_KEY value or the intended blockscout fallback
at hardhat.config.ts lines 149-153 and 275-275. Update both affected sites
consistently.

Source: Path instructions

robinhood: {
url: process.env.ROBINHOOD_RPC ?? '',
chainId: 4663,
Expand Down Expand Up @@ -267,6 +272,7 @@ const config: HardhatUserConfig = {
sei: explorerApiKey(process.env.SEI_ETHERSCAN_KEY),
soneium: explorerApiKey(process.env.SONEIUM_ETHERSCAN_KEY),
megaeth: explorerApiKey(process.env.MEGAETH_ETHERSCAN_KEY),
rise: process.env.RISE_ETHERSCAN_KEY ?? 'blockscout',
robinhood: explorerApiKey(process.env.ROBINHOOD_ETHERSCAN_KEY),
plume: explorerApiKey(process.env.PLUME_ETHERSCAN_KEY),
ethereal: explorerApiKey(process.env.ETHEREAL_ETHERSCAN_KEY),
Expand Down Expand Up @@ -453,6 +459,14 @@ const config: HardhatUserConfig = {
browserURL: 'https://mega.etherscan.io',
},
},
{
network: 'rise',
chainId: 4153,
urls: {
apiURL: 'https://explorer.risechain.com/api',
browserURL: 'https://explorer.risechain.com',
},
},
{
network: 'robinhood',
chainId: 4663,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"deploy:allowance-holder": "hardhat run scripts/deploy/deployAllowanceHolder.ts --network",
"deploy:receiver-executor": "hardhat run scripts/deploy/deployReceiverAllChains.ts",
"deploy:across-manipulator": "hardhat run scripts/deploy/deployAcrossERC20AmountManipulator.ts --network",
"deploy:math-manipulator": "hardhat run scripts/deploy/deployMathManipulator.ts --network",
"deploy:cctp-claim-executor:all": "ts-node scripts/deploy/deployCctpClaimExecutorAllChains.ts",
"deploy:rfq-vault-executor:all": "ts-node scripts/deploy/deployRFQVaultExecutorAllChains.ts",
"deploy:rfq-vault-executor": "hardhat run scripts/deploy/deployRFQVaultExecutor.ts --network",
Expand Down
12 changes: 12 additions & 0 deletions scripts/deploy/create3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export const ACROSS_MANIPULATOR_CREATE3_SALT = keccak256(
toUtf8Bytes(ACROSS_MANIPULATOR_CREATE3_SALT_TEXT),
);

/** CREATE3 salt label used by `deployMathManipulator.ts`. */
export const MATH_MANIPULATOR_CREATE3_SALT_TEXT = 'MathManipulator';

/** Keccak256 salt for deterministic MathManipulator CREATE3 deployments. */
export const MATH_MANIPULATOR_CREATE3_SALT = keccak256(
toUtf8Bytes(MATH_MANIPULATOR_CREATE3_SALT_TEXT),
);
Comment on lines +58 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

For CreateX deployCreate3(bytes32,bytes), how must a salt encode msg.sender to enable permissioned deploy protection while preserving cross-chain deterministic addresses?

💡 Result:

To enable permissioned deploy protection while using CreateX's deployCreate3, you must encode msg.sender into the first 20 bytes of the salt [1][2][3]. Because CreateX uses these bytes to verify that the transaction initiator matches the address embedded in the salt, this ensures that only the specified address can perform the deployment [3]. Regarding the preservation of cross-chain deterministic addresses, you must decide whether to enable or disable cross-chain redeploy protection using the 21st byte of the salt [2][4]: 1. To preserve the same address across all chains (disable cross-chain redeploy protection), set the 21st byte (index 20) to 0x00 [3]. 2. To use different addresses per chain (enable cross-chain redeploy protection), set the 21st byte to 0x01 [2][4]. Any value greater than 0x01 in the 21st byte will cause the transaction to revert, as CreateX enforces developer explicitness regarding these protection mechanisms [2][4]. The remaining 11 bytes (indices 21–31) are available for arbitrary entropy, such as a unique identifier, to distinguish deployments while maintaining the desired protection levels [3]. Summary of salt structure: - Bytes 0-19: msg.sender (Permissioned Deploy Protection) [3] - Byte 20: 0x00 (Same address on all chains) or 0x01 (Different address per chain) [2][3][4] - Bytes 21-31: Entropy [3]

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- tracked candidate files ---'
git ls-files \
  'scripts/deploy/create3.ts' \
  'scripts/deploy/deployMathManipulator.ts' \
  '*create*x*' \
  '*MathManipulator*' \
  '*addresses*' | sed -n '1,160p'

printf '%s\n' '--- create3.ts ---'
cat -n scripts/deploy/create3.ts | sed -n '1,140p'

printf '%s\n' '--- deployMathManipulator.ts ---'
cat -n scripts/deploy/deployMathManipulator.ts | sed -n '1,150p'

printf '%s\n' '--- CREATE3 and expected-address references ---'
rg -n -S 'MATH_MANIPULATOR_(CREATE3_SALT|EXPECTED_ADDRESS)|deployCreate3|create3Factory|hasContractBytecode|persist\(' \
  scripts test tests contracts config deployments 2>/dev/null | sed -n '1,260p'

Repository: SocketDotTech/openrouter

Length of output: 20806


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- create3 helpers and ABI ---'
cat -n scripts/deploy/create3.ts | sed -n '140,370p'

printf '%s\n' '--- comparable deployment flows ---'
for f in \
  scripts/deploy/deployCctpClaimExecutor.ts \
  scripts/deploy/deployRFQVaultExecutor.ts \
  scripts/deploy/deployEtherealExecutor.ts \
  scripts/deploy/deployCelerExecutor.ts \
  scripts/deploy/deployHypercoreExecutor.ts \
  scripts/deploy/deployAcrossERC20AmountManipulator.ts \
  scripts/deploy/deployOpenRouter.ts
do
  if [ -f "$f" ]; then
    printf '\n### %s\n' "$f"
    rg -n -A18 -B8 'existingBytecode|deployCreate3|computeFinalAddress|persist\(' "$f" | sed -n '1,180p'
  fi
done

printf '%s\n' '--- tests and documentation mentioning MathManipulator/CreateX salts ---'
rg -n -S 'MathManipulator|CREATE3_SALT|CreateX|permissioned|salt' \
  test tests scripts README.md docs 2>/dev/null | sed -n '1,260p'

Repository: SocketDotTech/openrouter

Length of output: 39974


🌐 Web query:

CreateX source _guard deployCreate3 salt first 20 bytes msg.sender bytes 20 cross-chain redeploy protection exact implementation

💡 Result:

The CreateX deployCreate3 implementation uses a specific 32-byte salt structure to enable security features like permissioned deploy protection and cross-chain redeploy protection. The salt is structured as follows [1][2]: Salt Structure (32 bytes) - Bytes 0-19: Permissioned Deploy Protection. Set these to your msg.sender (e.g., your factory address or EOA) to ensure that only you can trigger the deployment using this salt [1][3]. - Byte 20: Cross-chain Redeploy Protection. - 0x00: Disables cross-chain redeploy protection (the same address is generated on all chains) [3]. - 0x01: Enables cross-chain redeploy protection (the address will differ across chains) [1][3]. - Any value > 0x01: Causes the transaction to revert with an InvalidSalt error, enforcing developer explicitness [1][4]. - Bytes 21-31: Entropy. These 11 bytes provide room for custom entropy (e.g., keccak256 hash of a unique ID) to generate deterministic addresses [1][3]. Implementation Logic CreateX enforces these protections via internal guard functions (typically _guard). When deployCreate3 is called, the provided 32-byte salt is not used raw; it is processed through an _efficientHash (which includes msg.sender and/or block.chainid) to create a guardedSalt [1][4][5]. This prevents attackers from simply copying a salt from one chain and reusing it on another [4][5]. Best Practices - Always explicitly define the 21st byte (index 20) in your salt generation logic to avoid accidental rejections [3]. - Use the permissioned deploy protection (first 20 bytes) to prevent unauthorized parties from frontrunning your deployments with the same salt [1][2][3]. Example of Salt Generation (Solidity) function buildSalt(bytes32 uniqueId) public view returns (bytes32 salt) { salt = bytes32( abi.encodePacked( address(this), // 20 bytes: Permissioned protection hex"00", // 1 byte: 0x00 = No cross-chain protection bytes11(uint88(uint256(keccak256(abi.encodePacked(uniqueId))))) // 11 bytes: Entropy)); // Assertion ensures explicit configuration assert(uint8(salt) == 0x00); }

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from urllib.request import urlopen
urls = [
    "https://raw.githubusercontent.com/pcaversaccio/createx/main/src/CreateX.sol",
    "https://raw.githubusercontent.com/pcaversaccio/createx/main/README.md",
]
for url in urls:
    print(f"--- {url} ---")
    try:
        text = urlopen(url, timeout=10).read().decode()
    except Exception as exc:
        print(type(exc).__name__, exc)
        continue
    lines = text.splitlines()
    needles = ("function _guard", "deployCreate3", "permissioned", "cross-chain", "salt")
    for i, line in enumerate(lines):
        if any(n.lower() in line.lower() for n in needles):
            lo, hi = max(0, i-5), min(len(lines), i+15)
            print("\n".join(f"{j+1}:{lines[j]}" for j in range(lo, hi)))
            print()
PY

Repository: SocketDotTech/openrouter

Length of output: 612


🏁 Script executed:

#!/bin/bash
set -eu

for url in \
  https://raw.githubusercontent.com/pcaversaccio/createx/main/src/CreateX.sol \
  https://raw.githubusercontent.com/pcaversaccio/createx/main/README.md
do
  printf '%s\n' "--- $url ---"
  curl -ksSfL "$url" | tr -d '\r' | \
    rg -n -A24 -B12 '_guard|deployCreate3|permissioned|cross.?chain|InvalidSalt|salt' | sed -n '1,320p'
done

Repository: SocketDotTech/openrouter

Length of output: 36777


🏁 Script executed:

#!/bin/bash
set -eu

tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
curl -ksSfL https://raw.githubusercontent.com/pcaversaccio/createx/main/src/CreateX.sol > "$tmp"

printf '%s\n' '--- relevant CreateX source locations ---'
rg -n '_guard|deployCreate3|computeCreate3Address|SenderBytes|RedeployProtectionFlag' "$tmp"

printf '%s\n' '--- guard implementation and CREATE3 implementation ---'
line=$(rg -n '^    function _guard' "$tmp" | head -1 | cut -d: -f1)
if [ -n "${line:-}" ]; then
  start=$((line-12)); end=$((line+80))
  sed -n "${start},${end}p" "$tmp"
fi

line=$(rg -n 'function deployCreate3\(bytes32' "$tmp" | head -1 | cut -d: -f1)
if [ -n "${line:-}" ]; then
  start=$((line-12)); end=$((line+70))
  sed -n "${start},${end}p" "$tmp"
fi

printf '%s\n' '--- README special-feature section ---'
line=$(rg -n 'Permissioned Deploy Protection and Cross-Chain Redeploy Protection' README.tmp "$tmp" 2>/dev/null | head -1 | cut -d: -f1 || true)
rg -n -A100 -B8 'Permissioned Deploy Protection and Cross-Chain Redeploy Protection' "$tmp" | sed -n '1,180p'

Repository: SocketDotTech/openrouter

Length of output: 16099


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- MathManipulator implementation and address persistence ---'
cat -n src/manipulators/MathManipulator.sol | sed -n '1,240p'
cat -n scripts/deploy/manipulatorAddresses.ts | sed -n '1,220p'

printf '%s\n' '--- MathManipulator consumers and persisted entries ---'
rg -n -S 'MathManipulator|mathManipulator|manipulator' \
  src scripts deployments config test tests 2>/dev/null | sed -n '1,300p'

printf '%s\n' '--- deployment address records containing MathManipulator ---'
rg -n -S 'MathManipulator' deployments *.json 2>/dev/null | sed -n '1,180p'

Repository: SocketDotTech/openrouter

Length of output: 7180


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

try:
    from Crypto.Hash import keccak
except Exception as exc:
    print("PyCryptodome unavailable:", exc)
    raise SystemExit(0)

def k256(data: bytes) -> bytes:
    h = keccak.new(digest_bits=256)
    h.update(data)
    return h.digest()

def abi_encode_bytes32(value: bytes) -> bytes:
    assert len(value) == 32
    return value

def rlp_string(value: bytes) -> bytes:
    if len(value) == 1 and value[0] < 0x80:
        return value
    if len(value) <= 55:
        return bytes([0x80 + len(value)]) + value
    n = len(value).to_bytes((len(value).bit_length() + 7) // 8, "big")
    return bytes([0xb7 + len(n)]) + n + value

def rlp_list(items: list[bytes]) -> bytes:
    payload = b"".join(items)
    if len(payload) <= 55:
        return bytes([0xc0 + len(payload)]) + payload
    n = len(payload).to_bytes((len(payload).bit_length() + 7) // 8, "big")
    return bytes([0xf7 + len(n)]) + n + payload

def address_from_create3(guarded_salt: bytes) -> str:
    factory = bytes.fromhex("ba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed"[2:] if False else "ba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed")
    proxy_init_hash = bytes.fromhex("".join(re.findall(
        r"CREATE3_PROXY_INIT_CODE_HASH = keccak256\(\s*'0x([0-9a-f]+)'",
        Path("scripts/deploy/create3.ts").read_text(),
    )))
    proxy = k256(b"\xff" + factory + guarded_salt + proxy_init_hash)[12:]
    return "0x" + k256(rlp_list([rlp_string(proxy), rlp_string(b"\x01")]))[12:].hex()

salt_text = b"MathManipulator"
raw = k256(salt_text)
guarded = k256(abi_encode_bytes32(raw))
expected = "0x3c3d4F3D636C06bcEb3bCc71EafE36486Ef40581"

print("raw salt:", raw.hex())
print("raw first 20 bytes:", "0x" + raw[:20].hex())
print("raw byte 20:", f"0x{raw[20]:02x}")
print("guarded salt for CreateX Random path:", guarded.hex())
print("computed final address:", address_from_create3(guarded))
print("matches repository expected:", address_from_create3(guarded).lower() == expected.lower())
print("raw salt is permissioned for arbitrary deployer:", raw[:20] == b"\x00" * 20)
print("raw salt uses an explicit valid flag:", raw[20] in (0, 1))
PY

Repository: SocketDotTech/openrouter

Length of output: 213


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

# Minimal read-only Keccak-256 implementation for deterministic address checks.
RC = [
    1, 0x8082, 0x800000000000808A, 0x8000000080008000,
    0x808B, 0x80000001, 0x8000000080008081, 0x8000000000008009,
    0x8A, 0x88, 0x80008009, 0x8000000A,
    0x8000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003,
    0x8000000000008002, 0x8000000000000080, 0x800A, 0x800000008000000A,
    0x8000000080008081, 0x8000000000008080, 0x80000001, 0x8000000080008008,
]
ROT = [
    [0, 36, 3, 41, 18],
    [1, 44, 10, 45, 2],
    [62, 6, 43, 15, 61],
    [28, 55, 25, 21, 56],
    [27, 20, 39, 8, 14],
]
MASK = (1 << 64) - 1

def rol(x, n):
    return ((x << n) | (x >> (64 - n))) & MASK if n else x

def keccak_f(a):
    for rc in RC:
        c = [a[x] ^ a[x+5] ^ a[x+10] ^ a[x+15] ^ a[x+20] for x in range(5)]
        d = [c[(x-1) % 5] ^ rol(c[(x+1) % 5], 1) for x in range(5)]
        for x in range(5):
            for y in range(5):
                a[x + 5*y] ^= d[x]
        b = [0] * 25
        for x in range(5):
            for y in range(5):
                b[y + 5*((2*x + 3*y) % 5)] = rol(a[x + 5*y], ROT[x][y])
        for x in range(5):
            for y in range(5):
                a[x + 5*y] = b[x + 5*y] ^ ((~b[(x+1) % 5 + 5*y]) & b[(x+2) % 5 + 5*y])
        a[0] ^= rc
    return a

def k256(data):
    rate = 136
    padded = bytearray(data)
    padded.append(0x01)
    padded.extend(b"\x00" * ((rate - (len(padded) % rate) - 1) % rate))
    padded.append(0x80)
    state = [0] * 25
    for off in range(0, len(padded), rate):
        block = padded[off:off+rate]
        for i in range(rate // 8):
            state[i] ^= int.from_bytes(block[i*8:i*8+8], "little")
        keccak_f(state)
    out = b"".join(x.to_bytes(8, "little") for x in state)
    return out[:32]

assert k256(b"") .hex() == "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
assert k256(b"MathManipulator").hex() == "TODO".replace("TODO", k256(b"MathManipulator").hex())

def rlp_string(value):
    if len(value) == 1 and value[0] < 0x80:
        return value
    return bytes([0x80 + len(value)]) + value

def rlp_list(items):
    payload = b"".join(items)
    return bytes([0xc0 + len(payload)]) + payload

raw = k256(b"MathManipulator")
guarded = k256(raw)
factory = bytes.fromhex("ba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed")
init_hash_hex = re.search(
    r"CREATE3_PROXY_INIT_CODE_HASH = keccak256\(\s*'0x([0-9a-f]+)'",
    Path("scripts/deploy/create3.ts").read_text(),
).group(1)
proxy_init_hash = k256(bytes.fromhex(init_hash_hex))
proxy = k256(b"\xff" + factory + guarded + proxy_init_hash)[12:]
final = k256(rlp_list([rlp_string(proxy), rlp_string(b"\x01")]))[12:]

print("raw salt:", raw.hex())
print("raw first 20 bytes:", "0x" + raw[:20].hex())
print("raw byte 20:", f"0x{raw[20]:02x}")
print("guarded salt:", guarded.hex())
print("proxy init hash:", proxy_init_hash.hex())
print("computed final address:", "0x" + final.hex())
print("repository expected address:", "0x3c3d4F3D636C06bcEb3bCc71EafE36486Ef40581")
print("matches repository expected:", final.hex().lower() == "3c3d4f3d636c06bceb3bcc71eafe36486ef40581")
print("raw salt is permissioned for arbitrary deployer:", raw[:20] == b"\x00" * 20)
print("raw salt uses an explicit valid flag:", raw[20] in (0, 1))
PY

Repository: SocketDotTech/openrouter

Length of output: 751


Protect the MathManipulator CREATE3 salt and validate existing code.

keccak256(toUtf8Bytes('MathManipulator')) has a nonzero first 20 bytes and 0xc8 at byte 20. CreateX treats this salt as Random/Unspecified, so any caller can deploy to the derived address first. The existing-bytecode branch then accepts arbitrary code and persists that address as MathManipulator.

  • scripts/deploy/create3.ts#L58-L64: derive the salt with the deployment authority in the first 20 bytes and 0x00 at byte 20 for the same address across chains. Regenerate the expected address.
  • scripts/deploy/deployMathManipulator.ts#L40-L48: compare the existing runtime bytecode with the compiled MathManipulator bytecode or enforce contract-specific invariants before persisting.
  • scripts/deploy/deployMathManipulator.ts#L62-L79: use the protected salt and regenerated expected address for both the static call and deployment.
📍 Affects 2 files
  • scripts/deploy/create3.ts#L58-L64 (this comment)
  • scripts/deploy/deployMathManipulator.ts#L40-L48
  • scripts/deploy/deployMathManipulator.ts#L62-L79
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/deploy/create3.ts` around lines 58 - 64, Protect the MathManipulator
CREATE3 deployment and reject arbitrary pre-existing code: in
scripts/deploy/create3.ts lines 58-64, derive the salt using the deployment
authority in the first 20 bytes and 0x00 at byte 20, then regenerate the
expected address; in scripts/deploy/deployMathManipulator.ts lines 40-48,
validate existing runtime bytecode against the compiled MathManipulator bytecode
or enforce its contract-specific invariants before persisting; in
scripts/deploy/deployMathManipulator.ts lines 62-79, use the protected salt and
regenerated expected address for both the static call and deployment.

Source: Path instructions


/** CREATE3 salt label used by `deployCelerExecutor.ts`. */
export const CELER_EXECUTOR_CREATE3_SALT_TEXT = 'CelerExecutor';

Expand Down Expand Up @@ -112,6 +120,10 @@ export const OPEN_ROUTER_EXPECTED_ADDRESS =
export const ACROSS_MANIPULATOR_EXPECTED_ADDRESS =
'0x05481b7163c376ab4cb0ebc7d17f2cf7651042ee';

/** MathManipulator CREATE3 address for salt `MathManipulator` via canonical CreateX. */
export const MATH_MANIPULATOR_EXPECTED_ADDRESS =
'0x3c3d4F3D636C06bcEb3bCc71EafE36486Ef40581';

/**
* BungeeReceiver CREATE3 address for salt `BungeeReceiver` via canonical CreateX.
* Verified with {@link computeFinalAddress} (guarded salt + factory deployer), not
Expand Down
14 changes: 13 additions & 1 deletion scripts/deploy/deployAcrossERC20AmountManipulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
decodeCreate3DeploymentFromTxReceipt,
getAcrossManipulatorDeploymentStatus,
} from './create3';
import { confirm } from '../utils';
import { writeManipulatorAddress } from './manipulatorAddresses';

async function main() {
const [deployer] = await ethers.getSigners();
Expand All @@ -34,6 +34,12 @@ async function main() {
console.log(
`AcrossERC20AmountManipulator already deployed on ${networkName} at ${existing.address}`,
);
const filePath = await writeManipulatorAddress(
networkName,
'AcrossERC20AmountManipulator',
existing.address,
);
console.log('Deployment JSON:', filePath);
return;
}

Expand Down Expand Up @@ -74,6 +80,12 @@ async function main() {

console.log('\n=== Deployment Summary ===');
console.log(`AcrossERC20AmountManipulator: ${manipulatorAddress}`);
const filePath = await writeManipulatorAddress(
networkName,
'AcrossERC20AmountManipulator',
manipulatorAddress,
);
console.log('Deployment JSON:', filePath);

const chainId = (await ethers.provider.getNetwork()).chainId;
const skipVerify = process.env.SKIP_VERIFY?.trim().toLowerCase() === 'true';
Expand Down
114 changes: 114 additions & 0 deletions scripts/deploy/deployMathManipulator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Deployment script for MathManipulator via CreateX CREATE3.
*
* Usage:
* npx hardhat run scripts/deploy/deployMathManipulator.ts --network <network>
*
* Required env vars:
* DEPLOYER_PRIVATE_KEY - deployer wallet private key
*/

import hre from 'hardhat';
import { ethers } from 'hardhat';
import {
CREATE_X_FACTORY,
Create3ABI,
MATH_MANIPULATOR_CREATE3_SALT,
MATH_MANIPULATOR_EXPECTED_ADDRESS,
decodeCreate3DeploymentFromTxReceipt,
hasContractBytecode,
} from './create3';
import { writeManipulatorAddress } from './manipulatorAddresses';

async function persist(network: string): Promise<void> {
const filePath = await writeManipulatorAddress(
network,
'MathManipulator',
MATH_MANIPULATOR_EXPECTED_ADDRESS,
);
console.log('Deployment JSON:', filePath);
}

async function main() {
const [deployer] = await ethers.getSigners();
const networkName = hre.network.name;

console.log('Deployer: ', deployer.address);
console.log('Network: ', networkName);
console.log('');

const existingBytecode = await ethers.provider.getCode(
MATH_MANIPULATOR_EXPECTED_ADDRESS,
);
if (hasContractBytecode(existingBytecode)) {
console.log(
`MathManipulator already deployed on ${networkName} at ${MATH_MANIPULATOR_EXPECTED_ADDRESS}`,
);
await persist(networkName);
return;
}

const create3Factory = new ethers.Contract(
CREATE_X_FACTORY,
Create3ABI,
deployer,
);
const factory = await ethers.getContractFactory('MathManipulator');
const deployTransaction = await factory.getDeployTransaction();
if (!deployTransaction.data) {
throw new Error('MathManipulator deployment bytecode is empty');
}

const deployAddress = await create3Factory.deployCreate3.staticCall(
MATH_MANIPULATOR_CREATE3_SALT,
deployTransaction.data,
);
if (
deployAddress.toLowerCase() !==
MATH_MANIPULATOR_EXPECTED_ADDRESS.toLowerCase()
) {
throw new Error(
`CREATE3 address ${deployAddress} does not match expected ${MATH_MANIPULATOR_EXPECTED_ADDRESS}`,
);
}
console.log('Contract address will be:', deployAddress);

const deployment = await create3Factory.deployCreate3(
MATH_MANIPULATOR_CREATE3_SALT,
deployTransaction.data,
);
console.log('CREATE3 deployment tx:', deployment.hash);
const receipt = await deployment.wait();
if (!receipt || receipt.status !== 1) {
throw new Error(`MathManipulator deployment failed: ${deployment.hash}`);
}

const deployedAddress = decodeCreate3DeploymentFromTxReceipt({ receipt });
if (
!deployedAddress ||
deployedAddress.toLowerCase() !==
MATH_MANIPULATOR_EXPECTED_ADDRESS.toLowerCase()
) {
throw new Error(
`MathManipulator receipt address ${deployedAddress}, expected ${MATH_MANIPULATOR_EXPECTED_ADDRESS}`,
);
}

console.log('MathManipulator deployed to:', deployedAddress);
await persist(networkName);

const skipVerify = process.env.SKIP_VERIFY?.trim().toLowerCase() === 'true';
const chainId = (await ethers.provider.getNetwork()).chainId;
if (chainId !== 31337n && !skipVerify) {
await new Promise((resolve) => setTimeout(resolve, 5000));
await hre.run('verify:verify', {
address: deployedAddress,
constructorArguments: [],
});
}
}

main().catch((err) => {
console.error(err);
process.exit(1);
});
42 changes: 42 additions & 0 deletions scripts/deploy/manipulatorAddresses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { mkdir, readFile, writeFile } from 'fs/promises';
import { dirname, resolve } from 'path';

export type ManipulatorContractName =
| 'AcrossERC20AmountManipulator'
| 'MathManipulator';

export async function writeManipulatorAddress(
network: string,
contractName: ManipulatorContractName,
address: string,
stage = 'prod',
): Promise<string> {
const filePath = resolve(
process.cwd(),
'deployments',
stage,
'addresses',
`${network}.json`,
);
let deployments: Record<string, string> = {};

try {
deployments = JSON.parse(await readFile(filePath, 'utf8')) as Record<
string,
string
>;
} catch (err) {
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
throw err;
}
}

deployments[contractName] = address;
await mkdir(dirname(filePath), { recursive: true });
await writeFile(
filePath,
`${JSON.stringify(deployments, null, 2)}\n`,
'utf8',
);
Comment on lines +23 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file=$(git ls-files | rg '^scripts/deploy/manipulatorAddresses\.ts$')
printf '%s\n' "$file"
ast-grep outline "$file" --lang typescript
printf '\n--- file ---\n'
cat -n "$file"
printf '\n--- helper usages ---\n'
rg -n -C 3 'writeManipulatorAddress|manipulatorAddresses' scripts
printf '\n--- deployment-related files ---\n'
git ls-files scripts/deploy | sort

Repository: SocketDotTech/openrouter

Length of output: 6463


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- deployMathManipulator.ts ---'
cat -n scripts/deploy/deployMathManipulator.ts
printf '%s\n' '--- deployAcrossERC20AmountManipulator.ts ---'
cat -n scripts/deploy/deployAcrossERC20AmountManipulator.ts
printf '%s\n' '--- package scripts and deployment references ---'
rg -n -C 2 'deploy(Math|AcrossERC20AmountManipulator)|deployAcrossERC20AmountManipulator|deployMathManipulator' package.json .github scripts README.md 2>/dev/null || true
printf '%s\n' '--- existing file-lock or serialization patterns ---'
rg -n -i -C 2 'lockfile|proper-lockfile|mkdir.*lock|flock|rename\(.*tmp|writeFile.*tmp|exclusive.*flag' --glob '*.{ts,js,mjs,cjs,json}' . 2>/dev/null || true

Repository: SocketDotTech/openrouter

Length of output: 11562


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- deploymentRegistry lock implementation ---'
cat -n scripts/deploy/deploymentRegistry.ts | sed -n '55,125p'
printf '%s\n' '--- deploymentRegistry write path ---'
cat -n scripts/deploy/deploymentRegistry.ts | sed -n '350,395p'
printf '%s\n' '--- deterministic read-modify-write race probe ---'
python3 - <<'PY'
import json

initial = {}
# Both invocations complete their read before either write.
a = dict(initial)
b = dict(initial)
a["MathManipulator"] = "0xmath"
b["AcrossERC20AmountManipulator"] = "0xacross"

# The second write replaces the complete file, rather than merging with it.
first_write = json.dumps(a, sort_keys=True)
final_write = json.dumps(b, sort_keys=True)
final = json.loads(final_write)
print("first_write:", first_write)
print("final_write:", final_write)
print("lost MathManipulator:", "MathManipulator" not in final)
PY

Repository: SocketDotTech/openrouter

Length of output: 4336


Serialize updates to each deployment address file.

Concurrent runs for the same network can lose one address during the read-modify-write sequence. This should use a per-file cross-process lock around both operations. An atomic rename alone does not prevent the lost update.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/deploy/manipulatorAddresses.ts` around lines 23 - 40, Wrap the
read-modify-write sequence in the deployment address update flow around
deployments, deployments[contractName], and writeFile with a per-file
cross-process lock keyed by filePath. Acquire the lock before reading and hold
it through the write, then release it reliably on success or error while
preserving the existing ENOENT handling and output format.

Source: Path instructions

return filePath;
}
2 changes: 2 additions & 0 deletions scripts/deploy/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const RECEIVER_DEPLOY_NETWORKS: readonly ReceiverDeployNetwork[] = [
{ name: 'katana', chainId: CHAIN_IDS.KATANA, rpcEnvKey: 'KATANA_RPC', rpcFallback: 'https://rpc.katana.network' },
{ name: 'mode', chainId: CHAIN_IDS.MODE, rpcEnvKey: 'MODE_RPC', rpcFallback: 'https://1rpc.io/mode' },
{ name: 'megaeth', chainId: CHAIN_IDS.MEGAETH, rpcEnvKey: 'MEGAETH_RPC', rpcFallback: 'https://rpc.megaeth.xyz' },
{ name: 'rise', chainId: CHAIN_IDS.RISE, rpcEnvKey: 'RISE_RPC', rpcFallback: 'https://rpc.risechain.com/' },
{ name: 'robinhood', chainId: CHAIN_IDS.ROBINHOOD, rpcEnvKey: 'ROBINHOOD_RPC', rpcFallback: '' },
{ name: 'plume', chainId: CHAIN_IDS.PLUME, rpcEnvKey: 'PLUME_RPC', rpcFallback: 'https://rpc.plume.org' },
{ name: 'blast', chainId: CHAIN_IDS.BLAST, rpcEnvKey: 'BLAST_RPC', rpcFallback: 'https://blastl2-mainnet.public.blastapi.io' },
Expand Down Expand Up @@ -71,6 +72,7 @@ const RPC_ENV_ALIASES: Partial<Record<string, readonly string[]>> = {
PLASMA_RPC: ['PLASMA_RPC_URL'],
MONAD_RPC: ['MONAD_RPC_URL'],
MEGAETH_RPC: ['MEGAETH_RPC_URL'],
RISE_RPC: ['RISE_RPC_URL'],
ROBINHOOD_RPC: ['ROBINHOOD_RPC_URL'],
};

Expand Down
2 changes: 2 additions & 0 deletions scripts/deploy/openRouterBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const OPENROUTER_NETWORK_CHAIN_IDS: Record<string, number> = {
arc: CHAIN_IDS.ARC,
plasma: CHAIN_IDS.PLASMA,
tempo: CHAIN_IDS.TEMPO,
rise: CHAIN_IDS.RISE,
monad: CHAIN_IDS.MONAD,
linea: CHAIN_IDS.LINEA,
mantle: CHAIN_IDS.MANTLE,
Expand Down Expand Up @@ -83,6 +84,7 @@ export const OPENROUTER_DEPLOY_NETWORKS = [
'katana',
'mode',
'megaeth',
'rise',
'robinhood',
'plume',
'blast',
Expand Down
19 changes: 15 additions & 4 deletions scripts/deploy/verifyAllowanceHolderBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface AllowanceHolderDeploymentRecord {

type ExplorerConfig =
| { verifier: 'etherscan'; chainArg: string }
| { verifier: 'custom'; chainArg: string; verifierUrl: string }
| { verifier: 'custom'; chainArg: string; verifierUrl: string; apiKeyOptional?: boolean }
| { verifier: 'sourcify'; chainArg: string };

const ADDR_HEX_RE = /^0x[a-fA-F0-9]{40}$/;
Expand Down Expand Up @@ -63,6 +63,7 @@ const EXPLORERS_BY_CHAIN_ID = new Map<number, ExplorerConfig>([
[9745, { verifier: 'custom', chainArg: '1', verifierUrl: 'https://api.etherscan.io/v2/api?chainid=9745' }],
[98866, { verifier: 'sourcify', chainArg: '98866' }],
[4217, { verifier: 'sourcify', chainArg: '4217' }],
[4153, { verifier: 'custom', chainArg: '1', verifierUrl: 'https://explorer.risechain.com/api', apiKeyOptional: true }],
]);

function readDeploymentRecords(): AllowanceHolderDeploymentRecord[] {
Expand Down Expand Up @@ -134,12 +135,18 @@ function verifierArgs(
}

if (config.verifier === 'custom') {
args.push('--verifier-url', config.verifierUrl, '--verifier-api-key', apiKey);
args.push('--verifier-url', config.verifierUrl);
if (apiKey) {
args.push('--verifier-api-key', apiKey);
}
} else if (config.verifier === 'etherscan') {
args.push('--etherscan-api-key', apiKey);
}

if (config.verifier === 'custom' && config.chainArg === '1') {
if (
config.verifier === 'custom' &&
(config.chainArg === '1' || config.apiKeyOptional)
) {
args.push('--skip-is-verified-check');
}

Expand Down Expand Up @@ -179,7 +186,11 @@ function main() {
);
continue;
}
if (explorer.verifier !== 'sourcify' && !apiKey) {
if (
explorer.verifier !== 'sourcify' &&
!apiKey &&
!(explorer.verifier === 'custom' && explorer.apiKeyOptional)
) {
throw new Error('ETHERSCAN_API_KEY is required for non-Sourcify verification');
}

Expand Down
Loading