obol-mcp 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +130 -0
- package/dist/app.js.map +1 -0
- package/dist/config/env.d.ts +47 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +63 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/pricing.d.ts +12 -0
- package/dist/config/pricing.d.ts.map +1 -0
- package/dist/config/pricing.js +99 -0
- package/dist/config/pricing.js.map +1 -0
- package/dist/mcp.d.ts +35 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +150 -0
- package/dist/mcp.js.map +1 -0
- package/dist/middleware/validation.d.ts +6 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +40 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/middleware/x402.d.ts +33 -0
- package/dist/middleware/x402.d.ts.map +1 -0
- package/dist/middleware/x402.js +294 -0
- package/dist/middleware/x402.js.map +1 -0
- package/dist/plugins/defi/routes.d.ts +12 -0
- package/dist/plugins/defi/routes.d.ts.map +1 -0
- package/dist/plugins/defi/routes.js +430 -0
- package/dist/plugins/defi/routes.js.map +1 -0
- package/dist/plugins/token/routes.d.ts +10 -0
- package/dist/plugins/token/routes.d.ts.map +1 -0
- package/dist/plugins/token/routes.js +111 -0
- package/dist/plugins/token/routes.js.map +1 -0
- package/dist/plugins/wallet/routes.d.ts +13 -0
- package/dist/plugins/wallet/routes.d.ts.map +1 -0
- package/dist/plugins/wallet/routes.js +235 -0
- package/dist/plugins/wallet/routes.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +38 -0
- package/dist/server.js.map +1 -0
- package/dist/services/cache.d.ts +25 -0
- package/dist/services/cache.d.ts.map +1 -0
- package/dist/services/cache.js +82 -0
- package/dist/services/cache.js.map +1 -0
- package/dist/services/helius.d.ts +102 -0
- package/dist/services/helius.d.ts.map +1 -0
- package/dist/services/helius.js +456 -0
- package/dist/services/helius.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +15 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/retry.d.ts +9 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +45 -0
- package/dist/utils/retry.js.map +1 -0
- package/package.json +81 -0
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Obol MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes Obol's Solana data endpoints as MCP tools that any
|
|
6
|
+
* AI agent can discover and call. Runs over stdio transport.
|
|
7
|
+
*
|
|
8
|
+
* The MCP server proxies requests to either:
|
|
9
|
+
* - A live Obol instance (default: production)
|
|
10
|
+
* - A local dev instance
|
|
11
|
+
*
|
|
12
|
+
* In MCP mode, the server operates in "proxy" payment mode:
|
|
13
|
+
* - If OBOL_URL points to a mock-mode instance, data is free
|
|
14
|
+
* - If OBOL_URL points to an onchain instance, the MCP server
|
|
15
|
+
* can optionally include a payment signature via AGENT_PRIVATE_KEY
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* npx tsx src/mcp.ts # stdio transport
|
|
19
|
+
* OBOL_URL=http://localhost:3000 npx tsx src/mcp.ts # local dev
|
|
20
|
+
*
|
|
21
|
+
* Claude Desktop config (~/.claude/claude_desktop_config.json):
|
|
22
|
+
* {
|
|
23
|
+
* "mcpServers": {
|
|
24
|
+
* "obol": {
|
|
25
|
+
* "command": "npx",
|
|
26
|
+
* "args": ["tsx", "/path/to/obol/src/mcp.ts"],
|
|
27
|
+
* "env": {
|
|
28
|
+
* "OBOL_URL": "https://obol-production.up.railway.app"
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
35
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
36
|
+
import { z } from 'zod';
|
|
37
|
+
const OBOL_URL = process.env.OBOL_URL ?? 'https://obol-production.up.railway.app';
|
|
38
|
+
// ── Helpers ──
|
|
39
|
+
async function obolFetch(path, options) {
|
|
40
|
+
const url = `${OBOL_URL}${path}`;
|
|
41
|
+
// For MCP, we call the API without payment headers.
|
|
42
|
+
// If the server is in mock mode, it returns data.
|
|
43
|
+
// If in onchain mode, it returns 402 with pricing info.
|
|
44
|
+
const res = await fetch(url, {
|
|
45
|
+
method: options?.method ?? 'GET',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: options?.body ? JSON.stringify(options.body) : undefined,
|
|
48
|
+
});
|
|
49
|
+
const data = await res.json();
|
|
50
|
+
return { status: res.status, data };
|
|
51
|
+
}
|
|
52
|
+
function formatResult(result) {
|
|
53
|
+
if (result.status === 402) {
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
error: 'Payment required',
|
|
56
|
+
message: 'This endpoint requires USDC payment via x402. The Obol instance is running in onchain mode.',
|
|
57
|
+
paymentInfo: result.data,
|
|
58
|
+
}, null, 2);
|
|
59
|
+
}
|
|
60
|
+
return JSON.stringify(result.data, null, 2);
|
|
61
|
+
}
|
|
62
|
+
// ── Server ──
|
|
63
|
+
const server = new McpServer({
|
|
64
|
+
name: 'obol',
|
|
65
|
+
version: '2.0.0',
|
|
66
|
+
});
|
|
67
|
+
// ── Wallet Tools ──
|
|
68
|
+
server.tool('obol_wallet_overview', 'Get a Solana wallet overview — SOL balance, token count, total value. Costs $0.01 USDC.', { address: z.string().describe('Solana wallet address') }, async ({ address }) => {
|
|
69
|
+
const result = await obolFetch(`/api/v1/wallet/${address}/overview`);
|
|
70
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
71
|
+
});
|
|
72
|
+
server.tool('obol_wallet_portfolio', 'Get full wallet portfolio — all token holdings with prices, NFTs, and breakdown. Costs $0.05 USDC.', { address: z.string().describe('Solana wallet address') }, async ({ address }) => {
|
|
73
|
+
const result = await obolFetch(`/api/v1/wallet/${address}/portfolio`);
|
|
74
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
75
|
+
});
|
|
76
|
+
server.tool('obol_wallet_activity', 'Get wallet transaction history with categorization (swaps, transfers, etc). Costs $0.05 USDC.', { address: z.string().describe('Solana wallet address') }, async ({ address }) => {
|
|
77
|
+
const result = await obolFetch(`/api/v1/wallet/${address}/activity`);
|
|
78
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
79
|
+
});
|
|
80
|
+
server.tool('obol_wallet_risk', 'Multi-factor risk assessment for a Solana wallet — age, diversification, activity patterns. Costs $0.10 USDC.', { address: z.string().describe('Solana wallet address') }, async ({ address }) => {
|
|
81
|
+
const result = await obolFetch(`/api/v1/wallet/${address}/risk`);
|
|
82
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
83
|
+
});
|
|
84
|
+
server.tool('obol_wallet_pnl', 'Wallet P&L analysis — token flows, current values, transaction history analysis. Costs $0.15 USDC.', { address: z.string().describe('Solana wallet address') }, async ({ address }) => {
|
|
85
|
+
const result = await obolFetch(`/api/v1/wallet/${address}/pnl`);
|
|
86
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
87
|
+
});
|
|
88
|
+
// ── Token Tools ──
|
|
89
|
+
server.tool('obol_token_price', 'Get real-time token price via Jupiter. Costs $0.005 USDC.', { mint: z.string().describe('Token mint address (e.g., USDC: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v)') }, async ({ mint }) => {
|
|
90
|
+
const result = await obolFetch(`/api/v1/token/${mint}/price`);
|
|
91
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
92
|
+
});
|
|
93
|
+
server.tool('obol_token_metadata', 'Get token metadata — name, symbol, supply, decimals. Costs $0.01 USDC.', { mint: z.string().describe('Token mint address') }, async ({ mint }) => {
|
|
94
|
+
const result = await obolFetch(`/api/v1/token/${mint}/metadata`);
|
|
95
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
96
|
+
});
|
|
97
|
+
// ── DeFi Tools ──
|
|
98
|
+
server.tool('obol_swap_quote', 'Get a Jupiter swap quote with route planning and price impact. Costs $0.005 USDC.', {
|
|
99
|
+
inputMint: z.string().describe('Input token mint address (e.g., SOL: So11111111111111111111111111111111111111112)'),
|
|
100
|
+
outputMint: z.string().describe('Output token mint address'),
|
|
101
|
+
amount: z.string().describe('Amount in atomic units (e.g., 1000000000 for 1 SOL)'),
|
|
102
|
+
slippageBps: z.string().optional().describe('Slippage tolerance in basis points (default: 50 = 0.5%)'),
|
|
103
|
+
}, async ({ inputMint, outputMint, amount, slippageBps }) => {
|
|
104
|
+
const params = new URLSearchParams({ inputMint, outputMint, amount });
|
|
105
|
+
if (slippageBps)
|
|
106
|
+
params.set('slippageBps', slippageBps);
|
|
107
|
+
const result = await obolFetch(`/api/v1/defi/swap/quote?${params}`);
|
|
108
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
109
|
+
});
|
|
110
|
+
server.tool('obol_swap_execute', 'Build a Jupiter swap transaction for signing. Returns a serialized transaction the agent must sign and submit. Costs $0.25 USDC.', {
|
|
111
|
+
inputMint: z.string().describe('Input token mint address'),
|
|
112
|
+
outputMint: z.string().describe('Output token mint address'),
|
|
113
|
+
amount: z.string().describe('Amount in atomic units'),
|
|
114
|
+
userPublicKey: z.string().describe('The wallet public key that will sign and submit the transaction'),
|
|
115
|
+
slippageBps: z.string().optional().describe('Slippage in basis points (default: 50)'),
|
|
116
|
+
priorityFee: z.string().optional().describe('Priority fee in lamports (default: auto)'),
|
|
117
|
+
}, async ({ inputMint, outputMint, amount, userPublicKey, slippageBps, priorityFee }) => {
|
|
118
|
+
const result = await obolFetch('/api/v1/defi/swap/execute', {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
body: { inputMint, outputMint, amount, userPublicKey, slippageBps, priorityFee },
|
|
121
|
+
});
|
|
122
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
123
|
+
});
|
|
124
|
+
server.tool('obol_defi_positions', 'Get DeFi positions for a wallet — LSTs, LP tokens, lending positions, categorized by protocol. Costs $0.10 USDC.', { address: z.string().describe('Solana wallet address') }, async ({ address }) => {
|
|
125
|
+
const result = await obolFetch(`/api/v1/defi/positions/${address}`);
|
|
126
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
127
|
+
});
|
|
128
|
+
server.tool('obol_lst_yields', 'Compare LST yields across Solana — jitoSOL, mSOL, bSOL, jupSOL, hSOL, and more. APYs from Sanctum, exchange rates from Jupiter. Costs $0.02 USDC.', {}, async () => {
|
|
129
|
+
const result = await obolFetch('/api/v1/defi/lst/yields');
|
|
130
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
131
|
+
});
|
|
132
|
+
// ── Free Tools ──
|
|
133
|
+
server.tool('obol_health', 'Check Obol API health and status. Free.', {}, async () => {
|
|
134
|
+
const result = await obolFetch('/health');
|
|
135
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
136
|
+
});
|
|
137
|
+
server.tool('obol_info', 'Get Obol API info — all available endpoints, pricing, and payment mode. Free.', {}, async () => {
|
|
138
|
+
const result = await obolFetch('/');
|
|
139
|
+
return { content: [{ type: 'text', text: formatResult(result) }] };
|
|
140
|
+
});
|
|
141
|
+
// ── Start ──
|
|
142
|
+
async function main() {
|
|
143
|
+
const transport = new StdioServerTransport();
|
|
144
|
+
await server.connect(transport);
|
|
145
|
+
}
|
|
146
|
+
main().catch(err => {
|
|
147
|
+
console.error('Obol MCP server error:', err);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|
|
150
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
/** Validate :address param is a valid Solana public key */
|
|
3
|
+
export declare function validateSolanaAddress(request: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
4
|
+
/** Validate :mint param is a valid Solana public key (token mint) */
|
|
5
|
+
export declare function validateMintAddress(request: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/middleware/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAG5D,2DAA2D;AAC3D,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,qEAAqE;AACrE,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAkBf"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PublicKey } from '@solana/web3.js';
|
|
2
|
+
/** Validate :address param is a valid Solana public key */
|
|
3
|
+
export async function validateSolanaAddress(request, reply) {
|
|
4
|
+
const { address } = request.params;
|
|
5
|
+
if (!address) {
|
|
6
|
+
return reply.code(400).send({
|
|
7
|
+
error: 'Bad Request',
|
|
8
|
+
message: 'Missing wallet address',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
new PublicKey(address);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return reply.code(400).send({
|
|
16
|
+
error: 'Bad Request',
|
|
17
|
+
message: `Invalid Solana address: ${address}`,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Validate :mint param is a valid Solana public key (token mint) */
|
|
22
|
+
export async function validateMintAddress(request, reply) {
|
|
23
|
+
const { mint } = request.params;
|
|
24
|
+
if (!mint) {
|
|
25
|
+
return reply.code(400).send({
|
|
26
|
+
error: 'Bad Request',
|
|
27
|
+
message: 'Missing token mint address',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
new PublicKey(mint);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return reply.code(400).send({
|
|
35
|
+
error: 'Bad Request',
|
|
36
|
+
message: `Invalid token mint: ${mint}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/middleware/validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAAuB,EACvB,KAAmB;IAEnB,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAA8B,CAAC;IAE3D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,wBAAwB;SAClC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC;QACH,IAAI,SAAS,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,2BAA2B,OAAO,EAAE;SAC9C,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAuB,EACvB,KAAmB;IAEnB,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAA2B,CAAC;IAErD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,4BAA4B;SACtC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC;QACH,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YAC1B,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,uBAAuB,IAAI,EAAE;SACvC,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402 Payment Middleware for Fastify
|
|
3
|
+
*
|
|
4
|
+
* Built on the @x402/core SDK for protocol-compliant header encoding/decoding.
|
|
5
|
+
* Uses @x402/svm utilities for Solana-specific address validation.
|
|
6
|
+
*
|
|
7
|
+
* Architecture note:
|
|
8
|
+
* The full @x402 SDK uses a Facilitator model (verify + settle via a remote
|
|
9
|
+
* or local facilitator service). For Obol, we implement direct
|
|
10
|
+
* on-chain verification because we want zero third-party dependencies in
|
|
11
|
+
* the payment path. The SDK's header format and protocol types are used
|
|
12
|
+
* to ensure protocol compliance.
|
|
13
|
+
*
|
|
14
|
+
* When the x402 ecosystem matures further, we can swap in the SDK's
|
|
15
|
+
* x402HTTPResourceServer for full protocol support with one line change.
|
|
16
|
+
*/
|
|
17
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
18
|
+
export interface PaymentInfo {
|
|
19
|
+
wallet: string;
|
|
20
|
+
amount: number;
|
|
21
|
+
currency: string;
|
|
22
|
+
verifiedAt: string;
|
|
23
|
+
txSignature?: string;
|
|
24
|
+
network?: string;
|
|
25
|
+
mode: 'mock' | 'onchain';
|
|
26
|
+
}
|
|
27
|
+
declare module 'fastify' {
|
|
28
|
+
interface FastifyRequest {
|
|
29
|
+
payment?: PaymentInfo;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export declare function x402PaymentMiddleware(request: FastifyRequest, reply: FastifyReply): Promise<void>;
|
|
33
|
+
//# sourceMappingURL=x402.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402.d.ts","sourceRoot":"","sources":["../../src/middleware/x402.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAuB5D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,OAAO,CAAC,EAAE,WAAW,CAAC;KACvB;CACF;AAmCD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAqQf"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* x402 Payment Middleware for Fastify
|
|
3
|
+
*
|
|
4
|
+
* Built on the @x402/core SDK for protocol-compliant header encoding/decoding.
|
|
5
|
+
* Uses @x402/svm utilities for Solana-specific address validation.
|
|
6
|
+
*
|
|
7
|
+
* Architecture note:
|
|
8
|
+
* The full @x402 SDK uses a Facilitator model (verify + settle via a remote
|
|
9
|
+
* or local facilitator service). For Obol, we implement direct
|
|
10
|
+
* on-chain verification because we want zero third-party dependencies in
|
|
11
|
+
* the payment path. The SDK's header format and protocol types are used
|
|
12
|
+
* to ensure protocol compliance.
|
|
13
|
+
*
|
|
14
|
+
* When the x402 ecosystem matures further, we can swap in the SDK's
|
|
15
|
+
* x402HTTPResourceServer for full protocol support with one line change.
|
|
16
|
+
*/
|
|
17
|
+
import { PublicKey } from '@solana/web3.js';
|
|
18
|
+
import { config } from '../config/env.js';
|
|
19
|
+
import { getEndpointPrice, requiresPayment } from '../config/pricing.js';
|
|
20
|
+
import { cache } from '../services/cache.js';
|
|
21
|
+
import { helius } from '../services/helius.js';
|
|
22
|
+
import { logger } from '../utils/logger.js';
|
|
23
|
+
// SDK utilities for protocol compliance
|
|
24
|
+
import { validateSvmAddress, USDC_MAINNET_ADDRESS, SOLANA_MAINNET_CAIP2 } from '@x402/svm';
|
|
25
|
+
// ──────────────────────────────────────────────
|
|
26
|
+
// Constants
|
|
27
|
+
// ──────────────────────────────────────────────
|
|
28
|
+
const X402_VERSION = 2; // SDK v2 protocol
|
|
29
|
+
const MAX_TX_AGE_SECONDS = 300;
|
|
30
|
+
// ──────────────────────────────────────────────
|
|
31
|
+
// Helpers
|
|
32
|
+
// ──────────────────────────────────────────────
|
|
33
|
+
function usdcToAtomicUnits(usdcAmount) {
|
|
34
|
+
return Math.floor(usdcAmount * 1_000_000).toString();
|
|
35
|
+
}
|
|
36
|
+
function generateMemo() {
|
|
37
|
+
return `obol_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
38
|
+
}
|
|
39
|
+
/** Build a protocol-compliant payment requirement */
|
|
40
|
+
function buildPaymentRequirement(resource, priceUSDC) {
|
|
41
|
+
return {
|
|
42
|
+
scheme: 'exact',
|
|
43
|
+
network: SOLANA_MAINNET_CAIP2,
|
|
44
|
+
maxAmountRequired: usdcToAtomicUnits(priceUSDC),
|
|
45
|
+
resource,
|
|
46
|
+
description: `Obol: ${resource}`,
|
|
47
|
+
mimeType: 'application/json',
|
|
48
|
+
outputSchema: {},
|
|
49
|
+
payTo: config.payment.recipientAddress,
|
|
50
|
+
maxTimeoutSeconds: MAX_TX_AGE_SECONDS,
|
|
51
|
+
asset: USDC_MAINNET_ADDRESS,
|
|
52
|
+
extra: { memo: generateMemo() },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ──────────────────────────────────────────────
|
|
56
|
+
// Middleware
|
|
57
|
+
// ──────────────────────────────────────────────
|
|
58
|
+
export async function x402PaymentMiddleware(request, reply) {
|
|
59
|
+
const { url } = request;
|
|
60
|
+
if (!requiresPayment(url))
|
|
61
|
+
return;
|
|
62
|
+
let price;
|
|
63
|
+
try {
|
|
64
|
+
price = getEndpointPrice(url);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return reply.code(404).send({ error: 'Not Found', message: 'Endpoint not configured' });
|
|
68
|
+
}
|
|
69
|
+
// Check for payment header (SDK uses X-PAYMENT or PAYMENT-SIGNATURE)
|
|
70
|
+
const paymentHeader = (request.headers['x-payment'] ?? request.headers['payment-signature']);
|
|
71
|
+
if (!paymentHeader) {
|
|
72
|
+
const requirement = buildPaymentRequirement(url, price);
|
|
73
|
+
const paymentRequired = {
|
|
74
|
+
x402Version: X402_VERSION,
|
|
75
|
+
error: 'Payment Required',
|
|
76
|
+
accepts: [requirement],
|
|
77
|
+
};
|
|
78
|
+
logger.info({ url, price }, '402: Payment required');
|
|
79
|
+
// Set protocol-compliant header + JSON body
|
|
80
|
+
reply.header('X-PAYMENT-REQUIRED', Buffer.from(JSON.stringify(paymentRequired)).toString('base64'));
|
|
81
|
+
return reply.code(402).send(paymentRequired);
|
|
82
|
+
}
|
|
83
|
+
// Decode payment header (base64 JSON)
|
|
84
|
+
let payment;
|
|
85
|
+
try {
|
|
86
|
+
const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');
|
|
87
|
+
payment = JSON.parse(decoded);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return reply.code(400).send({
|
|
91
|
+
x402Version: X402_VERSION,
|
|
92
|
+
error: 'Invalid payment header — could not decode',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const payload = payment.payload;
|
|
96
|
+
if (!payload) {
|
|
97
|
+
return reply.code(400).send({
|
|
98
|
+
x402Version: X402_VERSION,
|
|
99
|
+
error: 'Invalid payment: missing payload',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
// ── Mock mode ──
|
|
103
|
+
if (config.payment.mode === 'mock') {
|
|
104
|
+
logger.debug({ url }, 'Mock: auto-approving');
|
|
105
|
+
const walletId = payload.fromAddress ?? payload.transaction?.slice(0, 20) ?? 'mock-wallet';
|
|
106
|
+
request.payment = {
|
|
107
|
+
wallet: walletId,
|
|
108
|
+
amount: price,
|
|
109
|
+
currency: 'USDC',
|
|
110
|
+
verifiedAt: new Date().toISOString(),
|
|
111
|
+
mode: 'mock',
|
|
112
|
+
};
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// ── On-chain mode ──
|
|
116
|
+
// SDK v2 sends { payload: { transaction: "base64..." } }
|
|
117
|
+
// Legacy v1 sends { payload: { signature: "base58...", fromAddress: "..." } }
|
|
118
|
+
const txSignature = payload.signature ?? payload.transaction;
|
|
119
|
+
const fromAddress = payload.fromAddress;
|
|
120
|
+
if (!txSignature) {
|
|
121
|
+
return reply.code(400).send({
|
|
122
|
+
x402Version: X402_VERSION,
|
|
123
|
+
error: 'Missing transaction signature in payment payload',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Validate sender address if provided
|
|
127
|
+
if (fromAddress && !validateSvmAddress(fromAddress)) {
|
|
128
|
+
return reply.code(400).send({
|
|
129
|
+
x402Version: X402_VERSION,
|
|
130
|
+
error: 'Invalid sender address',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// ── Replay prevention: check if this tx signature was already used ──
|
|
134
|
+
const txKey = `payment:tx:${txSignature}`;
|
|
135
|
+
if (await cache.exists(txKey)) {
|
|
136
|
+
logger.warn({ txSignature: String(txSignature).slice(0, 20) }, 'Replay rejected');
|
|
137
|
+
return reply.code(400).send({
|
|
138
|
+
x402Version: X402_VERSION,
|
|
139
|
+
error: 'Transaction already used for a previous payment',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// ── Fetch parsed transaction from Solana via Helius ──
|
|
143
|
+
let parsedTx;
|
|
144
|
+
try {
|
|
145
|
+
const conn = helius.getConnection();
|
|
146
|
+
parsedTx = await conn.getParsedTransaction(String(txSignature), {
|
|
147
|
+
maxSupportedTransactionVersion: 0,
|
|
148
|
+
commitment: 'confirmed',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
logger.error({ err, txSignature: String(txSignature).slice(0, 20) }, 'Failed to fetch transaction');
|
|
153
|
+
return reply.code(502).send({
|
|
154
|
+
x402Version: X402_VERSION,
|
|
155
|
+
error: 'Could not verify transaction — RPC error',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (!parsedTx) {
|
|
159
|
+
return reply.code(400).send({
|
|
160
|
+
x402Version: X402_VERSION,
|
|
161
|
+
error: 'Transaction not found on-chain. It may not be confirmed yet — retry in a few seconds.',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// ── Verify transaction succeeded ──
|
|
165
|
+
if (parsedTx.meta?.err) {
|
|
166
|
+
return reply.code(400).send({
|
|
167
|
+
x402Version: X402_VERSION,
|
|
168
|
+
error: 'Transaction failed on-chain',
|
|
169
|
+
details: parsedTx.meta.err,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// ── Verify transaction age ──
|
|
173
|
+
const txTimestamp = parsedTx.blockTime;
|
|
174
|
+
if (txTimestamp) {
|
|
175
|
+
const ageSeconds = Math.floor(Date.now() / 1000) - txTimestamp;
|
|
176
|
+
if (ageSeconds > MAX_TX_AGE_SECONDS) {
|
|
177
|
+
return reply.code(400).send({
|
|
178
|
+
x402Version: X402_VERSION,
|
|
179
|
+
error: `Transaction too old (${ageSeconds}s). Must be under ${MAX_TX_AGE_SECONDS}s.`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (ageSeconds < -60) {
|
|
183
|
+
// Clock skew guard — reject transactions "from the future" beyond 60s tolerance
|
|
184
|
+
return reply.code(400).send({
|
|
185
|
+
x402Version: X402_VERSION,
|
|
186
|
+
error: 'Transaction timestamp is in the future',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// ── Find the USDC transfer to our merchant wallet ──
|
|
191
|
+
const requiredAtomicUnits = BigInt(usdcToAtomicUnits(price));
|
|
192
|
+
const recipientPubkey = config.payment.recipientAddress;
|
|
193
|
+
const usdcMint = USDC_MAINNET_ADDRESS;
|
|
194
|
+
let verifiedAmount = BigInt(0);
|
|
195
|
+
let verifiedSender = 'unknown';
|
|
196
|
+
const innerInstructions = parsedTx.meta?.innerInstructions ?? [];
|
|
197
|
+
const allInstructions = [
|
|
198
|
+
...parsedTx.transaction.message.instructions,
|
|
199
|
+
...innerInstructions.flatMap(ix => ix.instructions),
|
|
200
|
+
];
|
|
201
|
+
for (const ix of allInstructions) {
|
|
202
|
+
// We need parsed instructions from the SPL Token program
|
|
203
|
+
if (!('parsed' in ix))
|
|
204
|
+
continue;
|
|
205
|
+
const parsed = ix;
|
|
206
|
+
if (parsed.program !== 'spl-token')
|
|
207
|
+
continue;
|
|
208
|
+
const { type, info } = parsed.parsed;
|
|
209
|
+
// Match transfer or transferChecked
|
|
210
|
+
if (type !== 'transfer' && type !== 'transferChecked')
|
|
211
|
+
continue;
|
|
212
|
+
const destination = info.destination ?? info.account;
|
|
213
|
+
const amount = info.amount ?? info.tokenAmount?.amount;
|
|
214
|
+
const mint = info.mint;
|
|
215
|
+
if (!destination || !amount)
|
|
216
|
+
continue;
|
|
217
|
+
// For transferChecked, mint is in the instruction — reject if wrong mint.
|
|
218
|
+
// For plain transfer, mint is absent — we verify via the token account below.
|
|
219
|
+
if (mint && mint !== usdcMint)
|
|
220
|
+
continue;
|
|
221
|
+
// For plain transfers without mint field, we MUST verify via token account.
|
|
222
|
+
// The token account lookup below enforces mint == USDC as a hard requirement.
|
|
223
|
+
// Resolve destination token account → check if owner is our merchant wallet
|
|
224
|
+
// The destination in SPL transfer is the token account, not the wallet.
|
|
225
|
+
// We check if the recipient token account belongs to our merchant.
|
|
226
|
+
try {
|
|
227
|
+
const destAccountInfo = await helius.getConnection().getParsedAccountInfo(new PublicKey(destination));
|
|
228
|
+
if (!destAccountInfo.value)
|
|
229
|
+
continue;
|
|
230
|
+
const accountData = destAccountInfo.value.data;
|
|
231
|
+
if (!('parsed' in accountData))
|
|
232
|
+
continue;
|
|
233
|
+
const tokenAccountInfo = accountData.parsed;
|
|
234
|
+
const owner = tokenAccountInfo.info?.owner;
|
|
235
|
+
const accountMint = tokenAccountInfo.info?.mint;
|
|
236
|
+
// Verify: token account is owned by our wallet AND it's USDC
|
|
237
|
+
// CRITICAL: both checks are mandatory — if either is missing, reject
|
|
238
|
+
if (owner !== recipientPubkey)
|
|
239
|
+
continue;
|
|
240
|
+
if (!accountMint || accountMint !== usdcMint)
|
|
241
|
+
continue;
|
|
242
|
+
verifiedAmount += BigInt(amount);
|
|
243
|
+
verifiedSender = info.authority ?? info.source ?? fromAddress ?? 'unknown';
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// If we can't resolve the account, skip this instruction
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (verifiedAmount < requiredAtomicUnits) {
|
|
251
|
+
logger.warn({
|
|
252
|
+
txSignature: String(txSignature).slice(0, 20),
|
|
253
|
+
required: requiredAtomicUnits.toString(),
|
|
254
|
+
found: verifiedAmount.toString(),
|
|
255
|
+
}, 'Insufficient USDC payment');
|
|
256
|
+
return reply.code(402).send({
|
|
257
|
+
x402Version: X402_VERSION,
|
|
258
|
+
error: 'Insufficient payment',
|
|
259
|
+
required: requiredAtomicUnits.toString(),
|
|
260
|
+
received: verifiedAmount.toString(),
|
|
261
|
+
currency: 'USDC',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// ── Payment verified — record receipt and pass through ──
|
|
265
|
+
logger.info({
|
|
266
|
+
url,
|
|
267
|
+
txSignature: String(txSignature).slice(0, 20) + '...',
|
|
268
|
+
amount: price,
|
|
269
|
+
sender: verifiedSender,
|
|
270
|
+
}, 'Payment verified on-chain');
|
|
271
|
+
// Record by tx signature for replay prevention (24h TTL)
|
|
272
|
+
await cache.set(txKey, {
|
|
273
|
+
txSignature: String(txSignature),
|
|
274
|
+
fromAddress: verifiedSender,
|
|
275
|
+
amount: price,
|
|
276
|
+
endpoint: url,
|
|
277
|
+
timestamp: new Date().toISOString(),
|
|
278
|
+
}, 86400);
|
|
279
|
+
request.payment = {
|
|
280
|
+
wallet: verifiedSender,
|
|
281
|
+
amount: price,
|
|
282
|
+
currency: 'USDC',
|
|
283
|
+
verifiedAt: new Date().toISOString(),
|
|
284
|
+
txSignature: String(txSignature),
|
|
285
|
+
network: SOLANA_MAINNET_CAIP2,
|
|
286
|
+
mode: 'onchain',
|
|
287
|
+
};
|
|
288
|
+
reply.header('X-PAYMENT-RESPONSE', Buffer.from(JSON.stringify({
|
|
289
|
+
success: true,
|
|
290
|
+
txHash: txSignature,
|
|
291
|
+
networkId: SOLANA_MAINNET_CAIP2,
|
|
292
|
+
})).toString('base64'));
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=x402.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x402.js","sourceRoot":"","sources":["../../src/middleware/x402.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
/**
|
|
3
|
+
* DeFi plugin — all /api/v1/defi/* routes
|
|
4
|
+
*
|
|
5
|
+
* Endpoints:
|
|
6
|
+
* GET /api/v1/defi/swap/quote ($0.005) — Jupiter swap quote
|
|
7
|
+
* POST /api/v1/defi/swap/execute ($0.25) — Jupiter swap transaction builder
|
|
8
|
+
* GET /api/v1/defi/positions/:addr ($0.10) — DeFi positions aggregation
|
|
9
|
+
* GET /api/v1/defi/lst/yields ($0.02) — LST yield comparison
|
|
10
|
+
*/
|
|
11
|
+
export declare function defiPlugin(app: FastifyInstance): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/plugins/defi/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAgB/C;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA+epE"}
|