Appearance
Developer Integration Guide
Base URL: https://krystal-api.foreseer.workers.dev
Chain: Base (8453) | Base Sepolia (84532)
Protocol version: 1
Authentication
Dev token (testnet)
Pass a base64-encoded JSON token in the Authorization header:
Authorization: Bearer <base64({ wallet: "0x...", userId: "0x...", isMaker: boolean })>Example payload:
json
{ "wallet": "0x27c3d04EB5655a9985816eD8f7698F00CDA2CC38", "userId": "0x27c3d04EB5655a9985816eD8f7698F00CDA2CC38", "isMaker": true }Auth-required endpoints
POST /rfqs— Create RFQGET /rfqs— List open RFQs for makersPOST /rfqs/:rfqId/quotes— Submit a quote (maker only)POST /quotes/:quoteId/accept— Accept a quotePOST /rfqs/:rfqId/cancel— Cancel an RFQPOST /trades/:tradeId/signing-payload— Get signing payloadPOST /trades/:tradeId/prepare-7702— Prepare EIP-7702 batchPOST /trades/:tradeId/submit— Submit tradeGET /trades— List trades
Optional-auth endpoints
GET /markets/:marketId/open-rfqs— Public + own RFQs when authed
No-auth endpoints
GET /markets— List marketsGET /markets/:marketId— Market detailGET /markets/recent-trades— Recent tradesGET /rfqs/:rfqId— RFQ detail (with?token=for private RFQs)GET /health— Health check
Idempotency
All mutation endpoints require an idempotencyKey (UUID). If a request with the same key is received, the server returns the existing result without side effects.
RFQ endpoints
Create an RFQ
POST /rfqs
Content-Type: application/json
Authorization: Bearer <token>
{
"idempotencyKey": "uuid",
"marketId": "0x...",
"side": "buy",
"baseAmount": "1000000000000000000",
"quoteAmount": "6000000000",
"expiresInSeconds": 60,
"riskAckVersion": "v1",
"visibility": "public",
"restrictedTo": "0x..."
}Visibility rules:
visibility | restrictedTo | Orderbook |
|---|---|---|
public | not sent | Visible to all |
private | not sent | Hidden, access via ?token= |
restricted | 0x... | Visible to owner + restrictedTo |
Fetch open RFQs
GET /markets/:marketId/open-rfqs
Authorization: Bearer <token> (optional)Returns public RFQs + the caller's own RFQs + RFQs where restrictedTo matches.
Fetch a specific RFQ
GET /rfqs/:rfqId
GET /rfqs/:rfqId?token=<shareToken>No auth required. Private RFQs require ?token=. Restricted RFQs require matching wallet.
Cancel an RFQ
POST /rfqs/:rfqId/cancel
Authorization: Bearer <token>
{}Only the RFQ owner can cancel. Only Open or Quoted RFQs.
Quote endpoints
Submit a quote (maker)
POST /rfqs/:rfqId/quotes
Authorization: Bearer <token> (isMaker: true)
Content-Type: application/json
{
"idempotencyKey": "uuid",
"baseAmount": "1000000000000000000",
"quoteAmount": "6000000000",
"sellerFeeBps": 50,
"sellerNonce": "1",
"expiresInSeconds": 30,
"intentExpiry": "1729575000",
"makerSignature": "0x..."
}The makerSignature is an EIP-712 signature over the TradeIntent struct.
Accept a quote
POST /quotes/:quoteId/accept
Authorization: Bearer <token>
Content-Type: application/json
{
"idempotencyKey": "uuid"
}Returns { quoteId, status: "accepted", tradeId }.
EIP-712 TradeIntent schema
Domain
json
{
"name": "KrystalEscrow",
"version": "1",
"chainId": 8453,
"verifyingContract": "<EscrowSettlement address>"
}Types
json
{
"TradeIntent": [
{ "name": "buyer", "type": "address" },
{ "name": "seller", "type": "address" },
{ "name": "baseToken", "type": "address" },
{ "name": "quoteToken", "type": "address" },
{ "name": "baseAmount", "type": "uint256" },
{ "name": "quoteAmount", "type": "uint256" },
{ "name": "sellerFeeBps", "type": "uint16" },
{ "name": "buyerFeeBps", "type": "uint16" },
{ "name": "expiry", "type": "uint256" },
{ "name": "marketEpoch", "type": "uint256" },
{ "name": "sellerNonce", "type": "uint256" }
]
}Never include marketId or feeRecipient in the signed payload.
Trade hash
solidity
bytes32 tradeHash = _hashTypedDataV4(keccak256(abi.encode(
TRADE_INTENT_TYPEHASH,
intent.buyer,
intent.seller,
intent.baseToken,
intent.quoteToken,
intent.baseAmount,
intent.quoteAmount,
intent.sellerFeeBps,
intent.buyerFeeBps,
intent.expiry,
intent.marketEpoch,
intent.sellerNonce
)));Fee calculation
Seller-only mode (MVP)
seller_fee = max(quoteAmount * sellerFeeBps / 10_000, minimumFee)
buyer_pays = quoteAmount
seller_receives = quoteAmount - seller_fee
platform_fee = seller_feeMaximum combined fee: 500 bps (hardcoded immutable cap).
Error codes
| Code | HTTP | Meaning |
|---|---|---|
MARKET_NOT_ACTIVE | 422 | Market is pending, blocked, or disabled |
RFQ_EXPIRED | 422 | RFQ has expired |
QUOTE_EXPIRED | 422 | Quote has expired |
QUOTE_NOT_FOUND | 404 | Quote ID unknown |
NONCE_USED | 409 | Seller nonce already consumed |
FEE_BELOW_MINIMUM | 422 | Fee below registry minimum |
FEE_EXCEEDS_CAP | 422 | Combined fee > 500 bps |
COMPLIANCE_REJECTED | 403 | Wallet failed screening |
SIMULATION_FAILED | 422 | Tx simulation reverted |
INSUFFICIENT_ALLOWANCE | 422 | Insufficient token allowance |
INSUFFICIENT_BALANCE | 422 | Insufficient token balance |
INVALID_STATUS | 422 | Action not allowed in current state |
FORBIDDEN | 403 | Not authorized |
INVALID_SIGNATURE | 422 | Signature doesn't match signer |
WALLET_INELIGIBLE | 403 | Jurisdiction or KYC tier blocked |
INTERNAL_ERROR | 500 | Unhandled server error |
Testnet addresses
| Contract | Address |
|---|---|
| EscrowSettlement | 0x2421F2A499edAb1D1dDDf6053de0Cb82E332858E |
| MarketRegistry | 0x69a0d8fb1301D7134ECB2AB5c272a46B5294E8f6 |
| FeeVault | 0xc202D91822b5e1b58219dcAdC2C87eAb6A027847 |
RPC: https://sepolia.base.org