Skip to main content
Delegations allow an agent owner to grant specific permissions to other wallets. A delegate can update metadata, submit feedback, spend tokens, or manage services on behalf of the agent — scoped by fine-grained permissions and constraints. Delegations also power x402 budget-based payments. By combining an on-chain delegation with an SPL Token approve, users can authorize autonomous spending without signing each transaction.

Delegation PDA

Seeds: [b"delegation", delegator.as_ref(), delegate.as_ref(), delegation_id.to_le_bytes()]
FieldTypeDescription
delegation_idu64Unique ID from the agent’s delegation_count counter.
delegatorPubkeyWallet granting permissions.
delegatePubkeyWallet receiving permissions.
nft_mintPubkeyAgent context (NFT mint).
owner_versionu64Stored at creation, verified at use. Mismatches invalidate the delegation.
can_transactboolSpend tokens on behalf of the agent.
can_give_feedbackboolSubmit feedback as the agent.
can_update_metadataboolUpdate agent metadata URI and hash.
can_update_pricingboolChange payment requirements.
can_register_servicesboolAdd, update, or remove services.
can_manageboolAll permissions except transferring ownership.
can_redelegateboolCreate sub-delegations.
max_spend_per_txu64Maximum tokens per transaction. 0 = no limit.
max_spend_totalu64Maximum cumulative tokens. 0 = no limit.
spent_totalu64Running total of tokens spent via this delegation.
allowed_tokensVec<Pubkey> (max 5)Allowed token mints. Empty = all tokens allowed.
allowed_programsVec<Pubkey> (max 5)Allowed CPI targets. Empty = all programs. Reserved for future use.
expires_ati64Unix timestamp expiry. 0 = no expiry.
uses_remainingu64Remaining uses. 0 = unlimited.
total_usesu64Total uses so far.
activeboolWhether the delegation is active.
parent_delegationOption<Pubkey>If this is a sub-delegation, points to the parent Delegation PDA.
depthu8Delegation depth. 0 = direct from owner, 1 = sub, 2 = sub-sub.
created_ati64Unix timestamp.
revoked_atOption<i64>Timestamp of revocation, if revoked.
bumpu8PDA bump seed.

Permission flags

Seven boolean flags control what a delegate can do:
PermissionDescription
can_transactSpend tokens on behalf of the agent (used for x402 settlements).
can_give_feedbackSubmit feedback as the agent.
can_update_metadataUpdate the agent’s metadata URI and hash.
can_update_pricingModify payment requirements for services.
can_register_servicesAdd, update, or remove service endpoints.
can_manageGrants all permissions except NFT transfer. A shorthand for full control.
can_redelegateCreate sub-delegations (required for delegation chaining).

Constraints

Constraints limit what a delegate can do even when they have the right permission:
ConstraintTypeDescription
max_spend_per_txu64Per-transaction spending cap. 0 = unlimited.
max_spend_totalu64Cumulative spending cap. 0 = unlimited.
allowed_tokensVec<Pubkey>Whitelist of token mints. Empty = all allowed. Max 5.
allowed_programsVec<Pubkey>Whitelist of CPI targets. Empty = all allowed. Max 5.
expires_ati64Unix timestamp after which the delegation is invalid. 0 = no expiry.
uses_remainingu64Number of remaining uses before the delegation expires. 0 = unlimited.
Zero values mean “no limit” for all constraint fields. Setting max_spend_per_tx = 0 means there is no per-transaction cap, not that the delegate cannot spend.

Delegation depth

Delegations support up to 3 levels of chaining:
DepthRelationship
0Owner delegates to a wallet.
1That wallet sub-delegates to another wallet.
2The sub-delegate creates a sub-sub-delegation (maximum depth).
When creating a sub-delegation, the program enforces that the child’s constraints are a subset of the parent’s:
  • max_spend_total cannot exceed the parent’s remaining budget (parent.max_spend_total - parent.spent_total).
  • expires_at cannot be later than the parent’s expiry.
  • allowed_tokens must be a subset of the parent’s allowed tokens.
  • allowed_programs must be a subset of the parent’s allowed programs.
  • Permissions must be a subset of the parent’s permissions.
The maximum depth value is 2, meaning 3 total levels (owner, delegate, sub-delegate). Attempting to create a delegation at depth 3 or greater will fail with MaxDelegationDepthExceeded.

Cascading invalidation via owner_version

When an agent NFT is transferred and claim_agent is called, the owner_version on the AgentIdentity increments. Every delegation stores the owner_version at creation time. At use time, the program checks:
delegation.owner_version == agent.owner_version
If the values do not match, the delegation is rejected. This provides instant, zero-cost invalidation of all delegations when an agent changes hands — no need to iterate and revoke each one individually. Sub-delegations are also invalidated because the parent delegation (which they verify at use time) fails the owner_version check.

DelegationBuilder fluent API

The SDK provides a builder pattern for constructing delegations:
import { DelegationBuilder } from "@x84-ai/sdk";

const { instruction, delegationPda, delegationId } = await new DelegationBuilder()
  .transact()
  .feedback()
  .metadata()
  .spendLimit(new BN(1_000_000), new BN(10_000_000)) // per-tx, total
  .tokens([usdcMint])
  .expiry(Math.floor(Date.now() / 1000) + 86400) // 24 hours
  .uses(100)
  .build(program, delegatorPubkey, delegatePubkey, nftMint);
// Signer: delegatorKeypair

Permission methods

MethodSets
.transact()can_transact = true
.feedback()can_give_feedback = true
.metadata()can_update_metadata = true
.pricing()can_update_pricing = true
.services()can_register_services = true
.manage()can_manage = true
.redelegate()can_redelegate = true
.allPermissions()All flags set to true.

Constraint methods

MethodSets
.spendLimit(perTx, total)max_spend_per_tx and max_spend_total
.tokens(mints[])allowed_tokens
.programs(ids[])allowed_programs
.expiry(unix)expires_at
.uses(count)uses_remaining
.parent(delegationPda)parent_delegation (for sub-delegations)

x402 budget integration

Delegations power budget-based x402 payments. Instead of signing every payment transaction, a user sets up a spending budget once and the x84 facilitator auto-debits within the budget constraints.
1

Create delegation and SPL approve in one transaction

The user signs a single transaction containing two instructions:
  1. spl_token::approve — authorizes the x84 facilitator wallet as delegate on the user’s token account (ATA), with an amount matching max_spend_total.
  2. x84::create_delegation — creates the on-chain delegation with can_transact = true and desired constraints.
import { createApproveInstruction } from "@solana/spl-token";
import { DelegationBuilder } from "@x84-ai/sdk";

// 1. SPL Token approve
const approveIx = createApproveInstruction(
  payerAta,
  facilitatorPubkey,
  payerPubkey,
  10_000_000 // 10 USDC budget
);

// 2. x84 delegation
const { instruction: delegationIx } = await new DelegationBuilder()
  .transact()
  .spendLimit(new BN(1_000_000), new BN(10_000_000))
  .tokens([usdcMint])
  .expiry(Math.floor(Date.now() / 1000) + 86400 * 30) // 30 days
  .build(program, payerPubkey, facilitatorPubkey, nftMint);

// Bundle into one transaction
const tx = new Transaction().add(approveIx, delegationIx);
2

Facilitator auto-debits on each request

When the user calls an x402-gated agent endpoint, the x402 gate reads the X-DELEGATION header, loads the Delegation PDA, verifies all constraints, and uses the SPL Token delegate authority to transfer tokens from the user’s ATA. No per-request signature is needed.
3

Spent tracking

Each delegated settlement updates delegation.spent_total on-chain, providing a full audit trail of cumulative spending per delegation.

Revocation

import { revokeDelegation } from "@x84-ai/sdk";

await revokeDelegation(program, {
  caller: delegatorPubkey, // delegator or agent owner
  nftMint: agentId,
  delegationPda: delegationPda,
});
A single revocation transaction can invalidate both the on-chain delegation and the SPL Token approve. The delegator or the agent owner can revoke at any time. Sub-delegations that reference a revoked parent become invalid at use time — the program checks the parent’s active status during verification.
When a delegate attempts to use their delegation, the program runs these checks in order:
  1. delegation.active == true
  2. delegation.delegate == caller
  3. delegation.nft_mint == agent.nft_mint
  4. delegation.owner_version == agent.owner_version
  5. Expiry: delegation.expires_at == 0 or delegation.expires_at > now
  6. Uses: delegation.uses_remaining == 0 or delegation.uses_remaining > 0
  7. Required permission flag is set
  8. If spending: amount <= max_spend_per_tx and spent_total + amount <= max_spend_total
  9. If token specified: allowed_tokens is empty or contains the token mint
  10. If program specified: allowed_programs is empty or contains the program ID
  11. If parent_delegation is set: recursively verify the parent (max depth ensures termination)