@thirdfy/agent-cli 0.1.5 → 0.1.7
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 +147 -12
- package/bin/thirdfy-agent.mjs +1021 -33
- package/package.json +10 -1
- package/src/utils/cli/intentNormalization.cjs +20 -1
package/bin/thirdfy-agent.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
4
5
|
import fs from 'fs';
|
|
5
6
|
import os from 'os';
|
|
6
7
|
import path from 'path';
|
|
@@ -105,6 +106,15 @@ async function main() {
|
|
|
105
106
|
case 'config get':
|
|
106
107
|
await commandProfileShow(context, subFlags);
|
|
107
108
|
return;
|
|
109
|
+
case 'config set':
|
|
110
|
+
await commandConfigSet(context, subFlags);
|
|
111
|
+
return;
|
|
112
|
+
case 'login':
|
|
113
|
+
await commandLogin(context, subFlags);
|
|
114
|
+
return;
|
|
115
|
+
case 'logout':
|
|
116
|
+
await commandLogout(context, subFlags);
|
|
117
|
+
return;
|
|
108
118
|
case 'whoami':
|
|
109
119
|
await commandProfileShow(context, subFlags);
|
|
110
120
|
return;
|
|
@@ -130,12 +140,36 @@ async function main() {
|
|
|
130
140
|
case 'credits balance':
|
|
131
141
|
await commandCreditsBalance(context, subFlags, capabilities);
|
|
132
142
|
return;
|
|
133
|
-
case 'delegation
|
|
134
|
-
await
|
|
143
|
+
case 'delegation create':
|
|
144
|
+
await commandDelegationCreate(context, subFlags, capabilities);
|
|
145
|
+
return;
|
|
146
|
+
case 'delegation init-custodial':
|
|
147
|
+
await commandDelegationInitCustodial(context, subFlags, capabilities);
|
|
148
|
+
return;
|
|
149
|
+
case 'delegation custodial-grant':
|
|
150
|
+
await commandDelegationCustodialGrant(context, subFlags, capabilities);
|
|
151
|
+
return;
|
|
152
|
+
case 'delegation activate':
|
|
153
|
+
await commandDelegationActivate(context, subFlags, capabilities);
|
|
135
154
|
return;
|
|
136
155
|
case 'delegation status':
|
|
137
156
|
await commandDelegationStatus(context, subFlags, capabilities);
|
|
138
157
|
return;
|
|
158
|
+
case 'delegation show':
|
|
159
|
+
await commandDelegationStatus(context, subFlags, capabilities);
|
|
160
|
+
return;
|
|
161
|
+
case 'delegation inspect':
|
|
162
|
+
await commandDelegationInspect(context, subFlags, capabilities);
|
|
163
|
+
return;
|
|
164
|
+
case 'delegation balance':
|
|
165
|
+
await commandDelegationBalance(context, subFlags, capabilities);
|
|
166
|
+
return;
|
|
167
|
+
case 'delegation revoke':
|
|
168
|
+
await commandDelegationRevoke(context, subFlags, capabilities);
|
|
169
|
+
return;
|
|
170
|
+
case 'delegation redeem':
|
|
171
|
+
await commandDelegationRedeem(context, subFlags, capabilities);
|
|
172
|
+
return;
|
|
139
173
|
case 'credentials upsert':
|
|
140
174
|
await commandCredentialsUpsert(context, subFlags, capabilities);
|
|
141
175
|
return;
|
|
@@ -145,6 +179,9 @@ async function main() {
|
|
|
145
179
|
case 'agent register':
|
|
146
180
|
await commandAgentRegister(context, subFlags, capabilities);
|
|
147
181
|
return;
|
|
182
|
+
case 'developer bootstrap':
|
|
183
|
+
await commandDeveloperBootstrap(context, subFlags, capabilities);
|
|
184
|
+
return;
|
|
148
185
|
default:
|
|
149
186
|
break;
|
|
150
187
|
}
|
|
@@ -179,6 +216,9 @@ async function main() {
|
|
|
179
216
|
case 'run':
|
|
180
217
|
await commandRun(context, subFlags, capabilities);
|
|
181
218
|
return;
|
|
219
|
+
case 'self-exec':
|
|
220
|
+
await commandSelfExec(context, subFlags, capabilities);
|
|
221
|
+
return;
|
|
182
222
|
case 'intent-status':
|
|
183
223
|
await commandIntentStatus(context, subFlags, capabilities);
|
|
184
224
|
return;
|
|
@@ -253,6 +293,162 @@ function parseJsonFlag(flags, key) {
|
|
|
253
293
|
}
|
|
254
294
|
}
|
|
255
295
|
|
|
296
|
+
function shouldApplySwapAmountContract(actionName) {
|
|
297
|
+
const action = String(actionName || '').trim().toLowerCase();
|
|
298
|
+
return action === 'swap' || /(^|:|-)swap$/.test(action);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function createCliError(code, message, payload = {}) {
|
|
302
|
+
const error = new Error(message);
|
|
303
|
+
error.payload = {
|
|
304
|
+
error: code,
|
|
305
|
+
blockedReason: code,
|
|
306
|
+
blockedStage: 'client_validation',
|
|
307
|
+
...payload,
|
|
308
|
+
};
|
|
309
|
+
return error;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function normalizeIntegerRawAmount(input, code = 'AMOUNT_UNIT_INVALID') {
|
|
313
|
+
const raw = String(input ?? '').trim();
|
|
314
|
+
if (!/^\d+$/.test(raw)) {
|
|
315
|
+
throw createCliError(code, 'amountInRaw must be an integer base-unit string (example: "1000000").');
|
|
316
|
+
}
|
|
317
|
+
return raw;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function decimalToRawUnits(amountHuman, decimals) {
|
|
321
|
+
const s = String(amountHuman ?? '').trim();
|
|
322
|
+
if (!/^\d+(\.\d+)?$/.test(s)) {
|
|
323
|
+
throw createCliError('AMOUNT_UNIT_INVALID', 'amountInHuman must be a decimal string (example: "100.5").');
|
|
324
|
+
}
|
|
325
|
+
const [whole, frac = ''] = s.split('.');
|
|
326
|
+
if (decimals === 0) {
|
|
327
|
+
if (frac && /[1-9]/.test(frac)) {
|
|
328
|
+
throw createCliError(
|
|
329
|
+
'AMOUNT_PRECISION_EXCESS',
|
|
330
|
+
'amountInHuman has fractional digits but tokenInDecimals is 0 (example: use "2" not "2.5").'
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
} else if (frac.length > decimals && /[1-9]/.test(frac.slice(decimals))) {
|
|
334
|
+
throw createCliError(
|
|
335
|
+
'AMOUNT_PRECISION_EXCESS',
|
|
336
|
+
`amountInHuman has more than ${decimals} fractional digit(s) for tokenInDecimals; round or use amountInRaw.`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const fracNorm = (frac + '0'.repeat(decimals)).slice(0, decimals);
|
|
340
|
+
const wholePart = BigInt(whole || '0') * 10n ** BigInt(decimals);
|
|
341
|
+
const fracPart = BigInt(fracNorm || '0');
|
|
342
|
+
return (wholePart + fracPart).toString();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function normalizeSwapAmountContract(params) {
|
|
346
|
+
const hasRaw = params.amountInRaw !== undefined && params.amountInRaw !== null && String(params.amountInRaw).trim() !== '';
|
|
347
|
+
const hasHuman = params.amountInHuman !== undefined && params.amountInHuman !== null && String(params.amountInHuman).trim() !== '';
|
|
348
|
+
const hasLegacy = params.amountIn !== undefined && params.amountIn !== null && String(params.amountIn).trim() !== '';
|
|
349
|
+
const populated = [hasRaw, hasHuman, hasLegacy].filter(Boolean).length;
|
|
350
|
+
if (populated === 0) {
|
|
351
|
+
throw createCliError(
|
|
352
|
+
'AMOUNT_UNIT_MISSING',
|
|
353
|
+
'Missing swap amount: provide one of amountInRaw, amountInHuman, or legacy amountIn.'
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (populated > 1) {
|
|
357
|
+
throw createCliError(
|
|
358
|
+
'AMOUNT_UNIT_AMBIGUOUS',
|
|
359
|
+
'Provide exactly one of amountInRaw, amountInHuman, or legacy amountIn.'
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (hasRaw) {
|
|
364
|
+
const amountInRaw = normalizeIntegerRawAmount(params.amountInRaw);
|
|
365
|
+
return {
|
|
366
|
+
params: {
|
|
367
|
+
...params,
|
|
368
|
+
amountInRaw,
|
|
369
|
+
amountIn: amountInRaw,
|
|
370
|
+
},
|
|
371
|
+
swapInput: {
|
|
372
|
+
amountInRaw,
|
|
373
|
+
amountInHuman: null,
|
|
374
|
+
tokenInDecimals: null,
|
|
375
|
+
unitSource: 'raw',
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (hasLegacy) {
|
|
381
|
+
const amountInRaw = normalizeIntegerRawAmount(params.amountIn, 'AMOUNT_UNIT_INVALID');
|
|
382
|
+
return {
|
|
383
|
+
params: {
|
|
384
|
+
...params,
|
|
385
|
+
amountInRaw,
|
|
386
|
+
amountIn: amountInRaw,
|
|
387
|
+
},
|
|
388
|
+
swapInput: {
|
|
389
|
+
amountInRaw,
|
|
390
|
+
amountInHuman: null,
|
|
391
|
+
tokenInDecimals: null,
|
|
392
|
+
unitSource: 'legacy_raw',
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const hasDecimals =
|
|
398
|
+
params.tokenInDecimals !== undefined &&
|
|
399
|
+
params.tokenInDecimals !== null &&
|
|
400
|
+
String(params.tokenInDecimals).trim() !== '';
|
|
401
|
+
if (!hasDecimals) {
|
|
402
|
+
throw createCliError(
|
|
403
|
+
'AMOUNT_UNIT_DECIMALS_REQUIRED',
|
|
404
|
+
'amountInHuman requires tokenInDecimals (integer 0-36).'
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const decimals = Number(params.tokenInDecimals);
|
|
408
|
+
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 36) {
|
|
409
|
+
throw createCliError(
|
|
410
|
+
'AMOUNT_UNIT_DECIMALS_REQUIRED',
|
|
411
|
+
'amountInHuman requires tokenInDecimals (integer 0-36).'
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
const amountInHuman = String(params.amountInHuman).trim();
|
|
415
|
+
const amountInRaw = decimalToRawUnits(amountInHuman, decimals);
|
|
416
|
+
return {
|
|
417
|
+
params: {
|
|
418
|
+
...params,
|
|
419
|
+
amountInHuman,
|
|
420
|
+
amountInRaw,
|
|
421
|
+
amountIn: amountInRaw,
|
|
422
|
+
tokenInDecimals: decimals,
|
|
423
|
+
},
|
|
424
|
+
swapInput: {
|
|
425
|
+
amountInRaw,
|
|
426
|
+
amountInHuman,
|
|
427
|
+
tokenInDecimals: decimals,
|
|
428
|
+
unitSource: 'human',
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function prepareActionParamsForFlags(flags, resolvedAction) {
|
|
434
|
+
const parsedParams = parseJsonFlag(flags, 'params');
|
|
435
|
+
if (!shouldApplySwapAmountContract(resolvedAction)) {
|
|
436
|
+
flags.__preparedParams = parsedParams;
|
|
437
|
+
flags.__swapInput = null;
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const normalized = normalizeSwapAmountContract(parsedParams);
|
|
441
|
+
flags.__preparedParams = normalized.params;
|
|
442
|
+
flags.__swapInput = normalized.swapInput;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function getPreparedParams(flags) {
|
|
446
|
+
if (flags.__preparedParams && typeof flags.__preparedParams === 'object') {
|
|
447
|
+
return flags.__preparedParams;
|
|
448
|
+
}
|
|
449
|
+
return parseJsonFlag(flags, 'params');
|
|
450
|
+
}
|
|
451
|
+
|
|
256
452
|
async function commandCatalogsList(ctx, flags, capabilities) {
|
|
257
453
|
const response = await apiGet(ctx, '/api/v1/agent/actions/catalog');
|
|
258
454
|
const actions = extractActions(response);
|
|
@@ -303,6 +499,7 @@ async function commandActions(ctx, flags, capabilities) {
|
|
|
303
499
|
|
|
304
500
|
async function commandPreflight(ctx, flags, capabilities) {
|
|
305
501
|
const resolved = await resolveActionSelection(ctx, flags);
|
|
502
|
+
prepareActionParamsForFlags(flags, resolved.resolvedAction);
|
|
306
503
|
const runMode = getEffectiveRunMode(ctx, flags);
|
|
307
504
|
enforceChainCompatibility(capabilities, flags, runMode);
|
|
308
505
|
const backendResult = await runPreflightByMode(runMode, ctx, flags, resolved.resolvedAction);
|
|
@@ -319,6 +516,7 @@ async function commandPreflight(ctx, flags, capabilities) {
|
|
|
319
516
|
resolvedAction: resolved.resolvedAction,
|
|
320
517
|
runMode,
|
|
321
518
|
profile: ctx.profile || null,
|
|
519
|
+
swapInput: flags.__swapInput || null,
|
|
322
520
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
323
521
|
},
|
|
324
522
|
});
|
|
@@ -327,6 +525,7 @@ async function commandPreflight(ctx, flags, capabilities) {
|
|
|
327
525
|
|
|
328
526
|
async function commandRun(ctx, flags, capabilities) {
|
|
329
527
|
const resolved = await resolveActionSelection(ctx, flags);
|
|
528
|
+
prepareActionParamsForFlags(flags, resolved.resolvedAction);
|
|
330
529
|
const runMode = getEffectiveRunMode(ctx, flags);
|
|
331
530
|
enforceChainCompatibility(capabilities, flags, runMode);
|
|
332
531
|
const skipPreflight = Boolean(flags.skipPreflight);
|
|
@@ -347,6 +546,7 @@ async function commandRun(ctx, flags, capabilities) {
|
|
|
347
546
|
runMode,
|
|
348
547
|
profile: ctx.profile || null,
|
|
349
548
|
skippedPreflight: skipPreflight,
|
|
549
|
+
swapInput: flags.__swapInput || null,
|
|
350
550
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
351
551
|
},
|
|
352
552
|
});
|
|
@@ -388,32 +588,145 @@ async function commandCreditsBalance(ctx, flags, capabilities) {
|
|
|
388
588
|
});
|
|
389
589
|
}
|
|
390
590
|
|
|
391
|
-
async function
|
|
591
|
+
async function commandDelegationCreate(ctx, flags, capabilities) {
|
|
592
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
593
|
+
flags,
|
|
594
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation create'
|
|
595
|
+
);
|
|
596
|
+
let sessionAccountAddress = String(flags.sessionAccountAddress || '').trim();
|
|
597
|
+
if (!sessionAccountAddress) {
|
|
598
|
+
try {
|
|
599
|
+
const executorAddressResponse = await apiGet(ctx, '/api/v1/agent/delegation/executor-address', {
|
|
600
|
+
...authHeaders,
|
|
601
|
+
});
|
|
602
|
+
sessionAccountAddress = String(executorAddressResponse?.data?.executorAddress || '').trim();
|
|
603
|
+
} catch (error) {
|
|
604
|
+
if (ctx.verbose) {
|
|
605
|
+
process.stderr.write(
|
|
606
|
+
`delegation create: executor-address discovery failed, falling back to --session-account-address (${error?.message || 'unknown error'})\n`
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
392
611
|
const payload = {
|
|
393
|
-
userDid: requireFlag(flags, 'userDid', 'Missing --user-did'),
|
|
394
612
|
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
395
|
-
|
|
613
|
+
ownerAddress: String(flags.ownerAddress || flags.walletAddress || '').trim() || requireFlag(flags, 'ownerAddress', 'Missing --owner-address'),
|
|
614
|
+
sessionAccountAddress: sessionAccountAddress || requireFlag(flags, 'sessionAccountAddress', 'Missing --session-account-address'),
|
|
396
615
|
chainId: Number(flags.chainId || 8453),
|
|
397
|
-
|
|
616
|
+
tokenAddress: requireFlag(flags, 'tokenAddress', 'Missing --token-address'),
|
|
617
|
+
maxUsdPerDay: Number(requireFlag(flags, 'maxUsdPerDay', 'Missing --max-usd-per-day')),
|
|
618
|
+
periodDuration: Number(flags.periodDuration || 86_400),
|
|
619
|
+
expirySeconds: Number(flags.expirySeconds || 86_400),
|
|
398
620
|
};
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
621
|
+
const response = await apiPost(ctx, '/api/v1/agent/delegation/create', payload, {
|
|
622
|
+
...authHeaders,
|
|
623
|
+
});
|
|
624
|
+
const success = Boolean(response?.success !== false);
|
|
625
|
+
printEnvelope({
|
|
626
|
+
success,
|
|
627
|
+
code: success ? 'DELEGATION_CREATED' : 'DELEGATION_CREATE_FAILED',
|
|
628
|
+
message: response?.message || (success ? 'Delegation payload created' : 'Delegation create failed'),
|
|
629
|
+
data: response,
|
|
630
|
+
meta: {
|
|
631
|
+
apiBase: ctx.apiBase,
|
|
632
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
}
|
|
407
636
|
|
|
637
|
+
async function commandDelegationInitCustodial(ctx, flags, capabilities) {
|
|
638
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
639
|
+
flags,
|
|
640
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation init-custodial'
|
|
641
|
+
);
|
|
642
|
+
const payload = {
|
|
643
|
+
chainId: Number(flags.chainId || 8453),
|
|
644
|
+
};
|
|
408
645
|
const response = await requestWithRouteFallback(ctx, {
|
|
409
646
|
method: 'POST',
|
|
410
|
-
routes: ['/api/v1/agent/delegation/
|
|
647
|
+
routes: ['/api/v1/agent/delegation/init-custodial'],
|
|
411
648
|
body: payload,
|
|
649
|
+
headers: {
|
|
650
|
+
...authHeaders,
|
|
651
|
+
},
|
|
412
652
|
});
|
|
653
|
+
const success = Boolean(response?.success !== false);
|
|
413
654
|
printEnvelope({
|
|
414
|
-
success
|
|
415
|
-
code:
|
|
416
|
-
message: response?.message || '
|
|
655
|
+
success,
|
|
656
|
+
code: success ? 'DELEGATION_CUSTODIAL_INITIALIZED' : 'DELEGATION_CUSTODIAL_INIT_FAILED',
|
|
657
|
+
message: response?.message || (success ? 'Custodial wallet initialized' : 'Custodial wallet init failed'),
|
|
658
|
+
data: response,
|
|
659
|
+
meta: {
|
|
660
|
+
apiBase: ctx.apiBase,
|
|
661
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async function commandDelegationCustodialGrant(ctx, flags, capabilities) {
|
|
667
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
668
|
+
flags,
|
|
669
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation custodial-grant'
|
|
670
|
+
);
|
|
671
|
+
const payload = {
|
|
672
|
+
chainId: Number(flags.chainId || 8453),
|
|
673
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
674
|
+
tokenAddress: requireFlag(flags, 'tokenAddress', 'Missing --token-address'),
|
|
675
|
+
maxUsdPerDay: Number(requireFlag(flags, 'maxUsdPerDay', 'Missing --max-usd-per-day')),
|
|
676
|
+
periodDuration: Number(flags.periodDuration || 86_400),
|
|
677
|
+
expirySeconds: Number(flags.expirySeconds || 86_400),
|
|
678
|
+
};
|
|
679
|
+
const response = await requestWithRouteFallback(ctx, {
|
|
680
|
+
method: 'POST',
|
|
681
|
+
routes: ['/api/v1/agent/delegation/custodial-grant'],
|
|
682
|
+
body: payload,
|
|
683
|
+
headers: {
|
|
684
|
+
...authHeaders,
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
const success = Boolean(response?.success !== false);
|
|
688
|
+
printEnvelope({
|
|
689
|
+
success,
|
|
690
|
+
code: success ? 'DELEGATION_CUSTODIAL_GRANTED' : 'DELEGATION_CUSTODIAL_GRANT_FAILED',
|
|
691
|
+
message: response?.message || (success ? 'Custodial delegation granted' : 'Custodial delegation grant failed'),
|
|
692
|
+
data: response,
|
|
693
|
+
meta: {
|
|
694
|
+
apiBase: ctx.apiBase,
|
|
695
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
696
|
+
},
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async function commandDelegationActivate(ctx, flags, capabilities) {
|
|
701
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
702
|
+
flags,
|
|
703
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation activate'
|
|
704
|
+
);
|
|
705
|
+
const payload = {
|
|
706
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
707
|
+
walletAddress: requireFlag(flags, 'walletAddress', 'Missing --wallet-address'),
|
|
708
|
+
sessionAccountAddress: requireFlag(flags, 'sessionAccountAddress', 'Missing --session-account-address'),
|
|
709
|
+
sessionAccountType: String(flags.sessionAccountType || 'smart').trim(),
|
|
710
|
+
chainId: Number(flags.chainId || 8453),
|
|
711
|
+
delegationManager: requireFlag(flags, 'delegationManager', 'Missing --delegation-manager'),
|
|
712
|
+
signature: requireFlag(flags, 'signature', 'Missing --signature'),
|
|
713
|
+
};
|
|
714
|
+
const delegation = parseJsonFlag(flags, 'delegation');
|
|
715
|
+
payload.delegation = delegation;
|
|
716
|
+
if (flags.permissionsExpiry) payload.permissionsExpiry = String(flags.permissionsExpiry).trim();
|
|
717
|
+
if (flags.permissionsContext) payload.permissionsContext = String(flags.permissionsContext).trim();
|
|
718
|
+
if (flags.permission) payload.permission = parseJsonFlag(flags, 'permission');
|
|
719
|
+
if (flags.permissionType) payload.permissionType = String(flags.permissionType).trim();
|
|
720
|
+
if (flags.permissionData) payload.permissionData = parseJsonFlag(flags, 'permissionData');
|
|
721
|
+
if (flags.modelAConstraints) payload.modelAConstraints = parseJsonFlag(flags, 'modelAConstraints');
|
|
722
|
+
const response = await apiPost(ctx, '/api/v1/agent/delegation/activate', payload, {
|
|
723
|
+
...authHeaders,
|
|
724
|
+
});
|
|
725
|
+
const success = Boolean(response?.success !== false);
|
|
726
|
+
printEnvelope({
|
|
727
|
+
success,
|
|
728
|
+
code: success ? 'DELEGATION_ACTIVATED' : 'DELEGATION_ACTIVATE_FAILED',
|
|
729
|
+
message: response?.message || (success ? 'Delegation activated' : 'Delegation activate failed'),
|
|
417
730
|
data: response,
|
|
418
731
|
meta: {
|
|
419
732
|
apiBase: ctx.apiBase,
|
|
@@ -423,7 +736,10 @@ async function commandDelegationUpsert(ctx, flags, capabilities) {
|
|
|
423
736
|
}
|
|
424
737
|
|
|
425
738
|
async function commandDelegationStatus(ctx, flags, capabilities) {
|
|
426
|
-
const
|
|
739
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
740
|
+
flags,
|
|
741
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation status'
|
|
742
|
+
);
|
|
427
743
|
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
428
744
|
const chainId = Number(flags.chainId || 8453);
|
|
429
745
|
const verify = Boolean(flags.verify);
|
|
@@ -431,7 +747,7 @@ async function commandDelegationStatus(ctx, flags, capabilities) {
|
|
|
431
747
|
? `/api/v1/agent/delegation/verify?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`
|
|
432
748
|
: `/api/v1/agent/delegation/check?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`;
|
|
433
749
|
const response = await apiGet(ctx, route, {
|
|
434
|
-
|
|
750
|
+
...authHeaders,
|
|
435
751
|
});
|
|
436
752
|
const normalized = normalizeDelegationStatus(response);
|
|
437
753
|
printEnvelope({
|
|
@@ -448,6 +764,129 @@ async function commandDelegationStatus(ctx, flags, capabilities) {
|
|
|
448
764
|
});
|
|
449
765
|
}
|
|
450
766
|
|
|
767
|
+
async function commandDelegationInspect(ctx, flags, capabilities) {
|
|
768
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
769
|
+
flags,
|
|
770
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation inspect'
|
|
771
|
+
);
|
|
772
|
+
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
773
|
+
const chainId = Number(flags.chainId || 8453);
|
|
774
|
+
const includeOnchain = Boolean(flags.verify || flags.includeOnchain || flags.onchain);
|
|
775
|
+
const checkRoute = `/api/v1/agent/delegation/check?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`;
|
|
776
|
+
const checkResponse = await apiGet(ctx, checkRoute, {
|
|
777
|
+
...authHeaders,
|
|
778
|
+
});
|
|
779
|
+
const checkNormalized = normalizeDelegationStatus(checkResponse);
|
|
780
|
+
|
|
781
|
+
let verifyNormalized = null;
|
|
782
|
+
if (includeOnchain) {
|
|
783
|
+
const verifyRoute = `/api/v1/agent/delegation/verify?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`;
|
|
784
|
+
const verifyResponse = await apiGet(ctx, verifyRoute, {
|
|
785
|
+
...authHeaders,
|
|
786
|
+
});
|
|
787
|
+
verifyNormalized = normalizeDelegationStatus(verifyResponse);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
printEnvelope({
|
|
791
|
+
success: true,
|
|
792
|
+
code: 'DELEGATION_INSPECT_OK',
|
|
793
|
+
message: includeOnchain ? 'Delegation inspect loaded (with onchain verification)' : 'Delegation inspect loaded',
|
|
794
|
+
data: {
|
|
795
|
+
check: checkNormalized,
|
|
796
|
+
verify: verifyNormalized,
|
|
797
|
+
state: verifyNormalized?.state || checkNormalized.state,
|
|
798
|
+
rawStatus: verifyNormalized?.rawStatus || checkNormalized.rawStatus,
|
|
799
|
+
},
|
|
800
|
+
meta: {
|
|
801
|
+
apiBase: ctx.apiBase,
|
|
802
|
+
includeOnchain,
|
|
803
|
+
governanceState: verifyNormalized?.state || checkNormalized.state || null,
|
|
804
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
async function commandDelegationRevoke(ctx, flags, capabilities) {
|
|
810
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
811
|
+
flags,
|
|
812
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation revoke'
|
|
813
|
+
);
|
|
814
|
+
const payload = {
|
|
815
|
+
agentKey: requireFlag(flags, 'agentKey', 'Missing --agent-key'),
|
|
816
|
+
reason: String(flags.reason || 'user_revoked').trim(),
|
|
817
|
+
chainId: Number(flags.chainId || 8453),
|
|
818
|
+
};
|
|
819
|
+
const response = await requestWithRouteFallback(ctx, {
|
|
820
|
+
method: 'POST',
|
|
821
|
+
routes: ['/api/v1/agent/delegation/revoke'],
|
|
822
|
+
body: payload,
|
|
823
|
+
headers: {
|
|
824
|
+
...authHeaders,
|
|
825
|
+
},
|
|
826
|
+
});
|
|
827
|
+
const success = Boolean(response?.success !== false);
|
|
828
|
+
printEnvelope({
|
|
829
|
+
success,
|
|
830
|
+
code: success ? 'DELEGATION_REVOKED' : 'DELEGATION_REVOKE_FAILED',
|
|
831
|
+
message: response?.message || (success ? 'Delegation revoked' : 'Delegation revoke failed'),
|
|
832
|
+
data: response,
|
|
833
|
+
meta: {
|
|
834
|
+
apiBase: ctx.apiBase,
|
|
835
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
836
|
+
},
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function commandDelegationBalance(ctx, flags, capabilities) {
|
|
841
|
+
const authHeaders = buildOwnerAuthHeaders(
|
|
842
|
+
flags,
|
|
843
|
+
'Missing --auth-token/--owner-session-token (or THIRDFY_AUTH_TOKEN/THIRDFY_OWNER_SESSION_TOKEN) for delegation balance'
|
|
844
|
+
);
|
|
845
|
+
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
846
|
+
const chainId = Number(flags.chainId || 8453);
|
|
847
|
+
const route = `/api/v1/agent/delegation/verify?agentKey=${encodeURIComponent(agentKey)}&chainId=${encodeURIComponent(String(chainId))}`;
|
|
848
|
+
const response = await apiGet(ctx, route, {
|
|
849
|
+
...authHeaders,
|
|
850
|
+
});
|
|
851
|
+
const normalized = normalizeDelegationStatus(response);
|
|
852
|
+
const verifyData = normalized?.data?.onChainVerification || {};
|
|
853
|
+
printEnvelope({
|
|
854
|
+
success: true,
|
|
855
|
+
code: 'DELEGATION_BALANCE_OK',
|
|
856
|
+
message: 'Delegation allowance balance loaded',
|
|
857
|
+
data: {
|
|
858
|
+
state: normalized.state,
|
|
859
|
+
rawStatus: normalized.rawStatus,
|
|
860
|
+
delegatedWalletAddress: normalized?.data?.delegatedWalletAddress || null,
|
|
861
|
+
periodAvailableUsd: verifyData?.periodAvailableUsd ?? null,
|
|
862
|
+
periodAvailableBaseUnits: verifyData?.periodAvailableBaseUnits ?? null,
|
|
863
|
+
verified: verifyData?.verified ?? null,
|
|
864
|
+
isRevoked: verifyData?.isRevoked ?? null,
|
|
865
|
+
onChainVerification: verifyData || null,
|
|
866
|
+
},
|
|
867
|
+
meta: {
|
|
868
|
+
apiBase: ctx.apiBase,
|
|
869
|
+
governanceState: normalized.state || null,
|
|
870
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
871
|
+
},
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
async function commandDelegationRedeem(ctx, flags, capabilities) {
|
|
876
|
+
const selectedRunMode = getEffectiveRunMode(ctx, flags);
|
|
877
|
+
if (!['thirdfy', 'hybrid'].includes(selectedRunMode)) {
|
|
878
|
+
throw createCliError('DELEGATION_REDEEM_MODE_INVALID', 'delegation redeem supports only --run-mode thirdfy|hybrid');
|
|
879
|
+
}
|
|
880
|
+
const delegatedFlags = {
|
|
881
|
+
...flags,
|
|
882
|
+
runMode: selectedRunMode,
|
|
883
|
+
};
|
|
884
|
+
if (!delegatedFlags.estimatedAmountUsd) {
|
|
885
|
+
delegatedFlags.estimatedAmountUsd = '5';
|
|
886
|
+
}
|
|
887
|
+
await commandRun(ctx, delegatedFlags, capabilities);
|
|
888
|
+
}
|
|
889
|
+
|
|
451
890
|
async function commandCredentialsUpsert(ctx, flags, capabilities) {
|
|
452
891
|
const authToken = getAuthToken(flags, 'Missing --auth-token (or THIRDFY_AUTH_TOKEN) for credentials upsert');
|
|
453
892
|
const payload = {
|
|
@@ -540,11 +979,83 @@ async function commandAgentRegister(ctx, flags, capabilities) {
|
|
|
540
979
|
? '/api/v1/agent/onboarding/register-with-wallet'
|
|
541
980
|
: '/api/v1/agent/onboarding/register-with-privy';
|
|
542
981
|
const response = await apiPost(ctx, route, payload, ownerHeaders);
|
|
982
|
+
if (!flags.__suppressOutput) {
|
|
983
|
+
printEnvelope({
|
|
984
|
+
success: true,
|
|
985
|
+
code: 'AGENT_REGISTERED',
|
|
986
|
+
message: 'Agent registered',
|
|
987
|
+
data: response,
|
|
988
|
+
meta: {
|
|
989
|
+
apiBase: ctx.apiBase,
|
|
990
|
+
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
991
|
+
},
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
return response;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async function commandDeveloperBootstrap(ctx, flags, capabilities) {
|
|
998
|
+
const agentKey = requireFlag(flags, 'agentKey', 'Missing --agent-key');
|
|
999
|
+
const name = requireFlag(flags, 'name', 'Missing --name');
|
|
1000
|
+
let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
1001
|
+
let usedWalletFlow = false;
|
|
1002
|
+
const challengeId = String(flags.challengeId || '').trim();
|
|
1003
|
+
const signature = String(flags.signature || '').trim();
|
|
1004
|
+
|
|
1005
|
+
if (!ownerSessionToken && signature) {
|
|
1006
|
+
if (!challengeId) {
|
|
1007
|
+
throw new Error('Missing --challenge-id for wallet bootstrap. Run `agent auth challenge` first.');
|
|
1008
|
+
}
|
|
1009
|
+
const verify = await apiPost(ctx, '/api/v1/agent/onboarding/wallet/verify', {
|
|
1010
|
+
challengeId,
|
|
1011
|
+
signature,
|
|
1012
|
+
agentKey,
|
|
1013
|
+
...(flags.walletAddress ? { walletAddress: String(flags.walletAddress).trim() } : {}),
|
|
1014
|
+
});
|
|
1015
|
+
ownerSessionToken = String(verify?.ownerSessionToken || verify?.data?.ownerSessionToken || '').trim();
|
|
1016
|
+
usedWalletFlow = true;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const registerFlags = {
|
|
1020
|
+
...flags,
|
|
1021
|
+
agentKey,
|
|
1022
|
+
name,
|
|
1023
|
+
...(ownerSessionToken ? { ownerSessionToken } : {}),
|
|
1024
|
+
};
|
|
1025
|
+
const registerResponse = await commandAgentRegister(
|
|
1026
|
+
ctx,
|
|
1027
|
+
{ ...registerFlags, __suppressOutput: true },
|
|
1028
|
+
capabilities
|
|
1029
|
+
);
|
|
1030
|
+
|
|
1031
|
+
const config = loadProfileConfig();
|
|
1032
|
+
const next = {
|
|
1033
|
+
...config,
|
|
1034
|
+
auth: {
|
|
1035
|
+
...(config.auth && typeof config.auth === 'object' ? config.auth : {}),
|
|
1036
|
+
...(ownerSessionToken ? { ownerSessionToken } : {}),
|
|
1037
|
+
lastLoginAt: new Date().toISOString(),
|
|
1038
|
+
source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
|
|
1039
|
+
},
|
|
1040
|
+
developer: {
|
|
1041
|
+
...(config.developer && typeof config.developer === 'object' ? config.developer : {}),
|
|
1042
|
+
agentKey,
|
|
1043
|
+
bootstrapCompletedAt: new Date().toISOString(),
|
|
1044
|
+
},
|
|
1045
|
+
};
|
|
1046
|
+
persistProfileConfig(next);
|
|
543
1047
|
printEnvelope({
|
|
544
1048
|
success: true,
|
|
545
|
-
code: '
|
|
546
|
-
message: '
|
|
547
|
-
data:
|
|
1049
|
+
code: 'DEVELOPER_BOOTSTRAP_OK',
|
|
1050
|
+
message: 'Developer bootstrap completed',
|
|
1051
|
+
data: {
|
|
1052
|
+
registration: registerResponse,
|
|
1053
|
+
persisted: {
|
|
1054
|
+
ownerSessionTokenSaved: Boolean(ownerSessionToken),
|
|
1055
|
+
source: usedWalletFlow ? 'wallet-verify-bootstrap' : 'bootstrap',
|
|
1056
|
+
configPath: getProfileConfigPath(),
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
548
1059
|
meta: {
|
|
549
1060
|
apiBase: ctx.apiBase,
|
|
550
1061
|
capabilitiesVersion: capabilities?.contractVersion || null,
|
|
@@ -659,10 +1170,140 @@ async function commandPrompt(ctx, flags, capabilities, promptTokens) {
|
|
|
659
1170
|
});
|
|
660
1171
|
}
|
|
661
1172
|
|
|
1173
|
+
async function commandSelfExec(ctx, flags, capabilities) {
|
|
1174
|
+
const resolved = await resolveActionSelection(ctx, flags);
|
|
1175
|
+
prepareActionParamsForFlags(flags, resolved.resolvedAction);
|
|
1176
|
+
enforceChainCompatibility(capabilities, flags, 'self');
|
|
1177
|
+
const selfResult = await executeSelfRun(ctx, flags, resolved, {
|
|
1178
|
+
skipPreflight: Boolean(flags.skipPreflight),
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
if (!selfResult?.success) {
|
|
1182
|
+
const blocked = Boolean(selfResult?.preflightBlocked);
|
|
1183
|
+
printEnvelope({
|
|
1184
|
+
success: false,
|
|
1185
|
+
code: blocked ? 'PREFLIGHT_BLOCKED' : 'SELF_EXECUTION_FAILED',
|
|
1186
|
+
message: blocked ? 'Preflight blocked self execution' : 'Self execution preparation failed',
|
|
1187
|
+
data: selfResult || {},
|
|
1188
|
+
meta: {
|
|
1189
|
+
apiBase: ctx.apiBase,
|
|
1190
|
+
runMode: 'self',
|
|
1191
|
+
resolvedAction: resolved.resolvedAction,
|
|
1192
|
+
},
|
|
1193
|
+
});
|
|
1194
|
+
process.exit(1);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const txd = resolveUnsignedTx(selfResult);
|
|
1198
|
+
if (!txd?.to || !txd?.data) {
|
|
1199
|
+
printEnvelope({
|
|
1200
|
+
success: false,
|
|
1201
|
+
code: 'SELF_UNSIGNED_TX_MISSING',
|
|
1202
|
+
message: 'Self execution did not return a valid unsigned transaction',
|
|
1203
|
+
data: selfResult || {},
|
|
1204
|
+
meta: {
|
|
1205
|
+
apiBase: ctx.apiBase,
|
|
1206
|
+
runMode: 'self',
|
|
1207
|
+
resolvedAction: resolved.resolvedAction,
|
|
1208
|
+
},
|
|
1209
|
+
});
|
|
1210
|
+
process.exit(1);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const chainId = Number(txd.chainId || flags.chainId || 8453);
|
|
1214
|
+
const walletAddress = String(flags.walletAddress || process.env.MARKET_MAKER_WALLET_ADDRESS || '').trim();
|
|
1215
|
+
const owsWalletName = String(flags.owsWalletName || process.env.OWS_WALLET_NAME || '').trim();
|
|
1216
|
+
const rpcUrl = String(flags.rpcUrl || process.env.BASE_RPC_URL || process.env.BASE_RPC_HTTP_URL || process.env.RPC_URL || 'https://mainnet.base.org').trim();
|
|
1217
|
+
if (!walletAddress) {
|
|
1218
|
+
throw createCliError('SIGNING_FAILED', 'Missing --wallet-address (or MARKET_MAKER_WALLET_ADDRESS) for self-exec signing.');
|
|
1219
|
+
}
|
|
1220
|
+
if (!owsWalletName) {
|
|
1221
|
+
throw createCliError('SIGNING_FAILED', 'Missing --ows-wallet-name (or OWS_WALLET_NAME) for self-exec signing.');
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const params = getPreparedParams(flags);
|
|
1225
|
+
const effectTokenIn = String(flags.effectTokenIn || params.tokenIn || '').trim();
|
|
1226
|
+
const effectTokenOut = String(flags.effectTokenOut || params.tokenOut || '').trim();
|
|
1227
|
+
|
|
1228
|
+
let balancesBefore = null;
|
|
1229
|
+
if (effectTokenIn && effectTokenOut) {
|
|
1230
|
+
balancesBefore = await readEffectBalances(rpcUrl, walletAddress, effectTokenIn, effectTokenOut);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
let submission;
|
|
1234
|
+
try {
|
|
1235
|
+
submission = await signAndBroadcastWithOws({
|
|
1236
|
+
txd,
|
|
1237
|
+
chainId,
|
|
1238
|
+
walletAddress,
|
|
1239
|
+
owsWalletName,
|
|
1240
|
+
rpcUrl,
|
|
1241
|
+
});
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
throw createCliError('SIGNING_FAILED', error?.message || 'OWS signing failed');
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
let receipt;
|
|
1247
|
+
try {
|
|
1248
|
+
receipt = await submission.sent.wait();
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
throw createCliError('BROADCAST_FAILED', error?.message || 'Broadcast wait failed');
|
|
1251
|
+
}
|
|
1252
|
+
const status = Number(receipt?.status ?? 0);
|
|
1253
|
+
if (status !== 1) {
|
|
1254
|
+
throw createCliError('BROADCAST_FAILED', 'Transaction receipt status is not success', {
|
|
1255
|
+
receiptStatus: receipt?.status ?? null,
|
|
1256
|
+
txHash: submission.sent.hash,
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
let effectCheck = { checked: false, hasEffect: null, inDeltaRaw: null, outDeltaRaw: null };
|
|
1261
|
+
if (balancesBefore) {
|
|
1262
|
+
const balancesAfter = await readEffectBalances(rpcUrl, walletAddress, effectTokenIn, effectTokenOut);
|
|
1263
|
+
const inDeltaRaw = (BigInt(balancesBefore.tokenIn) - BigInt(balancesAfter.tokenIn)).toString();
|
|
1264
|
+
const outDeltaRaw = (BigInt(balancesAfter.tokenOut) - BigInt(balancesBefore.tokenOut)).toString();
|
|
1265
|
+
const hasEffect = BigInt(inDeltaRaw) > 0n || BigInt(outDeltaRaw) > 0n;
|
|
1266
|
+
effectCheck = {
|
|
1267
|
+
checked: true,
|
|
1268
|
+
hasEffect,
|
|
1269
|
+
inDeltaRaw,
|
|
1270
|
+
outDeltaRaw,
|
|
1271
|
+
};
|
|
1272
|
+
if (!hasEffect) {
|
|
1273
|
+
throw createCliError('EFFECT_CHECK_FAILED', 'Transaction mined but did not produce observable token effect.', {
|
|
1274
|
+
txHash: submission.sent.hash,
|
|
1275
|
+
effectCheck,
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
printEnvelope({
|
|
1281
|
+
success: true,
|
|
1282
|
+
code: 'SELF_EXECUTED',
|
|
1283
|
+
message: 'Self-custody execution completed',
|
|
1284
|
+
data: {
|
|
1285
|
+
txHash: submission.sent.hash,
|
|
1286
|
+
chainId,
|
|
1287
|
+
blockNumber: receipt?.blockNumber ?? null,
|
|
1288
|
+
status: receipt?.status ?? null,
|
|
1289
|
+
unsignedTx: txd,
|
|
1290
|
+
effectCheck,
|
|
1291
|
+
},
|
|
1292
|
+
meta: {
|
|
1293
|
+
apiBase: ctx.apiBase,
|
|
1294
|
+
runMode: 'self',
|
|
1295
|
+
resolvedAction: resolved.resolvedAction,
|
|
1296
|
+
swapInput: flags.__swapInput || null,
|
|
1297
|
+
},
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
|
|
662
1301
|
async function commandProfileInit(ctx, flags) {
|
|
1302
|
+
const current = loadProfileConfig();
|
|
663
1303
|
const profile = normalizeProfileName(flags.profile || flags.name || 'personal');
|
|
664
1304
|
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
665
1305
|
persistProfileConfig({
|
|
1306
|
+
...current,
|
|
666
1307
|
profile,
|
|
667
1308
|
runMode,
|
|
668
1309
|
});
|
|
@@ -687,6 +1328,7 @@ async function commandProfileUse(ctx, flags) {
|
|
|
687
1328
|
// `profile use` intentionally re-applies profile defaults unless caller pins a mode.
|
|
688
1329
|
const runMode = normalizeRunMode(flags.runMode || PROFILE_DEFAULTS[profile]);
|
|
689
1330
|
persistProfileConfig({
|
|
1331
|
+
...current,
|
|
690
1332
|
profile,
|
|
691
1333
|
runMode,
|
|
692
1334
|
});
|
|
@@ -723,6 +1365,154 @@ async function commandProfileShow(ctx, _flags) {
|
|
|
723
1365
|
});
|
|
724
1366
|
}
|
|
725
1367
|
|
|
1368
|
+
async function commandConfigSet(ctx, flags) {
|
|
1369
|
+
const key = String(flags.key || '').trim();
|
|
1370
|
+
const value = flags.value;
|
|
1371
|
+
if (!key) {
|
|
1372
|
+
throw new Error('Missing --key for config set');
|
|
1373
|
+
}
|
|
1374
|
+
const canonicalKey = key.toLowerCase();
|
|
1375
|
+
const hasValue = !(value === undefined || String(value).trim() === '');
|
|
1376
|
+
if (canonicalKey === 'profile' && hasValue) {
|
|
1377
|
+
normalizeProfileName(value);
|
|
1378
|
+
}
|
|
1379
|
+
if (canonicalKey === 'runmode' && hasValue) {
|
|
1380
|
+
normalizeRunMode(value);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const current = loadProfileConfig();
|
|
1384
|
+
const next = setNestedConfigValue(current, key, value);
|
|
1385
|
+
persistProfileConfig(next);
|
|
1386
|
+
printEnvelope({
|
|
1387
|
+
success: true,
|
|
1388
|
+
code: 'CONFIG_UPDATED',
|
|
1389
|
+
message: 'Configuration updated',
|
|
1390
|
+
data: {
|
|
1391
|
+
key,
|
|
1392
|
+
value: value === undefined ? null : String(value),
|
|
1393
|
+
configPath: getProfileConfigPath(),
|
|
1394
|
+
},
|
|
1395
|
+
meta: { apiBase: ctx.apiBase },
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
async function commandLogin(ctx, flags) {
|
|
1400
|
+
const current = loadProfileConfig();
|
|
1401
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || '').trim();
|
|
1402
|
+
let ownerSessionToken = String(flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || '').trim();
|
|
1403
|
+
let loginSource = 'token';
|
|
1404
|
+
|
|
1405
|
+
if (!authToken && !ownerSessionToken) {
|
|
1406
|
+
const challengeId = String(flags.challengeId || '').trim();
|
|
1407
|
+
const signature = String(flags.signature || '').trim();
|
|
1408
|
+
const agentKey = String(flags.agentKey || '').trim();
|
|
1409
|
+
if (challengeId && signature && agentKey) {
|
|
1410
|
+
const verifyResponse = await requestWithRouteFallback(ctx, {
|
|
1411
|
+
method: 'POST',
|
|
1412
|
+
routes: ['/api/v1/agent/onboarding/wallet/verify'],
|
|
1413
|
+
body: {
|
|
1414
|
+
challengeId,
|
|
1415
|
+
signature,
|
|
1416
|
+
agentKey,
|
|
1417
|
+
...(flags.walletAddress ? { walletAddress: String(flags.walletAddress).trim() } : {}),
|
|
1418
|
+
},
|
|
1419
|
+
});
|
|
1420
|
+
ownerSessionToken = String(
|
|
1421
|
+
verifyResponse?.ownerSessionToken || verifyResponse?.data?.ownerSessionToken || ''
|
|
1422
|
+
).trim();
|
|
1423
|
+
loginSource = 'wallet-verify';
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
if (!authToken && !ownerSessionToken) {
|
|
1428
|
+
throw new Error(
|
|
1429
|
+
'Missing login credentials: provide --auth-token, --owner-session-token, or challenge verify inputs (--challenge-id/--signature/--agent-key).'
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const next = {
|
|
1434
|
+
...current,
|
|
1435
|
+
auth: {
|
|
1436
|
+
...(current.auth && typeof current.auth === 'object' ? current.auth : {}),
|
|
1437
|
+
lastLoginAt: new Date().toISOString(),
|
|
1438
|
+
source: loginSource,
|
|
1439
|
+
},
|
|
1440
|
+
};
|
|
1441
|
+
if (authToken) {
|
|
1442
|
+
next.auth.authToken = authToken;
|
|
1443
|
+
delete next.auth.ownerSessionToken;
|
|
1444
|
+
} else if (ownerSessionToken) {
|
|
1445
|
+
next.auth.ownerSessionToken = ownerSessionToken;
|
|
1446
|
+
delete next.auth.authToken;
|
|
1447
|
+
}
|
|
1448
|
+
if (flags.agentApiKey) {
|
|
1449
|
+
next.agent = {
|
|
1450
|
+
...(current.agent && typeof current.agent === 'object' ? current.agent : {}),
|
|
1451
|
+
apiKey: String(flags.agentApiKey).trim(),
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
persistProfileConfig(next);
|
|
1455
|
+
printEnvelope({
|
|
1456
|
+
success: true,
|
|
1457
|
+
code: 'LOGIN_OK',
|
|
1458
|
+
message: 'Login credentials stored',
|
|
1459
|
+
data: {
|
|
1460
|
+
hasAuthToken: Boolean(authToken),
|
|
1461
|
+
hasOwnerSessionToken: Boolean(ownerSessionToken),
|
|
1462
|
+
hasAgentApiKey: Boolean(flags.agentApiKey),
|
|
1463
|
+
configPath: getProfileConfigPath(),
|
|
1464
|
+
},
|
|
1465
|
+
meta: { apiBase: ctx.apiBase },
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
async function commandLogout(ctx, flags) {
|
|
1470
|
+
const current = loadProfileConfig();
|
|
1471
|
+
const localOnly = flags.all ? false : true;
|
|
1472
|
+
const auth = current.auth && typeof current.auth === 'object' ? current.auth : {};
|
|
1473
|
+
const ownerSessionToken = String(auth.ownerSessionToken || '').trim();
|
|
1474
|
+
let revokeAttempted = false;
|
|
1475
|
+
let revokeSuccess = false;
|
|
1476
|
+
let revokeError = null;
|
|
1477
|
+
|
|
1478
|
+
if (!localOnly && ownerSessionToken) {
|
|
1479
|
+
revokeAttempted = true;
|
|
1480
|
+
try {
|
|
1481
|
+
await requestWithRouteFallback(ctx, {
|
|
1482
|
+
method: 'POST',
|
|
1483
|
+
routes: ['/api/v1/agent/onboarding/wallet/logout', '/api/v1/agent/onboarding/wallet/revoke-session'],
|
|
1484
|
+
body: {},
|
|
1485
|
+
headers: {
|
|
1486
|
+
'x-owner-session-token': ownerSessionToken,
|
|
1487
|
+
},
|
|
1488
|
+
});
|
|
1489
|
+
revokeSuccess = true;
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
revokeError = error?.message || 'OWNER_SESSION_REVOKE_FAILED';
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
const next = { ...current };
|
|
1496
|
+
delete next.auth;
|
|
1497
|
+
if (flags.clearAgentKey) {
|
|
1498
|
+
delete next.agent;
|
|
1499
|
+
}
|
|
1500
|
+
persistProfileConfig(next);
|
|
1501
|
+
printEnvelope({
|
|
1502
|
+
success: true,
|
|
1503
|
+
code: 'LOGOUT_OK',
|
|
1504
|
+
message: 'Local credentials cleared',
|
|
1505
|
+
data: {
|
|
1506
|
+
localOnly,
|
|
1507
|
+
revokeAttempted,
|
|
1508
|
+
revokeSuccess,
|
|
1509
|
+
revokeError,
|
|
1510
|
+
configPath: getProfileConfigPath(),
|
|
1511
|
+
},
|
|
1512
|
+
meta: { apiBase: ctx.apiBase },
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
|
|
726
1516
|
function resolveProfileState(flags) {
|
|
727
1517
|
const persisted = loadProfileConfig();
|
|
728
1518
|
const profile = normalizeProfileName(flags.profile || process.env.THIRDFY_PROFILE || persisted.profile || 'personal');
|
|
@@ -771,8 +1561,60 @@ function loadProfileConfig() {
|
|
|
771
1561
|
function persistProfileConfig(config) {
|
|
772
1562
|
const configPath = getProfileConfigPath();
|
|
773
1563
|
const dirPath = path.dirname(configPath);
|
|
774
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
775
|
-
|
|
1564
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
1565
|
+
const payload = `${JSON.stringify(config, null, 2)}\n`;
|
|
1566
|
+
fs.writeFileSync(configPath, payload, { encoding: 'utf8', mode: 0o600 });
|
|
1567
|
+
try {
|
|
1568
|
+
fs.chmodSync(configPath, 0o600);
|
|
1569
|
+
} catch {
|
|
1570
|
+
/* chmod unsupported or restricted (e.g. some Windows FS) */
|
|
1571
|
+
}
|
|
1572
|
+
try {
|
|
1573
|
+
fs.chmodSync(dirPath, 0o700);
|
|
1574
|
+
} catch {
|
|
1575
|
+
/* ignore */
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function clonePlainConfigTree(input) {
|
|
1580
|
+
const obj = input && typeof input === 'object' && !Array.isArray(input) ? input : {};
|
|
1581
|
+
try {
|
|
1582
|
+
return structuredClone(obj);
|
|
1583
|
+
} catch {
|
|
1584
|
+
return JSON.parse(JSON.stringify(obj));
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function setNestedConfigValue(config, keyPath, value) {
|
|
1589
|
+
const next = clonePlainConfigTree(config || {});
|
|
1590
|
+
const parts = String(keyPath || '')
|
|
1591
|
+
.split('.')
|
|
1592
|
+
.map((v) => v.trim())
|
|
1593
|
+
.filter(Boolean);
|
|
1594
|
+
const blocked = new Set(['__proto__', 'prototype', 'constructor']);
|
|
1595
|
+
if (parts.some((part) => blocked.has(part))) {
|
|
1596
|
+
throw createCliError(
|
|
1597
|
+
'CONFIG_KEY_INVALID',
|
|
1598
|
+
'Invalid config key path: reserved segments (__proto__, prototype, constructor) are not allowed.'
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
if (!parts.length) return next;
|
|
1602
|
+
let cursor = next;
|
|
1603
|
+
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
1604
|
+
const key = parts[i];
|
|
1605
|
+
const existing = cursor[key];
|
|
1606
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
1607
|
+
cursor[key] = {};
|
|
1608
|
+
}
|
|
1609
|
+
cursor = cursor[key];
|
|
1610
|
+
}
|
|
1611
|
+
const finalKey = parts[parts.length - 1];
|
|
1612
|
+
if (value === undefined || String(value).trim() === '') {
|
|
1613
|
+
delete cursor[finalKey];
|
|
1614
|
+
} else {
|
|
1615
|
+
cursor[finalKey] = String(value);
|
|
1616
|
+
}
|
|
1617
|
+
return next;
|
|
776
1618
|
}
|
|
777
1619
|
|
|
778
1620
|
function getEffectiveRunMode(ctx, flags) {
|
|
@@ -983,10 +1825,11 @@ async function executeHybridRun(ctx, flags, resolved, options) {
|
|
|
983
1825
|
}
|
|
984
1826
|
|
|
985
1827
|
function buildBuildTxPayload(flags, resolvedAction) {
|
|
1828
|
+
const runMode = String(flags.runMode || '').trim().toLowerCase() || 'self';
|
|
986
1829
|
const payload = {
|
|
987
|
-
agentApiKey:
|
|
1830
|
+
agentApiKey: resolveAgentApiKey(flags, runMode),
|
|
988
1831
|
action: String(resolvedAction || requireFlag(flags, 'action', 'Missing --action')).trim(),
|
|
989
|
-
params:
|
|
1832
|
+
params: getPreparedParams(flags),
|
|
990
1833
|
};
|
|
991
1834
|
if (flags.chainId) payload.chainId = Number(flags.chainId);
|
|
992
1835
|
if (flags.walletAddress) payload.walletAddress = String(flags.walletAddress).trim();
|
|
@@ -994,10 +1837,11 @@ function buildBuildTxPayload(flags, resolvedAction) {
|
|
|
994
1837
|
}
|
|
995
1838
|
|
|
996
1839
|
function buildIntentPayload(flags, options) {
|
|
1840
|
+
const runMode = String(options.runMode || flags.runMode || '').trim().toLowerCase() || 'thirdfy';
|
|
997
1841
|
const payload = {
|
|
998
|
-
agentApiKey:
|
|
1842
|
+
agentApiKey: resolveAgentApiKey(flags, runMode),
|
|
999
1843
|
action: String(options.resolvedAction || requireFlag(flags, 'action', 'Missing --action')).trim(),
|
|
1000
|
-
params:
|
|
1844
|
+
params: getPreparedParams(flags),
|
|
1001
1845
|
};
|
|
1002
1846
|
if (flags.catalog) payload.catalog = String(flags.catalog).trim();
|
|
1003
1847
|
if (flags.chainId) payload.chainId = Number(flags.chainId);
|
|
@@ -1018,6 +1862,22 @@ function buildIntentPayload(flags, options) {
|
|
|
1018
1862
|
return payload;
|
|
1019
1863
|
}
|
|
1020
1864
|
|
|
1865
|
+
function resolveAgentApiKey(flags, runMode) {
|
|
1866
|
+
const explicit = String(flags.agentApiKey || '').trim();
|
|
1867
|
+
if (explicit) return explicit;
|
|
1868
|
+
const envKey = String(process.env.THIRDFY_AGENT_API_KEY || '').trim();
|
|
1869
|
+
if (envKey) return envKey;
|
|
1870
|
+
const config = loadProfileConfig();
|
|
1871
|
+
const configKey = String(config?.agent?.apiKey || '').trim();
|
|
1872
|
+
if (configKey) return configKey;
|
|
1873
|
+
if (runMode === 'self') {
|
|
1874
|
+
throw new Error(
|
|
1875
|
+
'SELF_OPEN_DISABLED: self mode without agent identity is not enabled on this API yet. Set --agent-api-key, THIRDFY_AGENT_API_KEY, or login/config default.'
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
throw new Error('Missing --agent-api-key (or THIRDFY_AGENT_API_KEY / config.agent.apiKey)');
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1021
1881
|
function getActionKey(action) {
|
|
1022
1882
|
return String(action?.action || action?.id || action?.key || action?.name || '')
|
|
1023
1883
|
.trim();
|
|
@@ -1141,14 +2001,18 @@ async function resolveActionSelection(ctx, flags) {
|
|
|
1141
2001
|
}
|
|
1142
2002
|
|
|
1143
2003
|
function getAuthToken(flags, missingMessage) {
|
|
1144
|
-
const
|
|
2004
|
+
const config = loadProfileConfig();
|
|
2005
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
|
|
1145
2006
|
if (!authToken) throw new Error(missingMessage);
|
|
1146
2007
|
return authToken;
|
|
1147
2008
|
}
|
|
1148
2009
|
|
|
1149
2010
|
function buildOwnerAuthHeaders(flags, missingMessage) {
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
2011
|
+
const config = loadProfileConfig();
|
|
2012
|
+
const authToken = String(flags.authToken || process.env.THIRDFY_AUTH_TOKEN || config?.auth?.authToken || '').trim();
|
|
2013
|
+
const ownerSessionToken = String(
|
|
2014
|
+
flags.ownerSessionToken || process.env.THIRDFY_OWNER_SESSION_TOKEN || config?.auth?.ownerSessionToken || ''
|
|
2015
|
+
).trim();
|
|
1152
2016
|
if (!authToken && !ownerSessionToken) {
|
|
1153
2017
|
throw new Error(missingMessage);
|
|
1154
2018
|
}
|
|
@@ -1177,6 +2041,116 @@ async function negotiateCapabilities(ctx) {
|
|
|
1177
2041
|
}
|
|
1178
2042
|
}
|
|
1179
2043
|
|
|
2044
|
+
function resolveUnsignedTx(result) {
|
|
2045
|
+
if (result?.unsignedTx && typeof result.unsignedTx === 'object') return result.unsignedTx;
|
|
2046
|
+
if (result?.data?.unsignedTx && typeof result.data.unsignedTx === 'object') return result.data.unsignedTx;
|
|
2047
|
+
if (result?.details?.unsignedTx && typeof result.details.unsignedTx === 'object') return result.details.unsignedTx;
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
async function loadEthers() {
|
|
2052
|
+
const candidates = ['ethers', path.join(process.cwd(), 'node_modules', 'ethers')];
|
|
2053
|
+
for (const candidate of candidates) {
|
|
2054
|
+
try {
|
|
2055
|
+
return require(candidate);
|
|
2056
|
+
} catch {
|
|
2057
|
+
// continue
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
throw new Error('Could not load ethers dependency for self-exec');
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
function resolveOwsBinary() {
|
|
2064
|
+
const explicit = String(process.env.OWS_CLI_PATH || '').trim();
|
|
2065
|
+
if (explicit && fs.existsSync(explicit)) return explicit;
|
|
2066
|
+
const localBin = path.join(process.cwd(), 'node_modules', '.bin', 'ows');
|
|
2067
|
+
if (fs.existsSync(localBin)) return localBin;
|
|
2068
|
+
const which = spawnSync('which', ['ows'], { encoding: 'utf-8' });
|
|
2069
|
+
const discovered = String(which.stdout || '').trim();
|
|
2070
|
+
if (discovered) return discovered;
|
|
2071
|
+
throw new Error('ows CLI not found. Install it or set OWS_CLI_PATH.');
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function signUnsignedTxWithOws(unsignedHex, walletName, chainId) {
|
|
2075
|
+
const passphrase = String(process.env.OWS_PASSPHRASE || process.env.OWS_API_KEY || '').trim();
|
|
2076
|
+
if (!passphrase) {
|
|
2077
|
+
throw new Error('Set OWS_PASSPHRASE (or OWS_API_KEY) for self-exec signing.');
|
|
2078
|
+
}
|
|
2079
|
+
const ows = resolveOwsBinary();
|
|
2080
|
+
const txHex = String(unsignedHex).startsWith('0x') ? String(unsignedHex).slice(2) : String(unsignedHex);
|
|
2081
|
+
const res = spawnSync(
|
|
2082
|
+
ows,
|
|
2083
|
+
['sign', 'tx', '--wallet', walletName, '--chain', `eip155:${chainId}`, '--tx', txHex, '--json'],
|
|
2084
|
+
{ env: { ...process.env, OWS_PASSPHRASE: passphrase }, encoding: 'utf-8' }
|
|
2085
|
+
);
|
|
2086
|
+
const out = String(res.stdout || '').trim();
|
|
2087
|
+
if (res.status !== 0 || !out) {
|
|
2088
|
+
throw new Error(`ows sign tx failed: ${String(res.stderr || out || res.status).slice(0, 300)}`);
|
|
2089
|
+
}
|
|
2090
|
+
let parsed;
|
|
2091
|
+
try {
|
|
2092
|
+
parsed = JSON.parse(out);
|
|
2093
|
+
} catch {
|
|
2094
|
+
throw new Error(`ows sign tx returned non-json payload: ${out.slice(0, 200)}`);
|
|
2095
|
+
}
|
|
2096
|
+
const signature = String(parsed.signature || '').replace(/^0x/i, '');
|
|
2097
|
+
if (signature.length < 128) {
|
|
2098
|
+
throw new Error('Invalid OWS signature payload');
|
|
2099
|
+
}
|
|
2100
|
+
return {
|
|
2101
|
+
recoveryId: Number(parsed.recovery_id),
|
|
2102
|
+
r: `0x${signature.slice(0, 64)}`,
|
|
2103
|
+
s: `0x${signature.slice(64, 128)}`,
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
async function signAndBroadcastWithOws({ txd, chainId, walletAddress, owsWalletName, rpcUrl }) {
|
|
2108
|
+
const ethers = await loadEthers();
|
|
2109
|
+
const { JsonRpcProvider, Transaction, Signature } = ethers;
|
|
2110
|
+
const provider = new JsonRpcProvider(rpcUrl, chainId);
|
|
2111
|
+
const nonce = await provider.getTransactionCount(walletAddress, 'pending');
|
|
2112
|
+
const fee = await provider.getFeeData();
|
|
2113
|
+
const txFields = {
|
|
2114
|
+
type: 2,
|
|
2115
|
+
chainId,
|
|
2116
|
+
nonce,
|
|
2117
|
+
to: String(txd.to).toLowerCase(),
|
|
2118
|
+
data: txd.data,
|
|
2119
|
+
value: txd.value ? BigInt(txd.value) : 0n,
|
|
2120
|
+
gasLimit: txd.gas ? BigInt(txd.gas) : txd.gasLimit ? BigInt(txd.gasLimit) : 500000n,
|
|
2121
|
+
maxFeePerGas: fee.maxFeePerGas ?? fee.gasPrice ?? 2_000_000_000n,
|
|
2122
|
+
maxPriorityFeePerGas: fee.maxPriorityFeePerGas ?? 1_000_000_000n,
|
|
2123
|
+
};
|
|
2124
|
+
const unsigned = Transaction.from(txFields);
|
|
2125
|
+
const sigParts = signUnsignedTxWithOws(unsigned.unsignedSerialized, owsWalletName, chainId);
|
|
2126
|
+
const sig = Signature.from({ r: sigParts.r, s: sigParts.s, yParity: sigParts.recoveryId });
|
|
2127
|
+
const tx = Transaction.from(unsigned.unsignedSerialized);
|
|
2128
|
+
tx.signature = sig;
|
|
2129
|
+
const sent = await provider.broadcastTransaction(tx.serialized);
|
|
2130
|
+
return { sent };
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
async function readTokenBalanceRaw(rpcUrl, walletAddress, tokenAddress) {
|
|
2134
|
+
const ethers = await loadEthers();
|
|
2135
|
+
const { JsonRpcProvider, Interface } = ethers;
|
|
2136
|
+
const provider = new JsonRpcProvider(rpcUrl);
|
|
2137
|
+
const erc20 = new Interface(['function balanceOf(address owner) view returns (uint256)']);
|
|
2138
|
+
const response = await provider.call({
|
|
2139
|
+
to: tokenAddress,
|
|
2140
|
+
data: erc20.encodeFunctionData('balanceOf', [walletAddress]),
|
|
2141
|
+
});
|
|
2142
|
+
const decoded = erc20.decodeFunctionResult('balanceOf', response);
|
|
2143
|
+
return BigInt(decoded[0].toString()).toString();
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
async function readEffectBalances(rpcUrl, walletAddress, tokenIn, tokenOut) {
|
|
2147
|
+
const [inBal, outBal] = await Promise.all([
|
|
2148
|
+
readTokenBalanceRaw(rpcUrl, walletAddress, tokenIn),
|
|
2149
|
+
readTokenBalanceRaw(rpcUrl, walletAddress, tokenOut),
|
|
2150
|
+
]);
|
|
2151
|
+
return { tokenIn: inBal, tokenOut: outBal };
|
|
2152
|
+
}
|
|
2153
|
+
|
|
1180
2154
|
async function apiGet(ctx, route, headers = {}) {
|
|
1181
2155
|
return requestJson(ctx, route, { method: 'GET', headers });
|
|
1182
2156
|
}
|
|
@@ -1261,20 +2235,33 @@ Core commands:
|
|
|
1261
2235
|
thirdfy-agent profile init [--profile personal|builder|network] [--run-mode thirdfy|self|hybrid] [--json]
|
|
1262
2236
|
thirdfy-agent profile use --profile <name> [--run-mode thirdfy|self|hybrid] [--json]
|
|
1263
2237
|
thirdfy-agent profile show [--json]
|
|
2238
|
+
thirdfy-agent config set --key <path> --value <value> [--json]
|
|
2239
|
+
thirdfy-agent login [--auth-token <token> | --owner-session-token <token>] [--agent-api-key <key>] [--json]
|
|
2240
|
+
thirdfy-agent logout [--all] [--clear-agent-key] [--json]
|
|
1264
2241
|
thirdfy-agent whoami [--json]
|
|
1265
2242
|
thirdfy-agent catalogs list [--json]
|
|
1266
2243
|
thirdfy-agent actions [--catalog <id>] [--provider <id>] [--agent-api-key <key>] [--chain-id <id>] [--run-mode <mode>] [--json]
|
|
1267
|
-
thirdfy-agent delegation
|
|
2244
|
+
thirdfy-agent delegation create --auth-token <token> --agent-key <key> --owner-address <addr> --token-address <addr> --max-usd-per-day <usd> [--session-account-address <addr>] [--json]
|
|
2245
|
+
thirdfy-agent delegation init-custodial --auth-token <token> [--chain-id <id>] [--json]
|
|
2246
|
+
thirdfy-agent delegation custodial-grant --auth-token <token> --agent-key <key> --token-address <addr> --max-usd-per-day <usd> [--chain-id <id>] [--period-duration <sec>] [--expiry-seconds <sec>] [--json]
|
|
2247
|
+
thirdfy-agent delegation activate --auth-token <token> --agent-key <key> --wallet-address <addr> --session-account-address <addr> --delegation-manager <addr> --delegation <json> --signature <hex> [--json]
|
|
1268
2248
|
thirdfy-agent delegation status --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
2249
|
+
thirdfy-agent delegation show --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
2250
|
+
thirdfy-agent delegation inspect --auth-token <token> --agent-key <key> [--verify] [--json]
|
|
2251
|
+
thirdfy-agent delegation balance --auth-token <token> --agent-key <key> [--chain-id <id>] [--json]
|
|
2252
|
+
thirdfy-agent delegation revoke --auth-token <token> --agent-key <key> [--reason <text>] [--chain-id <id>] [--json]
|
|
2253
|
+
thirdfy-agent delegation redeem --agent-api-key <key> --action <id> --params <json> [--run-mode thirdfy|hybrid] [--estimated-amount-usd <usd>] [--json]
|
|
1269
2254
|
thirdfy-agent credentials upsert --auth-token <token> --venue <id> --api-key <key> --api-secret <secret> [--json]
|
|
1270
2255
|
thirdfy-agent credentials status --auth-token <token> [--venue <id>] [--json]
|
|
1271
2256
|
thirdfy-agent agent auth challenge --agent-key <key> [--wallet-address <addr>] [--chain-id <id>] [--json]
|
|
1272
2257
|
thirdfy-agent agent auth verify --challenge-id <id> --signature <hex> --agent-key <key> [--wallet-address <addr>] [--json]
|
|
2258
|
+
thirdfy-agent developer bootstrap --agent-key <key> --name <name> [--owner-session-token <token> | --challenge-id <id> --signature <hex>] [--json]
|
|
1273
2259
|
thirdfy-agent agent register --agent-key <key> --name <name> [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1274
2260
|
thirdfy-agent agent key rotate [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1275
2261
|
thirdfy-agent agent key revoke [--agent-key <key>] [--auth-token <token> | --owner-session-token <token>] [--json]
|
|
1276
2262
|
thirdfy-agent preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1277
2263
|
thirdfy-agent run --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--idempotency-key <key>] [--json]
|
|
2264
|
+
thirdfy-agent self-exec --agent-api-key <key> --action <id> --params <json> --wallet-address <addr> --ows-wallet-name <name> [--rpc-url <url>] [--json]
|
|
1278
2265
|
thirdfy-agent jeff preflight --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1279
2266
|
thirdfy-agent jeff trade --agent-api-key <key> --action <id> --params <json> [--run-mode <mode>] [--json]
|
|
1280
2267
|
thirdfy-agent jeff status --intent-id <id> [--json]
|
|
@@ -1288,6 +2275,7 @@ Global flags:
|
|
|
1288
2275
|
--profile <name> personal | builder | network
|
|
1289
2276
|
--env <file> load env vars from file
|
|
1290
2277
|
--timeout <ms> request timeout
|
|
2278
|
+
--params <json> for swap: exactly one of {amountInRaw} or {amountInHuman + tokenInDecimals}
|
|
1291
2279
|
--owner-session-token <token> owner session token (or THIRDFY_OWNER_SESSION_TOKEN)
|
|
1292
2280
|
--json stable machine-readable output
|
|
1293
2281
|
--verbose print adapter diagnostics
|