---
title: Spend Permissions
description:
  Delegate token spending to third parties for automated and escrowed payments
---

Solana's Token Programs support **delegation**—granting another account
permission to transfer tokens from your token account up to a specified limit.
This enables use cases like automated payments, escrow services, and third-party
payment processing without giving up custody of your funds.

## How Delegation Works

When you approve a delegate, you're authorizing a specific account to transfer
tokens on your behalf:

- **Owner retains custody**: You still own the tokens and can transfer or revoke
  at any time
- **Capped spending**: The delegate can only transfer up to the approved amount
- **Single delegate per account**: Each token account can only have one active
  delegate
- **New approval replaces old**: Approving a new delegate automatically revokes
  the previous one

<Callout type="info">
  Delegation is non-custodial. The delegate can spend tokens up to the limit,
  but cannot access or drain the account beyond the approved amount. The owner
  can revoke at any time.
</Callout>

## Business Use Cases

| Use Case               | How Delegation Helps                                                 |
| ---------------------- | -------------------------------------------------------------------- |
| **Payment processors** | Merchant grants processor permission to settle transactions          |
| **Automated payroll**  | Treasury approves payroll service to disburse salaries               |
| **Escrow services**    | Buyer delegates to escrow agent for conditional release              |
| **Trading platforms**  | User approves exchange to execute trades on their behalf             |
| **Card issuance**      | User approves card issuer to charge purchases to their token account |

## Approving a Delegate

Grant another account permission to spend tokens from your account:

<CodeTabs storage="kit-cli">

```ts !! title="Kit"
import { getApproveCheckedInstruction } from "@solana-program/token";

// Approve delegate to spend up to 1,000 USDC (6 decimals)
const approveInstruction = getApproveCheckedInstruction({
  source: tokenAccountAddress, // Your token account
  mint: usdcMintAddress, // USDC mint
  delegate: delegateAddress, // Account receiving permission
  owner: ownerKeypair, // You (must sign)
  amount: 1_000_000_000n, // 1,000 USDC in base units
  decimals: 6
});
```

```bash !! title="CLI"
spl-token approve <TOKEN_ACCOUNT> <AMOUNT> <DELEGATE_ADDRESS>
# Example: Approve 1000 USDC to a delegate
spl-token approve 7v45Foih... 1000 DeLe9ate...
```

</CodeTabs>

**Parameters:**

- `source`: The token account granting permission
- `delegate`: The account that will have spending permission
- `owner`: Current owner of the token account (must sign the transaction)
- `amount`: Maximum tokens the delegate can transfer
- `decimals`: Token decimals for validation (prevents decimal errors)

### Demo

<CodeTabs flags="r">

```ts !! title="Approve Delegate"
// !collapse(1:16) collapsed
// Click ">" icon on left to expand demo imports
import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";
import type { KeyPairSigner } from "@solana/kit";
import { signer } from "@solana/kit-plugin-signer";
import { solanaLocalRpc } from "@solana/kit-plugin-rpc";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
  getCreateAssociatedTokenInstructionAsync,
  getInitializeMintInstruction,
  getMintSize,
  findAssociatedTokenPda,
  getMintToInstruction,
  getApproveCheckedInstruction,
  fetchToken,
  TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";

// Generate keypairs for sender and delegate
const sender = await generateKeyPairSigner();
const delegate = await generateKeyPairSigner();

console.log("Sender Address:", sender.address);
console.log("Delegate Address:", delegate.address);

// Demo Setup: Create client, mint account, token account, and fund with initial tokens
const { client, mint, senderAta } = await demoSetup(sender);

console.log("\nMint Address:", mint.address);
console.log("Sender ATA:", senderAta);

// =============================================================================
// Approve Delegate
// =============================================================================

// Create instruction to approve delegate
// !mark(1:8)
const approveInstruction = getApproveCheckedInstruction({
  source: senderAta,
  mint: mint.address,
  delegate: delegate.address,
  owner: sender,
  amount: 1_000_000n, // 1.0 tokens with 6 decimals
  decimals: 6
});

// Send approve transaction
// !mark
const result = await client.sendTransaction([approveInstruction]);

console.log("\n=== Approve Delegate ===");
console.log("Transaction Signature:", result.context.signature);

// Fetch token account data to show delegate is set
const tokenData = await fetchToken(client.rpc, senderAta);
console.log("\nSender Token Account Data:", tokenData.data);

// =============================================================================
// Demo Setup Helper Function
// =============================================================================
// !collapse(1:1000) collapsed

/**
 * Sets up for a delegate demo:
 * - Creates a @solana/kit client
 * - Airdrops SOL to sender for transaction fees
 * - Generates mint keypair and creates/initializes mint account
 * - Creates associated token account for sender
 * - Mints initial tokens to sender
 *
 * @param sender - The sender's keypair (will be funded and used as mint authority)
 * @returns Returns client instance, mint, and ATA address
 */
async function demoSetup(sender: KeyPairSigner) {
  // Create a @solana/kit client connected to the local validator
  const client = await createClient().use(signer(sender)).use(solanaLocalRpc());

  // Fund sender with SOL for transaction fees
  await client.airdrop(sender.address, lamports(1_000_000_000n));

  // Generate keypair to use as address of mint
  const mint = await generateKeyPairSigner();

  // Get default mint account size (in bytes), no extensions enabled
  const space = BigInt(getMintSize());

  // Get minimum balance for rent exemption
  const rent = await client.rpc.getMinimumBalanceForRentExemption(space).send();

  // Instruction to create new account for mint (token program)
  // Invokes the system program
  const createAccountInstruction = getCreateAccountInstruction({
    payer: sender,
    newAccount: mint,
    lamports: rent,
    space,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Instruction to initialize mint account data
  // Invokes the token 2022 program
  const initializeMintInstruction = getInitializeMintInstruction({
    mint: mint.address,
    decimals: 6,
    mintAuthority: sender.address
  });

  // Derive sender's associated token account address (ATA)
  const [senderAta] = await findAssociatedTokenPda({
    mint: mint.address,
    owner: sender.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Create instruction for sender's ATA
  const createSenderAtaInstruction =
    await getCreateAssociatedTokenInstructionAsync({
      payer: sender,
      mint: mint.address,
      owner: sender.address,
      tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
    });

  // Create instruction to mint initial tokens to sender
  const mintToInstruction = getMintToInstruction({
    mint: mint.address,
    token: senderAta,
    mintAuthority: sender,
    amount: 2_000_000n // Mint 2.00 tokens (2,000,000 base units with 6 decimals)
  });

  // Combine all instructions and send with the kit client
  const setupInstructions = [
    createAccountInstruction, // Create mint account
    initializeMintInstruction, // Initialize mint
    createSenderAtaInstruction, // Create sender's ATA
    mintToInstruction // Mint tokens to sender
  ];

  // Send the setup transaction with the kit client
  await client.sendTransaction(setupInstructions);

  return {
    client,
    mint,
    senderAta
  };
}
```

</CodeTabs>

## Revoking a Delegate

Remove all spending permissions from the current delegate:

<CodeTabs storage="kit-cli">

```ts !! title="Kit"
import { getRevokeInstruction } from "@solana-program/token";

const revokeInstruction = getRevokeInstruction({
  source: tokenAccountAddress, // Your token account
  owner: ownerKeypair // You (must sign)
});
```

```bash !! title="CLI"
spl-token revoke <TOKEN_ACCOUNT>
```

</CodeTabs>

<Callout type="caution">
  Revoke removes **all** delegate permissions—there's no partial revoke. If you
  need to reduce the limit, approve the same delegate with a lower amount.
</Callout>

### Demo

<CodeTabs flags="r">

```ts !! title="Revoke Delegate"
// !collapse(1:17) collapsed
// Click ">" icon on left to expand demo imports
import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";
import type { KeyPairSigner } from "@solana/kit";
import { signer } from "@solana/kit-plugin-signer";
import { solanaLocalRpc } from "@solana/kit-plugin-rpc";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
  getCreateAssociatedTokenInstructionAsync,
  getInitializeMintInstruction,
  getMintSize,
  findAssociatedTokenPda,
  getMintToInstruction,
  getApproveCheckedInstruction,
  getRevokeInstruction,
  fetchToken,
  TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";

// Generate keypairs for sender and delegate
const sender = await generateKeyPairSigner();
const delegate = await generateKeyPairSigner();

console.log("Sender Address:", sender.address);
console.log("Delegate Address:", delegate.address);

// Demo Setup: Create client, mint account, token account, and fund with initial tokens
const { client, mint, senderAta } = await demoSetup(sender);

console.log("\nMint Address:", mint.address);
console.log("Sender ATA:", senderAta);

// =============================================================================
// Transaction 1: Approve Delegate
// =============================================================================

// Create instruction to approve delegate
// !mark(1:8)
const approveInstruction = getApproveCheckedInstruction({
  source: senderAta,
  mint: mint.address,
  delegate: delegate.address,
  owner: sender,
  amount: 1_000_000n, // 1.0 tokens with 6 decimals
  decimals: 6
});

// Send approve transaction
// !mark
const approveResult = await client.sendTransaction([approveInstruction]);

console.log("\n=== Transaction 1: Approve Delegate ===");
console.log("Transaction Signature:", approveResult.context.signature);

// Fetch token account data to show delegate is set
const tokenDataAfterApprove = await fetchToken(client.rpc, senderAta);
console.log("\nSender Token Account Data:", tokenDataAfterApprove.data);

// =============================================================================
// Transaction 2: Revoke Delegate
// =============================================================================

// Create instruction to revoke delegate
// !mark(1:4)
const revokeInstruction = getRevokeInstruction({
  source: senderAta,
  owner: sender
});

// Send revoke transaction
// !mark
const revokeResult = await client.sendTransaction([revokeInstruction]);

console.log("\n=== Transaction 2: Revoke Delegate ===");
console.log("Transaction Signature:", revokeResult.context.signature);

// Fetch token account data to show delegate is revoked
const tokenDataAfterRevoke = await fetchToken(client.rpc, senderAta);
console.log("\nSender Token Account Data:", tokenDataAfterRevoke.data);

// =============================================================================
// Demo Setup Helper Function
// =============================================================================
// !collapse(1:1000) collapsed

/**
 * Sets up for a delegate demo:
 * - Creates a @solana/kit client
 * - Airdrops SOL to sender for transaction fees
 * - Generates mint keypair and creates/initializes mint account
 * - Creates associated token account for sender
 * - Mints initial tokens to sender
 *
 * @param sender - The sender's keypair (will be funded and used as mint authority)
 * @returns Returns client instance, mint, and ATA address
 */
async function demoSetup(sender: KeyPairSigner) {
  // Create a @solana/kit client connected to the local validator
  const client = await createClient().use(signer(sender)).use(solanaLocalRpc());

  // Fund sender with SOL for transaction fees
  await client.airdrop(sender.address, lamports(1_000_000_000n));

  // Generate keypair to use as address of mint
  const mint = await generateKeyPairSigner();

  // Get default mint account size (in bytes), no extensions enabled
  const space = BigInt(getMintSize());

  // Get minimum balance for rent exemption
  const rent = await client.rpc.getMinimumBalanceForRentExemption(space).send();

  // Instruction to create new account for mint (token program)
  // Invokes the system program
  const createAccountInstruction = getCreateAccountInstruction({
    payer: sender,
    newAccount: mint,
    lamports: rent,
    space,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Instruction to initialize mint account data
  // Invokes the token 2022 program
  const initializeMintInstruction = getInitializeMintInstruction({
    mint: mint.address,
    decimals: 6,
    mintAuthority: sender.address
  });

  // Derive sender's associated token account address (ATA)
  const [senderAta] = await findAssociatedTokenPda({
    mint: mint.address,
    owner: sender.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Create instruction for sender's ATA
  const createSenderAtaInstruction =
    await getCreateAssociatedTokenInstructionAsync({
      payer: sender,
      mint: mint.address,
      owner: sender.address,
      tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
    });

  // Create instruction to mint initial tokens to sender
  const mintToInstruction = getMintToInstruction({
    mint: mint.address,
    token: senderAta,
    mintAuthority: sender,
    amount: 2_000_000n // Mint 2.00 tokens (2,000,000 base units with 6 decimals)
  });

  // Combine all instructions and send with the kit client
  const setupInstructions = [
    createAccountInstruction, // Create mint account
    initializeMintInstruction, // Initialize mint
    createSenderAtaInstruction, // Create sender's ATA
    mintToInstruction // Mint tokens to sender
  ];

  // Send the setup transaction with the kit client
  await client.sendTransaction(setupInstructions);

  return {
    client,
    mint,
    senderAta
  };
}
```

</CodeTabs>

## Transferring as a Delegate

When acting as the delegate, use a standard transfer but sign with the delegate
keypair instead of the owner:

```ts title="Transfer as Delegate"
import { getTransferCheckedInstruction } from "@solana-program/token";

const transferInstruction = getTransferCheckedInstruction({
  source: ownerTokenAccount, // The account you have permission to spend from
  mint: usdcMintAddress,
  destination: recipientTokenAccount,
  authority: delegateKeypair, // You (the delegate) sign, not the owner
  amount: 100_000_000n, // 100 USDC
  decimals: 6
});
```

The transfer will succeed if:

- The source account has sufficient balance
- The delegate signs the transaction

Each transfer reduces the remaining allowance. When the allowance reaches zero,
the delegate can no longer transfer tokens.

### Demo

<CodeTabs flags="r">

```ts !! title="Transfer as Delegate"
// !collapse(1:17) collapsed
// Click ">" icon on left to expand demo imports
import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";
import type { KeyPairSigner } from "@solana/kit";
import { payer } from "@solana/kit-plugin-signer";
import { solanaLocalRpc } from "@solana/kit-plugin-rpc";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
  getCreateAssociatedTokenInstructionAsync,
  getInitializeMintInstruction,
  getMintSize,
  findAssociatedTokenPda,
  getMintToInstruction,
  getApproveCheckedInstruction,
  getTransferCheckedInstruction,
  fetchToken,
  TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";

// Generate keypairs for sender, delegate, and recipient
const sender = await generateKeyPairSigner();
const delegate = await generateKeyPairSigner();
const recipient = await generateKeyPairSigner();

console.log("Sender Address:", sender.address);
console.log("Delegate Address:", delegate.address);
console.log("Recipient Address:", recipient.address);

// Demo Setup: Create client, mint account, token accounts, and fund with initial tokens
const { client, mint, senderAta, recipientAta } = await demoSetup(
  sender,
  delegate,
  recipient
);

console.log("\nMint Address:", mint.address);
console.log("Sender ATA:", senderAta);
console.log("Recipient ATA:", recipientAta);

// =============================================================================
// Transaction 1: Approve Delegate
// =============================================================================

// Create instruction to approve delegate
// !mark(1:8)
const approveInstruction = getApproveCheckedInstruction({
  source: senderAta,
  mint: mint.address,
  delegate: delegate.address,
  owner: sender,
  amount: 1_000_000n, // 1.0 tokens with 6 decimals
  decimals: 6
});

// Send approve transaction
const approveResult = await client.sendTransaction([approveInstruction]);

console.log("\n=== Transaction 1: Approve Delegate ===");
console.log("Delegate Address:", delegate.address);
console.log("Transaction Signature:", approveResult.context.signature);

// =============================================================================
// Fetch Token Account Data to Demonstrate Delegate is Set
// =============================================================================

const tokenAccountData = await fetchToken(client.rpc, senderAta);
console.log("\nSender Token Account Data:", tokenAccountData.data);

// =============================================================================
// Transaction 2: Transfer Using Delegate
// =============================================================================

// Create instruction to transfer tokens using delegate
// Note: delegate is the authority here, not the owner
const transferInstruction = getTransferCheckedInstruction({
  source: senderAta,
  mint: mint.address,
  destination: recipientAta,
  // !mark
  authority: delegate, // Delegate signs this transaction
  amount: 500_000n, // 0.5 tokens with 6 decimals
  decimals: 6
});

// Send transfer transaction
// The delegate is the client payer, so it pays the fee and signs; the owner is not needed
const transferResult = await client.sendTransaction([transferInstruction]);

// =============================================================================
// Fetch Final Token Account Balances
// =============================================================================

const finalSenderToken = await fetchToken(client.rpc, senderAta);
const finalRecipientToken = await fetchToken(client.rpc, recipientAta);

console.log("\n=== Transaction 2: Transfer Using Delegate ===");
console.log("Transaction Signature:", transferResult.context.signature);
console.log("\nSender Token Account Data:", finalSenderToken.data);
console.log("\nRecipient Token Account Data:", finalRecipientToken.data);

// =============================================================================
// Demo Setup Helper Function
// =============================================================================
// !collapse(1:1000) collapsed

/**
 * Sets up for a delegate transfer demo:
 * - Creates a @solana/kit client with the delegate as the fee payer
 * - Airdrops SOL to sender and delegate for transaction fees
 * - Generates mint keypair and creates/initializes mint account
 * - Creates associated token accounts for sender and recipient
 * - Mints initial tokens to sender
 *
 * @param sender - The sender's keypair (will be funded and used as mint authority)
 * @param delegate - The delegate's keypair (funded and used as the client fee payer)
 * @param recipient - The recipient's keypair
 * @returns Returns client instance, mint, and ATA addresses
 */
async function demoSetup(
  sender: KeyPairSigner,
  delegate: KeyPairSigner,
  recipient: KeyPairSigner
) {
  // Create a @solana/kit client with the delegate as the fee payer
  const client = await createClient()
    .use(payer(delegate))
    .use(solanaLocalRpc());

  // Fund sender and delegate with SOL for transaction fees
  await client.airdrop(sender.address, lamports(1_000_000_000n));
  await client.airdrop(delegate.address, lamports(1_000_000_000n));

  // Generate keypair to use as address of mint
  const mint = await generateKeyPairSigner();

  // Get default mint account size (in bytes), no extensions enabled
  const space = BigInt(getMintSize());

  // Get minimum balance for rent exemption
  const rent = await client.rpc.getMinimumBalanceForRentExemption(space).send();

  // Instruction to create new account for mint (token program)
  // Invokes the system program
  const createAccountInstruction = getCreateAccountInstruction({
    payer: sender,
    newAccount: mint,
    lamports: rent,
    space,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Instruction to initialize mint account data
  // Invokes the token 2022 program
  const initializeMintInstruction = getInitializeMintInstruction({
    mint: mint.address,
    decimals: 6,
    mintAuthority: sender.address
  });

  // Derive sender's associated token account address (ATA)
  const [senderAta] = await findAssociatedTokenPda({
    mint: mint.address,
    owner: sender.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Derive recipient's associated token account address (ATA)
  const [recipientAta] = await findAssociatedTokenPda({
    mint: mint.address,
    owner: recipient.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Create instruction for sender's ATA
  const createSenderAtaInstruction =
    await getCreateAssociatedTokenInstructionAsync({
      payer: sender,
      mint: mint.address,
      owner: sender.address,
      tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
    });

  // Create instruction for recipient's ATA
  const createRecipientAtaInstruction =
    await getCreateAssociatedTokenInstructionAsync({
      payer: sender,
      mint: mint.address,
      owner: recipient.address,
      tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
    });

  // Create instruction to mint initial tokens to sender
  const mintToInstruction = getMintToInstruction({
    mint: mint.address,
    token: senderAta,
    mintAuthority: sender,
    amount: 2_000_000n // Mint 2.00 tokens (2,000,000 base units with 6 decimals)
  });

  // Combine all instructions and send with the kit client
  const setupInstructions = [
    createAccountInstruction, // Create mint account
    initializeMintInstruction, // Initialize mint
    createSenderAtaInstruction, // Create sender's ATA
    createRecipientAtaInstruction, // Create recipient's ATA
    mintToInstruction // Mint tokens to sender
  ];

  // Send the setup transaction with the kit client
  await client.sendTransaction(setupInstructions);

  return {
    client,
    mint,
    senderAta,
    recipientAta
  };
}
```

</CodeTabs>

## Checking Delegation Status

Query a token account to see its current delegate and remaining allowance:

<CodeTabs storage="kit-cli">

```ts !! title="Kit"
import { fetchToken } from "@solana-program/token";

const tokenAccount = await fetchToken(rpc, tokenAccountAddress);

if (tokenAccount.data.delegate) {
  console.log("Delegate:", tokenAccount.data.delegate);
  console.log("Remaining allowance:", tokenAccount.data.delegatedAmount);
} else {
  console.log("No delegate set");
}
```

```bash !! title="CLI"
spl-token display <TOKEN_ACCOUNT>
# Look for "Delegate" and "Delegated Amount" fields
```

</CodeTabs>

### Demo

<CodeTabs flags="r">

```ts !! title="Check Delegation Status"
// !collapse(1:15) collapsed
// Click ">" icon on left to expand demo imports
import { createClient, generateKeyPairSigner, lamports } from "@solana/kit";
import { signer } from "@solana/kit-plugin-signer";
import { solanaLocalRpc } from "@solana/kit-plugin-rpc";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
  getCreateAssociatedTokenInstructionAsync,
  getInitializeMintInstruction,
  getMintSize,
  findAssociatedTokenPda,
  getMintToInstruction,
  getApproveCheckedInstruction,
  fetchToken,
  TOKEN_2022_PROGRAM_ADDRESS
} from "@solana-program/token-2022";

// Demo Setup: Create client, mint, two token accounts (one with delegate, one without)
const { client, ataWithDelegate, ataWithoutDelegate } = await demoSetup();

// =============================================================================
// Fetch Token Accounts
// =============================================================================

// Fetch token account with delegate
// !mark
const tokenWithDelegate = await fetchToken(client.rpc, ataWithDelegate);
console.log("Token Account with Delegate:", tokenWithDelegate);

// Fetch token account without delegate
// !mark
const tokenWithoutDelegate = await fetchToken(client.rpc, ataWithoutDelegate);
console.log("\nToken Account without Delegate:", tokenWithoutDelegate);

// =============================================================================
// Demo Setup Helper Function
// =============================================================================
// !collapse(1:1000) collapsed

/**
 * Sets up two token accounts for comparison:
 * - Creates a @solana/kit client
 * - Airdrops SOL to owner for transaction fees
 * - Creates mint and two token accounts
 * - Approves delegate on one token account
 *
 * @returns Returns client and both ATA addresses
 */
async function demoSetup() {
  // Generate keypairs
  const owner = await generateKeyPairSigner();
  const owner2 = await generateKeyPairSigner();
  const delegate = await generateKeyPairSigner();

  // Create a @solana/kit client connected to the local validator
  const client = await createClient().use(signer(owner)).use(solanaLocalRpc());

  // Fund owner with SOL for transaction fees
  await client.airdrop(owner.address, lamports(1_000_000_000n));

  // Generate keypair to use as address of mint
  const mint = await generateKeyPairSigner();

  // Get default mint account size (in bytes), no extensions enabled
  const space = BigInt(getMintSize());

  // Get minimum balance for rent exemption
  const rent = await client.rpc.getMinimumBalanceForRentExemption(space).send();

  // Instruction to create new account for mint (token program)
  const createAccountInstruction = getCreateAccountInstruction({
    payer: owner,
    newAccount: mint,
    lamports: rent,
    space,
    programAddress: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Instruction to initialize mint account data
  const initializeMintInstruction = getInitializeMintInstruction({
    mint: mint.address,
    decimals: 6,
    mintAuthority: owner.address
  });

  // Derive ATAs for both owners
  const [ataWithDelegate] = await findAssociatedTokenPda({
    mint: mint.address,
    owner: owner.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  const [ataWithoutDelegate] = await findAssociatedTokenPda({
    mint: mint.address,
    owner: owner2.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Create ATA instructions
  const createAta1Instruction = await getCreateAssociatedTokenInstructionAsync({
    payer: owner,
    mint: mint.address,
    owner: owner.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  const createAta2Instruction = await getCreateAssociatedTokenInstructionAsync({
    payer: owner,
    mint: mint.address,
    owner: owner2.address,
    tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
  });

  // Mint tokens to both ATAs
  const mintTo1Instruction = getMintToInstruction({
    mint: mint.address,
    token: ataWithDelegate,
    mintAuthority: owner,
    amount: 1_000_000n // 1.0 tokens
  });

  const mintTo2Instruction = getMintToInstruction({
    mint: mint.address,
    token: ataWithoutDelegate,
    mintAuthority: owner,
    amount: 1_000_000n // 1.0 tokens
  });

  // Approve delegate on first ATA
  const approveInstruction = getApproveCheckedInstruction({
    source: ataWithDelegate,
    mint: mint.address,
    delegate: delegate.address,
    owner: owner,
    amount: 500_000n, // 0.5 tokens
    decimals: 6
  });

  // Send setup transaction
  await client.sendTransaction([
    createAccountInstruction,
    initializeMintInstruction,
    createAta1Instruction,
    createAta2Instruction,
    mintTo1Instruction,
    mintTo2Instruction,
    approveInstruction
  ]);

  return {
    client,
    ataWithDelegate,
    ataWithoutDelegate
  };
}
```

</CodeTabs>

## Security Considerations

**For account owners:**

- Only approve trusted delegates
- Set the minimum necessary spending limit
- Revoke delegations when no longer needed
- Monitor your accounts for unexpected transfers

**For service providers (delegates):**

- Clearly communicate the requested spending limit to users
- Implement proper key management for your delegate account
- Track allowance consumption to request re-approval before limits are exhausted

## Delegation vs. Custody

| Aspect           | Delegation                 | Full Custody                     |
| ---------------- | -------------------------- | -------------------------------- |
| Token ownership  | User retains               | User transfers to custodian      |
| Spending control | Capped at approved amount  | Full access to transferred funds |
| Revocation       | Instant, by owner          | Requires custodian cooperation   |
| Risk exposure    | Limited to approved amount | Entire balance                   |
| Trust required   | Limited                    | High                             |

Delegation provides a middle ground—enabling automated payments while limiting
risk exposure to the approved amount.

## Related Resources

| Resource                                                 | Description                                                               |
| -------------------------------------------------------- | ------------------------------------------------------------------------- |
| [Approve Delegate](/docs/tokens/basics/approve-delegate) | How to grant another account permission to spend from your token account. |
| [Revoke Delegate](/docs/tokens/basics/revoke-delegate)   | How to remove an existing delegate and revoke its spending permissions.   |
| [Transfer Token](/docs/tokens/basics/transfer-tokens)    | How to transfer tokens between token accounts.                            |
