@thirdfy/agent-cli 0.2.80 → 0.2.82
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 +21 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/cli/help.mjs +3 -3
- package/src/cli/manifest.mjs +5 -3
- package/src/commands/bootstrap.mjs +2 -2
- package/src/commands/doctor.mjs +22 -8
- package/src/commands/wallet.mjs +23 -4
- package/src/core/chainRegistry.mjs +82 -0
- package/src/core/fundingTokenIn.mjs +5 -0
- package/src/core/signedTx.mjs +23 -0
- package/src/runtime/actions/selection.mjs +1 -0
- package/src/runtime/execution/nativeGas.mjs +13 -0
- package/src/runtime/execution/runners.mjs +25 -3
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.82] - 2026-10-01
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`preflight` / `run --run-mode agent_wallet` keep the API's native gas readiness.** On user-pays-gas chains (DogeOS 6281971) Thirdfy API 3.18.2 returns `gasSponsored`, `gasReady`, `nativeGasBalance` and, at 0, `nativeGasWarning` from `/execution-wallet`; the CLI rebuilt the preflight from a fixed field list and dropped them. They now appear under `data.executionWalletPreflight`, and a write whose wallet holds 0 native coin stops before `/execute` with `INSUFFICIENT_NATIVE_GAS` and the faucet message. Sponsored chains and older APIs are unchanged. Pin: `test/preflight-native-gas.test.cjs`.
|
|
12
|
+
- **DogeOS Laika writes check the right funding token.** `dogeos-laika-buy-token` and `dogeos-laika-launch-token` spend native DOGE (`dogeAmountWei`, `initialPurchaseWei`), and `dogeos-laika-claim` receives `tokenAddress`; the CLI sent that bonding-curve token as the execution-wallet `tokenIn` and blocked a buy with `INSUFFICIENT_FUNDS`. They now skip the ERC-20 balance check, as does any action that carries `dogeAmountWei`. `dogeos-laika-sell-token` still checks the token it sells. Pin: `test/funding-token-in.test.cjs`.
|
|
13
|
+
|
|
14
|
+
## [0.2.81] - 2026-10-01
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`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).
|
|
19
|
+
- **`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`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **`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.
|
|
24
|
+
- **`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`.
|
|
25
|
+
- **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`.
|
|
26
|
+
- **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.
|
|
27
|
+
|
|
7
28
|
## [0.2.80] - 2026-10-01
|
|
8
29
|
|
|
9
30
|
### Fixed
|
package/README.md
CHANGED
|
@@ -40,10 +40,10 @@ Run without global install:
|
|
|
40
40
|
npx @thirdfy/agent-cli --help
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
## What's new in v0.2.
|
|
43
|
+
## What's new in v0.2.82
|
|
44
44
|
|
|
45
|
-
- **`
|
|
46
|
-
- **DogeOS
|
|
45
|
+
- **`agent_wallet` preflight shows native gas** on chains where the wallet pays gas (DogeOS 6281971): `gasSponsored`, `gasReady`, `nativeGasBalance`, from Thirdfy API 3.18.2. A write from a wallet with 0 native coin stops before `/execute` with `INSUFFICIENT_NATIVE_GAS` and the faucet link.
|
|
46
|
+
- **DogeOS Laika buys pass preflight.** A buy spends native DOGE (`dogeAmountWei`); the CLI no longer checks the balance of the token being bought. Launch and claim are treated the same way, and a sell still checks the token it sells.
|
|
47
47
|
- See [CHANGELOG.md](./CHANGELOG.md) and GitHub Releases for older versions.
|
|
48
48
|
|
|
49
49
|
## Quick start
|
package/package.json
CHANGED
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]
|
package/src/cli/manifest.mjs
CHANGED
|
@@ -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,
|
|
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
|
}
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -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 =
|
|
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:
|
|
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,
|
|
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:
|
|
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
|
});
|
package/src/commands/wallet.mjs
CHANGED
|
@@ -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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
}
|
|
@@ -9,6 +9,9 @@ const NATIVE_ETH_SPEND_ACTIONS = new Set([
|
|
|
9
9
|
'basedbid_create_lbp',
|
|
10
10
|
'basedbid_create_flash',
|
|
11
11
|
'basedbid_create_board',
|
|
12
|
+
// DogeOS Laika: buys and launches spend native DOGE; tokenAddress is the bonding-curve token received.
|
|
13
|
+
'dogeos_laika_buy_token',
|
|
14
|
+
'dogeos_laika_launch_token',
|
|
12
15
|
]);
|
|
13
16
|
|
|
14
17
|
/**
|
|
@@ -18,6 +21,7 @@ const NATIVE_ETH_SPEND_ACTIONS = new Set([
|
|
|
18
21
|
const POSITION_EXIT_ACTIONS = new Set([
|
|
19
22
|
'withdraw_earn_position',
|
|
20
23
|
'claim_earn_rewards',
|
|
24
|
+
'dogeos_laika_claim',
|
|
21
25
|
]);
|
|
22
26
|
|
|
23
27
|
export function resolveTokenInForFunding(params, action) {
|
|
@@ -28,5 +32,6 @@ export function resolveTokenInForFunding(params, action) {
|
|
|
28
32
|
if (NATIVE_ETH_SPEND_ACTIONS.has(actionId)) return '';
|
|
29
33
|
if (POSITION_EXIT_ACTIONS.has(actionId)) return '';
|
|
30
34
|
if (params.amountEth != null && String(params.amountEth).trim() !== '') return '';
|
|
35
|
+
if (params.dogeAmountWei != null && String(params.dogeAmountWei).trim() !== '') return '';
|
|
31
36
|
return String(params.tokenAddress || params.token || '').trim();
|
|
32
37
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const NATIVE_GAS_FIELDS = ['gasSponsored', 'gasReady', 'nativeGasBalance', 'nativeGasBalanceUnavailable', 'nativeGasWarning'];
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native gas readiness from an `/execution-wallet` reply (Thirdfy API 3.18.2+). Present only on
|
|
5
|
+
* user-pays-gas chains (DogeOS 6281971); empty for sponsored chains and older APIs.
|
|
6
|
+
*/
|
|
7
|
+
export function pickNativeGasFields(response) {
|
|
8
|
+
const out = {};
|
|
9
|
+
for (const key of NATIVE_GAS_FIELDS) {
|
|
10
|
+
if (response && response[key] !== undefined) out[key] = response[key];
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
+
import { pickNativeGasFields } from './nativeGas.mjs';
|
|
2
3
|
import { requireFlag } from '../../core/args.mjs';
|
|
3
4
|
import { createCliError } from '../../core/envelope.mjs';
|
|
4
5
|
import {
|
|
@@ -14,6 +15,7 @@ import { idempotentReplayFields } from '../../core/hostedRuntimeIdempotency.mjs'
|
|
|
14
15
|
import { withActionTimeout } from '../../core/actionTimeouts.mjs';
|
|
15
16
|
import { isReadOnlyResolvedAction } from '../../core/readOnlyActions.mjs';
|
|
16
17
|
import { resolveWalletExecuteChainId } from '../../core/walletExecuteChainId.mjs';
|
|
18
|
+
import { resolveChainRpcUrl } from '../../core/chainRegistry.mjs';
|
|
17
19
|
import { isEmptyQueuedExecuteIntent, shouldFallbackManagedReadToExecuteIntent } from './managedReadExecuteRail.mjs';
|
|
18
20
|
import { createRequire } from 'module';
|
|
19
21
|
|
|
@@ -777,6 +779,7 @@ async function resolveManagedExecutionPreflight(ctx, flags, resolved, options =
|
|
|
777
779
|
executionWalletAddress: preflightSeed.executionWalletAddress || null,
|
|
778
780
|
signerMethod: preflightSeed.signerMethod || null,
|
|
779
781
|
fundingTokenBalance: preflightSeed.fundingTokenBalance || null,
|
|
782
|
+
...pickNativeGasFields(preflightSeed),
|
|
780
783
|
}
|
|
781
784
|
: await apiGet(ctx, `/api/v1/agent/execution-wallet?${query.toString()}`, {
|
|
782
785
|
'x-agent-api-key': agentApiKey,
|
|
@@ -831,6 +834,23 @@ async function resolveManagedExecutionPreflight(ctx, flags, resolved, options =
|
|
|
831
834
|
};
|
|
832
835
|
}
|
|
833
836
|
|
|
837
|
+
// User-pays-gas chains (DogeOS): the API reports native gas readiness (API 3.18.2+). Keep it, and
|
|
838
|
+
// stop a write before /execute when the wallet holds 0 native coin.
|
|
839
|
+
const nativeGas = pickNativeGasFields(response);
|
|
840
|
+
if (options.requireFundingCheck && nativeGas.gasReady === false) {
|
|
841
|
+
return {
|
|
842
|
+
success: false,
|
|
843
|
+
blockedReason: 'INSUFFICIENT_NATIVE_GAS',
|
|
844
|
+
blockedStage: 'sizing',
|
|
845
|
+
error: nativeGas.nativeGasWarning?.message
|
|
846
|
+
|| `Execution wallet ${executionWalletAddress || 'unknown'} has 0 native gas on chain ${chainId}; gas on this chain is paid by the wallet.`,
|
|
847
|
+
executionWalletAddress,
|
|
848
|
+
signerMethod,
|
|
849
|
+
fundingTokenBalance: response?.fundingTokenBalance || null,
|
|
850
|
+
...nativeGas,
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
834
854
|
return {
|
|
835
855
|
success: true,
|
|
836
856
|
executionWalletAddress,
|
|
@@ -839,6 +859,7 @@ async function resolveManagedExecutionPreflight(ctx, flags, resolved, options =
|
|
|
839
859
|
userDid,
|
|
840
860
|
chainId,
|
|
841
861
|
tokenIn: tokenIn || null,
|
|
862
|
+
...nativeGas,
|
|
842
863
|
};
|
|
843
864
|
}
|
|
844
865
|
|
|
@@ -850,9 +871,10 @@ async function performSelfBroadcast(ctx, flags, resolvedAction, selfResult) {
|
|
|
850
871
|
const chainId = Number(txd.chainId || flags.chainId || 8453);
|
|
851
872
|
const walletAddress = String(flags.walletAddress || process.env.MARKET_MAKER_WALLET_ADDRESS || '').trim();
|
|
852
873
|
const owsWalletName = String(flags.owsWalletName || process.env.OWS_WALLET_NAME || '').trim();
|
|
853
|
-
const rpcUrl =
|
|
854
|
-
|
|
855
|
-
|
|
874
|
+
const rpcUrl = resolveChainRpcUrl({ chainId, rpcUrl: flags.rpcUrl });
|
|
875
|
+
if (!rpcUrl) {
|
|
876
|
+
throw createCliError('RPC_URL_REQUIRED', `self-exec has no known RPC for chain ${chainId}; pass --rpc-url <url> for that chain`);
|
|
877
|
+
}
|
|
856
878
|
if (!walletAddress) {
|
|
857
879
|
throw createCliError('SIGNING_FAILED', 'Missing --wallet-address (or MARKET_MAKER_WALLET_ADDRESS) for self-exec signing.');
|
|
858
880
|
}
|