@guiie/buda-mcp 1.4.0 → 1.4.1
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/CHANGELOG.md +10 -0
- package/dist/tools/simulate_order.js +1 -1
- package/marketplace/gemini-tools.json +87 -0
- package/marketplace/openapi.yaml +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/simulate_order.ts +1 -1
- package/test/run-all.ts +197 -0
- package/test/unit.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ This project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.4.1] – 2026-04-11
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`simulate_order`**: `taker_fee` returned by Buda API is already expressed as a percentage (`0.8` = 0.8%), not a decimal. Dividing by 100 before use gives correct fee calculations. Previously this caused fee_amount and total_cost to be ~100× too large.
|
|
15
|
+
- Integration test (`test/run-all.ts`): added live checks for all 5 v1.4.0 tools; fixed field name `candles_available` (was `candles_used`).
|
|
16
|
+
- Unit test mocks: updated `taker_fee` mock values from `"0.008"`/`"0.005"` to `"0.8"`/`"0.5"` to match the real Buda API format.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
10
20
|
## [1.4.0] – 2026-04-11
|
|
11
21
|
|
|
12
22
|
### Added
|
|
@@ -67,7 +67,7 @@ export async function handleSimulateOrder(args, client, cache) {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
const mid = (minAsk + maxBid) / 2;
|
|
70
|
-
const takerFeeRate = parseFloat(market.taker_fee);
|
|
70
|
+
const takerFeeRate = parseFloat(market.taker_fee) / 100;
|
|
71
71
|
const orderTypeAssumed = price !== undefined ? "limit" : "market";
|
|
72
72
|
let estimatedFillPrice;
|
|
73
73
|
if (orderTypeAssumed === "market") {
|
|
@@ -306,6 +306,93 @@
|
|
|
306
306
|
},
|
|
307
307
|
"required": ["market_id"]
|
|
308
308
|
}
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"name": "get_balances",
|
|
312
|
+
"description": "Returns all currency balances for the authenticated Buda.com account as flat typed objects. Each currency entry includes total amount, available amount (not frozen), frozen amount, and pending withdrawal amount — all as floats with separate _currency fields. Requires BUDA_API_KEY and BUDA_API_SECRET. Auth-gated. Example: 'How much BTC do I have available to trade right now?'",
|
|
313
|
+
"parameters": {
|
|
314
|
+
"type": "OBJECT",
|
|
315
|
+
"properties": {},
|
|
316
|
+
"required": []
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"name": "get_orders",
|
|
321
|
+
"description": "Returns orders for a given Buda.com market as flat typed objects. All monetary amounts are floats with separate _currency fields (e.g. amount + amount_currency). Filterable by state: pending, active, traded, canceled, canceled_and_traded. Supports pagination via per and page. Requires BUDA_API_KEY and BUDA_API_SECRET. Auth-gated. Example: 'Show my open limit orders on BTC-CLP.'",
|
|
322
|
+
"parameters": {
|
|
323
|
+
"type": "OBJECT",
|
|
324
|
+
"properties": {
|
|
325
|
+
"market_id": {
|
|
326
|
+
"type": "STRING",
|
|
327
|
+
"description": "Market identifier (e.g. 'BTC-CLP', 'ETH-BTC'). Case-insensitive."
|
|
328
|
+
},
|
|
329
|
+
"state": {
|
|
330
|
+
"type": "STRING",
|
|
331
|
+
"description": "Filter by order state: 'pending', 'active', 'traded', 'canceled', 'canceled_and_traded'. Omit to return all orders."
|
|
332
|
+
},
|
|
333
|
+
"per": {
|
|
334
|
+
"type": "INTEGER",
|
|
335
|
+
"description": "Results per page (default: 20, max: 300)."
|
|
336
|
+
},
|
|
337
|
+
"page": {
|
|
338
|
+
"type": "INTEGER",
|
|
339
|
+
"description": "Page number (default: 1)."
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
"required": ["market_id"]
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"name": "place_order",
|
|
347
|
+
"description": "Places a limit or market order on Buda.com. IMPORTANT: Requires confirmation_token='CONFIRM' to execute — any other value rejects without placing an order, preventing accidental execution. Requires BUDA_API_KEY and BUDA_API_SECRET. Auth-gated. WARNING: Only use on locally-run instances — never on a publicly exposed server.",
|
|
348
|
+
"parameters": {
|
|
349
|
+
"type": "OBJECT",
|
|
350
|
+
"properties": {
|
|
351
|
+
"market_id": {
|
|
352
|
+
"type": "STRING",
|
|
353
|
+
"description": "Market identifier (e.g. 'BTC-CLP', 'ETH-BTC'). Case-insensitive."
|
|
354
|
+
},
|
|
355
|
+
"type": {
|
|
356
|
+
"type": "STRING",
|
|
357
|
+
"description": "Order side: 'Bid' to buy, 'Ask' to sell."
|
|
358
|
+
},
|
|
359
|
+
"price_type": {
|
|
360
|
+
"type": "STRING",
|
|
361
|
+
"description": "Order type: 'limit' places at a specific price, 'market' executes immediately at best available price."
|
|
362
|
+
},
|
|
363
|
+
"amount": {
|
|
364
|
+
"type": "NUMBER",
|
|
365
|
+
"description": "Order size in the market's base currency (e.g. BTC amount for BTC-CLP)."
|
|
366
|
+
},
|
|
367
|
+
"limit_price": {
|
|
368
|
+
"type": "NUMBER",
|
|
369
|
+
"description": "Limit price in quote currency. Required when price_type is 'limit'. For Bid orders: highest price you will pay. For Ask orders: lowest price you will accept."
|
|
370
|
+
},
|
|
371
|
+
"confirmation_token": {
|
|
372
|
+
"type": "STRING",
|
|
373
|
+
"description": "Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to place the order. Any other value rejects the request."
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
"required": ["market_id", "type", "price_type", "amount", "confirmation_token"]
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"name": "cancel_order",
|
|
381
|
+
"description": "Cancels an open order by numeric ID on Buda.com. IMPORTANT: Requires confirmation_token='CONFIRM' to execute — any other value rejects without cancelling. Requires BUDA_API_KEY and BUDA_API_SECRET. Auth-gated.",
|
|
382
|
+
"parameters": {
|
|
383
|
+
"type": "OBJECT",
|
|
384
|
+
"properties": {
|
|
385
|
+
"order_id": {
|
|
386
|
+
"type": "INTEGER",
|
|
387
|
+
"description": "The numeric ID of the order to cancel."
|
|
388
|
+
},
|
|
389
|
+
"confirmation_token": {
|
|
390
|
+
"type": "STRING",
|
|
391
|
+
"description": "Safety confirmation. Must equal exactly 'CONFIRM' (case-sensitive) to cancel the order. Any other value rejects the request."
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
"required": ["order_id", "confirmation_token"]
|
|
395
|
+
}
|
|
309
396
|
}
|
|
310
397
|
]
|
|
311
398
|
}
|
package/marketplace/openapi.yaml
CHANGED
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/gtorreal/buda-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.4.
|
|
9
|
+
"version": "1.4.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@guiie/buda-mcp",
|
|
14
|
-
"version": "1.4.
|
|
14
|
+
"version": "1.4.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|
|
@@ -99,7 +99,7 @@ export async function handleSimulateOrder(
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
const mid = (minAsk + maxBid) / 2;
|
|
102
|
-
const takerFeeRate = parseFloat(market.taker_fee);
|
|
102
|
+
const takerFeeRate = parseFloat(market.taker_fee) / 100;
|
|
103
103
|
const orderTypeAssumed = price !== undefined ? "limit" : "market";
|
|
104
104
|
|
|
105
105
|
let estimatedFillPrice: number;
|
package/test/run-all.ts
CHANGED
|
@@ -18,6 +18,12 @@ try {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
import { BudaClient } from "../src/client.js";
|
|
21
|
+
import { MemoryCache } from "../src/cache.js";
|
|
22
|
+
import { handleSimulateOrder } from "../src/tools/simulate_order.js";
|
|
23
|
+
import { handleCalculatePositionSize } from "../src/tools/calculate_position_size.js";
|
|
24
|
+
import { handleMarketSentiment } from "../src/tools/market_sentiment.js";
|
|
25
|
+
import { handleTechnicalIndicators } from "../src/tools/technical_indicators.js";
|
|
26
|
+
import { handleScheduleCancelAll, handleDisarmCancelTimer } from "../src/tools/dead_mans_switch.js";
|
|
21
27
|
import type {
|
|
22
28
|
MarketsResponse,
|
|
23
29
|
TickerResponse,
|
|
@@ -225,6 +231,167 @@ try {
|
|
|
225
231
|
failures++;
|
|
226
232
|
}
|
|
227
233
|
|
|
234
|
+
// ----------------------------------------------------------------
|
|
235
|
+
// 9. simulate_order
|
|
236
|
+
// ----------------------------------------------------------------
|
|
237
|
+
section(`simulate_order — ${TEST_MARKET} market buy`);
|
|
238
|
+
{
|
|
239
|
+
const cache = new MemoryCache();
|
|
240
|
+
try {
|
|
241
|
+
const result = await handleSimulateOrder(
|
|
242
|
+
{ market_id: TEST_MARKET, side: "buy", amount: 0.001 },
|
|
243
|
+
client,
|
|
244
|
+
cache,
|
|
245
|
+
);
|
|
246
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
247
|
+
const parsed = JSON.parse(result.content[0].text) as {
|
|
248
|
+
simulation: boolean;
|
|
249
|
+
estimated_fill_price: number;
|
|
250
|
+
fee_amount: number;
|
|
251
|
+
fee_rate_pct: number;
|
|
252
|
+
total_cost: number;
|
|
253
|
+
slippage_vs_mid_pct: number;
|
|
254
|
+
order_type_assumed: string;
|
|
255
|
+
};
|
|
256
|
+
if (parsed.simulation !== true) throw new Error("simulation flag must be true");
|
|
257
|
+
pass("simulation: true", "✓");
|
|
258
|
+
pass("order_type_assumed", parsed.order_type_assumed);
|
|
259
|
+
pass("estimated_fill_price", `${parsed.estimated_fill_price.toLocaleString()} CLP`);
|
|
260
|
+
pass("fee_rate_pct", `${parsed.fee_rate_pct}%`);
|
|
261
|
+
pass("fee_amount", `${parsed.fee_amount.toFixed(2)} CLP`);
|
|
262
|
+
pass("total_cost", `${parsed.total_cost.toFixed(2)} CLP`);
|
|
263
|
+
pass("slippage_vs_mid_pct", `${parsed.slippage_vs_mid_pct}%`);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
fail("simulate_order", err);
|
|
266
|
+
failures++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ----------------------------------------------------------------
|
|
271
|
+
// 10. calculate_position_size
|
|
272
|
+
// ----------------------------------------------------------------
|
|
273
|
+
section(`calculate_position_size — ${TEST_MARKET}`);
|
|
274
|
+
{
|
|
275
|
+
// Fetch live ticker to use real entry/stop prices
|
|
276
|
+
try {
|
|
277
|
+
const tickerData = await client.get<TickerResponse>(
|
|
278
|
+
`/markets/${TEST_MARKET.toLowerCase()}/ticker`,
|
|
279
|
+
);
|
|
280
|
+
const lastPrice = parseFloat(tickerData.ticker.last_price[0]);
|
|
281
|
+
const entryPrice = lastPrice;
|
|
282
|
+
const stopLossPrice = parseFloat((lastPrice * 0.97).toFixed(0)); // 3% below entry
|
|
283
|
+
|
|
284
|
+
const result = handleCalculatePositionSize({
|
|
285
|
+
market_id: TEST_MARKET,
|
|
286
|
+
capital: 1_000_000,
|
|
287
|
+
risk_pct: 2,
|
|
288
|
+
entry_price: entryPrice,
|
|
289
|
+
stop_loss_price: stopLossPrice,
|
|
290
|
+
});
|
|
291
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
292
|
+
const parsed = JSON.parse(result.content[0].text) as {
|
|
293
|
+
side: string;
|
|
294
|
+
units: number;
|
|
295
|
+
capital_at_risk: number;
|
|
296
|
+
position_value: number;
|
|
297
|
+
fee_impact: number;
|
|
298
|
+
fee_currency: string;
|
|
299
|
+
};
|
|
300
|
+
pass("side", parsed.side);
|
|
301
|
+
pass("units", `${parsed.units} BTC`);
|
|
302
|
+
pass("capital_at_risk", `${parsed.capital_at_risk.toLocaleString()} CLP`);
|
|
303
|
+
pass("position_value", `${parsed.position_value.toLocaleString()} CLP`);
|
|
304
|
+
pass("fee_impact", `${parsed.fee_impact.toFixed(2)} ${parsed.fee_currency}`);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
fail("calculate_position_size", err);
|
|
307
|
+
failures++;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ----------------------------------------------------------------
|
|
312
|
+
// 11. get_market_sentiment
|
|
313
|
+
// ----------------------------------------------------------------
|
|
314
|
+
section(`get_market_sentiment — ${TEST_MARKET}`);
|
|
315
|
+
{
|
|
316
|
+
const cache = new MemoryCache();
|
|
317
|
+
try {
|
|
318
|
+
const result = await handleMarketSentiment({ market_id: TEST_MARKET }, client, cache);
|
|
319
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
320
|
+
const parsed = JSON.parse(result.content[0].text) as {
|
|
321
|
+
score: number;
|
|
322
|
+
label: string;
|
|
323
|
+
component_breakdown: {
|
|
324
|
+
price_variation_24h_pct: number;
|
|
325
|
+
volume_ratio: number;
|
|
326
|
+
spread_pct: number;
|
|
327
|
+
};
|
|
328
|
+
disclaimer: string;
|
|
329
|
+
};
|
|
330
|
+
if (!["bearish", "neutral", "bullish"].includes(parsed.label)) {
|
|
331
|
+
throw new Error(`unexpected label: ${parsed.label}`);
|
|
332
|
+
}
|
|
333
|
+
if (typeof parsed.score !== "number" || parsed.score < -100 || parsed.score > 100) {
|
|
334
|
+
throw new Error(`score out of range: ${parsed.score}`);
|
|
335
|
+
}
|
|
336
|
+
pass("score", String(parsed.score));
|
|
337
|
+
pass("label", parsed.label);
|
|
338
|
+
pass("price_variation_24h_pct", `${parsed.component_breakdown.price_variation_24h_pct}%`);
|
|
339
|
+
pass("volume_ratio", String(parsed.component_breakdown.volume_ratio));
|
|
340
|
+
pass("spread_pct", `${parsed.component_breakdown.spread_pct}%`);
|
|
341
|
+
pass("disclaimer", parsed.disclaimer.length > 0 ? "present" : "MISSING");
|
|
342
|
+
} catch (err) {
|
|
343
|
+
fail("get_market_sentiment", err);
|
|
344
|
+
failures++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ----------------------------------------------------------------
|
|
349
|
+
// 12. get_technical_indicators
|
|
350
|
+
// ----------------------------------------------------------------
|
|
351
|
+
section(`get_technical_indicators — ${TEST_MARKET} (1h, limit 1000)`);
|
|
352
|
+
{
|
|
353
|
+
try {
|
|
354
|
+
const result = await handleTechnicalIndicators(
|
|
355
|
+
{ market_id: TEST_MARKET, period: "1h", limit: 1000 },
|
|
356
|
+
client,
|
|
357
|
+
);
|
|
358
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
359
|
+
const parsed = JSON.parse(result.content[0].text) as {
|
|
360
|
+
candles_used?: number;
|
|
361
|
+
candles_available?: number;
|
|
362
|
+
warning?: string;
|
|
363
|
+
indicators: {
|
|
364
|
+
rsi: number | null;
|
|
365
|
+
macd: { line: number; signal: number; histogram: number } | null;
|
|
366
|
+
bollinger_bands: { upper: number; mid: number; lower: number } | null;
|
|
367
|
+
sma_20: number;
|
|
368
|
+
sma_50: number;
|
|
369
|
+
} | null;
|
|
370
|
+
signals: { rsi_signal: string; macd_signal: string; bb_signal: string };
|
|
371
|
+
disclaimer: string;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
if (parsed.warning === "insufficient_data") {
|
|
375
|
+
pass("warning", `insufficient_data (${parsed.candles_available} candles available, need 50)`);
|
|
376
|
+
} else {
|
|
377
|
+
pass("candles_used", String(parsed.candles_used));
|
|
378
|
+
if (!parsed.indicators) throw new Error("indicators is null without a warning");
|
|
379
|
+
pass("rsi", String(parsed.indicators.rsi));
|
|
380
|
+
pass("rsi_signal", parsed.signals.rsi_signal);
|
|
381
|
+
pass("macd_histogram", String(parsed.indicators.macd?.histogram));
|
|
382
|
+
pass("macd_signal", parsed.signals.macd_signal);
|
|
383
|
+
pass("bb_upper", String(parsed.indicators.bollinger_bands?.upper));
|
|
384
|
+
pass("bb_signal", parsed.signals.bb_signal);
|
|
385
|
+
pass("sma_20", String(parsed.indicators.sma_20));
|
|
386
|
+
pass("sma_50", String(parsed.indicators.sma_50));
|
|
387
|
+
pass("disclaimer", parsed.disclaimer.length > 0 ? "present" : "MISSING");
|
|
388
|
+
}
|
|
389
|
+
} catch (err) {
|
|
390
|
+
fail("get_technical_indicators", err);
|
|
391
|
+
failures++;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
228
395
|
// ----------------------------------------------------------------
|
|
229
396
|
// Auth tools: get_balances, get_orders, place_order, cancel_order
|
|
230
397
|
// ----------------------------------------------------------------
|
|
@@ -263,6 +430,34 @@ if (!client.hasAuth()) {
|
|
|
263
430
|
// cancel_order — confirmation guard test (must reject without CONFIRM)
|
|
264
431
|
console.log(" Skipping: cancel_order live execution (destructive — requires confirmation_token=CONFIRM)");
|
|
265
432
|
pass("cancel_order guard", "confirmation_token check enforced at tool layer (code-audited)");
|
|
433
|
+
|
|
434
|
+
// schedule_cancel_all — arm then immediately disarm (non-destructive)
|
|
435
|
+
try {
|
|
436
|
+
const armResult = await handleScheduleCancelAll(
|
|
437
|
+
{ market_id: TEST_MARKET, ttl_seconds: 300, confirmation_token: "CONFIRM" },
|
|
438
|
+
client,
|
|
439
|
+
);
|
|
440
|
+
if (armResult.isError) throw new Error(armResult.content[0].text);
|
|
441
|
+
const armed = JSON.parse(armResult.content[0].text) as {
|
|
442
|
+
active: boolean;
|
|
443
|
+
expires_at: string;
|
|
444
|
+
ttl_seconds: number;
|
|
445
|
+
warning: string;
|
|
446
|
+
};
|
|
447
|
+
if (!armed.active) throw new Error("active should be true after CONFIRM");
|
|
448
|
+
pass("schedule_cancel_all active", armed.active ? "true" : "false");
|
|
449
|
+
pass("schedule_cancel_all expires_at", armed.expires_at);
|
|
450
|
+
pass("schedule_cancel_all warning", armed.warning.length > 0 ? "present" : "MISSING");
|
|
451
|
+
|
|
452
|
+
// Immediately disarm so no orders are cancelled
|
|
453
|
+
const disarmResult = handleDisarmCancelTimer({ market_id: TEST_MARKET });
|
|
454
|
+
if (disarmResult.isError) throw new Error(disarmResult.content[0].text);
|
|
455
|
+
const disarmed = JSON.parse(disarmResult.content[0].text) as { disarmed: boolean };
|
|
456
|
+
pass("disarm_cancel_timer", disarmed.disarmed ? "timer cleared ✓" : "FAILED to disarm");
|
|
457
|
+
} catch (err) {
|
|
458
|
+
fail("schedule_cancel_all / disarm_cancel_timer", err);
|
|
459
|
+
failures++;
|
|
460
|
+
}
|
|
266
461
|
}
|
|
267
462
|
|
|
268
463
|
// ----------------------------------------------------------------
|
|
@@ -271,6 +466,8 @@ if (!client.hasAuth()) {
|
|
|
271
466
|
section("Summary");
|
|
272
467
|
if (failures === 0) {
|
|
273
468
|
console.log(" All tools returned valid data from the live Buda API.");
|
|
469
|
+
console.log(" Coverage: simulate_order, calculate_position_size, get_market_sentiment,");
|
|
470
|
+
console.log(" get_technical_indicators, schedule_cancel_all/disarm (auth-gated if credentials set).");
|
|
274
471
|
} else {
|
|
275
472
|
console.error(` ${failures} tool(s) failed. See errors above.`);
|
|
276
473
|
process.exit(1);
|
package/test/unit.ts
CHANGED
|
@@ -666,7 +666,7 @@ await test("handleMarketSummary returns correct liquidity_rating from mocked API
|
|
|
666
666
|
|
|
667
667
|
section("i. simulate_order");
|
|
668
668
|
|
|
669
|
-
function makeMockFetchForSimulate(takerFee = "0.
|
|
669
|
+
function makeMockFetchForSimulate(takerFee = "0.8"): typeof fetch {
|
|
670
670
|
const mockTicker = {
|
|
671
671
|
ticker: {
|
|
672
672
|
market_id: "BTC-CLP",
|
|
@@ -720,7 +720,7 @@ await test("market buy: estimated_fill_price = min_ask, simulation: true", async
|
|
|
720
720
|
assertEqual(parsed.simulation, true, "simulation flag must be true");
|
|
721
721
|
assertEqual(parsed.estimated_fill_price, 65100000, "market buy fills at min_ask");
|
|
722
722
|
assertEqual(parsed.order_type_assumed, "market", "order_type_assumed should be market");
|
|
723
|
-
assertEqual(parsed.fee_rate_pct, 0.8, "
|
|
723
|
+
assertEqual(parsed.fee_rate_pct, 0.8, "fee_rate_pct should be 0.8 for crypto (0.8% taker fee)");
|
|
724
724
|
} finally {
|
|
725
725
|
globalThis.fetch = savedFetch;
|
|
726
726
|
}
|
|
@@ -758,14 +758,14 @@ await test("limit order: order_type_assumed = 'limit'", async () => {
|
|
|
758
758
|
|
|
759
759
|
await test("stablecoin market uses 0.5% fee", async () => {
|
|
760
760
|
const savedFetch = globalThis.fetch;
|
|
761
|
-
globalThis.fetch = makeMockFetchForSimulate("0.
|
|
761
|
+
globalThis.fetch = makeMockFetchForSimulate("0.5");
|
|
762
762
|
try {
|
|
763
763
|
const client = new BudaClient("https://www.buda.com/api/v2");
|
|
764
764
|
const cache = new MemoryCache();
|
|
765
765
|
const result = await handleSimulateOrder({ market_id: "BTC-CLP", side: "buy", amount: 1 }, client, cache);
|
|
766
766
|
assert(!result.isError, "should not be an error");
|
|
767
767
|
const parsed = JSON.parse(result.content[0].text) as { fee_rate_pct: number };
|
|
768
|
-
assertEqual(parsed.fee_rate_pct, 0.5, "fee_rate_pct should be 0.5 for stablecoin");
|
|
768
|
+
assertEqual(parsed.fee_rate_pct, 0.5, "fee_rate_pct should be 0.5 for stablecoin (0.5% taker fee)");
|
|
769
769
|
} finally {
|
|
770
770
|
globalThis.fetch = savedFetch;
|
|
771
771
|
}
|