# Policies

Openfort's policy engine restricts what an Openfort wallet is allowed to sign. For Lighter, that wallet only ever does two things — deposit and register an API key — so the policy surface is small and worth locking down tightly.

## Why policies matter less for trading here

Every other recipe in this hub uses Openfort policies to constrain trading actions directly, because the trading wallet and the signing wallet are the same key. Lighter breaks that coupling: trading happens entirely off-wallet, signed by a [server-held API key](/docs/recipes/lighter/api-keys) that Openfort's policy engine never sees. Policies on the Openfort wallet can't help or hurt order placement — they only govern the two L1-signed actions: depositing and registering keys.

## Restrict deposits to Lighter's contract

Only allow the wallet to send transactions to Lighter's deposit contract or USDC (for the `approve` call):

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

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

await openfort.policies.create({
  scope: 'account',
  accountId: account.id,
  description: 'Only allow transactions to the Lighter deposit contract and USDC',
  rules: [
    {
      action: 'accept',
      operation: 'sendEvmTransaction',
      criteria: [
        {
          type: 'evmAddress',
          operator: 'in',
          addresses: [
            '0x3B4D794a66304F130a4Db8F2551B0070dfCf5ca7', // Lighter deposit contract
            '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC (approve calls)
          ],
        },
      ],
    },
  ],
})
```

## Restrict message signing to ChangePubKey

Only allow `personal_sign` for Lighter's exact registration template, so the wallet can't be tricked into signing an arbitrary message:

```ts
await openfort.policies.create({
  scope: 'account',
  accountId: account.id,
  description: 'Only allow ChangePubKey registration messages',
  rules: [
    {
      action: 'accept',
      operation: 'signEvmMessage',
      criteria: [
        {
          type: 'evmMessage',
          operator: 'match',
          pattern: '^Register Lighter Account',
        },
      ],
    },
  ],
})
```

:::tip
Combine this with the deposit-contract restriction above in a single account-scoped policy. Once you create the first rule for a wallet, every operation that doesn't match any rule is rejected by default — so an attacker who compromises your server still can't redirect the wallet to sign anything outside these two actions.
:::

## Gas sponsorship for deposits

If you sponsor gas for the deposit and approval transactions, scope the sponsorship policy the same way — restrict it to the deposit contract and USDC so sponsorship can't be used to subsidize unrelated transactions:

```ts
await openfort.policies.create({
  scope: 'project',
  description: 'Sponsor gas only for Lighter deposits',
  rules: [
    {
      action: 'accept',
      operation: 'sponsorEvmTransaction',
      criteria: [
        {
          type: 'evmAddress',
          operator: 'in',
          addresses: [
            '0x3B4D794a66304F130a4Db8F2551B0070dfCf5ca7',
            '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
          ],
        },
      ],
    },
  ],
})
```

See [Gas sponsorship](/docs/configuration/gas-sponsorship) for how to configure the sponsorship mode itself.

## What policies can't do here

Policies can't rate-limit or restrict individual orders, position sizes, or leverage on Lighter — those are enforced by whatever authorization logic your server applies before it calls `signCreateOrder` with the API key. The API key itself has no Openfort policy attached to it; its only protocol-level constraint is that withdrawals always land on the L1 owner's own address, regardless of what a compromised key tries to do.
