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

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.

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

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