Skip to content

feat: read-file + concat value resolvers and safe-exec-transaction std template#22

Open
Agusx1211 wants to merge 2 commits into
masterfrom
feat/read-file-value
Open

feat: read-file + concat value resolvers and safe-exec-transaction std template#22
Agusx1211 wants to merge 2 commits into
masterfrom
feat/read-file-value

Conversation

@Agusx1211

Copy link
Copy Markdown
Member

Summary

Adds two value resolvers and one std template, closing gaps hit while building a "Shape 1" Safe relay in 0xsequence/live-contracts — a catapult job that broadcasts a fully-signed Gnosis Safe execTransaction on-chain, rather than emitting calldata for a human to paste into the Safe UI.

1. read-file value resolver — reads a file's raw contents as a value (utf8 | hex | json), resolved relative to the job/template directory. The intended home for large, opaque, per-execution operational blobs (e.g. packed multisig signatures) that don't belong in constants and aren't typed build artifacts. Gitignorable.

2. concat value resolver — explicit string join for URL/path templating. Preferred over implicit whole-string interpolation: today a {{ref}} is only resolved when it is the entire value (value.match(/^{{(.*)}}$/)), so embedding a reference inside a longer URL was sent literally and 404'd us.

3. safe-exec-transaction std template — assembles execTransaction(...) via abi-encode and broadcasts it via send-transaction. Takes a single resolved signatures argument; the caller chooses the source with read-file (offline/air-gapped) or json-request + read-json against a concat-built Safe Transaction Service URL (hosted flow). No post-execution skip condition — the desired state is the inner call's effect, which the caller gates with job-level skip_if.

Rejected: a generic "blob registry"

We considered a free-form "blob of data" store (a free-form build-info) and rejected it: build-info earns its keep by being typed/validated and referenced semantically via Contract(name); a free-form analog is just "constants, but a second bag" with no added safety. The blob problem is better served by the smallest primitive that lets a blob live in its own file — read-file.

Security (read-file)

  • No absolute paths — rejected outright.
  • Confined to the project root — resolved against the job dir, then checked with path.relative(projectRoot, resolved); anything starting with .. or absolute (escaping the root) is refused, so ../../secrets can't climb out.
  • No secret auto-discovery — only the exact named file is read; no scanning/globbing of .env/keystores. The deployer key continues to arrive via env/CLI, never through this path.

Design note

Full write-up of the options, the rejection rationale, the API, security, and the before/after live-contracts YAML: notes/read-file-and-concat-value-resolvers.md.

Test & build status

  • New tests: 218/218 passread-file (11) + concat (6) in resolver.spec.ts, and safe-exec-transaction.spec.ts (2, parses the shipped YAML and asserts the broadcast tx carries execTransaction calldata matching an independent ethers encoding).
  • Build clean (pnpm build); lint 0 errors (only pre-existing repo-wide no-explicit-any warnings).
  • Full suite: 700 pass. The ~4 engine.spec.ts failures (send-signed-transaction, test-nicks-method) are pre-existing and environmental — they fail identically on clean master; those tests broadcast/mine and the local node is a Polygon fork, not a clean instant-mining anvil. Unrelated to this change.

Final YAML shapes

# read-file
signatures:
  type: "read-file"
  arguments: { path: "signatures.hex", encoding: "hex" }

# concat
url:
  type: "concat"
  arguments:
    values: [ "{{txServiceUrl}}", "/v1/multisig-transactions/", "{{safeTxHash}}", "/" ]

# safe-exec-transaction (offline signatures)
- name: "relay"
  template: "safe-exec-transaction"
  arguments:
    safe: "{{safe_address}}"
    to: "{{target}}"
    data: "{{inner_calldata}}"
    operation: "0"
    signatures: { type: "read-file", arguments: { path: "signatures.hex", encoding: "hex" } }

🤖 Generated with Claude Code

Agusx1211 and others added 2 commits July 1, 2026 17:04
Adds two pure value resolvers to close gaps hit while building a
Safe execTransaction relay in live-contracts:

- read-file: reads a file's raw contents as a value (utf8/hex/json),
  resolved relative to the job/template dir and confined to the project
  root. The intended home for large, opaque, per-execution operational
  blobs (e.g. packed multisig signatures) that don't belong in constants
  and aren't typed build artifacts.
- concat: explicit string join for URL/path templating, avoiding the
  ambiguity of interpolating {{...}} inside longer literals (only a whole
  {{ref}} value is resolved today).

ExecutionContext gains an optional projectRoot (backwards-compatible) so
read-file can confine reads. Includes unit tests, README docs, and a
design note (notes/) covering the options considered and why a generic
blob registry is rejected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A std template that assembles and broadcasts a fully-signed Gnosis Safe
execTransaction on-chain, rather than emitting calldata for a human to
paste into the Safe UI. Built on abi-encode + send-transaction and the
new read-file/concat resolvers.

Takes a single resolved `signatures` argument; the caller chooses the
source (read-file for offline/air-gapped, or json-request + read-json
against a concat-built Safe Transaction Service URL for the hosted flow).
Catapult has no conditional/coalesce resolver, so the file-vs-service
choice deliberately lives with the caller rather than inside the template,
keeping it single-responsibility (assemble + broadcast). No post-execution
skip condition: the desired state is the inner call's effect, which the
caller gates with job-level skip_if.

Test parses the shipped YAML, runs it through the engine, and asserts the
broadcast tx carries execTransaction calldata matching an independent
ethers encoding. README documents both signature-source patterns.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant