Skip to main content
POST https://hubra.app/api/v1/unstake
Build an unsigned unstake transaction. The route depends on strategy and kind:
Strategydeactivateinstantslow
sol-native-stakeStakeProgram.deactivate (epoch-bounded ~2.5d)Sanctum depositStake (active stake → SOL immediately)
sol-liquid-stakeSanctum swap raSOL → SOL via routed liquiditySanctum withdrawStake (raSOL → native stake account, then standard epoch deactivation)
sol-leveraged-stakeBurn raSOL Max LP → raSOL via the rasol-max SDK (vault-idle or MarginFi flash bracket)
usdc-earnVoltr direct-withdraw (no cooldown, no fee)
The returned transaction is base64 unsigned bytes. Sign and broadcast via POST /api/v1/broadcast.

Request

curl -X POST https://hubra.app/api/v1/unstake \
  -H 'Content-Type: application/json' \
  -d '{
    "strategy":      "sol-native-stake",
    "wallet":        "<your-wallet>",
    "stakeAccount":  "<your-stake-account>",
    "kind":          "deactivate"
  }'
FieldTypeRequiredDescription
strategystringyesOne of sol-native-stake, sol-liquid-stake, sol-leveraged-stake, usdc-earn.
walletstringyesSolana wallet pubkey.
kind"deactivate" | "instant" | "slow"yesThe unstake mode. See the matrix above for valid combinations. sol-leveraged-stake accepts only instant.
amountstringconditionalRequired except for usdc-earn with isWithdrawAll: true. For sol-leveraged-stake, this is the raSOL Max LP to burn (decimal, 9 decimals).
stakeAccountstringconditionalRequired for all sol-native-stake calls. The active stake account returned by your earlier /stake call.
isWithdrawAllbooleanoptionalValid only for usdc-earn. When true, withdraws the full vault position regardless of amount.
outputAsset"raSOL" | "SOL"optionalValid only for sol-leveraged-stake (default "raSOL"). "SOL" attaches a next step that converts the resulting raSOL → SOL. See below.

Response shapes

The response shape mirrors /stake: transaction (base64 unsigned), hubra_token (required at /broadcast), plus route metadata.

Sanctum-routed kinds

For sol-native-stake instant, sol-liquid-stake instant, and sol-liquid-stake slow:
{
  "strategy":      "sol-liquid-stake",
  "kind":          "instant",
  "transaction":   "<base64 unsigned tx>",
  "hubra_token":   "<token>",
  "route":         "sanctum",
  "sanctumKind":   "token",
  "sanctum_order": { "...": "..." },
  "signers":       ["<your-wallet>"]
}
sanctumKind varies by route:
Strategy + kindsanctumKind
sol-liquid-stake instanttoken
sol-liquid-stake slowwithdrawStake
sol-native-stake instantdepositStake
Forward hubra_token, sanctumKind, and sanctum_order to /broadcast.

Voltr-routed (usdc-earn instant)

{
  "strategy":    "usdc-earn",
  "kind":        "instant",
  "transaction": "<base64 unsigned tx>",
  "hubra_token": "<token>",
  "route":       "voltr",
  "signers":     ["<your-wallet>"]
}
Broadcast via route: "rpc".

Plain native deactivate (sol-native-stake deactivate)

{
  "strategy":    "sol-native-stake",
  "kind":        "deactivate",
  "transaction": "<base64 unsigned tx>",
  "hubra_token": "<token>",
  "signers":     ["<your-wallet>"],
  "notes":       "Stake becomes withdrawable after the deactivation epoch (~2.5 days). Then call /api/v1/withdraw."
}
No Sanctum or Voltr involvement. Broadcast via route: "rpc". After the deactivation epoch passes, call POST /api/v1/withdraw to close the stake account and pull SOL back.

Leveraged (sol-leveraged-stake instant)

Burns raSOL Max LP (amount) and pays out raSOL. Routes through the rasol-max SDK’s dual path — path tells you which ran:
  • vault-idle — one instruction, when the vault holds enough idle raSOL.
  • flash-bracket — 8–10 instructions + an address-lookup-table, deleveraging your slice through a MarginFi flashloan. The first redemption per wallet lazily initialises a MarginFi account (~0.005 SOL one-shot rent), flagged by initializedMarginfiAccount.
{
  "strategy":                    "sol-leveraged-stake",
  "kind":                        "instant",
  "outputAsset":                 "raSOL",
  "transaction":                 "<base64 unsigned tx>",
  "hubra_token":                 "<token>",
  "route":                       "rpc",
  "path":                        "flash-bracket",
  "burnedLpLamports":            "19500000",
  "expectedPayoutRasol":         "0.019485797",
  "expectedPayoutRasolLamports": "19485797",
  "initializedMarginfiAccount":  false,
  "receiptMint":                 "HUBsveNpjo5pWqNkH57QzxjQASdTVXcSK7bVKTSZtcSX",
  "receiptSymbol":               "raSOL",
  "signers":                     ["<your-wallet>"]
}
Broadcast via route: "rpc".

Finishing to SOL (outputAsset: "SOL")

The leveraged exit pays out raSOL, not SOL. Pass outputAsset: "SOL" to get the same single LP → raSOL transaction plus a ready-to-run next step for the raSOL → SOL conversion:
{
  "strategy":    "sol-leveraged-stake",
  "outputAsset": "SOL",
  "transaction": "<base64 unsigned tx>",
  "hubra_token": "<token>",
  "path":        "flash-bracket",
  "expectedPayoutRasol": "0.019414075",
  "next": {
    "description": "Broadcast THIS tx first, read the raSOL you actually received, then POST that amount here.",
    "endpoint": "/api/v1/unstake",
    "method":   "POST",
    "body": {
      "strategy": "sol-liquid-stake",
      "wallet":   "<your-wallet>",
      "amount":   "0.019414075",
      "kind":     "instant"
    }
  }
}
The SOL leg is a second signed transaction, not atomic. Sanctum won’t quote raSOL → SOL until the burned raSOL actually lands in your wallet. So: broadcast the withdraw, read the raSOL your wallet actually received, then fire the next call sized by that real balance — next.body.amount is only the build-time estimate.

Examples

# Native: epoch-bounded standard unstake
curl -X POST https://hubra.app/api/v1/unstake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-native-stake","wallet":"<your-wallet>","stakeAccount":"<your-stake-account>","kind":"deactivate"}'

# Native: instant settlement to SOL via Sanctum
curl -X POST https://hubra.app/api/v1/unstake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-native-stake","wallet":"<your-wallet>","stakeAccount":"<your-stake-account>","amount":"1.0","kind":"instant"}'

# Liquid: raSOL → SOL via Sanctum routing (instant)
curl -X POST https://hubra.app/api/v1/unstake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-liquid-stake","wallet":"<your-wallet>","amount":"1.0","kind":"instant"}'

# Liquid: raSOL → stake account, then epoch deactivation (slow)
curl -X POST https://hubra.app/api/v1/unstake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-liquid-stake","wallet":"<your-wallet>","amount":"1.0","kind":"slow"}'

# Leveraged: burn raSOL Max LP → raSOL
curl -X POST https://hubra.app/api/v1/unstake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-leveraged-stake","wallet":"<your-wallet>","amount":"0.0195","kind":"instant"}'

# Leveraged: burn raSOL Max LP, then chain to SOL (returns a `next` step)
curl -X POST https://hubra.app/api/v1/unstake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-leveraged-stake","wallet":"<your-wallet>","amount":"0.0195","kind":"instant","outputAsset":"SOL"}'

# USDC: full withdraw
curl -X POST https://hubra.app/api/v1/unstake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"usdc-earn","wallet":"<your-wallet>","kind":"instant","isWithdrawAll":true}'

Errors

StatusSlugWhen
400invalid_requestMissing fields, invalid kind for the strategy, stakeAccount missing for sol-native-stake, missing amount for sol-leveraged-stake, or an invalid outputAsset.
404not_foundUnknown strategy key.
502upstream_errorSanctum / Voltr could not build the transaction.
503service_unavailableStrategy is announced but not live.

See also

POST /quote

Preview the output before unstaking.

POST /withdraw

Complete a native deactivate.

POST /broadcast

Submit the signed transaction.