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
@@ -3,11 +3,12 @@ import * as client from './bitshares_client.js';
3
3
  const { BitShares } = client;
4
4
  import { executeOperations } from './chain_broadcast.js';
5
5
  import { loadDexbotOrderUtils, requireDexbot2Module, loadDexbotOrderConstants } from './dexbot_bridge.js';
6
- import { requireBtsBackedMpa, CORE_SYMBOL } from './mpa_utils.js';
7
- import { getAsset, getBackingAsset, getFullAccount, readOpenOrders, resolveAccountId, resolveAccountName } from './chain_queries.js';
6
+ import { requireBtsBackedMpa } from './mpa_utils.js';
7
+ import { getAsset, getFullAccount, readOpenOrders, resolveAccountId, resolveAccountName } from './chain_queries.js';
8
8
  import { getErrorMessage } from '../../modules/utils/errors.js';
9
9
 
10
- // Graphene protocol constant: operation type 4 = limit_order_create (fill events carry op[0] = 4 for matched orders)
10
+ // Graphene protocol constant: operation type 4 = fill_order (limit_order_create
11
+ // is type 1). Account history subscriptions emit op[0] = 4 for matched fills.
11
12
  const FILL_ORDER_OPERATION_TYPE = 4;
12
13
  const accountSubscriptions = new Map();
13
14
 
@@ -134,18 +135,7 @@ async function buildBorrowMpaOperation({
134
135
  throw new Error(`Account not found: ${accountName}`);
135
136
  }
136
137
 
137
- const mpaMeta = await resolveAssetMeta(mpaAsset);
138
- if (!mpaMeta.bitasset_data_id) {
139
- throw new Error(`${mpaAsset} is not a market-issued asset`);
140
- }
141
-
142
- const backingAsset = await getBackingAsset(mpaAsset);
143
- if (!backingAsset) {
144
- throw new Error(`Could not resolve backing asset for ${mpaAsset}`);
145
- }
146
- if (backingAsset.symbol !== CORE_SYMBOL) {
147
- throw new Error(`${mpaAsset} is backed by ${backingAsset.symbol}, not ${CORE_SYMBOL}; use short_mpa_strategy for non-BTS MPAs`);
148
- }
138
+ const { backingAsset, mpaAsset: mpaMeta } = await requireBtsBackedMpa(mpaAsset, 'use short_mpa_strategy for non-BTS MPAs');
149
139
 
150
140
  const floatToBlockchainInt = getFloatToBlockchainInt();
151
141
  const debtInt = floatToBlockchainInt(debtDelta, mpaMeta.precision);
@@ -179,18 +169,7 @@ async function buildSettleMpaOperation({ accountName, mpaAsset, amount }: any) {
179
169
  throw new Error(`Account not found: ${accountName}`);
180
170
  }
181
171
 
182
- const mpaMeta = await resolveAssetMeta(mpaAsset);
183
- if (!mpaMeta.bitasset_data_id) {
184
- throw new Error(`${mpaAsset} is not a market-issued asset`);
185
- }
186
-
187
- const backingAsset = await getBackingAsset(mpaAsset);
188
- if (!backingAsset) {
189
- throw new Error(`Could not resolve backing asset for ${mpaAsset}`);
190
- }
191
- if (backingAsset.symbol !== CORE_SYMBOL) {
192
- throw new Error(`${mpaAsset} is backed by ${backingAsset.symbol}, not ${CORE_SYMBOL}`);
193
- }
172
+ const { mpaAsset: mpaMeta } = await requireBtsBackedMpa(mpaAsset);
194
173
 
195
174
  const floatToBlockchainInt = getFloatToBlockchainInt();
196
175
  const settleAmountInt = floatToBlockchainInt(amount, mpaMeta.precision);
@@ -304,36 +283,19 @@ async function listenForFills(accountNameOrId: any, callback: any) {
304
283
 
305
284
  if (!accountSubscriptions.has(accountName)) {
306
285
  const callbacks: Set<Function> = new Set();
307
- const bsCallback = (updates: any[] = []) => {
308
- const fills = updates.filter((update) => update?.op && update.op[0] === FILL_ORDER_OPERATION_TYPE);
309
- if (fills.length === 0) {
310
- return;
311
- }
312
-
313
- for (const fn of Array.from(callbacks)) {
314
- // The callback may be async. Run it synchronously so sync throws are
315
- // caught here, and attach a catch for async rejections so a rejected
316
- // handler cannot become an unhandled promise rejection (crash on
317
- // Node >= 15).
318
- try {
319
- const result = fn(fills);
320
- if (result && typeof result.then === 'function') {
321
- result.catch((err: any) => {
322
- console.error('listenForFills callback error:', getErrorMessage(err));
323
- });
324
- }
325
- } catch (err: any) {
326
- console.error('listenForFills callback error:', getErrorMessage(err));
327
- }
328
- }
329
- };
330
-
331
- BitShares.subscribe('account', bsCallback, accountName);
286
+ let bsCallback: Function | null = null;
287
+ try {
288
+ bsCallback = await subscribeAccountFills(accountName, callbacks);
289
+ } catch (err: any) {
290
+ throw new Error(`Failed to subscribe to account updates for ${accountName}: ${getErrorMessage(err)}`);
291
+ }
292
+ // Register only after the remote subscription succeeded so callers never
293
+ // believe they are subscribed when setup actually failed.
332
294
  accountSubscriptions.set(accountName, { callbacks, bsCallback });
333
295
  }
334
296
 
335
297
  const entry = accountSubscriptions.get(accountName);
336
- if (!entry) {
298
+ if (!entry || !entry.bsCallback) {
337
299
  throw new Error(`Subscription entry not found for ${accountName}`);
338
300
  }
339
301
  entry.callbacks.add(callback);
@@ -357,5 +319,40 @@ async function listenForFills(accountNameOrId: any, callback: any) {
357
319
  };
358
320
  }
359
321
 
360
- export { adjustMpaCollateral, borrowMpa, buildBorrowMpaOperation, buildCancelLimitOrderOperation, buildCreateLimitOrderOperation, buildUpdateLimitOrderOperation, buildSettleMpaOperation, cancelLimitOrder, createLimitOrder, executeBatch, getMpaPosition, getOpenOrders, listenForFills, repayMpaDebt, updateLimitOrder, settleMpa }
322
+ /**
323
+ * Subscribe to account history fills and fan them out to every registered
324
+ * callback. The underlying subscription call is async and can reject (network
325
+ * error, unknown account); the promise is awaited by listenForFills so a
326
+ * failure surfaces instead of becoming an unhandled rejection.
327
+ */
328
+ async function subscribeAccountFills(accountName: string, callbacks: Set<Function>) {
329
+ const bsCallback = (updates: any[] = []) => {
330
+ const fills = updates.filter((update) => update?.op && update.op[0] === FILL_ORDER_OPERATION_TYPE);
331
+ if (fills.length === 0) {
332
+ return;
333
+ }
334
+
335
+ for (const fn of Array.from(callbacks)) {
336
+ // The callback may be async. Run it synchronously so sync throws are
337
+ // caught here, and attach a catch for async rejections so a rejected
338
+ // handler cannot become an unhandled promise rejection (crash on
339
+ // Node >= 15).
340
+ try {
341
+ const result = fn(fills);
342
+ if (result && typeof result.then === 'function') {
343
+ result.catch((err: any) => {
344
+ console.error('listenForFills callback error:', getErrorMessage(err));
345
+ });
346
+ }
347
+ } catch (err: any) {
348
+ console.error('listenForFills callback error:', getErrorMessage(err));
349
+ }
350
+ }
351
+ };
352
+
353
+ await BitShares.subscribe('account', bsCallback, accountName);
354
+ return bsCallback;
355
+ }
356
+
357
+ export { adjustMpaCollateral, borrowMpa, buildBorrowMpaOperation, buildCreateLimitOrderOperation, buildUpdateLimitOrderOperation, buildSettleMpaOperation, cancelLimitOrder, createLimitOrder, executeBatch, getMpaPosition, getOpenOrders, listenForFills, repayMpaDebt, updateLimitOrder, settleMpa }
361
358
 
@@ -130,7 +130,7 @@ function resolveAccountName(options: Record<string, any> = {}) {
130
130
  return options.accountName || Config.BITSHARES_ACCOUNT || null;
131
131
  }
132
132
 
133
- async function createSigningClientFromCredentialDaemon(options: Record<string, any> = {}) {
133
+ async function createSigningClientFromCredentialDaemon() {
134
134
  throw new Error(
135
135
  'The credential daemon no longer exports raw private keys. ' +
136
136
  'Use broadcastOperationViaCredentialDaemon() or executeOperationsViaCredentialDaemon() ' +
@@ -152,10 +152,7 @@ async function getSigningClient(options: Record<string, any> = {}) {
152
152
  throw new Error('Credential daemon is not ready');
153
153
  }
154
154
 
155
- return createSigningClientFromCredentialDaemon({
156
- ...options,
157
- accountName
158
- });
155
+ return createSigningClientFromCredentialDaemon();
159
156
  }
160
157
 
161
158
  function normalizeOperations(operations: any) {
@@ -163,6 +160,53 @@ function normalizeOperations(operations: any) {
163
160
  return ops.filter(Boolean);
164
161
  }
165
162
 
163
+ /**
164
+ * Shared credential-daemon broadcast plumbing: clamps the request timeout so
165
+ * the daemon's inner deadline can fire first, waits for the daemon, resolves
166
+ * session credentials, and retries once on session/HMAC failures (SIGHUP on
167
+ * source-auth denials). `dispatch` performs the actual daemon call and must
168
+ * be idempotent-safe: it is only re-invoked after credentials are re-resolved.
169
+ */
170
+ async function viaCredentialDaemon(accountName: string, options: Record<string, any>, dispatch: (creds: { sessionId: string | null; botHmacSecret: string | null }) => Promise<any>) {
171
+ const daemonTimeoutMs = Number.isFinite(Number(options.daemonTimeoutMs))
172
+ ? Number(options.daemonTimeoutMs)
173
+ : undefined;
174
+ // Clamp the broadcast request timeout up to the default outer window: the
175
+ // daemon's inner deadline (25s) must be allowed to fire and reply a typed
176
+ // BROADCAST_DEADLINE before this socket timer. A caller-supplied short
177
+ // timeout would time out mid-broadcast — a false uncertain.
178
+ const requestTimeoutMs = Number.isFinite(Number(options.daemonRequestTimeoutMs))
179
+ ? Math.max(Number(options.daemonRequestTimeoutMs), DEFAULT_BROADCAST_TIMEOUT_MS)
180
+ : undefined;
181
+
182
+ await waitForCredentialDaemon(daemonTimeoutMs, options);
183
+
184
+ const creds = await resolveSessionCredentials(accountName, options);
185
+ const socketOptions = {
186
+ socketPath: options.socketPath,
187
+ timeoutMs: requestTimeoutMs
188
+ };
189
+
190
+ try {
191
+ return await dispatch({ ...socketOptions, ...creds });
192
+ } catch (err: any) {
193
+ const msg = String(getErrorMessage(err) || err);
194
+ if (msg.includes(DAEMON_ERRORS.SOURCE_AUTH_DENIED) || msg.includes(DAEMON_ERRORS.SESSION_EXPIRED)) {
195
+ const isSourceAuthError = msg.includes(DAEMON_ERRORS.SOURCE_AUTH_DENIED);
196
+ console.warn(`[CLAW] Session/HMAC error, re-resolving credentials and retrying: ${msg}`);
197
+ if (isSourceAuthError) {
198
+ _sendSighupToDaemon();
199
+ }
200
+ const retry = await resolveSessionCredentials(accountName, options);
201
+ if (isSourceAuthError) {
202
+ await new Promise(r => setTimeout(r, 500));
203
+ }
204
+ return await dispatch({ ...socketOptions, ...retry });
205
+ }
206
+ throw err;
207
+ }
208
+ }
209
+
166
210
  async function executeOperations(operations: any, options: Record<string, any> = {}) {
167
211
  const ops = normalizeOperations(operations);
168
212
  if (ops.length === 0) {
@@ -175,66 +219,20 @@ async function executeOperations(operations: any, options: Record<string, any> =
175
219
  throw new Error('accountName is required');
176
220
  }
177
221
 
178
- const daemonTimeoutMs = Number.isFinite(Number(options.daemonTimeoutMs))
179
- ? Number(options.daemonTimeoutMs)
180
- : undefined;
181
- // Clamp the broadcast request timeout up to the default outer window: the
182
- // daemon's inner deadline (25s) must be allowed to fire and reply a typed
183
- // BROADCAST_DEADLINE before this socket timer. A caller-supplied short
184
- // timeout would time out mid-broadcast — a false uncertain.
185
- const requestTimeoutMs = Number.isFinite(Number(options.daemonRequestTimeoutMs))
186
- ? Math.max(Number(options.daemonRequestTimeoutMs), DEFAULT_BROADCAST_TIMEOUT_MS)
187
- : undefined;
188
-
189
- await waitForCredentialDaemon(daemonTimeoutMs, options);
190
-
191
- let { sessionId, botHmacSecret } = await resolveSessionCredentials(accountName, options);
192
-
193
- try {
194
- const result = await executeOperationsViaCredentialDaemon(accountName, ops, {
195
- socketPath: options.socketPath,
196
- timeoutMs: requestTimeoutMs,
197
- sessionId,
198
- botHmacSecret,
222
+ const result = await viaCredentialDaemon(accountName, options, async (daemonOptions) =>
223
+ executeOperationsViaCredentialDaemon(accountName, ops, {
224
+ ...daemonOptions,
199
225
  requestType: 'broadcast',
200
226
  batchId: options.batchId || null
201
- });
202
-
203
- return {
204
- ...result,
205
- operation_results: result.operation_results || [],
206
- raw: result.raw || null,
207
- success: true
208
- };
209
- } catch (err: any) {
210
- const msg = String(getErrorMessage(err) || err);
211
- if (msg.includes(DAEMON_ERRORS.SOURCE_AUTH_DENIED) || msg.includes(DAEMON_ERRORS.SESSION_EXPIRED)) {
212
- const isSourceAuthError = msg.includes(DAEMON_ERRORS.SOURCE_AUTH_DENIED);
213
- console.warn(`[CLAW] Session/HMAC error, re-resolving credentials and retrying: ${msg}`);
214
- if (isSourceAuthError) {
215
- _sendSighupToDaemon();
216
- }
217
- const retry = await resolveSessionCredentials(accountName, options);
218
- if (isSourceAuthError) {
219
- await new Promise(r => setTimeout(r, 500));
220
- }
221
- const result = await executeOperationsViaCredentialDaemon(accountName, ops, {
222
- socketPath: options.socketPath,
223
- timeoutMs: requestTimeoutMs,
224
- sessionId: retry.sessionId,
225
- botHmacSecret: retry.botHmacSecret,
226
- requestType: 'broadcast',
227
- batchId: options.batchId || null
228
- });
229
- return {
230
- ...result,
231
- operation_results: result.operation_results || [],
232
- raw: result.raw || null,
233
- success: true
234
- };
235
- }
236
- throw err;
237
- }
227
+ })
228
+ );
229
+
230
+ return {
231
+ ...result,
232
+ operation_results: result.operation_results || [],
233
+ raw: result.raw || null,
234
+ success: true
235
+ };
238
236
  }
239
237
 
240
238
  const client = await getSigningClient(options);
@@ -290,27 +288,10 @@ async function broadcastOperation(operation: any, options: Record<string, any> =
290
288
  throw new Error('accountName is required');
291
289
  }
292
290
 
293
- const daemonTimeoutMs = Number.isFinite(Number(options.daemonTimeoutMs))
294
- ? Number(options.daemonTimeoutMs)
295
- : undefined;
296
- // Clamp the broadcast request timeout up to the default outer window: the
297
- // daemon's inner deadline (25s) must be allowed to fire and reply a typed
298
- // BROADCAST_DEADLINE before this socket timer. A caller-supplied short
299
- // timeout would time out mid-broadcast — a false uncertain.
300
- const requestTimeoutMs = Number.isFinite(Number(options.daemonRequestTimeoutMs))
301
- ? Math.max(Number(options.daemonRequestTimeoutMs), DEFAULT_BROADCAST_TIMEOUT_MS)
302
- : undefined;
303
-
304
- await waitForCredentialDaemon(daemonTimeoutMs, options);
305
-
306
- let { sessionId, botHmacSecret } = await resolveSessionCredentials(accountName, options);
307
-
308
- async function doBroadcast(sid: string | null | undefined, secret: string | null) {
291
+ return viaCredentialDaemon(accountName, options, async (daemonOptions) => {
309
292
  const result = await broadcastOperationViaCredentialDaemon(accountName, operation, {
310
- socketPath: options.socketPath,
311
- timeoutMs: requestTimeoutMs,
312
- sessionId: sid,
313
- botHmacSecret: secret
293
+ ...daemonOptions,
294
+ batchId: options.batchId || null
314
295
  });
315
296
  const operationResults =
316
297
  (result && Array.isArray(result.operation_results) && result.operation_results) ||
@@ -318,26 +299,7 @@ async function broadcastOperation(operation: any, options: Record<string, any> =
318
299
  (Array.isArray(result) && result[0] && result[0].trx && Array.isArray(result[0].trx.operation_results) && result[0].trx.operation_results) ||
319
300
  [];
320
301
  return { success: true, raw: result, operation_results: operationResults };
321
- }
322
-
323
- try {
324
- return await doBroadcast(sessionId, botHmacSecret);
325
- } catch (err: any) {
326
- const msg = String(getErrorMessage(err) || err);
327
- if (msg.includes(DAEMON_ERRORS.SOURCE_AUTH_DENIED) || msg.includes(DAEMON_ERRORS.SESSION_EXPIRED)) {
328
- const isSourceAuthError = msg.includes(DAEMON_ERRORS.SOURCE_AUTH_DENIED);
329
- console.warn(`[CLAW] Session/HMAC error, re-resolving credentials and retrying: ${msg}`);
330
- if (isSourceAuthError) {
331
- _sendSighupToDaemon();
332
- }
333
- const retry = await resolveSessionCredentials(accountName, options);
334
- if (isSourceAuthError) {
335
- await new Promise(r => setTimeout(r, 500));
336
- }
337
- return await doBroadcast(retry.sessionId, retry.botHmacSecret);
338
- }
339
- throw err;
340
- }
302
+ });
341
303
  }
342
304
 
343
305
  const client = await getSigningClient(options);
@@ -348,5 +310,5 @@ async function broadcastOperation(operation: any, options: Record<string, any> =
348
310
  }
349
311
  }
350
312
 
351
- export { broadcastOperation, createSigningClient, createSigningClientFromCredentialDaemon, executeOperations, getSigningClient, resolveAccountName }
313
+ export { broadcastOperation, executeOperations, getSigningClient, resolveAccountName }
352
314
 
@@ -11,7 +11,8 @@ import { buildCloseShortPlan, buildOpenShortPlan, buildTakeProfitPlan, closeShor
11
11
  import { launcherRun, launcherDrystart, launcherReset, launcherDisable, launcherPm2Start, launcherPm2Stop, launcherPm2Delete, launcherPm2Restart } from './claw_launcher.js';
12
12
 
13
13
 
14
- import type { ClawBridgeOptions, Logger } from './types.js';
14
+ import type { ClawBridgeOptions } from './types.js';
15
+ import { validateMemuCommandArgs } from './memu_bridge.js';
15
16
 
16
17
  function stripPrivateKey(options: ClawBridgeOptions = {}): ClawBridgeOptions {
17
18
  const sanitized = { ...options };
@@ -21,24 +22,37 @@ function stripPrivateKey(options: ClawBridgeOptions = {}): ClawBridgeOptions {
21
22
 
22
23
  /**
23
24
  * Split a "BASE/QUOTE" pair string into { baseSymbol, quoteSymbol }.
24
- * Throws if the pair does not contain a '/'.
25
+ * Throws if the value is not a two-segment BASE/QUOTE string — extra segments
26
+ * are rejected instead of being silently dropped.
25
27
  */
26
28
  function splitPair(pairValue: string) {
27
29
  if (typeof pairValue !== 'string' || !pairValue.includes('/')) {
28
30
  throw new Error('pair must be provided as BASE/QUOTE');
29
31
  }
30
32
 
31
- const [baseSymbol, quoteSymbol] = pairValue.split('/');
32
- if (!baseSymbol || !quoteSymbol) {
33
+ const segments = pairValue.split('/');
34
+ if (segments.length !== 2 || !segments[0] || !segments[1]) {
33
35
  throw new Error('pair must be provided as BASE/QUOTE');
34
36
  }
35
37
 
36
38
  return {
37
- baseSymbol: baseSymbol.trim(),
38
- quoteSymbol: quoteSymbol.trim()
39
+ baseSymbol: segments[0].trim(),
40
+ quoteSymbol: segments[1].trim()
39
41
  };
40
42
  }
41
43
 
44
+ /**
45
+ * Resolve explicit asset symbols or fall back to the parsed pair option.
46
+ * Returns null when neither is available so callers can raise their own
47
+ * command-specific error.
48
+ */
49
+ function resolvePairSymbols(safeOptions: ClawBridgeOptions) {
50
+ if (!safeOptions.pair) {
51
+ return null;
52
+ }
53
+ return splitPair(safeOptions.pair);
54
+ }
55
+
42
56
  /**
43
57
  * Extract a profile context reference string from command options.
44
58
  * Returns the first matching field: botRef, identifier, botId, or pair.
@@ -47,6 +61,25 @@ function getProfileContextRef(options: ClawBridgeOptions = {}): string | null {
47
61
  return options.botRef || options.identifier || options.botId || options.pair || null;
48
62
  }
49
63
 
64
+ const MEMU_COMMAND_MAP: Record<string, string> = {
65
+ 'memu-memorize': 'memorize',
66
+ 'memu-retrieve': 'retrieve',
67
+ 'memu-create-item': 'create-item',
68
+ 'memu-update-item': 'update-item',
69
+ 'memu-delete-item': 'delete-item',
70
+ 'memu-memorize-conversation': 'memorize-conversation',
71
+ 'memu-memorize-trading-context': 'memorize-trading-context',
72
+ 'memu-retrieve-trading-context': 'retrieve-trading-context'
73
+ };
74
+
75
+ /**
76
+ * Credit runtime commands address bots by botRef or identifier only (never
77
+ * botId/pair, unlike profile-context).
78
+ */
79
+ function getCreditRuntimeBotRef(options: ClawBridgeOptions = {}): string | null {
80
+ return options.botRef || options.identifier || null;
81
+ }
82
+
50
83
  /**
51
84
  * Extract and validate the patch object from command options.
52
85
  * Throws if patch is missing or not a plain object.
@@ -121,12 +154,12 @@ async function runClawCommand(command: string, options: ClawBridgeOptions = {}):
121
154
 
122
155
  case 'profile-context':
123
156
  return bridge.profiles.getClawProfileContext(
124
- safeOptions.botRef || safeOptions.identifier || safeOptions.botId || safeOptions.pair || null,
157
+ getProfileContextRef(safeOptions),
125
158
  safeOptions
126
159
  );
127
160
 
128
161
  case 'market-snapshot': {
129
- const pair = safeOptions.pair ? splitPair(safeOptions.pair) : null;
162
+ const pair = resolvePairSymbols(safeOptions);
130
163
  const baseSymbol = safeOptions.baseSymbol || pair?.baseSymbol;
131
164
  const quoteSymbol = safeOptions.quoteSymbol || pair?.quoteSymbol;
132
165
 
@@ -173,7 +206,7 @@ async function runClawCommand(command: string, options: ClawBridgeOptions = {}):
173
206
  });
174
207
 
175
208
  case 'honest-pair': {
176
- const pair = safeOptions.pair ? splitPair(safeOptions.pair) : null;
209
+ const pair = resolvePairSymbols(safeOptions);
177
210
  const assetA = safeOptions.assetA || pair?.baseSymbol;
178
211
  const assetB = safeOptions.assetB || pair?.quoteSymbol;
179
212
 
@@ -185,7 +218,7 @@ async function runClawCommand(command: string, options: ClawBridgeOptions = {}):
185
218
  }
186
219
 
187
220
  case 'honest-price': {
188
- const pair = safeOptions.pair ? splitPair(safeOptions.pair) : null;
221
+ const pair = resolvePairSymbols(safeOptions);
189
222
  const assetA = safeOptions.assetA || pair?.baseSymbol;
190
223
  const assetB = safeOptions.assetB || pair?.quoteSymbol;
191
224
 
@@ -290,18 +323,14 @@ async function runClawCommand(command: string, options: ClawBridgeOptions = {}):
290
323
  return getMpaPosition(safeOptions.accountName || safeOptions.accountRef || accountName, safeOptions.mpaAsset);
291
324
 
292
325
  case 'credit-runtime-status':
293
- return bridge.creditRuntime.getStatus(
294
- safeOptions.botRef || safeOptions.identifier || null
295
- );
326
+ return bridge.creditRuntime.getStatus(getCreditRuntimeBotRef(safeOptions));
296
327
 
297
328
  case 'credit-runtime-refresh':
298
- return bridge.creditRuntime.refresh(
299
- safeOptions.botRef || safeOptions.identifier || null
300
- );
329
+ return bridge.creditRuntime.refresh(getCreditRuntimeBotRef(safeOptions));
301
330
 
302
331
  case 'credit-runtime-maintenance':
303
332
  return bridge.creditRuntime.runMaintenance(
304
- safeOptions.botRef || safeOptions.identifier || null,
333
+ getCreditRuntimeBotRef(safeOptions),
305
334
  safeOptions.context || 'periodic',
306
335
  safeOptions,
307
336
  options.privateKey
@@ -309,13 +338,13 @@ async function runClawCommand(command: string, options: ClawBridgeOptions = {}):
309
338
 
310
339
  case 'credit-runtime-watchdog':
311
340
  return bridge.creditRuntime.runWatchdog(
312
- safeOptions.botRef || safeOptions.identifier || null,
341
+ getCreditRuntimeBotRef(safeOptions),
313
342
  options.privateKey
314
343
  );
315
344
 
316
345
  case 'credit-runtime-reborrows':
317
346
  return bridge.creditRuntime.processReborrows(
318
- safeOptions.botRef || safeOptions.identifier || null,
347
+ getCreditRuntimeBotRef(safeOptions),
319
348
  options.privateKey
320
349
  );
321
350
 
@@ -346,72 +375,23 @@ async function runClawCommand(command: string, options: ClawBridgeOptions = {}):
346
375
  case 'memu-manifest':
347
376
  return require('./memu_bridge').describeMemuBridge(safeOptions);
348
377
 
349
- case 'memu-memorize': {
350
- if (!safeOptions.resourceUrl || !safeOptions.modality) {
351
- throw new Error('memu-memorize requires resourceUrl and modality');
352
- }
353
- return runMemuCommand('memorize', safeOptions);
354
- }
355
-
356
- case 'memu-retrieve': {
357
- if (!safeOptions.queries) {
358
- throw new Error('memu-retrieve requires queries');
359
- }
360
- return runMemuCommand('retrieve', safeOptions);
361
- }
362
-
378
+ case 'memu-memorize':
379
+ case 'memu-retrieve':
363
380
  case 'memu-list-categories':
364
- return runMemuCommand('list-categories', safeOptions);
365
-
366
381
  case 'memu-list-items':
367
- return runMemuCommand('list-items', safeOptions);
368
-
369
- case 'memu-create-item': {
370
- if (!(safeOptions.categoryId || safeOptions.categoryName || safeOptions.category) || !safeOptions.summary) {
371
- throw new Error('memu-create-item requires categoryId or categoryName, plus summary');
372
- }
373
- return runMemuCommand('create-item', safeOptions);
374
- }
375
-
376
- case 'memu-update-item': {
377
- if (!safeOptions.itemId || !safeOptions.updates) {
378
- throw new Error('memu-update-item requires itemId and updates');
379
- }
380
- return runMemuCommand('update-item', safeOptions);
381
- }
382
-
383
- case 'memu-delete-item': {
384
- if (!safeOptions.itemId) {
385
- throw new Error('memu-delete-item requires itemId');
386
- }
387
- return runMemuCommand('delete-item', safeOptions);
388
- }
389
-
382
+ case 'memu-create-item':
383
+ case 'memu-update-item':
384
+ case 'memu-delete-item':
390
385
  case 'memu-clear':
391
- return runMemuCommand('clear', safeOptions);
392
-
393
386
  case 'memu-status':
394
- return runMemuCommand('status', safeOptions);
395
-
396
- case 'memu-memorize-conversation': {
397
- if (!safeOptions.messages) {
398
- throw new Error('memu-memorize-conversation requires messages');
399
- }
400
- return runMemuCommand('memorize-conversation', safeOptions);
401
- }
402
-
403
- case 'memu-memorize-trading-context': {
404
- if (!safeOptions.context) {
405
- throw new Error('memu-memorize-trading-context requires context');
406
- }
407
- return runMemuCommand('memorize-trading-context', safeOptions);
408
- }
409
-
387
+ case 'memu-memorize-conversation':
388
+ case 'memu-memorize-trading-context':
410
389
  case 'memu-retrieve-trading-context': {
411
- if (!safeOptions.query) {
412
- throw new Error('memu-retrieve-trading-context requires query');
413
- }
414
- return runMemuCommand('retrieve-trading-context', safeOptions);
390
+ const memuCommand = MEMU_COMMAND_MAP[command] || command.replace(/^memu-/, '');
391
+ // Shared validation with runMemuCommand/memu_mcp_server so every surface
392
+ // rejects missing args with the same message.
393
+ validateMemuCommandArgs(memuCommand, safeOptions);
394
+ return runMemuCommand(memuCommand, safeOptions);
415
395
  }
416
396
 
417
397
  default: