@shreyassp002/pinionos-emulator 0.1.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.
Files changed (39) hide show
  1. package/README.md +171 -0
  2. package/dist/examples/basic-agent.js +22 -0
  3. package/dist/examples/custom-skill.js +24 -0
  4. package/dist/examples/yield-optimizer.js +31 -0
  5. package/dist/src/app.js +116 -0
  6. package/dist/src/cli.js +136 -0
  7. package/dist/src/client/MockPinionClient.js +69 -0
  8. package/dist/src/config.js +149 -0
  9. package/dist/src/emulator.js +23 -0
  10. package/dist/src/freeApis/binance.js +36 -0
  11. package/dist/src/freeApis/coingecko.js +129 -0
  12. package/dist/src/mcp/server.js +122 -0
  13. package/dist/src/middleware/apiKeyStore.js +4 -0
  14. package/dist/src/middleware/chaos.js +33 -0
  15. package/dist/src/middleware/paymentLogger.js +82 -0
  16. package/dist/src/middleware/recorder.js +67 -0
  17. package/dist/src/middleware/x402.js +137 -0
  18. package/dist/src/routes/balance.js +28 -0
  19. package/dist/src/routes/broadcast.js +38 -0
  20. package/dist/src/routes/chat.js +106 -0
  21. package/dist/src/routes/facilitator.js +48 -0
  22. package/dist/src/routes/fund.js +52 -0
  23. package/dist/src/routes/price.js +79 -0
  24. package/dist/src/routes/send.js +101 -0
  25. package/dist/src/routes/trade.js +119 -0
  26. package/dist/src/routes/tx.js +48 -0
  27. package/dist/src/routes/unlimited.js +54 -0
  28. package/dist/src/routes/wallet.js +35 -0
  29. package/dist/src/routes/x402service.js +90 -0
  30. package/dist/src/state/balances.js +90 -0
  31. package/dist/src/types.js +30 -0
  32. package/dist/src/ui/colors.js +15 -0
  33. package/dist/src/ui/dashboard.js +340 -0
  34. package/dist/src/ui/panels/feed.js +31 -0
  35. package/dist/src/ui/panels/header.js +56 -0
  36. package/dist/src/ui/panels/inspector.js +19 -0
  37. package/dist/src/ui/panels/prices.js +23 -0
  38. package/dist/src/ui/panels/wallet.js +25 -0
  39. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ <div align="center">
2
+
3
+ # PinionOS Emulator
4
+
5
+ **Local PinionOS-compatible backend for product development and testing**
6
+
7
+ Test your app with the same `pinion-os` SDK calls, without real USDC spend.
8
+
9
+ </div>
10
+
11
+ ## What This Project Is
12
+
13
+ `pinionos-emulator` is a local server that mirrors Pinion skill APIs so teams can build and test products safely.
14
+
15
+ Use it when you want to:
16
+ - develop app flows without hitting production
17
+ - run deterministic integration tests in CI
18
+ - exercise x402/unlimited-key paths locally
19
+ - inspect request/response behavior in a terminal dashboard
20
+
21
+ ## Pinion SDK Compatibility
22
+
23
+ This emulator is designed for `pinion-os` client usage by pointing the SDK to local URL:
24
+
25
+ ```ts
26
+ import { PinionClient } from 'pinion-os';
27
+
28
+ const client = new PinionClient({
29
+ privateKey: process.env.PRIVATE_KEY!,
30
+ apiUrl: 'http://localhost:4020'
31
+ });
32
+ ```
33
+
34
+ ## Supported SDK Skills
35
+
36
+ | Skill | SDK Method | Emulator Endpoint | Status |
37
+ |---|---|---|---|
38
+ | balance | `skills.balance(address)` | `GET /balance/:address` | Supported |
39
+ | tx | `skills.tx(hash)` | `GET /tx/:hash` | Supported |
40
+ | price | `skills.price(token)` | `GET /price/:token` | Supported |
41
+ | wallet | `skills.wallet()` | `GET /wallet/generate` | Supported |
42
+ | chat | `skills.chat(message)` | `POST /chat` | Supported |
43
+ | send | `skills.send(to, amount, token)` | `POST /send` | Supported |
44
+ | trade | `skills.trade(src, dst, amount, slippage)` | `POST /trade` | Supported |
45
+ | fund | `skills.fund(address)` | `GET /fund/:address` | Supported |
46
+ | broadcast | `skills.broadcast(tx)` | `POST /broadcast` | Supported |
47
+ | unlimited | `skills.unlimited()` | `POST /unlimited` | Supported |
48
+ | unlimited-verify | `skills.unlimitedVerify(key)` | `GET /unlimited/verify?key=...` | Supported |
49
+
50
+ ## Supported MCP Tools
51
+
52
+ Your emulator MCP server currently exposes:
53
+ - `pinion_price`
54
+ - `pinion_balance`
55
+ - `pinion_wallet`
56
+ - `pinion_tx`
57
+ - `pinion_chat`
58
+ - `pinion_send`
59
+ - `pinion_trade`
60
+ - `pinion_fund`
61
+ - `pinion_broadcast`
62
+ - `pinion_unlimited`
63
+ - `pinion_unlimited_verify`
64
+ - `pinion_pay_service`
65
+ - `pinion_facilitator_verify`
66
+
67
+ ## Emulator Features Beyond Core Skills
68
+
69
+ - x402 middleware mode (`--x402`) with 402 challenge flow
70
+ - unlimited API key issuance and verification
71
+ - generic x402 test service (`/x402/*`)
72
+ - mock facilitator endpoints (`/facilitator/verify`, `/facilitator/status`)
73
+ - request recording (`/recording/start`, `/recording/stop`, `/recording/status`)
74
+ - chaos/error injection via config (`errorSimulation`)
75
+ - mutable in-memory balances + reset (`POST /reset`)
76
+ - terminal dashboard (feed, prices, wallet, inspector)
77
+
78
+ ## Quick Start
79
+
80
+ ```bash
81
+ npm install
82
+ npm start
83
+ ```
84
+
85
+ Health check:
86
+
87
+ ```bash
88
+ curl -s http://localhost:4020/health | jq
89
+ ```
90
+
91
+ Headless mode:
92
+
93
+ ```bash
94
+ npx pinionos-emulator --no-dashboard
95
+ ```
96
+
97
+ ## CLI Commands
98
+
99
+ ```bash
100
+ pinionos-emulator start
101
+ pinionos-emulator mcp
102
+ pinionos-emulator init
103
+ ```
104
+
105
+ Useful options:
106
+ - `--port <n>`
107
+ - `--x402`
108
+ - `--network base|base-sepolia`
109
+ - `--no-dashboard`
110
+ - `--config <path>`
111
+
112
+ ## Product Testing Workflow
113
+
114
+ 1. Start emulator locally.
115
+ 2. Configure your app's Pinion client with `apiUrl: 'http://localhost:4020'`.
116
+ 3. Run your app and test suite.
117
+ 4. Assert your product behavior (not just raw route responses).
118
+
119
+ CI example:
120
+
121
+ ```bash
122
+ npx pinionos-emulator --no-dashboard > /tmp/pinionos-emulator.log 2>&1 & EMU_PID=$!
123
+ sleep 2
124
+ npm test
125
+ kill $EMU_PID
126
+ ```
127
+
128
+ ## Route Summary
129
+
130
+ Core routes:
131
+ - `GET /price/:token`
132
+ - `GET /balance/:address`
133
+ - `GET /wallet`
134
+ - `GET /wallet/generate`
135
+ - `GET /tx/:hash`
136
+ - `POST /send`
137
+ - `POST /trade`
138
+ - `GET /fund/:address`
139
+ - `POST /chat`
140
+ - `POST /broadcast`
141
+ - `GET /unlimited`
142
+ - `POST /unlimited`
143
+ - `GET /unlimited/verify?key=...`
144
+ - `GET /unlimited/verify/:key`
145
+
146
+ System routes:
147
+ - `GET /`
148
+ - `GET /health`
149
+ - `POST /reset`
150
+ - `POST /recording/start`
151
+ - `POST /recording/stop`
152
+ - `GET /recording/status`
153
+ - `POST /facilitator/verify`
154
+ - `GET /facilitator/status`
155
+ - `ALL /x402/*`
156
+
157
+ ## Notes On Behavior
158
+
159
+ - Responses are mock/simulated with realistic structure.
160
+ - Success envelope includes `mock: true` and payment metadata.
161
+ - Price path uses config override -> CoinGecko -> Binance -> fallback.
162
+ - This is for development/testing, not production settlement.
163
+
164
+ ## Documentation
165
+
166
+ - Detailed usage and testing guide: [user_guide.md](user_guide.md)
167
+ - Planning/spec docs: [`docs/`](docs)
168
+
169
+ ## License
170
+
171
+ MIT
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ process.env.PINION_API_URL = 'http://localhost:4020';
4
+ const pinion_os_1 = require("pinion-os");
5
+ async function run() {
6
+ const pinion = new pinion_os_1.PinionClient({
7
+ privateKey: '0x0000000000000000000000000000000000000000000000000000000000000001'
8
+ });
9
+ console.log('=== STEP 1: PRICE ===');
10
+ const price = await pinion.skills.price('ETH');
11
+ console.log(price);
12
+ console.log('\n=== STEP 2: BALANCE ===');
13
+ const balance = await pinion.skills.balance('0x123');
14
+ console.log(balance);
15
+ console.log('\n=== STEP 3: WALLET ===');
16
+ const wallet = await pinion.skills.wallet();
17
+ console.log(wallet);
18
+ }
19
+ run().catch((error) => {
20
+ console.error(error);
21
+ process.exit(1);
22
+ });
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const server_1 = require("pinion-os/server");
4
+ const server = (0, server_1.createSkillServer)({
5
+ payTo: '0x000000000000000000000000000000000000dEaD',
6
+ network: 'base'
7
+ });
8
+ server.add({
9
+ name: 'yieldRecs',
10
+ price: 0.01,
11
+ endpoint: '/yield-recs',
12
+ handler: async (req, res) => {
13
+ res.json({
14
+ data: {
15
+ recommendation: 'Rotate 20% into stablecoins during high volatility.',
16
+ confidence: 'medium'
17
+ },
18
+ mock: true
19
+ });
20
+ }
21
+ });
22
+ server.listen(4500, () => {
23
+ console.log('Custom skill server listening on :4500');
24
+ });
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ process.env.PINION_API_URL = 'http://localhost:4020';
4
+ const pinion_os_1 = require("pinion-os");
5
+ const client = new pinion_os_1.PinionClient({
6
+ privateKey: '0x0000000000000000000000000000000000000000000000000000000000000001'
7
+ });
8
+ let iteration = 0;
9
+ async function runOnce() {
10
+ iteration += 1;
11
+ console.log('\n=== Yield Check', iteration, '===');
12
+ const priceResult = await client.skills.price('ETH');
13
+ const ethPrice = Number(priceResult.data?.usd ?? 0);
14
+ console.log('ETH Price:', ethPrice);
15
+ const decision = ethPrice < 3000 ? 'BUY ETH->USDC rebalance' : 'HOLD';
16
+ console.log('Decision:', decision);
17
+ if (decision.startsWith('BUY')) {
18
+ const tradeResult = await client.skills.trade('ETH', 'USDC', '0.01');
19
+ console.log('Trade Executed:', tradeResult.data?.toAmount ?? 'n/a', 'USDC');
20
+ console.log('Simulated Yield: +$0.02');
21
+ }
22
+ }
23
+ runOnce().catch((error) => {
24
+ console.error(error);
25
+ process.exit(1);
26
+ });
27
+ setInterval(() => {
28
+ runOnce().catch((error) => {
29
+ console.error('Loop error:', error);
30
+ });
31
+ }, 10000);
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createApp = createApp;
7
+ const cors_1 = __importDefault(require("cors"));
8
+ const express_1 = __importDefault(require("express"));
9
+ const config_1 = require("./config");
10
+ const chaos_1 = require("./middleware/chaos");
11
+ const paymentLogger_1 = require("./middleware/paymentLogger");
12
+ const recorder_1 = require("./middleware/recorder");
13
+ const x402_1 = require("./middleware/x402");
14
+ const price_1 = require("./routes/price");
15
+ const wallet_1 = require("./routes/wallet");
16
+ const balance_1 = __importDefault(require("./routes/balance"));
17
+ const broadcast_1 = __importDefault(require("./routes/broadcast"));
18
+ const chat_1 = __importDefault(require("./routes/chat"));
19
+ const fund_1 = require("./routes/fund");
20
+ const send_1 = require("./routes/send");
21
+ const trade_1 = require("./routes/trade");
22
+ const facilitator_1 = require("./routes/facilitator");
23
+ const tx_1 = __importDefault(require("./routes/tx"));
24
+ const unlimited_1 = __importDefault(require("./routes/unlimited"));
25
+ const x402service_1 = require("./routes/x402service");
26
+ const balances_1 = require("./state/balances");
27
+ const apiKeyStore_1 = require("./middleware/apiKeyStore");
28
+ const types_1 = require("./types");
29
+ function createApp(opts = {}) {
30
+ const dashboard = opts.dashboard;
31
+ const noop = {
32
+ logSkillCall() { },
33
+ updatePrices() { },
34
+ logError() { },
35
+ setWalletInfo() { },
36
+ destroy() { },
37
+ };
38
+ const db = dashboard ?? noop;
39
+ const app = (0, express_1.default)();
40
+ const config = (0, config_1.loadConfig)();
41
+ (0, balances_1.initBalances)();
42
+ app.use((0, cors_1.default)());
43
+ app.use(express_1.default.json());
44
+ // Request recording
45
+ app.use((0, recorder_1.recorderMiddleware)());
46
+ if (config.recording) {
47
+ (0, recorder_1.startRecording)();
48
+ }
49
+ // Chaos error injection
50
+ app.use((0, chaos_1.chaosMiddleware)(db));
51
+ // Log X-API-KEY usage
52
+ app.use((req, _res, next) => {
53
+ const apiKey = req.headers['x-api-key'];
54
+ if (typeof apiKey === 'string' && apiKey.length > 0) {
55
+ const entry = apiKeyStore_1.issuedKeys.get(apiKey);
56
+ const label = entry ? `✓ key valid (${apiKey.slice(0, 12)}...)` : `✗ key unknown (${apiKey.slice(0, 12)}...)`;
57
+ db.logSkillCall('x-api-key', '', label);
58
+ }
59
+ next();
60
+ });
61
+ app.use((0, paymentLogger_1.paymentLogger)(db));
62
+ app.use((0, x402_1.x402Middleware)(db));
63
+ app.get('/', (_req, res) => {
64
+ res.json({ status: 'ok', emulator: true });
65
+ });
66
+ app.get('/health', (_req, res) => {
67
+ res.json({ status: 'ok', emulator: true, port: config.port });
68
+ });
69
+ app.use('/price', (0, price_1.createPriceRouter)(db));
70
+ app.use('/wallet', (0, wallet_1.createWalletRouter)(db));
71
+ app.use('/balance', balance_1.default);
72
+ app.use('/tx', tx_1.default);
73
+ app.use('/send', (0, send_1.createSendRouter)(db));
74
+ app.use('/trade', (0, trade_1.createTradeRouter)(db));
75
+ app.use('/fund', (0, fund_1.createFundRouter)(db));
76
+ app.use('/chat', chat_1.default);
77
+ app.use('/unlimited', unlimited_1.default);
78
+ app.use('/broadcast', broadcast_1.default);
79
+ app.use('/facilitator', (0, facilitator_1.createFacilitatorRouter)(db));
80
+ app.use('/x402', (0, x402service_1.createX402ServiceRouter)(db));
81
+ app.post('/reset', (_req, res) => {
82
+ (0, balances_1.resetBalances)();
83
+ apiKeyStore_1.issuedKeys.clear();
84
+ db.logSkillCall('SYSTEM', '', 'State reset to config defaults');
85
+ res.json((0, types_1.success)({ message: 'All balances and API keys reset to defaults' }));
86
+ });
87
+ app.post('/recording/start', (_req, res) => {
88
+ (0, recorder_1.startRecording)();
89
+ db.logSkillCall('SYSTEM', '', 'Request recording started');
90
+ res.json((0, types_1.success)({ recording: true }));
91
+ });
92
+ app.post('/recording/stop', (_req, res) => {
93
+ (0, recorder_1.stopRecording)();
94
+ db.logSkillCall('SYSTEM', '', 'Request recording stopped');
95
+ res.json((0, types_1.success)({ recording: false }));
96
+ });
97
+ app.get('/recording/status', (_req, res) => {
98
+ res.json((0, types_1.success)({ recording: (0, recorder_1.isRecording)() }));
99
+ });
100
+ app.use((err, _req, res, next) => {
101
+ if (res.headersSent) {
102
+ next(err);
103
+ return;
104
+ }
105
+ if (err instanceof SyntaxError) {
106
+ res.status(400).json((0, types_1.errorResponse)('invalid json body'));
107
+ return;
108
+ }
109
+ db.logError(`internal emulator error: ${String(err?.message ?? err)}`);
110
+ res.status(500).json((0, types_1.errorResponse)('internal emulator error'));
111
+ });
112
+ app.use((_req, res) => {
113
+ res.status(404).json((0, types_1.errorResponse)('route not found'));
114
+ });
115
+ return { app, config };
116
+ }
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ const node_util_1 = require("node:util");
41
+ const node_fs_1 = __importDefault(require("node:fs"));
42
+ const node_path_1 = __importDefault(require("node:path"));
43
+ const HELP = `
44
+ pinionos-emulator — Local PinionOS emulator for agent development
45
+
46
+ Usage:
47
+ pinionos-emulator [command] [options]
48
+
49
+ Commands:
50
+ start Start the emulator (default)
51
+ mcp Start MCP stdio server (emulator must be running)
52
+ init Generate a starter config.json in the current directory
53
+
54
+ Options:
55
+ --port <n> Port to listen on (default: 4020)
56
+ --x402 Enable x402 payment simulation mode
57
+ --network <name> Network: "base" (default) or "base-sepolia"
58
+ --no-dashboard Run without the terminal dashboard UI
59
+ --config <path> Path to config.json (default: ./config.json)
60
+ --help, -h Show this help message
61
+ --version, -v Show version
62
+
63
+ Examples:
64
+ pinionos-emulator # Start with defaults
65
+ pinionos-emulator --port 3000 --x402 # Custom port + x402 mode
66
+ pinionos-emulator init # Create config.json
67
+ npx pinionos-emulator # Zero-install usage
68
+ `.trim();
69
+ async function main() {
70
+ const { values, positionals } = (0, node_util_1.parseArgs)({
71
+ args: process.argv.slice(2),
72
+ options: {
73
+ port: { type: 'string', short: 'p' },
74
+ x402: { type: 'boolean', default: false },
75
+ network: { type: 'string', short: 'n' },
76
+ 'no-dashboard': { type: 'boolean', default: false },
77
+ config: { type: 'string', short: 'c' },
78
+ help: { type: 'boolean', short: 'h', default: false },
79
+ version: { type: 'boolean', short: 'v', default: false },
80
+ },
81
+ allowPositionals: true,
82
+ strict: true,
83
+ });
84
+ if (values.help) {
85
+ console.log(HELP);
86
+ return;
87
+ }
88
+ if (values.version) {
89
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(__dirname, '..', 'package.json'), 'utf-8'));
90
+ console.log(pkg.version);
91
+ return;
92
+ }
93
+ // Apply config path override before command handling
94
+ if (values.config) {
95
+ const { setConfigPath } = await Promise.resolve().then(() => __importStar(require('./config')));
96
+ setConfigPath(values.config);
97
+ process.env.PINION_CONFIG_PATH = node_path_1.default.resolve(values.config);
98
+ }
99
+ const command = positionals[0] ?? 'start';
100
+ if (command === 'init') {
101
+ const { getDefaultConfig } = await Promise.resolve().then(() => __importStar(require('./config')));
102
+ const dest = node_path_1.default.resolve(process.cwd(), 'config.json');
103
+ if (node_fs_1.default.existsSync(dest)) {
104
+ console.error(`config.json already exists at ${dest}`);
105
+ process.exit(1);
106
+ }
107
+ node_fs_1.default.writeFileSync(dest, JSON.stringify(getDefaultConfig(), null, 2) + '\n');
108
+ console.log(`Created config.json at ${dest}`);
109
+ return;
110
+ }
111
+ if (command === 'mcp') {
112
+ await Promise.resolve().then(() => __importStar(require('./mcp/server')));
113
+ return;
114
+ }
115
+ // Apply CLI overrides
116
+ const overrides = {};
117
+ if (values.port)
118
+ overrides.port = parseInt(values.port, 10);
119
+ if (values.x402)
120
+ overrides.x402Mode = true;
121
+ if (values.network)
122
+ overrides.network = values.network;
123
+ if (Object.keys(overrides).length > 0) {
124
+ const { setCliOverrides } = await Promise.resolve().then(() => __importStar(require('./config')));
125
+ setCliOverrides(overrides);
126
+ }
127
+ // Set env flag for no-dashboard mode
128
+ if (values['no-dashboard']) {
129
+ process.env.PINION_NO_DASHBOARD = '1';
130
+ }
131
+ await Promise.resolve().then(() => __importStar(require('./emulator')));
132
+ }
133
+ main().catch((error) => {
134
+ console.error(error);
135
+ process.exit(1);
136
+ });
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MockPinionClient = void 0;
7
+ exports.wrapAsSkillResponse = wrapAsSkillResponse;
8
+ const axios_1 = __importDefault(require("axios"));
9
+ function wrapAsSkillResponse(data) {
10
+ return {
11
+ status: 200,
12
+ data,
13
+ paidAmount: '0.01',
14
+ responseTimeMs: 0
15
+ };
16
+ }
17
+ /**
18
+ * Drop-in replacement for PinionClient when the SDK cannot be redirected via apiUrl.
19
+ * All skill methods return the same SkillResponse<T> shape as the real SDK.
20
+ * Usage:
21
+ * const client = new MockPinionClient({ baseUrl: 'http://localhost:4020' });
22
+ * const result = await client.skills.price('ETH');
23
+ * console.log(result.data.priceUSD);
24
+ */
25
+ class MockPinionClient {
26
+ constructor(options = {}) {
27
+ this.skills = {
28
+ price: (token) => this.get(`/price/${token}`),
29
+ balance: (address) => this.get(`/balance/${address}`),
30
+ wallet: () => this.get('/wallet/generate'),
31
+ tx: (hash) => this.get(`/tx/${hash}`),
32
+ fund: (address) => this.get(`/fund/${address ?? this.address}`),
33
+ chat: (message, history) => {
34
+ const messages = history
35
+ ? [...history, { role: 'user', content: message }]
36
+ : [{ role: 'user', content: message }];
37
+ return this.post('/chat', { messages });
38
+ },
39
+ send: (to, amount, token) => this.post('/send', { to, amount, token }),
40
+ trade: (src, dst, amount, slippage) => this.post('/trade', { src, dst, amount, slippage: slippage ?? 1 }),
41
+ broadcast: (tx, privateKey) => this.post('/broadcast', { tx, privateKey }),
42
+ unlimited: () => this.post('/unlimited', {}),
43
+ unlimitedVerify: async (key) => {
44
+ const res = await this.http.get('/unlimited/verify', { params: { key } });
45
+ return res.data;
46
+ }
47
+ };
48
+ this.http = axios_1.default.create({
49
+ baseURL: options.baseUrl ?? 'http://localhost:4020',
50
+ timeout: 10000
51
+ });
52
+ this.address = options.address ?? '';
53
+ }
54
+ async get(path) {
55
+ const start = Date.now();
56
+ const res = await this.http.get(path);
57
+ const body = res.data;
58
+ const data = body.data ?? body;
59
+ return { status: res.status, data, paidAmount: '0.01', responseTimeMs: Date.now() - start };
60
+ }
61
+ async post(path, payload) {
62
+ const start = Date.now();
63
+ const res = await this.http.post(path, payload);
64
+ const body = res.data;
65
+ const data = body.data ?? body;
66
+ return { status: res.status, data, paidAmount: '0.01', responseTimeMs: Date.now() - start };
67
+ }
68
+ }
69
+ exports.MockPinionClient = MockPinionClient;