Pricing Engines
The Maker SDK handles all the plumbing — WebSocket, API-key auth, ed25519 quote signing in the exact XDR the contract verifies, inventory reads, and trade confirmations. You only decide how to price, and that lives in a pluggable MakerEngine.
The engine interface
An engine is an object that answers two questions. The SDK calls it; you never touch the network or the signer.
| Method | Called | Returns |
|---|---|---|
| getLevels() | every ~3s | your resting book { sellLevels, buyLevels } (empty arrays → go offline gracefully) |
| getQuote(ctx) | on each RFQ | amountOut in stroops as a string, or null to skip this trade (no penalty) |
| onTradeConfirmed(trade) | when a fill settles (optional) | nothing — refresh inventory, hedge on a CEX, log |
Tier 1 — Built-in ghost-price engine (default, no code)
npm run dev <yourname>Set one ghost price (EURC per USDC); the SDK auto-bids it, fee-adjusted, on every RFQ. It is gated by an inventory check (never quotes more than ~80% of your pool balance) and a drift guard — warns at >1% drift from the live oracle mid, pauses quoting at >3%. Press Ctrl+R to re-price, Ctrl+C to disconnect.
Tier 2 / 3 — Custom engine
Point the SDK at any engine file with --engine. Note the -- separator — without it, npm swallows the flag:
npm run dev <yourname> -- --engine=./examples/fixed-rate-engine.ts
npm run dev <yourname> -- --engine=./examples/binance-engine.ts
npm run dev <yourname> -- --engine=./path/to/your-engine.tsA custom engine owns its full pricing logic, so the SDK skips the ghost-price prompt and Ctrl+R. If the file is missing or doesn't implement getLevels/getQuote, the SDK logs the error and falls back to the built-in engine — it won't crash. Confirm which one loaded from the Engine: line in the startup banner.
Writing your own
// my-engine.ts
import { MakerEngine, RfqContext, PriceLevels } from '../src/types/MakerEngine'
const engine: MakerEngine = {
async getLevels(): Promise<PriceLevels> {
return {
sellLevels: [{ quantity: '1000000000', price: '0.87800000' }], // USDC→EURC
buyLevels: [{ quantity: '1000000000', price: '1.13800000' }], // EURC→USDC
}
},
async getQuote(ctx: RfqContext): Promise<string | null> {
const rate = ctx.tokenInSymbol === 'USDC' ? 0.8780 : 1 / 0.8780
const feeAdj = 1 - ctx.feesBps * 0.0001 // protocol fee
const out = Math.floor(ctx.amountInHuman * rate * feeAdj * 1e7)
return out > 0 ? out.toString() : null // null = skip
},
}
export default engineEURUSDT (~1.14 = USDT per EUR), the USDC→EURC rate is 1 / price, not price. 2. Inventory. The SDK does not stop a custom engine from quoting more than your pool holds — quoting size you can't fill makes the on-chain swap revert. Read your balance and cap your quote (the default engine does this automatically).Working templates live in maker-sdk/examples/. Full guides: maker-sdk/CUSTOM_ENGINE.md (building engines) and maker-sdk/TESTING_ENGINES.md (E2E-testing + pitfalls).