Skip to content

Add PausableSapient support to Trails intents - #367

Merged
ScreamingHawk merged 17 commits into
masterfrom
pausable
Aug 4, 2026
Merged

Add PausableSapient support to Trails intents#367
ScreamingHawk merged 17 commits into
masterfrom
pausable

Conversation

@ScreamingHawk

@ScreamingHawk ScreamingHawk commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds an optional gate leaf to CreateIntentTree/CreateIntentConfiguration via the WithGate option: when set, the calls and the sapient signer leaf (e.g. a timed-refund or gasless-deposit signer, added via WithSapientSigner) share a single gate requiring the gate leaf's co-signature — either group alone, plus that co-signature, is sufficient (an inner threshold-1 OR-nest across both groups, wrapped in an outer threshold of the gate leaf's weight + 1). mainSigner is never gated, so the owner can always act (e.g. recover funds) regardless of the gate's state. Omitting WithGate keeps the exact legacy tree shape. A sapient-only config (no calls) omits the calls side of the gate rather than producing a broken nil-tree nest.
  • Optional leaves are passed as functional options (WithGate, WithSapientSigner) instead of positional parameters; nil options are ignored. The gate leaf is threaded through GetIntentConfigurationSignature, and supplied signatures are embedded deterministically.
  • Validates the gate at construction so a misconfigured tree can't silently weaken the gate:
    • The gate leaf must be a terminal signer leaf (address or sapient) with positive weight — payload-matching (subdigest) leaves and typed-nil leaves are rejected.
    • The gate signer must not appear anywhere in the gated subtree, at any depth (checked recursively via WalletConfig.Signers()). A gated occurrence of the gate's identity — even buried in a nested leaf or node branch — would let a single signature satisfy both sides of the outer threshold, allowing the gate to authorize arbitrary payloads alone.
  • Removes the unused CreateIntentConfigurationWithTimedRefundSapient wrapper (hardcoded checkpoint=0, no production callers anywhere in the workspace — only test-only usage in this repo and in trails-watchtower).

Intended consumer: gating v1.5 intent wallet execution (calls, gasless-deposit permit, and timed-refund) behind the PausableSapient contract, so pausing it blocks all of those while owner recovery keeps working.

Test plan

  • go build ./...
  • go test . (intent-config tests, full local suite against anvil) — all pass except a pre-existing, unrelated TestWalletDeploy/v1 failure (confirmed present on master too)
  • Regression tests for gate validation: gate signer duplicated as a gated leaf (identical, different weight, inside a nested leaf, inside a node branch) is rejected; legitimate nested gated trees and same-address/different-image-hash sapient leaves still pass
  • trails-api integration validated against this branch, including against a real paused PausableSapient deployment on Base

🤖 Generated with Claude Code

The wrapper hardcodes checkpoint=0, which no real caller can use (intent
wallets need a salt-derived checkpoint), and has no production callers
anywhere in the workspace — only its own test and two trails-watchtower
test files exercise it. Callers build the timed-refund sapient leaf
directly via TimedRefundSapientImageHash and pass it to
CreateIntentConfiguration, which remains unchanged.
…Configuration

CreateIntentTree and CreateIntentConfiguration take a new payloadGateLeafNode
parameter: when set, the wallet is satisfied by [calls && payloadGateLeafNode]
signing together (a 2-of-2 subtree that caps the calls' any-address-subdigest
leaves' own, otherwise uncapped, weight) OR by sapientSignerLeafNode,
untouched. This lets a caller gate payload execution behind a revocable
signer (e.g. a pausable contract) while leaving other leaves (e.g. a
timed-refund signer) unaffected. Passing nil preserves the exact legacy tree
shape, so already-derived counterfactual addresses do not change.
@ScreamingHawk

ScreamingHawk commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

Confirming we only want the calls to be pausable (current implementation).

I think making the the deposit pausable under the same lock makes sense. If execution is blocked, there is no reason we should have deposits enabled too.

Locking the timed refund sapient with the same lock, I'm less sure about. If the argument for using the lock is "Polygon contracts may be exploitable" then it makes sense. But blocking automated refund capability is iffy. Note that in either case the owner will still have 1/1 signing power and can do whatever they want with the intent.

Update: Confirmed we gate all sapients too. Only the owner remains ungated.

CreateIntentTree/CreateIntentConfiguration now wrap sapientSignerLeafNode
in its own independent 2-of-2 subtree with payloadGateLeafNode, the same
way the calls leaves already are, when both are provided. A deposit or
timed-refund sapient leaf is a payload-execution path just like the
calls leaves, so withholding the gate leaf's signature blocks it too.
mainSigner is never gated, so the owner can always act (e.g. recover
funds) regardless of the gate's state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ScreamingHawk
ScreamingHawk marked this pull request as ready for review July 29, 2026 20:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4b4377dc4c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread intent_config.go Outdated
Comment thread intent_config.go Outdated
ScreamingHawk and others added 3 commits July 30, 2026 09:22
A sapient-only config (empty calls) left wrapPayloadGate's inner
threshold-1 node wrapping zero protected leaves, producing a NestedLeaf
with a nil Tree that panics on ImageHash or any other tree traversal.
Omit the calls gate entirely when there are no calls to gate, leaving
the separately gated sapient leaf intact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
payloadGateLeafNode is an opaque, caller-supplied v3.WalletConfigTree.
Inserted raw, a misweighted or malicious leaf (weight >= 2) could meet
wrapPayloadGate's threshold-2 requirement on its own, with the
protected leaves contributing nothing — silently collapsing "calls &&
gate" down to "gate alone". Wrap it in its own weight-1 nested leaf so
its contribution is capped regardless of its declared weight.

Also rewrites the sapient-leaf-gated signature test to check each
leaf's wiring independently instead of combining two signatures in one
BuildIntentConfigurationSignature call: once the gate leaf alone
already meets the wallet's overall threshold (via the calls gate's
auto-satisfying any-address-subdigest leaf), that combination races
BuildRegularSignature's early-cancellation against collecting the
other signer's signature, an existing behavior unrelated to this fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Share one OR-nest gate (threshold = gateWeight+1) so either group needs the
payload gate's co-signature, without separate per-leaf gates or weight-1 rejection.
@ScreamingHawk
ScreamingHawk marked this pull request as draft July 29, 2026 23:02
@ScreamingHawk
ScreamingHawk marked this pull request as ready for review July 30, 2026 00:12

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f1286b4b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread intent_config.go
Comment thread intent_config.go
Comment thread intent_config.go Outdated
Comment thread intent_config.go Outdated
Comment thread intent_config.go Outdated
Comment thread intent_config_timed_refund.go
ScreamingHawk and others added 5 commits July 31, 2026 07:12
leafWeight backs wrapPayloadGate's cap on payloadGateLeaf's contribution.
Only terminal leaf types (address, sapient signer) have a weight that
actually bounds what they contribute; a NestedLeaf's declared Weight
doesn't bound its subtree, so accepting it let a caller understate the
gate leaf's real contribution.
signingFunc matched signerSignatures by Address only, so two Signer
values sharing an address but differing in IsSapient/ImageHash (e.g. a
payload gate and a sapient signer leaf on the same contract) could
both receive the first same-address signature, leaving one leaf
signed for the wrong image hash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GetIntentConfigurationSignature previously hardcoded nil for the payload
gate leaf when building its intent configuration, so a caller with a
pausable gate configured got a signature for the wrong (ungated) wallet
config. Add the payloadGateLeafNode param, matching
CreateIntentConfiguration's signature, and update all callers.

Add subtests covering GetIntentConfigurationSignature with the gate
alone and with the gate plus a sapient signer leaf.
…natures

BuildIntentConfigurationSignature routed pre-collected signatures through
BuildRegularSignature's signing orchestrator, which cancels outstanding
signers once the config threshold looks met. Subdigest leaves report max
weight regardless of payload, so a payload gate signature arriving first
satisfied the threshold and nondeterministically dropped the sapient
signature that recovery of a non-matching payload still needs.

Add WalletConfig.BuildRegularSignatureFromSignatures, which builds the
signature tree directly from the supplied signatures with no orchestration
or cancellation, and use it in BuildIntentConfigurationSignature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add IntentConfigOption with WithPayloadGate and WithSapientSigner so
CreateIntentTree, CreateIntentConfiguration and
GetIntentConfigurationSignature take named options instead of adjacent
positional WalletConfigTree params. Future optional leaves become
additive options rather than signature breaks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6237ebec6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread intent_config.go Outdated
Comment thread intent_config.go Outdated
An untyped nil is assignable to the variadic option type, so legacy
calls passing nil for the removed positional leaf params compile and
then panicked at invocation. Skip nil options instead; nil meant "no
leaf" under the positional API and now means "no option", preserving
identical behavior for those callers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9e096336b8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread intent_config.go Outdated
ScreamingHawk and others added 3 commits July 31, 2026 12:37
A gated signer leaf sharing the gate's identity satisfies both sides of
the outer threshold with one signature, letting the gate authorize
alone. Replace leafWeight with signerLeaf, which also returns the
leaf's signer identity, and reject configs where the gate signer
appears among the gated leaves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Payload-matching leaves (subdigest and any-address-subdigest) carry no
signer and match any weight requirement, so one used as the gate would
authorize alone. Report them from signerLeaf with a signerless identity
and maxUint256 weight, and cap the gate weight at maxUint64 so they are
rejected before the threshold conversion can truncate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A typed-nil leaf pointer passes the interface nil check, so signerLeaf
dereferenced it and CreateIntentConfiguration panicked instead of
returning an invalid-gate error. Check the concrete pointer for nil in
both signer cases before reading its fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4cde50e6ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread intent_config.go Outdated
The gate co-signs more than just the payload (e.g. sapient leaves), so drop the payload- prefix from the leaf, option, and helper names.

Co-authored-by: Cursor <cursoragent@cursor.com>
Gated leaves may be nested trees, so check the gate identity against the
full recursive signer set (WalletConfig.Signers) instead of only
top-level terminal leaves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ScreamingHawk
ScreamingHawk merged commit bd1bc68 into master Aug 4, 2026
6 checks passed
@ScreamingHawk
ScreamingHawk deleted the pausable branch August 4, 2026 20:19
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.

2 participants