Minimal SDK for execution-bound commitments on top of delegation-framework.
Execution intent turns "what is allowed" into "what must be executed."
Composition defines authority. Execution intent defines the action.
This repo is a TypeScript SDK for execution-bound commitments in delegated wallet / agent systems.
It is:
- a signing / verification / encoding layer for exact execution commitments
- usable from backend agents, relayers, and browser-wallet signing flows
- parity-tested against the included onchain verifier
It is not:
- a wallet
- a relayer
- a full delegation framework
- a complete browser app
It is designed to sit on top of broader delegation / policy systems when exact action binding is required.
In delegated execution systems, a smart account grants authority to an agent or relayer. An onchain enforcer contract then checks whether the submitted action is valid at redemption time.
- Wallet / delegation layer — decides whether an agent may act at all (broad policy, caveats, standing authority)
- execution-intent-sdk — binds the exact action when the risk is high (this repo)
- Relayer / executor — submits the signed payload without mutation
- Onchain verifier / enforcer — checks exact match at redemption, rejects mutation and replay
This SDK lives between the delegation layer and the relayer. It is the commitment layer.
Delegated permissions define what is allowed, but not what is executed. This creates an execution gap.
A relayer or agent constructing calldata offchain can mutate parameters within policy bounds and still pass validation. This is especially relevant for agent and relayer-based execution.
Sign exact execution intent at redemption time and enforce it on-chain.
All guarantees are committed in one EIP-712 signature:
- exact calldata (dataHash)
- authorized signer
- nonce (replay protection)
- deadline (expiry)
In the execution-bound enforcing flow, partial satisfaction is not possible. If any committed field deviates, onchain enforcement reverts.
- Broad policy approval can still allow the wrong exact calldata — a relayer can mutate parameters within policy bounds and pass validation silently.
- This SDK binds the exact action: target, calldata, signer, nonce, and deadline are all committed in one signed artifact.
- Useful for high-risk wallet and agent actions where "allowed to act" is not sufficient — you need "authorized to do exactly this."
Example: A user delegates to an agent with a policy of "swap up to 1,000 USDC." The policy permits any swap within that limit. A malicious or misconfigured relayer could route through a bad pool, set a harmful slippage, or send to the wrong recipient — and the policy check would still pass. An execution intent binds the exact calldata, target, and signer at the moment of execution, so any deviation reverts.
Published on npm. Complete at the SDK engineering layer. Remaining work is adoption and production integration, not unfinished core implementation. See What is proven here for the honest scope.
npm install execution-intent-sdk
npm install execution-intent-sdk # install from npm
npm test # unit tests (fast, no Anvil)
npm run test:all # full suite: unit + parity + onchain (starts Anvil)
npm run example:onchain:local # real deploy + exact execution proof on local chain
The parity tests (test:all) prove the SDK output is byte-for-byte compatible with the included onchain verifier.
import {
createIntent,
signIntent,
verifySignedIntent,
executionMatchesIntent,
encodeIntentArgs,
defaultDomain,
} from "execution-intent-sdk";
const domain = defaultDomain("0xYourEnforcer", 84532);
const intent = createIntent({
account: "0xYourSmartAccount", // account the execution is authorized for
target: "0xUSDC",
value: 0n,
data: "0xa9059cbb...",
nonce: 1n,
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
});
// Sign with a private key (backend / agent)
const signed = await signIntent(intent, domain, process.env.PRIVATE_KEY);
// Verify offchain
const valid = await verifySignedIntent(signed, domain);
// Check execution matches intent (mirrors on-chain enforcement)
const ok = executionMatchesIntent(intent, intent.target, intent.value, intent.data);
// Encode for on-chain submission
const args = encodeIntentArgs(intent, signed.signer, signed.signature);
For browser wallet signing, see: Signing contexts below.
Both flows enable safe third-party execution, but express the trust boundary differently.
Guarantees are stacked as separate caveats on a delegation:
- ExactExecutionEnforcer: exact calldata committed at delegation time
- IdEnforcer or NonceEnforcer: replay protection
- TimestampEnforcer: deadline
Each caveat encodes its own terms independently. The boundary is assembled at enforcement time.
The delegator signs the delegation once; enforcement happens via caveats at redemption:
npm run example:composition
Shows: delegation struct assembled, caveats encoded (ExactExecution: 224 bytes, Id: 32 bytes, Timestamp: 64 bytes), delegation signed by delegator.
When to use:
- guarantees may be reused independently
- calldata is known at delegation time
- composition flexibility matters
This is a redemption-time commitment, not a delegation-time policy.
All guarantees are bundled into one EIP-712 signed artifact at redemption time. The signing happens close to execution, not at delegation creation.
When to use:
- a specific agent must authorize exact execution
- calldata is determined near execution time, not at delegation time
- partial satisfaction weakens the trust boundary
This SDK is a good fit for:
- delegated wallet / agent systems where broad policy is not enough
- high-risk, money-moving, irreversible, or permission-changing actions
- wallet / smart-account flows that need exact action commitment
- backend or browser signing flows that need exact calldata, signer, nonce, and deadline binding
Use broader delegation / policy systems for standing authority. Use this SDK when the safety property depends on one exact action being authorized and enforced.
import { signIntent } from "execution-intent-sdk";
const signed = await signIntent(intent, domain, process.env.PRIVATE_KEY);
import { buildSigningPayload, wrapSignedIntent } from "execution-intent-sdk";
const payload = buildSigningPayload(intent, domain);
// viem WalletClient
const sig = await walletClient.signTypedData({
account: userAddress,
domain: payload.domain,
types: payload.types,
primaryType: payload.primaryType,
message: payload.message,
});
const browserSigned = wrapSignedIntent(intent, userAddress, sig);
See examples/browser-wallet/index.ts for the full integration pattern.
This is a reference integration pattern, not a full browser demo app.
The SDK provides a clean helper surface for relayer and backend workflows.
import {
prepareRelayerPayload,
validateBeforeSubmission,
buildRelayerLogEntry,
} from "execution-intent-sdk";
// Bundle everything a relayer needs to submit
const payload = prepareRelayerPayload(signed);
// payload.encodedArgs -> ABI-encoded bytes for enforcer beforeHook
// payload.intent_type -> "ExecutionBoundIntent" (routing key)
// payload.deadlineValid -> offchain deadline check
// Validate before forwarding
const check = validateBeforeSubmission(signed, intent.target, intent.value, intent.data);
if (!check.valid) {
console.error("Intent invalid:", check.reasons);
}
// Structured log entry (intent_type always first)
const log = buildRelayerLogEntry(payload);
console.log(log);
// { intent_type: "ExecutionBoundIntent", account: "0x...", signer: "0x...", ... }
This payload is the exact boundary a relayer forwards onchain.
intent_type is the routing key relayers and downstream systems use to distinguish execution-bound payloads from other delegated actions. It is always the first field — alerts, analytics, and vendors can filter by it.
Nonces are scoped to (account, signer). Any value is valid exactly once.
import { createSequentialNonceManager, randomNonce, timestampNonce } from "execution-intent-sdk";
// Sequential — for single-process agents and relayers
const nonces = createSequentialNonceManager();
const intent = createIntent({ ..., nonce: nonces.next() });
// Random — for multi-agent or concurrent flows
const intent = createIntent({ ..., nonce: randomNonce() });
// Timestamp-based — for low-frequency flows
const intent = createIntent({ ..., nonce: timestampNonce() });
For production distributed systems, coordinate nonce allocation externally. Nonce uniqueness is enforced by the onchain verifier / enforcer at redemption.
npm run example:composition # Flow A: composable, real EIP-712 + encoded caveats
npm run example:composition:real # Flow A: real delegation-framework redemption (requires execution-bound-intent repo + forge)
npm run example:intent # Flow B: full signing/verification/encoding flow
npm run example:onchain:local # Flow B onchain: one-command deploy + proof (requires Anvil)
npm run example:composition:real
Runs a real end-to-end composition flow through actual MetaMask delegation-framework contracts:
- HybridDeleGator smart account as delegator
- ExactExecutionEnforcer + TimestampEnforcer + IdEnforcer stacked as caveats
- DelegationManager.redeemDelegations as the redemption path
Four cases proven:
- Exact execution succeeds
- Mutated calldata reverts (ExactExecutionEnforcer)
- Replay reverts (IdEnforcer)
- Expired delegation reverts (TimestampEnforcer)
Prerequisites: execution-bound-intent repo cloned locally, forge installed.
This flow is real, but it is not self-contained in this package — it depends on the separate execution-bound-intent repo.
See: https://github.com/terriclaw/execution-bound-intent/blob/master/test/CompositionFlow.t.sol
Important difference from execution-intent path:
- calldata is committed at delegation time (not redemption time)
- guarantees are enforced independently by separate contracts
- no per-execution signer authorization
npm run example:onchain:local
This script:
- checks Anvil is installed
- starts Anvil automatically
- waits until ready
- deploys MinimalIntentVerifier
- proves: exact execution succeeds, mutated calldata reverts, replay reverts
- cleans up Anvil
Prerequisites: Anvil installed (foundryup), PRIVATE_KEY in .env.
npm test # unit tests only (fast, no Anvil)
npm run test:all # full suite including parity tests (starts Anvil)
62 tests across:
- sdk.test.ts — core SDK functions
- nonce.test.ts — nonce strategies and concurrency properties
- relayer.test.ts — payload shape, failure codes, log entry format
- parity.test.ts — SDK ↔ onchain byte-for-byte compatibility (requires Anvil)
The SDK encodes args compatible with ExecutionBoundCaveat / ExecutionBoundEnforcer:
const args = encodeIntentArgs(intent, signed.signer, signed.signature);
// matches: abi.decode(_args, (ExecutionIntent, address signer, bytes signature))
A minimal Solidity verifier with compiled artifact is included:
- Contract: examples/onchain/contracts/MinimalIntentVerifier.sol
- Artifact: examples/onchain/artifacts/MinimalIntentVerifier.json
Reference enforcer: https://github.com/terriclaw/execution-bound-intent
createIntent(params)
Build an ExecutionIntent. Stores raw calldata; derives dataHash on demand.
dataHash(intent)
keccak256 of intent.data. What the enforcer checks against calldata.
hashIntent(intent, domain)
EIP-712 digest. Exactly what the on-chain enforcer recomputes.
buildSigningPayload(intent, domain)
Typed data payload for wallet.signTypedData().
signIntent(intent, domain, privateKey)
Sign with a private key. Returns SignedIntent.
wrapSignedIntent(intent, signer, signature)
Wrap a pre-existing signature into a SignedIntent.
verifySignedIntent(signed, domain)
Verify a signature against the declared signer. Returns boolean.
recoverIntentSigner(intent, domain, signature)
Recover signer address from signature.
executionMatchesIntent(intent, target, value, data)
Check exact execution match. Mirrors on-chain enforcement.
isDeadlineValid(intent, nowSeconds?)
Check deadline has not passed.
encodeIntentArgs(intent, signer, signature)
ABI-encode args for enforcer beforeHook. 384 bytes for standard intent.
defaultDomain(verifyingContract, chainId?)
Convenience domain builder.
createSequentialNonceManager(start?)
In-memory sequential nonce manager.
randomNonce() / timestampNonce()
Nonce generation helpers.
prepareRelayerPayload(signed)
Bundle signed intent into relayer submission object.
validateBeforeSubmission(signed, target, value, data)
Offchain validation before forwarding. Returns { valid, reasons, codes }.
Failure codes: DEADLINE_EXPIRED, EXECUTION_MISMATCH, INVALID_SIGNATURE, NONCE_REUSE_RISK
buildRelayerLogEntry(payload)
Structured log entry with intent_type as first field.
src/
types.ts ExecutionIntent, SignedIntent, IntentDomain interfaces
eip712.ts EIP-712 type definitions, dataHash, hashIntent
intent.ts createIntent, executionMatchesIntent, isDeadlineValid, encodeIntentArgs
sign.ts signIntent, verifySignedIntent, recoverIntentSigner, buildSigningPayload, wrapSignedIntent
nonce.ts createSequentialNonceManager, randomNonce, timestampNonce
relayer.ts prepareRelayerPayload, validateBeforeSubmission, buildRelayerLogEntry
index.ts public SDK surface
examples/
composition/index.ts Flow A: real EIP-712 delegation + encoded caveats
execution-intent/index.ts Flow B: full signing/verification/encoding
browser-wallet/index.ts Browser wallet integration reference
onchain/index.ts Onchain deploy + proof flow
onchain/contracts/ MinimalIntentVerifier.sol
onchain/artifacts/ Compiled artifact (no build step needed)
scripts/
run-onchain-example.sh One-command Anvil + onchain example
test/
sdk.test.ts core SDK functions
nonce.test.ts nonce strategies and concurrency
relayer.test.ts payload shape and failure codes
parity.test.ts SDK ↔ onchain parity (requires Anvil)
This is a pattern / SDK layer on top of delegation-framework composable primitives. It does not replace composition.
Useful when exact execution intent is the trust boundary: agents, relayers, and third-party execution flows where partial satisfaction is unsafe.
- EIP-712 signing, verification, and signer recovery
- Byte-for-byte parity between SDK output and the included onchain verifier (digest, encoded args, execution matching)
- Onchain mutation rejection and replay rejection via the included
MinimalIntentVerifier - Relayer payload encoding and structured failure classification
- A real end-to-end composition flow is included via
example:composition:real, but it depends on the separateexecution-bound-intentrepo and forge - External consumer install works cleanly via
npm install execution-intent-sdk
- Production adoption or battle-tested usage
- Universal multi-framework integration beyond the included verifier/example paths
- Distributed nonce coordination (documented as caller responsibility)
- Full wallet UX integration (browser-wallet example is a reference pattern, not a running app)
- Reference enforcer: https://github.com/terriclaw/execution-bound-intent
- Design research: https://github.com/terriclaw/execution-bound-intent-global-replay
- MetaMask delegation-framework: https://github.com/MetaMask/delegation-framework