@thirdfy/agent-cli 0.4.1 → 0.4.3

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 (68) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +4 -7
  3. package/package.json +5 -3
  4. package/src/cli/help.mjs +1 -1
  5. package/src/cli/options/index.mjs +11 -11
  6. package/src/cli/options/onboarding/credentials.mjs +17 -0
  7. package/src/cli/program.mjs +1 -1
  8. package/src/commands/{agent.mjs → account/agent.mjs} +5 -5
  9. package/src/commands/{credits.mjs → account/credits.mjs} +13 -15
  10. package/src/commands/{wallet.mjs → account/wallet.mjs} +29 -33
  11. package/src/commands/{walletControl.mjs → account/walletControl.mjs} +3 -3
  12. package/src/commands/discovery.mjs +1 -1
  13. package/src/commands/doctor.mjs +5 -5
  14. package/src/commands/execute.mjs +4 -13
  15. package/src/commands/index.mjs +9 -9
  16. package/src/commands/{bootstrap.mjs → onboarding/bootstrap.mjs} +8 -8
  17. package/src/commands/{consoleMandate.mjs → onboarding/consoleMandate.mjs} +2 -2
  18. package/src/commands/{credentials.mjs → onboarding/credentials.mjs} +16 -29
  19. package/src/commands/{delegation.mjs → onboarding/delegation.mjs} +5 -5
  20. package/src/commands/{avantis.mjs → venues/avantis.mjs} +1 -1
  21. package/src/commands/{condor.mjs → venues/condor.mjs} +4 -4
  22. package/src/commands/{hummingbot.mjs → venues/hummingbot.mjs} +4 -4
  23. package/src/commands/{hyperliquid.mjs → venues/hyperliquid.mjs} +1 -1
  24. package/src/commands/{lighter.mjs → venues/lighter.mjs} +1 -1
  25. package/src/commands/{polymarket.mjs → venues/polymarket.mjs} +4 -4
  26. package/src/commands/{venue.mjs → venues/venue.mjs} +3 -3
  27. package/src/core/chainRegistry.mjs +11 -0
  28. package/src/core/constants.mjs +0 -19
  29. package/src/core/earnclawAttribution.mjs +1 -1
  30. package/src/core/{actionTimeouts.mjs → execution/actionTimeouts.mjs} +2 -2
  31. package/src/core/{walletExecuteChainId.mjs → execution/walletExecuteChainId.mjs} +1 -1
  32. package/src/core/http.mjs +1 -1
  33. package/src/runtime/actions/selection.mjs +0 -1
  34. package/src/runtime/execution/hostedRuntimeWriteGate.mjs +1 -1
  35. package/src/runtime/execution/payloads.mjs +4 -4
  36. package/src/runtime/execution/runners/agentWalletLane.mjs +109 -0
  37. package/src/runtime/execution/runners/createExecutionRunners.mjs +312 -0
  38. package/src/runtime/execution/runners/hybridLane.mjs +88 -0
  39. package/src/runtime/execution/runners/managedPreflight.mjs +253 -0
  40. package/src/runtime/execution/runners/managedResponse.mjs +119 -0
  41. package/src/runtime/execution/runners/selfLane.mjs +233 -0
  42. package/src/runtime/execution/runners/thirdfyLane.mjs +97 -0
  43. package/src/runtime/execution/svmLane.mjs +1 -1
  44. package/src/runtime/handlers.mjs +15 -20
  45. package/src/runtime/main.mjs +1 -11
  46. package/src/runtime/owsSigning.mjs +17 -16
  47. package/src/runtime/providerHints/cex.mjs +1 -52
  48. package/src/runtime/risk/notional.mjs +1 -1
  49. package/src/runtime/validation/actionParamsGate.mjs +1 -1
  50. package/src/cli/options/credentials.mjs +0 -15
  51. package/src/runtime/bitfinexProbe.mjs +0 -121
  52. package/src/runtime/execution/runners.mjs +0 -1134
  53. package/src/runtime/onboardingHints.mjs +0 -54
  54. /package/src/cli/options/{agent.mjs → account/agent.mjs} +0 -0
  55. /package/src/cli/options/{credits.mjs → account/credits.mjs} +0 -0
  56. /package/src/cli/options/{wallet.mjs → account/wallet.mjs} +0 -0
  57. /package/src/cli/options/{walletControl.mjs → account/walletControl.mjs} +0 -0
  58. /package/src/cli/options/{bootstrap.mjs → onboarding/bootstrap.mjs} +0 -0
  59. /package/src/cli/options/{delegation.mjs → onboarding/delegation.mjs} +0 -0
  60. /package/src/cli/options/{condor.mjs → venues/condor.mjs} +0 -0
  61. /package/src/cli/options/{hummingbot.mjs → venues/hummingbot.mjs} +0 -0
  62. /package/src/cli/options/{polymarket.mjs → venues/polymarket.mjs} +0 -0
  63. /package/src/cli/options/{venue.mjs → venues/venue.mjs} +0 -0
  64. /package/src/core/{executionContext.mjs → execution/executionContext.mjs} +0 -0
  65. /package/src/core/{fundingTokenIn.mjs → execution/fundingTokenIn.mjs} +0 -0
  66. /package/src/core/{hostedRuntimeIdempotency.mjs → execution/hostedRuntimeIdempotency.mjs} +0 -0
  67. /package/src/core/{readOnlyActions.mjs → execution/readOnlyActions.mjs} +0 -0
  68. /package/src/core/{signedTx.mjs → execution/signedTx.mjs} +0 -0
@@ -0,0 +1,97 @@
1
+ import { apiPost, responseHeadersFor } from '../../../core/http.mjs';
2
+ import { idempotentReplayFields } from '../../../core/execution/hostedRuntimeIdempotency.mjs';
3
+ import { withActionTimeout } from '../../../core/execution/actionTimeouts.mjs';
4
+ import { actionNameForApi } from '../../actions/selection.mjs';
5
+ import { isReadOnlyAction } from './managedResponse.mjs';
6
+ import { createRequire } from 'module';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const { normalizeIntentResponse } = require('../../../utils/cli/intentNormalization.cjs');
10
+
11
+ /** The thirdfy lane (execute intent), also the rail read-only actions use from the other lanes. */
12
+ export function createThirdfyLane(deps) {
13
+ const {
14
+ buildIntentPayload,
15
+ resolveEffectiveUserDid,
16
+ } = deps;
17
+
18
+ async function executeThirdfyRun(ctx, flags, resolved, options, runMode = 'thirdfy') {
19
+ const skipPreflight = Boolean(options?.skipPreflight);
20
+ const actionCtx = withActionTimeout(ctx, resolved);
21
+ if (!skipPreflight) {
22
+ const preflightPayload = buildIntentPayload(flags, {
23
+ validationOnly: true,
24
+ forceIdempotency: false,
25
+ resolvedAction: resolved.resolvedAction,
26
+ apiAction: actionNameForApi(resolved),
27
+ runMode,
28
+ mirrorOnly: false,
29
+ hybridWalletMode: options?.hybridWalletMode,
30
+ });
31
+ // validationOnly never writes, so a timed-out preflight is safe to resend.
32
+ const preflight = await apiPost(
33
+ { ...actionCtx, writeAction: false },
34
+ '/api/v1/agent/execute-intent',
35
+ preflightPayload,
36
+ );
37
+ const preflightNormalized = normalizeIntentResponse(preflight);
38
+ const blockedCount = preflightNormalized.blocked || 0;
39
+ if (!preflightNormalized.success || blockedCount > 0) {
40
+ preflightNormalized.success = false;
41
+ preflightNormalized.error = preflightNormalized.error || 'Preflight blocked execution';
42
+ preflightNormalized.preflightBlocked = true;
43
+ return preflightNormalized;
44
+ }
45
+ }
46
+ const payload = buildIntentPayload(flags, {
47
+ validationOnly: false,
48
+ forceIdempotency: true,
49
+ resolvedAction: resolved.resolvedAction,
50
+ apiAction: actionNameForApi(resolved),
51
+ runMode,
52
+ mirrorOnly: false,
53
+ hybridWalletMode: options?.hybridWalletMode,
54
+ });
55
+ const response = await apiPost(actionCtx, '/api/v1/agent/execute-intent', payload);
56
+ const normalized = normalizeIntentResponse(response);
57
+ normalized.idempotencyKey = payload.idempotencyKey;
58
+ if (!isReadOnlyAction(resolved)) {
59
+ Object.assign(normalized, idempotentReplayFields(response, responseHeadersFor(response), 'idempotencyKey'));
60
+ }
61
+ return normalized;
62
+ }
63
+
64
+ async function runReadOnlyActionViaIntentRail(ctx, flags, resolved, options, preflight, reportedMode = 'agent_wallet') {
65
+ const userDid = resolveEffectiveUserDid(flags, {
66
+ runMode: 'agent_wallet',
67
+ hybridWalletMode: options?.hybridWalletMode,
68
+ preferDelegationIdentity: options?.preferDelegationIdentity,
69
+ });
70
+ const intentResult = await executeThirdfyRun(
71
+ ctx,
72
+ {
73
+ ...flags,
74
+ chainId: Number(flags.chainId || 8453),
75
+ runMode: 'thirdfy',
76
+ userDid: userDid || flags.userDid,
77
+ executionScope: flags.executionScope || 'solo_owner_mirror',
78
+ },
79
+ resolved,
80
+ { ...options, skipPreflight: true },
81
+ 'thirdfy'
82
+ );
83
+ intentResult.mode = reportedMode;
84
+ intentResult.routeFallback = 'execute_intent_read_only';
85
+ if (preflight) {
86
+ intentResult.executionWalletPreflight = preflight;
87
+ intentResult.executionWalletAddress = preflight.executionWalletAddress || intentResult.executionWalletAddress || null;
88
+ }
89
+ intentResult.signerMethod = 'managed_wallet_server';
90
+ return intentResult;
91
+ }
92
+
93
+ return {
94
+ executeThirdfyRun,
95
+ runReadOnlyActionViaIntentRail,
96
+ };
97
+ }
@@ -4,7 +4,7 @@ import { chainRegistrySource, configuredSvmChainIds, getChainInfo, isSvmChainId,
4
4
  import {
5
5
  inferChainIdFromCatalogRow,
6
6
  resolveWalletExecuteChainId,
7
- } from '../../core/walletExecuteChainId.mjs';
7
+ } from '../../core/execution/walletExecuteChainId.mjs';
8
8
 
9
9
  /**
10
10
  * Solana (svm) lane rules. Solana actions run only with run mode `agent_wallet` on the managed
@@ -1,27 +1,27 @@
1
1
  import { negotiateCapabilities } from '../core/http.mjs';
2
- import { createPolymarketCommands } from '../commands/polymarket.mjs';
3
- import { createHummingbotCommands } from '../commands/hummingbot.mjs';
4
- import { createCondorCommands } from '../commands/condor.mjs';
5
- import { createHyperliquidHelpers } from '../commands/hyperliquid.mjs';
6
- import { createLighterHelpers } from '../commands/lighter.mjs';
7
- import { createAvantisHelpers } from '../commands/avantis.mjs';
2
+ import { createPolymarketCommands } from '../commands/venues/polymarket.mjs';
3
+ import { createHummingbotCommands } from '../commands/venues/hummingbot.mjs';
4
+ import { createCondorCommands } from '../commands/venues/condor.mjs';
5
+ import { createHyperliquidHelpers } from '../commands/venues/hyperliquid.mjs';
6
+ import { createLighterHelpers } from '../commands/venues/lighter.mjs';
7
+ import { createAvantisHelpers } from '../commands/venues/avantis.mjs';
8
8
  import { createExecuteCommands } from '../commands/execute.mjs';
9
- import { createWalletCommands } from '../commands/wallet.mjs';
10
- import { createDelegationCommands } from '../commands/delegation.mjs';
11
- import { createConsoleMandateCommands } from '../commands/consoleMandate.mjs';
12
- import { createBootstrapCommands } from '../commands/bootstrap.mjs';
9
+ import { createWalletCommands } from '../commands/account/wallet.mjs';
10
+ import { createDelegationCommands } from '../commands/onboarding/delegation.mjs';
11
+ import { createConsoleMandateCommands } from '../commands/onboarding/consoleMandate.mjs';
12
+ import { createBootstrapCommands } from '../commands/onboarding/bootstrap.mjs';
13
13
  import { createDiscoveryCommands } from '../commands/discovery.mjs';
14
14
  import { createSignalsCommands } from '../commands/signals/command.mjs';
15
- import { createCreditsCommands } from '../commands/credits.mjs';
15
+ import { createCreditsCommands } from '../commands/account/credits.mjs';
16
16
  import { createChatCommands } from '../commands/chat.mjs';
17
17
  import { createBrainCommands } from '../commands/brain.mjs';
18
18
  import { createTelemetryCommands } from '../commands/telemetry.mjs';
19
- import { createCredentialsCommands } from '../commands/credentials.mjs';
20
- import { createAgentCommands } from '../commands/agent.mjs';
21
- import { createWalletControlCommands } from '../commands/walletControl.mjs';
19
+ import { createCredentialsCommands } from '../commands/onboarding/credentials.mjs';
20
+ import { createAgentCommands } from '../commands/account/agent.mjs';
21
+ import { createWalletControlCommands } from '../commands/account/walletControl.mjs';
22
22
  import { createDoctorCommands } from '../commands/doctor.mjs';
23
23
  import { createPromptCommands } from '../commands/prompt.mjs';
24
- import { createVenueCommands } from '../commands/venue.mjs';
24
+ import { createVenueCommands } from '../commands/venues/venue.mjs';
25
25
 
26
26
  /**
27
27
  * Wire command-module factories and local handlers for Commander manifest registration.
@@ -35,7 +35,6 @@ export function createRuntimeHandlers(deps) {
35
35
  maskSecret,
36
36
  getAuthToken,
37
37
  buildOwnerAuthHeaders,
38
- probeBitfinexPermissions,
39
38
  applyActionFilters,
40
39
  getProviderHintForActions,
41
40
  resolveChainSupport,
@@ -51,7 +50,6 @@ export function createRuntimeHandlers(deps) {
51
50
  submitSignedTx,
52
51
  runPreflightByMode,
53
52
  runExecutionByMode,
54
- applyExecutionFallbackHints,
55
53
  performSelfBroadcast,
56
54
  resolveManagedExecutionPreflight,
57
55
  resolveEffectiveUserDid,
@@ -66,7 +64,6 @@ export function createRuntimeHandlers(deps) {
66
64
  applyActionFilters,
67
65
  getProviderHintForActions,
68
66
  resolveChainSupport,
69
- getAuthToken,
70
67
  buildOwnerAuthHeaders,
71
68
  resolveAgentApiKey,
72
69
  });
@@ -98,7 +95,6 @@ export function createRuntimeHandlers(deps) {
98
95
  printEnvelope,
99
96
  getAuthToken,
100
97
  buildOwnerAuthHeaders,
101
- probeBitfinexPermissions,
102
98
  });
103
99
 
104
100
  const __walletControl = createWalletControlCommands({ printEnvelope, buildOwnerAuthHeaders });
@@ -174,7 +170,6 @@ export function createRuntimeHandlers(deps) {
174
170
  runPreflightByMode,
175
171
  runExecutionByMode,
176
172
  resolveManagedExecutionPreflight,
177
- applyExecutionFallbackHints,
178
173
  performSelfBroadcast,
179
174
  executeManagedWalletRun,
180
175
  executeSelfRun,
@@ -16,16 +16,14 @@ import { createInitialRuntimeContext, applyFlagsToRuntimeContext } from './conte
16
16
  import { createProviderHints } from './providerHints/dispatch.mjs';
17
17
  import { createRuntimeHandlers } from './handlers.mjs';
18
18
  import { isCommanderCliError } from './errors.mjs';
19
- import { createOnboardingHints } from './onboardingHints.mjs';
20
19
  import { walletControlErrorFields } from './execution/walletControl.mjs';
21
20
  import { createAgentExtract } from './agentExtract.mjs';
22
21
  import { createAuthHelpers } from './authHelpers.mjs';
23
22
  import { createExecutionAmounts } from './execution/amounts.mjs';
24
23
  import { createExecutionLanes } from './execution/lanes.mjs';
25
24
  import { createExecutionPayloads } from './execution/payloads.mjs';
26
- import { createExecutionRunners } from './execution/runners.mjs';
25
+ import { createExecutionRunners } from './execution/runners/createExecutionRunners.mjs';
27
26
  import { createActionSelection } from './actions/selection.mjs';
28
- import { createBitfinexProbe } from './bitfinexProbe.mjs';
29
27
  import { createOwsSigning } from './owsSigning.mjs';
30
28
  import { enforceCatalogActionParams } from './validation/actionParamsGate.mjs';
31
29
 
@@ -66,7 +64,6 @@ export function createThirdfyAgentRuntime() {
66
64
  providerHints;
67
65
 
68
66
  const { extractAgentApiKey, extractAgentKey, maskSecret } = createAgentExtract();
69
- const onboarding = createOnboardingHints();
70
67
  const amounts = createExecutionAmounts();
71
68
  const lanes = createExecutionLanes();
72
69
  const auth = createAuthHelpers();
@@ -82,7 +79,6 @@ export function createThirdfyAgentRuntime() {
82
79
  const catalogActionParamsGate = (resolved, flags) =>
83
80
  enforceCatalogActionParams(resolved, flags, amounts.getPreparedParams);
84
81
  const ows = createOwsSigning();
85
- const bitfinex = createBitfinexProbe();
86
82
  const runners = createExecutionRunners({
87
83
  buildIntentPayload: payloads.buildIntentPayload,
88
84
  buildBuildTxPayload: payloads.buildBuildTxPayload,
@@ -92,8 +88,6 @@ export function createThirdfyAgentRuntime() {
92
88
  resolveTokenInForFunding: payloads.resolveTokenInForFunding,
93
89
  shouldUseBuildTxPreflight: lanes.shouldUseBuildTxPreflight,
94
90
  shouldUseBuildTxExecution: lanes.shouldUseBuildTxExecution,
95
- buildBitfinexOnboardingCommand: onboarding.buildBitfinexOnboardingCommand,
96
- inferBitfinexPermissionProfileFromAction: onboarding.inferBitfinexPermissionProfileFromAction,
97
91
  resolveAgentApiKey: payloads.resolveAgentApiKey,
98
92
  resolveAgentKey: payloads.resolveAgentKey,
99
93
  loadEthers: ows.loadEthers,
@@ -111,7 +105,6 @@ export function createThirdfyAgentRuntime() {
111
105
  maskSecret,
112
106
  getAuthToken: auth.getAuthToken,
113
107
  buildOwnerAuthHeaders: auth.buildOwnerAuthHeaders,
114
- probeBitfinexPermissions: bitfinex.probeBitfinexPermissions,
115
108
  applyActionFilters: actions.applyActionFilters,
116
109
  getProviderHintForActions,
117
110
  resolveChainSupport: lanes.resolveChainSupport,
@@ -127,7 +120,6 @@ export function createThirdfyAgentRuntime() {
127
120
  submitSignedTx: runners.submitSignedTx,
128
121
  runPreflightByMode: runners.runPreflightByMode,
129
122
  runExecutionByMode: runners.runExecutionByMode,
130
- applyExecutionFallbackHints: runners.applyExecutionFallbackHints,
131
123
  performSelfBroadcast: runners.performSelfBroadcast,
132
124
  resolveManagedExecutionPreflight: runners.resolveManagedExecutionPreflight,
133
125
  resolveEffectiveUserDid: payloads.resolveEffectiveUserDid,
@@ -195,7 +187,6 @@ export function createThirdfyAgentRuntime() {
195
187
  const blockedReason = String(payload.blockedReason || payload.error || '').trim() || undefined;
196
188
  const blockedStage = String(payload.blockedStage || '').trim() || undefined;
197
189
  const clientCode = String(payload.error || payload.blockedReason || '').trim() || undefined;
198
- const onboardingHint = onboarding.buildTopLevelOnboardingHint(payload);
199
190
  printEnvelope({
200
191
  success: false,
201
192
  // A missing or malformed flag is INVALID_ARGUMENTS at the top too, not an unhandled error.
@@ -209,7 +200,6 @@ export function createThirdfyAgentRuntime() {
209
200
  field: payload.field || null,
210
201
  resolvedAction: payload.resolvedAction || null,
211
202
  requestedAction: payload.requestedAction || null,
212
- onboardingHint,
213
203
  // errorCode, retryable, retryAfterSeconds, nextAction, hint: as thirdfy-mcp's error envelope.
214
204
  ...describeCliError(error),
215
205
  // API 4.0 wallet-control refusals: walletControl, nextAction, userMessage, retryable, hint.
@@ -1,19 +1,20 @@
1
1
  import { spawnSync } from 'child_process';
2
2
  import fs from 'fs';
3
+ import { createRequire } from 'module';
3
4
  import os from 'os';
4
5
  import path from 'path';
6
+ import { pinnedRpcProvider } from '../core/chainRegistry.mjs';
5
7
  import { createCliError } from '../core/envelope.mjs';
8
+
9
+ const require = createRequire(import.meta.url);
6
10
  export function createOwsSigning() {
11
+ // ethers is a dependency of this package; resolve it from here, never from the caller's directory.
7
12
  async function loadEthers() {
8
- const candidates = ['ethers', path.join(process.cwd(), 'node_modules', 'ethers')];
9
- for (const candidate of candidates) {
10
- try {
11
- return require(candidate);
12
- } catch {
13
- // continue
14
- }
13
+ try {
14
+ return require('ethers');
15
+ } catch {
16
+ throw new Error('Could not load the ethers dependency of @thirdfy/agent-cli; reinstall the package');
15
17
  }
16
- throw new Error('Could not load ethers dependency for self-exec');
17
18
  }
18
19
 
19
20
  function resolveOwsBinary() {
@@ -62,8 +63,8 @@ function signUnsignedTxWithOws(unsignedHex, walletName, chainId) {
62
63
 
63
64
  async function signAndBroadcastWithOws({ txd, chainId, walletAddress, owsWalletName, rpcUrl }) {
64
65
  const ethers = await loadEthers();
65
- const { JsonRpcProvider, Transaction, Signature } = ethers;
66
- const provider = new JsonRpcProvider(rpcUrl, chainId);
66
+ const { Transaction, Signature } = ethers;
67
+ const provider = pinnedRpcProvider(ethers, { rpcUrl, chainId });
67
68
  const nonce = await provider.getTransactionCount(walletAddress, 'pending');
68
69
  const fee = await provider.getFeeData();
69
70
  const txFields = {
@@ -86,10 +87,10 @@ async function signAndBroadcastWithOws({ txd, chainId, walletAddress, owsWalletN
86
87
  return { sent };
87
88
  }
88
89
 
89
- async function readTokenBalanceRaw(rpcUrl, walletAddress, tokenAddress) {
90
+ async function readTokenBalanceRaw(rpcUrl, walletAddress, tokenAddress, chainId) {
90
91
  const ethers = await loadEthers();
91
- const { JsonRpcProvider, Interface } = ethers;
92
- const provider = new JsonRpcProvider(rpcUrl);
92
+ const { Interface } = ethers;
93
+ const provider = pinnedRpcProvider(ethers, { rpcUrl, chainId });
93
94
  const erc20 = new Interface(['function balanceOf(address owner) view returns (uint256)']);
94
95
  const response = await provider.call({
95
96
  to: tokenAddress,
@@ -99,10 +100,10 @@ async function readTokenBalanceRaw(rpcUrl, walletAddress, tokenAddress) {
99
100
  return BigInt(decoded[0].toString()).toString();
100
101
  }
101
102
 
102
- async function readEffectBalances(rpcUrl, walletAddress, tokenIn, tokenOut) {
103
+ async function readEffectBalances(rpcUrl, walletAddress, tokenIn, tokenOut, chainId) {
103
104
  const [inBal, outBal] = await Promise.all([
104
- readTokenBalanceRaw(rpcUrl, walletAddress, tokenIn),
105
- readTokenBalanceRaw(rpcUrl, walletAddress, tokenOut),
105
+ readTokenBalanceRaw(rpcUrl, walletAddress, tokenIn, chainId),
106
+ readTokenBalanceRaw(rpcUrl, walletAddress, tokenOut, chainId),
106
107
  ]);
107
108
  return { tokenIn: inBal, tokenOut: outBal };
108
109
  }
@@ -1,59 +1,8 @@
1
1
  /**
2
- * Static provider hints. Centralized exchanges and their executors: Bitfinex, Hummingbot and Condor.
2
+ * Static provider hints. Centralized exchange executors: Hummingbot and Condor.
3
3
  * Each entry builds the hint for one provider id; `dispatch.mjs` picks the entry.
4
4
  */
5
5
  export const CEX_PROVIDER_HINTS = Object.freeze({
6
- bitfinex: ({ provider }) => ({
7
- provider,
8
- category: 'cex_funding_and_spot',
9
- canonicalReadAction: 'get_bitfinex_funding_ticker',
10
- canonicalWriteAction: 'submit_bitfinex_funding_offer',
11
- readActions: [
12
- 'get_bitfinex_funding_stats_hist',
13
- 'get_bitfinex_funding_ticker',
14
- 'get_bitfinex_funding_book',
15
- 'get_bitfinex_wallets',
16
- 'list_bitfinex_funding_offers',
17
- 'list_bitfinex_funding_offers_hist',
18
- 'list_bitfinex_funding_credits',
19
- 'list_bitfinex_funding_loans',
20
- 'get_bitfinex_funding_info',
21
- 'get_bitfinex_ledgers_hist',
22
- 'get_bitfinex_orders',
23
- ],
24
- orderManagementActions: [
25
- 'submit_bitfinex_funding_offer',
26
- 'cancel_bitfinex_funding_offer',
27
- 'transfer_bitfinex_between_wallets',
28
- 'get_bitfinex_orders',
29
- 'submit_bitfinex_order',
30
- 'update_bitfinex_order',
31
- 'cancel_bitfinex_order',
32
- ],
33
- credentialType: 'api_key',
34
- credentialRef: 'bitfinex:funding-primary',
35
- credentialUx: 'Bitfinex credentials are owned by Thirdfy API encrypted delegation storage. CLI commands pass credentialRef only; never apiKey/apiSecret.',
36
- laneCompatibility: 'Use `thirdfy-agent actions --provider bitfinex` for discovery, then `thirdfy-agent run --run-mode thirdfy` with credentialRef in params. The CLI does not store, log, or sign raw exchange credentials.',
37
- example: 'thirdfy-agent actions --provider bitfinex && thirdfy-agent run --run-mode thirdfy --action submit_bitfinex_funding_offer --params \'{"symbol":"fUSD","amount":"100","rate":"0.0002","period":2,"credentialRef":"bitfinex:funding-primary"}\'',
38
- credentialProfiles: [
39
- {
40
- id: 'bitfinex_funding_v1',
41
- credentialRef: 'bitfinex:funding-primary',
42
- required: ['funding.read', 'funding.write', 'wallets.read'],
43
- },
44
- {
45
- id: 'bitfinex_spot_v1',
46
- credentialRef: 'bitfinex:spot-primary',
47
- required: ['orders.read', 'orders.write', 'wallets.read'],
48
- },
49
- ],
50
- setupBlockers: [
51
- 'CEX_CREDENTIAL_NOT_FOUND',
52
- 'BITFINEX_CREDENTIALS_NOT_IN_THIRDFY_DELEGATION_CONTEXT',
53
- 'BITFINEX_PERMISSION_SCOPE_MISSING',
54
- 'CEX_POLICY_ACTION_NOT_ALLOWED',
55
- ],
56
- }),
57
6
  hummingbot: ({ provider }) => ({
58
7
  provider,
59
8
  category: 'cex_executor',
@@ -1,5 +1,5 @@
1
1
  import { createCliError } from '../../core/envelope.mjs';
2
- import { isReadOnlyResolvedAction } from '../../core/readOnlyActions.mjs';
2
+ import { isReadOnlyResolvedAction } from '../../core/execution/readOnlyActions.mjs';
3
3
  import { resolveExecuteEstimatedAmountUsd } from '../execution/payloads.mjs';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { createCliError } from '../../core/envelope.mjs';
2
- import { isReadOnlyResolvedAction } from '../../core/readOnlyActions.mjs';
2
+ import { isReadOnlyResolvedAction } from '../../core/execution/readOnlyActions.mjs';
3
3
  import {
4
4
  sanitizeParamsForSchemaValidation,
5
5
  validateParamsSchema,
@@ -1,15 +0,0 @@
1
- export function attachCredentialsOptions(cmd) {
2
- return cmd
3
- .option('--venue <id>', 'credential venue')
4
- .option('--api-key <key>', 'exchange API key')
5
- .option('--api-secret <secret>', 'exchange API secret')
6
- .option('--permission-profile <profile>', 'funding|spot')
7
- .option('--credential-ref <ref>', 'credential reference')
8
- .option('--credential-type <type>', 'credential type')
9
- .option('--verify-permissions', 'verify exchange permissions')
10
- .option('--skip-permissions-probe', 'skip permissions probe')
11
- .option('--bitfinex-base-url <url>', 'Bitfinex API base URL')
12
- .option('--passphrase <value>', 'exchange passphrase')
13
- .option('--metadata <json>', 'credential metadata JSON')
14
- .option('--policy <json>', 'credential policy JSON');
15
- }
@@ -1,121 +0,0 @@
1
- import { createHmac } from 'crypto';
2
- import { DEFAULT_BITFINEX_BASE_URL, BITFINEX_PERMISSION_PROFILES } from '../core/constants.mjs';
3
- import { createCliError } from '../core/envelope.mjs';
4
- import { requestJson, safeJsonParse } from '../core/http.mjs';
5
- export function createBitfinexProbe() {
6
- function parseBitfinexPermissionRows(rawData) {
7
- const rows = Array.isArray(rawData) ? rawData : [];
8
- const parsed = {};
9
- for (const row of rows) {
10
- if (!Array.isArray(row) || row.length < 3) continue;
11
- const scope = String(row[0] || '').trim().toLowerCase();
12
- if (!scope) continue;
13
- const read = Number(row[1]) === 1 ? 1 : 0;
14
- const write = Number(row[2]) === 1 ? 1 : 0;
15
- parsed[scope] = { read, write };
16
- }
17
- return parsed;
18
- }
19
-
20
- function getMissingBitfinexScopes(scopePermissions, permissionProfile = 'funding') {
21
- const profile = BITFINEX_PERMISSION_PROFILES[permissionProfile] || BITFINEX_PERMISSION_PROFILES.funding;
22
- const missing = [];
23
- for (const [scope, required] of Object.entries(profile.required)) {
24
- const actual = scopePermissions[scope] || { read: 0, write: 0 };
25
- if (actual.read < required.read || actual.write < required.write) {
26
- missing.push({
27
- scope,
28
- required,
29
- actual,
30
- });
31
- }
32
- }
33
- return missing;
34
- }
35
-
36
- async function probeBitfinexPermissions({ apiKey, apiSecret, permissionProfile = 'funding', timeoutMs, baseUrl }) {
37
- const normalizedBaseUrl = String(baseUrl || DEFAULT_BITFINEX_BASE_URL).trim().replace(/\/+$/, '');
38
- const apiPath = '/v2/auth/r/permissions';
39
- const nonce = Date.now().toString();
40
- const payload = '{}';
41
- const signature = createHmac('sha384', String(apiSecret || ''))
42
- .update(`/api${apiPath}${nonce}${payload}`)
43
- .digest('hex');
44
- const controller = new AbortController();
45
- const timer = setTimeout(() => controller.abort(), Number(timeoutMs || DEFAULT_TIMEOUT_MS));
46
- try {
47
- const response = await fetch(`${normalizedBaseUrl}${apiPath}`, {
48
- method: 'POST',
49
- headers: {
50
- 'content-type': 'application/json',
51
- 'bfx-apikey': String(apiKey || ''),
52
- 'bfx-nonce': nonce,
53
- 'bfx-signature': signature,
54
- },
55
- body: payload,
56
- signal: controller.signal,
57
- });
58
- const text = await response.text();
59
- const parsed = text ? safeJsonParse(text) : {};
60
- const bitfinexApiError =
61
- Array.isArray(parsed) && String(parsed[0] || '').trim().toLowerCase() === 'error'
62
- ? {
63
- code: Number(parsed[1]) || null,
64
- message: String(parsed[2] || 'Unknown Bitfinex API error'),
65
- }
66
- : parsed && typeof parsed === 'object' && String(parsed.event || '').trim().toLowerCase() === 'error'
67
- ? {
68
- code: Number(parsed.code) || null,
69
- message: String(parsed.msg || parsed.message || 'Unknown Bitfinex API error'),
70
- }
71
- : null;
72
- if (!response.ok) {
73
- throw createCliError(
74
- 'BITFINEX_PERMISSIONS_PROBE_FAILED',
75
- `Bitfinex permissions probe failed with HTTP ${response.status}. Verify API key/secret and key validity before onboarding.`,
76
- { statusCode: response.status }
77
- );
78
- }
79
- if (bitfinexApiError) {
80
- throw createCliError(
81
- 'BITFINEX_PERMISSIONS_PROBE_FAILED',
82
- `Bitfinex permissions probe failed: ${bitfinexApiError.message}. Verify API key/secret and key validity before onboarding.`,
83
- { statusCode: response.status, bitfinexApiError }
84
- );
85
- }
86
- const scopePermissions = parseBitfinexPermissionRows(parsed);
87
- const missingScopes = getMissingBitfinexScopes(scopePermissions, permissionProfile);
88
- if (missingScopes.length > 0) {
89
- throw createCliError(
90
- 'BITFINEX_PERMISSION_SCOPE_MISSING',
91
- `Bitfinex key is missing required scopes (${missingScopes
92
- .map((entry) => entry.scope)
93
- .join(', ')}). Enable the ${permissionProfile} profile scopes and retry onboarding.`,
94
- { missingScopes, scopePermissions, permissionProfile }
95
- );
96
- }
97
- return {
98
- venue: 'bitfinex',
99
- permissionProfile,
100
- checked: true,
101
- scopes: scopePermissions,
102
- missingScopes: [],
103
- };
104
- } catch (error) {
105
- if (error?.payload) throw error;
106
- throw createCliError(
107
- 'BITFINEX_PERMISSIONS_PROBE_FAILED',
108
- `Could not verify Bitfinex key permissions. ${String(error?.message || 'Unknown probe error')}`,
109
- {}
110
- );
111
- } finally {
112
- clearTimeout(timer);
113
- }
114
- }
115
-
116
- return {
117
- parseBitfinexPermissionRows,
118
- getMissingBitfinexScopes,
119
- probeBitfinexPermissions,
120
- };
121
- }