dexbot 1.4.20 → 1.4.21

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 (234) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +22 -12
  3. package/analysis/ama_fitting/calibrate_convergence_er.ts +1 -1
  4. package/analysis/ama_fitting/package.json +1 -1
  5. package/analysis/bot_fitting/shared_utils.ts +1 -1
  6. package/analysis/bot_key_utils.ts +1 -5
  7. package/analysis/bot_usage/kibana_bot_queries.ts +1 -256
  8. package/analysis/chart_css.ts +1 -1
  9. package/analysis/math_utils.ts +1 -8
  10. package/analysis/trend_detection/package.json +1 -1
  11. package/claw/ecosystem.config.cjs +2 -3
  12. package/claw/examples/memu_integration_example.ts +17 -17
  13. package/claw/modules/chain_actions.ts +51 -54
  14. package/claw/modules/chain_broadcast.ts +66 -104
  15. package/claw/modules/claw_bridge.ts +60 -80
  16. package/claw/modules/claw_catalog.ts +47 -16
  17. package/claw/modules/claw_infra.ts +1 -5
  18. package/claw/modules/claw_launcher.ts +47 -49
  19. package/claw/modules/claw_skill_md.ts +8 -11
  20. package/claw/modules/credit_runtime_adapter.ts +2 -18
  21. package/claw/modules/decision_loop.ts +4 -0
  22. package/claw/modules/dexbot_bridge.ts +1 -1
  23. package/claw/modules/dexbot_profiles.ts +5 -21
  24. package/claw/modules/honest_ecosystem.ts +24 -8
  25. package/claw/modules/liquidity_pools.ts +10 -3
  26. package/claw/modules/mcp_utils.ts +109 -0
  27. package/claw/modules/memu_bridge.ts +76 -54
  28. package/claw/modules/mpa_utils.ts +26 -3
  29. package/claw/modules/position_discovery.ts +16 -27
  30. package/claw/modules/position_manager.ts +32 -41
  31. package/claw/modules/position_manager_watch.ts +3 -2
  32. package/claw/modules/short_mpa_strategy.ts +1 -9
  33. package/claw/modules/skill_utils.ts +5 -3
  34. package/claw/modules/utils.ts +9 -1
  35. package/claw/package.json +1 -1
  36. package/claw/runtimes/openclaw-plugin/index.ts +27 -10
  37. package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
  38. package/claw/runtimes/openclaw-plugin/package.json +1 -1
  39. package/claw/scripts/claw_bridge.ts +1 -1
  40. package/claw/scripts/claw_mcp_server.ts +19 -78
  41. package/claw/scripts/honest_assets_report.ts +19 -84
  42. package/claw/scripts/memu_mcp_server.ts +57 -155
  43. package/claw/tests/test_claw_bridge.ts +6 -2
  44. package/claw/tests/test_claw_mcp_transport.ts +2 -2
  45. package/dist/analysis/ama_fitting/calibrate_convergence_er.js +1 -1
  46. package/dist/analysis/ama_fitting/calibrate_convergence_er.js.map +1 -1
  47. package/dist/analysis/bot_fitting/shared_utils.d.ts +1 -12
  48. package/dist/analysis/bot_fitting/shared_utils.d.ts.map +1 -1
  49. package/dist/analysis/bot_fitting/shared_utils.js +1 -1
  50. package/dist/analysis/bot_fitting/shared_utils.js.map +1 -1
  51. package/dist/analysis/bot_key_utils.d.ts +1 -1
  52. package/dist/analysis/bot_key_utils.d.ts.map +1 -1
  53. package/dist/analysis/bot_key_utils.js +1 -5
  54. package/dist/analysis/bot_key_utils.js.map +1 -1
  55. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +1 -337
  56. package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
  57. package/dist/analysis/bot_usage/kibana_bot_queries.js +1 -241
  58. package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
  59. package/dist/analysis/chart_css.d.ts +1 -15
  60. package/dist/analysis/chart_css.d.ts.map +1 -1
  61. package/dist/analysis/chart_css.js +1 -1
  62. package/dist/analysis/chart_css.js.map +1 -1
  63. package/dist/analysis/math_utils.d.ts +3 -5
  64. package/dist/analysis/math_utils.d.ts.map +1 -1
  65. package/dist/analysis/math_utils.js +3 -5
  66. package/dist/analysis/math_utils.js.map +1 -1
  67. package/dist/bot.js +2 -2
  68. package/dist/bot.js.map +1 -1
  69. package/dist/credential-daemon.js +1 -1
  70. package/dist/credential-daemon.js.map +1 -1
  71. package/dist/dexbot.d.ts.map +1 -1
  72. package/dist/dexbot.js +18 -14
  73. package/dist/dexbot.js.map +1 -1
  74. package/dist/market_adapter/ama_signal_runner.js +3 -3
  75. package/dist/market_adapter/core/kibana_client.d.ts +1 -3
  76. package/dist/market_adapter/core/kibana_client.d.ts.map +1 -1
  77. package/dist/market_adapter/core/kibana_client.js +1 -1
  78. package/dist/market_adapter/core/kibana_client.js.map +1 -1
  79. package/dist/market_adapter/core/market_adapter_service.d.ts +1 -2
  80. package/dist/market_adapter/core/market_adapter_service.d.ts.map +1 -1
  81. package/dist/market_adapter/core/market_adapter_service.js +1 -1
  82. package/dist/market_adapter/core/market_adapter_service.js.map +1 -1
  83. package/dist/market_adapter/core/strategies/regime_gate.d.ts +1 -2
  84. package/dist/market_adapter/core/strategies/regime_gate.d.ts.map +1 -1
  85. package/dist/market_adapter/core/strategies/regime_gate.js +1 -1
  86. package/dist/market_adapter/core/strategies/regime_gate.js.map +1 -1
  87. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +2 -2
  88. package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -1
  89. package/dist/market_adapter/inputs/fetch_lp_data.d.ts +5 -5
  90. package/dist/market_adapter/inputs/fetch_lp_data.js +5 -5
  91. package/dist/market_adapter/inputs/kibana_source.d.ts +1 -2
  92. package/dist/market_adapter/inputs/kibana_source.d.ts.map +1 -1
  93. package/dist/market_adapter/inputs/kibana_source.js +1 -1
  94. package/dist/market_adapter/inputs/kibana_source.js.map +1 -1
  95. package/dist/market_adapter/lp_chart_strategy_loader.d.ts +1 -2
  96. package/dist/market_adapter/lp_chart_strategy_loader.d.ts.map +1 -1
  97. package/dist/market_adapter/lp_chart_strategy_loader.js +4 -4
  98. package/dist/market_adapter/lp_chart_strategy_loader.js.map +1 -1
  99. package/dist/market_adapter/market_adapter.d.ts.map +1 -1
  100. package/dist/market_adapter/market_adapter.js +2 -1
  101. package/dist/market_adapter/market_adapter.js.map +1 -1
  102. package/dist/modules/account_bots.d.ts +3 -0
  103. package/dist/modules/account_bots.d.ts.map +1 -1
  104. package/dist/modules/account_bots.js +156 -46
  105. package/dist/modules/account_bots.js.map +1 -1
  106. package/dist/modules/account_orders.d.ts +1 -7
  107. package/dist/modules/account_orders.d.ts.map +1 -1
  108. package/dist/modules/account_orders.js +9 -17
  109. package/dist/modules/account_orders.js.map +1 -1
  110. package/dist/modules/bitshares_client.d.ts +2 -14
  111. package/dist/modules/bitshares_client.d.ts.map +1 -1
  112. package/dist/modules/bitshares_client.js +22 -31
  113. package/dist/modules/bitshares_client.js.map +1 -1
  114. package/dist/modules/bot_settings.js +1 -1
  115. package/dist/modules/bot_settings.js.map +1 -1
  116. package/dist/modules/chain_keys.js +2 -2
  117. package/dist/modules/chain_keys.js.map +1 -1
  118. package/dist/modules/chain_orders.d.ts +2 -3
  119. package/dist/modules/chain_orders.d.ts.map +1 -1
  120. package/dist/modules/chain_orders.js +8 -25
  121. package/dist/modules/chain_orders.js.map +1 -1
  122. package/dist/modules/config.d.ts.map +1 -1
  123. package/dist/modules/config.js +4 -1
  124. package/dist/modules/config.js.map +1 -1
  125. package/dist/modules/constants.d.ts +0 -1
  126. package/dist/modules/constants.d.ts.map +1 -1
  127. package/dist/modules/constants.js +0 -4
  128. package/dist/modules/constants.js.map +1 -1
  129. package/dist/modules/credential_policy.js +1 -1
  130. package/dist/modules/credential_policy.js.map +1 -1
  131. package/dist/modules/dexbot_class.d.ts.map +1 -1
  132. package/dist/modules/dexbot_class.js +3 -2
  133. package/dist/modules/dexbot_class.js.map +1 -1
  134. package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
  135. package/dist/modules/dexbot_state_recovery.js +2 -1
  136. package/dist/modules/dexbot_state_recovery.js.map +1 -1
  137. package/dist/modules/fund_registry.d.ts.map +1 -1
  138. package/dist/modules/fund_registry.js +5 -2
  139. package/dist/modules/fund_registry.js.map +1 -1
  140. package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -1
  141. package/dist/modules/launcher/bot_supervisor.js +6 -7
  142. package/dist/modules/launcher/bot_supervisor.js.map +1 -1
  143. package/dist/modules/launcher/foreign_cred_daemon.d.ts.map +1 -1
  144. package/dist/modules/launcher/foreign_cred_daemon.js +6 -0
  145. package/dist/modules/launcher/foreign_cred_daemon.js.map +1 -1
  146. package/dist/modules/launcher/market_adapter_runtime.d.ts +2 -3
  147. package/dist/modules/launcher/market_adapter_runtime.d.ts.map +1 -1
  148. package/dist/modules/launcher/market_adapter_runtime.js +7 -15
  149. package/dist/modules/launcher/market_adapter_runtime.js.map +1 -1
  150. package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
  151. package/dist/modules/launcher/monolithic_runtime.js +1 -0
  152. package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
  153. package/dist/modules/launcher/status_reporting.d.ts +1 -2
  154. package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
  155. package/dist/modules/launcher/status_reporting.js +1 -4
  156. package/dist/modules/launcher/status_reporting.js.map +1 -1
  157. package/dist/modules/order/accounting.js +2 -2
  158. package/dist/modules/order/accounting.js.map +1 -1
  159. package/dist/modules/order/format.d.ts +0 -8
  160. package/dist/modules/order/format.d.ts.map +1 -1
  161. package/dist/modules/order/format.js +0 -8
  162. package/dist/modules/order/format.js.map +1 -1
  163. package/dist/modules/order/logger.js +1 -1
  164. package/dist/modules/order/logger.js.map +1 -1
  165. package/dist/modules/order/logger_state.d.ts +1 -13
  166. package/dist/modules/order/logger_state.d.ts.map +1 -1
  167. package/dist/modules/order/logger_state.js +1 -11
  168. package/dist/modules/order/logger_state.js.map +1 -1
  169. package/dist/modules/order/processed_fill_store.d.ts +0 -5
  170. package/dist/modules/order/processed_fill_store.d.ts.map +1 -1
  171. package/dist/modules/order/processed_fill_store.js +1 -12
  172. package/dist/modules/order/processed_fill_store.js.map +1 -1
  173. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  174. package/dist/modules/order/sync_engine.js +10 -3
  175. package/dist/modules/order/sync_engine.js.map +1 -1
  176. package/dist/modules/order/utils/math.d.ts +7 -1
  177. package/dist/modules/order/utils/math.d.ts.map +1 -1
  178. package/dist/modules/order/utils/math.js +19 -1
  179. package/dist/modules/order/utils/math.js.map +1 -1
  180. package/dist/modules/order/utils/order.d.ts.map +1 -1
  181. package/dist/modules/order/utils/order.js +10 -2
  182. package/dist/modules/order/utils/order.js.map +1 -1
  183. package/dist/modules/runtime_settings.js +3 -1
  184. package/dist/modules/runtime_settings.js.map +1 -1
  185. package/dist/modules/settings_merge.d.ts.map +1 -1
  186. package/dist/modules/settings_merge.js +3 -1
  187. package/dist/modules/settings_merge.js.map +1 -1
  188. package/dist/modules/storage/browser_adapter.d.ts +8 -3
  189. package/dist/modules/storage/browser_adapter.d.ts.map +1 -1
  190. package/dist/modules/storage/browser_adapter.js +53 -4
  191. package/dist/modules/storage/browser_adapter.js.map +1 -1
  192. package/dist/modules/utils/sanitize_key.d.ts +7 -0
  193. package/dist/modules/utils/sanitize_key.d.ts.map +1 -0
  194. package/dist/modules/utils/sanitize_key.js +15 -0
  195. package/dist/modules/utils/sanitize_key.js.map +1 -0
  196. package/dist/pm2.js +6 -6
  197. package/dist/pm2.js.map +1 -1
  198. package/dist/scripts/analyze-orders.d.ts +2 -2
  199. package/dist/scripts/analyze-orders.d.ts.map +1 -1
  200. package/dist/scripts/analyze-orders.js +4 -12
  201. package/dist/scripts/analyze-orders.js.map +1 -1
  202. package/dist/scripts/runner.js +1 -1
  203. package/dist/scripts/runner.js.map +1 -1
  204. package/dist/scripts/update.js +1 -1
  205. package/dist/scripts/update.js.map +1 -1
  206. package/docs/BITSHARES_ONBOARDING.md +63 -18
  207. package/docs/COPY_ON_WRITE_MASTER_PLAN.md +5 -14
  208. package/docs/COW_INVARIANTS.md +4 -4
  209. package/docs/CREDENTIAL_SECURITY.md +0 -11
  210. package/docs/DEXBOT_COMPARISON.md +11 -11
  211. package/docs/EVOLUTION.md +11 -10
  212. package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +27 -83
  213. package/docs/GRID_RECALCULATION.md +1 -13
  214. package/docs/GRID_RECONCILE.md +24 -24
  215. package/docs/LIFECYCLE.md +4 -4
  216. package/docs/LOGGING.md +1 -1
  217. package/docs/MPA_CREDIT_USAGE.md +3 -3
  218. package/docs/README.md +2 -2
  219. package/docs/WORKFLOW.md +2 -2
  220. package/docs/architecture.md +43 -38
  221. package/docs/developer_guide.md +16 -18
  222. package/market_adapter/README.md +0 -2
  223. package/modules/README.md +2 -1
  224. package/package.json +1 -1
  225. package/scripts/README.md +1 -1
  226. package/claw/openclaw.plugin.json +0 -13
  227. package/dist/market_adapter/merge_lp_data.d.ts +0 -3
  228. package/dist/market_adapter/merge_lp_data.d.ts.map +0 -1
  229. package/dist/market_adapter/merge_lp_data.js +0 -125
  230. package/dist/market_adapter/merge_lp_data.js.map +0 -1
  231. package/dist/market_adapter/utils/paths.d.ts +0 -3
  232. package/dist/market_adapter/utils/paths.d.ts.map +0 -1
  233. package/dist/market_adapter/utils/paths.js +0 -5
  234. package/dist/market_adapter/utils/paths.js.map +0 -1
@@ -43,6 +43,106 @@ export function failure(id: any, code: any, message: any, data = undefined) {
43
43
  });
44
44
  }
45
45
 
46
+ /**
47
+ * Build an Error that createJsonRpcToolsHandler maps to a JSON-RPC error
48
+ * response with the given code instead of the isError:true result convention.
49
+ * Use -32602 for caller-input problems (invalid params, unknown tool).
50
+ */
51
+ export function jsonRpcError(code: number, message: string): Error & { statusCode: number } {
52
+ const err = new Error(message) as Error & { statusCode: number };
53
+ err.statusCode = code;
54
+ return err;
55
+ }
56
+
57
+ /**
58
+ * Shared JSON-RPC request shell for the claw/memu stdio servers: initialize
59
+ * handshake (echoing the client's protocolVersion), notifications/initialized,
60
+ * ping, tools/list (internal failures map to -32603), tools/call delegation,
61
+ * and method-not-found. `callTool(params, defaults)` returns the result value
62
+ * for a successful call; thrown Errors carrying a numeric `statusCode`
63
+ * become JSON-RPC errors, everything else becomes an isError:true result.
64
+ */
65
+ export function createJsonRpcToolsHandler(options: {
66
+ serverName: string;
67
+ serverVersion?: string;
68
+ listTools: () => any[] | Promise<any[]>;
69
+ callTool: (params: any, defaults: any) => Promise<any>;
70
+ }) {
71
+ const { serverName, listTools, callTool } = options;
72
+ const serverVersion = options.serverVersion || '0.1.0';
73
+
74
+ return async function handleRequest(message: any, defaults: any) {
75
+ const { id, method, params } = message;
76
+
77
+ switch (method) {
78
+ case 'initialize':
79
+ return success(id, {
80
+ // Echo the client's requested protocol version when provided, so a
81
+ // JSONL-speaking client can negotiate its own version.
82
+ protocolVersion: params?.protocolVersion || '2024-11-05',
83
+ capabilities: {
84
+ tools: {
85
+ listChanged: false
86
+ }
87
+ },
88
+ serverInfo: {
89
+ name: serverName,
90
+ version: serverVersion
91
+ }
92
+ });
93
+
94
+ case 'notifications/initialized':
95
+ return;
96
+
97
+ case 'ping':
98
+ return success(id, {});
99
+
100
+ case 'tools/list':
101
+ try {
102
+ return success(id, {
103
+ tools: await listTools()
104
+ });
105
+ } catch (error: any) {
106
+ // Catalog load failure is a server-side problem, not bad input.
107
+ return failure(id, -32603, `Failed to load tool catalog: ${error && error.message ? error.message : String(error)}`);
108
+ }
109
+
110
+ case 'tools/call': {
111
+ try {
112
+ const result = await callTool(params, defaults);
113
+ return success(id, {
114
+ content: [
115
+ {
116
+ type: 'text',
117
+ text: JSON.stringify(result, null, 2)
118
+ }
119
+ ],
120
+ structuredContent: result
121
+ });
122
+ } catch (error: any) {
123
+ if (error && typeof error.statusCode === 'number') {
124
+ return failure(id, error.statusCode, error.message);
125
+ }
126
+ return success(id, {
127
+ content: [
128
+ {
129
+ type: 'text',
130
+ text: error && error.stack ? error.stack : String(error)
131
+ }
132
+ ],
133
+ isError: true
134
+ });
135
+ }
136
+ }
137
+
138
+ default:
139
+ if (id !== undefined) {
140
+ return failure(id, -32601, `Method not found: ${method}`);
141
+ }
142
+ }
143
+ };
144
+ }
145
+
46
146
  export function createMessageParser(onMessage: any, onDrain?: () => void) {
47
147
  if (!hasProcess()) {
48
148
  throw new Error('MCP stdio transport not available in this environment');
@@ -128,6 +228,7 @@ export async function runMcpServer(parseArgs: (argv: string[]) => Record<string,
128
228
  }
129
229
  });
130
230
  let lastQueue = Promise.resolve();
231
+ let finishInput: () => void = () => {};
131
232
 
132
233
  runtime.stdin!.on('data', (chunk) => {
133
234
  lastQueue = parser.push(chunk);
@@ -138,10 +239,18 @@ export async function runMcpServer(parseArgs: (argv: string[]) => Record<string,
138
239
  runtime.stdin!.pause();
139
240
  }
140
241
  });
242
+ // Without an 'error' listener an EPIPE/EIO on stdin emits an unhandled
243
+ // 'error' event and crashes the MCP server mid-session. Treat it as EOF.
244
+ runtime.stdin!.on('error', (err: any) => {
245
+ runtime.stderr.write(`[mcp] stdin error: ${err && err.stack ? err.stack : String(err)}\n`);
246
+ // The 'end' event never fires after a stream error; unblock the wait.
247
+ finishInput();
248
+ });
141
249
  runtime.stdin!.resume();
142
250
 
143
251
  await new Promise<void>((resolve) => {
144
252
  runtime.stdin!.on('end', () => resolve());
253
+ finishInput = resolve;
145
254
  });
146
255
  await lastQueue;
147
256
  }
@@ -41,17 +41,7 @@ const DEXBOT_VERSION = (() => {
41
41
  })();
42
42
 
43
43
  function resolveMemuScript() {
44
- const candidates = [
45
- PATHS.CLAW.MEMU_RUNNER_SCRIPT,
46
- ];
47
-
48
- for (const candidate of candidates) {
49
- if (storage.exists(candidate)) {
50
- return candidate;
51
- }
52
- }
53
-
54
- return candidates[0];
44
+ return PATHS.CLAW.MEMU_RUNNER_SCRIPT;
55
45
  }
56
46
 
57
47
  function ensureMemuDir(dir: any) {
@@ -132,6 +122,11 @@ function runMemuPython(args: string[], options: Record<string, any> = {}) {
132
122
  });
133
123
 
134
124
  if (options.stdin) {
125
+ // The child may exit before it drains stdin (bad args, crash); without
126
+ // an 'error' listener the resulting EPIPE crashes the host process.
127
+ child.stdin.on('error', () => {
128
+ // Best-effort write; the close handler reports the real exit status.
129
+ });
135
130
  child.stdin.write(JSON.stringify(options.stdin));
136
131
  child.stdin.end();
137
132
  }
@@ -431,26 +426,78 @@ function describeMemuBridge(options: Record<string, any> = {}) {
431
426
  };
432
427
  }
433
428
 
429
+ /**
430
+ * Required-argument spec shared by runMemuCommand, the memU MCP server, and
431
+ * the claw bridge's memu-* commands, so every surface rejects bad input with
432
+ * the same message instead of maintaining three copies of the checks.
433
+ */
434
+ const MEMU_REQUIRED_ARGS: Record<string, { groups: string[][]; message: string }> = {
435
+ 'memorize': {
436
+ groups: [['resourceUrl'], ['modality']],
437
+ message: 'memorize requires resourceUrl and modality'
438
+ },
439
+ 'retrieve': {
440
+ groups: [['queries']],
441
+ message: 'retrieve requires queries'
442
+ },
443
+ 'create-item': {
444
+ groups: [['categoryId', 'categoryName', 'category'], ['summary']],
445
+ message: 'create-item requires categoryId or categoryName, plus summary'
446
+ },
447
+ 'update-item': {
448
+ groups: [['itemId'], ['updates']],
449
+ message: 'update-item requires itemId and updates'
450
+ },
451
+ 'delete-item': {
452
+ groups: [['itemId']],
453
+ message: 'delete-item requires itemId'
454
+ },
455
+ 'memorize-conversation': {
456
+ groups: [['messages']],
457
+ message: 'memorize-conversation requires messages array'
458
+ },
459
+ 'memorize-trading-context': {
460
+ groups: [['context']],
461
+ message: 'memorize-trading-context requires context'
462
+ },
463
+ 'retrieve-trading-context': {
464
+ groups: [['query']],
465
+ message: 'retrieve-trading-context requires query'
466
+ }
467
+ };
468
+
469
+ /**
470
+ * Throw when a memU command is missing required arguments.
471
+ * Each group lists acceptable alternative field names; every group must be
472
+ * satisfied by at least one present (truthy) field.
473
+ */
474
+ function validateMemuCommandArgs(command: string, options: Record<string, any>) {
475
+ const spec = MEMU_REQUIRED_ARGS[command];
476
+ if (!spec) {
477
+ return;
478
+ }
479
+
480
+ const satisfied = spec.groups.every(
481
+ (group) => group.some((field) => options[field])
482
+ );
483
+ if (!satisfied) {
484
+ throw new Error(spec.message);
485
+ }
486
+ }
487
+
434
488
  async function runMemuCommand(command: string, options: Record<string, any> = {}) {
489
+ validateMemuCommandArgs(command, options);
435
490
  const bridge = createMemuBridge(options);
436
491
 
437
492
  switch (command) {
438
493
  case 'manifest':
439
494
  return describeMemuBridge(options);
440
495
 
441
- case 'memorize': {
442
- if (!options.resourceUrl || !options.modality) {
443
- throw new Error('memorize requires resourceUrl and modality');
444
- }
496
+ case 'memorize':
445
497
  return bridge.memorize(options.resourceUrl, options.modality, options.user);
446
- }
447
498
 
448
- case 'retrieve': {
449
- if (!options.queries) {
450
- throw new Error('retrieve requires queries');
451
- }
499
+ case 'retrieve':
452
500
  return bridge.retrieve(options.queries, options.where, options.method || 'rag');
453
- }
454
501
 
455
502
  case 'list-categories':
456
503
  return bridge.listCategories(normalizeScopeWhere(options.where, options.user));
@@ -458,32 +505,19 @@ async function runMemuCommand(command: string, options: Record<string, any> = {}
458
505
  case 'list-items':
459
506
  return bridge.listItems(normalizeScopeWhere(options.where, options.user));
460
507
 
461
- case 'create-item': {
462
- const categoryRef = options.categoryId || options.categoryName || options.category;
463
- if (!categoryRef || !options.summary) {
464
- throw new Error('create-item requires categoryId or categoryName, plus summary');
465
- }
508
+ case 'create-item':
466
509
  return bridge.createMemoryItem(
467
- categoryRef,
510
+ options.categoryId || options.categoryName || options.category,
468
511
  options.summary,
469
512
  options.memoryType || 'knowledge',
470
513
  options.user
471
514
  );
472
- }
473
515
 
474
- case 'update-item': {
475
- if (!options.itemId || !options.updates) {
476
- throw new Error('update-item requires itemId and updates');
477
- }
516
+ case 'update-item':
478
517
  return bridge.updateMemoryItem(options.itemId, options.updates);
479
- }
480
518
 
481
- case 'delete-item': {
482
- if (!options.itemId) {
483
- throw new Error('delete-item requires itemId');
484
- }
519
+ case 'delete-item':
485
520
  return bridge.deleteMemoryItem(options.itemId);
486
- }
487
521
 
488
522
  case 'clear':
489
523
  return bridge.clearMemory(normalizeScopeWhere(options.where, options.user));
@@ -491,31 +525,19 @@ async function runMemuCommand(command: string, options: Record<string, any> = {}
491
525
  case 'status':
492
526
  return bridge.getStatus(normalizeScopeWhere(options.where, options.user));
493
527
 
494
- case 'memorize-conversation': {
495
- if (!options.messages) {
496
- throw new Error('memorize-conversation requires messages array');
497
- }
528
+ case 'memorize-conversation':
498
529
  return bridge.memorizeConversation(options.messages, options.user);
499
- }
500
530
 
501
- case 'memorize-trading-context': {
502
- if (!options.context) {
503
- throw new Error('memorize-trading-context requires context');
504
- }
531
+ case 'memorize-trading-context':
505
532
  return bridge.memorizeTradingContext(options.context, options.user);
506
- }
507
533
 
508
- case 'retrieve-trading-context': {
509
- if (!options.query) {
510
- throw new Error('retrieve-trading-context requires query');
511
- }
534
+ case 'retrieve-trading-context':
512
535
  return bridge.retrieveTradingContext(options.query, options.user);
513
- }
514
536
 
515
537
  default:
516
538
  throw new Error(`Unsupported memU command: ${command}`);
517
539
  }
518
540
  }
519
541
 
520
- export { createMemuBridge, describeMemuBridge, runMemuCommand, resolveMemuScript, DEFAULT_MEMU_DIR }
542
+ export { createMemuBridge, describeMemuBridge, runMemuCommand, validateMemuCommandArgs }
521
543
 
@@ -5,7 +5,7 @@ import { loadDexbotOrderUtils } from './dexbot_bridge.js';
5
5
 
6
6
  const CORE_SYMBOL = 'BTS';
7
7
 
8
- async function requireBtsBackedMpa(mpaSymbolOrId: any) {
8
+ async function requireBtsBackedMpa(mpaSymbolOrId: any, hint?: string) {
9
9
  const mpaAsset = await getAsset(mpaSymbolOrId);
10
10
  if (!mpaAsset) {
11
11
  throw new Error(`Asset not found: ${mpaSymbolOrId}`);
@@ -19,7 +19,8 @@ async function requireBtsBackedMpa(mpaSymbolOrId: any) {
19
19
  throw new Error(`Could not resolve backing asset for ${mpaSymbolOrId}`);
20
20
  }
21
21
  if (backingAsset.symbol !== CORE_SYMBOL) {
22
- throw new Error(`${mpaAsset.symbol || mpaSymbolOrId} is backed by ${backingAsset.symbol}, not ${CORE_SYMBOL}`);
22
+ const hintSuffix = hint ? `; ${hint}` : '';
23
+ throw new Error(`${mpaAsset.symbol || mpaSymbolOrId} is backed by ${backingAsset.symbol}, not ${CORE_SYMBOL}${hintSuffix}`);
23
24
  }
24
25
 
25
26
  return {
@@ -62,5 +63,27 @@ function computeBtsPerMpa(settlementPrice: any, mpaAsset: any, backingAsset: any
62
63
  return null;
63
64
  }
64
65
 
65
- export { CORE_SYMBOL, computeBtsPerMpa, requireBtsBackedMpa }
66
+ /**
67
+ * Compute the normalized debt/collateral figures for a raw call order.
68
+ * Canonical implementation shared by position discovery and the position
69
+ * manager so both surfaces report identical amounts and ratios.
70
+ */
71
+ function computeCallOrderAmounts(callOrder: any, mpaAsset: any, backingAsset: any, bitassetData: any) {
72
+ const blockchainToFloat = getBlockchainToFloat();
73
+ const debtAmount = blockchainToFloat(callOrder.debt, mpaAsset.precision);
74
+ const collateralAmount = blockchainToFloat(callOrder.collateral, backingAsset.precision);
75
+ const btsPerMpa = computeBtsPerMpa(bitassetData?.current_feed?.settlement_price, mpaAsset, backingAsset);
76
+ const debtValueInBts = debtAmount && btsPerMpa ? debtAmount * btsPerMpa : 0;
77
+ const collateralRatio = debtValueInBts > 0 ? collateralAmount / debtValueInBts : null;
78
+
79
+ return {
80
+ btsPerMpa,
81
+ collateralAmount: collateralAmount || 0,
82
+ collateralRatio,
83
+ debtAmount: debtAmount || 0,
84
+ debtValueInBts
85
+ };
86
+ }
87
+
88
+ export { CORE_SYMBOL, computeBtsPerMpa, computeCallOrderAmounts, getBlockchainToFloat, requireBtsBackedMpa }
66
89
 
@@ -9,16 +9,11 @@
9
9
  */
10
10
 
11
11
 
12
- import { loadDexbotOrderUtils } from './dexbot_bridge.js';
13
- import { computeBtsPerMpa } from './mpa_utils.js';
14
12
  import { roundTo } from '../../modules/order/utils/math.js';
15
13
  'use strict';
16
14
 
17
15
  import { getAsset, getBackingAsset, getBitassetData, getFullAccount } from './chain_queries.js';
18
-
19
- function getBlockchainToFloat() {
20
- return loadDexbotOrderUtils().blockchainToFloat;
21
- }
16
+ import { computeCallOrderAmounts } from './mpa_utils.js';
22
17
 
23
18
  /**
24
19
  * Normalize a raw call order into a position object compatible with
@@ -31,13 +26,7 @@ function getBlockchainToFloat() {
31
26
  * @returns {Object} Normalized position
32
27
  */
33
28
  function normalizeCallOrder(callOrder: any, mpaAsset: any, backingAsset: any, bitassetData: any) {
34
- const blockchainToFloat = getBlockchainToFloat();
35
- const debtAmount = blockchainToFloat(callOrder.debt, mpaAsset.precision);
36
- const collateralAmount = blockchainToFloat(callOrder.collateral, backingAsset.precision);
37
- const settlement = bitassetData?.current_feed?.settlement_price;
38
- const btsPerMpa = computeBtsPerMpa(settlement, mpaAsset, backingAsset);
39
- const debtValueInBts = debtAmount && btsPerMpa ? debtAmount * btsPerMpa : 0;
40
- const collateralRatio = debtValueInBts > 0 ? collateralAmount / debtValueInBts : null;
29
+ const amounts = computeCallOrderAmounts(callOrder, mpaAsset, backingAsset, bitassetData);
41
30
 
42
31
  return {
43
32
  id: callOrder.id,
@@ -48,11 +37,11 @@ function normalizeCallOrder(callOrder: any, mpaAsset: any, backingAsset: any, bi
48
37
  backingSymbol: backingAsset.symbol,
49
38
  onChain: {
50
39
  callOrderId: callOrder.id,
51
- collateralAmount: collateralAmount || 0,
52
- collateralRatio,
53
- debtAmount: debtAmount || 0,
54
- debtValueInBts,
55
- btsPerMpa,
40
+ collateralAmount: amounts.collateralAmount,
41
+ collateralRatio: amounts.collateralRatio,
42
+ debtAmount: amounts.debtAmount,
43
+ debtValueInBts: amounts.debtValueInBts,
44
+ btsPerMpa: amounts.btsPerMpa,
56
45
  feedPublicationTime: bitassetData?.current_feed_publication_time || null,
57
46
  },
58
47
  };
@@ -76,21 +65,21 @@ async function discoverPositions(accountName: string) {
76
65
  callOrders.map((co: any) => co?.call_price?.quote?.asset_id).filter(Boolean)
77
66
  )];
78
67
 
79
- // Resolve all assets in parallel
68
+ // Resolve every debt asset's MPA/backing/bitasset triple in parallel.
80
69
  const assetCache = new Map();
81
70
  const bitassetCache = new Map();
82
71
 
83
- for (const assetId of debtAssetIds) {
84
- const mpaAsset = await getAsset(assetId);
85
- if (!mpaAsset) continue;
86
- assetCache.set(assetId, mpaAsset);
72
+ await Promise.all(debtAssetIds.map(async (assetId) => {
73
+ const [mpaAsset, backingAsset, bitassetData] = await Promise.all([
74
+ getAsset(assetId).catch(() => null),
75
+ getBackingAsset(assetId).catch(() => null),
76
+ getBitassetData(assetId).catch(() => null)
77
+ ]);
87
78
 
88
- const backingAsset = await getBackingAsset(assetId);
79
+ if (mpaAsset) assetCache.set(assetId, mpaAsset);
89
80
  if (backingAsset) assetCache.set(`backing:${assetId}`, backingAsset);
90
-
91
- const bitassetData = await getBitassetData(assetId);
92
81
  if (bitassetData) bitassetCache.set(assetId, bitassetData);
93
- }
82
+ }));
94
83
 
95
84
  // Normalize each call order
96
85
  const positions: any[] = [];
@@ -3,11 +3,10 @@ import { getStorage } from '../../modules/storage/index.js';
3
3
  import { path } from '../../modules/path_api.js';
4
4
  import { PATHS } from '../../modules/paths.js';
5
5
  import { Config } from '../../modules/config.js';
6
- import { requireBtsBackedMpa, computeBtsPerMpa } from './mpa_utils.js';
6
+ import { requireBtsBackedMpa, computeCallOrderAmounts, getBlockchainToFloat } from './mpa_utils.js';
7
7
  import { listenForFills } from './chain_actions.js';
8
8
  import { writeJsonFileAtomic } from './dexbot_profiles.js';
9
- import { loadDexbotOrderUtils } from './dexbot_bridge.js';
10
- import { clone } from './utils.js';
9
+ import { clone, requirePositiveNumber } from './utils.js';
11
10
  const storage = getStorage();
12
11
  import { closeShortOnBts, openShortOnBts, placeTakeProfitBuyOrderOnBts } from './short_mpa_strategy.js';
13
12
  import { getAsset, getBackingAsset, getBalances, getBitassetData, getFullAccount } from './chain_queries.js';
@@ -15,10 +14,6 @@ import { getErrorMessage } from '../../modules/utils/errors.js';
15
14
 
16
15
  import type { ShortPositionOptions, PositionManagerOptions } from './types.js';
17
16
 
18
- function getBlockchainToFloat() {
19
- return loadDexbotOrderUtils().blockchainToFloat;
20
- }
21
-
22
17
  const DEFAULT_STATE_PATH = PATHS.CLAW.POSITIONS_FILE;
23
18
  const STRATEGY_NAME = 'short-mpa-bts';
24
19
 
@@ -38,14 +33,6 @@ function requireString(value: any, fieldName: string) {
38
33
  return value.trim();
39
34
  }
40
35
 
41
- function requirePositiveNumber(value: any, fieldName: string) {
42
- const numericValue = Number(value);
43
- if (!Number.isFinite(numericValue) || numericValue <= 0) {
44
- throw new Error(`${fieldName} must be a positive number`);
45
- }
46
- return numericValue;
47
- }
48
-
49
36
  function almostZero(value: any, epsilon: number = 1e-12) {
50
37
  return Math.abs(Number(value) || 0) <= epsilon;
51
38
  }
@@ -116,18 +103,6 @@ function toTrackedAsset(asset: any) {
116
103
  return asset ? { id: asset.id, precision: asset.precision, symbol: asset.symbol } : null;
117
104
  }
118
105
 
119
- function resolvePositionAssets(position: any) {
120
- const tracked = position?.assets || {};
121
- return {
122
- collateral: tracked.collateral || null,
123
- mpa: tracked.mpa || null
124
- };
125
- }
126
-
127
- function getOrderTracking(position: any, side: string) {
128
- return side === 'entry' ? position.entry : position.exit;
129
- }
130
-
131
106
  function getAssetPrecisionFromTracking(orderTracking: any, assetId: string) {
132
107
  if (orderTracking?.sellAsset?.id === assetId) {
133
108
  return orderTracking.sellAsset.precision;
@@ -265,7 +240,7 @@ function refreshPositionPnl(position: any) {
265
240
  }
266
241
 
267
242
  function normalizeCallPosition(callOrder: any, mpaAsset: any, backingAsset: any, bitassetData: any) {
268
- if (!callOrder) {
243
+ if (!callOrder || !mpaAsset || !backingAsset) {
269
244
  return {
270
245
  collateralAmount: 0,
271
246
  collateralRatio: null,
@@ -276,18 +251,13 @@ function normalizeCallPosition(callOrder: any, mpaAsset: any, backingAsset: any,
276
251
  };
277
252
  }
278
253
 
279
- const blockchainToFloat = getBlockchainToFloat();
280
- const debtAmount = blockchainToFloat(callOrder.debt, mpaAsset.precision);
281
- const collateralAmount = blockchainToFloat(callOrder.collateral, backingAsset.precision);
282
- const btsPerMpa = computeBtsPerMpa(bitassetData?.current_feed?.settlement_price, mpaAsset, backingAsset);
283
- const debtValueInBts = debtAmount && btsPerMpa ? debtAmount * btsPerMpa : 0;
284
- const collateralRatio = debtValueInBts > 0 ? collateralAmount / debtValueInBts : null;
254
+ const amounts = computeCallOrderAmounts(callOrder, mpaAsset, backingAsset, bitassetData);
285
255
 
286
256
  return {
287
- collateralAmount: collateralAmount || 0,
288
- collateralRatio,
289
- debtAmount: debtAmount || 0,
290
- debtValueInBts,
257
+ collateralAmount: amounts.collateralAmount,
258
+ collateralRatio: amounts.collateralRatio,
259
+ debtAmount: amounts.debtAmount,
260
+ debtValueInBts: amounts.debtValueInBts,
291
261
  exists: true,
292
262
  feedPublicationTime: bitassetData?.current_feed_publication_time || null
293
263
  };
@@ -560,7 +530,14 @@ class PositionManager {
560
530
  });
561
531
  position.lastCloseTx = result?.repayResult?.raw || null;
562
532
 
563
- await this.syncPosition(positionId);
533
+ // Persist before syncing: syncPosition can throw (network failure, asset
534
+ // resolution), and losing the close event/tx record after a successful
535
+ // on-chain repay would corrupt the position history. Mirrors openShort.
536
+ await this.saveState();
537
+
538
+ await this.syncPosition(positionId).catch((syncErr: any) => {
539
+ console.warn(`[POSITION] Post-close sync failed for ${positionId}: ${getErrorMessage(syncErr)}`);
540
+ });
564
541
  return this.getPosition(positionId);
565
542
  }
566
543
 
@@ -577,7 +554,15 @@ class PositionManager {
577
554
  const mpaAsset = await getAsset(position.debt.asset);
578
555
  const backingAsset = await getBackingAsset(position.debt.asset);
579
556
  const bitassetData = await getBitassetData(position.debt.asset);
580
- const callOrder = callOrders.find((entry: any) => entry?.call_price?.quote?.asset_id === mpaAsset?.id) || null;
557
+ // Only match a call order when the debt asset resolved; matching against
558
+ // an undefined id could pair the position with an unrelated call order,
559
+ // and normalizing without asset precision would crash.
560
+ if (!mpaAsset) {
561
+ console.warn(`[POSITION] Could not resolve debt asset ${position.debt.asset} for ${positionId}; skipping on-chain debt sync`);
562
+ }
563
+ const callOrder = mpaAsset
564
+ ? callOrders.find((entry: any) => entry?.call_price?.quote?.asset_id === mpaAsset.id) || null
565
+ : null;
581
566
  const debtState = normalizeCallPosition(callOrder, mpaAsset, backingAsset, bitassetData);
582
567
  const openOrderIds = openOrders.map((entry: any) => entry?.id).filter(Boolean);
583
568
  const entryOrderOpen = position.entry.orderId ? openOrderIds.includes(position.entry.orderId) : false;
@@ -692,8 +677,14 @@ class PositionManager {
692
677
  });
693
678
  changed = true;
694
679
 
680
+ // Isolate user callback failures: a rejected onFill must not abort
681
+ // this handler before saveState()/post-fill syncs persist the fill.
695
682
  if (typeof onFill === 'function') {
696
- await onFill(clone(position), fill);
683
+ try {
684
+ await onFill(clone(position), fill);
685
+ } catch (err: any) {
686
+ console.error(`[POSITION] onFill callback failed for ${position.id}: ${getErrorMessage(err)}`);
687
+ }
697
688
  }
698
689
  }
699
690
  }
@@ -1,6 +1,5 @@
1
1
 
2
2
  import { getStorage } from '../../modules/storage/index.js';
3
- import { path } from '../../modules/path_api.js';
4
3
  import { PATHS } from '../../modules/paths.js';
5
4
  import { PositionManager, DEFAULT_STATE_PATH } from './position_manager.js';
6
5
  import * as client from './bitshares_client.js';
@@ -178,12 +177,14 @@ function createPositionManagerWatcher(options: Record<string, any> = {}) {
178
177
  throw new Error('accountName is required. Pass --account or set BITSHARES_ACCOUNT.');
179
178
  }
180
179
 
181
- running = true;
182
180
  logger.info(`[position-manager-watch] starting for ${resolvedOptions.accountName}`);
183
181
 
182
+ // Only flag running once the connection succeeded: a failed start must
183
+ // leave the watcher non-running (and re-startable) with healthy=false.
184
184
  await waitForConnected().catch((err: any) => {
185
185
  throw new Error(`BitShares connection not ready: ${getErrorMessage(err)}`);
186
186
  });
187
+ running = true;
187
188
 
188
189
  await manager.syncAllPositions()
189
190
  .then(() => recordSyncSuccess())
@@ -2,15 +2,7 @@
2
2
  import { Config } from '../../modules/config.js';
3
3
  import { borrowMpa, createLimitOrder, repayMpaDebt } from './chain_actions.js';
4
4
  import { requireBtsBackedMpa } from './mpa_utils.js';
5
-
6
- function requirePositiveNumber(value: any, fieldName: string) {
7
- const numericValue = Number(value);
8
- if (!Number.isFinite(numericValue) || numericValue <= 0) {
9
- throw new Error(`${fieldName} must be a positive number`);
10
- }
11
-
12
- return numericValue;
13
- }
5
+ import { requirePositiveNumber } from './utils.js';
14
6
 
15
7
  function requireNonNegativeNumber(value: any, fieldName: string) {
16
8
  const numericValue = Number(value);
@@ -42,11 +42,13 @@ export function tomlString(value: any) {
42
42
  }
43
43
 
44
44
  export function buildSkillTomlLines(skillName: string, description: string, tags: string[], tools: any[]) {
45
+ // name/description go through tomlString so embedded quotes/backslashes
46
+ // produce valid escaped TOML basic strings.
45
47
  const lines = [
46
48
  '[skill]',
47
- `name = "${skillName}"`,
48
- `description = "${description}"`,
49
- `version = "${DEXBOT_VERSION}"`,
49
+ `name = ${tomlString(skillName)}`,
50
+ `description = ${tomlString(description)}`,
51
+ `version = ${tomlString(DEXBOT_VERSION)}`,
50
52
  `tags = [${tags.map(t => JSON.stringify(t)).join(', ')}]`
51
53
  ];
52
54
 
@@ -5,5 +5,13 @@ function clone(value: any) {
5
5
  return JSON.parse(JSON.stringify(value));
6
6
  }
7
7
 
8
- export { clone }
8
+ function requirePositiveNumber(value: any, fieldName: string) {
9
+ const numericValue = Number(value);
10
+ if (!Number.isFinite(numericValue) || numericValue <= 0) {
11
+ throw new Error(`${fieldName} must be a positive number`);
12
+ }
13
+ return numericValue;
14
+ }
15
+
16
+ export { clone, requirePositiveNumber }
9
17
 
package/claw/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dexbot2-claw",
3
- "version": "1.4.20",
3
+ "version": "1.4.21",
4
4
  "private": true,
5
5
  "description": "Claw bridge and runtime integration layer for BitShares and DEXBot2.",
6
6
  "type": "module",