Protocol Spec

Quote Struct & XDR

The Quote is a Soroban #[contracttype] struct. It is the core data structure of the protocol — everything revolves around constructing, signing, and verifying it.

Struct definition (Rust)

#[contracttype]
pub struct Quote {
    pub quote_id:   BytesN<32>,  // SHA256(tokenIn|tokenOut|amountIn|taker|expiry) — unique
    pub maker:      Address,     // Registered maker Stellar address
    pub taker:      Address,     // Specific taker — quote is non-transferable
    pub token_in:   Address,     // SAC address of token taker sends (EURC or USDC)
    pub token_out:  Address,     // SAC address of token taker receives
    pub amount_in:  i128,        // Amount taker sends, in stroops (1 token = 1e7 stroops)
    pub amount_out: i128,        // Amount taker receives (before fee deduction)
    pub expiry:     u64,         // Unix timestamp (seconds) — quote invalid after this
    pub salt:       BytesN<32>,  // Random 32 bytes — prevents signature replay
}

Stroops — the unit of account

All amounts are in stroops — Stellar's base unit. 1 token = 10,000,000 stroops (1e7). Always multiply human-readable amounts before building a Quote struct.

// 20 EURC in stroops:
const amount_in = BigInt(20) * BigInt(10_000_000); // = 200_000_000n

Signing a Quote (TypeScript)

import * as StellarSdk from "@stellar/stellar-sdk";
import { createHash } from "crypto";

// 1. Build the Quote struct fields
const quote = {
  quote_id:   sha256(tokenIn + tokenOut + amountIn + taker + expiry),
  maker:      MAKER_ADDRESS,
  taker:      TAKER_ADDRESS,
  token_in:   EURC_SAC,
  token_out:  USDC_SAC,
  amount_in:  200_000_000n,   // 20 EURC
  amount_out: 218_100_000n,   // 21.81 USDC (before fee)
  expiry:     Math.floor(Date.now() / 1000) + 35,
  salt:       crypto.randomBytes(32),
};

// 2. Encode as XDR ScVal (matches Soroban contracttype layout)
const quoteXdr = encodeQuoteAsScVal(quote);  // using @stellar/stellar-sdk

// 3. Hash and sign
const msgHash  = createHash("sha256").update(quoteXdr).digest();
const keypair  = StellarSdk.Keypair.fromSecret(SIGNER_SECRET);
const signature = keypair.sign(msgHash);  // 64-byte ed25519 signature

// 4. Submit bid to backend auction
await fetch(`/api/auctions/${auctionId}/bid`, {
  method: "POST",
  headers: { "Authorization": `Bearer ${API_KEY}` },
  body: JSON.stringify({
    quote,
    signature: Buffer.from(signature).toString("hex"),
  }),
});
HyperDex | Trade Without Limits