openbroker 1.0.75 → 1.0.80

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.
@@ -398,14 +398,27 @@ export function createTools(watcherOrCtx: PositionWatcher | null | ToolsContext)
398
398
  if (!typeFilter || typeFilter === 'spot') {
399
399
  try {
400
400
  const spotData = await client.getSpotMetaAndAssetCtxs();
401
- for (let i = 0; i < spotData.meta.universe.length; i++) {
402
- const pair = spotData.meta.universe[i];
403
- if (pair.name.toUpperCase().includes(query)) {
401
+ // Build ctx map by coin name — contexts are NOT aligned with universe by index
402
+ const ctxMap = new Map<string, Record<string, string>>();
403
+ for (const ctx of spotData.assetCtxs as Array<Record<string, string>>) {
404
+ if (ctx.coin) ctxMap.set(ctx.coin, ctx);
405
+ }
406
+ // Build token name map
407
+ const tMap = new Map<number, string>();
408
+ for (const t of spotData.meta.tokens) tMap.set(t.index, t.name);
409
+
410
+ for (const pair of spotData.meta.universe) {
411
+ const baseName = tMap.get(pair.tokens[0]) ?? '';
412
+ const quoteName = tMap.get(pair.tokens[1]) ?? '';
413
+ const searchable = `${pair.name} ${baseName} ${quoteName}`.toUpperCase();
414
+ if (searchable.includes(query)) {
415
+ const ctx = ctxMap.get(pair.name);
416
+ const displayName = baseName && quoteName ? `${baseName}/${quoteName}` : pair.name;
404
417
  results.push({
405
- coin: pair.name,
418
+ coin: displayName,
406
419
  type: 'spot',
407
- markPx: spotData.assetCtxs[i]?.markPx,
408
- dayVolume: spotData.assetCtxs[i]?.dayNtlVlm,
420
+ markPx: ctx?.markPx,
421
+ dayVolume: ctx?.dayNtlVlm,
409
422
  });
410
423
  }
411
424
  }
@@ -1193,6 +1206,113 @@ export function createTools(watcherOrCtx: PositionWatcher | null | ToolsContext)
1193
1206
  },
1194
1207
  },
1195
1208
 
1209
+ {
1210
+ name: 'ob_spot_buy',
1211
+ description: 'Buy spot tokens on Hyperliquid. Always use dry=true first to preview.',
1212
+ parameters: {
1213
+ type: 'object',
1214
+ properties: {
1215
+ coin: { type: 'string', description: 'Base token symbol (PURR, HYPE, etc.)' },
1216
+ size: { type: 'number', description: 'Order size in base token units' },
1217
+ price: { type: 'number', description: 'Limit price (omit for market order)' },
1218
+ tif: { type: 'string', description: 'Time-in-force for limit: Gtc, Ioc, Alo (default: Gtc)' },
1219
+ slippage: { type: 'number', description: 'Slippage tolerance in bps for market orders (default: 50)' },
1220
+ dry: { type: 'boolean', description: 'Preview without executing' },
1221
+ },
1222
+ required: ['coin', 'size'],
1223
+ },
1224
+ async execute(_id, params) {
1225
+ const { getClient } = await import('../core/client.js');
1226
+ const { formatUsd } = await import('../core/utils.js');
1227
+ const client = getClient();
1228
+
1229
+ if (client.isReadOnly) return error('Wallet not configured. Run "openbroker setup" first.');
1230
+
1231
+ const coin = (params.coin as string).toUpperCase();
1232
+ const size = params.size as number;
1233
+ const price = params.price as number | undefined;
1234
+ const isMarket = price === undefined;
1235
+
1236
+ if (params.dry) {
1237
+ // Use allMids for accurate spot price preview
1238
+ await client.getMetaAndAssetCtxs(); // ensure spot meta loaded
1239
+ const spotIdx = client.getSpotAssetIndex(coin);
1240
+ const mids = await client.getAllMids();
1241
+ const spotKey = spotIdx !== undefined ? (spotIdx === 10000 ? 'PURR/USDC' : `@${spotIdx - 10000}`) : '';
1242
+ const midPrice = parseFloat(mids[spotKey] || '0');
1243
+ return json({
1244
+ dryRun: true,
1245
+ action: 'spot_buy',
1246
+ coin,
1247
+ size,
1248
+ type: isMarket ? 'market' : 'limit',
1249
+ midPrice,
1250
+ price: price ?? midPrice,
1251
+ notional: formatUsd(midPrice * size),
1252
+ });
1253
+ }
1254
+
1255
+ const result = isMarket
1256
+ ? await client.spotMarketOrder(coin, true, size, params.slippage as number | undefined)
1257
+ : await client.spotLimitOrder(coin, true, size, price!, (params.tif as 'Gtc' | 'Ioc' | 'Alo') ?? 'Gtc');
1258
+
1259
+ return json({ action: 'spot_buy', coin, size, type: isMarket ? 'market' : 'limit', result });
1260
+ },
1261
+ },
1262
+
1263
+ {
1264
+ name: 'ob_spot_sell',
1265
+ description: 'Sell spot tokens on Hyperliquid. Always use dry=true first to preview.',
1266
+ parameters: {
1267
+ type: 'object',
1268
+ properties: {
1269
+ coin: { type: 'string', description: 'Base token symbol (PURR, HYPE, etc.)' },
1270
+ size: { type: 'number', description: 'Order size in base token units' },
1271
+ price: { type: 'number', description: 'Limit price (omit for market order)' },
1272
+ tif: { type: 'string', description: 'Time-in-force for limit: Gtc, Ioc, Alo (default: Gtc)' },
1273
+ slippage: { type: 'number', description: 'Slippage tolerance in bps for market orders (default: 50)' },
1274
+ dry: { type: 'boolean', description: 'Preview without executing' },
1275
+ },
1276
+ required: ['coin', 'size'],
1277
+ },
1278
+ async execute(_id, params) {
1279
+ const { getClient } = await import('../core/client.js');
1280
+ const { formatUsd } = await import('../core/utils.js');
1281
+ const client = getClient();
1282
+
1283
+ if (client.isReadOnly) return error('Wallet not configured. Run "openbroker setup" first.');
1284
+
1285
+ const coin = (params.coin as string).toUpperCase();
1286
+ const size = params.size as number;
1287
+ const price = params.price as number | undefined;
1288
+ const isMarket = price === undefined;
1289
+
1290
+ if (params.dry) {
1291
+ await client.getMetaAndAssetCtxs();
1292
+ const spotIdx = client.getSpotAssetIndex(coin);
1293
+ const mids = await client.getAllMids();
1294
+ const spotKey = spotIdx !== undefined ? (spotIdx === 10000 ? 'PURR/USDC' : `@${spotIdx - 10000}`) : '';
1295
+ const midPrice = parseFloat(mids[spotKey] || '0');
1296
+ return json({
1297
+ dryRun: true,
1298
+ action: 'spot_sell',
1299
+ coin,
1300
+ size,
1301
+ type: isMarket ? 'market' : 'limit',
1302
+ midPrice,
1303
+ price: price ?? midPrice,
1304
+ notional: formatUsd(midPrice * size),
1305
+ });
1306
+ }
1307
+
1308
+ const result = isMarket
1309
+ ? await client.spotMarketOrder(coin, false, size, params.slippage as number | undefined)
1310
+ : await client.spotLimitOrder(coin, false, size, price!, (params.tif as 'Gtc' | 'Ioc' | 'Alo') ?? 'Gtc');
1311
+
1312
+ return json({ action: 'spot_sell', coin, size, type: isMarket ? 'market' : 'limit', result });
1313
+ },
1314
+ },
1315
+
1196
1316
  {
1197
1317
  name: 'ob_cancel',
1198
1318
  description: 'Cancel open orders on Hyperliquid',