WRIT Protocol on X@writnetwork on Twitterx.com/writnetworkwrit.networkWRIT Protocol official X account: @writnetworkFollow WRIT Protocol on X: https://x.com/writnetworkTwitter: @writnetwork · Website: writ.network
docs·WRIT Protocol
docs / programs / delegation

delegation

A bounded permission transfer from a registered human to an agent wallet. Every scope is a PDA with four hard constraints. Up to five active scopes per writ. Revocable in one transaction.

Program ID (devnet)
EnoPMLDuLo33PUvYBekpaTzyembPuZD82PAcv3qvRFxK
Layer
L2 — depends on writ_registry
Max concurrent scopes per writ
5
Revocation
O(1), failure-closed

Why five

The cap of five concurrent delegations is a policy number, not a cryptographic one. It bounds the attacker's gain from a single compromised identity. A human with five active scopes loses at most the union of their five budgets if every agent is captured simultaneously. Increasing the cap widens the blast radius of one compromised human; decreasing it hurts usability for power users who run multiple agents.

State

programs/delegation/src/state.rsrust
#[account]
#[derive(InitSpace)]
pub struct Scope {
    pub writ:             Pubkey,     // 32
    pub agent:            Pubkey,     // 32
    pub programs:         [Pubkey; 8],// 256 — zero pubkey = slot empty
    pub budget_lamports:  u64,        //  8
    pub spent_lamports:   u64,        //  8
    pub expires_at:       i64,        //  8 — unix seconds
    pub actions:          u16,        //  2 — bitmask
    pub revoked:          bool,       //  1
    pub index:            u8,         //  1 — slot in [0, 5)
    pub bump:             u8,         //  1
}

Action bitmask

The actions field encodes what kinds of operations the agent may perform. Bits are ORed; setting bit n authorizes action n.

BitActionTypical use
0SWAPDEX token swaps
1PROVIDE_LIQUIDITYAMM LP, CLOB market make
2BORROWmoney market draws
3REPAYmoney market repay
4STAKEvalidator / LST staking
5UNSTAKEunstake / unbond
6NFT_BUYmarketplace purchase
7NFT_SELLlisting / delist / transfer
8BRIDGEcross-chain outflow
9COMPUTE_PAYagent compute marketplace
10–15CUSTOM_*reserved for program-defined actions

Instructions

create_delegation

Called by the human (writ authority) to initialize a new scope in one of the five slots. If all slots are full, the caller must first revoke one.

pub fn create_delegation(
    ctx: Context<CreateDelegation>,
    slot_index: u8,                 // 0..=4
    agent: Pubkey,
    programs: [Pubkey; 8],
    budget_lamports: u64,
    duration_seconds: u32,           // expires_at = now + duration
    actions: u16,
) -> Result<()>
Duration capduration_seconds is capped at 7,776,000 (90 days). Longer delegations must be renewed — by design, so dormant permissions cannot silently persist.

revoke_delegation

Sets revoked = true. The scope remains on-chain for audit purposes but all reads via writ_gate return NoActiveDelegation for this slot.

pub fn revoke_delegation(
    ctx: Context<RevokeDelegation>,
    slot_index: u8,
) -> Result<()>

record_spend

Called by writ_gate during a successful verify_and_record to charge spent_lamports against the scope budget. This is the only instruction that mutates scope state after creation.

pub fn record_spend(
    ctx: Context<RecordSpend>,
    amount: u64,
) -> Result<()>

The instruction is permissioned — only writ_gate can call it. Anchor enforces this via a constraint = program.key() == WRIT_GATE_ID on the RecordSpend context.

PDA derivation

Scope PDA seedsrust
let (scope_pda, bump) = Pubkey::find_program_address(
    &[
        b"delegation",
        writ_account.key().as_ref(),
        &[slot_index],
    ],
    &delegation_program_id,
);

Errors

CodeWhen
SlotOccupiedcreate_delegation into a slot where an unrevoked scope already exists
NotWritAuthoritycaller is not the authority on the parent WritAccount
DurationTooLongduration_seconds > 7,776,000
AlreadyRevokedrevoke_delegation on a scope already marked revoked
BudgetOverflowrecord_spend would overflow u64
Unauthorizedrecord_spend caller is not writ_gate

Example: create a one-week LP delegation

app/delegate-lp.tstypescript
const ix = await program.methods
  .createDelegation(
    0,                        // slot 0
    agentKeypair.publicKey,
    [RAYDIUM, ORCA, METEORA, ZERO, ZERO, ZERO, ZERO, ZERO],
    new BN(50 * LAMPORTS_PER_SOL),
    60 * 60 * 24 * 7,         // 7 days
    0b10,                     // PROVIDE_LIQUIDITY only
  )
  .accounts({ writ, scope })
  .instruction();