@elisym/cli 0.26.1 → 0.28.0
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/dist/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { ReadableStream } from 'node:stream/web';
|
|
3
3
|
import { readFileSync, existsSync, readdirSync, statSync, renameSync, chmodSync, mkdirSync, writeFileSync, appendFileSync, unlinkSync, rmdirSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve, basename, extname, relative, sep } from 'node:path';
|
|
5
|
-
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount,
|
|
6
|
-
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, loadPoliciesFromDir, ensureGitignoreHasIrohEntry, ensureGitignoreHasX402Entries, ensureGitignoreHasSessionsEntry, ensureGitignoreHasDelegationNoncesEntry, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
5
|
+
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, splAssetsForNetwork, formatSol, formatAssetAmount, signerFromSecretKeyBase58, resolveUsdcAsset, ElisymClient, KIND_EXTERNAL_IDENTITIES, POLICY_D_TAG_PREFIX, KIND_LONG_FORM_ARTICLE, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, createBlossomTransport, generateSolanaWallet, verifyAgentIdentities, getProtocolConfig, getProtocolProgramId, calculateProtocolFee, LIMITS, makeCensor, DEFAULT_REDACT_PATHS, POLICY_T_TAG, SESSION_ID_REGEX, createSlidingWindowLimiter, utf8ByteLength, readAcceptedTransports, resolveDelegationAsset, MAX_PROOF_TTL_SECS, PROOF_CLOCK_SKEW_SECS, verifyDelegationAuthProof, deriveOwnerDelegationAta, getDelegation, buildDelegatedTransfer, buildSignedPull, sendConfirmToTerminal, parseDelegatedPayment, confirmPullToTerminal, isDefinitelyUnpaid, decodeJobPayload, BoundedSet, KIND_JOB_FEEDBACK, encodeSecretKeyBase58, GITHUB_USERNAME_REGEX, X_USERNAME_REGEX, normalizeNip05Identifier, splitNip05Identifier, NATIVE_SOL, GIST_ID_REGEX, TWEET_ID_REGEX } from '@elisym/sdk';
|
|
6
|
+
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, loadPoliciesFromDir, ensureGitignoreHasIrohEntry, ensureGitignoreHasX402Entries, ensureGitignoreHasSessionsEntry, ensureGitignoreHasDelegationNoncesEntry, readAgentPublic, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
7
7
|
import { isAddress, createSolanaRpc, address, signature } from '@solana/kit';
|
|
8
8
|
import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools';
|
|
9
9
|
import YAML2 from 'yaml';
|
|
10
10
|
import { Command } from 'commander';
|
|
11
11
|
import { mkdir, writeFile, readFile, mkdtemp, rm, rename, stat } from 'node:fs/promises';
|
|
12
|
-
import { parseSkillMd, validateSkillFrontmatter, DEFAULT_X402_MAX_INPUT_BYTES, resolveInsidePathReal, DEFAULT_SCRIPT_TIMEOUT_MS, DynamicScriptSkill as DynamicScriptSkill$1, StaticScriptSkill as StaticScriptSkill$1, StaticFileSkill as StaticFileSkill$1, ScriptSkill as ScriptSkill$1 } from '@elisym/sdk/skills';
|
|
12
|
+
import { parseSkillMd, validateSkillFrontmatter, DEFAULT_X402_MAX_INPUT_BYTES, resolveInsidePathReal, DEFAULT_SCRIPT_TIMEOUT_MS, OnchainCallSkill, DynamicScriptSkill as DynamicScriptSkill$1, StaticScriptSkill as StaticScriptSkill$1, StaticFileSkill as StaticFileSkill$1, ScriptSkill as ScriptSkill$1 } from '@elisym/sdk/skills';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import Decimal from 'decimal.js-light';
|
|
15
15
|
import { decodePaymentRequiredHeader } from '@x402/core/http';
|
|
@@ -1064,8 +1064,18 @@ var init_llm = __esm({
|
|
|
1064
1064
|
var init_exports = {};
|
|
1065
1065
|
__export(init_exports, {
|
|
1066
1066
|
cmdInit: () => cmdInit,
|
|
1067
|
-
fetchModels: () => fetchModels3
|
|
1067
|
+
fetchModels: () => fetchModels3,
|
|
1068
|
+
parseNetworkOption: () => parseNetworkOption
|
|
1068
1069
|
});
|
|
1070
|
+
function parseNetworkOption(raw) {
|
|
1071
|
+
if (raw === void 0) {
|
|
1072
|
+
return void 0;
|
|
1073
|
+
}
|
|
1074
|
+
if (raw === "devnet" || raw === "mainnet") {
|
|
1075
|
+
return raw;
|
|
1076
|
+
}
|
|
1077
|
+
throw new Error(`--network must be "devnet" or "mainnet"; got "${raw}"`);
|
|
1078
|
+
}
|
|
1069
1079
|
function buildDefaultYaml() {
|
|
1070
1080
|
return ElisymYamlSchema.parse({
|
|
1071
1081
|
description: "An elisym AI agent",
|
|
@@ -1097,6 +1107,7 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
1097
1107
|
if (options.config && options.defaults) {
|
|
1098
1108
|
throw new Error("--config and --defaults are mutually exclusive; pick one.");
|
|
1099
1109
|
}
|
|
1110
|
+
const networkFlag = parseNetworkOption(options.network);
|
|
1100
1111
|
const skipConfirms = options.yes || options.defaults;
|
|
1101
1112
|
let template;
|
|
1102
1113
|
if (options.config) {
|
|
@@ -1105,9 +1116,20 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
1105
1116
|
template = ElisymYamlSchema.parse(YAML2.parse(raw) ?? {});
|
|
1106
1117
|
console.log(` Loaded template from ${configPath}
|
|
1107
1118
|
`);
|
|
1119
|
+
const templateSolana = template.payments.find((entry) => entry.chain === "solana");
|
|
1120
|
+
if (networkFlag !== void 0 && templateSolana && templateSolana.network !== networkFlag) {
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`--network ${networkFlag} conflicts with the template's payments[].network (${templateSolana.network}). Edit the template or drop the flag.`
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1108
1125
|
} else if (options.defaults) {
|
|
1109
1126
|
template = buildDefaultYaml();
|
|
1110
1127
|
console.log(" Using default skeleton (no LLM, no payments). Edit later via `profile`.\n");
|
|
1128
|
+
if (networkFlag !== void 0) {
|
|
1129
|
+
console.warn(
|
|
1130
|
+
" ! --network has no effect with --defaults (no payments entry is created). The wallet network is chosen when you configure a wallet.\n"
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1111
1133
|
}
|
|
1112
1134
|
const agentName = await resolveAgentName2(nameArg, inquirer);
|
|
1113
1135
|
const target = pickTarget(options);
|
|
@@ -1166,7 +1188,7 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
1166
1188
|
if (template) {
|
|
1167
1189
|
yaml = template;
|
|
1168
1190
|
} else {
|
|
1169
|
-
const result = await promptYaml(inquirer);
|
|
1191
|
+
const result = await promptYaml(inquirer, networkFlag);
|
|
1170
1192
|
yaml = result.yaml;
|
|
1171
1193
|
promptedApiKey = result.apiKey;
|
|
1172
1194
|
}
|
|
@@ -1259,6 +1281,14 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
1259
1281
|
]);
|
|
1260
1282
|
}
|
|
1261
1283
|
}
|
|
1284
|
+
const mainnetBound = yaml.payments.some(
|
|
1285
|
+
(entry) => entry.chain === "solana" && entry.network === "mainnet"
|
|
1286
|
+
);
|
|
1287
|
+
if (mainnetBound && !passphrase) {
|
|
1288
|
+
console.warn(
|
|
1289
|
+
" ! No passphrase set: this MAINNET agent will store its secrets unencrypted on disk.\n Strongly consider one (--passphrase or ELISYM_PASSPHRASE) - its keys will guard real funds."
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1262
1292
|
const nostrSecretBytes = generateSecretKey();
|
|
1263
1293
|
const nostrPubkey = getPublicKey(nostrSecretBytes);
|
|
1264
1294
|
const nostrSecretHex = Buffer.from(nostrSecretBytes).toString("hex");
|
|
@@ -1284,7 +1314,7 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
1284
1314
|
console.log(` Location: ${created.dir}`);
|
|
1285
1315
|
console.log(` Nostr: ${npub}`);
|
|
1286
1316
|
if (yaml.payments[0]?.address) {
|
|
1287
|
-
console.log(` Solana: ${yaml.payments[0].address}`);
|
|
1317
|
+
console.log(` Solana: ${yaml.payments[0].address} (${yaml.payments[0].network})`);
|
|
1288
1318
|
}
|
|
1289
1319
|
if (passphrase) {
|
|
1290
1320
|
console.log(" Secrets encrypted with your passphrase.");
|
|
@@ -1314,7 +1344,7 @@ async function resolveAgentName2(nameArg, inquirer) {
|
|
|
1314
1344
|
validateAgentName(inputName);
|
|
1315
1345
|
return inputName;
|
|
1316
1346
|
}
|
|
1317
|
-
async function promptYaml(inquirer) {
|
|
1347
|
+
async function promptYaml(inquirer, networkFlag) {
|
|
1318
1348
|
const { description } = await inquirer.prompt([
|
|
1319
1349
|
{
|
|
1320
1350
|
type: "input",
|
|
@@ -1361,10 +1391,34 @@ async function promptYaml(inquirer) {
|
|
|
1361
1391
|
}
|
|
1362
1392
|
}
|
|
1363
1393
|
]);
|
|
1394
|
+
let walletNetwork = "devnet";
|
|
1364
1395
|
if (solanaAddress) {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1396
|
+
if (networkFlag !== void 0) {
|
|
1397
|
+
walletNetwork = networkFlag;
|
|
1398
|
+
} else {
|
|
1399
|
+
const { network } = await inquirer.prompt([
|
|
1400
|
+
{
|
|
1401
|
+
type: "list",
|
|
1402
|
+
name: "network",
|
|
1403
|
+
message: "Solana network (fixed at init - to go mainnet later, create a new agent):",
|
|
1404
|
+
choices: [
|
|
1405
|
+
{ name: "devnet (test tokens - recommended to start)", value: "devnet" },
|
|
1406
|
+
{ name: "mainnet (REAL funds)", value: "mainnet" }
|
|
1407
|
+
],
|
|
1408
|
+
default: "devnet"
|
|
1409
|
+
}
|
|
1410
|
+
]);
|
|
1411
|
+
walletNetwork = network;
|
|
1412
|
+
}
|
|
1413
|
+
if (walletNetwork === "mainnet") {
|
|
1414
|
+
console.log(
|
|
1415
|
+
" ! MAINNET: this wallet handles REAL funds - every skill price is real money\n and there is no faucet. Fund it with real SOL (and USDC if skills price in it).\n The network is fixed at init: to go back to devnet, create a new agent."
|
|
1416
|
+
);
|
|
1417
|
+
} else {
|
|
1418
|
+
console.log(
|
|
1419
|
+
" The wallet receives every asset on Solana (SOL directly, USDC and other\n SPL tokens via their ATA). Each skill declares its own price and token\n in SKILL.md. Fund with SOL via `solana airdrop` or USDC via https://faucet.circle.com."
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1368
1422
|
}
|
|
1369
1423
|
const { llmProvider } = await inquirer.prompt([
|
|
1370
1424
|
{
|
|
@@ -1389,7 +1443,7 @@ async function promptYaml(inquirer) {
|
|
|
1389
1443
|
picture: picture || void 0,
|
|
1390
1444
|
banner: banner || void 0,
|
|
1391
1445
|
relays: [...RELAYS],
|
|
1392
|
-
payments: solanaAddress ? [{ chain: "solana", network:
|
|
1446
|
+
payments: solanaAddress ? [{ chain: "solana", network: walletNetwork, address: solanaAddress }] : [],
|
|
1393
1447
|
security: {}
|
|
1394
1448
|
};
|
|
1395
1449
|
if (llmProvider === "none") {
|
|
@@ -1450,15 +1504,24 @@ var WATCHDOG_PROBE_TIMEOUT_MS = 1e4;
|
|
|
1450
1504
|
var WATCHDOG_SELF_PING_INTERVAL_MS = 10 * 60 * 1e3;
|
|
1451
1505
|
var WATCHDOG_SELF_PING_TIMEOUT_MS = 15e3;
|
|
1452
1506
|
var WATCHDOG_SLEEP_DETECT_MULTIPLIER = 2;
|
|
1453
|
-
function getRpcUrl(
|
|
1507
|
+
function getRpcUrl(network) {
|
|
1454
1508
|
const envUrl = process.env.SOLANA_RPC_URL;
|
|
1455
1509
|
if (envUrl) {
|
|
1456
1510
|
return envUrl;
|
|
1457
1511
|
}
|
|
1458
|
-
return "https://api.devnet.solana.com";
|
|
1512
|
+
return network === "mainnet" ? "https://api.mainnet-beta.solana.com" : "https://api.devnet.solana.com";
|
|
1513
|
+
}
|
|
1514
|
+
async function solanaLineFor(agent) {
|
|
1515
|
+
try {
|
|
1516
|
+
const { yaml } = await readAgentPublic(agent);
|
|
1517
|
+
const solana = yaml.payments.find((entry) => entry.chain === "solana");
|
|
1518
|
+
return solana ? ` | Solana: ${solana.address} (${solana.network})` : "";
|
|
1519
|
+
} catch {
|
|
1520
|
+
return "";
|
|
1521
|
+
}
|
|
1459
1522
|
}
|
|
1460
|
-
async function
|
|
1461
|
-
const mint =
|
|
1523
|
+
async function fetchSplBalance(rpc, owner, asset) {
|
|
1524
|
+
const mint = asset.mint;
|
|
1462
1525
|
if (!mint) {
|
|
1463
1526
|
return 0n;
|
|
1464
1527
|
}
|
|
@@ -1472,21 +1535,39 @@ async function fetchUsdcBalance(rpc, owner) {
|
|
|
1472
1535
|
for (const entry of response.value) {
|
|
1473
1536
|
const parsed = entry.account.data;
|
|
1474
1537
|
const raw = parsed?.parsed?.info?.tokenAmount?.amount;
|
|
1475
|
-
if (typeof raw
|
|
1476
|
-
|
|
1538
|
+
if (typeof raw !== "string") {
|
|
1539
|
+
return null;
|
|
1477
1540
|
}
|
|
1541
|
+
total += BigInt(raw);
|
|
1478
1542
|
}
|
|
1479
1543
|
return total;
|
|
1480
1544
|
} catch {
|
|
1481
|
-
return
|
|
1545
|
+
return null;
|
|
1482
1546
|
}
|
|
1483
1547
|
}
|
|
1548
|
+
function formatSplBalanceValue(asset, balance) {
|
|
1549
|
+
return balance === void 0 || balance === null ? "unavailable (balance read failed)" : formatAssetAmount(asset, balance);
|
|
1550
|
+
}
|
|
1551
|
+
async function fetchUsdcBalance(rpc, owner, network) {
|
|
1552
|
+
return fetchSplBalance(rpc, owner, resolveUsdcAsset(network));
|
|
1553
|
+
}
|
|
1484
1554
|
|
|
1485
1555
|
// src/x402/constants.ts
|
|
1486
1556
|
var X402_SOLANA_DEVNET_CAIP2 = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
1487
1557
|
var X402_SOLANA_DEVNET_V1 = "solana-devnet";
|
|
1558
|
+
var X402_SOLANA_MAINNET_CAIP2 = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
1559
|
+
var X402_SOLANA_MAINNET_V1 = "solana";
|
|
1560
|
+
function x402SolanaNetworkIds(network) {
|
|
1561
|
+
return network === "mainnet" ? { caip2: X402_SOLANA_MAINNET_CAIP2, v1: X402_SOLANA_MAINNET_V1 } : { caip2: X402_SOLANA_DEVNET_CAIP2, v1: X402_SOLANA_DEVNET_V1 };
|
|
1562
|
+
}
|
|
1488
1563
|
var X402_GET_INPUT_MAX_ENCODED_BYTES = 2048;
|
|
1489
1564
|
var X402_MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
1565
|
+
var X402_ERROR_EXCERPT_CHARS = 400;
|
|
1566
|
+
var X402_MAX_ERROR_BODY_BYTES = 8 * 1024;
|
|
1567
|
+
var X402_ERROR_BODY_READ_MS = 5e3;
|
|
1568
|
+
var X402_MAX_ERROR_BODY_READS = 2048;
|
|
1569
|
+
var X402_MAX_CHALLENGE_HEADER_CHARS = 64 * 1024;
|
|
1570
|
+
var X402_MASKED_INPUT_MIN_CHARS = 8;
|
|
1490
1571
|
var X402_MAX_CHALLENGE_BYTES = 256 * 1024;
|
|
1491
1572
|
var X402_PROBE_TTL_MS = 3e4;
|
|
1492
1573
|
var X402_MAX_PAID_ATTEMPTS = 2;
|
|
@@ -1494,8 +1575,6 @@ var X402_MAX_PAYMENT_SIGNATURES = 2 * X402_MAX_PAID_ATTEMPTS;
|
|
|
1494
1575
|
var X402_FREE_RETRY_DELAYS_MS = [1e3, 3e3];
|
|
1495
1576
|
var X402_REFUNDED_RETRIES = 1;
|
|
1496
1577
|
var X402_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1497
|
-
|
|
1498
|
-
// src/x402/matcher.ts
|
|
1499
1578
|
function requirementAmount(requirement) {
|
|
1500
1579
|
const v1Amount = requirement.maxAmountRequired;
|
|
1501
1580
|
const raw = requirement.amount ?? v1Amount;
|
|
@@ -1509,10 +1588,12 @@ function isAcceptableRequirement(requirement, rule) {
|
|
|
1509
1588
|
return false;
|
|
1510
1589
|
}
|
|
1511
1590
|
const network = requirement.network;
|
|
1512
|
-
|
|
1591
|
+
const acceptedIds = x402SolanaNetworkIds(rule.network);
|
|
1592
|
+
if (network !== acceptedIds.caip2 && network !== acceptedIds.v1) {
|
|
1513
1593
|
return false;
|
|
1514
1594
|
}
|
|
1515
|
-
|
|
1595
|
+
const usdcMint = resolveUsdcAsset(rule.network).mint;
|
|
1596
|
+
if (usdcMint === void 0 || requirement.asset !== usdcMint) {
|
|
1516
1597
|
return false;
|
|
1517
1598
|
}
|
|
1518
1599
|
const amount = requirementAmount(requirement);
|
|
@@ -1697,7 +1778,6 @@ async function probePaymentRequiredCached(url, method, ttlMs) {
|
|
|
1697
1778
|
// src/commands/x402-add.ts
|
|
1698
1779
|
var BPS_DENOMINATOR = 1e4;
|
|
1699
1780
|
var DEFAULT_MARGIN_BPS = 1e3;
|
|
1700
|
-
var SOLANA_MAINNET_CAIP2 = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
1701
1781
|
var CIRCLE_FAUCET_URL = "https://faucet.circle.com";
|
|
1702
1782
|
function fail(message) {
|
|
1703
1783
|
console.error(`
|
|
@@ -1782,7 +1862,7 @@ function buildSkillMd(input) {
|
|
|
1782
1862
|
"Operator notes for this x402 bridge skill (the executor ignores this body):",
|
|
1783
1863
|
"",
|
|
1784
1864
|
`- Upstream: ${input.url} (${input.method})`,
|
|
1785
|
-
`- Upstream quote at generation time: ${formatAssetAmount(
|
|
1865
|
+
`- Upstream quote at generation time: ${formatAssetAmount(resolveUsdcAsset(input.network), input.quote)} (= x402_max_upstream, the signing ceiling)`,
|
|
1786
1866
|
`- Price: ${input.priceDisplay} USDC (margin ${input.marginPercent}%, protocol fee ${input.feePercent}% at generation time)`,
|
|
1787
1867
|
"- To reprice after an upstream change, re-run:",
|
|
1788
1868
|
"",
|
|
@@ -1834,15 +1914,17 @@ function scanExistingSkills(skillsDir, candidateDTag, url) {
|
|
|
1834
1914
|
}
|
|
1835
1915
|
return scan;
|
|
1836
1916
|
}
|
|
1837
|
-
function describeUnacceptableAccepts(probe) {
|
|
1917
|
+
function describeUnacceptableAccepts(probe, agentNetwork) {
|
|
1838
1918
|
const networks = [...new Set(probe.accepts.map((requirement) => String(requirement.network)))];
|
|
1839
1919
|
if (networks.length > 0 && networks.every((network) => network.startsWith("eip155:"))) {
|
|
1840
1920
|
return `the service accepts only EVM networks (${networks.join(", ")}) - not supported yet`;
|
|
1841
1921
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
1922
|
+
const otherNetwork = agentNetwork === "mainnet" ? "devnet" : "mainnet";
|
|
1923
|
+
const otherIds = x402SolanaNetworkIds(otherNetwork);
|
|
1924
|
+
if (networks.some((network) => network === otherIds.caip2 || network === otherIds.v1)) {
|
|
1925
|
+
return `the service settles on Solana ${otherNetwork} but this agent is bound to ${agentNetwork} (one agent = one network) - bridge it from a ${otherNetwork} agent instead` + (otherNetwork === "mainnet" ? " (create one: npx @elisym/cli init --network mainnet)" : "");
|
|
1844
1926
|
}
|
|
1845
|
-
return `no exact-scheme
|
|
1927
|
+
return `no exact-scheme ${agentNetwork}-USDC requirement found (service accepts: ${networks.join(", ") || "nothing parseable"})`;
|
|
1846
1928
|
}
|
|
1847
1929
|
async function cmdX402Add(url, agentName, options) {
|
|
1848
1930
|
const cwd = process.cwd();
|
|
@@ -1886,6 +1968,33 @@ async function cmdX402Add(url, agentName, options) {
|
|
|
1886
1968
|
}
|
|
1887
1969
|
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
1888
1970
|
const loaded = await loadAgent(agentName, cwd, passphrase);
|
|
1971
|
+
const existingSolPayment = loaded.yaml.payments.find((entry) => entry.chain === "solana");
|
|
1972
|
+
let agentNetwork;
|
|
1973
|
+
if (existingSolPayment !== void 0) {
|
|
1974
|
+
agentNetwork = existingSolPayment.network;
|
|
1975
|
+
} else if (options.yes) {
|
|
1976
|
+
agentNetwork = "devnet";
|
|
1977
|
+
} else {
|
|
1978
|
+
const { network } = await inquirer.prompt([
|
|
1979
|
+
{
|
|
1980
|
+
type: "list",
|
|
1981
|
+
name: "network",
|
|
1982
|
+
message: "This agent has no Solana wallet entry yet. Which network should it operate on (fixed once set)?",
|
|
1983
|
+
choices: [
|
|
1984
|
+
{ name: "devnet (test tokens - recommended to start)", value: "devnet" },
|
|
1985
|
+
{ name: "mainnet (REAL funds)", value: "mainnet" }
|
|
1986
|
+
],
|
|
1987
|
+
default: "devnet"
|
|
1988
|
+
}
|
|
1989
|
+
]);
|
|
1990
|
+
agentNetwork = network;
|
|
1991
|
+
if (agentNetwork === "mainnet") {
|
|
1992
|
+
console.log(
|
|
1993
|
+
" ! MAINNET: the bridge float and its upstream payments are REAL funds.\n The network is fixed once written - to switch, create a new agent."
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
const usdcAsset = resolveUsdcAsset(agentNetwork);
|
|
1889
1998
|
console.log(`
|
|
1890
1999
|
Probing ${url} (${method}, unpaid)...`);
|
|
1891
2000
|
let probe;
|
|
@@ -1898,10 +2007,13 @@ async function cmdX402Add(url, agentName, options) {
|
|
|
1898
2007
|
const message = error instanceof Error ? error.message : String(error);
|
|
1899
2008
|
fail(`upstream unreachable: ${message}`);
|
|
1900
2009
|
}
|
|
1901
|
-
const anyCeiling = {
|
|
2010
|
+
const anyCeiling = {
|
|
2011
|
+
maxUpstreamSubunits: BigInt(Number.MAX_SAFE_INTEGER),
|
|
2012
|
+
network: agentNetwork
|
|
2013
|
+
};
|
|
1902
2014
|
const requirement = selectAcceptableRequirement(probe.accepts, anyCeiling);
|
|
1903
2015
|
if (requirement === void 0) {
|
|
1904
|
-
fail(describeUnacceptableAccepts(probe));
|
|
2016
|
+
fail(describeUnacceptableAccepts(probe, agentNetwork));
|
|
1905
2017
|
}
|
|
1906
2018
|
const quote = requirementAmount(requirement);
|
|
1907
2019
|
if (quote === null || quote <= 0n) {
|
|
@@ -1963,6 +2075,11 @@ async function cmdX402Add(url, agentName, options) {
|
|
|
1963
2075
|
);
|
|
1964
2076
|
}
|
|
1965
2077
|
}
|
|
2078
|
+
if (agentNetwork === "mainnet" && !passphrase) {
|
|
2079
|
+
console.warn(
|
|
2080
|
+
" ! No passphrase set: this MAINNET wallet key will be stored unencrypted on disk.\n Strongly consider one (ELISYM_PASSPHRASE) - the key will guard real funds."
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
1966
2083
|
await writeSecrets(
|
|
1967
2084
|
loaded.dir,
|
|
1968
2085
|
{ ...loaded.secrets, solana_secret_key: solanaSecretKey },
|
|
@@ -1971,33 +2088,35 @@ async function cmdX402Add(url, agentName, options) {
|
|
|
1971
2088
|
console.log(" Wallet key saved to .secrets.json");
|
|
1972
2089
|
}
|
|
1973
2090
|
const signer = await signerFromSecretKeyBase58(solanaSecretKey);
|
|
1974
|
-
|
|
1975
|
-
if (solPayment !== void 0 && solPayment.address !== signer.address) {
|
|
2091
|
+
if (existingSolPayment !== void 0 && existingSolPayment.address !== signer.address) {
|
|
1976
2092
|
fail(
|
|
1977
|
-
`wallet invariant violated: elisym.yaml payments[].address (${
|
|
2093
|
+
`wallet invariant violated: elisym.yaml payments[].address (${existingSolPayment.address}) differs from the solana_secret_key address (${signer.address}).
|
|
1978
2094
|
Revenue must land in the wallet the bridge spends from, or the float never refills.
|
|
1979
2095
|
Fix: set payments[].address to the key address, or import the matching key.`
|
|
1980
2096
|
);
|
|
1981
2097
|
}
|
|
1982
|
-
if (
|
|
2098
|
+
if (existingSolPayment === void 0) {
|
|
1983
2099
|
console.log(
|
|
1984
|
-
` elisym.yaml has no Solana payments entry; adding address ${signer.address} (
|
|
2100
|
+
` elisym.yaml has no Solana payments entry; adding address ${signer.address} (${agentNetwork}).`
|
|
1985
2101
|
);
|
|
1986
2102
|
await writeYaml(loaded.dir, {
|
|
1987
2103
|
...loaded.yaml,
|
|
1988
2104
|
payments: [
|
|
1989
2105
|
...loaded.yaml.payments,
|
|
1990
|
-
{ chain: "solana", network:
|
|
2106
|
+
{ chain: "solana", network: agentNetwork, address: signer.address }
|
|
1991
2107
|
]
|
|
1992
2108
|
});
|
|
1993
2109
|
}
|
|
1994
|
-
const rpc = createSolanaRpc(getRpcUrl());
|
|
1995
|
-
const balance = await fetchUsdcBalance(rpc, address(signer.address));
|
|
2110
|
+
const rpc = createSolanaRpc(getRpcUrl(agentNetwork));
|
|
2111
|
+
const balance = await fetchUsdcBalance(rpc, address(signer.address), agentNetwork);
|
|
1996
2112
|
let feeBps;
|
|
1997
2113
|
try {
|
|
1998
|
-
const protocolConfig = await getProtocolConfig(
|
|
1999
|
-
|
|
2000
|
-
|
|
2114
|
+
const protocolConfig = await getProtocolConfig(
|
|
2115
|
+
rpc,
|
|
2116
|
+
getProtocolProgramId(agentNetwork),
|
|
2117
|
+
agentNetwork,
|
|
2118
|
+
{ forceRefresh: true }
|
|
2119
|
+
);
|
|
2001
2120
|
feeBps = protocolConfig.feeBps;
|
|
2002
2121
|
} catch (error) {
|
|
2003
2122
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2009,7 +2128,7 @@ async function cmdX402Add(url, agentName, options) {
|
|
|
2009
2128
|
);
|
|
2010
2129
|
}
|
|
2011
2130
|
const priceSubunits = computeBridgePriceSubunits(quote, marginBps, feeBps);
|
|
2012
|
-
const priceDisplay = new Decimal(priceSubunits.toString()).div(new Decimal(10).pow(
|
|
2131
|
+
const priceDisplay = new Decimal(priceSubunits.toString()).div(new Decimal(10).pow(usdcAsset.decimals)).toString();
|
|
2013
2132
|
const protocolFee = calculateProtocolFee(Number(priceSubunits), feeBps);
|
|
2014
2133
|
const netMargin = priceSubunits - BigInt(protocolFee) - quote;
|
|
2015
2134
|
const fallbackName = slugFromUrl(parsedUrl);
|
|
@@ -2078,18 +2197,17 @@ async function cmdX402Add(url, agentName, options) {
|
|
|
2078
2197
|
console.log(
|
|
2079
2198
|
` Upstream ${url} (${method}${queryParam !== void 0 ? `, input in ?${queryParam}=` : ""}${noInput ? ", no buyer input" : ""})`
|
|
2080
2199
|
);
|
|
2081
|
-
console.log(`
|
|
2200
|
+
console.log(` Network ${agentNetwork}`);
|
|
2201
|
+
console.log(` Quote ${formatAssetAmount(usdcAsset, quote)}`);
|
|
2082
2202
|
console.log(
|
|
2083
|
-
` Your price ${formatAssetAmount(
|
|
2203
|
+
` Your price ${formatAssetAmount(usdcAsset, priceSubunits)} (margin ${marginPercent}% + protocol fee ${feePercent}%)`
|
|
2084
2204
|
);
|
|
2085
|
-
console.log(` Net margin ${formatAssetAmount(
|
|
2086
|
-
console.log(
|
|
2087
|
-
|
|
2088
|
-
);
|
|
2089
|
-
if (balance < quote) {
|
|
2205
|
+
console.log(` Net margin ${formatAssetAmount(usdcAsset, netMargin)} per job`);
|
|
2206
|
+
console.log(` Float ${formatSplBalanceValue(usdcAsset, balance)} in ${signer.address}`);
|
|
2207
|
+
if (balance !== null && balance < quote) {
|
|
2090
2208
|
console.log(
|
|
2091
2209
|
chalk.yellow(
|
|
2092
|
-
` ! Float below one upstream quote - fund the wallet with devnet USDC: ${CIRCLE_FAUCET_URL}`
|
|
2210
|
+
agentNetwork === "mainnet" ? " ! Float below one upstream quote - fund the wallet with USDC (mainnet, REAL funds)" : ` ! Float below one upstream quote - fund the wallet with devnet USDC: ${CIRCLE_FAUCET_URL}`
|
|
2093
2211
|
)
|
|
2094
2212
|
);
|
|
2095
2213
|
}
|
|
@@ -2113,10 +2231,12 @@ async function cmdX402Add(url, agentName, options) {
|
|
|
2113
2231
|
queryParam,
|
|
2114
2232
|
quote,
|
|
2115
2233
|
marginPercent,
|
|
2116
|
-
feePercent
|
|
2234
|
+
feePercent,
|
|
2235
|
+
network: agentNetwork
|
|
2117
2236
|
});
|
|
2118
2237
|
const roundTrip = parseSkillMd(content);
|
|
2119
2238
|
validateSkillFrontmatter(roundTrip.frontmatter, roundTrip.systemPrompt, {
|
|
2239
|
+
network: agentNetwork,
|
|
2120
2240
|
allowFreeSkills: false,
|
|
2121
2241
|
allowX402Skills: true
|
|
2122
2242
|
});
|
|
@@ -2125,8 +2245,10 @@ async function cmdX402Add(url, agentName, options) {
|
|
|
2125
2245
|
await ensureGitignoreHasX402Entries(dirname(loaded.dir));
|
|
2126
2246
|
console.log(`
|
|
2127
2247
|
Wrote ${join(targetDir, "SKILL.md")}`);
|
|
2128
|
-
if (balance < quote) {
|
|
2129
|
-
console.log(
|
|
2248
|
+
if (balance !== null && balance < quote) {
|
|
2249
|
+
console.log(
|
|
2250
|
+
agentNetwork === "mainnet" ? " Next: fund the float with USDC on mainnet (REAL funds)." : ` Next: fund the float (devnet USDC): ${CIRCLE_FAUCET_URL}`
|
|
2251
|
+
);
|
|
2130
2252
|
}
|
|
2131
2253
|
console.log(` Start the agent: npx @elisym/cli start ${agentName}
|
|
2132
2254
|
`);
|
|
@@ -2162,7 +2284,7 @@ async function resolveAgentName(name, cwd, nonInteractive) {
|
|
|
2162
2284
|
}
|
|
2163
2285
|
async function printDelegateStatus(delegateSecretKey, network) {
|
|
2164
2286
|
const signer = await signerFromSecretKeyBase58(delegateSecretKey);
|
|
2165
|
-
const rpc = createSolanaRpc(getRpcUrl());
|
|
2287
|
+
const rpc = createSolanaRpc(getRpcUrl(network));
|
|
2166
2288
|
let gasLine = "";
|
|
2167
2289
|
try {
|
|
2168
2290
|
const { value: balance } = await rpc.getBalance(address(signer.address)).send();
|
|
@@ -2186,7 +2308,7 @@ async function cmdDelegateKey(name, options) {
|
|
|
2186
2308
|
const agentName = await resolveAgentName(name, cwd, options.yes === true);
|
|
2187
2309
|
const passphrase = process.env.ELISYM_PASSPHRASE;
|
|
2188
2310
|
const loaded = await loadAgent(agentName, cwd, passphrase);
|
|
2189
|
-
loaded.yaml.payments.find((entry) => entry.chain === "solana")?.network ?? "devnet";
|
|
2311
|
+
const network = loaded.yaml.payments.find((entry) => entry.chain === "solana")?.network ?? "devnet";
|
|
2190
2312
|
const existing = loaded.secrets.solana_delegate_secret_key;
|
|
2191
2313
|
if (options.show) {
|
|
2192
2314
|
if (!existing) {
|
|
@@ -2195,12 +2317,12 @@ async function cmdDelegateKey(name, options) {
|
|
|
2195
2317
|
);
|
|
2196
2318
|
process.exit(1);
|
|
2197
2319
|
}
|
|
2198
|
-
await printDelegateStatus(existing);
|
|
2320
|
+
await printDelegateStatus(existing, network);
|
|
2199
2321
|
return;
|
|
2200
2322
|
}
|
|
2201
2323
|
if (existing && !options.rotate) {
|
|
2202
2324
|
console.log(` Agent "${agentName}" already has a delegate key.`);
|
|
2203
|
-
await printDelegateStatus(existing);
|
|
2325
|
+
await printDelegateStatus(existing, network);
|
|
2204
2326
|
console.log(" Use --rotate to replace it (this invalidates outstanding owner approvals).");
|
|
2205
2327
|
return;
|
|
2206
2328
|
}
|
|
@@ -2257,6 +2379,11 @@ async function cmdDelegateKey(name, options) {
|
|
|
2257
2379
|
process.exit(1);
|
|
2258
2380
|
}
|
|
2259
2381
|
}
|
|
2382
|
+
if (network === "mainnet" && !passphrase) {
|
|
2383
|
+
console.warn(
|
|
2384
|
+
" ! No passphrase set: this MAINNET delegate key will be stored unencrypted on disk.\n Strongly consider one (ELISYM_PASSPHRASE) - the key spends owner-approved real funds."
|
|
2385
|
+
);
|
|
2386
|
+
}
|
|
2260
2387
|
await writeSecrets(
|
|
2261
2388
|
loaded.dir,
|
|
2262
2389
|
{ ...loaded.secrets, solana_delegate_secret_key: delegateSecretKey },
|
|
@@ -2265,7 +2392,7 @@ async function cmdDelegateKey(name, options) {
|
|
|
2265
2392
|
console.log(
|
|
2266
2393
|
existing ? "\n Delegate key rotated and saved." : "\n Delegate key generated and saved."
|
|
2267
2394
|
);
|
|
2268
|
-
await printDelegateStatus(delegateSecretKey);
|
|
2395
|
+
await printDelegateStatus(delegateSecretKey, network);
|
|
2269
2396
|
}
|
|
2270
2397
|
var IDENTITY_PLATFORMS = ["github", "x", "website"];
|
|
2271
2398
|
var X_PROOF_URL_HOSTS = /* @__PURE__ */ new Set([
|
|
@@ -2899,12 +3026,12 @@ async function cmdProfile(name) {
|
|
|
2899
3026
|
console.log(" Profile updated.\n");
|
|
2900
3027
|
}
|
|
2901
3028
|
if (section === "wallet") {
|
|
2902
|
-
const current = loaded.yaml.payments
|
|
3029
|
+
const current = loaded.yaml.payments.find((entry) => entry.chain === "solana");
|
|
2903
3030
|
const answers = await inquirer.prompt([
|
|
2904
3031
|
{
|
|
2905
3032
|
type: "input",
|
|
2906
3033
|
name: "address",
|
|
2907
|
-
message: "Solana address (empty to
|
|
3034
|
+
message: current ? "Solana address:" : "Solana address (empty to skip):",
|
|
2908
3035
|
default: current?.address ?? "",
|
|
2909
3036
|
validate: (value) => {
|
|
2910
3037
|
if (!value) {
|
|
@@ -2914,9 +3041,40 @@ async function cmdProfile(name) {
|
|
|
2914
3041
|
}
|
|
2915
3042
|
}
|
|
2916
3043
|
]);
|
|
3044
|
+
if (!answers.address && current) {
|
|
3045
|
+
console.log(
|
|
3046
|
+
" ! The wallet entry cannot be removed: it pins the agent network, which is\n fixed at creation - to change networks, create a new agent. To stop\n charging, set every skill price to 0 instead. Wallet unchanged.\n"
|
|
3047
|
+
);
|
|
3048
|
+
continue;
|
|
3049
|
+
}
|
|
3050
|
+
let walletNetwork = current?.network;
|
|
3051
|
+
if (answers.address && walletNetwork === void 0) {
|
|
3052
|
+
const { network } = await inquirer.prompt([
|
|
3053
|
+
{
|
|
3054
|
+
type: "list",
|
|
3055
|
+
name: "network",
|
|
3056
|
+
message: "Solana network (fixed once set - to switch later, create a new agent):",
|
|
3057
|
+
choices: [
|
|
3058
|
+
{ name: "devnet (test tokens - recommended to start)", value: "devnet" },
|
|
3059
|
+
{ name: "mainnet (REAL funds)", value: "mainnet" }
|
|
3060
|
+
],
|
|
3061
|
+
default: "devnet"
|
|
3062
|
+
}
|
|
3063
|
+
]);
|
|
3064
|
+
walletNetwork = network;
|
|
3065
|
+
if (walletNetwork === "mainnet") {
|
|
3066
|
+
console.log(
|
|
3067
|
+
" ! MAINNET: this wallet handles REAL funds - every skill price is real money\n and there is no faucet. Strongly consider encrypting secrets with a passphrase."
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
const otherChainEntries = loaded.yaml.payments.filter((entry) => entry.chain !== "solana");
|
|
2917
3072
|
const nextYaml = {
|
|
2918
3073
|
...loaded.yaml,
|
|
2919
|
-
payments: answers.address
|
|
3074
|
+
payments: answers.address && walletNetwork !== void 0 ? [
|
|
3075
|
+
...otherChainEntries,
|
|
3076
|
+
{ chain: "solana", network: walletNetwork, address: answers.address }
|
|
3077
|
+
] : otherChainEntries
|
|
2920
3078
|
};
|
|
2921
3079
|
await writeYaml(loaded.dir, nextYaml);
|
|
2922
3080
|
loaded.yaml = nextYaml;
|
|
@@ -4748,14 +4906,11 @@ var AgentRuntime = class {
|
|
|
4748
4906
|
}
|
|
4749
4907
|
/** Fetch on-chain protocol config (fee, treasury). Always fetches fresh to avoid stale treasury. */
|
|
4750
4908
|
async fetchProtocolConfig() {
|
|
4751
|
-
|
|
4752
|
-
throw new Error(
|
|
4753
|
-
`Network "${this.config.network}" is not supported. Only "devnet" is available until the on-chain protocol program is deployed on mainnet.`
|
|
4754
|
-
);
|
|
4755
|
-
}
|
|
4756
|
-
const programId = getProtocolProgramId("devnet");
|
|
4909
|
+
const programId = getProtocolProgramId(this.config.network);
|
|
4757
4910
|
const rpc = createSolanaRpc(getRpcUrl(this.config.network));
|
|
4758
|
-
const config = await getProtocolConfig(rpc, programId, {
|
|
4911
|
+
const config = await getProtocolConfig(rpc, programId, this.config.network, {
|
|
4912
|
+
forceRefresh: true
|
|
4913
|
+
});
|
|
4759
4914
|
return { feeBps: config.feeBps, treasury: config.treasury };
|
|
4760
4915
|
}
|
|
4761
4916
|
async run() {
|
|
@@ -5243,12 +5398,6 @@ var AgentRuntime = class {
|
|
|
5243
5398
|
"this agent does not accept delegated payment."
|
|
5244
5399
|
);
|
|
5245
5400
|
}
|
|
5246
|
-
if (this.config.network !== "devnet") {
|
|
5247
|
-
return reject(
|
|
5248
|
-
`delegation is devnet-only (network: ${this.config.network})`,
|
|
5249
|
-
"this agent does not accept delegated payment."
|
|
5250
|
-
);
|
|
5251
|
-
}
|
|
5252
5401
|
const network = this.config.network;
|
|
5253
5402
|
if (!skill || skill.delegation === void 0 || skill.priceSubunits <= 0) {
|
|
5254
5403
|
return reject(
|
|
@@ -5365,7 +5514,9 @@ var AgentRuntime = class {
|
|
|
5365
5514
|
// first delegated job does not dead-letter on a missing ATA.
|
|
5366
5515
|
ensureDestination: { owner: this.config.solanaAddress }
|
|
5367
5516
|
});
|
|
5368
|
-
const pull = await buildSignedPull(ctx.rpc, ctx.delegateSigner, instructions
|
|
5517
|
+
const pull = await buildSignedPull(ctx.rpc, ctx.delegateSigner, instructions, {
|
|
5518
|
+
network: ctx.network
|
|
5519
|
+
});
|
|
5369
5520
|
this.ledger.recordPullSignature(
|
|
5370
5521
|
job.jobId,
|
|
5371
5522
|
pull.signature,
|
|
@@ -5840,6 +5991,7 @@ var AgentRuntime = class {
|
|
|
5840
5991
|
this.config.solanaAddress,
|
|
5841
5992
|
jobPrice,
|
|
5842
5993
|
protocolConfig,
|
|
5994
|
+
this.config.network,
|
|
5843
5995
|
{ expirySecs: this.config.paymentTimeoutSecs, asset: jobAsset }
|
|
5844
5996
|
);
|
|
5845
5997
|
const requestJson = JSON.stringify(request);
|
|
@@ -6552,6 +6704,51 @@ var DynamicScriptSkill = class {
|
|
|
6552
6704
|
return this.inner.execute(input, ctx);
|
|
6553
6705
|
}
|
|
6554
6706
|
};
|
|
6707
|
+
var OnchainSkill = class {
|
|
6708
|
+
name;
|
|
6709
|
+
description;
|
|
6710
|
+
capabilities;
|
|
6711
|
+
priceSubunits;
|
|
6712
|
+
asset;
|
|
6713
|
+
mode = "onchain";
|
|
6714
|
+
image;
|
|
6715
|
+
imageFile;
|
|
6716
|
+
dir;
|
|
6717
|
+
llmOverride;
|
|
6718
|
+
onchain;
|
|
6719
|
+
inner;
|
|
6720
|
+
constructor(params) {
|
|
6721
|
+
this.name = params.name;
|
|
6722
|
+
this.description = params.description;
|
|
6723
|
+
this.capabilities = params.capabilities;
|
|
6724
|
+
this.priceSubunits = params.priceSubunits;
|
|
6725
|
+
this.asset = params.asset;
|
|
6726
|
+
this.image = params.image;
|
|
6727
|
+
this.imageFile = params.imageFile;
|
|
6728
|
+
this.dir = params.dir;
|
|
6729
|
+
this.llmOverride = params.llmOverride;
|
|
6730
|
+
this.onchain = params.onchain;
|
|
6731
|
+
this.inner = new OnchainCallSkill({
|
|
6732
|
+
name: params.name,
|
|
6733
|
+
description: params.description,
|
|
6734
|
+
capabilities: params.capabilities,
|
|
6735
|
+
priceSubunits: BigInt(Math.round(params.priceSubunits)),
|
|
6736
|
+
asset: params.asset,
|
|
6737
|
+
scriptPath: params.scriptPath,
|
|
6738
|
+
scriptArgs: params.scriptArgs,
|
|
6739
|
+
scriptTimeoutMs: params.scriptTimeoutMs,
|
|
6740
|
+
scriptEnv: params.scriptEnv,
|
|
6741
|
+
image: params.image,
|
|
6742
|
+
imageFile: params.imageFile,
|
|
6743
|
+
llmOverride: params.llmOverride,
|
|
6744
|
+
onchain: params.onchain,
|
|
6745
|
+
network: params.network
|
|
6746
|
+
});
|
|
6747
|
+
}
|
|
6748
|
+
async execute(input, ctx) {
|
|
6749
|
+
return this.inner.execute(input, ctx);
|
|
6750
|
+
}
|
|
6751
|
+
};
|
|
6555
6752
|
var ScriptSkill = class {
|
|
6556
6753
|
name;
|
|
6557
6754
|
description;
|
|
@@ -6684,7 +6881,7 @@ function scopeLlmKeys(scriptEnv, declaredProvider) {
|
|
|
6684
6881
|
}
|
|
6685
6882
|
return env;
|
|
6686
6883
|
}
|
|
6687
|
-
function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
6884
|
+
function buildCliSkill(parsed, entryPath, scriptEnv, network) {
|
|
6688
6885
|
let safeImageFile = parsed.imageFile;
|
|
6689
6886
|
if (safeImageFile !== void 0 && resolveInsidePathReal(entryPath, safeImageFile) === null) {
|
|
6690
6887
|
console.warn(
|
|
@@ -6772,6 +6969,35 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
6772
6969
|
}) : new StaticScriptSkill(scriptParams);
|
|
6773
6970
|
break;
|
|
6774
6971
|
}
|
|
6972
|
+
case "onchain": {
|
|
6973
|
+
if (parsed.script === void 0 || parsed.onchain === void 0) {
|
|
6974
|
+
throw new Error(
|
|
6975
|
+
`SKILL.md "${parsed.name}": internal error - script or onchain block missing for mode 'onchain'`
|
|
6976
|
+
);
|
|
6977
|
+
}
|
|
6978
|
+
const scriptPath = resolveInsidePathReal(entryPath, parsed.script);
|
|
6979
|
+
if (!scriptPath) {
|
|
6980
|
+
throw new Error(`SKILL.md "${parsed.name}": "script" must stay inside the skill directory`);
|
|
6981
|
+
}
|
|
6982
|
+
skill = new OnchainSkill({
|
|
6983
|
+
name: parsed.name,
|
|
6984
|
+
description: parsed.description,
|
|
6985
|
+
capabilities: parsed.capabilities,
|
|
6986
|
+
priceSubunits: Number(parsed.priceSubunits),
|
|
6987
|
+
asset: parsed.asset,
|
|
6988
|
+
scriptPath,
|
|
6989
|
+
scriptArgs: parsed.scriptArgs,
|
|
6990
|
+
scriptTimeoutMs: parsed.scriptTimeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS,
|
|
6991
|
+
scriptEnv: scopeLlmKeys(scriptEnv, parsed.llmOverride?.provider),
|
|
6992
|
+
image: parsed.image,
|
|
6993
|
+
imageFile: safeImageFile,
|
|
6994
|
+
dir: entryPath,
|
|
6995
|
+
llmOverride: parsed.llmOverride,
|
|
6996
|
+
onchain: parsed.onchain,
|
|
6997
|
+
network
|
|
6998
|
+
});
|
|
6999
|
+
break;
|
|
7000
|
+
}
|
|
6775
7001
|
case "x402": {
|
|
6776
7002
|
if (parsed.x402 === void 0) {
|
|
6777
7003
|
throw new Error(
|
|
@@ -6804,7 +7030,7 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
6804
7030
|
}
|
|
6805
7031
|
return skill;
|
|
6806
7032
|
}
|
|
6807
|
-
function loadSkillsFromDir(skillsDir, options
|
|
7033
|
+
function loadSkillsFromDir(skillsDir, options) {
|
|
6808
7034
|
const skills = [];
|
|
6809
7035
|
let entries;
|
|
6810
7036
|
try {
|
|
@@ -6826,12 +7052,18 @@ function loadSkillsFromDir(skillsDir, options = {}) {
|
|
|
6826
7052
|
const content = readFileSync(skillMdPath, "utf-8");
|
|
6827
7053
|
const { frontmatter, systemPrompt } = parseSkillMd(content);
|
|
6828
7054
|
const parsed = validateSkillFrontmatter(frontmatter, systemPrompt, {
|
|
7055
|
+
network: options.network,
|
|
6829
7056
|
allowFreeSkills: true,
|
|
6830
7057
|
// The CLI runtime wires an x402 driver into the skill context, so
|
|
6831
7058
|
// x402 skills are executable here (SDK-only hosts reject them).
|
|
6832
|
-
allowX402Skills: true
|
|
7059
|
+
allowX402Skills: true,
|
|
7060
|
+
// Surface SDK load-time warnings (e.g. the devnet lsm->SOL pricing
|
|
7061
|
+
// fallback) in the start flow's ` !` console pattern.
|
|
7062
|
+
logger: {
|
|
7063
|
+
warn: (_obj, msg) => console.warn(` ! ${msg ?? ""}`)
|
|
7064
|
+
}
|
|
6833
7065
|
});
|
|
6834
|
-
skills.push(buildCliSkill(parsed, entryPath, options.scriptEnv));
|
|
7066
|
+
skills.push(buildCliSkill(parsed, entryPath, options.scriptEnv, options.network));
|
|
6835
7067
|
} catch (e) {
|
|
6836
7068
|
const message = e instanceof Error ? e.message : String(e);
|
|
6837
7069
|
console.warn(` ! Skipping skill "${entry}": ${message}`);
|
|
@@ -7368,6 +7600,15 @@ function isTextContentType(contentType) {
|
|
|
7368
7600
|
const mime = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
7369
7601
|
return mime.startsWith("text/") || mime === "application/json" || mime.endsWith("+json");
|
|
7370
7602
|
}
|
|
7603
|
+
function concatChunks(chunks, total) {
|
|
7604
|
+
const combined = new Uint8Array(total);
|
|
7605
|
+
let offset = 0;
|
|
7606
|
+
for (const chunk of chunks) {
|
|
7607
|
+
combined.set(chunk, offset);
|
|
7608
|
+
offset += chunk.byteLength;
|
|
7609
|
+
}
|
|
7610
|
+
return combined;
|
|
7611
|
+
}
|
|
7371
7612
|
async function readBodyCapped(response, maxBytes) {
|
|
7372
7613
|
const reader = response.body?.getReader();
|
|
7373
7614
|
if (reader === void 0) {
|
|
@@ -7392,13 +7633,43 @@ async function readBodyCapped(response, maxBytes) {
|
|
|
7392
7633
|
}
|
|
7393
7634
|
chunks.push(value);
|
|
7394
7635
|
}
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7636
|
+
return concatChunks(chunks, total);
|
|
7637
|
+
}
|
|
7638
|
+
async function readBodyPrefix(response, maxBytes, timeoutMs) {
|
|
7639
|
+
const reader = response.body?.getReader();
|
|
7640
|
+
if (reader === void 0) {
|
|
7641
|
+
return { text: "", truncated: false };
|
|
7400
7642
|
}
|
|
7401
|
-
|
|
7643
|
+
const chunks = [];
|
|
7644
|
+
let total = 0;
|
|
7645
|
+
let ended = false;
|
|
7646
|
+
let timedOut = false;
|
|
7647
|
+
const deadline = setTimeout(() => {
|
|
7648
|
+
timedOut = true;
|
|
7649
|
+
reader.cancel().catch(() => {
|
|
7650
|
+
});
|
|
7651
|
+
}, timeoutMs);
|
|
7652
|
+
try {
|
|
7653
|
+
for (let reads = 0; reads < X402_MAX_ERROR_BODY_READS && total < maxBytes; reads += 1) {
|
|
7654
|
+
const { done, value } = await reader.read();
|
|
7655
|
+
if (done) {
|
|
7656
|
+
ended = true;
|
|
7657
|
+
break;
|
|
7658
|
+
}
|
|
7659
|
+
const fitting = value.subarray(0, maxBytes - total);
|
|
7660
|
+
total += fitting.byteLength;
|
|
7661
|
+
chunks.push(fitting);
|
|
7662
|
+
}
|
|
7663
|
+
} catch {
|
|
7664
|
+
} finally {
|
|
7665
|
+
clearTimeout(deadline);
|
|
7666
|
+
}
|
|
7667
|
+
reader.cancel().catch(() => {
|
|
7668
|
+
});
|
|
7669
|
+
return {
|
|
7670
|
+
text: new TextDecoder().decode(concatChunks(chunks, total)),
|
|
7671
|
+
truncated: !ended || timedOut
|
|
7672
|
+
};
|
|
7402
7673
|
}
|
|
7403
7674
|
async function capChallengeResponse(response) {
|
|
7404
7675
|
if (response.status !== 402) {
|
|
@@ -7411,6 +7682,105 @@ async function capChallengeResponse(response) {
|
|
|
7411
7682
|
headers: response.headers
|
|
7412
7683
|
});
|
|
7413
7684
|
}
|
|
7685
|
+
var PAYMENT_IDENTIFIER_EXTENSION = "payment-identifier";
|
|
7686
|
+
function asRecord(value) {
|
|
7687
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
7688
|
+
}
|
|
7689
|
+
function extensionInfo(declaration) {
|
|
7690
|
+
if (declaration === null) {
|
|
7691
|
+
return {};
|
|
7692
|
+
}
|
|
7693
|
+
return Object.hasOwn(declaration, "info") ? asRecord(declaration.info) ?? {} : declaration;
|
|
7694
|
+
}
|
|
7695
|
+
function requiredPaymentIdentifier(response) {
|
|
7696
|
+
const header = response.headers.get("payment-required");
|
|
7697
|
+
if (header === null || header.length > X402_MAX_CHALLENGE_HEADER_CHARS) {
|
|
7698
|
+
return null;
|
|
7699
|
+
}
|
|
7700
|
+
try {
|
|
7701
|
+
const decoded = JSON.parse(Buffer.from(header, "base64").toString("utf8"));
|
|
7702
|
+
const extensions = asRecord(asRecord(decoded)?.extensions);
|
|
7703
|
+
const declaration = asRecord(extensions?.[PAYMENT_IDENTIFIER_EXTENSION]);
|
|
7704
|
+
return extensionInfo(declaration).required === true ? declaration : null;
|
|
7705
|
+
} catch {
|
|
7706
|
+
return null;
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
function withPaymentIdentifier(headerValue, declaration, jobId) {
|
|
7710
|
+
try {
|
|
7711
|
+
const record = asRecord(JSON.parse(Buffer.from(headerValue, "base64").toString("utf8")));
|
|
7712
|
+
if (record === null) {
|
|
7713
|
+
return headerValue;
|
|
7714
|
+
}
|
|
7715
|
+
const transaction = asRecord(record.payload)?.transaction;
|
|
7716
|
+
const signed = typeof transaction === "string" ? transaction : headerValue;
|
|
7717
|
+
const digest = createHash("sha256").update(`${jobId}:${signed}`).digest("hex");
|
|
7718
|
+
const id = `pay_${digest.slice(0, 32)}`;
|
|
7719
|
+
const extensions = asRecord(record.extensions) ?? {};
|
|
7720
|
+
const echoed = asRecord(extensions[PAYMENT_IDENTIFIER_EXTENSION]) ?? {};
|
|
7721
|
+
const info = { id, ...extensionInfo(declaration), ...extensionInfo(echoed) };
|
|
7722
|
+
record.extensions = {
|
|
7723
|
+
...extensions,
|
|
7724
|
+
// Mirror the shape the server declared. Its echo VALIDATOR would accept
|
|
7725
|
+
// either (`@x402/core` normalizes both sides through `getExtensionInfo`
|
|
7726
|
+
// first), but whatever reads the id back out afterwards is the server's
|
|
7727
|
+
// own code, and the one place it can be counted on to look is where it
|
|
7728
|
+
// declared the extension.
|
|
7729
|
+
[PAYMENT_IDENTIFIER_EXTENSION]: Object.hasOwn(declaration, "info") ? { ...echoed, info } : info
|
|
7730
|
+
};
|
|
7731
|
+
return Buffer.from(JSON.stringify(record), "utf8").toString("base64");
|
|
7732
|
+
} catch {
|
|
7733
|
+
return headerValue;
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7736
|
+
var UNICODE_FORMAT_MARKS = /\p{Cf}/gu;
|
|
7737
|
+
function flattenForOperator(text) {
|
|
7738
|
+
return sanitizeForTerminal(text).replace(UNICODE_FORMAT_MARKS, "").replace(/\s+/g, " ").trim();
|
|
7739
|
+
}
|
|
7740
|
+
function printedPrefix(flattened) {
|
|
7741
|
+
return flattened.length <= X402_ERROR_EXCERPT_CHARS ? flattened : flattened.slice(0, X402_ERROR_EXCERPT_CHARS).replace(/[\ud800-\udbff]$/, "");
|
|
7742
|
+
}
|
|
7743
|
+
function clipForOperator(flattened) {
|
|
7744
|
+
return flattened.length <= X402_ERROR_EXCERPT_CHARS ? flattened : `${printedPrefix(flattened)}...`;
|
|
7745
|
+
}
|
|
7746
|
+
function quoteUpstream(text) {
|
|
7747
|
+
return clipForOperator(flattenForOperator(text));
|
|
7748
|
+
}
|
|
7749
|
+
function quoteUpstreamWithInput(text, sentInput) {
|
|
7750
|
+
return clipForOperator(maskCustomerInput(flattenForOperator(text), inputForms(sentInput)));
|
|
7751
|
+
}
|
|
7752
|
+
function wireForm(sentInput) {
|
|
7753
|
+
return new TextDecoder().decode(new TextEncoder().encode(sentInput));
|
|
7754
|
+
}
|
|
7755
|
+
function jsonEscapedForm(sentInput) {
|
|
7756
|
+
return JSON.stringify(sentInput).slice(1, -1);
|
|
7757
|
+
}
|
|
7758
|
+
function urlEncodedForms(sentInput) {
|
|
7759
|
+
const forms = [new URLSearchParams({ q: sentInput }).toString().slice("q=".length)];
|
|
7760
|
+
try {
|
|
7761
|
+
forms.push(encodeURIComponent(sentInput));
|
|
7762
|
+
} catch {
|
|
7763
|
+
}
|
|
7764
|
+
return forms;
|
|
7765
|
+
}
|
|
7766
|
+
function endsInsideInput(text, forms) {
|
|
7767
|
+
const tail = text.replace(/\ufffd+$/, "").slice(-X402_MASKED_INPUT_MIN_CHARS);
|
|
7768
|
+
return tail.length >= X402_MASKED_INPUT_MIN_CHARS && forms.some((form) => form.includes(tail));
|
|
7769
|
+
}
|
|
7770
|
+
function inputForms(sentInput) {
|
|
7771
|
+
return [wireForm(sentInput), jsonEscapedForm(sentInput), ...urlEncodedForms(sentInput)].map(
|
|
7772
|
+
flattenForOperator
|
|
7773
|
+
);
|
|
7774
|
+
}
|
|
7775
|
+
function maskCustomerInput(text, forms) {
|
|
7776
|
+
let masked = text;
|
|
7777
|
+
for (const form of forms) {
|
|
7778
|
+
if (form.length >= X402_MASKED_INPUT_MIN_CHARS) {
|
|
7779
|
+
masked = masked.replaceAll(form, "[input]");
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
return masked;
|
|
7783
|
+
}
|
|
7414
7784
|
function abortableDelay(ms, signal) {
|
|
7415
7785
|
return new Promise((resolve4) => {
|
|
7416
7786
|
if (signal?.aborted) {
|
|
@@ -7433,15 +7803,17 @@ var X402Driver = class {
|
|
|
7433
7803
|
this.options = options;
|
|
7434
7804
|
this.store = new X402JobStore(options.agentDir);
|
|
7435
7805
|
this.rpc = createSolanaRpc(options.rpcUrl);
|
|
7806
|
+
this.usdcAsset = resolveUsdcAsset(options.network);
|
|
7436
7807
|
this.store.sweepExpired().catch(() => {
|
|
7437
7808
|
});
|
|
7438
7809
|
}
|
|
7439
7810
|
store;
|
|
7440
7811
|
rpc;
|
|
7812
|
+
usdcAsset;
|
|
7441
7813
|
signerPromise = null;
|
|
7442
7814
|
repriceHintLogged = /* @__PURE__ */ new Set();
|
|
7443
7815
|
log(message) {
|
|
7444
|
-
(this.options.log ?? console.log)(`[x402] ${message}`);
|
|
7816
|
+
(this.options.log ?? console.log)(`[x402] ${flattenForOperator(message)}`);
|
|
7445
7817
|
}
|
|
7446
7818
|
getSigner() {
|
|
7447
7819
|
const secret = this.options.solanaSecretKeyBase58;
|
|
@@ -7498,13 +7870,16 @@ var X402Driver = class {
|
|
|
7498
7870
|
return;
|
|
7499
7871
|
}
|
|
7500
7872
|
const signer = await this.assertWalletInvariant();
|
|
7501
|
-
if (job.asset.mint !==
|
|
7873
|
+
if (job.asset.mint !== this.usdcAsset.mint || this.usdcAsset.mint === void 0) {
|
|
7502
7874
|
throw new X402PreflightError(
|
|
7503
|
-
`x402 skills must be priced in
|
|
7875
|
+
`x402 skills must be priced in ${this.options.network} USDC (skill "${job.skillName}" is priced in ${job.asset.symbol}); the margin check compares the elisym price to the upstream USDC quote - re-run \`npx @elisym/cli x402 add\``
|
|
7504
7876
|
);
|
|
7505
7877
|
}
|
|
7506
7878
|
this.assertInputRules(job, input);
|
|
7507
|
-
const rule = {
|
|
7879
|
+
const rule = {
|
|
7880
|
+
maxUpstreamSubunits: job.params.maxUpstreamSubunits,
|
|
7881
|
+
network: this.options.network
|
|
7882
|
+
};
|
|
7508
7883
|
let probe;
|
|
7509
7884
|
try {
|
|
7510
7885
|
probe = await probePaymentRequiredCached(
|
|
@@ -7514,10 +7889,10 @@ var X402Driver = class {
|
|
|
7514
7889
|
);
|
|
7515
7890
|
} catch (error) {
|
|
7516
7891
|
if (error instanceof X402ProbeError) {
|
|
7517
|
-
throw new X402PreflightError(`upstream probe failed: ${error.message}`);
|
|
7892
|
+
throw new X402PreflightError(`upstream probe failed: ${quoteUpstream(error.message)}`);
|
|
7518
7893
|
}
|
|
7519
7894
|
const message = error instanceof Error ? error.message : String(error);
|
|
7520
|
-
throw new X402PreflightError(`upstream unreachable: ${message}`);
|
|
7895
|
+
throw new X402PreflightError(`upstream unreachable: ${quoteUpstream(message)}`);
|
|
7521
7896
|
}
|
|
7522
7897
|
const quote = maxAcceptableQuote(probe.accepts, rule);
|
|
7523
7898
|
if (quote === null) {
|
|
@@ -7528,13 +7903,18 @@ var X402Driver = class {
|
|
|
7528
7903
|
if (quote < job.params.maxUpstreamSubunits && !this.repriceHintLogged.has(job.skillName)) {
|
|
7529
7904
|
this.repriceHintLogged.add(job.skillName);
|
|
7530
7905
|
this.log(
|
|
7531
|
-
`upstream for "${job.skillName}" now quotes ${formatAssetAmount(
|
|
7906
|
+
`upstream for "${job.skillName}" now quotes ${formatAssetAmount(this.usdcAsset, quote)} (your price was computed from ${formatAssetAmount(this.usdcAsset, job.params.maxUpstreamSubunits)}); re-run \`npx @elisym/cli x402 add\` to reprice`
|
|
7907
|
+
);
|
|
7908
|
+
}
|
|
7909
|
+
const balance = await fetchUsdcBalance(this.rpc, address(signer.address), this.options.network);
|
|
7910
|
+
if (balance === null) {
|
|
7911
|
+
throw new X402PreflightError(
|
|
7912
|
+
`could not read the bridge float balance for ${signer.address} - refusing to pay the upstream without verifying it; retry in a moment`
|
|
7532
7913
|
);
|
|
7533
7914
|
}
|
|
7534
|
-
const balance = await fetchUsdcBalance(this.rpc, address(signer.address));
|
|
7535
7915
|
if (balance < quote) {
|
|
7536
7916
|
throw new X402PreflightError(
|
|
7537
|
-
`bridge float insufficient: ${formatAssetAmount(
|
|
7917
|
+
`bridge float insufficient: ${formatAssetAmount(this.usdcAsset, balance)} USDC in ${signer.address}, upstream quotes ${formatAssetAmount(this.usdcAsset, quote)} - top up the wallet`
|
|
7538
7918
|
);
|
|
7539
7919
|
}
|
|
7540
7920
|
const feeBps = await this.options.getFeeBps();
|
|
@@ -7542,7 +7922,7 @@ var X402Driver = class {
|
|
|
7542
7922
|
const net = BigInt(job.priceSubunits - fee);
|
|
7543
7923
|
if (net < quote) {
|
|
7544
7924
|
throw new X402PreflightError(
|
|
7545
|
-
`negative margin: net revenue ${formatAssetAmount(
|
|
7925
|
+
`negative margin: net revenue ${formatAssetAmount(this.usdcAsset, net)} (price minus ${feeBps} bps protocol fee) is below the upstream quote ${formatAssetAmount(this.usdcAsset, quote)} - re-run \`npx @elisym/cli x402 add\` to reprice`
|
|
7546
7926
|
);
|
|
7547
7927
|
}
|
|
7548
7928
|
}
|
|
@@ -7558,6 +7938,7 @@ var X402Driver = class {
|
|
|
7558
7938
|
*/
|
|
7559
7939
|
buildInstrumentedFetch(jobId, attemptState) {
|
|
7560
7940
|
const store = this.store;
|
|
7941
|
+
let requiredIdentifier = null;
|
|
7561
7942
|
return async function instrumentedFetch(info, init) {
|
|
7562
7943
|
const request = new Request(info, init);
|
|
7563
7944
|
if (request.headers.has("PAYMENT-SIGNATURE") || request.headers.has("X-PAYMENT")) {
|
|
@@ -7572,19 +7953,30 @@ var X402Driver = class {
|
|
|
7572
7953
|
);
|
|
7573
7954
|
}
|
|
7574
7955
|
attemptState.paymentSent = true;
|
|
7956
|
+
const signature = request.headers.get("PAYMENT-SIGNATURE");
|
|
7957
|
+
if (requiredIdentifier !== null && signature !== null) {
|
|
7958
|
+
request.headers.set(
|
|
7959
|
+
"PAYMENT-SIGNATURE",
|
|
7960
|
+
withPaymentIdentifier(signature, requiredIdentifier, jobId)
|
|
7961
|
+
);
|
|
7962
|
+
}
|
|
7575
7963
|
}
|
|
7576
7964
|
const response = await globalThis.fetch(new Request(request, { redirect: "error" }));
|
|
7965
|
+
if (response.status === 402) {
|
|
7966
|
+
requiredIdentifier = requiredPaymentIdentifier(response);
|
|
7967
|
+
}
|
|
7577
7968
|
return capChallengeResponse(response);
|
|
7578
7969
|
};
|
|
7579
7970
|
}
|
|
7580
7971
|
buildClient(signer, rule) {
|
|
7581
7972
|
const scheme = new ExactSvmScheme(signer, { rpcUrl: this.options.rpcUrl });
|
|
7973
|
+
const networkIds = x402SolanaNetworkIds(this.options.network);
|
|
7582
7974
|
return x402Client.fromConfig({
|
|
7583
|
-
schemes: [{ network:
|
|
7975
|
+
schemes: [{ network: networkIds.caip2, client: scheme }],
|
|
7584
7976
|
policies: [buildRequirementsPolicy(rule)]
|
|
7585
|
-
}).registerV1(
|
|
7977
|
+
}).registerV1(networkIds.v1, scheme);
|
|
7586
7978
|
}
|
|
7587
|
-
classifyWrapperError(error) {
|
|
7979
|
+
classifyWrapperError(error, sentInput) {
|
|
7588
7980
|
if (error instanceof Error && error.name === "AbortError") {
|
|
7589
7981
|
return error;
|
|
7590
7982
|
}
|
|
@@ -7592,21 +7984,63 @@ var X402Driver = class {
|
|
|
7592
7984
|
return error;
|
|
7593
7985
|
}
|
|
7594
7986
|
const message = error instanceof Error ? error.message : String(error);
|
|
7987
|
+
const quoted = quoteUpstreamWithInput(message, sentInput);
|
|
7595
7988
|
if (message.includes("Failed to create payment payload")) {
|
|
7596
7989
|
return new X402PermanentError(
|
|
7597
|
-
`payment refused by policy (upstream likely repriced above x402_max_upstream): ${
|
|
7990
|
+
`payment refused by policy (upstream likely repriced above x402_max_upstream): ${quoted}`
|
|
7991
|
+
);
|
|
7992
|
+
}
|
|
7993
|
+
return new X402TransientError(`upstream request failed: ${quoted}`);
|
|
7994
|
+
}
|
|
7995
|
+
/**
|
|
7996
|
+
* Read a short, safe excerpt of a failing upstream's body.
|
|
7997
|
+
*
|
|
7998
|
+
* A bare status code is not a diagnosis: a 4xx from an x402 upstream can
|
|
7999
|
+
* mean a rejected payload, a missing required extension, or an argument the
|
|
8000
|
+
* service did not like, and the operator cannot tell which without the
|
|
8001
|
+
* body's own words. The body is untrusted, so only a bounded prefix is read
|
|
8002
|
+
* at all, and it is stripped of anything that could forge a log line before
|
|
8003
|
+
* it reaches the operator's. Reading it never fails the caller - an
|
|
8004
|
+
* unreadable body simply yields no excerpt, which is exactly the
|
|
8005
|
+
* status-only message as before.
|
|
8006
|
+
*
|
|
8007
|
+
* `sentInput` is the customer's own job input, masked out of the quote by
|
|
8008
|
+
* `maskCustomerInput`: an upstream that echoes the request back would
|
|
8009
|
+
* otherwise put it in the operator's log, which the runtime keeps it out of
|
|
8010
|
+
* everywhere else.
|
|
8011
|
+
*/
|
|
8012
|
+
async errorExcerpt(response, sentInput) {
|
|
8013
|
+
try {
|
|
8014
|
+
const body = await readBodyPrefix(
|
|
8015
|
+
response,
|
|
8016
|
+
X402_MAX_ERROR_BODY_BYTES,
|
|
8017
|
+
this.options.errorBodyReadMs ?? X402_ERROR_BODY_READ_MS
|
|
7598
8018
|
);
|
|
8019
|
+
const forms = inputForms(sentInput);
|
|
8020
|
+
const quoted = maskCustomerInput(flattenForOperator(body.text), forms);
|
|
8021
|
+
if (quoted.length === 0) {
|
|
8022
|
+
return "";
|
|
8023
|
+
}
|
|
8024
|
+
const shown = printedPrefix(quoted);
|
|
8025
|
+
if (body.truncated && endsInsideInput(shown, forms)) {
|
|
8026
|
+
return "";
|
|
8027
|
+
}
|
|
8028
|
+
return `: ${clipForOperator(quoted)}`;
|
|
8029
|
+
} catch {
|
|
8030
|
+
return "";
|
|
7599
8031
|
}
|
|
7600
|
-
return new X402TransientError(`upstream request failed: ${message}`);
|
|
7601
8032
|
}
|
|
7602
|
-
classifyStatus(status) {
|
|
8033
|
+
classifyStatus(status, excerpt) {
|
|
7603
8034
|
if (status === 402) {
|
|
7604
|
-
return new X402TransientError(
|
|
8035
|
+
return new X402TransientError(
|
|
8036
|
+
`upstream still returned 402 after a payment attempt${excerpt}`,
|
|
8037
|
+
status
|
|
8038
|
+
);
|
|
7605
8039
|
}
|
|
7606
8040
|
if (status === 429 || status >= 500) {
|
|
7607
|
-
return new X402TransientError(`upstream returned ${status}`, status);
|
|
8041
|
+
return new X402TransientError(`upstream returned ${status}${excerpt}`, status);
|
|
7608
8042
|
}
|
|
7609
|
-
return new X402PermanentError(`upstream returned ${status}`);
|
|
8043
|
+
return new X402PermanentError(`upstream returned ${status}${excerpt}`);
|
|
7610
8044
|
}
|
|
7611
8045
|
async execute(job, input, signal) {
|
|
7612
8046
|
const cached = await this.store.getResult(input.jobId);
|
|
@@ -7637,7 +8071,10 @@ var X402Driver = class {
|
|
|
7637
8071
|
headers["content-type"] = jsonContentType(input.data) ? "application/json" : "text/plain";
|
|
7638
8072
|
}
|
|
7639
8073
|
const signer = await this.assertWalletInvariant();
|
|
7640
|
-
const rule = {
|
|
8074
|
+
const rule = {
|
|
8075
|
+
maxUpstreamSubunits: job.params.maxUpstreamSubunits,
|
|
8076
|
+
network: this.options.network
|
|
8077
|
+
};
|
|
7641
8078
|
const client = this.buildClient(signer, rule);
|
|
7642
8079
|
const jobTag = input.jobId.slice(0, 8);
|
|
7643
8080
|
let freeRetriesUsed = 0;
|
|
@@ -7650,7 +8087,7 @@ var X402Driver = class {
|
|
|
7650
8087
|
requestUrl,
|
|
7651
8088
|
headers,
|
|
7652
8089
|
body,
|
|
7653
|
-
input
|
|
8090
|
+
input,
|
|
7654
8091
|
client,
|
|
7655
8092
|
attemptState,
|
|
7656
8093
|
signal
|
|
@@ -7698,9 +8135,9 @@ var X402Driver = class {
|
|
|
7698
8135
|
}
|
|
7699
8136
|
}
|
|
7700
8137
|
/** One full payment-wrapper pass against the upstream; throws classified errors. */
|
|
7701
|
-
async attemptUpstream(job, requestUrl, headers, body,
|
|
8138
|
+
async attemptUpstream(job, requestUrl, headers, body, input, client, attemptState, signal) {
|
|
7702
8139
|
const fetchWithPayment = wrapFetchWithPayment(
|
|
7703
|
-
this.buildInstrumentedFetch(jobId, attemptState),
|
|
8140
|
+
this.buildInstrumentedFetch(input.jobId, attemptState),
|
|
7704
8141
|
client
|
|
7705
8142
|
);
|
|
7706
8143
|
let response;
|
|
@@ -7712,27 +8149,25 @@ var X402Driver = class {
|
|
|
7712
8149
|
signal
|
|
7713
8150
|
});
|
|
7714
8151
|
} catch (error) {
|
|
7715
|
-
throw this.classifyWrapperError(error);
|
|
8152
|
+
throw this.classifyWrapperError(error, input.data);
|
|
7716
8153
|
}
|
|
7717
8154
|
if (!response.ok) {
|
|
7718
|
-
|
|
7719
|
-
});
|
|
7720
|
-
throw this.classifyStatus(response.status);
|
|
8155
|
+
throw this.classifyStatus(response.status, await this.errorExcerpt(response, input.data));
|
|
7721
8156
|
}
|
|
7722
8157
|
let bytes;
|
|
7723
8158
|
try {
|
|
7724
8159
|
bytes = await readBodyCapped(response, X402_MAX_RESPONSE_BYTES);
|
|
7725
8160
|
} catch (error) {
|
|
7726
|
-
throw this.classifyWrapperError(error);
|
|
8161
|
+
throw this.classifyWrapperError(error, input.data);
|
|
7727
8162
|
}
|
|
7728
8163
|
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
|
|
7729
8164
|
if (isTextContentType(contentType)) {
|
|
7730
8165
|
const data = new TextDecoder().decode(bytes);
|
|
7731
|
-
await this.store.saveTextResult(jobId, data);
|
|
8166
|
+
await this.store.saveTextResult(input.jobId, data);
|
|
7732
8167
|
return { data };
|
|
7733
8168
|
}
|
|
7734
8169
|
const mime = contentType.split(";")[0]?.trim() ?? "application/octet-stream";
|
|
7735
|
-
const filePath = await this.store.saveFileResult(jobId, mime, bytes);
|
|
8170
|
+
const filePath = await this.store.saveFileResult(input.jobId, mime, bytes);
|
|
7736
8171
|
return { data: "", outputMime: mime, filePath };
|
|
7737
8172
|
}
|
|
7738
8173
|
};
|
|
@@ -7770,9 +8205,10 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
7770
8205
|
const rpcUrl = getRpcUrl(walletNetwork);
|
|
7771
8206
|
const rpc = createSolanaRpc(rpcUrl);
|
|
7772
8207
|
const walletAddress = address(solanaAddress);
|
|
7773
|
-
const
|
|
8208
|
+
const splAssets = splAssetsForNetwork(walletNetwork);
|
|
8209
|
+
const [{ value: balanceLamports }, ...splBalances] = await Promise.all([
|
|
7774
8210
|
rpc.getBalance(walletAddress).send(),
|
|
7775
|
-
|
|
8211
|
+
...splAssets.map((asset) => fetchSplBalance(rpc, walletAddress, asset))
|
|
7776
8212
|
]);
|
|
7777
8213
|
const balance = Number(balanceLamports);
|
|
7778
8214
|
console.log(" Wallet");
|
|
@@ -7782,9 +8218,16 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
7782
8218
|
console.log(` RPC ${stripRpcSecrets(process.env.SOLANA_RPC_URL)} (custom)`);
|
|
7783
8219
|
}
|
|
7784
8220
|
console.log(` SOL ${formatSol(balance)} (${balance} lamports)`);
|
|
7785
|
-
|
|
8221
|
+
for (const [index, asset] of splAssets.entries()) {
|
|
8222
|
+
const symbolColumn = asset.symbol.padEnd(8);
|
|
8223
|
+
console.log(` ${symbolColumn} ${formatSplBalanceValue(asset, splBalances[index])}`);
|
|
8224
|
+
}
|
|
7786
8225
|
if (balance === 0) {
|
|
7787
|
-
|
|
8226
|
+
if (walletNetwork === "mainnet") {
|
|
8227
|
+
console.log(" ! Wallet is empty. Fund it with real SOL to operate on mainnet.");
|
|
8228
|
+
} else {
|
|
8229
|
+
console.log(" ! Wallet is empty. Get devnet SOL: https://faucet.solana.com");
|
|
8230
|
+
}
|
|
7788
8231
|
}
|
|
7789
8232
|
console.log();
|
|
7790
8233
|
} catch (e) {
|
|
@@ -7793,10 +8236,15 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
7793
8236
|
`);
|
|
7794
8237
|
}
|
|
7795
8238
|
}
|
|
8239
|
+
if (walletNetwork === "mainnet") {
|
|
8240
|
+
console.log(
|
|
8241
|
+
" ! Mainnet: per-skill prices are REAL funds. Review each SKILL.md price - skills copied from a devnet agent keep their absolute amounts.\n"
|
|
8242
|
+
);
|
|
8243
|
+
}
|
|
7796
8244
|
const scriptEnv = buildScriptEnv(loaded.secrets);
|
|
7797
8245
|
const paths = agentPaths(loaded.dir);
|
|
7798
8246
|
const skillsDir = paths.skills;
|
|
7799
|
-
const allSkills = loadSkillsFromDir(skillsDir, { scriptEnv });
|
|
8247
|
+
const allSkills = loadSkillsFromDir(skillsDir, { network: walletNetwork, scriptEnv });
|
|
7800
8248
|
if (allSkills.length === 0) {
|
|
7801
8249
|
console.error(` ! No skills found in ${skillsDir}
|
|
7802
8250
|
`);
|
|
@@ -7994,6 +8442,12 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
7994
8442
|
agentName,
|
|
7995
8443
|
agentDescription: loaded.yaml.description ?? ""
|
|
7996
8444
|
};
|
|
8445
|
+
const onchainSkillCount = allSkills.filter((skill) => skill.onchain !== void 0).length;
|
|
8446
|
+
if (solanaAddress === void 0 && onchainSkillCount > 0) {
|
|
8447
|
+
console.warn(
|
|
8448
|
+
` ! ${onchainSkillCount} onchain capability(ies) declare a block but this agent has no Solana payment address, so the network their cards would promise is a guess. Add a payments entry to elisym.yaml.`
|
|
8449
|
+
);
|
|
8450
|
+
}
|
|
7997
8451
|
const x402Skills = allSkills.filter((skill) => skill.mode === "x402");
|
|
7998
8452
|
let x402InvariantBroken;
|
|
7999
8453
|
if (x402Skills.length > 0) {
|
|
@@ -8003,15 +8457,19 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8003
8457
|
} else if (!solanaAddress) {
|
|
8004
8458
|
x402InvariantBroken = "no payments[] entry in elisym.yaml";
|
|
8005
8459
|
} else {
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8460
|
+
try {
|
|
8461
|
+
const bridgeSigner = await signerFromSecretKeyBase58(solanaSecretKey);
|
|
8462
|
+
if (bridgeSigner.address !== solanaAddress) {
|
|
8463
|
+
x402InvariantBroken = `payments[].address (${solanaAddress}) differs from the solana_secret_key address (${bridgeSigner.address})`;
|
|
8464
|
+
}
|
|
8465
|
+
} catch (error) {
|
|
8466
|
+
x402InvariantBroken = `solana_secret_key is present but unreadable - ${error instanceof Error ? error.message : String(error)}`;
|
|
8009
8467
|
}
|
|
8010
8468
|
}
|
|
8011
8469
|
for (const skill of x402Skills) {
|
|
8012
|
-
if (skill.asset.mint !==
|
|
8470
|
+
if (skill.asset.mint !== resolveUsdcAsset(walletNetwork).mint) {
|
|
8013
8471
|
console.warn(
|
|
8014
|
-
` ! x402 skill "${skill.name}" is priced in ${skill.asset.symbol}, not
|
|
8472
|
+
` ! x402 skill "${skill.name}" is priced in ${skill.asset.symbol}, not ${walletNetwork} USDC - every job will be refused at preflight. Re-run \`npx @elisym/cli x402 add\`.`
|
|
8015
8473
|
);
|
|
8016
8474
|
}
|
|
8017
8475
|
}
|
|
@@ -8021,24 +8479,27 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8021
8479
|
" ! x402 skills will NOT be advertised and their jobs will be refused before payment."
|
|
8022
8480
|
);
|
|
8023
8481
|
console.warn(" ! Fix: re-run `npx @elisym/cli x402 add` or align elisym.yaml with the key.");
|
|
8482
|
+
} else {
|
|
8483
|
+
const x402RpcUrl = getRpcUrl(walletNetwork);
|
|
8484
|
+
async function fetchLiveFeeBps() {
|
|
8485
|
+
const config = await getProtocolConfig(
|
|
8486
|
+
createSolanaRpc(x402RpcUrl),
|
|
8487
|
+
getProtocolProgramId(walletNetwork),
|
|
8488
|
+
walletNetwork,
|
|
8489
|
+
{ forceRefresh: true }
|
|
8490
|
+
);
|
|
8491
|
+
return config.feeBps;
|
|
8492
|
+
}
|
|
8493
|
+
skillCtx.x402Driver = new X402Driver({
|
|
8494
|
+
agentDir: loaded.dir,
|
|
8495
|
+
paymentsAddress: solanaAddress,
|
|
8496
|
+
solanaSecretKeyBase58: loaded.secrets.solana_secret_key,
|
|
8497
|
+
rpcUrl: x402RpcUrl,
|
|
8498
|
+
network: walletNetwork,
|
|
8499
|
+
getFeeBps: fetchLiveFeeBps,
|
|
8500
|
+
log: (message) => console.log(` ${message}`)
|
|
8501
|
+
});
|
|
8024
8502
|
}
|
|
8025
|
-
const x402RpcUrl = getRpcUrl();
|
|
8026
|
-
async function fetchLiveFeeBps() {
|
|
8027
|
-
const config = await getProtocolConfig(
|
|
8028
|
-
createSolanaRpc(x402RpcUrl),
|
|
8029
|
-
getProtocolProgramId("devnet"),
|
|
8030
|
-
{ forceRefresh: true }
|
|
8031
|
-
);
|
|
8032
|
-
return config.feeBps;
|
|
8033
|
-
}
|
|
8034
|
-
skillCtx.x402Driver = new X402Driver({
|
|
8035
|
-
agentDir: loaded.dir,
|
|
8036
|
-
paymentsAddress: solanaAddress,
|
|
8037
|
-
solanaSecretKeyBase58: loaded.secrets.solana_secret_key,
|
|
8038
|
-
rpcUrl: x402RpcUrl,
|
|
8039
|
-
getFeeBps: fetchLiveFeeBps,
|
|
8040
|
-
log: (message) => console.log(` ${message}`)
|
|
8041
|
-
});
|
|
8042
8503
|
}
|
|
8043
8504
|
console.log(" Connecting to relays and publishing capabilities...");
|
|
8044
8505
|
const identity2 = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
@@ -8216,11 +8677,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8216
8677
|
const declaresDelegation = allSkills.some((skill) => skill.delegation !== void 0);
|
|
8217
8678
|
let delegatePubkey;
|
|
8218
8679
|
let delegateSigner;
|
|
8219
|
-
if (declaresDelegation
|
|
8220
|
-
console.warn(
|
|
8221
|
-
` ! A skill declares delegation but it is devnet-only today (network: ${walletNetwork}) - omitted from its card(s).`
|
|
8222
|
-
);
|
|
8223
|
-
} else if (declaresDelegation) {
|
|
8680
|
+
if (declaresDelegation) {
|
|
8224
8681
|
const delegateSecret = loaded.secrets.solana_delegate_secret_key;
|
|
8225
8682
|
if (delegateSecret && delegateSecret.length > 0) {
|
|
8226
8683
|
try {
|
|
@@ -8231,7 +8688,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8231
8688
|
const { value: delegateLamports } = await createSolanaRpc(getRpcUrl(walletNetwork)).getBalance(address(delegatePubkey)).send();
|
|
8232
8689
|
if (delegateLamports === 0n) {
|
|
8233
8690
|
console.warn(
|
|
8234
|
-
" ! Delegate key holds no SOL - delegated job pulls will fail on tx fees. Fund it: https://faucet.solana.com"
|
|
8691
|
+
" ! Delegate key holds no SOL - delegated job pulls will fail on tx fees. " + (walletNetwork === "mainnet" ? "Fund it with real SOL." : "Fund it: https://faucet.solana.com")
|
|
8235
8692
|
);
|
|
8236
8693
|
}
|
|
8237
8694
|
} catch {
|
|
@@ -8253,38 +8710,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8253
8710
|
console.warn(` ! Run \`npx @elisym/cli delegate-key ${loaded.name}\` to enable it.`);
|
|
8254
8711
|
}
|
|
8255
8712
|
}
|
|
8256
|
-
|
|
8257
|
-
const isStatic = skill.mode === "static-file" || skill.mode === "static-script" || skill.noInput === true;
|
|
8258
|
-
return {
|
|
8259
|
-
name: skill.name,
|
|
8260
|
-
description: skill.description,
|
|
8261
|
-
capabilities: skill.capabilities,
|
|
8262
|
-
image: skill.image,
|
|
8263
|
-
...isStatic ? { static: true } : {},
|
|
8264
|
-
// File-exchange hints (dynamic-script only). `inputMime` flags a file input;
|
|
8265
|
-
// `inputText` tells the web whether to also show its text box for that file job.
|
|
8266
|
-
...skill.inputMime ? { inputMime: skill.inputMime } : {},
|
|
8267
|
-
...skill.inputText ? { inputText: skill.inputText } : {},
|
|
8268
|
-
...skill.outputMime ? { outputMime: skill.outputMime } : {},
|
|
8269
|
-
// Conversation-context flag: clients gate chat affordances (session-carrying
|
|
8270
|
-
// sends) on it. Strict true only - the read side coerces anything else away.
|
|
8271
|
-
...skill.context === true ? { context: true } : {},
|
|
8272
|
-
// Delegation descriptor: only when the skill opts in AND a delegate key
|
|
8273
|
-
// exists (delegatePubkey resolved). The pubkey is injected here, never
|
|
8274
|
-
// taken from the SKILL.md - the operator declares only the cap/mechanism.
|
|
8275
|
-
...skill.delegation && delegatePubkey ? { delegation: { ...skill.delegation, delegate_pubkey: delegatePubkey } } : {},
|
|
8276
|
-
payment: solanaAddress ? {
|
|
8277
|
-
chain: "solana",
|
|
8278
|
-
network: walletNetwork,
|
|
8279
|
-
address: solanaAddress,
|
|
8280
|
-
job_price: skill.priceSubunits,
|
|
8281
|
-
token: skill.asset.token,
|
|
8282
|
-
...skill.asset.mint ? { mint: skill.asset.mint } : {},
|
|
8283
|
-
decimals: skill.asset.decimals,
|
|
8284
|
-
symbol: skill.asset.symbol
|
|
8285
|
-
} : void 0
|
|
8286
|
-
};
|
|
8287
|
-
}
|
|
8713
|
+
const buildCard = (skill) => buildCapabilityCard(skill, { walletNetwork, solanaAddress, delegatePubkey });
|
|
8288
8714
|
function retiredModelKeyForSkill(skill) {
|
|
8289
8715
|
if (skill.resolvedTriple) {
|
|
8290
8716
|
return `${skill.resolvedTriple.provider}::${skill.resolvedTriple.model}`;
|
|
@@ -8313,6 +8739,16 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8313
8739
|
);
|
|
8314
8740
|
continue;
|
|
8315
8741
|
}
|
|
8742
|
+
if (withholdsOnchainCard(skill, solanaAddress)) {
|
|
8743
|
+
console.warn(
|
|
8744
|
+
` ! Not republishing "${skill.name}" - no Solana payments entry. An earlier card for it, if any, stays on the relays until it expires.`
|
|
8745
|
+
);
|
|
8746
|
+
logger.warn(
|
|
8747
|
+
{ event: "publish_skipped_onchain_no_wallet", kind: 31990, skill: skill.name },
|
|
8748
|
+
"capability not advertised - onchain block with no Solana payment address"
|
|
8749
|
+
);
|
|
8750
|
+
continue;
|
|
8751
|
+
}
|
|
8316
8752
|
try {
|
|
8317
8753
|
await client.discovery.publishCapability(identity2, buildCard(skill), kinds);
|
|
8318
8754
|
cardsPublished += 1;
|
|
@@ -8391,7 +8827,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8391
8827
|
if (nonceStore !== void 0) {
|
|
8392
8828
|
await ensureGitignoreHasDelegationNoncesEntry(dirname(loaded.dir));
|
|
8393
8829
|
}
|
|
8394
|
-
const rpcUrlForLog = stripRpcSecrets(process.env.SOLANA_RPC_URL ?? getRpcUrl());
|
|
8830
|
+
const rpcUrlForLog = stripRpcSecrets(process.env.SOLANA_RPC_URL ?? getRpcUrl(walletNetwork));
|
|
8395
8831
|
logger.debug(
|
|
8396
8832
|
{
|
|
8397
8833
|
event: "config_resolved",
|
|
@@ -8469,6 +8905,49 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
8469
8905
|
console.log(" * Running. Press Ctrl+C to stop.\n");
|
|
8470
8906
|
await runtime.run();
|
|
8471
8907
|
}
|
|
8908
|
+
function buildCapabilityCard(skill, inputs) {
|
|
8909
|
+
const { walletNetwork, solanaAddress, delegatePubkey } = inputs;
|
|
8910
|
+
const isStatic = skill.mode === "static-file" || skill.mode === "static-script" || skill.noInput === true;
|
|
8911
|
+
return {
|
|
8912
|
+
name: skill.name,
|
|
8913
|
+
description: skill.description,
|
|
8914
|
+
capabilities: skill.capabilities,
|
|
8915
|
+
image: skill.image,
|
|
8916
|
+
...isStatic ? { static: true } : {},
|
|
8917
|
+
// File-exchange hints (dynamic-script only). `inputMime` flags a file input;
|
|
8918
|
+
// `inputText` tells the web whether to also show its text box for that file job.
|
|
8919
|
+
...skill.inputMime ? { inputMime: skill.inputMime } : {},
|
|
8920
|
+
...skill.inputText ? { inputText: skill.inputText } : {},
|
|
8921
|
+
...skill.outputMime ? { outputMime: skill.outputMime } : {},
|
|
8922
|
+
// Conversation-context flag: clients gate chat affordances (session-carrying
|
|
8923
|
+
// sends) on it. Strict true only - the read side coerces anything else away.
|
|
8924
|
+
...skill.context === true ? { context: true } : {},
|
|
8925
|
+
// Delegation descriptor: only when the skill opts in AND a delegate key
|
|
8926
|
+
// exists (delegatePubkey resolved). The pubkey is injected here, never
|
|
8927
|
+
// taken from the SKILL.md - the operator declares only the cap/mechanism.
|
|
8928
|
+
...skill.delegation && delegatePubkey ? { delegation: { ...skill.delegation, delegate_pubkey: delegatePubkey } } : {},
|
|
8929
|
+
// On-chain promise (`mode: onchain`): the network is stamped from the
|
|
8930
|
+
// agent's wallet here, never from SKILL.md, so a capability copied between
|
|
8931
|
+
// a devnet and a mainnet agent cannot advertise the wrong chain.
|
|
8932
|
+
// Only stamped when the agent actually has a Solana payment entry: without
|
|
8933
|
+
// one `walletNetwork` is a default, and a card must never promise a chain
|
|
8934
|
+
// the operator never chose. `cmdStart` warns for such a skill at step 7a.
|
|
8935
|
+
...skill.onchain && solanaAddress ? { onchain: { ...skill.onchain, network: walletNetwork } } : {},
|
|
8936
|
+
payment: solanaAddress ? {
|
|
8937
|
+
chain: "solana",
|
|
8938
|
+
network: walletNetwork,
|
|
8939
|
+
address: solanaAddress,
|
|
8940
|
+
job_price: skill.priceSubunits,
|
|
8941
|
+
token: skill.asset.token,
|
|
8942
|
+
...skill.asset.mint ? { mint: skill.asset.mint } : {},
|
|
8943
|
+
decimals: skill.asset.decimals,
|
|
8944
|
+
symbol: skill.asset.symbol
|
|
8945
|
+
} : void 0
|
|
8946
|
+
};
|
|
8947
|
+
}
|
|
8948
|
+
function withholdsOnchainCard(skill, solanaAddress) {
|
|
8949
|
+
return skill.onchain !== void 0 && solanaAddress === void 0;
|
|
8950
|
+
}
|
|
8472
8951
|
function buildScriptEnv(secrets) {
|
|
8473
8952
|
const scriptEnv = { ...process.env };
|
|
8474
8953
|
delete scriptEnv.ELISYM_PASSPHRASE;
|
|
@@ -8689,15 +9168,20 @@ async function cmdWallet(name) {
|
|
|
8689
9168
|
const rpcUrl = getRpcUrl(solPayment.network);
|
|
8690
9169
|
const rpc = createSolanaRpc(rpcUrl);
|
|
8691
9170
|
const walletAddress = address(solPayment.address);
|
|
8692
|
-
const
|
|
8693
|
-
const
|
|
9171
|
+
const splAssets = splAssetsForNetwork(solPayment.network);
|
|
9172
|
+
const [{ value: balance }, ...splBalances] = await Promise.all([
|
|
9173
|
+
rpc.getBalance(walletAddress).send(),
|
|
9174
|
+
...splAssets.map((asset) => fetchSplBalance(rpc, walletAddress, asset))
|
|
9175
|
+
]);
|
|
8694
9176
|
console.log(`
|
|
8695
9177
|
Agent: ${name}`);
|
|
8696
9178
|
console.log(` Network: ${solPayment.network}`);
|
|
8697
9179
|
console.log(` Address: ${solPayment.address}`);
|
|
8698
9180
|
console.log(` SOL balance: ${formatSol(Number(balance))} (${balance} lamports)`);
|
|
8699
|
-
|
|
8700
|
-
`);
|
|
9181
|
+
for (const [index, asset] of splAssets.entries()) {
|
|
9182
|
+
console.log(` ${asset.symbol} balance: ${formatSplBalanceValue(asset, splBalances[index])}`);
|
|
9183
|
+
}
|
|
9184
|
+
console.log();
|
|
8701
9185
|
}
|
|
8702
9186
|
function readPackageVersion() {
|
|
8703
9187
|
try {
|
|
@@ -8727,6 +9211,9 @@ program.command("init [name]").description("Create a new agent").option("-c, --c
|
|
|
8727
9211
|
"--defaults",
|
|
8728
9212
|
"Skip every prompt and use the same defaults the wizard would have suggested (description, default relays, no payments, no LLM, no encryption). Mutually exclusive with --config."
|
|
8729
9213
|
).option("--local", "Create in project <project>/.elisym/<name>/ (default: ~/.elisym/<name>/)").option(
|
|
9214
|
+
"--network <network>",
|
|
9215
|
+
"Solana network for the wallet entry: devnet (default) or mainnet (REAL funds, no faucet). The network is fixed at init - to go mainnet later, create a new agent."
|
|
9216
|
+
).option(
|
|
8730
9217
|
"--passphrase <value>",
|
|
8731
9218
|
'Passphrase to encrypt secrets at rest. Empty string ("") skips encryption. Also reads ELISYM_PASSPHRASE env var. When neither is provided, prompts interactively.'
|
|
8732
9219
|
).option(
|
|
@@ -8756,16 +9243,16 @@ program.command("list").description("List all agents (project-local and home-glo
|
|
|
8756
9243
|
}
|
|
8757
9244
|
console.log("\nAgents:");
|
|
8758
9245
|
for (const agent of agents) {
|
|
9246
|
+
const solana = await solanaLineFor(agent);
|
|
9247
|
+
const shadow = agent.shadowsGlobal ? " [shadows global]" : "";
|
|
8759
9248
|
try {
|
|
8760
9249
|
const loaded = await loadAgent(agent.name, cwd);
|
|
8761
9250
|
const identity2 = ElisymIdentity.fromHex(loaded.secrets.nostr_secret_key);
|
|
8762
9251
|
const npub = nip19.npubEncode(identity2.publicKey);
|
|
8763
|
-
|
|
8764
|
-
const shadow = agent.shadowsGlobal ? " [shadows global]" : "";
|
|
8765
|
-
console.log(` ${agent.name} (${agent.source})${shadow} | ${npub}${solAddr}`);
|
|
9252
|
+
console.log(` ${agent.name} (${agent.source})${shadow} | ${npub}${solana}`);
|
|
8766
9253
|
} catch (e) {
|
|
8767
9254
|
const hint = /encrypted secrets/i.test(e?.message ?? "") ? " (encrypted)" : "";
|
|
8768
|
-
console.log(` ${agent.name} (${agent.source})${hint}`);
|
|
9255
|
+
console.log(` ${agent.name} (${agent.source})${shadow}${hint}${solana}`);
|
|
8769
9256
|
}
|
|
8770
9257
|
}
|
|
8771
9258
|
console.log();
|