Developer docs
overviewStake your Web Weavers NFTs to earn SPDR, programmatically. There are three ways in: the HTTP API (recommended), building your own on-chain transactions, or dropping the agent skill into your AI tool.
Every write follows one pattern: authenticate then POST /…/build then sign the returned transaction with your wallet then POST /tx/submit. The build endpoint hands back a partially-built wire transaction (the server co-signs only its own delegate slot, and only when required; usually it is single-signer); your wallet signs the owner slot. The server never sees or holds your private key.
- api_base
- https://weavers.carbium.io
- program_id
- ww3st35u7nDFdAypP8nXjyCQy9cUMp9i1cuAJq2JKgL
- collection
- 44drPeDtf1tLwRXjRYZdNm7ZHX3tQytrYgvdbGjfbADC
- reward_mint
- AT79ReYU9XtHUTF5vM6Q4oa9K8w7918Fp5SU7G1MDMQY · SPDR · 9 decimals
Authentication
authBuild, submit, and per-owner read endpoints require a wallet JWT. Prove control of your wallet by signing a short challenge message (a plain ed25519 message signature, no transaction, no funds move), then exchange it for a bearer token. Send that token as Authorization: Bearer <JWT> on every call; the owner in each request body must equal the wallet that authenticated.
/auth/wallet/challenge- auth
- none
- body
- none
- returns
- { challengeId, message }
- rate
- 30 / min
/auth/wallet/verify- auth
- none
- body
- { challengeId, wallet, signature }
- returns
- { token, expiresIn }
- rate
- 30 / min
signature is base64 of the ed25519 signature over the challenge message bytes. A bad signature returns 401; an expired/unknown challenge returns 400.
BASE=https://weavers.carbium.io
# 1) challenge
CH=$(curl -s -X POST $BASE/auth/wallet/challenge)
# -> {"challengeId":"...","message":"weavers wallet auth\nid:...\nts:...\nSign to prove wallet control. No funds move."}
# 2) sign CH.message with your wallet -> base64(signature)
# 3) verify -> bearer token
curl -s -X POST $BASE/auth/wallet/verify \
-H 'content-type: application/json' \
-d '{"challengeId":"<id>","wallet":"<OWNER_PUBKEY>","signature":"<BASE64_SIG>"}'
# -> {"token":"<JWT>","expiresIn":"30d"}Build · Sign · Submit
core_loopThe core loop. A build endpoint returns either { tx } (one base64 wire transaction) or { txs: [...] } (a batch too large for a single transaction). For each transaction: deserialize it, partial-sign the owner slot with your wallet, re-serialize to base64, and POST it to /tx/submit. Do not strip any signature the server already placed.
const BASE = "https://weavers.carbium.io";
const headers = { "content-type": "application/json", authorization: `Bearer ${jwt}` };
// 1) build
const { tx } = await fetch(`${BASE}/stake/build`, {
method: "POST", headers,
body: JSON.stringify({ owner, mint, period: "604800" }),
}).then((r) => r.json());
// 2) sign the wire tx with the user's wallet (partialSign the owner slot), then:
const res = await fetch(`${BASE}/tx/submit`, {
method: "POST", headers,
body: JSON.stringify({ tx: signedWireTxBase64, owner }),
}).then((r) => r.json());
// res -> { txId: "<signature>", success: true }Stake
stakeFreeze a Weaver into the pool and start earning. period is the lock duration in seconds (as a string) and must match an on-chain lock tier; longer locks earn a higher reward multiplier. Use the bulk build to stake many Weavers in the fewest transactions.
/stake/build- auth
- wallet JWT
- body
- { owner, mint, period }
- returns
- { tx }
- rate
- 90 / min
/stake/bulk-build- auth
- wallet JWT
- body
- { owner, mints: string[], period }
- returns
- { tx } | { txs: [...] }
- rate
- 30 / min
curl -s -X POST $BASE/stake/build \
-H 'content-type: application/json' -H "authorization: Bearer $JWT" \
-d '{"owner":"<OWNER>","mint":"<WEAVER_MINT>","period":"604800"}'
# -> {"tx":"<base64 wire tx>"}Unstake
unstakeSettle a matured stake, thaw the NFT, and return it to your wallet. Rejected on-chain until the lock period has fully elapsed. The bulk build skips any requested mint not staked by the owner and reports it in skipped instead of failing the whole batch.
/unstake/build- auth
- wallet JWT
- body
- { owner, mint }
- returns
- { tx }
- rate
- 90 / min
/unstake/bulk-build- auth
- wallet JWT
- body
- { owner, mints: string[] }
- returns
- { txs: [...], skipped: [...] }
- rate
- 30 / min
Claim SPDR
claimTransfer earned SPDR from the pool vault to your token account. Omit mint to claim across all your stake accounts, or pass one to claim a single asset. May return multiple transactions when you hold stakes across several accounts.
/claim/build- auth
- wallet JWT
- body
- { owner, mint? }
- returns
- { tx } | { txs: [...] }
- rate
- 90 / min
Read state
readsEnumerate the Weavers in a wallet with their live on-chain staked / frozen flags. Self-scoped: the wallet in the path must match your authenticated session.
/api/nfts/owner/:wallet- auth
- wallet JWT (self)
- body
- none (query: ?mints=a,b,c optional)
- returns
- { assets: [{ mint, staked, frozen, name, imageUrl, attributes }] }
- rate
- 120 / min
Submit relay
relayRelays a fully-signed wire transaction: it simulates, sends, and confirms, returning the signature. The relay only accepts Weavers transactions (a program allowlist), so it cannot be used to land arbitrary programs.
/tx/submit- auth
- wallet JWT
- body
- { tx, owner? }
- returns
- { txId, success }
- rate
- 45 / min
On failure the relay returns HTTP 502 { error, correlationId }. Surface the correlationId; do not retry blindly.
Direct program invocation
on_chainFor power users building their own transactions. This is a pinocchio program: each instruction carries a 1-byte leading discriminator in instruction data[0] (not an 8-byte Anchor discriminator), and state is repr(C, packed) little-endian. Accounts are passed in the exact fixed order below.
pool = findPda(["weavers-pool", collection, reward_mint], programId)
user_stakes = findPda(["weavers-user-stakes", pool, owner, [accountIndex:u8]], programId)
accountIndex is a u8 (one byte). An owner can hold several user_stakes accounts (max 10 stakes each); pick an existing index with room, else the next free one.
body: serving_period i64 (LE) · account_index u8
accounts (in order):
- owner (signer, writable)
- delegate_authority (writable): == pool.delegate_authority
- asset (writable): the Weaver NFT
- collection (writable)
- core_program: MPL Core
- system_program
- pool (writable)
- user_stakes (writable)
user_stakes must already exist; if not, prepend an initUserStakes (disc 1) instruction in the same transaction. The HTTP build does this for you.
body: account_index u8
accounts (in order):
- owner (signer, writable)
- delegate_authority (writable): signer only when the pool charges an unstake refund
- asset (writable)
- collection (writable)
- core_program
- system_program
- pool (writable)
- user_stakes (writable)
- token_program
body: has_asset u8 (0|1) · (if 1) asset Pubkey(32)
accounts (in order):
- owner (signer, writable)
- asset: pass a read-only placeholder on a global (has_asset=0) claim; the slot must stay present
- collection
- pool (writable)
- delegate_authority
- user_stakes (writable)
- reward_token_account (writable): pool reward vault
- reward_mint
- user_reward_token_account (writable): owner ATA
- token_program
body: account_index u8
accounts (in order):
- cranker (signer, writable): pays the tx; no role gate
- pool (writable)
- user_stakes (writable)
Anyone may call this to advance expired-stake accounting for any user_stakes account. It only zeroes expired weight (honest accounting); no fund path, no role gate.
The canonical wire descriptor (discriminators, account layouts, struct offsets, error codes) ships in the @weavers/idl package as weavers.wire.json.
AI agent skill
skillA self-contained, user-scoped skill for Claude Code (or any agent that reads SKILL.md). Drop it into your agent to stake, unstake, and claim Weavers through the API. It contains no keys; signing always happens in your own wallet.
---
name: weavers-staking
description: Stake, unstake, and claim SPDR rewards for Web Weavers NFTs through the public Weavers API at https://weavers.carbium.io. Use when the user wants to stake/unstake a Weaver, claim staking rewards, or check what they have staked. The flow is always: authenticate the wallet, ask the API to BUILD an unsigned transaction, sign it with the user's wallet, then SUBMIT it back. The server never holds the user's key.
---
# Weavers Staking
Stake Web Weavers NFTs to earn SPDR rewards, via the public HTTP API at
`https://weavers.carbium.io`. All write actions follow one pattern:
```
authenticate -> POST /<action>/build -> sign the returned tx -> POST /tx/submit
```
The API returns a partially-built transaction (the server co-signs only its own
delegate slot when required; usually it is single-signer). The user's wallet signs
the owner slot. You then relay the signed wire transaction back through
`/tx/submit`. The server never sees or holds the user's private key.
## On-chain constants
| Name | Value |
| --- | --- |
| Program ID | `ww3st35u7nDFdAypP8nXjyCQy9cUMp9i1cuAJq2JKgL` |
| Collection | `44drPeDtf1tLwRXjRYZdNm7ZHX3tQytrYgvdbGjfbADC` |
| Reward mint (SPDR) | `AT79ReYU9XtHUTF5vM6Q4oa9K8w7918Fp5SU7G1MDMQY` (9 decimals) |
| API base | `https://weavers.carbium.io` |
## 1. Authenticate (wallet JWT)
Two requests: get a challenge, sign its `message` with the wallet (ed25519, this is
a plain message signature, no funds move), and verify to receive a bearer token.
```bash
BASE=https://weavers.carbium.io
# a) get a challenge
CH=$(curl -s -X POST $BASE/auth/wallet/challenge)
# CH -> {"challengeId":"...","message":"weavers wallet auth\nid:...\nts:...\nSign to prove wallet control. No funds move."}
# b) sign CH.message with the wallet -> base64(signature)
# c) verify
curl -s -X POST $BASE/auth/wallet/verify \
-H 'content-type: application/json' \
-d '{"challengeId":"<challengeId>","wallet":"<OWNER_PUBKEY>","signature":"<BASE64_SIG>"}'
# -> {"token":"<JWT>","expiresIn":"30d"}
```
Send the token as `Authorization: Bearer <JWT>` on every build/submit call. The
`owner` in each request body must equal the wallet that authenticated.
## 2. Build -> sign -> submit (the core loop)
```js
const BASE = "https://weavers.carbium.io";
// 1) build (server returns a base64 wire tx, or {txs:[...]} for multi-tx batches)
const { tx } = await fetch(`${BASE}/stake/build`, {
method: "POST",
headers: { "content-type": "application/json", authorization: `Bearer ${jwt}` },
body: JSON.stringify({ owner, mint, period: "604800" }),
}).then((r) => r.json());
// 2) sign the wire tx with the user's wallet (deserialize -> partialSign owner ->
// re-serialize to base64). Do NOT strip the server's existing signature.
// 3) submit the fully-signed wire tx
const res = await fetch(`${BASE}/tx/submit`, {
method: "POST",
headers: { "content-type": "application/json", authorization: `Bearer ${jwt}` },
body: JSON.stringify({ tx: signedWireTxBase64, owner }),
}).then((r) => r.json());
// res -> { txId: "<signature>", success: true }
```
When a build returns `{ txs: [...] }` (a batch too large for one transaction), sign
and submit each entry in order.
## Actions
All require the `Bearer` token. `owner` = the authenticated wallet (base58).
### Stake
`POST /stake/build`: body `{ owner, mint, period }`.
- `mint`: the Weaver NFT address to stake.
- `period`: lock duration in **seconds**, as a string. Must match an on-chain lock
tier. Longer locks earn a higher reward multiplier.
- Returns `{ tx }`.
`POST /stake/bulk-build`: body `{ owner, mints: string[], period }`. Stakes many in
the fewest transactions. Returns `{ tx }` or `{ txs: [...] }`.
### Unstake (only after the lock period has elapsed)
`POST /unstake/build`: body `{ owner, mint }`. Returns `{ tx }`.
`POST /unstake/bulk-build`: body `{ owner, mints: string[] }`. Returns
`{ txs: [...], skipped: [...] }`; `skipped` lists requested mints that were not
staked by the owner.
### Claim SPDR
`POST /claim/build`: body `{ owner }` to claim everything, or `{ owner, mint }` to
claim one asset. Returns `{ tx }` or `{ txs: [...] }`.
### Read what is staked / owned
`GET /api/nfts/owner/<wallet>` (Bearer token; wallet must match). Returns
`{ assets: [{ mint, staked, frozen, name, imageUrl, attributes }] }`.
## Notes
- All endpoints are rate limited (per minute, per IP). The auth + build endpoints
sit around 30-90 requests/min; submit is lower. Back off on HTTP 429.
- A failed submit returns HTTP 502 `{ error, correlationId }`. Surface the
correlationId; never retry blindly.
- Unstake is rejected on-chain until the lock period has fully elapsed.
- This skill is user-scoped. It never needs, asks for, or stores a private key;
signing happens in the user's own wallet.