Skip to main content
The SDK opens positions where the end-user’s wallet signs its own transactions. Cesto builds the exact unsigned transactions, the wallet signs them, and Cesto verifies and lands them — Cesto never holds the user’s key. The flow is prepare → sign → submit → poll:
Opening requires a write-scoped API key (read-only keys get a 403 on write routes), and the user’s wallet pays its own gas — it must hold some SOL. Cesto does not sponsor gas for SDK positions.
There is no separate ownership challenge: the signatures are the ownership proof. Prepare only needs the wallet address.

One-call flow (you hold the keypair)

When your backend controls the signing keypair — bots, agents, services you custody — open.execute runs the whole flow in one call. You supply a signTransactions callback, so the SDK never touches the private key:

Split flow (browser wallet signs)

In a web app the SDK runs on your backend, but the signatures come from the user’s wallet in the browser. Use the lower-level methods and hop between the two:
1

Backend: prepare

Cesto builds the unsigned transactions and retains the canonical bytes for ~60 seconds.
Send prepared.transactions to the browser immediately — the set expires ~60s after prepare. Slippage uses the platform default; there is no slippage parameter.
2

Browser: the user signs

Sign the transactions as-is — any modified byte is rejected at submit.
3

Backend: submit and wait

Method reference

open.prepare(params)

user
string
required
Solana address of the wallet opening the position — it signs and pays for every transaction.
slug
string
required
Basket to open, by slug (a product id also works).
amount
bigint
required
Amount to invest, in input-token base units. Serialized as a decimal string on the wire.
Returns { executionId, transactions, expiresAt }.

positions.submit(params)

executionId
string
required
Execution from the prepare response.
transactions
{ nodeId, signedTransaction }[]
required
Every prepared transaction, signed by the wallet (base64).
submit is single-use and never retried automatically. If it times out client-side it may still have been accepted — poll getExecution instead of re-sending. An expired (~60s), replaced, or already-submitted preparation fails with a 409; run prepare again.

positions.getExecution(executionId) / positions.waitForExecution(executionId, options?)

getExecution fetches the current status with per-transaction outcomes. waitForExecution polls it until a terminal status and throws a CestoError on timeout (the execution keeps running server-side).
pollIntervalMs
number
default:"2500"
Delay between status polls.
timeoutMs
number
default:"180000"
Max total wait before throwing.

Execution results

Terminal statuses are COMPLETED, PARTIALLY_COMPLETED, and FAILED.
Partial completion is real. A multi-token open is several independent transactions with no atomicity across them. If a leg fails, the execution ends PARTIALLY_COMPLETED and result.transactions shows exactly which legs landed (with signatures) and which failed. What to do next is your call: retry the open for the remainder, or close what landed.
To read the resulting position, use positions.getPosition — SDK positions are self-custody and do not appear in positions.getPositions.

Common errors

Security model

  • Ownership proof = the signatures. Only the wallet holder can produce a valid signature over the prepared message bytes; prepare takes the address only and creates nothing durable.
  • Tamper-proof submit. Cesto keeps the canonical unsigned transactions it built. On submit, each transaction’s message bytes must be byte-identical — only signatures may be added. Any changed instruction, account, or fee payer is rejected.
  • Single-use, short-lived preparations. One submit per prepare, ~60s expiry, and a new prepare for the same wallet + product replaces the previous one.
  • Quotes locked at prepare. The swap quote is baked into the prepared transaction; slippage tolerance protects against drift between prepare and submit.

Constraints

  • Open and close only — no rebalance via the SDK.
  • Swap-only baskets (one or more tokens). No prediction markets, lending, or perps.
  • One in-flight execution per wallet + product.
  • User pays gas — the wallet must hold SOL.

Closing

Closing is symmetric — see Close a Position.