> ## Documentation Index
> Fetch the complete documentation index at: https://docs.turnkey.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Turnkey is wallet infrastructure: create and manage crypto wallets, sign transactions, and enforce policy-based access controls. Best-fit uses: embedded consumer wallets (email/passkey/social auth, no seed phrases), automated onchain operations with server-side wallets, AI agent wallets with policy-scoped signing, enterprise key management, and verifiable off-chain workloads on Turnkey Verifiable Cloud (TVC).
> Every API call is a JSON POST to https://api.turnkey.com signed with a P-256 API key; create an organization and key self-serve at https://app.turnkey.com.
> Key Turnkey developer resources: API reference (https://docs.turnkey.com/api-reference/overview/intro.md), OpenAPI spec (https://docs.turnkey.com/public_api.swagger.json), authentication (https://docs.turnkey.com/features/authentication/overview.md), webhooks (https://docs.turnkey.com/features/webhooks/overview.md), MCP server for docs search (https://docs.turnkey.com/mcp), agent skills (https://docs.turnkey.com/get-started/ai-skills.md), CLI (https://docs.turnkey.com/sdks/cli.md), SDK reference (https://docs.turnkey.com/sdks/introduction.md), full docs content (https://docs.turnkey.com/llms-full.txt).

# Solana

> This page provides examples of policies governing Solana signing.

Note: see the [language section](/features/policies/language#solana) for various approaches on writing Solana policies.

<Note>
  For sponsored Solana transactions, start with
  [Solana Rent Sponsorship](/features/networks/solana-rent-refunds) before applying the
  examples below. That guide covers account-creation risk, rent refunds, and
  mitigation strategy for sponsored flows.
</Note>

#### Allow IDL-specific program instructions

See [Smart Contract Interfaces](/features/policies/smart-contract-interfaces) for more information and examples.

#### Allow V1 transfers with explicit resource limits

See [Solana transactions](/features/networks/solana-transactions) for transaction construction and [V1 config fields](/features/policies/language#solana-v1-config) for units.
Replace the placeholders with your non-root user ID, wallet ID, and Solana addresses before creating these policies.

This policy permits one System Program transfer of up to 10,000,000 lamports from one sender to one recipient.
It caps the V1 priority fee in `unsignedTransaction` at 1,000 total lamports and requires explicit resource limits within the stated bounds.

```json theme={"system"}
{
  "policyName": "Allow bounded V1 SOL transfers",
  "notes": "Requires explicit V1 resource limits and one approved transfer",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<USER_ID>')",
  "condition": "wallet.id == '<WALLET_ID>' && solana.tx.version == 'V1' && solana.tx.instructions.count() == 1 && solana.tx.program_keys.all(p, p == '11111111111111111111111111111111') && solana.tx.transfers.count() == 1 && solana.tx.transfers.all(t, t.from == '<SENDER_ADDRESS>' && t.to == '<RECIPIENT_ADDRESS>' && t.amount > 0 && t.amount <= 10000000) && solana.tx.priority_fee_lamports <= 1000 && solana.tx.compute_unit_limit > 0 && solana.tx.compute_unit_limit <= 200000 && solana.tx.loaded_accounts_data_size_limit > 0 && solana.tx.loaded_accounts_data_size_limit <= 67108864 && solana.tx.heap_size == 32768"
}
```

For example, set the V1 transaction config to 200,000 CU, 67,108,864 loaded-account bytes, 32,768 heap bytes, and a 1,000-lamport priority fee.
Turnkey preserves these explicit config values during sponsorship.
Policies inspect the instructions in `unsignedTransaction`, so sponsor-added rent-funding instructions do not change this policy's instruction count.

<Warning>
  An absent V1 priority fee reads as zero, but sponsorship can fill a nonzero fee later.
  Supply the priority fee explicitly, including zero, to make this cap apply to the final sponsored fee.
  The policy cannot distinguish an absent priority fee from an explicit zero.
  This policy alone cannot enforce a final sponsored fee cap when callers can omit the fee.
</Warning>

If a V1 resource limit is absent, this policy produces an evaluation error.
It also produces an error for legacy/V0, because those transactions have no V1 config fields.
The version check does not prevent this access: `&&` and `||` do not short-circuit.

#### Allow the same transfer for legacy and V0

If your application also accepts legacy/V0, create this separate policy alongside the V1 policy.
It repeats the user, wallet, and transfer restrictions without accessing V1-only fields.

```json theme={"system"}
{
  "policyName": "Allow bounded legacy and V0 SOL transfers",
  "notes": "One approved transfer with no Compute Budget instructions or ALTs",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<USER_ID>')",
  "condition": "wallet.id == '<WALLET_ID>' && (solana.tx.version == 'LEGACY' || solana.tx.version == 'V0') && solana.tx.address_table_lookups.count() == 0 && solana.tx.instructions.count() == 1 && solana.tx.program_keys.all(p, p == '11111111111111111111111111111111') && solana.tx.transfers.count() == 1 && solana.tx.transfers.all(t, t.from == '<SENDER_ADDRESS>' && t.to == '<RECIPIENT_ADDRESS>' && t.amount > 0 && t.amount <= 10000000)"
}
```

This example permits no customer-supplied Compute Budget instructions.
For sponsored legacy/V0 sends, Turnkey can add compute and fee values after policy evaluation.
If your application accepts explicit legacy/V0 compute and fee controls, add their instruction checks to this version-scoped policy.

A matching explicit allow can authorize a legacy/V0 request despite the V1 policy's evaluation error.
An explicit deny takes precedence over either allow.
Review existing allow policies before adding these examples: a broader allow can still authorize transactions that fail these conditions.

#### Deny V1 until policy migration is complete

This optional policy prevents non-root users from using V1 while you update instruction-based fee policies.
It reads only the version field, which is available for all three Solana transaction versions.
Remove it when your V1 policies are ready; its explicit deny overrides V1 allow policies.

```json theme={"system"}
{
  "policyName": "Deny Solana V1 during policy migration",
  "notes": "Remove after V1 config restrictions are ready",
  "effect": "EFFECT_DENY",
  "condition": "solana.tx.version == 'V1'"
}
```

Test policies with a non-root user; root quorum authorization can bypass policy restrictions.
Include V1 transactions with allowed and excessive fees, missing resource limits, and explicit zero fees, plus legacy/V0 regression cases.

#### Allow Solana transactions that include a transfer from one specific sender

```json theme={"system"}
{
  "policyName": "Enable transactions with a transfer sent by <SENDER_ADDRESS>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "condition": "solana.tx.transfers.all(transfer, transfer.from == '<SENDER_ADDRESS>')"
}
```

#### Allow Solana transactions that include a transfer to only one specific recipient

```json theme={"system"}
{
  "policyName": "Enable transactions with a single transfer sent to <RECIPIENT_ADDRESS>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "condition": "solana.tx.transfers.count() == 1 && solana.tx.transfers[0].to == '<RECIPIENT_ADDRESS>'"
}
```

#### Allow Solana transactions that have exactly one transfer, to one specific recipient

```json theme={"system"}
{
  "policyName": "Enable transactions with a transfer sent to <RECIPIENT_ADDRESS>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "condition": "solana.tx.transfers.all(transfer, transfer.to == '<RECIPIENT_ADDRESS>')"
}
```

#### Allow Solana transactions that only use the Solana System Program

```json theme={"system"}
{
  "policyName": "Enable transactions that only use the system program",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "condition": "solana.tx.program_keys.all(p, p == '11111111111111111111111111111111')"
}
```

#### Deny all Solana transactions transferring to an undesired address

```json theme={"system"}
{
  "policyName": "Reject transactions with a transfer sent to <BAD_ADDRESS>",
  "notes": "",
  "effect": "EFFECT_DENY",
  "condition": "solana.tx.transfers.any(transfer, transfer.to == '<BAD_ADDRESS>')"
}
```

#### Allow Solana transactions with specific expected instruction data

```json theme={"system"}
{
  "policyName": "Enable transactions where the first instruction has precisely <HEX BYTES>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "condition": "solana.tx.instructions[0].instruction_data_hex == '<HEX BYTES>'"
}
```

#### Allow Solana transactions whose first instruction involves a specific address

```json theme={"system"}
{
  "policyName": "Enable transactions where the first instruction has a first account involving <ADDRESS>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "condition": "solana.tx.instructions[0].accounts[0].account_key == '<ADDRESS>'"
}
```

#### Deny all address table lookups

```json theme={"system"}
{
  "policyName": "Deny Solana transactions that use address table lookups",
  "notes": "",
  "effect": "EFFECT_DENY",
  "condition": "solana.tx.address_table_lookups.count() > 0"
}
```

#### Deny sending to an address from a table lookup

```json theme={"system"}
{
  "policyName": "Deny Solana transactions sending directly to an address from a table lookup",
  "notes": "",
  "effect": "EFFECT_DENY",
  "condition": "solana.tx.transfers.any(t, t.to == 'ADDRESS_TABLE_LOOKUP') || solana.tx.spl_transfers.any(t, t.to == 'ADDRESS_TABLE_LOOKUP')"
}
```

#### Solana SPL token transfers -- context and examples

Turnkey’s policy engine supports policies for SPL token transfers. Specifically, we support creating policies for the `Transfer`, `TransferChecked` and `TransferCheckedWithFee` instructions across both the Solana Token Program and the Solana Token 2022 Program.

Some important context for using SPL token policies with Turnkey:

**Token Account Addresses** For context, Solana implements SPL token balances for a particular wallet address by creating a whole new account called a "token account" which has a pointer in its data field labeled "owner" that points back to the wallet address in question. So to hold a particular token in your Solana wallet, you have to create a new token account meant to hold that token, owned by your Solana wallet. For policies related to the receiving token address of an SPL transfer, the token address receiving the tokens will have to be used, NOT the wallet address that is the owner for the receiving token address. This is because, while both the owning wallet address and the receiving token address are specified in the transfer instruction, the owning wallet address of the recipient token address is not specified. For this we highly recommend using the convention of “associated token addresses” to set policies that, for example, allow SPL token transfers to a particular wallet address.

For further context on associated token addresses check out Solana’s documentation on it: [https://spl.solana.com/associated-token-account](https://spl.solana.com/associated-token-account)

<Note>
  An example implementation of using a policy to allow transfers to the
  associated token address of the intended recipient wallet address can be found
  in our SDK examples
  [here](https://github.com/tkhq/sdk/tree/main/examples/chain-integrations/with-solana#6-running-the-create-spl-token-transfer-with-policy-example).
</Note>

**Mint Address Accessibility** The mint account address of the token will only be accessible when the transaction is constructed using instructions that specify the mint address – `TransferChecked` and `TransferCheckedWithFee`. For transactions constructed using the simple `Transfer` method, the mint account will be considered empty.

Here are some example policies for SPL transfers:

#### Allow a user to sign Solana transactions that include a single instruction which is an SPL token transfer from a particular sending token address

```json theme={"system"}
{
  "policyName": "Allow user <USER_ID> to sign Solana transactions that include only a single SPL Transfer FROM <TOKEN_ADDRESS>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<USER_ID>')",
  "condition": "solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.count() == 1 && solana.tx.spl_transfers.all(transfer, transfer.from == '<TOKEN_ADDRESS>')"
}
```

#### Allow a user to sign Solana transactions only if ALL of the instructions are SPL transfers TO a particular token address

```json theme={"system"}
{
  "policyName": "Allow user <USER_ID> to sign Solana transactions only if ALL of the instructions are SPL transfers TO <TOKEN_ADDRESS>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<USER_ID>')",
  "condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.to == '<TOKEN_ADDRESS>')"
}
```

#### Allow users with a specific tag to sign Solana transactions only if ALL of the instructions are SPL token transfers with a specific address as the owner of the sending token address

```json theme={"system"}
{
  "policyName": "Allow users with <USER_TAG_ID> to sign Solana transactions only if ALL of the instructions are SPL token transfers with <OWNER_ADDRESS> as the owner of the sending token address",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.tags.contains('<USER_TAG_ID>'))",
  "condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.owner == '<OWNER_ADDRESS>')"
}
```

#### Allow a user to sign Solana transactions that include a single instruction which is an SPL token transfer where the atomic units of the transfer are less than a threshold amount

```json theme={"system"}
{
  "policyName": "Allow user <USER_ID> to sign Solana transactions that include a single instruction which is an SPL token transfer where the atomic units of the transfer are less than <AMOUNT>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<USER_ID>')",
  "condition": "solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.count() == 1 && solana.tx.spl_transfers.all(transfer, transfer.amount < <AMOUNT>)"
}
```

#### Allow a user to sign Solana transactions only if ALL of the instructions are SPL token transfers where the token mint address is a particular address

```json theme={"system"}
{
  "policyName": "Allow <USER_ID> to sign a Solana transaction only if ALL of the instructions are SPL token transfers where the token mint address is <TOKEN_MINT_ADDRESS>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<USER_ID>')",
  "condition": "solana.tx.instructions.count() == solana.tx.spl_transfers.count() && solana.tx.spl_transfers.all(transfer, transfer.token_mint == '<TOKEN_MINT_ADDRESS>')"
}
```

#### Allow a user to sign Solana transactions that includes a single instruction which is an SPL token transfer where one of the multisig signers of the owner is a particular address

```json theme={"system"}
{
  "policyName": "Allow <USER_ID> to sign a Solana transaction only if ALL of it's instructions are SPL token transfers where one of the multisig signers of the owner is <SIGNER_ADDRESS>",
  "notes": "",
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<USER_ID>')",
  "condition": "solana.tx.instructions.count() == 1 && solana.tx.spl_transfers.count() == 1 && solana.tx.spl_transfers.all(transfer, transfer.signers.any(s, s == '<SIGNER_ADDRESS>'))"
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.