Skip to main content

Grammar

The grammar has been designed for flexibility and expressiveness. We currently support the following operations:

Keywords

Keywords are reserved words that are dynamically interchanged for real values at evaluation time. Each field supports a different set of keywords.

Consensus

Condition

Types

The language is strongly typed which makes policies easy to author and maintain.

Primitive

Struct

The ContractArgument type, used in documentation for ABI and IDL arguments represents an enum indicating this type could be any one of the string, number, array or struct types listed in our Primitives section.

Nested structs

Activity breakdown

** Legacy features, deprecated in the latest SDKs.

Appendix

Policy evaluation

Note that our policy engine does not short circuit during evaluation. In practice, this means that if any clause within the condition field of a policy results in an error, the evaluated outcome for that policy will be an error. In such cases, consider breaking up complex policies into separate policies. For example, suppose you’re looking to construct a policy with the condition `wallet.id == ‘<wallet UUID>’ || private_key.id == ‘<private key UUID>’. This condition will always error out during evaluation, because only one of the two clauses can ever be valid: an activity cannot target both a wallet and private key. That means if you’re trying to, say, sign with a private key, then the wallet clause will fail (because a wallet isn’t being passed into the policy evaluation). Conversely, if you’re trying to sign with a wallet, the private key clause will fail for the same reason.

Activity parameters

The activity.params field exposes specific parameters based on the activity type. Most activities do not expose any parameters, but certain activities expose fields that can be used in policy conditions.

Examples

To deny signing raw payloads with the NO_OP hash function (except for EIP-712):
To allow a user to delete only themselves:

Root quorum activities

There are a select few activities that are not governed by policies, but rather by an organization’s root quorum. These activities are: ACTIVITY_TYPE_UPDATE_ROOT_QUORUM, ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE, ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE. For example, if a policy is added that allows a specific non-root user to perform ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE activities, these requests will still fail as they are subject specifically to root quorum.

Spending limits (credential and spend)

The credential and spend keywords are 0xkey extensions — they are not part of the upstream policy language. Policies that don’t reference them are unaffected.
The credential keyword exposes the credential that stamped (initiated) the activity — this complements the plural credentials consensus keyword, which lists the credentials that approved it. In 0xkey’s model there is no separate “agent” resource: an AI agent is simply a dedicated user holding an API key credential (optionally an expiring session key). Anchoring spend rules to the credential and user therefore covers agents and humans with one mechanism — the same policy shape enforces a per-session-key budget for an agent or a per-API-key cap for a human operator. The spend keyword exposes daily spend counters at two scopes:
  • spend.credential_today — accumulated against the stamping credential. Finest granularity; resets if the key is rotated.
  • spend.user_today — accumulated against the initiating user across all of their credentials. Rotation-proof: minting a fresh API key does not reset this counter, closing the “new key, new budget” loophole.
Semantics:
  • What is metered: the native-asset value of send-path activities — eth.tx.value (wei) for EVM, and the sum of native SOL transfers (lamports) for Solana managed sends. Counters are scoped per network (CAIP-2), so wei spent on Base does not count against an Ethereum-mainnet budget. Token (ERC-20 / SPL) amounts are not metered yet.
  • Window: UTC calendar day, resetting at 00:00 UTC.
  • Accounting point: counters accumulate when a transaction is signed and broadcast, not when it confirms — a transaction that later fails on-chain still counts toward the day’s budget (conservative).
  • Absence: on activities that are not metered (e.g. a message signing request), spend.credential_today / spend.user_today are absent. Budget caps are written as literals in the policy itself, so write budget checks as EFFECT_ALLOW conditions — if the counters are missing or the policy errors, the request is not allowed (fails closed).
Example — allow an agent’s session keys to spend at most 5 ETH per day on mainnet, counted across every key the agent user holds:
Example — restrict one specific session key to small transfers only:
Spend counters are maintained by the 0xkey coordinator and declared to the policy engine at evaluation time; in the current phase they are not yet covered by enclave attestation. A notarizer-endorsed spend snapshot is planned. See the design record in the enclave repository (docs/CREDENTIAL_SPEND_POLICY.md) for the full trust-boundary discussion.

Ethereum

Our Ethereum policy language (accessible via eth.tx) allows for the granular governance of signing Ethereum (EVM-compatible) transactions. Our policy engine exposes a fairly standard set of properties belonging to a transaction. See the Ethereum policy examples for sample scenarios.
The policy engine’s int type is limited to 128 bits (i128). Ethereum smart contracts support signed integers up to 256 bits (int256), but values exceeding the 128-bit signed range cannot be used in policy conditions.

EIP-712

Our policy engine supports EIP-712 typed data signing (accessible via eth.eip_712). When defining policies for EIP-712 messages, please ensure that all hex-encoded strings (e.g. addresses, function selectors, bytes) are lowercase. The policy engine expects lowercase hex strings to conform to Ethereum’s standard convention. Using uppercase hex strings may result in errors or policy rejection. Note that EIP-712 messages passed in as transactions will be normalized to lowercase.

Solana

Similarly, our Solana policy language (accessible via solana.tx) allows for control over signing Solana transactions. Note that there are some fundamental differences between the architecture of the two types of transactions, hence the resulting differences in policy structure. Notably, within our policy engine, a Solana transaction contains a list of Transfers, currently corresponding to native SOL transfers. Each transfer within a transaction is considered a separate entity. Each entity represents only top-level transfers that directly invoke supported System Program transfer instructions and do not include transfers performed indirectly inside other programs, or via unsupported System Program instructions. Similarly, the policy engine exposes solana.tx.spl_transfers, which contains only top-level SPL transfers using the supported Token and Token-2022 instructions Transfer, TransferChecked, and TransferCheckedWithFee. SPL transfers performed indirectly inside other programs or via unsupported Token and Token-2022 instructions are not detected and do not appear in this list. Here are some approaches you might take to govern transfers:
  • All transfers need to match the policy condition. Useful for allowlists (example)
  • Just one transfer needs to match the policy condition. Useful for blocklists (example)
  • Only match if there is a single transfer in the transaction, and that transfer meets the criteria (example). This is the most secure approach, and thus most restrictive.

Account address lookups

Solana transactions can reference onchain address lookup tables for account addresses. 0xkey surfaces any account address pulled from a lookup table as the literal string ADDRESS_TABLE_LOOKUP in Solana address fields (account_keys, instruction accounts, transfers, and spl_transfers). The solana.tx.address_table_lookups array indicates when lookups are present, but the specific addresses are not resolved. If you rely on allowlists or denylists of addresses, add a guard for this placeholder (for example, require solana.tx.address_table_lookups.count == 0 before comparing addresses, or explicitly deny when ADDRESS_TABLE_LOOKUP appears with something like solana.tx.transfers.any(t, t.to == 'ADDRESS_TABLE_LOOKUP')) so that dynamic lookups cannot bypass or unexpectedly fail your policy. See the address table lookup examples for examples on how to enforce this.

Program address lookups

0xkey rejects Solana transactions where program addresses are resolved via address lookup tables. Program IDs must be statically defined in the transaction to enable static analysis of instructions without requiring on-chain data lookups. If your transaction references a program via an address table lookup, the signing request will fail. Account addresses (non-program) can still be dynamically resolved via lookup tables as described above. See the Solana policy examples for sample scenarios.

Tron

Our Tron policy language (accessible via tron.tx) allows for policy control over signing Tron transactions. Our policy language supports the standard fields in a Tron transaction: https://developers.tron.network/docs/tron-protocol-transaction. To reference a Contract within a Transaction you should use tron.tx.contract[0].field_name in your policy where field_name is some field of the contract used in your transaction. While Tron only currently supports 1 contract per transaction this could change in the future, and we’re ready for it if it does! The policy engine currently supports the following Tron contract types:
  • TransferContract (TRX transfers)
  • TriggerSmartContract (Smart contract, including, but not limited to TRC-20, invocations)
  • DelegateResourceContract
  • UnDelegateResourceContract
  • FreezeBalanceV2Contract
  • UnfreezeBalanceV2Contract
  • AccountPermissionUpdateContract
See the Tron policy examples for sample scenarios.

Bitcoin

Our Bitcoin policy language (accessible via bitcoin.tx) allows for policy control over signing Bitcoin transactions. NOTE: While our SIGN TRANSACTION endpoint takes in a Partially Signed Bitcoin Transaction (PSBT) as required for signing context — our policy language supports only the standard fields inside a Bitcoin transaction: https://learnmeabitcoin.com/technical/transaction/#structure For further reference on how 0xkey handles Bitcoin transactions in our policy-enabled transaction signing flow, check out this section in our Bitcoin Network Support page. See the Bitcoin policy examples for sample Bitcoin policies.

Tempo

Our Tempo policy language (accessible via tempo.tx) allows for policy control over signing Tempo transactions. Tempo transactions natively support batched calls — a single transaction can contain one or more calls that execute atomically. The tempo.tx.calls list gives you access to each individual call, including its destination and raw input data. The tempo.tx namespace exposes the following transaction-level fields: chain_id, from, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas, fee_token, valid_before, and valid_after. Each call in tempo.tx.calls exposes: to, input, and function_signature. You can also govern Tempo transactions at the activity level using activity.params.type == 'TRANSACTION_TYPE_TEMPO', combined with other fields such as specific wallets, private keys, or consensus rules. See the Tempo policy examples for sample scenarios.