@thirdfy/agent-cli 0.2.79 → 0.2.81

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/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to `@thirdfy/agent-cli` are documented here. The format is b
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## [0.2.81] - 2026-10-01
8
+
9
+ ### Added
10
+
11
+ - **`dogeos-laika-sell-token`** is inferred as provider `dogeos-laika` for catalog rows without a `providerId`, like `dogeos-laika-buy-token`. The action ships in Thirdfy API 3.18.1 (`tokenAddress`, `tokenAmountRaw` in raw 18-decimal units, optional `minDogeWei`, `maxSlippageBps` default 100).
12
+ - **`src/core/chainRegistry.mjs`**: verified chain facts only: Base 8453 (`https://mainnet.base.org`) and DogeOS 6281971 (RPC `https://rpc.testnet.dogeos.com`, user-paid DOGE gas, faucet link). Other chains need `--rpc-url` or `RPC_URL`.
13
+
14
+ ### Fixed
15
+
16
+ - **`wallet submit` broadcast on Base for every chain.** It used `https://mainnet.base.org` (or `BASE_RPC_URL` / `RPC_URL`) whatever `--chain-id` said, and `--chain-id` itself was rejected as an unknown option although help listed it. It now accepts `--chain-id`; without it, the chain comes from the signed transaction, and a `--chain-id` that differs from the signed transaction fails with `CHAIN_ID_MISMATCH`. The RPC is `--rpc-url` first. On Base (or no chain) the order is unchanged: `BASE_RPC_URL`, `BASE_RPC_HTTP_URL`, `RPC_URL`, then `https://mainnet.base.org`. On any other chain it is `RPC_URL`, then the chain registry; `BASE_RPC_URL` is never used off Base. With none of these it fails with `RPC_URL_REQUIRED`. `doctor self` resolves its RPC the same way, accepts `--chain-id`, and checks that the RPC is on that chain.
17
+ - **`run --run-mode self --broadcast` / `self-exec` signed and broadcast through a Base RPC on every chain.** It now resolves the RPC for the transaction's chain the same way as `wallet submit`.
18
+ - **Gas note was always "Network gas may be sponsored".** `help onboarding` and `doctor auth` now take `--chain-id`; on a user-pays chain (DogeOS 6281971) they say gas is paid in native DOGE by the sending wallet and point to the faucet. Without a chain, the note names the user-pays chains. Pin: `test/dogeos-onboarding.test.cjs`.
19
+ - **DogeOS quickstart and provider runbook** now start with email onboarding (`login email`, then `--code <otp> --accept-terms --key-name`, which saves the agent key), warn not to use `managed wallet init` for `agent_wallet` (it creates a separate custodial delegation wallet), say the faucet also gives 100 test USDC, explain that Barkswap swaps are ERC-20 only so the first swap is USDC to WDOGE, and add a `dogeos-laika-sell-token` example after the buy.
20
+
21
+ ## [0.2.80] - 2026-10-01
22
+
23
+ ### Fixed
24
+
25
+ - **`actions --chain-id <id>` now filters.** It returned the whole catalog (359 rows) for any chain. It now keeps rows whose `supportedChains` includes the chain, plus rows with no `supportedChains` (chain-agnostic; several offchain venues have none yet). `run` and `preflight` are unchanged. Pin: `test/dogeos-provider-discovery.test.cjs`.
26
+ - **DogeOS quickstart and provider runbook used an action that does not exist.** `barkswap.swap` with `amountInRaw` failed with `PARAMS_SCHEMA_INVALID`. The docs now use the live catalog names (`dogeos-barkswap-get-quote`, `dogeos-barkswap-execute-swap` with `amountIn` in token units, `dogeos-laika-buy-token` with `dogeAmountWei`), list all 19 DogeOS actions across `dogeos-barkswap`, `dogeos-laika` and `dogeos-ecosystem`, and add the public testnet facts: RPC, explorer, faucet, WDOGE/USDC addresses, user-paid DOGE gas, and the allowlist 403.
27
+
7
28
  ## [0.2.79] - 2026-09-30
8
29
 
9
30
  ### Added
package/README.md CHANGED
@@ -40,13 +40,11 @@ Run without global install:
40
40
  npx @thirdfy/agent-cli --help
41
41
  ```
42
42
 
43
- ## What's new in v0.2.79
43
+ ## What's new in v0.2.81
44
44
 
45
- - **`thirdfy-agent signals`** lists the data provider apps (CoinMarketCap, Nansen, Allora, ...) and the signals they publish: what each measures, its fields and units, freshness, roles, the read that returns it, credits per call and attribution. Filter with `--provider <id>` and `--role direction|filter|context`; add `--json` for the envelope. No key is needed to browse.
46
- - **`thirdfy-agent actions --data-category <category[,category]>`** keeps only data reads of those categories (`market_data`, `signal`, `research`, `wallet`).
47
- - **`run` shows a data read's typed reading first** in human mode (`typed`, `parseIssues`, `freshness`, `attribution`), then the full result. `--json` is unchanged.
48
- - Large `--json` output piped to another program is no longer cut at the pipe buffer size. Exit codes are unchanged.
49
- - Signals and typed readings need Thirdfy API 3.17.0 or later; an older API lists no signals.
45
+ - **DogeOS onboarding**: the quickstart starts with `login email`, warns not to use `managed wallet init` for `agent_wallet`, funds gas and test USDC from the DogeOS faucet, starts with a USDC→WDOGE swap, and adds `dogeos-laika-sell-token`. See [docs/dogeos-quickstart.md](./docs/dogeos-quickstart.md).
46
+ - **Chain-aware gas notes**: `help onboarding --chain-id` and `doctor auth --chain-id` say when the sending wallet pays gas (DogeOS) and link the faucet.
47
+ - **`wallet submit --chain-id` works**, reads the chain from the signed transaction when `--chain-id` is omitted, and picks the RPC for that chain. Base keeps its order (`BASE_RPC_URL`, `BASE_RPC_HTTP_URL`, `RPC_URL`); other chains use `--rpc-url`, then `RPC_URL`, then the built-in DogeOS RPC. Self-mode broadcast (`self-exec`) uses the same rule. A Base RPC is never used on DogeOS.
50
48
  - See [CHANGELOG.md](./CHANGELOG.md) and GitHub Releases for older versions.
51
49
 
52
50
  ## Quick start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thirdfy/agent-cli",
3
- "version": "0.2.79",
3
+ "version": "0.2.81",
4
4
  "description": "Thirdfy Agent CLI for onboarding, governance preflight, execute-intent, and status polling.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/help.mjs CHANGED
@@ -11,7 +11,7 @@ Core commands:
11
11
  thirdfy-agent login email <address> [--code <otp>] [--accept-terms] [--key-name <name>] [--agent-key <key>] [--not-interactive|--ni] [--json]
12
12
  thirdfy-agent logout [--all] [--clear-agent-key] [--json]
13
13
  thirdfy-agent whoami [--json]
14
- thirdfy-agent help onboarding [--json]
14
+ thirdfy-agent help onboarding [--chain-id <id>] [--json]
15
15
  thirdfy-agent catalogs list [--json]
16
16
  thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
17
17
  thirdfy-agent data summary --agent-key <key> [--chain-id <id>] [--network <id>] [--category trading|yield|prediction] [--limit <n>] [--json]
@@ -69,8 +69,8 @@ Core commands:
69
69
  thirdfy-agent preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
70
70
  thirdfy-agent run --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--broadcast] [--idempotency-key <key>] [--json]
71
71
  thirdfy-agent self-exec --agent-api-key <key> --action <id> --params <json> --wallet-address <addr> --ows-wallet-name <name> [--rpc-url <url>] [--effect-check strict|relaxed|off] [--json]
72
- thirdfy-agent doctor self [--wallet-address <addr>] [--ows-wallet-name <name>] [--rpc-url <url>] [--json]
73
- thirdfy-agent doctor auth [--json]
72
+ thirdfy-agent doctor self [--wallet-address <addr>] [--ows-wallet-name <name>] [--chain-id <id>] [--rpc-url <url>] [--json]
73
+ thirdfy-agent doctor auth [--chain-id <id>] [--json]
74
74
  thirdfy-agent doctor certify execution [--no-write] [--run-mode <mode>] [--schema-digest <digest>] [--required-actions <csv>] [--json]
75
75
  thirdfy-agent jeff preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
76
76
  thirdfy-agent jeff trade --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
@@ -60,8 +60,8 @@ export const CLI_MANIFEST = [
60
60
  },
61
61
  { path: ['logout'], handler: 'commandLogout', options: ['bootstrap'], description: 'Logout and clear local credentials' },
62
62
  { path: ['whoami'], handler: 'commandWhoami', description: 'Show masked local identity' },
63
- { path: ['doctor', 'self'], handler: 'commandDoctorSelf', options: ['wallet'], description: 'Self-mode wallet diagnostics' },
64
- { path: ['doctor', 'auth'], handler: 'commandDoctorAuth', description: 'Auth and onboarding diagnostics' },
63
+ { path: ['doctor', 'self'], handler: 'commandDoctorSelf', options: ['wallet', 'chains'], description: 'Self-mode wallet diagnostics' },
64
+ { path: ['doctor', 'auth'], handler: 'commandDoctorAuth', options: ['chains'], description: 'Auth and onboarding diagnostics' },
65
65
  {
66
66
  path: ['doctor', 'certify', 'execution'],
67
67
  handler: 'commandDoctorCertifyExecution',
@@ -72,11 +72,13 @@ export const CLI_MANIFEST = [
72
72
  {
73
73
  path: ['help', 'onboarding'],
74
74
  handler: 'commandHelpOnboarding',
75
+ options: ['chains'],
75
76
  description: 'Structured onboarding help (JSON envelope)',
76
77
  },
77
78
  {
78
79
  path: ['onboarding', 'help'],
79
80
  handler: 'commandHelpOnboarding',
81
+ options: ['chains'],
80
82
  description: 'Structured onboarding help (JSON envelope)',
81
83
  },
82
84
  { path: ['wallet', 'list'], handler: 'commandWalletList', description: 'List configured wallets' },
@@ -592,7 +594,7 @@ export const CLI_MANIFEST = [
592
594
  path: ['wallet', 'submit'],
593
595
  handler: 'commandWalletSubmit',
594
596
  tier: 'online',
595
- options: ['wallet'],
597
+ options: ['wallet', 'chains'],
596
598
  description: 'Submit signed transaction',
597
599
  },
598
600
  {
@@ -153,12 +153,12 @@ async function commandConfigSet(ctx, flags) {
153
153
  }
154
154
 
155
155
 
156
- async function commandHelpOnboarding(ctx, _flags) {
156
+ async function commandHelpOnboarding(ctx, flags) {
157
157
  printEnvelope({
158
158
  success: true,
159
159
  code: 'ONBOARDING_HELP_OK',
160
160
  message: 'Thirdfy onboarding help',
161
- data: buildOnboardingHelp(ctx),
161
+ data: buildOnboardingHelp(ctx, flags),
162
162
  meta: { apiBase: ctx.apiBase },
163
163
  });
164
164
  }
@@ -3,6 +3,7 @@ import { apiGet, apiPost } from '../core/http.mjs';
3
3
  import { extractActions, getCachedActionsCatalog } from '../core/context.mjs';
4
4
  import { getEffectiveRunMode } from '../core/runMode.mjs';
5
5
  import { formatCreditsBalanceSummary } from '../lib/creditsUsdSummary.mjs';
6
+ import { filterActionsByChain } from '../runtime/actions/chainFilter.mjs';
6
7
  export function createDiscoveryCommands({ printEnvelope, applyActionFilters, getTradingProviderHint, resolveChainSupport, getAuthToken, resolveAgentApiKey }) {
7
8
  async function commandCatalogsList(ctx, flags, capabilities) {
8
9
  const response = await apiGet(ctx, '/api/v1/agent/actions/catalog');
@@ -32,10 +33,10 @@ async function commandCatalogsList(ctx, flags, capabilities) {
32
33
 
33
34
  async function commandActions(ctx, flags, capabilities) {
34
35
  const policyAware = Boolean(flags.agentApiKey);
35
- const actions = applyActionFilters(await getCachedActionsCatalog(ctx, flags), flags);
36
+ const chainIdFilter = Number(flags.chainId || 0) || undefined;
37
+ const actions = filterActionsByChain(applyActionFilters(await getCachedActionsCatalog(ctx, flags), flags), chainIdFilter);
36
38
  const providerHint = getTradingProviderHint(flags.provider);
37
39
  const effectiveRunMode = getEffectiveRunMode(ctx, flags);
38
- const chainIdFilter = Number(flags.chainId || 0) || undefined;
39
40
  const chainSupport = resolveChainSupport(capabilities, chainIdFilter, effectiveRunMode);
40
41
  printEnvelope({
41
42
  success: true,
@@ -3,6 +3,7 @@ import { requireFlag } from '../core/args.mjs';
3
3
  import { apiGet, requestWithRouteFallback } from '../core/http.mjs';
4
4
  import { loadProfileConfig, getProfileConfigPath, getCachedActionsCatalog } from '../core/context.mjs';
5
5
  import { DEFAULT_API_BASE, PROFILE_DEFAULTS } from '../core/constants.mjs';
6
+ import { describeChainGas, parseChainIdFlag, resolveChainRpcUrl } from '../core/chainRegistry.mjs';
6
7
  import { exitAfterOutput } from '../core/envelope.mjs';
7
8
  import { normalizeRunMode, normalizeManagedRunMode } from '../core/runMode.mjs';
8
9
  import { buildExecutionCertificationReport } from '../runtime/certification/executionCertify.mjs';
@@ -25,9 +26,7 @@ export function createDoctorCommands({
25
26
  async function commandDoctorSelf(ctx, flags) {
26
27
  const walletAddress = String(flags.walletAddress || process.env.MARKET_MAKER_WALLET_ADDRESS || '').trim();
27
28
  const owsWalletName = String(flags.owsWalletName || process.env.OWS_WALLET_NAME || '').trim();
28
- const rpcUrl = String(
29
- flags.rpcUrl || process.env.BASE_RPC_URL || process.env.BASE_RPC_HTTP_URL || process.env.RPC_URL || 'https://mainnet.base.org'
30
- ).trim();
29
+ const rpcUrl = resolveChainRpcUrl({ chainId: flags.chainId, rpcUrl: flags.rpcUrl });
31
30
  const passphrasePresent = Boolean(String(process.env.OWS_PASSPHRASE || process.env.OWS_API_KEY || '').trim());
32
31
  const checks = [];
33
32
 
@@ -62,10 +61,23 @@ async function commandDoctorSelf(ctx, flags) {
62
61
  }
63
62
 
64
63
  try {
64
+ if (!rpcUrl) throw new Error(`no known RPC for chain ${flags.chainId}; pass --rpc-url <url>`);
65
65
  const ethers = await loadEthers();
66
66
  const provider = new ethers.JsonRpcProvider(rpcUrl);
67
67
  const block = await provider.getBlockNumber();
68
68
  checks.push({ name: 'rpc-connectivity', ok: Number.isFinite(block), detail: `latestBlock=${block}` });
69
+ const expectedChainId = parseChainIdFlag(flags.chainId);
70
+ if (expectedChainId) {
71
+ const network = await provider.getNetwork();
72
+ const rpcChainId = Number(network.chainId);
73
+ checks.push({
74
+ name: 'rpc-chain-id',
75
+ ok: rpcChainId === expectedChainId,
76
+ detail: rpcChainId === expectedChainId
77
+ ? `chainId=${rpcChainId}`
78
+ : `RPC is on chain ${rpcChainId}, expected ${expectedChainId}; pass --rpc-url for chain ${expectedChainId}`,
79
+ });
80
+ }
69
81
  } catch (error) {
70
82
  checks.push({ name: 'rpc-connectivity', ok: false, detail: error?.message || 'rpc check failed' });
71
83
  }
@@ -91,7 +103,8 @@ async function commandDoctorSelf(ctx, flags) {
91
103
  if (!ok) await exitAfterOutput(1);
92
104
  }
93
105
 
94
- function buildOnboardingHelp(ctx) {
106
+ function buildOnboardingHelp(ctx, flags = {}) {
107
+ const chainId = Number(flags?.chainId || 0) || null;
95
108
  return {
96
109
  title: 'Thirdfy Agent CLI onboarding',
97
110
  summary: 'Use email OTP, wallet-sign, or existing credentials to create owner auth, wallets, and hidden agent execution credentials. Fresh solo profiles default to agent_wallet.',
@@ -140,8 +153,9 @@ function buildOnboardingHelp(ctx) {
140
153
  'thirdfy-agent login email user@example.com --code 123456 --accept-terms --ni (place --ni after the subcommand so it is not parsed as a flag value)',
141
154
  rule: 'Commands must fail fast instead of waiting for prompts when required input is missing.',
142
155
  },
156
+ gas: describeChainGas(chainId),
143
157
  executionModes: {
144
- agent_wallet: 'Solo managed server-wallet execution through /execution-wallet + /execute; no subscriber binding when owner userDid matches. Network gas may be sponsored, but Thirdfy credits/free-quota gates still apply.',
158
+ agent_wallet: `Solo managed server-wallet execution through /execution-wallet + /execute; no subscriber binding when owner userDid matches. ${describeChainGas(chainId)}`,
145
159
  self: 'BYOW/OWS local signing through build-tx, wallet sign, and wallet submit.',
146
160
  hybrid: 'Primary self or agent_wallet execution with optional Thirdfy mirror validation.',
147
161
  thirdfy: 'Publisher/fanout execute-intent; requires active subscriber/delegation bindings for writes.',
@@ -159,7 +173,7 @@ function buildOnboardingHelp(ctx) {
159
173
  };
160
174
  }
161
175
 
162
- async function commandDoctorAuth(ctx, _flags) {
176
+ async function commandDoctorAuth(ctx, flags = {}) {
163
177
  const config = loadProfileConfig();
164
178
  const auth = config.auth && typeof config.auth === 'object' ? config.auth : {};
165
179
  const agent = config.agent && typeof config.agent === 'object' ? config.agent : {};
@@ -220,7 +234,7 @@ async function commandDoctorAuth(ctx, _flags) {
220
234
  name: 'thirdfy-credits-vs-gas-sponsorship',
221
235
  optional: true,
222
236
  ok: true,
223
- detail: 'Thirdfy credits/free quota gate action access; Privy gas sponsorship only pays network gas when chain/action sponsorship is enabled.',
237
+ detail: `Thirdfy credits/free quota gate action access; Privy gas sponsorship only pays network gas when chain/action sponsorship is enabled. ${describeChainGas(flags?.chainId)}`,
224
238
  },
225
239
  ];
226
240
  const ok = checks.every((entry) => entry.optional || entry.ok);
@@ -230,7 +244,7 @@ async function commandDoctorAuth(ctx, _flags) {
230
244
  message: ok ? 'Auth profile is ready' : 'Auth profile needs setup',
231
245
  data: {
232
246
  checks,
233
- help: buildOnboardingHelp(ctx),
247
+ help: buildOnboardingHelp(ctx, flags),
234
248
  },
235
249
  meta: { apiBase: ctx.apiBase },
236
250
  });
@@ -1,6 +1,8 @@
1
1
  import { requireFlag } from '../core/args.mjs';
2
2
  import { createCliError, exitAfterOutput } from '../core/envelope.mjs';
3
3
  import { loadProfileConfig } from '../core/context.mjs';
4
+ import { DEFAULT_CHAIN_ID, parseChainIdFlag, resolveChainRpcUrl } from '../core/chainRegistry.mjs';
5
+ import { readSignedTxChainId } from '../core/signedTx.mjs';
4
6
 
5
7
  export function createWalletCommands(deps) {
6
8
  const {
@@ -85,10 +87,27 @@ async function commandWalletSubmit(ctx, flags, capabilities) {
85
87
  if (!signedTxHex) {
86
88
  throw createCliError('MISSING_SIGNED_TX', 'wallet submit requires --signed-tx-hex');
87
89
  }
88
- const chainId = Number(flags.chainId || 8453);
89
- const rpcUrl = String(
90
- flags.rpcUrl || process.env.BASE_RPC_URL || process.env.BASE_RPC_HTTP_URL || process.env.RPC_URL || 'https://mainnet.base.org'
91
- ).trim();
90
+ const flagChainId = parseChainIdFlag(flags.chainId);
91
+ if (Number.isNaN(flagChainId)) {
92
+ throw createCliError('INVALID_CHAIN_ID', `wallet submit --chain-id must be a positive integer (got ${flags.chainId})`);
93
+ }
94
+ // The signed tx carries its chain id (EIP-155 / typed tx). Use it when --chain-id is absent,
95
+ // and refuse a mismatch so a DogeOS-signed tx is never sent to a Base RPC (or vice versa).
96
+ const txChainId = readSignedTxChainId(signedTxHex);
97
+ if (flagChainId && txChainId && flagChainId !== txChainId) {
98
+ throw createCliError(
99
+ 'CHAIN_ID_MISMATCH',
100
+ `wallet submit --chain-id ${flagChainId} does not match the signed transaction chain id ${txChainId}`
101
+ );
102
+ }
103
+ const chainId = flagChainId || txChainId || DEFAULT_CHAIN_ID;
104
+ const rpcUrl = resolveChainRpcUrl({ chainId, rpcUrl: flags.rpcUrl });
105
+ if (!rpcUrl) {
106
+ throw createCliError(
107
+ 'RPC_URL_REQUIRED',
108
+ `wallet submit has no known RPC for chain ${chainId}; pass --rpc-url <url> for that chain`
109
+ );
110
+ }
92
111
  const txHash = await submitSignedTx({ rpcUrl, chainId, signedTxHex });
93
112
  printEnvelope({
94
113
  success: true,
@@ -0,0 +1,82 @@
1
+ // Chain facts the CLI needs locally: a public RPC for BYOW submit / balance reads, and
2
+ // whether network gas is paid by the sending wallet (no sponsorship on that chain).
3
+ // The API capabilities document stays the source of truth for lane support.
4
+
5
+ export const DEFAULT_CHAIN_ID = 8453;
6
+
7
+ // Only chains whose RPC we have verified. Other chains need --rpc-url or RPC_URL.
8
+ export const CHAIN_REGISTRY = {
9
+ 8453: { chainKey: 'base-mainnet', displayName: 'Base', rpcUrl: 'https://mainnet.base.org', nativeSymbol: 'ETH' },
10
+ 6281971: {
11
+ chainKey: 'dogeos-testnet',
12
+ displayName: 'DogeOS Chikyu testnet',
13
+ rpcUrl: 'https://rpc.testnet.dogeos.com',
14
+ nativeSymbol: 'DOGE',
15
+ userPaysGas: true,
16
+ faucetUrl: 'https://faucet.testnet.dogeos.com',
17
+ },
18
+ };
19
+
20
+ function positiveChainId(value) {
21
+ const n = Number(value);
22
+ if (!Number.isFinite(n) || n <= 0) return undefined;
23
+ return Math.trunc(n);
24
+ }
25
+
26
+ export function getChainInfo(chainId) {
27
+ const id = positiveChainId(chainId);
28
+ return id ? CHAIN_REGISTRY[id] || null : null;
29
+ }
30
+
31
+ /** Chains where the sending wallet pays network gas in the native token (never sponsored). */
32
+ export function chainUserPaysGas(chainId) {
33
+ return Boolean(getChainInfo(chainId)?.userPaysGas);
34
+ }
35
+
36
+ /** Chain ids where the sending wallet pays gas. */
37
+ export const USER_PAYS_GAS_CHAIN_IDS = Object.entries(CHAIN_REGISTRY)
38
+ .filter(([, info]) => info.userPaysGas)
39
+ .map(([id]) => Number(id));
40
+
41
+ /**
42
+ * One-line gas note for a chain. Without a chain id it describes the generic case.
43
+ */
44
+ export function describeChainGas(chainId) {
45
+ const info = getChainInfo(chainId);
46
+ if (info?.userPaysGas) {
47
+ const faucet = info.faucetUrl ? ` Fund it from ${info.faucetUrl}.` : '';
48
+ return `Network gas on ${info.displayName} (${positiveChainId(chainId)}) is paid in native ${info.nativeSymbol} by the sending wallet; it is not sponsored.${faucet}`;
49
+ }
50
+ const userPays = USER_PAYS_GAS_CHAIN_IDS.map((id) => `${CHAIN_REGISTRY[id].displayName} ${id}`).join(', ');
51
+ return `Network gas may be sponsored on supported chains, but Thirdfy credits/free-quota gates still apply. On user-pays chains (${userPays}) the sending wallet pays gas in the native token.`;
52
+ }
53
+
54
+ /**
55
+ * RPC for a chain. The --rpc-url flag always wins. Then:
56
+ * - Base (or no chain given): BASE_RPC_URL, BASE_RPC_HTTP_URL, RPC_URL, then the registry.
57
+ * This is the pre-0.2.81 order, so existing Base setups keep the same RPC.
58
+ * - Any other chain: RPC_URL, then the registry. BASE_RPC_URL / BASE_RPC_HTTP_URL are never used.
59
+ * Returns '' when nothing matches; the caller asks for --rpc-url.
60
+ */
61
+ export function resolveChainRpcUrl({ chainId, rpcUrl, env = process.env } = {}) {
62
+ const explicit = String(rpcUrl || '').trim();
63
+ if (explicit) return explicit;
64
+ const id = positiveChainId(chainId) || DEFAULT_CHAIN_ID;
65
+ const generic = String(env.RPC_URL || '').trim();
66
+ if (id === DEFAULT_CHAIN_ID) {
67
+ const baseEnv = String(env.BASE_RPC_URL || env.BASE_RPC_HTTP_URL || '').trim();
68
+ if (baseEnv) return baseEnv;
69
+ }
70
+ if (generic) return generic;
71
+ return getChainInfo(id)?.rpcUrl || '';
72
+ }
73
+
74
+ /**
75
+ * Parse a --chain-id value. Returns null when absent, a positive integer when valid,
76
+ * and NaN when present but not a positive integer (the caller rejects it).
77
+ */
78
+ export function parseChainIdFlag(value) {
79
+ if (value === undefined || value === null || String(value).trim() === '') return null;
80
+ const n = Number(String(value).trim());
81
+ return Number.isInteger(n) && n > 0 ? n : NaN;
82
+ }
@@ -0,0 +1,23 @@
1
+ import { createRequire } from 'module';
2
+
3
+ const require = createRequire(import.meta.url);
4
+
5
+ /**
6
+ * Chain id encoded in a signed raw transaction (EIP-155 / typed tx), or null when the
7
+ * hex does not parse or carries no chain id (legacy pre-EIP-155).
8
+ */
9
+ export function readSignedTxChainId(signedTxHex) {
10
+ let Transaction;
11
+ try {
12
+ ({ Transaction } = require('ethers'));
13
+ } catch {
14
+ return null;
15
+ }
16
+ try {
17
+ const tx = Transaction.from(String(signedTxHex || '').trim());
18
+ const id = Number(tx.chainId);
19
+ return Number.isInteger(id) && id > 0 ? id : null;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `actions --chain-id <id>` keeps catalog rows that run on that chain.
3
+ * A row without `supportedChains` is chain-agnostic and stays.
4
+ */
5
+ export function filterActionsByChain(actions, chainId) {
6
+ const wanted = Number(chainId || 0);
7
+ if (!wanted || !Array.isArray(actions)) return actions;
8
+ return actions.filter((action) => {
9
+ const chains = action?.supportedChains;
10
+ if (!Array.isArray(chains) || chains.length === 0) return true;
11
+ return chains.some((value) => Number(value) === wanted);
12
+ });
13
+ }
@@ -77,6 +77,7 @@ function inferActionProvider(action) {
77
77
  'dogeos-laika-execute-raw': 'dogeos-laika',
78
78
  'dogeos-laika-launch-token': 'dogeos-laika',
79
79
  'dogeos-laika-buy-token': 'dogeos-laika',
80
+ 'dogeos-laika-sell-token': 'dogeos-laika',
80
81
  'dogeos-laika-claim': 'dogeos-laika',
81
82
  'dogeos-laika-token-state': 'dogeos-laika',
82
83
  'get-governance-v2-overview': 'governance-v2',
@@ -14,6 +14,7 @@ import { idempotentReplayFields } from '../../core/hostedRuntimeIdempotency.mjs'
14
14
  import { withActionTimeout } from '../../core/actionTimeouts.mjs';
15
15
  import { isReadOnlyResolvedAction } from '../../core/readOnlyActions.mjs';
16
16
  import { resolveWalletExecuteChainId } from '../../core/walletExecuteChainId.mjs';
17
+ import { resolveChainRpcUrl } from '../../core/chainRegistry.mjs';
17
18
  import { isEmptyQueuedExecuteIntent, shouldFallbackManagedReadToExecuteIntent } from './managedReadExecuteRail.mjs';
18
19
  import { createRequire } from 'module';
19
20
 
@@ -850,9 +851,10 @@ async function performSelfBroadcast(ctx, flags, resolvedAction, selfResult) {
850
851
  const chainId = Number(txd.chainId || flags.chainId || 8453);
851
852
  const walletAddress = String(flags.walletAddress || process.env.MARKET_MAKER_WALLET_ADDRESS || '').trim();
852
853
  const owsWalletName = String(flags.owsWalletName || process.env.OWS_WALLET_NAME || '').trim();
853
- const rpcUrl = String(
854
- flags.rpcUrl || process.env.BASE_RPC_URL || process.env.BASE_RPC_HTTP_URL || process.env.RPC_URL || 'https://mainnet.base.org'
855
- ).trim();
854
+ const rpcUrl = resolveChainRpcUrl({ chainId, rpcUrl: flags.rpcUrl });
855
+ if (!rpcUrl) {
856
+ throw createCliError('RPC_URL_REQUIRED', `self-exec has no known RPC for chain ${chainId}; pass --rpc-url <url> for that chain`);
857
+ }
856
858
  if (!walletAddress) {
857
859
  throw createCliError('SIGNING_FAILED', 'Missing --wallet-address (or MARKET_MAKER_WALLET_ADDRESS) for self-exec signing.');
858
860
  }