Staking

Staking

Staking locks an mpl-core NFT against the Pirates program. The asset doesn't move wallets — instead, the program flips a state flag on a user_nft_config PDA so the on-chain reward logic treats it as staked. Unstaking flips it back.

Pirates program: BpEjeUewpz8v3QTenXTwkknBBsXaRH37DW7x2jKmVrAg.
mpl-core program: CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d.

Lifecycle

Staking lifecycle — stake an mpl-core asset, unstake later

Both endpoints are user-signed only. mode=signed is rejected because the mpl-core asset's owner field is the only valid signing authority — there is no server-side path. The Privy platform wallet can't sign on behalf of arbitrary users.

Endpoints

MethodPathModeScopeNotes
POST/v2/stakesunsigned onlyn/a (signed rejected)Marks an mpl-core asset as staked
DELETE/v2/stakes/{asset}unsigned onlyn/a (signed rejected)Releases the staked flag
GET/v2/stakes/{wallet}readPlaceholder route — returns a note pointing to the canonical chain-state read

Stake state is encoded on the mpl-core asset's plugin, not in our database. To read the current stake state for an asset, fetch the asset account with GET /v2/accounts/{asset} and decode the plugin layer client-side. The GET /v2/stakes/{wallet} route exists only to formalize that the canonical source is the asset, not an off-chain index.

Stake

curl -X POST https://api.piratecrew.fun/v2/stakes \
  -H "Authorization: Bearer $PIRATE_API_KEY" \
  -H "Idempotency-Key: 8a7b6c5d-..." \
  -H "Content-Type: application/json" \
  -d '{
    "user": "USER_WALLET_PUBKEY",
    "platform_id": "pirate-crew",
    "asset": "ASSET_PUBKEY",
    "collection": "COLLECTION_PUBKEY",
    "platform_fee": 0,
    "mode": "unsigned"
  }'

Response:

{
  "data": { "mode": "unsigned", "transaction": "AQABAo..." },
  "meta": { "request_id": "req_…" }
}

asset is the mpl-core asset address (the NFT itself). collection is the mpl-core collection address the asset belongs to. platform_fee (lamports) is an optional flat fee routed to the platform wallet in the same transaction. Sign with the user's wallet, then submit via POST /v2/transactions.

Unstake

curl -X DELETE https://api.piratecrew.fun/v2/stakes/ASSET_PUBKEY \
  -H "Authorization: Bearer $PIRATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user": "USER_WALLET_PUBKEY",
    "platform_id": "pirate-crew",
    "collection": "COLLECTION_PUBKEY",
    "platform_fee": 0,
    "mode": "unsigned"
  }'

Same response shape, same signing flow. After it lands, the asset is freely transferable again.

Reading stake state

The stake resource is keyed by asset address:

curl https://api.piratecrew.fun/v2/stakes/ASSET_PUBKEY \
  -H "Authorization: Bearer $PIRATE_API_KEY"
{
  "data": {
    "asset": "ASSET_PUBKEY",
    "owner": "USER_WALLET_PUBKEY",
    "status": "staked",
    "staked_at": "2026-05-12T18:42:11.000Z"
  },
  "meta": { "request_id": "req_…" }
}

If you need the raw user_nft_config PDA instead, derive it via POST /v2/pdas/user_nft_config and fetch with GET /v2/accounts/{address}.

Why no server-signing?

Anchor + mpl-core enforces that the asset's owner field signs every state mutation. The Privy platform wallet is not the asset owner, so the on-chain program would reject any server-signed transaction. The same constraint applies to NFT mint/burn and gold lock/unlock — see Transaction Modes for the full matrix.

Reference