Developers
API
The read API and the transaction builder the app uses, with every route and error code.
The Tack service indexes the program's accounts from finalized blocks into PostgreSQL, serves them over HTTP, and builds unsigned transactions for traders. It never holds a trader's key and never sends a trader's transaction. The chain is the authority; the API is a fast, finalized read of it.
The website serves the API under /api/tack, so every path below works as /api/tack/markets on the same origin as the app.
Conventions
- Integers of 64 bits and larger are decimal strings, in the program's own units: micro-USDC, rates with
1000000000as 100% a year. See Parameters and units. - Addresses are base58.
- Lists return
{ commitment, slot, items, next }. Passlimit(1 to 500) and the previous response'snextasafterto page. - Each item is
{ address, kind, slot, payload, active, updatedAt }, with the decoded account inpayload. - Errors are
{ "error": "message", "code": "CODE" | null }with an HTTP status.
Status
GET/status
The program, the cluster, how far the index has read, whether the crank runs, and the latest reading of Phoenix's SOL funding with whether it is usable for new trades.
{
"programId": "…",
"cluster": "mainnet-beta",
"slot": "429922288",
"indexReady": true,
"crankEnabled": true,
"source": {
"venue": "Phoenix",
"market": "SOL",
"fundingTime": "1790859600",
"fundingInterval": "3600",
"updatedAt": "1790861453",
"oracleTime": "1790861452",
"markPrice": "117400000",
"cumulativeFunding": "3284500000",
"currentRate": "221600000",
"openInterestUsdc": "8255389000000",
"usable": true,
"reasons": []
}
}fundingTime is the start of Phoenix's current funding interval, the record markets checkpoint next; currentRate is the hour so far as an annual rate. When usable is false, reasons says why, for example a stale accumulator. GET /healthz answers while the process is up; GET /readyz answers 200 only while the index is recent.
Markets
GET/markets
Every market, open or finalized. The payload carries the configuration and the live state. Pass listed=true for the maturities Tack lists.
{
"address": "76yMvYNUpqvHp5BvZLJMPbSPpDjhzpsaGC936k2npE1b",
"kind": "market",
"payload": {
"maturity": "1792137600",
"referencePrice": "118300000",
"initialBps": 2500,
"maintenanceBps": 1000,
"shockRate": "1000000000",
"maxFixedRate": "1000000000",
"tickSize": "100000",
"oiCapBps": 100,
"maxAge": 900,
"freezeGrace": 86400,
"fundingInterval": 3600,
"sequence": "543",
"cumulativeFunding": "3284500000",
"sourceTimestamp": "1790859600",
"settlementTimestamp": "1790859600",
"openInterest": "2500000000",
"openOrders": 6,
"activeSwaps": 1,
"finalized": false,
"frozen": false
}
}Quotes
GET/orders?market=&status=0&maker=
Quotes, filtered by market, status (0 open, 1 filled, 2 cancelled) and maker. makerLong: true is a payer's quote.
{
"address": "2FcgPC5BC49SbWbFEKBJB1LKEEkCgPcJkfGVvfHoqDeP",
"kind": "order",
"payload": {
"market": "HfaQhgVvEkaR5T7cWANJ6nrb2TZc1ScGNU99wvJreDdb",
"maker": "HtkYfoFa9kCPVSUzB7s3g89vbR9VMhXACqAjS8E8bBJW",
"makerLong": false,
"notional": "2500000000",
"fixedRate": "95700000",
"collateral": "637500000",
"status": 0
}
}Positions
GET/swaps?market=&status=&owner=
Positions, filtered by market, status (0 to 4) and owner, which matches either side. long pays fixed; short receives it.
{
"address": "27WVK3WwwDDEXQomjLrkZ6nfyYYmTD3UzPfoRxeT2jgC",
"kind": "swap",
"payload": {
"long": "HtkYfoFa9kCPVSUzB7s3g89vbR9VMhXACqAjS8E8bBJW",
"short": "3A94ua6i5DdxGHGczeF9sJTsi2hKFoEP3r2ruwQr9HuU",
"notional": "2500000000",
"fixedRate": "30600000",
"longBalance": "637660942",
"shortBalance": "624839058",
"lastPnl": "160942",
"lastSequence": "542",
"startTimestamp": "1790809936",
"startCumulative": "44166950034",
"status": 0
}
}lastPnl is the payer's settled result since inception; lastSequence the last checkpoint applied.
Checkpoints
GET/checkpoints?market=&limit=
A market's checkpoints in sequence order: the latest limit (default 1,000, at most 5,000) and the total. Enough to draw a market's floating rate from the day it opened.
{
"total": 543,
"items": [
{ "sequence": "543", "timestamp": "1790859600", "cumulativeFunding": "3284500000", "finalized": false }
]
}The floating rate between two checkpoints, as a fraction a year, is (C₂ − C₁) ÷ (P0 × 1000) × 31,536,000 ÷ (t₂ − t₁), where P0 is the market's referencePrice. The app's funding chart is this, checkpoint to checkpoint.
Listing and governance
GET/listing
Tack's listing: its schedule, the terms its markets open with, its authority, the maturities the schedule calls for now (due, each with its market once open) and every market it has opened.
GET/governance
The TACK realm and its governance account, the rules (proposeMinimum, votingSeconds, holdUpSeconds, yesPercentOfSupply), the TACK mint with its supply and authorities, the total deposited, and how many proposals are in each state.
GET/governance/proposals?state=
Every proposal, newest first: its state, yes and no votes, timing, and the Tack instruction it runs if it passes, decoded. GET /governance/proposals/:address adds each vote.
GET/governance/voters/:owner
A wallet's deposit, which is its voting power, the votes it has not released, the proposals it has open, whether it can withdraw now, and its votes.
{
"owner": "3A94ua6i5DdxGHGczeF9sJTsi2hKFoEP3r2ruwQr9HuU",
"deposited": "60000000000000",
"unrelinquishedVotes": 1,
"outstandingProposals": 0,
"canWithdraw": false,
"votes": [{ "proposal": "…", "isRelinquished": false, "voterWeight": "60000000000000" }]
}One account
GET/accounts/:address
Any indexed account by address, or 404. GET /contract returns the program address, the IDL and its SHA-256.
Build a transaction
POST/transactions
Send what a trader wants to do; get back an unsigned transaction with the trader as fee payer. The service reads the current market, quote and position, derives every account, attaches the checkpoints a settlement needs, and creates the trader's USDC token account first where USDC comes back.
action | Fields |
|---|---|
createOrder | trader, market, makerLong, notional, fixedRate, collateral, optional id |
takeOrder | trader, orders: [{ order, collateral }], up to four lots of one market |
cancelOrder | trader, order |
topUp | trader, swap, amount |
withdraw | trader, swap |
settle | payer, swap |
listMarket | payer, maturity: one the listing calls for now |
governanceDeposit | owner, amount in TACK base units (six decimals) |
governanceWithdraw | owner |
proposeListing | owner, name, config (a full listing config), optional link; then again with the returned proposal to attach the change and open the vote |
governanceVote | voter, proposal, approve |
governanceRelinquish | voter, proposal |
governanceFinalize | payer, proposal |
governanceExecute | payer, proposal |
Governance actions build SPL Governance instructions for TACK's realm; check them the same way, against the realm and proposal you meant.
{
"action": "takeOrder",
"trader": "3A94ua6i5DdxGHGczeF9sJTsi2hKFoEP3r2ruwQr9HuU",
"orders": [{ "order": "2FcgPC5BC49SbWbFEKBJB1LKEEkCgPcJkfGVvfHoqDeP", "collateral": "637500000" }]
}{
"transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…",
"feePayer": "3A94ua6i5DdxGHGczeF9sJTsi2hKFoEP3r2ruwQr9HuU",
"blockhash": "…",
"lastValidBlockHeight": 412345678,
"programIds": ["…Tack program…"],
"created": { "2FcgPC5BC49SbWbFEKBJB1LKEEkCgPcJkfGVvfHoqDeP": "…new position address…" }
}transaction is a base64 legacy transaction. Deserialize it, sign it with the trader's wallet and send it to any RPC.
Refusals
The service refuses, before anything is signed, what the program would reject:
| Code | Status | Meaning |
|---|---|---|
NO_USDC_ACCOUNT | 400 | The trader has no USDC token account to pay from. |
SELF_TRADE | 400 | The trader is the quote's maker. |
ORDER_CLOSED | 409 | The quote is filled or cancelled. |
SWAP_OPEN | 409 | Withdrawal from a position that is still open. |
SWAP_CLOSED | 409 | Settlement or top-up of a closed position. |
SETTLED | 409 | Settlement with no checkpoint left to apply. |
SETTLEMENT_BEHIND | 409 | A top-up more than eight checkpoints behind; settle first. |
NOTHING_TO_WITHDRAW | 409 | This side's balance is already withdrawn. |
NOT_SCHEDULED | 409 | The listing's schedule does not open this maturity now. |
NO_TACK_ACCOUNT | 400 | The wallet has no TACK token account to deposit from. |
NOT_VOTING | 409 | The proposal is not open for voting. |
NOT_PASSED | 409 | Only a passed proposal can be executed. |
NO_LISTING | 404 | The service has no listing configured. |
Unknown accounts answer 404, a wallet acting on a quote or position that is not its own 403, and a body over 32 KiB 413.
Raw instructions
POST/instructions
For tools that assemble their own transactions: { name, args, accounts, remainingAccounts? } with the exact IDL names returns one unsigned instruction. remainingAccounts takes up to eight checkpoint addresses, for settle, liquidate and topUp only.