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 1000000000 as 100% a year. See Parameters and units.
  • Addresses are base58.
  • Lists return { commitment, slot, items, next }. Pass limit (1 to 500) and the previous response's next as after to page.
  • Each item is { address, kind, slot, payload, active, updatedAt }, with the decoded account in payload.
  • 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.

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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.

actionFields
createOrdertrader, market, makerLong, notional, fixedRate, collateral, optional id
takeOrdertrader, orders: [{ order, collateral }], up to four lots of one market
cancelOrdertrader, order
topUptrader, swap, amount
withdrawtrader, swap
settlepayer, swap
listMarketpayer, maturity: one the listing calls for now
governanceDepositowner, amount in TACK base units (six decimals)
governanceWithdrawowner
proposeListingowner, name, config (a full listing config), optional link; then again with the returned proposal to attach the change and open the vote
governanceVotevoter, proposal, approve
governanceRelinquishvoter, proposal
governanceFinalizepayer, proposal
governanceExecutepayer, proposal

Governance actions build SPL Governance instructions for TACK's realm; check them the same way, against the realm and proposal you meant.

JSON
{
  "action": "takeOrder",
  "trader": "3A94ua6i5DdxGHGczeF9sJTsi2hKFoEP3r2ruwQr9HuU",
  "orders": [{ "order": "2FcgPC5BC49SbWbFEKBJB1LKEEkCgPcJkfGVvfHoqDeP", "collateral": "637500000" }]
}
JSON
{
  "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:

CodeStatusMeaning
NO_USDC_ACCOUNT400The trader has no USDC token account to pay from.
SELF_TRADE400The trader is the quote's maker.
ORDER_CLOSED409The quote is filled or cancelled.
SWAP_OPEN409Withdrawal from a position that is still open.
SWAP_CLOSED409Settlement or top-up of a closed position.
SETTLED409Settlement with no checkpoint left to apply.
SETTLEMENT_BEHIND409A top-up more than eight checkpoints behind; settle first.
NOTHING_TO_WITHDRAW409This side's balance is already withdrawn.
NOT_SCHEDULED409The listing's schedule does not open this maturity now.
NO_TACK_ACCOUNT400The wallet has no TACK token account to deposit from.
NOT_VOTING409The proposal is not open for voting.
NOT_PASSED409Only a passed proposal can be executed.
NO_LISTING404The 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.