This page describes the attributes that a transaction policy can check, and
shows example rules for Ethereum (ETHEREUM_TX_V2 and
EIP712_TYPED_DATA_V1), Bitcoin (BITCOIN_TX_V2), and Solana
(SOLANA_TX_V1) transactions.
To learn how policies are combined, which policies are enforced, and why a transaction is denied, see Transaction policy evaluation.
Rule structure
A transaction policy is a single Cedar
permit or forbid statement over the principal, action, resource, and
context:
permit (
principal,
action == Action::"buy_transaction",
resource == Resource::"transaction_management"
)
when {
context.principal_keyring == "institutional-treasury" &&
context.schema == "ETHEREUM_TX_V2" &&
context has amount_token_scaled && context.amount_token_scaled <= 1000000000
};
Keep the following in mind:
- Attributes are at the top level of the context. Write
context.amount_token_scaled, notcontext.transaction.spend_amount_scaledas inv1alpha1. - Some attributes are optional. An optional attribute that isn't in the
context makes the policy fail to evaluate, and a policy that fails to
evaluate doesn't match. Check for an optional attribute with
hasbefore you read it, or withunless { context has ... }in aforbidrule. For more information, see Scenario 6 and Scenario 8. - Cedar strings are case-sensitive. Compare addresses in the format that the table for each chain describes.
For the principal, see
The Cedar request. For the actions
that CreateTransaction accepts (buy_transaction, sell_transaction, and
internal_transfer_transaction), see
Action and resource.
Context attributes
Each transaction's context has the attributes decoded from its payload, the
IDs of the signing key and its KeyRing, and the
metadata attributes (fiat_amount,
validity, principal_value1, and principal_value2) that the initiator
sets.
The payload attributes are the same for every blockchain. The following table describes all context attributes, and the tables for Ethereum, Bitcoin, and Solana describe how Multi-party Compute populates each attribute for that blockchain:
| Attribute | Cedar type | Always present? | Value |
|---|---|---|---|
schema |
String |
Yes | The payload format: "ETHEREUM_TX_V2", "BITCOIN_TX_V2", "SOLANA_TX_V1", or "EIP712_TYPED_DATA_V1". For more information, see Schema names. |
blockchain |
String |
Yes | "ETH", "BTC", or "SOL". |
principal_keyring |
String |
Yes | The ID of the KeyRing that holds the signing key, for example "treasury". |
principal_key |
String |
Yes | The ID of the signing key, for example "hot-wallet-1". |
secondary_key |
String |
Yes | The destination address, token or NFT recipient, approved spender or operator, or EIP-712 counterparty decoded from the payload. |
amount_token_scaled |
Long |
No | The fungible amount transferred or approved, in units of 10-9 of the asset. Omitted when no fungible amount is transferred, for example an ERC-721 operation or a contract call that sends no ETH. For more information, see Amounts. |
token_symbol |
String |
No | The symbol of the asset, for example "ETH", "BTC", "SOL", or "USDC". Omitted when the asset isn't one that Multi-party Compute recognizes, and for ERC-721 operations. |
contract_address |
String |
No | The ERC-20 or ERC-721 token contract or called contract (Ethereum), the verifying contract (EIP-712), or the mint (Solana). Omitted for native transfers. |
token_standard |
String |
No | "ERC721" for ERC-721 transfers and approvals (ETHEREUM_TX_V2). Omitted for native transfers and fungible token operations. |
token_id |
String |
No | The 256-bit ERC-721 tokenId as a base-10 string (ETHEREUM_TX_V2). Omitted for setApprovalForAll and for transactions that aren't ERC-721. |
from_address |
String |
No | The current owner (from) in ERC-721 transferFrom and safeTransferFrom calls, in mixed-case EIP-55 checksum format. |
approve_all |
Bool |
No | The approved flag in ERC-721 setApprovalForAll(operator, approved) calls: true when granting operator approval, false when revoking it. |
deadline |
Long |
No | The expiration time of an EIP712_TYPED_DATA_V1 message, in seconds since the Unix epoch, from its deadline, sigDeadline, or validBefore field. |
eip712 |
Record |
No | The structured EIP-712 payload: primary_type, domain, and message, with 256-bit integers as base-10 strings. EIP712_TYPED_DATA_V1 only. |
chain_id |
Long |
No | The EVM chain ID, for example 1 for Ethereum Mainnet or 11155111 for Sepolia. Ethereum and EIP-712 only. |
max_fee_atomic |
Long |
Yes | The most that the transaction can pay in network fees, in units of 10-9 of the blockchain's native coin. 0 for EIP712_TYPED_DATA_V1. |
payload |
String |
Yes | The hex-encoded digest of the payload, in lowercase. |
time_stamp |
Long |
Yes | The transaction's createTime, in seconds since the Unix epoch. |
key_lists |
Record of Set<String> |
Yes | A record mapping each KeyList ID in the instance to the Set<String> of blockchain addresses in that KeyList, resolved at createTime. An empty record when the instance has no KeyLists. Read a KeyList with bracket syntax, for example context.key_lists["trusted-destinations"]. For more information, see KeyLists in the Cedar context. |
principal_keyring and principal_key are IDs, not full resource names. A
key ID is unique only within its KeyRing. Check principal_keyring to apply a
rule to every key in a KeyRing, or both attributes to name one key:
context.principal_keyring == "treasury" && context.principal_key == "hot-wallet-1"
If the payload can't be decoded by the parser for Transaction.schema, or
can't be projected into a context, no policy is evaluated and the transaction
is denied with UNRECOGNIZED_PAYLOAD. For more information, see
Deny reasons.
Schema names
The schema attribute is the name of the PayloadSchema value in the
v1beta API: "ETHEREUM_TX_V2", "BITCOIN_TX_V2", "SOLANA_TX_V1", or
"EIP712_TYPED_DATA_V1". The lowercase names that v1alpha1 rules use, such
as "ethereum.transaction.v2", never match in v1beta.
A rule without a schema or blockchain check applies to every blockchain.
To limit a rule to one blockchain, check schema.
Amounts
amount_token_scaled and max_fee_atomic use the same scale for every asset:
1 unit is 10-9 of the asset, so 1.0 of any asset is 1000000000.
To get the value for a rule, multiply the amount by 1,000,000,000
(109). The following table shows some examples:
| Amount | amount_token_scaled |
|---|---|
| 0.1 ETH | 100000000 |
| 1.0 ETH, 1.0 BTC, 1.0 SOL, or 1.0 USDC | 1000000000 |
| 0.0025 BTC (250,000 satoshis) | 2500000 |
| 1.5 SOL (1,500,000,000 lamports) | 1500000000 |
| 5,000 USDC | 5000000000000 |
For assets with more than 9 decimal places, such as ETH, the part of the amount smaller than 10-9 is dropped. A rule can't check it.
Ethereum
The following table describes the context attributes for ETHEREUM_TX_V2
payloads (RLP-encoded legacy and EIP-2718 transactions, including EIP-1559):
| Attribute | Value |
|---|---|
secondary_key |
For ETH transfers and contract calls, the transaction's to address. For ERC-20 transfer and transferFrom, and ERC-721 transferFrom and safeTransferFrom, the token or NFT recipient. For ERC-20 and ERC-721 approve, the approved spender. For ERC-721 setApprovalForAll, the operator. In mixed-case EIP-55 checksum format. |
amount_token_scaled |
For ETH transfers and ERC-20 transfers and approvals, the amount. For contract calls, the ETH sent with the call; omitted if no ETH is sent. Always omitted for ERC-721 transfers and approvals, so that fungible spending limits don't match NFT token IDs. |
token_symbol |
"ETH" for ETH transfers, and for contract calls that send ETH. For recognized ERC-20 tokens on Ethereum Mainnet, "USDC", "USDT", "DAI", or "WBTC"; omitted for other tokens and for ERC-721 operations. |
contract_address |
For ERC-20 and ERC-721 transfers and approvals, the token or NFT contract. For contract calls, the contract called. In mixed-case EIP-55 checksum format. Omitted for ETH transfers. |
token_standard |
"ERC721" for ERC-721 transfers and approvals. Omitted for ETH transfers, ERC-20 operations, and other contract calls. |
token_id |
For ERC-721 transferFrom, safeTransferFrom, and approve, the 256-bit tokenId as a base-10 String, so that NFT IDs never overflow Cedar's 64-bit Long. Because it's a String, compare it for equality with == or in [...]; < and > compare strings, not numbers. Omitted for setApprovalForAll and for transactions that aren't ERC-721. |
from_address |
For ERC-721 transferFrom and safeTransferFrom, the current owner (from), in mixed-case EIP-55 checksum format. |
approve_all |
For ERC-721 setApprovalForAll(operator, approved), the approved flag as a Bool: true when granting the operator approval over every NFT in the collection, false when revoking it. |
chain_id |
The chain ID from the transaction. Can be omitted on legacy transactions, so check it with has. |
max_fee_atomic |
Gas limit × maximum fee per gas (or gas price for legacy transactions), in gwei. |
For ERC-20 transfer(address, uint256) (0xa9059cbb), whose selector is
unique to ERC-20, a token that Multi-party Compute doesn't recognize has its
amount scaled as if the token had 18 decimal places. Match recognized tokens
by contract_address, and don't set amount limits on unrecognized tokens
whose decimals aren't 18. ERC-20 transferFrom and approve share their
selectors with ERC-721, so they need a recognized contract; a call to an
unrecognized contract with one of these selectors is denied with
UNRECOGNIZED_PAYLOAD. For more information, see
ERC-721 non-fungible tokens.
The context doesn't say which kind of Ethereum transaction it is, so keep the following in mind:
- Contract calls that send ETH look like ETH transfers. Both have
token_symbol"ETH"and anamount_token_scaled. Only a contract call hascontract_address. To allow only ETH transfers, add!(context has contract_address). - ERC-20 approvals look like ERC-20 transfers. An
approvehas the samecontract_address,amount_token_scaled, andsecondary_key(the spender) as a transfer of the same amount to the same address. A policy that permits the transfer also permits the approval.
Contract creation transactions (with an empty to address) can't be decoded
and are denied with UNRECOGNIZED_PAYLOAD.
ERC-721 non-fungible tokens
Under ETHEREUM_TX_V2, Multi-party Compute decodes the following ERC-721
methods:
safeTransferFrom(address from, address to, uint256 tokenId)(0x42842e0e)safeTransferFrom(address from, address to, uint256 tokenId, bytes data)(0xb88d4fde)transferFrom(address from, address to, uint256 tokenId)(0x23b872dd)approve(address to, uint256 tokenId)(0x095ea7b3)setApprovalForAll(address operator, bool approved)(0xa22cb465)
ERC-20 and ERC-721 share the same 4-byte selectors for transferFrom
(0x23b872dd) and approve (0x095ea7b3). To decide whether the uint256
argument is a fungible amount or a token ID, Multi-party Compute looks up the
to contract address in its registry of recognized token contracts. For
ERC-721 calls, amount_token_scaled is omitted, token_standard is
"ERC721", and token_id is a base-10 String. A call to an unrecognized
contract with one of these shared selectors is denied with
UNRECOGNIZED_PAYLOAD.
EIP-712 typed data
For EIP712_TYPED_DATA_V1 payloads (UTF-8 JSON-encoded EIP-712 typed data
with types, primaryType, domain, and message), Multi-party Compute
validates the JSON structure and computes the 32-byte EIP-712 signing digest,
keccak256(0x1901 ++ domainSeparator ++ hashStruct(message)), where ++ is
byte concatenation. Validation
rejects nesting deeper than 15 levels, cyclic types, null array elements,
and domain fields that don't match types.EIP712Domain, and preserves
256-bit integers without floating-point precision loss.
An EIP712_TYPED_DATA_V1 payload projects the following attributes:
| Attribute | Value |
|---|---|
blockchain |
Always "ETH". |
schema |
Always "EIP712_TYPED_DATA_V1". |
contract_address |
domain.verifyingContract, in mixed-case EIP-55 checksum format. Omitted when the domain doesn't set verifyingContract. |
chain_id |
domain.chainId as a Long. Omitted when the domain doesn't set chainId. |
max_fee_atomic |
Always 0, because signing typed data doesn't pay a network fee. |
secondary_key |
For recognized message types, the counterparty: spender for ERC-2612 Permit and Uniswap Permit2, or to for ERC-3009 TransferWithAuthorization and Safe SafeTx. For other message types, domain.verifyingContract. |
amount_token_scaled |
For recognized message types (Permit, Permit2, TransferWithAuthorization, and SafeTx), the authorized token or ETH value (value or details.amount), in units of 10-9. Omitted for other message types. |
token_symbol |
"USDC", "USDT", "DAI", or "WBTC" when the verifying contract or the Permit2 token (details.token) is a recognized token on Ethereum Mainnet. Omitted otherwise. |
deadline |
The expiration time of the message, in seconds since the Unix epoch, when the message has a deadline, sigDeadline, or validBefore field. Omitted otherwise. |
eip712 |
A Record with the typed data. For more information, see the following table. |
The eip712 record has the following fields:
| Field | Cedar type | Value |
|---|---|---|
context.eip712.primary_type |
String |
The primaryType name, for example "Permit", "PermitSingle", "TransferWithAuthorization", or "SafeTx". |
context.eip712.domain |
Record |
The EIP712Domain fields that the payload sets: name, version, chainId, verifyingContract, and salt. |
context.eip712.message |
Record |
The message fields. 256-bit integer values (uint256 and int256) are base-10 Strings, so rules can match exact values without 64-bit overflow. |
Bitcoin
The following table describes the context attributes for BITCOIN_TX_V2
payloads (BIP 174 PSBT v0):
| Attribute | Value |
|---|---|
secondary_key |
The address of the one output that isn't change. For a consolidation, where every output is change, the first output's address. |
amount_token_scaled |
The amount sent to secondary_key, not counting change (1 satoshi = 10). 0 for a consolidation. |
token_symbol |
Always "BTC". |
contract_address |
Never present. |
chain_id |
Never present, because a PSBT doesn't say which network it's for. Addresses are always formatted as Mainnet addresses, so a Testnet address also starts with bc1, 1, or 3. The address prefix shows the address type, not the network. |
max_fee_atomic |
The miner fee: total inputs minus total outputs (1 satoshi = 10). |
To keep Bitcoin networks apart, use different keys for each network, for
example in a KeyRing for Mainnet and another for Testnet, and scope rules to
them with principal_keyring or principal_key. The same key can sign on both
networks, so this only works if you never use a Mainnet key on Testnet.
A transaction that sends to more than one address that isn't change can't be
decoded. A transaction with no address to send to, such as one whose only
output that isn't change is an OP_RETURN output, can't be evaluated.
Solana
The following table describes the context attributes for SOLANA_TX_V1
payloads (unsigned legacy or v0 Solana messages):
| Attribute | Value |
|---|---|
secondary_key |
For SOL transfers, the destination wallet. For token transfers, the destination token account, not the wallet that owns it. In base58. |
amount_token_scaled |
The amount. For SOL, the number of lamports (1 lamport = 1). For tokens, the raw amount scaled with the decimals that the TransferChecked instruction carries. |
token_symbol |
"SOL" for SOL transfers. For tokens, "USDC", "USDT", or "PYUSD"; omitted for other mints. |
contract_address |
For token transfers, the mint address, in base58. Omitted for SOL transfers. |
chain_id |
Never present. Solana messages don't name a cluster. |
max_fee_atomic |
The most that the whole transaction can pay in lamports: 5,000 for each required signature, plus the priority fee, ⌈compute unit limit × compute unit price in micro-lamports ÷ 106⌉, if the transaction sets a compute unit price. |
A Solana transaction can contain several instructions:
- Transfer instructions: System Program
Transfer(SOL), and SPL Token or Token-2022TransferChecked. The uncheckedTransferinstruction, and tokens with more than 9 decimal places, can't be decoded. - Other instructions: ComputeBudget
SetComputeUnitLimitandSetComputeUnitPrice, Memo (v1 and v2), Associated Token AccountCreateandCreateIdempotent, and System ProgramAdvanceNonceAccount. These instructions don't produce a context of their own.
Each transfer instruction is evaluated separately with its own context. The
transaction is permitted only if every transfer is permitted; if any transfer
is denied, the whole transaction is denied. max_fee_atomic is the fee for
the whole transaction and is the same in every context, so don't add it up
across transfers.
If the transaction sets a compute unit price without a compute unit limit, the fee uses the Solana default limit of 200,000 compute units for each instruction other than ComputeBudget instructions, up to 1,400,000.
The transaction's signers must match the accounts that each instruction needs
to sign, and the source and destination accounts must be writable. v0 messages
that use address lookup tables can't be decoded. Pass the unsigned message
bytes, not a signed transaction envelope, in Transaction.payload and
Transaction.rawMessage.
The recognized mints are the Solana Mainnet mints. On Devnet or Testnet, a
token transfer has no token_symbol, so check contract_address instead.
An Associated Token Account Create instruction funds the new account's
rent-exempt reserve in SOL. That amount isn't in amount_token_scaled.
Example rules
The following examples are permit and forbid policies that you can adapt.
Each example checks every optional attribute with has before it reads it.
Buy limit for a KeyRing using payload and metadata
Permit buy_transaction transfers of up to 500,000 USDC on Ethereum Mainnet
from any key in the institutional-treasury KeyRing, when the initiator
labels the transaction with "equities-desk", sets a validity of at most 300
seconds, and states a value of at most $500,000:
permit (
principal,
action == Action::"buy_transaction",
resource == Resource::"transaction_management"
)
when {
context.principal_keyring == "institutional-treasury" &&
context.schema == "ETHEREUM_TX_V2" &&
context has chain_id && context.chain_id == 1 &&
context has contract_address &&
context.contract_address == "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" &&
context has amount_token_scaled && context.amount_token_scaled <= 500000000000000 &&
context has fiat_amount && context.fiat_amount <= 500000 &&
context has validity && context.validity <= 300 &&
context has principal_value1 && context.principal_value1 == "equities-desk"
};
Multi-party Compute doesn't check fiat_amount against the payload. For more
information, see Context attributes.
Spending limit for one key
Permit ETH transfers of up to 1 ETH from one key on Ethereum Mainnet.
!(context has contract_address) excludes contract calls that send ETH:
permit (principal, action, resource)
when {
context.principal_keyring == "treasury" &&
context.principal_key == "hot-wallet-1" &&
context.schema == "ETHEREUM_TX_V2" &&
context has chain_id && context.chain_id == 1 &&
context has token_symbol && context.token_symbol == "ETH" &&
!(context has contract_address) &&
context has amount_token_scaled && context.amount_token_scaled <= 1000000000
};
Stablecoin allowlist on Ethereum
Permit transfers of up to 5,000 USDC to one address, and approvals
(allowances) of up to 5,000 USDC for that address. The policy checks
contract_address rather than token_symbol, so a token from another
contract that uses the same symbol doesn't match:
permit (principal, action, resource)
when {
context.schema == "ETHEREUM_TX_V2" &&
context has contract_address &&
context.contract_address == "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" &&
context.secondary_key == "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" &&
context has amount_token_scaled && context.amount_token_scaled <= 5000000000000
};
token_symbol is "USDC" for this transaction.
ERC-721 transfer and operator approval guard
Permit transferring one tokenized treasury note (token_id == "1042") from an
approved ERC-721 contract to a settlement address, and forbid granting
collection-wide operator approvals (setApprovalForAll with
approve_all == true).
Policy A permits the one NFT transfer:
permit (
principal,
action == Action::"internal_transfer_transaction",
resource == Resource::"transaction_management"
)
when {
context.schema == "ETHEREUM_TX_V2" &&
context has chain_id && context.chain_id == 1 &&
context has token_standard && context.token_standard == "ERC721" &&
context has contract_address &&
context.contract_address == "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D" &&
context has token_id && context.token_id == "1042" &&
context.secondary_key == "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
};
Policy B forbids collection-wide operator approvals:
forbid (principal, action, resource)
when {
context.schema == "ETHEREUM_TX_V2" &&
context has approve_all && context.approve_all == true
};
EIP-712 gasless USDC permit limit
Permit signing an off-chain ERC-2612 Permit for up to 25,000 USDC on
Ethereum Mainnet for an approved settlement router, when the permit expires
before a given time:
permit (
principal,
action == Action::"buy_transaction",
resource == Resource::"transaction_management"
)
when {
context.schema == "EIP712_TYPED_DATA_V1" &&
context has chain_id && context.chain_id == 1 &&
context has contract_address &&
context.contract_address == "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" &&
context has eip712 && context.eip712.primary_type == "Permit" &&
context.secondary_key == "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" &&
context has amount_token_scaled && context.amount_token_scaled <= 25000000000000 &&
context has deadline && context.deadline <= 1793491200
};
Bitcoin spending limit and fee cap
Permit transfers of up to 0.0025 BTC with a miner fee of up to 10,000 satoshis, from keys in a KeyRing that you use only on Bitcoin Mainnet:
permit (principal, action, resource)
when {
context.schema == "BITCOIN_TX_V2" &&
context.principal_keyring == "btc-mainnet" &&
context has amount_token_scaled && context.amount_token_scaled <= 2500000 &&
context.max_fee_atomic <= 100000
};
SOL transfer and priority fee limit
Permit SOL transfers of up to 1.5 SOL when the total fee, including any priority fee, is at most 50,000 lamports (0.00005 SOL):
permit (principal, action, resource)
when {
context.schema == "SOLANA_TX_V1" &&
context has token_symbol && context.token_symbol == "SOL" &&
!(context has contract_address) &&
context has amount_token_scaled && context.amount_token_scaled <= 1500000000 &&
context.max_fee_atomic <= 50000
};
SPL token or Token-2022 transfer on Solana
Permit transfers of up to 250 USDC to one token account. The same rule matches the SPL Token and Token-2022 programs, because the context doesn't say which program the transfer uses:
permit (principal, action, resource)
when {
context.schema == "SOLANA_TX_V1" &&
context has contract_address &&
context.contract_address == "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" &&
context.secondary_key == "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" &&
context has amount_token_scaled && context.amount_token_scaled <= 250000000000
};
secondary_key is the recipient's token account for this mint. To allow a
recipient wallet, list its token account for each mint that you allow. In a
transaction with several transfers, this rule must match every transfer.
Unrecognized asset block
Deny any transfer whose asset Multi-party Compute doesn't recognize. This forbid
policy only uses has, so it can't fail to evaluate:
forbid (principal, action, resource)
when { context has amount_token_scaled }
unless { context has token_symbol };
Fee cap for a blockchain
max_fee_atomic is always present, so this forbid policy can't fail to
evaluate. Because the scale is 10-9 of each native coin, the same
number is a different value on each blockchain. Scope the policy to one
blockchain:
forbid (principal, action, resource)
when { context.blockchain == "ETH" && context.max_fee_atomic > 10000000 };
This denies Ethereum transactions that can pay more than 0.01 ETH in fees.
KeyRings
A KeyRing is a static container that groups MPC keys,
for example "institutional-treasury" or "btc-cold-storage". Each key is
created inside a single KeyRing and stays in that KeyRing permanently.
KeyRings participate in policies in three ways:
- KeyRing
KeyPolicy(signing quorum). When you create a KeyRing, you set its immutable KeyPolicy, which defines the threshold tree of teams and users that hold key shares and must approve before any key in the KeyRing can sign. Transaction policies work alongside theKeyPolicy: the transaction policy evaluates the business rules for the payload (PERMITorDENY), while the KeyRing'sKeyPolicygoverns whose key shares must authorize the signing ceremony. Scoping transaction policies by signing KeyRing (
context.principal_keyring). Every transaction's context includescontext.principal_keyring, set to the ID of the KeyRing that holds the signing key, andcontext.principal_key, set to the ID of the signing key. A rule withcontext.principal_keyring == "institutional-treasury"applies to every key in the KeyRing, including keys created in that KeyRing later, without creating or approving a new policy:permit ( principal, action == Action::"internal_transfer_transaction", resource == Resource::"transaction_management" ) when { context.principal_keyring == "institutional-treasury" && context has fiat_amount && context.fiat_amount <= 100000 };Destination KeyRing allowlist (in a KeyList). You can also add a KeyRing as an entry in a KeyList, so that transfers to the blockchain address of any active key in that KeyRing, such as internal sweeps between hot and cold KeyRings, are permitted automatically as new keys are created. For an example, see KeyList allowlist and blocklist.
KeyList allowlist and blocklist
Permit transfers of up to 1 ETH only to addresses in the trusted-destinations
KeyList. context.key_lists["trusted-destinations"] is the set of addresses
that the KeyList expands to at the transaction's createTime, so adding or
removing an entry changes what this rule permits without a new policy:
// Assuming you created a KeyList with ID "trusted-destinations":
permit (principal, action, resource)
when {
context.schema == "ETHEREUM_TX_V2" &&
context has token_symbol && context.token_symbol == "ETH" &&
!(context has contract_address) &&
context.key_lists["trusted-destinations"].contains(context.secondary_key) &&
context has amount_token_scaled && context.amount_token_scaled <= 1000000000
};
Deny any transaction to an address in the blocked-addresses KeyList, on
every blockchain. key_lists and secondary_key are always present, and
CreateTransactionPolicy verifies that the KeyList exists, so this forbid
policy can't fail to evaluate:
// Assuming you created a KeyList with ID "blocked-addresses":
forbid (principal, action, resource)
when { context.key_lists["blocked-addresses"].contains(context.secondary_key) };
Write the KeyList ID in bracket syntax, context.key_lists["..."], rather
than dot syntax. Dot syntax fails to parse for IDs that contain a hyphen or
start with a digit. Entries are compared as strings, so add manual entries in
the address format that the table for each blockchain describes. For more
information, see
KeyLists in the Cedar context.