Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

Agent Threshold AA Scaffold

This scaffold implements the design discussed in the chat:

  • 5 backend/off-chain signers sign the same EIP-712 AgentAction digest in parallel.
  • Backend proceeds after collecting the first valid threshold, e.g. 3-of-5.
  • Only one UserOperation / transaction goes on-chain.
  • The module decodes userOp.signature, extracts agentId, loads the configured signer set, checks signer indexes, recovers signers, enforces threshold, then lets the smart account execute.
  • The same payload can be adapted to a GlobalSessionPolicyModule hook, ERC-7579 validator, ERC-1271 threshold signer, or Safe-style module.

References

  • ERC-4337: account validates UserOperation via validateUserOp; EntryPoint does not call arbitrary modules directly.
  • ERC-7579: modular smart accounts and validator modules.
  • ERC-1271: isValidSignature(bytes32,bytes) returns 0x1626ba7e when contract-based signature validation succeeds.

Project layout

contracts/
  src/AgentThresholdValidator7579.sol
  src/GlobalSessionPolicyModuleAdapter.sol
backend/
  src/index.ts
  src/agentDigest.ts
  src/packSignature.ts
  src/types.ts

Solidity module

AgentThresholdValidator7579.sol stores config per:

account => agentId => AgentConfig
account => agentId => signerIndex => signerAddress

The signature payload format is:

abi.encode(
    SignatureMode.AGENT_THRESHOLD,
    bytes32 agentId,
    AgentAction action,
    IndexedSignature[] sigs,
    bytes executionContext
)

Where:

struct IndexedSignature {
    uint8 index;
    bytes signature;
}

The module does not trust the index by itself. It uses the index only to load the expected signer, then recovers the real signer from the ECDSA signature and compares:

expectedSigner = agentSignerAt[account][agentId][index];
recoveredSigner = ECDSA.recover(digest, signature);
require(recoveredSigner == expectedSigner);

Replay protection is enforced by binding the EIP-712 digest to:

chainId
validator/module address
account
agentId
target
value
selector
calldataHash
nonce
deadline
policyHash

Backend flow

1. Backend receives action request.
2. Builds AgentAction.
3. Hashes calldata and extracts selector.
4. Sends the same EIP-712 typed data to 5 signers in parallel.
5. Collects the first 3 valid signatures.
6. Packs moduleSignature.
7. That moduleSignature becomes userOp.signature or the nested module signature, depending on your smart-account implementation.

Run locally:

cd backend
cp .env.example .env
npm install
npm run dev

Example request:

curl -X POST http://localhost:3000/agent/sign-action \
  -H 'content-type: application/json' \
  -d '{
    "account":"0xYourSmartAccount",
    "agentId":"0x1111111111111111111111111111111111111111111111111111111111111111",
    "target":"0xYourPositionManager",
    "value":"0",
    "callData":"0xabcdef01...",
    "nonce":"0",
    "policyHash":"0x2222222222222222222222222222222222222222222222222222222222222222",
    "threshold":3,
    "deadlineSeconds":60
  }'

How this reaches the module

EntryPoint will not call this module automatically. Your smart account must route validation to it. The expected path is:

Bundler
  -> EntryPoint.handleOps()
  -> SmartAccount.validateUserOp()
  -> ERC-7579 validator module / GlobalSessionPolicyModule hook
  -> threshold verification
  -> policy checks
  -> account execute()

If your account expects a wrapper signature format, wrap moduleSignature in the account-specific format.

Production notes

Do not keep all five private keys on one server in production. For a real deployment, use signer isolation:

Signer 1 -> AWS KMS
Signer 2 -> GCP KMS
Signer 3 -> Fireblocks/MPC
Signer 4 -> separate HSM/self-hosted signer
Signer 5 -> fallback signer in separate infra

This scaffold uses env private keys only to demonstrate the flow.

Security checklist

  • Use off-chain parallel signatures, not five on-chain pre-transactions.
  • Bind signatures to chainId, module address, account, agentId, nonce, deadline, policyHash, target, value, selector, and calldataHash.
  • Use short deadlines for MEV-sensitive actions.
  • Use private RPC / MEV-protected submission for swaps and rebalances.
  • Keep policy checks on-chain: allowlists, slippage, oracle skew, recipient restrictions, spender restrictions, and post-execution accounting.
  • Use Safe/timelock for signer rotation and config changes.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages