EVM Authorities

EVM authority support lives in the authority-evm feature and currently requires the ecc platform.

Build example:

./gradlew shadowJar -Pkeeper.features=authority-evm -Pkeeper.platforms=ecc

An EVM authority lets TKeeper parse an unsigned serialized transaction, decode configured contract calls, expose normalized effects to policy, and sign after the resulting allow decision and any approval requirements are satisfied.

Use EVM authorities for:

  • treasury transactions
  • spender approvals
  • contract-specific governed actions
  • policy inputs from AML, KYT, fraud, or business systems

Enforcement boundary

The authority should pin the intended chain and describe every contract call the policy is allowed to approve. Native intent handling fails closed when TKeeper cannot map a call to a known effect.

TKeeper signs the approved transaction. The surrounding wallet or custody service remains responsible for transaction construction, nonce and gas strategy, broadcast, replacement, receipt tracking, and settlement state. It must broadcast exactly the transaction that policy approved.

External risk verdicts must be bound to the same transaction intent; a verdict for an address or amount outside the signed transaction is only advisory metadata.

Authority config

type: evm.transaction
config:
  chainId: 1
  contracts:
    - standard: erc20
      address: "0x1111111111111111111111111111111111111111"

Custom contract calls declare their ABI signature, argument names, and effects:

config:
  chainId: 1
  contracts:
    - name: vault
      address: "0x4444444444444444444444444444444444444444"
      functions:
        - signature: "withdraw(address,uint256)"
          arguments: [to, amount]
          effects:
            - type: vault.withdraw
              fields:
                vault: "$transaction.to"
                to: "$to"
                amount: "$amount"

chainId comes from trusted config. Typed transactions must match it; legacy unsigned transactions carry no chain id.

Built-in effects:

  • native.transfer
  • erc20.transfer
  • erc20.approval
  • erc20.transferFrom

Strict CEL roots:

  • type, chainId, nonce
  • gasPrice, gasLimit, maxPriorityFeePerGas, maxFeePerGas
  • to, value, data, selector
  • transaction, call, effects

Intent validation rejects unsigned-data violations, chain mismatch, unlisted contracts or functions, calldata decoding failures, and effects that the trusted config cannot describe.

Authority example: treasury USDC transfer

This authority represents one logical action: transfer mainnet USDC from the treasury to one operating wallet. It allows transfers up to 100 USDC directly and requires one treasury approval above 100 and up to 1,000 USDC. Other recipients, effects, contracts, chains, and larger amounts fall through to DENY.

schemaVersion: verdict.authority/v1
id: evm-usdc-operating-transfer
type: evm.transaction
version: 1.0.0

metadata:
  title: Treasury USDC transfer to operating wallet

config:
  chainId: 1
  contracts:
    - standard: erc20
      address: "0x1111111111111111111111111111111111111111"

policy:
  id: evm-usdc-operating-transfer
  fallback: DENY
  approvers:
    treasury-reviewer:
      algorithm: ED25519
      publicKey64: "BASE64_ENCODED_ED25519_PUBLIC_KEY"
  variables:
    recipientAddress: "0x2222222222222222222222222222222222222222"
    automaticLimit: "100000000"
    reviewedLimit: "1000000000"
  allow:
    - id: automatic-transfer
      where:
        - "chainId == 1"
        - "effect.onlyTypes(effects, ['erc20.transfer'])"
        - "effect.one(effects, 'erc20.transfer')"
        - "effect.any(effects, 'erc20.transfer', {'to': recipientAddress})"
        - "bigint.lte(effect.amount(effects, 'erc20.transfer'), automaticLimit)"
    - id: reviewed-transfer
      where:
        - "chainId == 1"
        - "effect.onlyTypes(effects, ['erc20.transfer'])"
        - "effect.one(effects, 'erc20.transfer')"
        - "effect.any(effects, 'erc20.transfer', {'to': recipientAddress})"
        - "bigint.gt(effect.amount(effects, 'erc20.transfer'), automaticLimit)"
        - "bigint.lte(effect.amount(effects, 'erc20.transfer'), reviewedLimit)"
      approvals:
        threshold: 1
        approvers: [treasury-reviewer]
  deny: []

Replace the token contract, recipient, limits, and publicKey64 with trusted production values. A transfer to another wallet should use another authority id and document.

See Authorities for the document and policy schema and CEL Functions for policy helpers.