Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ SNAPSHOT_CACHE_TTL=15
# How long an API-key lookup is cached in-process, in seconds (default: 30).
# Worst-case window for a key revoked on another instance to stop working here.
API_KEY_CACHE_TTL=30
# Maximum age in seconds before /premium price data is considered stale and rejected (default: 60)
PREMIUM_STALENESS_SECONDS=60
# Default timeout for outbound HTTP requests in milliseconds (default: 10000)
HTTP_TIMEOUT_MS=10000

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ npx @modelcontextprotocol/inspector npm run mcp:server
| GET | `/v1/status` | Get API status and indexer state |
| GET | `/v1/snapshot` | Whole-network snapshot (venues, TVL, 24h volume, top pairs, pegs, freshness) |
| GET | `/v1/price/:assetA/:assetB` | Get aggregated price for a pair |
| GET | `/v1/premium/:assetA/:assetB` | Get on-chain vs CEX price premium and spread |
| GET | `/v1/price/:assetA/:assetB/route` | Get best swap route for a pair and amount |
| GET | `/v1/price/:asset/fiat` | Convert asset price to fiat currency with NGN support (#290) |
| GET | `/v1/price/:assetA/:assetB/history` | Get price history buckets |
Expand Down
94 changes: 94 additions & 0 deletions clients/typescript/src/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,26 @@ export interface paths {
patch?: never;
trace?: never;
};
"/v1/premium/{assetA}/{assetB}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* Get on-chain vs CEX price premium and spread
* @description Returns the on-chain price, the best CEX price, the spread in basis points, both sources, and both timestamps. Refuses to answer with a 400 when either side is older than the configured staleness bound.
*/
get: operations["getPremium"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/price/{assetA}/{assetB}/route": {
parameters: {
query?: never;
Expand Down Expand Up @@ -1606,6 +1626,29 @@ export interface components {
} & {
[key: string]: unknown;
};
PremiumPricePoint: {
price: number;
source: string;
/** Format: date-time */
timestamp: string;
};
PremiumResponse: {
assetA: string;
assetB: string;
network: string;
onChain: components["schemas"]["PremiumPricePoint"];
cex: components["schemas"]["PremiumPricePoint"];
spreadBps: number;
};
PremiumErrorResponse: {
error: string;
code?: string;
/** @enum {string} */
stale?: "onChain" | "cex" | "both";
ageSeconds?: number | null;
onChainAgeSeconds?: number | null;
cexAgeSeconds?: number | null;
};
};
responses: {
/** @description Payment Required */
Expand Down Expand Up @@ -1999,6 +2042,57 @@ export interface operations {
429: components["responses"]["TooManyRequests"];
};
};
getPremium: {
parameters: {
query?: {
/** @description Stellar network to answer for — `testnet` or `mainnet`. Defaults to this instance's configured `STELLAR_NETWORK`. An unrecognised value is rejected with 400. DB-backed reads are currently served from whichever network this instance indexes, even when another is requested. */
network?: components["parameters"]["NetworkQuery"];
};
header?: never;
path: {
/** @example XLM */
assetA: string;
/** @example USDC */
assetB: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Premium and spread calculation */
200: {
headers: {
"Cache-Control": components["headers"]["CacheControl"];
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PremiumResponse"];
};
};
/** @description Stale price data or unrecognised network */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PremiumErrorResponse"];
};
};
429: components["responses"]["TooManyRequests"];
/** @description Internal server error */
500: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
error: string;
code: string;
};
};
};
};
};
getBestRoute: {
parameters: {
query?: {
Expand Down
168 changes: 168 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,89 @@
}
}
},
"/v1/premium/{assetA}/{assetB}": {
"get": {
"operationId": "getPremium",
"summary": "Get on-chain vs CEX price premium and spread",
"description": "Returns the on-chain price, the best CEX price, the spread in basis points, both sources, and both timestamps. Refuses to answer with a 400 when either side is older than the configured staleness bound.",
"security": [],
"parameters": [
{
"name": "assetA",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"example": "XLM"
},
{
"name": "assetB",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"example": "USDC"
},
{
"$ref": "#/components/parameters/NetworkQuery"
}
],
"responses": {
"200": {
"description": "Premium and spread calculation",
"headers": {
"Cache-Control": {
"$ref": "#/components/headers/CacheControl"
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PremiumResponse"
}
}
}
},
"400": {
"description": "Stale price data or unrecognised network",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PremiumErrorResponse"
}
}
}
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error",
"code"
],
"properties": {
"error": {
"type": "string"
},
"code": {
"type": "string"
}
}
}
}
}
}
}
}
},
"/v1/price/{assetA}/{assetB}/route": {
"get": {
"operationId": "getBestRoute",
Expand Down Expand Up @@ -6507,6 +6590,91 @@
}
}
}
},
"PremiumPricePoint": {
"type": "object",
"required": [
"price",
"source",
"timestamp"
],
"properties": {
"price": {
"type": "number"
},
"source": {
"type": "string"
},
"timestamp": {
"type": "string",
"format": "date-time"
}
}
},
"PremiumResponse": {
"type": "object",
"required": [
"assetA",
"assetB",
"network",
"onChain",
"cex",
"spreadBps"
],
"properties": {
"assetA": {
"type": "string"
},
"assetB": {
"type": "string"
},
"network": {
"type": "string"
},
"onChain": {
"$ref": "#/components/schemas/PremiumPricePoint"
},
"cex": {
"$ref": "#/components/schemas/PremiumPricePoint"
},
"spreadBps": {
"type": "number"
}
}
},
"PremiumErrorResponse": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "string"
},
"code": {
"type": "string"
},
"stale": {
"type": "string",
"enum": [
"onChain",
"cex",
"both"
]
},
"ageSeconds": {
"type": "integer",
"nullable": true
},
"onChainAgeSeconds": {
"type": "integer",
"nullable": true
},
"cexAgeSeconds": {
"type": "integer",
"nullable": true
}
}
}
}
}
Expand Down
Loading
Loading