Skip to content

terriclaw/execution-intent-sdk

Repository files navigation

execution-intent-sdk

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.

What this repo is / is not

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.

execution-intent-sdk architecture

Where this fits in a wallet stack

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.

Problem

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.

Solution

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.

Why this matters

  • 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.


Current repo status

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.


Install

npm install execution-intent-sdk

How to evaluate this repo quickly

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.


Quick start

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.


Two approaches

Both flows enable safe third-party execution, but express the trust boundary differently.

Composable (delegation-framework style)

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

Execution Intent (this SDK)

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

Best fit use cases

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.


Signing contexts

Backend / agent (private key)

import { signIntent } from "execution-intent-sdk";

const signed = await signIntent(intent, domain, process.env.PRIVATE_KEY);

Browser wallet (MetaMask, Coinbase, etc.)

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.


Relayer / backend usage

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.


Nonce handling

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.


Running examples

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)

Real delegation-framework composition flow

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:

  1. Exact execution succeeds
  2. Mutated calldata reverts (ExactExecutionEnforcer)
  3. Replay reverts (IdEnforcer)
  4. 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

One-command local onchain flow

npm run example:onchain:local

This script:

  1. checks Anvil is installed
  2. starts Anvil automatically
  3. waits until ready
  4. deploys MinimalIntentVerifier
  5. proves: exact execution succeeds, mutated calldata reverts, replay reverts
  6. cleans up Anvil

Prerequisites: Anvil installed (foundryup), PRIVATE_KEY in .env.


Tests

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)

On-chain integration

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


API

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.

Structure

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)

Positioning

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.


What is proven here

  • 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 separate execution-bound-intent repo and forge
  • External consumer install works cleanly via npm install execution-intent-sdk

What is not proven here

  • 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)

Related

About

A minimal SDK for execution-bound commitments on top of delegation-framework primitives

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors