Skip to main content

PDA Derivation

Program Derived Addresses (PDAs) for all Seesaw accounts.

Overview

PDAs provide deterministic, program-owned addresses without private keys.

Config PDA

Protocol-wide configuration singleton.

Seeds

["seesaw", "config"]

Derivation

let (config_pda, bump) = Pubkey::find_program_address(
    &[b"seesaw", b"config"],
    &program_id,
);

TypeScript

function findConfigPda(programId: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('seesaw'), Buffer.from('config')],
    programId
  );
}

Uniqueness

Exactly one per program deployment.

Market PDA

Per-epoch market account.

Seeds

["seesaw", "market", pyth_feed_id, duration_seconds.to_le_bytes(), market_id.to_le_bytes(), creator_pubkey]

Derivation

let (market_pda, bump) = Pubkey::find_program_address(
    &[
        b"seesaw",
        b"market",
        &pyth_feed_id,
        &duration_seconds.to_le_bytes(),
        &market_id.to_le_bytes(),
        creator_pubkey.as_ref(),
    ],
    &program_id,
);

TypeScript

function findMarketPda(
  pythFeedId: Uint8Array,
  durationSeconds: bigint,
  marketId: bigint,
  creatorPubkey: PublicKey,
  programId: PublicKey
): [PublicKey, number] {
  const durationBuffer = Buffer.alloc(8);
  durationBuffer.writeBigUInt64LE(durationSeconds);
  const marketIdBuffer = Buffer.alloc(8);
  marketIdBuffer.writeBigUInt64LE(marketId);

  return PublicKey.findProgramAddressSync(
    [
      Buffer.from('seesaw'),
      Buffer.from('market'),
      pythFeedId,
      durationBuffer,
      marketIdBuffer,
      creatorPubkey.toBuffer(),
    ],
    programId
  );
}

Market ID Calculation

function getCurrentMarketId(durationSeconds: number = 900): bigint {
  const now = Math.floor(Date.now() / 1000);
  return BigInt(Math.floor(now / durationSeconds));
}

Uniqueness

One market per creator per feed per duration per epoch. Multiple creators and durations can coexist for the same asset.

Orderbook PDA

Order book for a specific market.

Seeds

["seesaw", "orderbook", market_pubkey]

Derivation

let (orderbook_pda, bump) = Pubkey::find_program_address(
    &[
        b"seesaw",
        b"orderbook",
        market_pda.as_ref(),
    ],
    &program_id,
);

TypeScript

function findOrderbookPda(marketPda: PublicKey, programId: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('seesaw'), Buffer.from('orderbook'), marketPda.toBuffer()],
    programId
  );
}

Uniqueness

One orderbook per market.

Vault PDA

Token account holding market collateral.

Seeds

["seesaw", "vault", market_pubkey]

Derivation

let (vault_pda, bump) = Pubkey::find_program_address(
    &[
        b"seesaw",
        b"vault",
        market_pda.as_ref(),
    ],
    &program_id,
);

TypeScript

function findVaultPda(marketPda: PublicKey, programId: PublicKey): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('seesaw'), Buffer.from('vault'), marketPda.toBuffer()],
    programId
  );
}

Notes

  • This is an SPL Token account
  • Owned by the market PDA
  • Holds settlement currency (USDC)

Position PDA

User's position in a specific market.

Seeds

["seesaw", "position", market_pubkey, user_pubkey]

Derivation

let (position_pda, bump) = Pubkey::find_program_address(
    &[
        b"seesaw",
        b"position",
        market_pda.as_ref(),
        user_pubkey.as_ref(),
    ],
    &program_id,
);

TypeScript

function findPositionPda(
  marketPda: PublicKey,
  userPubkey: PublicKey,
  programId: PublicKey
): [PublicKey, number] {
  return PublicKey.findProgramAddressSync(
    [Buffer.from('seesaw'), Buffer.from('position'), marketPda.toBuffer(), userPubkey.toBuffer()],
    programId
  );
}

Uniqueness

One position per user per market.

Bump Seeds

Storage

All PDAs store their bump seed for efficient re-derivation:

pub struct MarketAccount {
    pub bump: u8,
    pub orderbook_bump: u8,
    pub vault_bump: u8,
    // ...
}

Verification

On each instruction, verify PDA derivation:

fn verify_pda(
    account: &AccountInfo,
    seeds: &[&[u8]],
    bump: u8,
    program_id: &Pubkey,
) -> Result<()> {
    let expected = Pubkey::create_program_address(
        &[seeds, &[&[bump]]].concat(),
        program_id,
    )?;

    require!(account.key == &expected, Error::InvalidPDA);
    Ok(())
}

Canonical Bumps

Always use find_program_address during creation, which returns the canonical (highest valid) bump.

Derivation Hierarchy

Program ID
├── Config PDA
│   └── seeds: ["seesaw", "config"]
│
└── Market PDA (per epoch)
    ├── seeds: ["seesaw", "market", market_id]
    │
    ├── Orderbook PDA
    │   └── seeds: ["seesaw", "orderbook", market_pda]
    │
    ├── Vault PDA
    │   └── seeds: ["seesaw", "vault", market_pda]
    │
    └── Position PDA (per user)
        └── seeds: ["seesaw", "position", market_pda, user_pda]

Helper Functions

Derive All Market PDAs

function deriveMarketPdas(
  marketId: bigint,
  programId: PublicKey
): {
  market: [PublicKey, number];
  orderbook: [PublicKey, number];
  vault: [PublicKey, number];
} {
  const [marketPda, marketBump] = findMarketPda(marketId, programId);
  const [orderbookPda, orderbookBump] = findOrderbookPda(marketPda, programId);
  const [vaultPda, vaultBump] = findVaultPda(marketPda, programId);

  return {
    market: [marketPda, marketBump],
    orderbook: [orderbookPda, orderbookBump],
    vault: [vaultPda, vaultBump],
  };
}

Derive All User PDAs

function deriveUserPdas(
  marketId: bigint,
  userPubkey: PublicKey,
  programId: PublicKey
): {
  market: PublicKey;
  position: [PublicKey, number];
} {
  const [marketPda] = findMarketPda(marketId, programId);
  const [positionPda, positionBump] = findPositionPda(marketPda, userPubkey, programId);

  return {
    market: marketPda,
    position: [positionPda, positionBump],
  };
}

Collision Prevention

PDAs are guaranteed unique due to:

  1. Prefix uniqueness: "seesaw" namespace
  2. Type uniqueness: Different prefixes per account type
  3. Key uniqueness: Market ID, user pubkey
  4. Bump search: find_program_address finds valid bump
["seesaw", "config"]           - unique (singleton)
["seesaw", "market", ID]       - unique per epoch
["seesaw", "orderbook", MKT]   - unique per market
["seesaw", "vault", MKT]       - unique per market
["seesaw", "position", MKT, U] - unique per user/market

Security Considerations

Always Verify Derivation

// WRONG: Trust caller-provided address
let market = &ctx.accounts.market;

// RIGHT: Verify derivation
let expected = Pubkey::create_program_address(
    &[
        b"seesaw",
        b"market",
        &market_id.to_le_bytes(),
        &[market.bump],
    ],
    &ctx.program_id,
)?;
require!(market.key() == expected, Error::InvalidPDA);

Store Bumps

Store bumps to avoid recomputing:

// During creation
market.bump = bump;

// During use
let seeds = &[
    b"seesaw",
    b"market",
    &market_id.to_le_bytes(),
    &[market.bump],
];

Next Steps