# Solana policy rules

This page covers every Solana operation and criterion type available for policies.
For shared concepts like evaluation logic and allowlist vs denylist patterns, see the [rules reference](/docs/configuration/policies/rules-reference).

## Operations

### Backend wallet operations

| Operation | Description | Available criteria |
| :--- | :--- | :--- |
| `signSolTransaction` | Sign a Solana transaction | `solAddress`, `solValue`, `splAddress`, `splValue`, `mintAddress`, `solData`, `programId` |
| `sendSolTransaction` | Sign and broadcast a Solana transaction | `solAddress`, `solValue`, `splAddress`, `splValue`, `mintAddress`, `solData`, `programId`, `solNetwork` |
| `signSolMessage` | Sign an off-chain Solana message | `solMessage` |

### gas sponsorship operations

| Operation | Description | Available criteria |
| :--- | :--- | :--- |
| `sponsorSolTransaction` | Sponsor a Solana transaction via the [paymaster](/docs/products/infrastructure/paymaster/solana) | `solAddress`, `solValue`, `splAddress`, `splValue`, `mintAddress`, `solData`, `programId`, `solNetwork` |

## Criteria

### `solAddress`

Check the transaction recipient against a Solana address list.

:::info
Solana addresses are stored as [Base58](https://solana.com/docs/more/exchange#basic-validation) strings. Ensure you use the standard Base58 encoding when supplying addresses.
:::

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'solAddress'` | Criterion type |
| `operator` | `'in'` | `'not in'` | Set membership operator |
| `addresses` | string\[] | Solana addresses (Base58) |

```ts
{
  type: 'solAddress',
  operator: 'in',
  addresses: ['DtdSSG8ZJRZVv5Jx7K1MeWp7Zxcu19GD5wQRGRpQ9uMF'],
}
```

### `solValue`

Compare the SOL transfer amount in lamports.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'solValue'` | Criterion type |
| `operator` | `'<='` | `'>='` | Comparison operator |
| `value` | string | Amount in lamports (numeric string) |

```ts
{
  type: 'solValue',
  operator: '<=',
  value: '5000000000', // 5 SOL
}
```

### `splAddress`

Check SPL token transfer recipient addresses.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'splAddress'` | Criterion type |
| `operator` | `'in'` | `'not in'` | Set membership operator |
| `addresses` | string\[] | Recipient addresses (Base58) |

```ts
{
  type: 'splAddress',
  operator: 'in',
  addresses: ['DtdSSG8ZJRZVv5Jx7K1MeWp7Zxcu19GD5wQRGRpQ9uMF'],
}
```

### `splValue`

Compare SPL token transfer amount.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'splValue'` | Criterion type |
| `operator` | `'<='` | `'>='` | Comparison operator |
| `value` | string | Token amount (numeric string, in smallest unit) |

```ts
{
  type: 'splValue',
  operator: '<=',
  value: '1000000000', // 1000 USDC (6 decimals)
}
```

### `mintAddress`

Check the SPL token mint address.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'mintAddress'` | Criterion type |
| `operator` | `'=='` | `'in'` | Match operator |
| `addresses` | string\[] | Mint addresses (Base58) |

```ts
{
  type: 'mintAddress',
  operator: '==',
  addresses: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'], // USDC
}
```

### `solData`

Validate Solana instruction data against an Anchor IDL.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'solData'` | Criterion type |
| `operator` | `'=='` | `'in'` | `'not in'` | `'<='` | `'>='` | `'match'` | Comparison operator |
| `idl` | string | Anchor IDL JSON string (v0.30+) |
| `instructionName` | string | Instruction name to match |
| `args` | object | Argument constraints (optional) |

The `solData` criterion decodes Solana instruction data using an [Anchor IDL](https://www.anchor-lang.com/) (v0.30+) and validates instruction parameters, similar to how the EVM `evmData` criterion uses a contract ABI.

**Supported argument types:** `bool`, `string`, `pubkey`, `u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64`, `i128`, `f32`, `f64`

:::warning
User-defined types, arrays, vectors, and optionals are **not supported** in `solData` argument validation.
:::

The IDL must include `instructions` with an 8-byte `discriminator`, a `name`, and `args` with `name` and `type` for each parameter:

```ts
{
  type: 'solData',
  operator: '<=',
  idl: JSON.stringify({
    instructions: [
      {
        name: 'transfer',
        discriminator: [163, 52, 200, 231, 140, 3, 69, 186], // 8-byte discriminator
        args: [
          { name: 'amount', type: 'u64' },
        ],
      },
    ],
  }),
  instructionName: 'transfer',
  args: {
    amount: '5000000000', // 5 SOL in lamports
  },
}
```

The `args` field constrains decoded instruction arguments by name or positional key (`_0`, `_1`, ...). If `args` is omitted, matching the instruction name is sufficient.

### `programId`

Check which Solana programs are involved in the transaction.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'programId'` | Criterion type |
| `operator` | `'in'` | `'not in'` | Set membership operator |
| `programIds` | string\[] | Program IDs (Base58) |

```ts
{
  type: 'programId',
  operator: 'in',
  programIds: [
    'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', // Token Program
    '11111111111111111111111111111111',               // System Program
  ],
}
```

### `solNetwork`

Restrict to specific Solana networks. Only available for `sendSolTransaction`.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'solNetwork'` | Criterion type |
| `operator` | `'in'` | `'not in'` | Set membership operator |
| `networks` | string\[] | `'mainnet-beta'`, `'devnet'`, or `'testnet'` |

```ts
{
  type: 'solNetwork',
  operator: 'in',
  networks: ['mainnet-beta'],
}
```

### `solMessage`

Match an off-chain Solana message against a regex pattern. Only available for `signSolMessage`.

| Field | Type | Description |
| :--- | :--- | :--- |
| `type` | `'solMessage'` | Criterion type |
| `operator` | `'match'` | Regex match operator |
| `pattern` | string | RE2 regex pattern |

```ts
{
  type: 'solMessage',
  operator: 'match',
  pattern: '^Sign in to MyApp:',
}
```

## Examples

### Address allowlist

Only accept SOL transfers to known addresses.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Only allow transfers to treasury',
  rules: [
    {
      action: 'accept',
      operation: 'signSolTransaction',
      criteria: [
        {
          type: 'solAddress',
          operator: 'in',
          addresses: ['DtdSSG8ZJRZVv5Jx7K1MeWp7Zxcu19GD5wQRGRpQ9uMF'],
        },
      ],
    },
  ],
})
```

### SOL transfer limit

Reject SOL transfers above a threshold.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Reject SOL transfers above 10 SOL',
  rules: [
    {
      action: 'reject',
      operation: 'signSolTransaction',
      criteria: [
        {
          type: 'solValue',
          operator: '>=',
          value: '10000000000', // 10 SOL in lamports
        },
      ],
    },
  ],
})
```

### Multi-mint SPL allowlist

Only allow transfers of USDC or USDT by using `operator: 'in'` with multiple mint addresses.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Only allow USDC and USDT transfers',
  rules: [
    {
      action: 'accept',
      operation: 'signSolTransaction',
      criteria: [
        {
          type: 'mintAddress',
          operator: 'in',
          addresses: [
            'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
            'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',  // USDT
          ],
        },
      ],
    },
  ],
})
```

### SPL token restriction (USDC)

Only allow USDC transfers under 1000 USDC to approved recipients.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Restrict USDC transfers',
  rules: [
    {
      action: 'accept',
      operation: 'signSolTransaction',
      criteria: [
        {
          type: 'mintAddress',
          operator: '==',
          addresses: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'], // USDC
        },
        {
          type: 'splValue',
          operator: '<=',
          value: '1000000000', // 1000 USDC (6 decimals)
        },
        {
          type: 'splAddress',
          operator: 'in',
          addresses: ['DtdSSG8ZJRZVv5Jx7K1MeWp7Zxcu19GD5wQRGRpQ9uMF'],
        },
      ],
    },
  ],
})
```

### Program ID allowlist

Restrict transactions to only interact with known programs.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Only allow System and Token programs',
  rules: [
    {
      action: 'accept',
      operation: 'sendSolTransaction',
      criteria: [
        {
          type: 'programId',
          operator: 'in',
          programIds: [
            'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', // Token Program
            '11111111111111111111111111111111',               // System Program
          ],
        },
      ],
    },
  ],
})
```

### Network restriction

Only allow transactions on mainnet.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Mainnet only',
  rules: [
    {
      action: 'accept',
      operation: 'sendSolTransaction',
      criteria: [
        {
          type: 'solNetwork',
          operator: 'in',
          networks: ['mainnet-beta'],
        },
      ],
    },
  ],
})
```

### Message signing restriction

Only allow messages matching a specific prefix.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Only allow sign-in messages',
  rules: [
    {
      action: 'accept',
      operation: 'signSolMessage',
      criteria: [
        {
          type: 'solMessage',
          operator: 'match',
          pattern: '^Sign in to MyApp:',
        },
      ],
    },
  ],
})
```

### Instruction data validation (solData)

Validate Solana instruction parameters using an Anchor IDL. This example restricts a custom program's `transfer` instruction to amounts under 5 SOL.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Limit custom program transfers to 5 SOL',
  rules: [
    {
      action: 'accept',
      operation: 'signSolTransaction',
      criteria: [
        {
          type: 'solData',
          operator: '<=',
          idl: JSON.stringify({
            instructions: [
              {
                name: 'transfer',
                discriminator: [163, 52, 200, 231, 140, 3, 69, 186],
                args: [
                  { name: 'amount', type: 'u64' },
                ],
              },
            ],
          }),
          instructionName: 'transfer',
          args: {
            amount: '5000000000', // 5 SOL in lamports
          },
        },
      ],
    },
  ],
})
```

### Combined: SOL and USDC limits

Allow native SOL transfers and USDC transfers with separate limits.

```ts
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'SOL and USDC transfer limits',
  rules: [
    // Rule 1: SOL transfers under 10 SOL
    {
      action: 'accept',
      operation: 'sendSolTransaction',
      criteria: [
        {
          type: 'solValue',
          operator: '<=',
          value: '10000000000', // 10 SOL
        },
      ],
    },
    // Rule 2: USDC transfers under 10,000 USDC
    {
      action: 'accept',
      operation: 'sendSolTransaction',
      criteria: [
        {
          type: 'mintAddress',
          operator: '==',
          addresses: ['EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'],
        },
        {
          type: 'splValue',
          operator: '<=',
          value: '10000000000', // 10,000 USDC (6 decimals)
        },
      ],
    },
  ],
})
```

### Complete implementation

End-to-end example: create a policy, sign a message that passes, then attempt one that gets rejected.

```ts
import Openfort from '@openfort/openfort-node'

const openfort = new Openfort(process.env.OPENFORT_SECRET_KEY!, {
  walletSecret: process.env.OPENFORT_WALLET_SECRET,
})

// 1. Create a Solana backend wallet
const account = await openfort.accounts.solana.backend.create()
console.log('Wallet:', account.address)

// 2. Create a policy that only allows sign-in messages
const policy = await openfort.policies.create({
  scope: 'project',
  description: 'Only allow sign-in messages',
  rules: [
    {
      action: 'accept',
      operation: 'signSolMessage',
      criteria: [
        {
          type: 'solMessage',
          operator: 'match',
          pattern: '^Sign in to MyApp:',
        },
      ],
    },
  ],
})
console.log('Policy created:', policy.id)

// 3. Sign an allowed message — should succeed
const signature = await account.signMessage({
  message: 'Sign in to MyApp: session_abc123',
})
console.log('✅ Signed:', signature)

// 4. Attempt a disallowed message — should be rejected
try {
  await account.signMessage({
    message: 'Send 1000 SOL to attacker',
  })
} catch (error) {
  console.log('❌ Rejected by policy:', error.message)
}
```

## Token decimals

| Token | Decimals | 1 unit in smallest denomination |
| :--- | :--- | :--- |
| SOL | 9 | 1 SOL = 1,000,000,000 lamports |
| USDC | 6 | 1 USDC = 1,000,000 |
| USDT | 6 | 1 USDT = 1,000,000 |

## Common addresses

**Token mint addresses:**

| Token | Mint address |
| :--- | :--- |
| USDC | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` |
| USDT | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` |
| Wrapped SOL | `So11111111111111111111111111111111111111112` |

**Program IDs:**

| Program | Address |
| :--- | :--- |
| System Program | `11111111111111111111111111111111` |
| Token Program | `TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA` |
| Associated Token Program | `ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL` |
