Developers
HTTP API
The Plumb API reads the chain and builds, simulates and relays transactions. It never holds keys. The app uses it for everything.
The API serves JSON over HTTPS under /api/plumb on this site, and it is the same API the app uses. In the examples, $PLUMB_API stands for that base URL, for example https://<this site>/api/plumb.
Conventions
- On-chain quantities, timestamps and slots are decimal strings in raw units. PLMB has 6 decimals and JitoSOL 9, so
"100000000"is 100 PLMB and"2500000000"is 2.5 JitoSOL. - Addresses are base58 strings.
- Errors are
{"error": "a sentence"}with a 4xx or 5xx status. - Read routes return
503while the API's index of the chain is not current, rather than stale data.
Routes
| Route | Returns |
|---|---|
GET /v1/branches |
Every branch and the finalized slot it was read at |
GET /v1/branches/:branch |
The branch account as recorded |
GET /v1/branches/:branch/buckets |
All sixteen rates with their troves |
GET /v1/branches/:branch/troves?owner= |
Troves, optionally for one owner |
GET /v1/branches/:branch/pool |
Stability pool totals and depositors |
GET /v1/branches/:branch/live?owner= |
One confirmed read of everything the app shows, with the price |
POST /v1/transactions |
An unsigned transaction, built and simulated |
POST /v1/send |
Relays a signed Plumb transaction |
GET /v1/signatures/:signature |
Confirmation status, with a reason if it failed |
The branches, buckets, troves and pool routes read a finalized snapshot of the chain. live reads at confirmed commitment, a few seconds fresher.
Live state
curl "$PLUMB_API/v1/branches/$BRANCH/live?owner=$WALLET"One response, read from a single slot (abbreviated):
{
"programId": "…",
"commitment": "confirmed",
"slot": "312040551",
"clock": { "slot": "312040551", "epoch": "740", "unixTimestamp": "1790000000" },
"address": "…",
"branch": { "debt": "…", "collateral": "…", "poolStable": "…", "bitmap": 120, "ledger": [] },
"buckets": [],
"price": { "microUsdPerToken": "153498899", "issues": [], "sol": {}, "lst": {} },
"owner": { "lamports": "…", "stable": {}, "collateral": {}, "trove": { "active": true, "tick": 4, "slot": 2 } }
}price.microUsdPerToken is the price the program would use at this slot, in millionths of a dollar per JitoSOL: 153498899 is $153.498899. When the program would refuse the price it is null, and issues lists every check that failed. buckets has sixteen entries, null for a rate that was never opened. owner appears only when you pass ?owner=.
Building a transaction
curl -X POST "$PLUMB_API/v1/transactions" \
-H "content-type: application/json" \
-d '{"kind":"open","branch":"…","owner":"…","tick":4,"collateral":"3000000000","debt":"100000000"}'tick is the rate minus one: tick 4 is 5%. Kinds and their fields:
| Kind | Fields |
|---|---|
open |
tick, collateral, debt |
adjust |
add, withdraw, borrow, repay |
changeRate |
tick |
redeem |
amount, minCollateral, maxFeeBps |
poolDeposit |
amount |
poolWithdraw |
amount, collateral |
liquidate |
targetOwner |
accrue |
tick |
Every request also needs branch and owner; the owner signs and pays the fee. The API adds the compute-budget instructions and creates any associated token account the owner lacks. redeem always uses the lowest rate that holds debt.
A success returns the transaction for the owner to sign:
{
"transactionBase64": "AQAAAA…",
"lastValidBlockHeight": 290118843,
"requiredSigner": "…",
"unitsConsumed": 99907,
"units": "raw integer token units"
}Before answering, the API simulates the transaction. If the program would reject it, the response is 422 with a sentence, the Plumb error code if there is one, and the program's last log lines:
{
"ok": false,
"error": "Collateral ratio failed, trove is not liquidatable, or redemption requires liquidation first",
"code": 5,
"logs": ["Program log: …"]
}Sending and confirming
curl -X POST "$PLUMB_API/v1/send" \
-H "content-type: application/json" \
-d '{"transactionBase64":"<signed>"}'
# {"signature":"5h…"}
curl "$PLUMB_API/v1/signatures/5h…"
# {"signature":"5h…","status":"confirmed","slot":312040560,"failure":null}/v1/send accepts only signed transactions that call the Plumb program, and simulates them once more with signature checks before forwarding them. status moves from null (not seen yet) through processed and confirmed to finalized. If the transaction failed on chain, failure explains why.
You can also submit signed transactions through any Solana RPC yourself; the relay exists so a browser needs no RPC endpoint of its own.
# HTTP API
The Plumb API reads the chain and builds, simulates and relays transactions. It never holds keys. The app uses it for everything.
The API serves JSON over HTTPS under `/api/plumb` on this site, and it is the same API the app uses. In the examples, `$PLUMB_API` stands for that base URL, for example `https://<this site>/api/plumb`.
## Conventions
- On-chain quantities, timestamps and slots are **decimal strings** in raw units. PLMB has 6 decimals and JitoSOL 9, so `"100000000"` is 100 PLMB and `"2500000000"` is 2.5 JitoSOL.
- Addresses are base58 strings.
- Errors are `{"error": "a sentence"}` with a 4xx or 5xx status.
- Read routes return `503` while the API's index of the chain is not current, rather than stale data.
## Routes
| Route | Returns |
| --- | --- |
| `GET /v1/branches` | Every branch and the finalized slot it was read at |
| `GET /v1/branches/:branch` | The branch account as recorded |
| `GET /v1/branches/:branch/buckets` | All sixteen rates with their troves |
| `GET /v1/branches/:branch/troves?owner=` | Troves, optionally for one owner |
| `GET /v1/branches/:branch/pool` | Stability pool totals and depositors |
| `GET /v1/branches/:branch/live?owner=` | One confirmed read of everything the app shows, with the price |
| `POST /v1/transactions` | An unsigned transaction, built and simulated |
| `POST /v1/send` | Relays a signed Plumb transaction |
| `GET /v1/signatures/:signature` | Confirmation status, with a reason if it failed |
The `branches`, `buckets`, `troves` and `pool` routes read a finalized snapshot of the chain. `live` reads at confirmed commitment, a few seconds fresher.
## Live state
```bash
curl "$PLUMB_API/v1/branches/$BRANCH/live?owner=$WALLET"
```
One response, read from a single slot (abbreviated):
```json
{
"programId": "…",
"commitment": "confirmed",
"slot": "312040551",
"clock": { "slot": "312040551", "epoch": "740", "unixTimestamp": "1790000000" },
"address": "…",
"branch": { "debt": "…", "collateral": "…", "poolStable": "…", "bitmap": 120, "ledger": [] },
"buckets": [],
"price": { "microUsdPerToken": "153498899", "issues": [], "sol": {}, "lst": {} },
"owner": { "lamports": "…", "stable": {}, "collateral": {}, "trove": { "active": true, "tick": 4, "slot": 2 } }
}
```
`price.microUsdPerToken` is the price the program would use at this slot, in millionths of a dollar per JitoSOL: `153498899` is $153.498899. When the program would refuse the price it is `null`, and `issues` lists every check that failed. `buckets` has sixteen entries, `null` for a rate that was never opened. `owner` appears only when you pass `?owner=`.
## Building a transaction
```bash
curl -X POST "$PLUMB_API/v1/transactions" \
-H "content-type: application/json" \
-d '{"kind":"open","branch":"…","owner":"…","tick":4,"collateral":"3000000000","debt":"100000000"}'
```
`tick` is the rate minus one: tick `4` is 5%. Kinds and their fields:
| Kind | Fields |
| --- | --- |
| `open` | `tick`, `collateral`, `debt` |
| `adjust` | `add`, `withdraw`, `borrow`, `repay` |
| `changeRate` | `tick` |
| `redeem` | `amount`, `minCollateral`, `maxFeeBps` |
| `poolDeposit` | `amount` |
| `poolWithdraw` | `amount`, `collateral` |
| `liquidate` | `targetOwner` |
| `accrue` | `tick` |
Every request also needs `branch` and `owner`; the owner signs and pays the fee. The API adds the compute-budget instructions and creates any associated token account the owner lacks. `redeem` always uses the lowest rate that holds debt.
> [!TIP]
> `repay` and `withdraw` in `adjust`, and both fields of `poolWithdraw`, accept `"18446744073709551615"` to mean *everything*, settled when the transaction runs. Use it to close a trove or leave the pool without dust.
A success returns the transaction for the owner to sign:
```json
{
"transactionBase64": "AQAAAA…",
"lastValidBlockHeight": 290118843,
"requiredSigner": "…",
"unitsConsumed": 99907,
"units": "raw integer token units"
}
```
Before answering, the API simulates the transaction. If the program would reject it, the response is `422` with a sentence, the Plumb error code if there is one, and the program's last log lines:
```json
{
"ok": false,
"error": "Collateral ratio failed, trove is not liquidatable, or redemption requires liquidation first",
"code": 5,
"logs": ["Program log: …"]
}
```
## Sending and confirming
```bash
curl -X POST "$PLUMB_API/v1/send" \
-H "content-type: application/json" \
-d '{"transactionBase64":"<signed>"}'
# {"signature":"5h…"}
curl "$PLUMB_API/v1/signatures/5h…"
# {"signature":"5h…","status":"confirmed","slot":312040560,"failure":null}
```
`/v1/send` accepts only signed transactions that call the Plumb program, and simulates them once more with signature checks before forwarding them. `status` moves from `null` (not seen yet) through `processed` and `confirmed` to `finalized`. If the transaction failed on chain, `failure` explains why.
You can also submit signed transactions through any Solana RPC yourself; the relay exists so a browser needs no RPC endpoint of its own.