Stocks
Requires stocks:read for the reads and transactions:write for the trade
flow - granted by name, or through the
all:read / all:write wildcards.
Stocks are tokenized shares of real equities. They trade on-chain against a USD
stablecoin, they follow the US market calendar, and every endpoint here keys
off a slug in the same way the staking endpoints key off a pool slug.
The same catalogue also carries tokenized commodities such as gold. They go
through every endpoint on this page exactly as a stock does, and assetType
tells the two apart - see Commodities for where they behave
differently.
| Endpoint | Scope | Returns |
|---|---|---|
GET …/stocks/list | stocks:read | The catalogue with live prices |
GET …/stocks/spot-price/{slug} | stocks:read | One stock's price |
GET …/stocks/quote/{slug} | stocks:read | What an amount would trade at |
GET …/stocks/fee-estimate/{slug} | stocks:read | The approximate fee on an amount |
GET …/stocks/chart/line/{slug} | stocks:read | Price history |
POST …/stocks/construct/buy/transaction/batch | transactions:write | An unsigned buy |
POST …/stocks/construct/sell/transaction/batch | transactions:write | An unsigned sell |
POST …/stocks/transaction/batch/send | transactions:write | Submits a signed trade |
Holdings are read from the portfolio API:
GET …/portfolio/positions/open/stocks/list.
The catalogue
Code
Code
slug is the handle every other call takes. stockToken is the share token,
paymentToken the USD stablecoin it trades against - you need the decimals of
both, because a buy is denominated in one and a sell in the other.
assetType is equity for a tokenized share and commodity for a tokenized
commodity. The list carries both unless you ask for one:
Code
?assetType= takes equity or commodity; any other value is a 400. It only
narrows the list - a slug it leaves out still works on every other endpoint.
Integrated before commodities?
Without ?assetType= the list now includes commodity rows. If your screens
assume every row is a share - a sector per row, the US market calendar, a live
price on the stream - pass ?assetType=equity until they handle
commodities.
buyEnabled / sellEnabled are Restake's own switches. They are independent of
whether the market is open: a side switched off is refused at any hour, while a
shut market is a route that does not exist yet.
price is the market price as the venue that issues these tokens reports it.
sparkline is hourly closes over the last 24 hours of open market with that
price appended, and change24hPercent / change24hUsd are measured over the
same window.
Polling faster does not get fresher numbers
Prices, series and changes are cached for a minute across all callers. Read the list once a minute; anything faster returns the same numbers. For prices as they move, subscribe to the live price stream instead.
One stock on its own, with the same fields:
Code
And its history, over period - 24H (the default), 1W, 1M or 1Y:
Code
Code
time is a unix timestamp in milliseconds. An empty array is a real answer,
not an error: it means there is no price history for that stock to draw.
Market hours are the thing to design around
Most of what looks like a broken integration is the US market being shut. Commodities keep their own hours - see Commodities.
Every stock carries marketOpen and nextOpenAt (ISO 8601, null while open).
Read them before you conclude anything is wrong, because while a market is
closed:
- the price does not move;
sparklineand thechange24h*fields describe the last 24 hours the market was open, so over a weekend they show the previous session's move, not zero;- a PnL on a holding does not change;
- a quote or a trade usually fails, because there is no route to price
against - this comes back as a
400, not as a "market closed" error.
tradableSessions says which sessions a stock trades in - most trade the four
weekday sessions, a minority also trade weekends. It is there for display; do
not derive the schedule from it, because the windows carry US market holidays
and a single stock can be paused inside an open session for a dividend or a
split. marketOpen and nextOpenAt already account for both.
Trades are not gated on the session
A buy or sell of a shut stock is still attempted, and fails upstream for want
of liquidity. Refusing it here would be a second opinion about tradability,
and the one that matters is whether a route exists. If you want to stop a user
before that, branch on marketOpen yourself.
Quoting
Code
Code
amount is always in the token going in: the payment token on a buy,
shares on a sell. No wallet and no user are involved - a quote is a read.
executionPrice is what this amount fills at on the on-chain route, in payment
tokens per share, on either side. spotPriceUsd is the market price the
catalogue shows. The difference between the two is the route's spread and price
impact for this amount - show both if your users should see what a trade costs
beyond the fee. spotPriceUsd is null when the market price could not be
read; the quote itself is unaffected.
fee is what the trade would be charged on top of the route: systemFeeUsd is
the tiered platform fee - feePercent is the tier, and a larger trade falls in a
lower one. The tiers are the ones you set for your users in the partner panel
(Fees), or the platform's default tiers if you have not set any - and gasCoverageUsd is the gas Restake sponsors, priced from the
chain's current gas price. The two are summed into a single stablecoin transfer inside the batch,
which is what feeAmountRaw (base units, rounded up to the token's smallest
unit) settles. notionalUsd says which leg it was taken on: the dollars going in
on a buy, the route's proceeds on a sell. A null fee means the trade would be
charged nothing, not that the fee is unknown.
It is an indication. The number a trade actually executes at is the quote returned alongside the batch that builds it, taken from the same routing call as the transaction itself - and the fee on that quote is the one the batch charges. Quote here to show a price; read the trade's own quote to confirm one.
The trade flow
Buying and selling are three steps, exactly as staking is, and for the same reason: the API never holds a user's key.
POST …/construct/buy/transaction/batch(or…/sell/…) returns an unsigned EIP-7702 batch user operation, anintentId, and the quote it was built against.- The user's wallet signs
bundle.userOpHashand, whenbundle.authorizationis notnull, the EIP-7702 authorization tuple. POST …/transaction/batch/sendreturns the signatures with theintentId, and the API submits the operation.
The batch is atomic: the token approval, the swap and the platform fee execute together or not at all. That is what makes a sell safe - the fee comes out of proceeds the batch has not received yet, and a swap that returns less than quoted reverts the whole operation rather than charging for a trade that did not settle.
1. Build
Code
Code
userId names the user the trade acts for - a partner token identifies your
organisation, not a person. It must be one of your users; anything else is a
404.
slippage is a percentage between 0.1 and 5, defaulting to 1.
Gas is always sponsored: the user's address never needs the chain's native token. The platform fee includes a gas-coverage component that pays for it, priced from the chain's current gas price.
Trading the largest amount (maxAmount)
Send "maxAmount": true instead of amount to trade the largest amount the
wallet can afford right now. quote.sourceAmount in the response is the amount
used.
- Buy: the payment-token balance less the platform fee. A tiny remainder stays in the wallet, so a gas price that rises before the batch lands cannot make it unaffordable.
- Sell: every share the wallet holds. The fee comes out of the proceeds and is checked against the route's minimum return, as on any sale.
amountis ignored whenmaxAmountistrue.
2. Sign
The wallet signs identically to the staking flow:
signature- EIP-191 over the 32 bytes ofbundle.userOpHash, prefixed with0xff00.authorization- the EIP-7702 tuple signed as{ r, s, yParity }, usingbundle.authorization. Only whenbundle.authorizationis notnull; for an already-delegated wallet it isnull, and the submit body leavesauthorizationout.
Signing with Privy and Signing with Wallet Core cover both signatures in code.
3. Submit
Code
Code
An intent is single-use and short-lived. A replayed or late submit is a
400 - rebuild and re-sign rather than retrying the same intentId.
This endpoint is separate from staking's …/staking-pools/transaction/batch/send
even though the two carry the same payload: submit a trade through the endpoint
that built it.
Estimating the fee (fee-estimate)
GET …/fee-estimate/:slug?side=buy|sell&amount=… answers the platform fee and
gas fee on an amount, and what is left of it once they are taken. It reads no
wallet and asks no route, so it is cheap enough to call as the user types. The
response has the same shape as on
staking.
Code
- A buy takes
amountin payment-token units.finalAmountisamountless the fee, so for a wallet holdingamountit is about whatmaxAmountwill spend. - A sell takes
amountin shares. The proceeds are valued atspotPriceUsd, before the route's spread.finalAmountis those proceeds less the fee, in the payment token. - The figures are approximate. For the route's own price use
quote; the construct call charges the exact fee.
The units trap
The same trap as staking's, in a different pair of tokens:
| Direction | amount is denominated in |
|---|---|
| Buy | the payment token - paymentToken.decimals |
| Sell | the stock token (shares) - stockToken.decimals |
A buy is sized in the money going in; a sell is sized in the shares going out.
amount is a human-readable number (250.5, 1.1018), not base units - the
API scales it by the decimals of the correct token.
Selling a whole holding
Read the user's share balance from
…/portfolio/positions/open/stocks/list and pass
that amount straight through. Converting a USD figure back into shares
through a price you fetched yourself will be wrong by the time the trade lands.
Commodities
Rows with assetType: "commodity" are tokenized physical commodities. Today
that is Tether Gold (XAUt, slug xaut-usdc-bsc), a token backed by one troy
ounce of gold. Reads, quotes and the three-step trade are the same calls as for
a stock, and the same units rule applies - a sell is sized in the commodity
token. What differs:
categoryisother. Sectors describe companies; group commodities byassetTypeinstead.- Their hours are their own, not the US calendar. XAUt trades whenever the
chain does: it is always
marketOpen: true,nextOpenAt: null, with an emptytradableSessions. Branch onmarketOpenas you would for a stock, not onassetType- a commodity issued as a US-listed product would keep US hours. - Decimals are the token's own. XAUt has 6, not the 18 of the tokenized
shares. Read
stockToken.decimals; never assume it. - The price is a market-wide reference. For XAUt,
priceand a quote'sspotPriceUsdare the gold token's price across markets, not the price of the pool the trade fills in, so the gap toexecutionPricecan be wider than for a stock. Show the quote before a user commits. - XAUt is not on the live price stream. Subscribing to it
succeeds, but no update ever arrives. Read its price from the list or
…/stocks/spot-price/{slug}- once a minute is as fresh as it gets.
Which stocks you see
Every stock and commodity Restake lists is available to you by default, new ones
included - there is nothing to opt into. Your team can switch individual stocks
off in the partner panel, as with pools. A stock
switched off leaves …/stocks/list, and its spot price, quote, fee estimate,
chart, buy and sell answer 403 with STOCK_DISABLED.
That is your own configuration, not a permission we set, so the fix is a toggle
in the panel rather than a support ticket. A change takes effect within a minute.
A trade built before the switch can still be submitted, because
…/transaction/batch/send does not look at the stock again.
The price stream does not apply your selection. It serves every listed stock to every connection.
Failure modes
| Status | Cause |
|---|---|
400 | An unknown assetType on the list, that side is switched off for the stock, the user has no linked wallet, the wallet cannot afford the trade and its fee (INSUFFICIENT_BALANCE - checked before routing, so it costs no quote), no route exists for the amount (a shut market is the usual reason), or the intent is unknown, already used or expired |
403 | Your token lacks stocks:read or transactions:write, or your team has switched this stock off in the panel (STOCK_DISABLED) |
404 | No such stock slug - or no such user, or a user belonging to another partner |
