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
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
| Method | Path | Mode | Scope | Notes |
|---|---|---|---|---|
POST | /v2/stakes | unsigned only | n/a (signed rejected) | Marks an mpl-core asset as staked |
DELETE | /v2/stakes/{asset} | unsigned only | n/a (signed rejected) | Releases the staked flag |
GET | /v2/stakes/{wallet} | — | read | Placeholder 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
- NFTs — mint and burn the assets you'll be staking.
- Transaction Modes — why
signedis rejected here. - OpenAPI — full schemas at
/v2/openapi.json.