@thirdfy/agent-cli 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -271,6 +271,7 @@ Provider and venue guides are kept outside `README` so this page stays stable as
271
271
 
272
272
  - Discovery: `catalogs list`, `actions`
273
273
  - Execution: `preflight`, `run`, `intent-status`, `jeff preflight`, `jeff trade`, `jeff status`
274
+ - Managed signer execution: `wallet execute`, `wallet sign`, `wallet submit`, `agent run`
274
275
  - Profiles: `profile init`, `profile use`, `profile show`, `whoami`
275
276
  - Onboarding: `bootstrap begin`, `bootstrap complete`, `onboarding begin`, `onboarding complete`, `agent auth challenge`, `agent auth verify`, `agent register`, `agent key rotate`, `agent key revoke`, `developer bootstrap`
276
277
  - Governance readiness: `delegation create`, `delegation activate`, `delegation status`, `credentials upsert`, `credentials status`
@@ -369,9 +370,24 @@ thirdfy-agent managed wallet init --auth-token "$THIRDFY_AUTH_TOKEN" --json
369
370
  # or: --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN"
370
371
  thirdfy-agent managed wallet grant --auth-token "$THIRDFY_AUTH_TOKEN" --agent-key "0xYOUR_AGENT_KEY" --token-address "0x..." --max-usd-per-day 250 --json
371
372
  # or: --owner-session-token "$THIRDFY_OWNER_SESSION_TOKEN"
372
- thirdfy-agent managed run --agent-api-key "$AGENT_API_KEY" --run-mode thirdfy --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' --json
373
+ thirdfy-agent agent run --agent-api-key "$AGENT_API_KEY" --signer-method fanout_intent --action swap --params '{"tokenIn":"0x...","tokenOut":"0x...","amountInRaw":"1000000"}' --json
373
374
  ```
374
375
 
376
+ ### Signer command model (managed + BYOW)
377
+
378
+ The CLI now supports the same signer primitives used by the MCP:
379
+
380
+ - `wallet execute` -> direct managed self-wallet execution (`/api/v1/agent/execute`)
381
+ - `wallet sign` -> unsigned tx packaging for BYOW signing flow
382
+ - `wallet submit` -> signed tx broadcast
383
+ - `agent run` -> signer router (`managed_wallet_server`, `fanout_intent`, `byow`)
384
+
385
+ Managed-self execution performs strict wallet preflight and returns deterministic blockers:
386
+
387
+ - `blockedReason`
388
+ - `blockedStage`
389
+ - `executionWalletAddress`
390
+
375
391
  ## More docs
376
392
 
377
393
  - OpenClaw CLI onboarding flow:
@@ -155,8 +155,8 @@ async function main() {
155
155
  const capabilities = await negotiateCapabilities(context);
156
156
 
157
157
  switch (commandKey) {
158
- case 'managed run':
159
- await commandManagedRun(context, subFlags, capabilities);
158
+ case 'agent run':
159
+ await commandAgentRun(context, subFlags, capabilities);
160
160
  return;
161
161
  case 'managed setup':
162
162
  await commandManagedSetup(context, subFlags, capabilities);
@@ -253,6 +253,18 @@ async function main() {
253
253
  await commandDelegationCustodialGrant(context, subFlags, capabilities);
254
254
  return;
255
255
  }
256
+ if (commandKey3 === 'wallet execute') {
257
+ await commandWalletExecute(context, subFlags, capabilities);
258
+ return;
259
+ }
260
+ if (commandKey3 === 'wallet sign') {
261
+ await commandWalletSign(context, subFlags, capabilities);
262
+ return;
263
+ }
264
+ if (commandKey3 === 'wallet submit') {
265
+ await commandWalletSubmit(context, subFlags, capabilities);
266
+ return;
267
+ }
256
268
 
257
269
  const rootCommand = parsed.positionals[0];
258
270
  switch (rootCommand) {
@@ -640,6 +652,129 @@ async function commandRun(ctx, flags, capabilities) {
640
652
  if (!normalized.success) process.exit(1);
641
653
  }
642
654
 
655
+ async function commandAgentRun(ctx, flags, capabilities) {
656
+ const signerMethod = String(flags.signerMethod || '').trim().toLowerCase();
657
+ const strategyIntent = String(flags.strategyIntent || '').trim().toLowerCase();
658
+ const requestedRunMode = String(flags.runMode || '').trim().toLowerCase();
659
+ if (signerMethod && !['managed_wallet_server', 'byow', 'fanout_intent'].includes(signerMethod)) {
660
+ throw createCliError(
661
+ 'SIGNER_METHOD_INVALID',
662
+ `Unsupported --signer-method "${signerMethod}". Use managed_wallet_server, fanout_intent, or byow.`
663
+ );
664
+ }
665
+ if (signerMethod === 'byow' || requestedRunMode === 'self') {
666
+ await commandWalletSign(ctx, { ...flags, runMode: 'self' }, capabilities);
667
+ return;
668
+ }
669
+ if (signerMethod === 'fanout_intent') {
670
+ await commandRun(ctx, { ...flags, runMode: 'thirdfy' }, capabilities);
671
+ return;
672
+ }
673
+ if (signerMethod === 'managed_wallet_server') {
674
+ await commandWalletExecute(ctx, { ...flags, runMode: 'agent_wallet' }, capabilities);
675
+ return;
676
+ }
677
+ if (requestedRunMode) {
678
+ const runMode = normalizeRunMode(requestedRunMode);
679
+ if (runMode === 'self') {
680
+ await commandWalletSign(ctx, { ...flags, runMode }, capabilities);
681
+ return;
682
+ }
683
+ if (runMode === 'thirdfy' || runMode === 'hybrid') {
684
+ await commandRun(ctx, { ...flags, runMode }, capabilities);
685
+ return;
686
+ }
687
+ await commandWalletExecute(ctx, { ...flags, runMode: 'agent_wallet' }, capabilities);
688
+ return;
689
+ }
690
+ if (strategyIntent === 'fanout') {
691
+ await commandRun(ctx, { ...flags, runMode: 'thirdfy' }, capabilities);
692
+ return;
693
+ }
694
+ await commandWalletExecute(ctx, { ...flags, runMode: 'agent_wallet' }, capabilities);
695
+ }
696
+
697
+ async function commandWalletExecute(ctx, flags, capabilities) {
698
+ const normalizedFlags = { ...flags, runMode: 'agent_wallet' };
699
+ const resolved = await resolveActionSelection(ctx, normalizedFlags);
700
+ prepareActionParamsForFlags(normalizedFlags, resolved.resolvedAction);
701
+ enforceChainCompatibility(capabilities, normalizedFlags, 'agent_wallet');
702
+ const skipPreflight = Boolean(normalizedFlags.skipPreflight);
703
+ const result = await executeManagedWalletRun(ctx, normalizedFlags, resolved, { skipPreflight });
704
+ printEnvelope({
705
+ success: result.success,
706
+ code: result.success ? 'WALLET_EXECUTED' : result.preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'RUN_FAILED',
707
+ message: result.success ? 'Managed wallet execution completed' : result.error || 'Managed wallet execution failed',
708
+ data: result,
709
+ meta: {
710
+ apiBase: ctx.apiBase,
711
+ requestedAction: resolved.requestedAction,
712
+ resolvedAction: resolved.resolvedAction,
713
+ runMode: 'agent_wallet',
714
+ idempotencyKey: result.idempotencyKey || null,
715
+ executionWalletAddress: result.executionWalletAddress || null,
716
+ profile: ctx.profile || null,
717
+ capabilitiesVersion: capabilities?.contractVersion || null,
718
+ },
719
+ });
720
+ if (!result.success) process.exit(1);
721
+ }
722
+
723
+ async function commandWalletSign(ctx, flags, capabilities) {
724
+ const normalizedFlags = { ...flags, runMode: 'self' };
725
+ const resolved = await resolveActionSelection(ctx, normalizedFlags);
726
+ prepareActionParamsForFlags(normalizedFlags, resolved.resolvedAction);
727
+ enforceChainCompatibility(capabilities, normalizedFlags, 'self');
728
+ const selfResult = await executeSelfRun(ctx, normalizedFlags, resolved, {
729
+ skipPreflight: Boolean(normalizedFlags.skipPreflight),
730
+ runMode: 'self',
731
+ });
732
+ printEnvelope({
733
+ success: selfResult.success,
734
+ code: selfResult.success ? 'WALLET_SIGN_READY' : selfResult.preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'WALLET_SIGN_FAILED',
735
+ message: selfResult.success ? 'Unsigned transaction prepared for BYOW signing' : selfResult.error || 'wallet sign failed',
736
+ data: selfResult,
737
+ meta: {
738
+ apiBase: ctx.apiBase,
739
+ requestedAction: resolved.requestedAction,
740
+ resolvedAction: resolved.resolvedAction,
741
+ runMode: 'self',
742
+ profile: ctx.profile || null,
743
+ capabilitiesVersion: capabilities?.contractVersion || null,
744
+ },
745
+ });
746
+ if (!selfResult.success) process.exit(1);
747
+ }
748
+
749
+ async function commandWalletSubmit(ctx, flags, capabilities) {
750
+ const signedTxHex = String(flags.signedTxHex || flags.signedTx || '').trim();
751
+ if (!signedTxHex) {
752
+ throw createCliError('MISSING_SIGNED_TX', 'wallet submit requires --signed-tx-hex');
753
+ }
754
+ const chainId = Number(flags.chainId || 8453);
755
+ const rpcUrl = String(
756
+ flags.rpcUrl || process.env.BASE_RPC_URL || process.env.BASE_RPC_HTTP_URL || process.env.RPC_URL || 'https://mainnet.base.org'
757
+ ).trim();
758
+ const txHash = await submitSignedTx({ rpcUrl, chainId, signedTxHex });
759
+ printEnvelope({
760
+ success: true,
761
+ code: 'WALLET_SUBMITTED',
762
+ message: 'Signed transaction broadcasted',
763
+ data: {
764
+ txHash,
765
+ chainId,
766
+ signerMethod: 'byow',
767
+ executionMode: 'byow_submit',
768
+ },
769
+ meta: {
770
+ apiBase: ctx.apiBase,
771
+ rpcUrl,
772
+ profile: ctx.profile || null,
773
+ capabilitiesVersion: capabilities?.contractVersion || null,
774
+ },
775
+ });
776
+ }
777
+
643
778
  async function commandIntentStatus(ctx, flags, capabilities) {
644
779
  const intentId = requireFlag(flags, 'intentId', 'Missing --intent-id');
645
780
  const response = await apiGet(ctx, `/api/v1/agent/execute-intent/status?intentId=${encodeURIComponent(intentId)}`);
@@ -1565,41 +1700,6 @@ async function commandDoctorSelf(ctx, flags) {
1565
1700
  if (!ok) process.exit(1);
1566
1701
  }
1567
1702
 
1568
- async function commandManagedRun(ctx, flags, capabilities) {
1569
- const mode = normalizeManagedRunMode(flags.runMode || 'thirdfy');
1570
- const managedFlags = { ...flags, runMode: mode };
1571
- const resolved = await resolveActionSelection(ctx, managedFlags);
1572
- prepareActionParamsForFlags(managedFlags, resolved.resolvedAction);
1573
- enforceChainCompatibility(capabilities, managedFlags, mode);
1574
- const skipPreflight = Boolean(managedFlags.skipPreflight);
1575
- const backendResult = await runExecutionByMode(mode, ctx, managedFlags, resolved, {
1576
- skipPreflight,
1577
- });
1578
- const normalized = backendResult.normalized;
1579
- printEnvelope({
1580
- success: normalized.success,
1581
- code: normalized.success ? 'MANAGED_RUN_QUEUED' : backendResult.code,
1582
- message: normalized.success
1583
- ? mode === 'hybrid'
1584
- ? 'Managed easy-wallet hybrid execution prepared'
1585
- : 'Managed easy-wallet execution queued'
1586
- : backendResult.message,
1587
- data: normalized,
1588
- meta: {
1589
- apiBase: ctx.apiBase,
1590
- requestedAction: resolved.requestedAction,
1591
- resolvedAction: resolved.resolvedAction,
1592
- lane: 'managed_easy_wallet',
1593
- runMode: mode,
1594
- skippedPreflight: skipPreflight,
1595
- profile: ctx.profile || null,
1596
- swapInput: managedFlags.__swapInput || null,
1597
- capabilitiesVersion: capabilities?.contractVersion || null,
1598
- },
1599
- });
1600
- if (!normalized.success) process.exit(1);
1601
- }
1602
-
1603
1703
  async function commandManagedSetup(ctx, flags, capabilities) {
1604
1704
  const mode = normalizeManagedRunMode(flags.runMode || 'agent_wallet');
1605
1705
  const authHeaders = buildOwnerAuthHeaders(
@@ -2189,6 +2289,34 @@ function shouldUseBuildTxPreflight(runMode, resolved) {
2189
2289
 
2190
2290
  async function runPreflightByMode(runMode, ctx, flags, resolved, options = {}) {
2191
2291
  const resolvedAction = resolved.resolvedAction;
2292
+ if (runMode === 'agent_wallet') {
2293
+ const preflight = await resolveManagedExecutionPreflight(ctx, flags, resolved, {
2294
+ runMode,
2295
+ requireFundingCheck: true,
2296
+ });
2297
+ const normalized = normalizeIntentResponse({
2298
+ success: preflight.success,
2299
+ status: preflight.success ? 'ready' : 'failed',
2300
+ mode: 'agent_wallet',
2301
+ executionWalletAddress: preflight.executionWalletAddress || null,
2302
+ signerMethod: preflight.signerMethod || 'managed_wallet_server',
2303
+ blockedReason: preflight.blockedReason || null,
2304
+ blockedStage: preflight.blockedStage || null,
2305
+ error: preflight.success ? null : preflight.error || 'Managed wallet preflight failed',
2306
+ preflightBlocked: !preflight.success,
2307
+ fundingTokenBalance: preflight.fundingTokenBalance || null,
2308
+ executionWalletPreflight: preflight,
2309
+ });
2310
+ normalized.executionWalletAddress = preflight.executionWalletAddress || null;
2311
+ normalized.signerMethod = preflight.signerMethod || 'managed_wallet_server';
2312
+ normalized.executionWalletPreflight = preflight;
2313
+ normalized.fundingTokenBalance = preflight.fundingTokenBalance || null;
2314
+ return {
2315
+ message: preflight.success ? 'Managed wallet preflight completed' : 'Managed wallet preflight failed',
2316
+ route: 'execution_wallet_preflight',
2317
+ normalized,
2318
+ };
2319
+ }
2192
2320
  if (runMode === 'self') {
2193
2321
  const route = shouldUseBuildTxPreflight(runMode, resolved) ? 'build_tx' : 'execute_intent_validation';
2194
2322
  const normalized = await executeSelfRun(
@@ -2269,7 +2397,7 @@ async function runPreflightByMode(runMode, ctx, flags, resolved, options = {}) {
2269
2397
  }
2270
2398
 
2271
2399
  async function runExecutionByMode(runMode, ctx, flags, resolved, options) {
2272
- if (runMode === 'thirdfy' || runMode === 'agent_wallet') {
2400
+ if (runMode === 'thirdfy') {
2273
2401
  const normalized = await executeThirdfyRun(ctx, flags, resolved, options, runMode);
2274
2402
  const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
2275
2403
  return {
@@ -2283,6 +2411,20 @@ async function runExecutionByMode(runMode, ctx, flags, resolved, options) {
2283
2411
  normalized,
2284
2412
  };
2285
2413
  }
2414
+ if (runMode === 'agent_wallet') {
2415
+ const normalized = await executeManagedWalletRun(ctx, flags, resolved, options);
2416
+ const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
2417
+ return {
2418
+ code: normalized.success ? 'WALLET_EXECUTED' : preflightBlocked ? 'PREFLIGHT_BLOCKED' : 'RUN_FAILED',
2419
+ message: normalized.success
2420
+ ? 'Managed wallet execution completed'
2421
+ : preflightBlocked
2422
+ ? 'Managed wallet preflight blocked execution'
2423
+ : 'Managed wallet execution failed',
2424
+ idempotencyKey: normalized.idempotencyKey || null,
2425
+ normalized,
2426
+ };
2427
+ }
2286
2428
  if (runMode === 'self') {
2287
2429
  const normalized = await executeSelfRun(ctx, flags, resolved, options);
2288
2430
  const preflightBlocked = !normalized.success && Boolean(normalized.preflightBlocked);
@@ -2355,6 +2497,61 @@ async function executeThirdfyRun(ctx, flags, resolved, options, runMode = 'third
2355
2497
  return normalized;
2356
2498
  }
2357
2499
 
2500
+ async function executeManagedWalletRun(ctx, flags, resolved, options) {
2501
+ const skipPreflight = Boolean(options?.skipPreflight);
2502
+ let preflight = null;
2503
+ if (!skipPreflight) {
2504
+ preflight = await resolveManagedExecutionPreflight(ctx, flags, resolved, {
2505
+ runMode: 'agent_wallet',
2506
+ requireFundingCheck: true,
2507
+ });
2508
+ if (!preflight.success) {
2509
+ const normalized = normalizeIntentResponse({
2510
+ success: false,
2511
+ status: 'failed',
2512
+ mode: 'agent_wallet',
2513
+ blockedReason: preflight.blockedReason || 'PRECHECK_FAILED',
2514
+ blockedStage: preflight.blockedStage || 'routing',
2515
+ error: preflight.error || 'Managed wallet preflight failed',
2516
+ preflightBlocked: true,
2517
+ executionWalletAddress: preflight.executionWalletAddress || null,
2518
+ executionWalletPreflight: preflight,
2519
+ });
2520
+ normalized.executionWalletAddress = preflight.executionWalletAddress || null;
2521
+ normalized.executionWalletPreflight = preflight;
2522
+ return normalized;
2523
+ }
2524
+ }
2525
+ const payload = buildManagedExecutePayload(flags, {
2526
+ resolvedAction: resolved.resolvedAction,
2527
+ runMode: 'agent_wallet',
2528
+ forceIdempotency: true,
2529
+ });
2530
+ const response = await apiPost(ctx, '/api/v1/agent/execute', payload);
2531
+ const normalized = normalizeIntentResponse({
2532
+ success: Boolean(response?.success),
2533
+ status: response?.success ? 'completed' : 'failed',
2534
+ mode: 'agent_wallet',
2535
+ txHash: response?.txHash || null,
2536
+ blockedReason: response?.blockedReason || response?.data?.blockedReason || null,
2537
+ blockedStage: response?.blockedStage || response?.data?.blockedStage || null,
2538
+ error: response?.error || null,
2539
+ executionWalletAddress: response?.executionWalletAddress || response?.data?.executionWalletAddress || null,
2540
+ signerMethod: 'managed_wallet_server',
2541
+ idempotencyKey: payload.executionIdempotencyKey || null,
2542
+ executionWalletPreflight: preflight,
2543
+ amountNormalization: response?.amountNormalization || response?.data?.amountNormalization || null,
2544
+ raw: response,
2545
+ });
2546
+ normalized.executionWalletAddress = response?.executionWalletAddress || response?.data?.executionWalletAddress || null;
2547
+ normalized.signerMethod = 'managed_wallet_server';
2548
+ normalized.executionWalletPreflight = preflight;
2549
+ normalized.amountNormalization = response?.amountNormalization || response?.data?.amountNormalization || null;
2550
+ normalized.raw = response;
2551
+ normalized.idempotencyKey = payload.executionIdempotencyKey || null;
2552
+ return normalized;
2553
+ }
2554
+
2358
2555
  async function executeSelfRun(ctx, flags, resolved, options) {
2359
2556
  const skipPreflight = Boolean(options?.skipPreflight);
2360
2557
  const blockedReasonCode = String(options?.blockedReasonCode || 'SELF_MODE_REQUIRES_BUILD_TX');
@@ -2401,7 +2598,7 @@ async function executeSelfRun(ctx, flags, resolved, options) {
2401
2598
  async function executeHybridRun(ctx, flags, resolved, options) {
2402
2599
  const hybridWalletMode = normalizeHybridWalletMode(options?.hybridWalletMode || flags.hybridWalletMode || 'self');
2403
2600
  if (hybridWalletMode === 'agent_wallet') {
2404
- const managedResult = await executeThirdfyRun(ctx, flags, resolved, options, 'agent_wallet');
2601
+ const managedResult = await executeManagedWalletRun(ctx, { ...flags, runMode: 'agent_wallet' }, resolved, options);
2405
2602
  if (!managedResult.success) {
2406
2603
  return normalizeIntentResponse({
2407
2604
  ...managedResult,
@@ -2468,6 +2665,122 @@ function buildBuildTxPayload(flags, resolvedAction) {
2468
2665
  return payload;
2469
2666
  }
2470
2667
 
2668
+ function buildManagedExecutePayload(flags, options) {
2669
+ const runMode = String(options.runMode || flags.runMode || 'agent_wallet').trim().toLowerCase();
2670
+ const payload = {
2671
+ agentApiKey: resolveAgentApiKey(flags, runMode),
2672
+ action: String(options.resolvedAction || requireFlag(flags, 'action', 'Missing --action')).trim(),
2673
+ userDid: String(flags.userDid || process.env.THIRDFY_USER_DID || '').trim(),
2674
+ params: getPreparedParams(flags),
2675
+ chainId: Number(flags.chainId || 8453),
2676
+ executionStrategy: {
2677
+ runMode,
2678
+ mirrorOnly: false,
2679
+ },
2680
+ };
2681
+ if (!payload.userDid) {
2682
+ throw createCliError(
2683
+ 'MISSING_USER_DID',
2684
+ 'Managed wallet execution requires --user-did (or THIRDFY_USER_DID).'
2685
+ );
2686
+ }
2687
+ if (flags.estimatedAmountUsd) payload.estimatedAmountUsd = Number(flags.estimatedAmountUsd);
2688
+ if (options.forceIdempotency) {
2689
+ payload.executionIdempotencyKey = String(
2690
+ flags.idempotencyKey || `cli:walletExecute:${payload.action}:${Date.now()}:${randomUUID()}`
2691
+ );
2692
+ } else if (flags.idempotencyKey) {
2693
+ payload.executionIdempotencyKey = String(flags.idempotencyKey);
2694
+ }
2695
+ return payload;
2696
+ }
2697
+
2698
+ function resolveTokenInForFunding(params) {
2699
+ if (!params || typeof params !== 'object') return '';
2700
+ const tokenIn = String(params.tokenIn || params.tokenAddress || params.token || '').trim();
2701
+ return tokenIn;
2702
+ }
2703
+
2704
+ async function resolveManagedExecutionPreflight(ctx, flags, resolved, options = {}) {
2705
+ const chainId = Number(flags.chainId || 8453);
2706
+ const runMode = String(options.runMode || 'agent_wallet').trim().toLowerCase();
2707
+ const agentApiKey = resolveAgentApiKey(flags, runMode);
2708
+ const userDid = String(flags.userDid || process.env.THIRDFY_USER_DID || '').trim();
2709
+ if (!userDid) {
2710
+ return {
2711
+ success: false,
2712
+ blockedReason: 'MISSING_USER_DID',
2713
+ blockedStage: 'routing',
2714
+ error: 'Managed wallet execution requires --user-did (or THIRDFY_USER_DID).',
2715
+ signerMethod: 'managed_wallet_server',
2716
+ };
2717
+ }
2718
+ const tokenIn = resolveTokenInForFunding(getPreparedParams(flags));
2719
+ const payload = {
2720
+ agentApiKey,
2721
+ userDid,
2722
+ chainId,
2723
+ runMode,
2724
+ ...(tokenIn ? { tokenIn } : {}),
2725
+ };
2726
+ const response = await apiPost(ctx, '/api/v1/agent/execution-wallet', payload);
2727
+ const executionWalletAddress = String(response?.executionWalletAddress || '').trim().toLowerCase();
2728
+ const signerMethod = String(response?.signerMethod || 'managed_wallet_server').trim();
2729
+ const rawBalance = String(response?.fundingTokenBalance?.raw || '').trim();
2730
+ let parsedRawBalance = null;
2731
+ if (rawBalance) {
2732
+ try {
2733
+ parsedRawBalance = BigInt(rawBalance);
2734
+ } catch {
2735
+ return {
2736
+ success: false,
2737
+ blockedReason: 'INVALID_FUNDING_BALANCE',
2738
+ blockedStage: 'sizing',
2739
+ error: `Execution wallet ${executionWalletAddress || 'unknown'} returned invalid funding token balance.`,
2740
+ executionWalletAddress,
2741
+ signerMethod,
2742
+ fundingTokenBalance: response?.fundingTokenBalance || null,
2743
+ };
2744
+ }
2745
+ }
2746
+ const expectedWallet = String(flags.walletAddress || flags.executionWalletAddress || '').trim().toLowerCase();
2747
+ const allowWalletMismatch = parseBooleanFlag(flags.allowWalletMismatch, false);
2748
+
2749
+ if (expectedWallet && executionWalletAddress && expectedWallet !== executionWalletAddress && !allowWalletMismatch) {
2750
+ return {
2751
+ success: false,
2752
+ blockedReason: 'EXECUTION_WALLET_MISMATCH',
2753
+ blockedStage: 'routing',
2754
+ error: `Funded wallet (${expectedWallet}) does not match execution wallet (${executionWalletAddress}). Use --allow-wallet-mismatch to override.`,
2755
+ executionWalletAddress,
2756
+ signerMethod,
2757
+ fundingTokenBalance: response?.fundingTokenBalance || null,
2758
+ };
2759
+ }
2760
+
2761
+ if (options.requireFundingCheck && parsedRawBalance !== null && parsedRawBalance <= 0n) {
2762
+ return {
2763
+ success: false,
2764
+ blockedReason: 'INSUFFICIENT_FUNDS',
2765
+ blockedStage: 'sizing',
2766
+ error: `Execution wallet ${executionWalletAddress || 'unknown'} has insufficient tokenIn balance.`,
2767
+ executionWalletAddress,
2768
+ signerMethod,
2769
+ fundingTokenBalance: response?.fundingTokenBalance || null,
2770
+ };
2771
+ }
2772
+
2773
+ return {
2774
+ success: true,
2775
+ executionWalletAddress,
2776
+ signerMethod,
2777
+ fundingTokenBalance: response?.fundingTokenBalance || null,
2778
+ userDid,
2779
+ chainId,
2780
+ tokenIn: tokenIn || null,
2781
+ };
2782
+ }
2783
+
2471
2784
  function buildIntentPayload(flags, options) {
2472
2785
  const runMode = String(options.runMode || flags.runMode || '').trim().toLowerCase() || 'thirdfy';
2473
2786
  const payload = {
@@ -2938,6 +3251,14 @@ async function signAndBroadcastWithOws({ txd, chainId, walletAddress, owsWalletN
2938
3251
  return { sent };
2939
3252
  }
2940
3253
 
3254
+ async function submitSignedTx({ rpcUrl, chainId, signedTxHex }) {
3255
+ const ethers = await loadEthers();
3256
+ const { JsonRpcProvider } = ethers;
3257
+ const provider = new JsonRpcProvider(rpcUrl, chainId);
3258
+ const sent = await provider.broadcastTransaction(String(signedTxHex));
3259
+ return sent?.hash || null;
3260
+ }
3261
+
2941
3262
  async function readTokenBalanceRaw(rpcUrl, walletAddress, tokenAddress) {
2942
3263
  const ethers = await loadEthers();
2943
3264
  const { JsonRpcProvider, Interface } = ethers;
@@ -3170,7 +3491,10 @@ Core commands:
3170
3491
  thirdfy-agent managed wallet init [--auth-token <token> | --owner-session-token <token>] [--chain-id <id>] [--json]
3171
3492
  thirdfy-agent managed wallet grant [--auth-token <token> | --owner-session-token <token>] --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
3172
3493
  thirdfy-agent managed setup [--auth-token <token> | --owner-session-token <token>] --agent-key <key> [--run-mode thirdfy|hybrid|agent_wallet] [--token-address <addr>] [--max-usd-per-day <usd>] [--chain-id <id>] [--json]
3173
- thirdfy-agent managed run --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid|agent_wallet] [--idempotency-key <key>] [--json]
3494
+ thirdfy-agent wallet execute --agent-api-key <key> --user-did <did> --action <id> --params <json> [--chain-id <id>] [--idempotency-key <key>] [--wallet-address <expected>] [--allow-wallet-mismatch] [--json]
3495
+ thirdfy-agent wallet sign --agent-api-key <key> --action <id> --params <json> [--chain-id <id>] [--json]
3496
+ thirdfy-agent wallet submit --signed-tx-hex <hex> [--chain-id <id>] [--rpc-url <url>] [--json]
3497
+ thirdfy-agent agent run --agent-api-key <key> --action <id> --params <json> [--signer-method managed_wallet_server|byow|fanout_intent] [--strategy-intent single_wallet|fanout] [--run-mode <mode>] [--user-did <did>] [--json]
3174
3498
  thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--verify-permissions] [--skip-permissions-probe] [--bitfinex-base-url <url>] [--json]
3175
3499
  thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
3176
3500
  thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
@@ -3205,6 +3529,8 @@ Global flags:
3205
3529
  --timeout <ms> request timeout
3206
3530
  --params <json> for swap: exactly one of {amountInRaw} or {amountInHuman + tokenInDecimals}
3207
3531
  --broadcast with run --run-mode self, sign and broadcast using OWS
3532
+ --user-did <did> required for managed wallet server execution paths
3533
+ --allow-wallet-mismatch bypass strict funded-wallet vs execution-wallet guard
3208
3534
  --effect-check <mode> self-exec check mode: strict | relaxed | off (default: relaxed)
3209
3535
  --owner-session-token <token> owner session token (or THIRDFY_OWNER_SESSION_TOKEN)
3210
3536
  --json stable machine-readable output
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,6 +54,7 @@ function normalizeIntentResponse(response) {
54
54
  mode: response?.mode || null,
55
55
  unsignedTx: response?.unsignedTx || null,
56
56
  estimatedGas: response?.estimatedGas || null,
57
+ txHash: response?.txHash || null,
57
58
  idempotencyKey: response?.idempotencyKey || null,
58
59
  mirror: response?.mirror || null,
59
60
  self: response?.self || null,