Quick Start
- Install dependencies
- Run the server
npm run dev # or yarn dev # or pnpm dev # or bun dev
- Open http://localhost:3000 with your browser to see the result.
Task Repository Integration
To use this tool in a task repository like contract-deployments, clone this repo into the root of the task repo.
git clone https://github.com/base/task-signing-tool.git
Expected Directory Layout
Place this repository at the root of your task repository. Network folders (e.g., mainnet, sepolia, zeronet) must live alongside it, and each task must be a date-prefixed folder inside a network folder.
contract-deployments/ # your task repo root (example)
├─ task-signing-tool/ # this repo cloned here
│ ├─ src/
│ └─ ...
├─ mainnet/ # network directory
│ ├─ 2025-06-04-upgrade-foo/ # task directory (YYYY-MM-DD-task-name)
│ │ ├─ README.md # optional, used for status parsing
│ │ ├─ validations/
│ │ │ ├─ base-sc.json # config for "Base SC" user type
│ │ │ ├─ coinbase.json # config for "Coinbase" user type
│ │ │ └─ op.json # config for "OP" user type
│ │ └─ foundry-project/ # directory where you run Foundry scripts
│ │ └─ ...
│ ├─ 2025-07-12-upgrade-bar/
│ │ └─ ...
│ └─ signatures/ # signatures directory (separate from tasks)
│ ├─ 2025-06-04-upgrade-foo/ # matches task directory name
│ │ ├─ creator-signature.json
│ │ ├─ base-facilitator-signature.json
│ │ └─ base-sc-facilitator-signature.json
│ └─ 2025-07-12-upgrade-bar/
│ └─ ...
├─ sepolia/
│ ├─ 2025-05-10-upgrade-baz/
│ │ └─ ...
│ └─ signatures/
│ └─ 2025-05-10-upgrade-baz/
│ └─ ...
└─ zeronet/
├─ 2025-08-01-upgrade-qux/
│ └─ ...
└─ signatures/
└─ 2025-08-01-upgrade-qux/
└─ ...
Key requirements and notes:
- Networks: Supported networks are listed in
src/lib/constants.tsand currently includemainnet,sepolia,sepolia-alpha, andzeronet. - Task folder naming: Task directories must begin with a date prefix,
YYYY-MM-DD-, for example2025-06-04-upgrade-foo. The UI lists only folders matching that pattern. - Validation configs: For each task, place config files under
validations/named by user type in kebab-case plus.json:- "Base SC" →
base-sc.json - "Coinbase" →
coinbase.json - "OP" →
op.json
- "Base SC" →
- Task origin signatures: By default, tasks require three signature files stored in
<network>/signatures/<task-name>/:creator-signature.json,base-facilitator-signature.json, andbase-sc-facilitator-signature.json. Signatures are stored separately from task directories to avoid changing the tarball content. Use thegenTaskOriginSig.tsscript to generate these. SetskipTaskOriginValidation: truein the validation config to opt out. - Script execution: The tool executes Foundry from the task directory root (
<network>/<task>/). Ensure your Foundry project or script context is available under that path; the tool will run thecmdspecified in your validation config. Temporary outputs liketemp-script-output.txtwill be written there. - Optional README parsing: If
<network>/<task>/README.mdexists, the tool may parse it to display status and execution links.
Task README structure
When present, each task's README.md is parsed to populate the UI. Place it at <network>/<YYYY-MM-DD-slug>/README.md and follow these rules:
-
Status line (required for status display): Include a line containing
Status:within the first 20 lines. Recognized values arePENDING,READY TO SIGN, andEXECUTED(case-insensitive). IfEXECUTEDis not present andREADY TO SIGNis not present, the status is treated asPENDING. Supported formats:- Single-line with link (any one of these):
Status: EXECUTED (https://explorer/tx/0x...)Status: [EXECUTED](https://explorer/tx/0x...)Status: EXECUTED https://explorer/tx/0x...
- Multi-line links (up to 5 lines after the status line):
- First line:
Status: EXECUTED - Next lines:
Label: https://...(e.g.,Transaction: https://...,Proposal: https://...)
- First line:
- Single-line with link (any one of these):
-
Description (optional but recommended): Add a
## Descriptionsection. The first paragraph after this header is shown in the UI and should be concise (aim for ≤150 chars). Formatting like bold, italics, and inline code is stripped. -
Title (optional): You may start with a
#title. It is ignored for parsing the description fallback, but is fine for readability. -
Task name display: The UI derives the display name from the folder slug after the date (e.g.,
2025-06-04-upgrade-system-config→Upgrade System Config). Choose meaningful slugs.
Examples
Minimal pending task:
# Upgrade System Config Status: PENDING ## Description Upgrade System Config to enable feature flags for new modules.
Ready to sign:
# Upgrade System Config Status: READY TO SIGN ## Description Multisig proposal prepared; awaiting signatures from designated signers.
Executed with single-line link:
# Upgrade System Config Status: EXECUTED (https://explorer/tx/0xabc123) ## Description Executed upgrade to System Config with no parameter changes to gas config.
Executed with multiple labeled links:
# Upgrade System Config Status: EXECUTED Transaction: https://explorer/tx/0xabc123 Proposal: https://snapshot.org/#/proposal/0xdef456 ## Description Executed upgrade; links include both onchain transaction and governance proposal.
Validation file structure
Validation configs live under each task directory at <network>/<YYYY-MM-DD-slug>/validations/ and are selected by user type:
base-sc.jsonfor Base SCcoinbase.jsonfor Coinbaseop.jsonfor OP
These files must be valid JSON and conform to the schema enforced by the app. Required fields and constraints:
- cmd (string): The full forge command to execute (e.g.,
forge script script/Test.s.sol --sig 'run()' --sender 0xabc...). - ledgerId (number): Non‑negative integer Ledger account index.
- rpcUrl (string): HTTPS RPC endpoint to use for simulation.
- expectedDomainAndMessageHashes (object):
- address (0x40 hex string)
- domainHash (0x64 hex string)
- messageHash (0x64 hex string)
- stateOverrides (array): Each entry:
- name (string)
- address (0x40 hex string)
- overrides (array of objects): each with key (0x64), value (0x64), description (string)
- stateChanges (array): Each entry:
- name (string)
- address (0x40 hex string)
- changes (array of objects): each with key (0x64), before (0x64), after (0x64), description (string)
- balanceChanges (array, optional): Each entry:
- name (string)
- address (0x40 hex string)
- field (string)
- before (0x64 hex string)
- after (0x64 hex string)
- description (string)
- allowDifference (boolean)
- skipTaskOriginValidation (boolean, optional): Set to
trueto opt out of task origin signature validation. If omitted orfalse, task origin validation is enabled and signatures are required. - taskOriginConfig (object, optional but required if task origin validation is enabled):
- taskCreator (object):
- commonName (string): The email address of the task signer/creator (extracted from their certificate's Subject Alternative Name).
- taskCreator (object):
Notes:
- Sorting is not required; the tool sorts by address and storage slot for comparison.
- The tool reads
rpcUrlandledgerIddirectly from this file. - When task origin validation is enabled, three signature files must exist in
<network>/signatures/<task-name>/(see Task Origin Signing below).
Minimal example (validations/base-sc.json):
{
"cmd": "forge script script/Upgrade.s.sol --sig 'run()' --sender 0x1234567890123456789012345678901234567890",
"ledgerId": 0,
"rpcUrl": "https://mainnet.example.com",
"expectedDomainAndMessageHashes": {
"address": "0x9C4a57Feb77e294Fd7BF5EBE9AB01CAA0a90A110",
"domainHash": "0x88aac3dc27cc1618ec43a87b3df21482acd24d172027ba3fbb5a5e625d895a0b",
"messageHash": "0x9ef8cce91c002602265fd0d330b1295dc002966e87cd9dc90e2a76efef2517dc"
},
"stateOverrides": [
{
"name": "Base Multisig",
"address": "0x9855054731540A48b28990B63DcF4f33d8AE46A1",
"overrides": [
{
"key": "0x0000000000000000000000000000000000000000000000000000000000000004",
"value": "0x0000000000000000000000000000000000000000000000000000000000000001",
"description": "Lower threshold for simulation"
}
]
}
],
"stateChanges": [
{
"name": "System Config",
"address": "0x73a79Fab69143498Ed3712e519A88a918e1f4072",
"changes": [
{
"key": "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
"before": "0x000000000000000000000000340f923e5c7cbb2171146f64169ec9d5a9ffe647",
"after": "0x00000000000000000000000078ffe9209dff6fe1c9b6f3efdf996bee60346d0e",
"description": "Update implementation address"
}
]
}
],
"taskOriginConfig": {
"taskCreator": {
"commonName": "alice@example.com"
}
}
}Generate a validation file from a Foundry run
Use scripts/genValidationFile.ts to transform a Foundry run (which emits a stateDiff.json file in your task directory) into the validation JSON the app consumes.
Requirements:
- Foundry installed (
forgeon PATH) - RPC URL for the target L1 network
- Your Foundry script must write
stateDiff.jsoninto the provided--workdir
Flags:
--rpc-url, -r: HTTPS RPC URL. Used to resolvechainIdfor decoding--workdir, -w: Directory wherestateDiff.jsonis produced and where the forge command will run--forge-cmd, -f: Full forge command to execute (quoted as a single string)--ledger-id, -l(optional): Ledger account index to use in the validation JSON (defaults to 0)--out, -o(optional): Output file for the resulting JSON (defaults to stdout)--estimate-l2-gas(optional): Enable L2 gas estimation (only use for depositTransaction calls)--l2-rpc-url <url>(optional): L2 RPC URL for gas estimation (required when using--estimate-l2-gas)--l2-gas-buffer <percent>(optional): Buffer percentage to add to estimated L2 gas (defaults to 20, range: 0-100)--help, -h: Show help
General usage (tsx):
# From the task-signing-tool repo root npm ci npx tsx scripts/genValidationFile.ts \ --rpc-url https://mainnet.example \ --workdir <network>/<YYYY-MM-DD-task> \ --forge-cmd "forge script <path>:<Contract> --sig 'run()' --sender 0xabc..." \ --out <network>/<YYYY-MM-DD-task>/validations/<user-type>.json
Alternative (bun):
# From the task-signing-tool repo root npm ci bun run scripts/genValidationFile.ts \ --rpc-url https://mainnet.example \ --workdir <network>/<YYYY-MM-DD-task> \ --forge-cmd "forge script <path>:<Contract> --sig 'run()' --sender 0xabc..." \ --out <network>/<YYYY-MM-DD-task>/validations/<user-type>.json
Example from a Makefile (variables expanded in your environment):
cd "$SIGNER_TOOL_PATH" && \ npm ci && \ bun run scripts/genValidationFile.ts --rpc-url "$L1_RPC_URL" \ --workdir .. \ --forge-cmd 'forge script --rpc-url "$L1_RPC_URL" SwapOwner --sig "sign()" --sender "$SENDER"' \ --out ../validations/test.json
Notes:
- Quote the entire
--forge-cmdso that inner quotes for--sigare preserved by your shell. On macOS/Linux, prefer single quotes around the whole command and double quotes inside for signatures/addresses. --workdirtypically points to the task directory (e.g.,mainnet/2025-06-04-upgrade-foo). If you keep this repo inside the task repo root,..will refer to the task directory when running fromtask-signing-tool/.- If
--outis omitted, the JSON is printed to stdout.
Task Origin Signing
Use scripts/genTaskOriginSig.ts to sign task folders for origin validation. Task origin validation ensures that tasks are signed by authorized parties before execution.
Signature files
When task origin validation is enabled, three signature files must exist in <network>/signatures/<task-name>/:
- Task Creator:
creator-signature.json— common name is the email of the task author (e.g.,alice@example.com) - Base Facilitator:
base-facilitator-signature.json— common name isbase-facilitators(group) - Security Council Facilitator:
base-sc-facilitator-signature.json— common name isbase-sc-facilitators(group)
The common name is extracted from the signer's X.509 certificate Subject Alternative Name (SAN). For task creators, this is their email address. For facilitators, it is the group name they belong to.
Signatures are stored separately from task directories to ensure the tarball content remains unchanged after signing.
Requirements
ottr-cliinstalled and available on PATH for signing
Commands
The script supports four commands:
sign: Generate a signature for a taskverify: Verify a single signatureverify-all: Verify all three signatures (creator + both facilitators)tar: Create a deterministic tarball from a task folder (for debugging and testing)
Flags
--task-folder, -t: Required. Path to the task folder to sign/verify--signature-path, -p: Directory to store/read signatures (defaults to task folder)--facilitator, -f: Facilitator type:baseorsecurity-council. Omit to sign/verify as task creator--common-name, -c: Common name for verification (required for task creator verification)--help, -h: Show help message
Run --help for the full usage guide:
npm ci npx tsx scripts/genTaskOriginSig.ts --help
Usage examples
Sign as task creator:
npm ci npx tsx scripts/genTaskOriginSig.ts sign \ --task-folder <path>/<network>/<task> \ --signature-path <path>/<network>/signatures/<task>
Sign as Base facilitator:
npm ci npx tsx scripts/genTaskOriginSig.ts sign \ --task-folder <path>/<network>/<task> \ --signature-path <path>/<network>/signatures/<task> \ --facilitator base
Verify task creator signature:
npm ci npx tsx scripts/genTaskOriginSig.ts verify \ --task-folder <path>/<network>/<task> \ --signature-path <path>/<network>/signatures/<task> \ --common-name alice@example.com
Opting out of task origin validation
To disable task origin validation for a specific task, set skipTaskOriginValidation: true in the validation config file:
{
"cmd": "forge script script/Upgrade.s.sol --sig 'run()' --sender 0xabc...",
"ledgerId": 0,
"rpcUrl": "https://mainnet.example.com",
"skipTaskOriginValidation": true,
"expectedDomainAndMessageHashes": { ... },
"stateOverrides": [ ... ],
"stateChanges": [ ... ]
}L2 Gas Estimation for Deposit Transactions
For tasks that involve deposit transactions to L2, you can optionally estimate the L2 gas required by enabling the --estimate-l2-gas flag.
How It Works
When you enable --estimate-l2-gas:
- The tool parses the forge simulation output for the
TransactionDepositedevent - Extracts L2 transaction details from the event's
opaqueDatafield (target, value, gasLimit, data) - Uses viem's
estimateGasto estimate L2 gas via RPC call - Adds a configurable buffer (default 20%)
- The estimated gas is included in the validation JSON
Important:
- Only use this flag when your transaction emits a
TransactionDepositedevent (typically from callingdepositTransactionon OptimismPortal) - The tool automatically adds
-vvvvto the forge command to capture event output
Usage Example
npx tsx scripts/genValidationFile.ts \
--rpc-url https://mainnet.example \
--workdir mainnet/2025-06-04-my-l2-deposit \
--forge-cmd "forge script script/MyDeposit.s.sol:MyDeposit --sig 'run()' --sender 0xabc --json" \
--estimate-l2-gas \
--l2-rpc-url https://base-mainnet.example \
--l2-gas-buffer 25 \
--out validations/base-sc.jsonValidation File Output
When L2 gas estimation is enabled, the validation file will include:
{
"taskName": "...",
"...": "...",
"l2GasEstimation": {
"estimatedGas": "123456",
"buffer": 25,
"recommendedGasLimit": "154320"
}
}The recommendedGasLimit is calculated as: estimatedGas * (100 + buffer) / 100