Transactions
Build unsigned deposit and withdraw transactions, check withdrawal status, and sign, submit, and record them.
The deposit and withdraw endpoints return an unsigned, base64-encoded VersionedTransaction that your wallet signs and submits. They require a write-scope key. The withdraw-status read requires read. After a transaction confirms, record it with POST /api/history so the activity feed updates immediately.
POST /api/vault/{vault_id}/deposit
Builds an unsigned deposit transaction.
Body:
| Field | Description |
|---|---|
wallet |
Wallet pubkey (required). |
amount |
Deposit amount in lamports (smallest asset unit). Wins if both are present. |
uiAmount |
Human amount (e.g. 1.5 USDC) — an ergonomic alternative to amount. |
curl -X POST "https://app.gma.fi/api/vault/GPZW7ihHMMfZg5eNDmn376mwVzBKHh867vdWmjvNvYPK/deposit" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"wallet":"YOUR_WALLET_PUBKEY","amount":100000000}'
Response data:
| Field | Description |
|---|---|
transaction |
Base64 VersionedTransaction to sign and submit. |
message |
Human summary of the deposit. |
details |
{ vaultId, vaultName, depositAmount, depositAmountHuman, assetDecimals, estimatedSharesReceived, currentSharePrice }. |
GET /api/vault/{vault_id}/withdraw
Withdrawal status for a wallet. Add sharesAmount to get the same amount-specific
withdrawal economics used by the app. Requires a read-scope API key; wallet
binding and consumer vault restrictions apply to the preview too.
| Param | Description |
|---|---|
wallet |
Wallet pubkey (required). |
sharesAmount |
Optional positive integer decimal string in raw share units, identical to the amount used by withdrawal POST. Bounded to u64 and the wallet's current share balance. Omit for status-only polling. |
curl "https://app.gma.fi/api/vault/GPZW7ihHMMfZg5eNDmn376mwVzBKHh867vdWmjvNvYPK/withdraw?wallet=YOUR_WALLET_PUBKEY" \
-H "Authorization: Bearer YOUR_API_KEY"
Response data:
| Field | Description |
|---|---|
hasPendingWithdrawal |
Whether a pending withdrawal exists. |
pendingShares |
Pending shares in display share units, or 0 (legacy field). |
estimatedUsdcAmount |
Estimated value of pending shares in the vault's asset units, including SOL for SOL vaults (legacy field name). |
canClaimNow |
Whether the receipt is claimable on-chain now. |
createdAt |
Latest verified queued-withdrawal request's confirmed block time (ISO 8601), or null if no shares are pending or request history is unavailable. The receipt's NAV ordering floor is not a request timestamp. |
estimate |
{ kind, band, closeSlippage, asOf }, or null. With sharesAmount, describes that prospective withdrawal; otherwise describes pending timing and has no amount-specific slippage quote. |
progress |
Live staging progress, or null. |
preview |
Present only when sharesAmount is supplied. Amount-bound advisory economics described below. |
Preview before withdrawing
curl "https://app.gma.fi/api/vault/VAULT_PUBKEY/withdraw?wallet=YOUR_WALLET_PUBKEY&sharesAmount=50000000" \
-H "Authorization: Bearer YOUR_API_KEY"
50000000 means raw shares, not 50 million SOL lamports or a USD value.
Convert display shares using the vault's share decimals. Send the same raw string
as sharesAmount to POST when the user chooses to proceed. The API does not
simulate a deposit for requests above the wallet's balance.
Response data.preview:
| Field | Description |
|---|---|
advisory |
Always true. No transaction is built and no capacity is reserved. |
asOf |
Estimate-generation time (ISO 8601). Underlying liquidity/provider reads may use the app's short-lived caches; this is not an atomic blockchain snapshot or expiry guarantee. |
sharesAmountRaw, walletSharesRaw |
Requested and owned raw shares as exact decimal strings. |
asset |
{ mint, symbol, decimals } identifying the denomination of all asset amounts. |
sharePrice, navRaw, totalSharesRaw |
Current raw asset/share price and exact raw vault NAV/supply used for gross sizing. |
withdrawalPolicy |
Live retained-fee and net-outflow-cap policy. |
withdrawalLimits, limitBreach |
Current hourly/daily capacity and the first breached window, or null for no breach. Limits are advisory and rechecked when building/executing. |
withdrawalEstimate |
Exact grossAssetsRaw, feeAssetsRaw, netAssetsRaw strings and corresponding display amounts before unwind costs, plus fee/waiver metadata. The final-holder waiver requires ownership of the exact live supply. |
blockedReasons |
Current receipt/cap blockers: pending-withdrawal, claim-required, hourly-capacity-exceeded, or daily-capacity-exceeded. An empty array does not guarantee execution; NAV freshness and other transaction checks still apply. |
estimate |
Full shared app estimate: arrival kind (instant, queued, queued_bridge), measured/fallback band, closeSlippage, and asOf. |
quoteStatus |
complete when proceeds can be estimated; unavailable for missing, incomplete, or unsupported unwind quotes. |
costs |
The app's cost breakdown below, or null when the quote is unavailable. |
costBasisAssets |
Acquisition cost allocated to the selected shares, or null when accounting is provisional/unavailable or cannot be represented safely. |
estimatedNetProfitAssets |
Estimated proceeds minus personal acquisition cost, or null when costs or basis are unavailable. |
preview.costs contains display amounts in the vault's asset, not raw integers:
| Field | Meaning |
|---|---|
estimatedReceivedAssets |
Estimated amount received after the user's share of NAV loss and the retained withdrawal fee. |
totalUserCostAssets |
Gross claim minus estimated proceeds. |
estimatedFeeAssets |
Retained withdrawal fee applied after the estimated NAV loss. |
estimatedUserNavImpactAssets |
This holder's attributed share of unwind loss. |
estimatedVaultUnwindCostAssets |
Total loss from the vault unwind; do not subtract it again from this holder's proceeds. |
estimatedRemainingHolderImpactAssets |
Unwind loss attributed to the remaining holders. |
estimatedFeeCoverageShortfallAssets, estimatedFeeCoverageSurplusAssets |
Remaining-holder loss relative to retained fees. A surplus stays in the vault; it is not refunded to the withdrawing wallet. |
projectedOwnershipFraction |
Fraction used to attribute unwind loss; 0 for a cash-only preview with no unwind. |
estimate.closeSlippage retains the app's model scope, completeness/provider
details, estimated basis points, and any unavailability reason. Its
estimatedCostAssets is the vault-level unwind loss. Use preview.costs for
the user's costs and proceeds. For example, a 100 SOL claim owning 10% of a vault
with a 2 SOL unwind loss incurs 0.2 SOL of NAV impact; a 10 bps retained fee on
99.8 SOL is 0.0998 SOL, producing an estimated 99.7002 SOL payout. This is an
illustrative calculation, not a live quote or a fixed fee policy.
Render expected proceeds only when quoteStatus === "complete" and costs is
non-null. Otherwise show the quote as unavailable; withdrawalEstimate.netAssets
does not include unquoted unwind costs. Missing cost basis only suppresses
profit, not an otherwise complete proceeds estimate. Network fees are excluded.
All display amounts are advisory floating-point values; use exact raw strings
for the withdrawal request. Refresh before submission. Execution independently
checks balances, policies, capacity and market conditions and re-quotes any trades;
the preview is neither a slippage tolerance nor a guaranteed minimum output.
Invalid/duplicate, zero, fractional, out-of-u64, or over-balance sharesAmount
returns 400. Missing required live balance/NAV/policy/limit data returns 503.
Provider quote failure returns 200 with quoteStatus: "unavailable" and
costs: null, preserving fee, capacity and receipt information.
POST /api/vault/{vault_id}/withdraw
Initiates a new withdrawal or claims a pending one, chosen from on-chain state (or forced with an explicit action). PUT is an alias for POST.
Body:
| Field | Description |
|---|---|
wallet |
Wallet pubkey (required). |
sharesAmount |
Shares to withdraw, raw units (required when initiating a new withdrawal). |
action |
initiate or claim (optional; auto-detected otherwise). |
curl -X POST "https://app.gma.fi/api/vault/GPZW7ihHMMfZg5eNDmn376mwVzBKHh867vdWmjvNvYPK/withdraw" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"wallet":"YOUR_WALLET_PUBKEY","sharesAmount":50000000}'
Response data always includes action (initiate or claim), the base64 transaction, a message, and details. When initiating, details carries the withdrawal accounting:
| Field | Description |
|---|---|
withdrawalPolicy |
Fee/cap policy in effect. |
withdrawalLimits |
Current net-withdrawal limit state. |
withdrawalEstimate |
Gross/fee/net asset breakdown for the request. |
withdrawalReservation |
Deprecated; always null. Preparation does not reserve capacity. Withdrawal caps are enforced on-chain when the signed transaction executes. |
When claiming, the reservation and estimate fields are null; the message states the approximate amount to claim.
Withdrawal capacity errors
Preparation checks capacity without reserving it. Another withdrawal can consume capacity before your signed transaction executes. The program can return:
| Code | Error | Recovery |
|---|---|---|
6020 |
HourlyWithdrawalCapExceeded |
Refresh the quote and reduce the amount, or wait for capacity to become available. |
6021 |
DailyWithdrawalCapExceeded |
Refresh the quote and reduce the amount, or wait for capacity to become available. |
Signing, submitting, and recording
- Deserialize the base64 into a
VersionedTransaction. - Simulate (recommended) before signing.
- Sign with the wallet.
- Submit and confirm.
- Record the confirmed signature with
POST /api/history. Omit the deprecatedwithdrawal_reservation_idfor newly prepared withdrawals.
Blockhash freshness matters: build, sign, and submit promptly. A transaction whose blockhash has expired must be rebuilt (call the endpoint again) rather than retried as-is.
import { Connection, VersionedTransaction } from '@solana/web3.js';
// Your backend keeps the _gamma write key server-side; the browser never sees it.
const PARTNER_API = '/api/gamma';
// 1. Ask your backend for the unsigned transaction.
const res = await fetch(PARTNER_API + '/deposit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
wallet: wallet.publicKey.toBase58(),
amount: 100_000_000, // 100 USDC in lamports (6 decimals)
}),
});
const { data } = await res.json();
// 2. Deserialize the base64 transaction.
const transaction = VersionedTransaction.deserialize(
Buffer.from(data.transaction, 'base64'),
);
// 3. Sign (browser wallet shown; a Keypair works server-side).
const signedTx = await window.solana.signTransaction(transaction);
// 4. Submit and confirm.
const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
const signature = await connection.sendRawTransaction(signedTx.serialize());
const confirmation = await connection.confirmTransaction(signature, 'confirmed');
if (confirmation.value.err) throw new Error('Transaction failed');
// 5. Record the confirmed signature (idempotent; duplicate === true is success).
await fetch(PARTNER_API + '/history', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
wallet: wallet.publicKey.toBase58(),
vault_id: 'GPZW7ihHMMfZg5eNDmn376mwVzBKHh867vdWmjvNvYPK',
signature,
}),
});
For a queued withdrawal, record the initiate signature before recording the claim; a claim recorded first returns 409. See POST /api/history.
Next: Public endpoints