Skip to main content
POST
/
v1
/
buy
curl -X POST https://grail-stack-dev.onrender.com/v1/buy \
  -H "x-api-key: grail_partner_<hex>" \
  -H "Content-Type: application/json" \
  -d '{
    "grail_user_id": "gu_6b60956e-a8ee-4de2-8128-04c7fdf633c3",
    "usdc_amount": 100,
    "slippage_bps": 50
  }'
{
  "trade_id": "trd_4e7a1b8f-9c32-4a91-b3e6-7f12a8d4c5e9",
  "side": "buy",
  "quote": {
    "usdc_amount": 100,
    "gold_amount": 0.0205,
    "price_per_troy_oz": 4872.84,
    "fee_bps": 50,
    "fee_usd": 0.50,
    "min_gold_out": 0.0204
  },
  "partially_signed_transaction": "AQAAAAABAAEC...<base64>..."
}

Overview

Builds a buy transaction (USDC → $GOLD) and returns it partially-signed by GRAIL. The client must co-sign with the partner wallet and the user wallet, then either call Submit Buy or broadcast directly to a Solana RPC. This endpoint is stateless — no database row is created at quote time. The Trade row is written by the indexer once the transaction confirms on-chain (confirmed or failed).
The partial-signed transaction expires in ~60 seconds (Solana recentBlockhash TTL). If you take too long to co-sign and submit, you’ll get broadcast_failed: Blockhash not found. Re-quote.

Headers

x-api-key
string
required
A valid PARTNER scope key.

Request Body

grail_user_id
string
required
GRAIL user ID (prefixed gu_). User must belong to the authenticated partner, be active, and have kyc_level: "full".
usdc_amount
number
required
USDC the user will spend, in human decimal (e.g., 100 = 100 USDC). Must be positive.
slippage_bps
integer
Slippage tolerance in basis points. Default 50 (0.5%). Ignored if min_gold_out is provided.
min_gold_out
number
Absolute minimum $GOLD to receive (human decimal). If supplied, overrides slippage_bps. If omitted, computed as quoted_gold * (10000 - slippage_bps) / 10000.

Response

trade_id
string
Trade identifier, prefixed trd_. Use with Submit Buy and Get Trade.
side
string
Always "buy".
quote
object
partially_signed_transaction
string
Base64-encoded Solana transaction, already signed by GRAIL. Co-sign with partner + user and submit.

Errors

HTTPerrorWhen
400invalid_requestMissing or non-positive usdc_amount, missing grail_user_id
400kyc_level_insufficientUser’s KYC level is not full
400onchain_config_missingPartner’s on-chain config hasn’t been set up yet. Contact ORO.
400wallet_missingPartner has no registered wallet
403partner_mismatchUser belongs to a different partner
403user_suspendedUser status is suspended
404user_not_foundNo user with the given grail_user_id
503pricing_unavailableGold price oracle unreachable or returned stale data
curl -X POST https://grail-stack-dev.onrender.com/v1/buy \
  -H "x-api-key: grail_partner_<hex>" \
  -H "Content-Type: application/json" \
  -d '{
    "grail_user_id": "gu_6b60956e-a8ee-4de2-8128-04c7fdf633c3",
    "usdc_amount": 100,
    "slippage_bps": 50
  }'
{
  "trade_id": "trd_4e7a1b8f-9c32-4a91-b3e6-7f12a8d4c5e9",
  "side": "buy",
  "quote": {
    "usdc_amount": 100,
    "gold_amount": 0.0205,
    "price_per_troy_oz": 4872.84,
    "fee_bps": 50,
    "fee_usd": 0.50,
    "min_gold_out": 0.0204
  },
  "partially_signed_transaction": "AQAAAAABAAEC...<base64>..."
}