Skip to main content
Note: see the language section for more details.

Overview

Tempo transactions are fully parsed by the policy engine via the tempo.tx namespace. Tempo natively supports batched calls — a single transaction can contain multiple calls that execute atomically. The tempo.tx.calls list gives you granular access to each call’s destination, input data, and function selector.

Transaction-level policies

Allow Tempo transactions

{
  "policyName": "Allow Tempo transactions",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.action == 'SIGN' && activity.params.type == 'TRANSACTION_TYPE_TEMPO'"
}

Restrict to a specific chain ID

{
  "policyName": "Only allow Tempo testnet",
  "effect": "EFFECT_ALLOW",
  "condition": "tempo.tx.chain_id == 42431"
}

Allow specific fee token

{
  "policyName": "Only allow specific fee token",
  "effect": "EFFECT_ALLOW",
  "condition": "tempo.tx.fee_token == '0xdAC17F958D2ee523a2206206994597C13D831ec7'"
}

Deny Tempo transactions

{
  "policyName": "Deny Tempo transactions",
  "effect": "EFFECT_DENY",
  "condition": "activity.action == 'SIGN' && activity.params.type == 'TRANSACTION_TYPE_TEMPO'"
}

Cap gas limit

{
  "policyName": "Deny high gas limit",
  "effect": "EFFECT_DENY",
  "condition": "tempo.tx.gas_limit > 100000"
}

Cap max fee per gas

{
  "policyName": "Deny high max fee per gas",
  "effect": "EFFECT_DENY",
  "condition": "tempo.tx.max_fee_per_gas > 15000000000"
}

Restrict validity window

{
  "policyName": "Deny transactions with specific valid_before",
  "effect": "EFFECT_DENY",
  "condition": "tempo.tx.valid_before == 9999999999"
}

Call-level policies

Tempo transactions contain one or more calls in tempo.tx.calls. You can target individual calls by index or use quantifiers (all, any) to apply rules across all calls.

Allow calls to a specific contract

{
  "policyName": "Allow calls to approved contract",
  "effect": "EFFECT_ALLOW",
  "condition": "tempo.tx.calls[0].to == '0x40f008f4c17075EFcA092aE650655f6693AECEd0'"
}

Allow ERC-20 transfer function selector

{
  "policyName": "Allow ERC-20 transfer calls",
  "effect": "EFFECT_ALLOW",
  "condition": "tempo.tx.calls[0].function_signature == '0xa9059cbb'"
}

Deny a specific call destination

{
  "policyName": "Deny calls to blocked address",
  "effect": "EFFECT_DENY",
  "condition": "tempo.tx.calls[0].to == '0x40f008f4c17075EFcA092aE650655f6693AECEd0'"
}

Deny single-call transactions (require batching)

{
  "policyName": "Deny single-call transactions",
  "effect": "EFFECT_DENY",
  "condition": "tempo.tx.calls.count() == 1"
}

Batch call policies with quantifiers

Allow only when all calls target an approved address

{
  "policyName": "All calls must target approved address",
  "effect": "EFFECT_ALLOW",
  "condition": "private_key.id == '<PRIVATE_KEY_ID>' && tempo.tx.calls.all(call, call.to == '0x40f008f4c17075EFcA092aE650655f6693AECEd0')"
}

Allow only when all call destinations are known addresses

{
  "policyName": "All calls must target known address",
  "effect": "EFFECT_ALLOW",
  "condition": "wallet_account.address == '<SIGNER_ADDRESS>' && tempo.tx.calls.all(call, call.to == '<ALLOWED_ADDRESS>')"
}

Raw calldata inspection

Since Tempo does not support Smart Contract Interfaces (ABI parsing), you can use slicing on tempo.tx.calls[i].input to inspect encoded arguments directly. The input field is case-insensitive, so hex comparisons work regardless of casing. In standard ABI encoding, each argument occupies a 32-byte (64 hex character) word. The function selector occupies the first 4 bytes (8 hex characters, plus the 0x prefix), so the first argument word starts at position 10. For an address argument, the address value itself starts at position 34, because it is right-aligned within the 32-byte word and preceded by 12 bytes (24 hex characters) of left-padding. Each subsequent argument word starts 64 hex characters later.

Restrict ERC-20 transfer recipient via calldata

This example allows an ERC-20 transfer(address,uint256) only when the recipient (first ABI argument) matches a specific address:
{
  "policyName": "Allow ERC-20 transfer to relay address only",
  "effect": "EFFECT_ALLOW",
  "condition": "wallet_account.address == '<SIGNER_ADDRESS>' && tempo.tx.calls[0].to == '<TOKEN_CONTRACT>' && tempo.tx.calls[0].input[34..74] == '<RECIPIENT_ADDRESS_NO_0x_PREFIX>'"
}

Verify calldata recipient address

Combine calldata slicing with an address comparison to ensure the encoded recipient matches an expected address:
{
  "policyName": "Allow when encoded recipient matches expected address",
  "effect": "EFFECT_ALLOW",
  "condition": "wallet_account.address == '<SIGNER_ADDRESS>' && tempo.tx.calls[0].input[34..74] == '<RECIPIENT_ADDRESS_NO_0x_PREFIX>'"
}

Verify both from and to in transferFrom calldata

For transferFrom(address,address,uint256), the from is the first argument (positions 34..74) and to is the second (positions 98..138):
{
  "policyName": "Allow transferFrom only between known addresses",
  "effect": "EFFECT_ALLOW",
  "condition": "wallet_account.address == '<SIGNER_ADDRESS>' && tempo.tx.calls[0].input[34..74] == '<FROM_ADDRESS_NO_0x_PREFIX>' && tempo.tx.calls[0].input[98..138] == '<TO_ADDRESS_NO_0x_PREFIX>'"
}

Wallet and key scoping

These policies work the same as other blockchain types — you can combine tempo.tx conditions with wallet, private key, and consensus rules.

Allow Tempo transactions for a specific wallet

{
  "policyName": "Allow Tempo transactions for specific wallet",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.action == 'SIGN' && activity.params.type == 'TRANSACTION_TYPE_TEMPO' && wallet.id == '<WALLET_ID>'"
}

Allow Tempo transactions for a specific private key

{
  "policyName": "Allow Tempo transactions for specific private key",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.action == 'SIGN' && activity.params.type == 'TRANSACTION_TYPE_TEMPO' && private_key.id == '<PRIVATE_KEY_ID>'"
}

Require specific approver for Tempo transactions

{
  "policyName": "Require specific approver for Tempo transactions",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<USER_ID>')",
  "condition": "activity.action == 'SIGN' && activity.params.type == 'TRANSACTION_TYPE_TEMPO'"
}

Allow Tempo transactions for wallets with a specific label

{
  "policyName": "Allow Tempo transactions for wallets with specific label",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.action == 'SIGN' && activity.params.type == 'TRANSACTION_TYPE_TEMPO' && wallet.label == 'tempo-production'"
}

Allow Tempo transactions for private keys with a specific tag

{
  "policyName": "Allow Tempo transactions for private keys with tag",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.action == 'SIGN' && activity.params.type == 'TRANSACTION_TYPE_TEMPO' && private_key.tags.contains('tempo-enabled')"
}

Legacy Ethereum transactions on Tempo

During Tempo’s testnet period, legacy Ethereum transactions are also supported on Tempo and can be governed using standard Ethereum policy syntax. See the Ethereum policy examples for more details on how to write policies for these transaction types.