- Install dependencies
npm ci- 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.
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.gitPlace this repository at the root of your task repository. Network folders (e.g., mainnet, sepolia) 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/
└─ ...
Key requirements and notes:
- Networks: Supported networks are listed in
src/lib/constants.tsand currently includemainnetandsepolia. - 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.
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 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"
}
}
}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>.jsonAlternative (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>.jsonExample 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.jsonNotes:
- 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.
Use scripts/genTaskOriginSig.ts to sign task folders for origin validation. Task origin validation ensures that tasks are signed by authorized parties before execution.
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.
ottr-cliinstalled and available on PATH for signing
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)
--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 --helpSign 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 baseVerify 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.comTo 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": [ ... ]
}For tasks that involve deposit transactions to L2, you can optionally estimate the L2 gas required by enabling the --estimate-l2-gas flag.
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
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.jsonWhen 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