@agentis-hq/cli 0.1.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 +1422 -0
- package/package.json +40 -0
- package/templates/facilitator/.env.example.tpl +20 -0
- package/templates/facilitator/README.md.tpl +46 -0
- package/templates/facilitator/kora/kora.toml.tpl +103 -0
- package/templates/facilitator/kora/signers.toml +8 -0
- package/templates/facilitator/package.json.tpl +25 -0
- package/templates/facilitator/src/facilitator.ts +163 -0
- package/templates/facilitator/src/heartbeat.ts +36 -0
- package/templates/facilitator/src/ledger.ts +141 -0
- package/templates/facilitator/tsconfig.json +11 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1422 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
// src/lib/keychain.ts
|
|
4
|
+
import { Entry } from "@napi-rs/keyring";
|
|
5
|
+
var entry = new Entry("agentis-cli", "account-key");
|
|
6
|
+
async function saveToken(token) {
|
|
7
|
+
entry.setPassword(token);
|
|
8
|
+
}
|
|
9
|
+
async function getToken() {
|
|
10
|
+
if (process.env.AGENTIS_ACCOUNT_KEY) return process.env.AGENTIS_ACCOUNT_KEY;
|
|
11
|
+
try {
|
|
12
|
+
return entry.getPassword();
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function deleteToken() {
|
|
18
|
+
try {
|
|
19
|
+
entry.deletePassword();
|
|
20
|
+
} catch {
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/lib/config.ts
|
|
25
|
+
var API_BASE = process.env.AGENTIS_API_URL ?? "https://api.agentis.systems";
|
|
26
|
+
async function apiFetch(path, opts = {}, token) {
|
|
27
|
+
const headers = {
|
|
28
|
+
"content-type": "application/json",
|
|
29
|
+
...opts.headers ?? {}
|
|
30
|
+
};
|
|
31
|
+
if (token) headers["authorization"] = `Bearer ${token}`;
|
|
32
|
+
return fetch(`${API_BASE}${path}`, { ...opts, headers });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/commands/auth.ts
|
|
36
|
+
async function login() {
|
|
37
|
+
const existing = await getToken();
|
|
38
|
+
if (existing) {
|
|
39
|
+
console.log("Already logged in. Run `agentis logout` first.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const res = await apiFetch("/auth/session", { method: "POST" });
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
console.error("Failed to start login session.");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const { sessionId, loginUrl } = await res.json();
|
|
48
|
+
console.log("\nOpen this URL in your browser to authenticate:\n");
|
|
49
|
+
console.log(` ${loginUrl}
|
|
50
|
+
`);
|
|
51
|
+
try {
|
|
52
|
+
const { exec } = await import("child_process");
|
|
53
|
+
const open = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
54
|
+
exec(`${open} "${loginUrl}"`);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
console.log("Waiting for authentication...");
|
|
58
|
+
const deadline = Date.now() + 10 * 60 * 1e3;
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
61
|
+
const poll = await apiFetch(`/auth/session/${sessionId}`);
|
|
62
|
+
if (!poll.ok) {
|
|
63
|
+
if (poll.status === 410) {
|
|
64
|
+
console.error("\nSession expired. Run `agentis login` again.");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const data = await poll.json();
|
|
70
|
+
if (data.status === "complete" && data.accountKey) {
|
|
71
|
+
await saveToken(data.accountKey);
|
|
72
|
+
console.log("\nAuthenticated! You can now use the Agentis CLI.\n");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
console.error("\nLogin timed out. Run `agentis login` again.");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
async function logout() {
|
|
80
|
+
const token = await getToken();
|
|
81
|
+
if (!token) {
|
|
82
|
+
console.log("Not logged in.");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
await deleteToken();
|
|
86
|
+
console.log("Logged out.");
|
|
87
|
+
}
|
|
88
|
+
async function whoami() {
|
|
89
|
+
const token = await getToken();
|
|
90
|
+
if (!token) {
|
|
91
|
+
console.log("Not logged in. Run `agentis login`.");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const masked = token.slice(0, 13) + "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" + token.slice(-4);
|
|
95
|
+
console.log(`Logged in as ${masked}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/lib/account.ts
|
|
99
|
+
function printExpiredLoginAndExit() {
|
|
100
|
+
console.error("Stored Agentis login is expired or invalid. Run `agentis logout` and then `agentis login`.");
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
async function fetchAccountAgents(token) {
|
|
104
|
+
const res = await apiFetch("/account/agents", {}, token);
|
|
105
|
+
if (res.status === 401) printExpiredLoginAndExit();
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const data = await res.json().catch(() => ({}));
|
|
108
|
+
console.error("Failed to fetch hosted agents:", data.error ?? res.statusText);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
return res.json();
|
|
112
|
+
}
|
|
113
|
+
async function findAccountAgent(nameOrId, token) {
|
|
114
|
+
const agents = await fetchAccountAgents(token);
|
|
115
|
+
return agents.find((a) => a.id === nameOrId || a.name === nameOrId) ?? null;
|
|
116
|
+
}
|
|
117
|
+
async function resolveAccountAgent(nameOrId, token) {
|
|
118
|
+
const agent = await findAccountAgent(nameOrId, token);
|
|
119
|
+
if (!agent) {
|
|
120
|
+
console.error(`Hosted agent not found: ${nameOrId}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
return agent;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/lib/local-wallet.ts
|
|
127
|
+
import { generateMnemonic, mnemonicToSeedSync } from "@scure/bip39";
|
|
128
|
+
import { wordlist as englishWordlist } from "@scure/bip39/wordlists/english.js";
|
|
129
|
+
import { HDKey } from "@scure/bip32";
|
|
130
|
+
import { scrypt } from "@noble/hashes/scrypt.js";
|
|
131
|
+
import { gcm } from "@noble/ciphers/aes.js";
|
|
132
|
+
import { randomBytes } from "@noble/hashes/utils.js";
|
|
133
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
134
|
+
import { v4 as uuidv4 } from "uuid";
|
|
135
|
+
import { join } from "path";
|
|
136
|
+
import { homedir } from "os";
|
|
137
|
+
import { mkdirSync, writeFileSync, readFileSync, readdirSync, chmodSync, existsSync } from "fs";
|
|
138
|
+
import { createKeyPairSignerFromPrivateKeyBytes } from "@solana/kit";
|
|
139
|
+
var VAULT_DIR = join(homedir(), ".agentis", "wallets");
|
|
140
|
+
var DEFAULT_LOCAL_POLICY = {
|
|
141
|
+
hourlyLimit: null,
|
|
142
|
+
dailyLimit: null,
|
|
143
|
+
monthlyLimit: null,
|
|
144
|
+
maxBudget: null,
|
|
145
|
+
maxPerTx: null,
|
|
146
|
+
allowedDomains: [],
|
|
147
|
+
killSwitch: false
|
|
148
|
+
};
|
|
149
|
+
function ensureVaultDir() {
|
|
150
|
+
mkdirSync(VAULT_DIR, { recursive: true });
|
|
151
|
+
chmodSync(join(homedir(), ".agentis"), 448);
|
|
152
|
+
chmodSync(VAULT_DIR, 448);
|
|
153
|
+
}
|
|
154
|
+
function deriveSolanaAddress(mnemonic) {
|
|
155
|
+
const privateKey = deriveSolanaPrivateKeyBytes(mnemonic);
|
|
156
|
+
const pubkey = ed25519.getPublicKey(privateKey);
|
|
157
|
+
return encodeBase58(pubkey);
|
|
158
|
+
}
|
|
159
|
+
function deriveSolanaPrivateKeyBytes(mnemonic) {
|
|
160
|
+
const seed = mnemonicToSeedSync(mnemonic);
|
|
161
|
+
const root = HDKey.fromMasterSeed(seed);
|
|
162
|
+
const child = root.derive("m/44'/501'/0'/0'");
|
|
163
|
+
if (!child.privateKey) {
|
|
164
|
+
throw new Error("Failed to derive Solana private key");
|
|
165
|
+
}
|
|
166
|
+
return child.privateKey;
|
|
167
|
+
}
|
|
168
|
+
function encodeBase58(bytes) {
|
|
169
|
+
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
170
|
+
let num = BigInt("0x" + Buffer.from(bytes).toString("hex"));
|
|
171
|
+
let result = "";
|
|
172
|
+
while (num > 0n) {
|
|
173
|
+
result = ALPHABET[Number(num % 58n)] + result;
|
|
174
|
+
num = num / 58n;
|
|
175
|
+
}
|
|
176
|
+
for (const byte of bytes) {
|
|
177
|
+
if (byte === 0) result = "1" + result;
|
|
178
|
+
else break;
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
function encryptMnemonic(mnemonic, passphrase = "") {
|
|
183
|
+
const salt = randomBytes(32);
|
|
184
|
+
const iv = randomBytes(12);
|
|
185
|
+
const key = scrypt(passphrase, salt, { N: 65536, r: 8, p: 1, dkLen: 32 });
|
|
186
|
+
const cipher = gcm(key, iv);
|
|
187
|
+
const data = new TextEncoder().encode(mnemonic);
|
|
188
|
+
const encrypted = cipher.encrypt(data);
|
|
189
|
+
const ciphertext = encrypted.slice(0, encrypted.length - 16);
|
|
190
|
+
const authTag = encrypted.slice(encrypted.length - 16);
|
|
191
|
+
return {
|
|
192
|
+
cipher: "aes-256-gcm",
|
|
193
|
+
ciphertext: Buffer.from(ciphertext).toString("hex"),
|
|
194
|
+
cipherparams: { iv: Buffer.from(iv).toString("hex") },
|
|
195
|
+
auth_tag: Buffer.from(authTag).toString("hex"),
|
|
196
|
+
kdf: "scrypt",
|
|
197
|
+
kdfparams: { dklen: 32, n: 65536, r: 8, p: 1, salt: Buffer.from(salt).toString("hex") }
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function decryptMnemonic(wallet, passphrase = "") {
|
|
201
|
+
const { kdfparams, cipherparams, ciphertext, auth_tag } = wallet.crypto;
|
|
202
|
+
const salt = Buffer.from(kdfparams.salt, "hex");
|
|
203
|
+
const iv = Buffer.from(cipherparams.iv, "hex");
|
|
204
|
+
const key = scrypt(passphrase, salt, { N: kdfparams.n, r: kdfparams.r, p: kdfparams.p, dkLen: kdfparams.dklen });
|
|
205
|
+
const cipher = gcm(key, iv);
|
|
206
|
+
const encrypted = Buffer.concat([Buffer.from(ciphertext, "hex"), Buffer.from(auth_tag, "hex")]);
|
|
207
|
+
const decrypted = cipher.decrypt(encrypted);
|
|
208
|
+
return new TextDecoder().decode(decrypted);
|
|
209
|
+
}
|
|
210
|
+
function createLocalWallet(name) {
|
|
211
|
+
ensureVaultDir();
|
|
212
|
+
const mnemonic = generateMnemonic(englishWordlist, 128);
|
|
213
|
+
const solanaAddress = deriveSolanaAddress(mnemonic);
|
|
214
|
+
const crypto = encryptMnemonic(mnemonic);
|
|
215
|
+
const wallet = {
|
|
216
|
+
id: uuidv4(),
|
|
217
|
+
name,
|
|
218
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
219
|
+
solanaAddress,
|
|
220
|
+
policy: { ...DEFAULT_LOCAL_POLICY },
|
|
221
|
+
spendHistory: [],
|
|
222
|
+
crypto
|
|
223
|
+
};
|
|
224
|
+
const path = join(VAULT_DIR, `${wallet.id}.json`);
|
|
225
|
+
writeFileSync(path, JSON.stringify(wallet, null, 2));
|
|
226
|
+
chmodSync(path, 384);
|
|
227
|
+
return { wallet, mnemonic };
|
|
228
|
+
}
|
|
229
|
+
function walletPath(id) {
|
|
230
|
+
return join(VAULT_DIR, `${id}.json`);
|
|
231
|
+
}
|
|
232
|
+
function normalizeLocalWallet(wallet) {
|
|
233
|
+
return {
|
|
234
|
+
...wallet,
|
|
235
|
+
policy: { ...DEFAULT_LOCAL_POLICY, ...wallet.policy ?? {} },
|
|
236
|
+
spendHistory: wallet.spendHistory ?? []
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function saveLocalWallet(wallet) {
|
|
240
|
+
ensureVaultDir();
|
|
241
|
+
const normalized = normalizeLocalWallet(wallet);
|
|
242
|
+
const path = walletPath(normalized.id);
|
|
243
|
+
writeFileSync(path, JSON.stringify(normalized, null, 2));
|
|
244
|
+
chmodSync(path, 384);
|
|
245
|
+
}
|
|
246
|
+
function listLocalWallets() {
|
|
247
|
+
ensureVaultDir();
|
|
248
|
+
if (!existsSync(VAULT_DIR)) return [];
|
|
249
|
+
return readdirSync(VAULT_DIR).filter((f) => f.endsWith(".json")).map((f) => normalizeLocalWallet(JSON.parse(readFileSync(join(VAULT_DIR, f), "utf8")))).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
250
|
+
}
|
|
251
|
+
function loadLocalWalletByNameOrId(nameOrId) {
|
|
252
|
+
const wallets = listLocalWallets();
|
|
253
|
+
return wallets.find((w) => w.id === nameOrId || w.name === nameOrId) ?? null;
|
|
254
|
+
}
|
|
255
|
+
async function getLocalWalletSigner(wallet, passphrase = "") {
|
|
256
|
+
const mnemonic = decryptMnemonic(wallet, passphrase);
|
|
257
|
+
const signer = await createKeyPairSignerFromPrivateKeyBytes(deriveSolanaPrivateKeyBytes(mnemonic));
|
|
258
|
+
if (signer.address !== wallet.solanaAddress) {
|
|
259
|
+
throw new Error(`Local wallet key derivation mismatch: expected ${wallet.solanaAddress}, got ${signer.address}`);
|
|
260
|
+
}
|
|
261
|
+
return signer;
|
|
262
|
+
}
|
|
263
|
+
function recordLocalSpend(wallet, spend) {
|
|
264
|
+
const updated = normalizeLocalWallet({
|
|
265
|
+
...wallet,
|
|
266
|
+
spendHistory: [...wallet.spendHistory ?? [], spend]
|
|
267
|
+
});
|
|
268
|
+
saveLocalWallet(updated);
|
|
269
|
+
return updated;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/commands/agent.ts
|
|
273
|
+
import { checkPolicy } from "@agentis-hq/core";
|
|
274
|
+
import {
|
|
275
|
+
address,
|
|
276
|
+
appendTransactionMessageInstruction,
|
|
277
|
+
createSolanaRpc,
|
|
278
|
+
createSolanaRpcSubscriptions,
|
|
279
|
+
createTransactionMessage,
|
|
280
|
+
devnet,
|
|
281
|
+
getSignatureFromTransaction,
|
|
282
|
+
pipe,
|
|
283
|
+
sendAndConfirmTransactionFactory,
|
|
284
|
+
setTransactionMessageFeePayerSigner,
|
|
285
|
+
setTransactionMessageLifetimeUsingBlockhash,
|
|
286
|
+
signTransactionMessageWithSigners
|
|
287
|
+
} from "@solana/kit";
|
|
288
|
+
import { getTransferSolInstruction } from "@solana-program/system";
|
|
289
|
+
var DEVNET_RPC = "https://api.devnet.solana.com";
|
|
290
|
+
var DEVNET_WS = "wss://api.devnet.solana.com";
|
|
291
|
+
var LAMPORTS_PER_SOL = 1e9;
|
|
292
|
+
var SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
293
|
+
async function requireAuth() {
|
|
294
|
+
const token = await getToken();
|
|
295
|
+
if (!token) {
|
|
296
|
+
console.error("Not logged in. Run `agentis login` first.");
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
return token;
|
|
300
|
+
}
|
|
301
|
+
async function findHostedAgent(nameOrId, token) {
|
|
302
|
+
return findAccountAgent(nameOrId, token);
|
|
303
|
+
}
|
|
304
|
+
async function getSolBalance(walletAddress) {
|
|
305
|
+
const solRes = await fetch(DEVNET_RPC, {
|
|
306
|
+
method: "POST",
|
|
307
|
+
headers: { "content-type": "application/json" },
|
|
308
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getBalance", params: [walletAddress, { commitment: "confirmed" }] })
|
|
309
|
+
});
|
|
310
|
+
const solData = await solRes.json();
|
|
311
|
+
return (solData.result?.value ?? 0) / LAMPORTS_PER_SOL;
|
|
312
|
+
}
|
|
313
|
+
var cachedSolPrice = null;
|
|
314
|
+
async function solToUsd(sol) {
|
|
315
|
+
const now = Date.now();
|
|
316
|
+
if (cachedSolPrice && now - cachedSolPrice.fetchedAt < 6e4) return sol * cachedSolPrice.usd;
|
|
317
|
+
const res = await fetch(`https://api.jup.ag/price/v3?ids=${SOL_MINT}`);
|
|
318
|
+
const data = await res.json();
|
|
319
|
+
const price = data[SOL_MINT]?.usdPrice ?? 0;
|
|
320
|
+
cachedSolPrice = { usd: price, fetchedAt: now };
|
|
321
|
+
return sol * price;
|
|
322
|
+
}
|
|
323
|
+
async function agentList() {
|
|
324
|
+
const token = await requireAuth();
|
|
325
|
+
const agents = await fetchAccountAgents(token);
|
|
326
|
+
if (agents.length === 0) {
|
|
327
|
+
console.log("No agents found. Run `agentis agent create <name>` to create one.");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
console.log();
|
|
331
|
+
for (const a of agents) {
|
|
332
|
+
console.log(` ${a.name.padEnd(20)} ${a.walletAddress} [${a.id}]`);
|
|
333
|
+
}
|
|
334
|
+
console.log();
|
|
335
|
+
}
|
|
336
|
+
async function agentCreate(args2) {
|
|
337
|
+
const parts = Array.isArray(args2) ? args2 : args2 ? [args2] : [];
|
|
338
|
+
const name = parts.find((part) => !part.startsWith("--"));
|
|
339
|
+
const policyMode = parts.includes("--onchain-policy") || parts.includes("--policy-onchain") ? "onchain" : "backend";
|
|
340
|
+
if (!name) {
|
|
341
|
+
console.error("Usage: agentis agent create <name> [--onchain-policy]");
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
const token = await requireAuth();
|
|
345
|
+
const res = await apiFetch("/account/agents", {
|
|
346
|
+
method: "POST",
|
|
347
|
+
body: JSON.stringify({ name, policyMode })
|
|
348
|
+
}, token);
|
|
349
|
+
if (!res.ok) {
|
|
350
|
+
const data = await res.json();
|
|
351
|
+
console.error("Failed to create agent:", data.error ?? res.statusText);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
const agent = await res.json();
|
|
355
|
+
console.log(`
|
|
356
|
+
Agent created`);
|
|
357
|
+
console.log(` Name: ${agent.name}`);
|
|
358
|
+
console.log(` ID: ${agent.id}`);
|
|
359
|
+
console.log(` Wallet: ${agent.walletAddress}`);
|
|
360
|
+
console.log(` Policy: ${agent.policyMode ?? "backend"}${agent.onchainPolicy?.initialized ? " (initialized)" : agent.policyMode === "onchain" ? " (pending init)" : ""}`);
|
|
361
|
+
console.log(` API Key: ${agent.apiKey}
|
|
362
|
+
`);
|
|
363
|
+
}
|
|
364
|
+
async function agentBalance(nameOrId) {
|
|
365
|
+
if (!nameOrId) {
|
|
366
|
+
console.error("Usage: agentis agent balance <name-or-id>");
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
const token = await getToken();
|
|
370
|
+
const hosted = token ? await findHostedAgent(nameOrId, token) : null;
|
|
371
|
+
const local = hosted ? null : loadLocalWalletByNameOrId(nameOrId);
|
|
372
|
+
const walletAddress = hosted?.walletAddress ?? local?.solanaAddress;
|
|
373
|
+
const name = hosted?.name ?? local?.name;
|
|
374
|
+
if (!walletAddress || !name) {
|
|
375
|
+
console.error(`Agent or local wallet not found: ${nameOrId}`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
const KNOWN_TOKENS = {
|
|
379
|
+
"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU": "USDC",
|
|
380
|
+
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": "USDT",
|
|
381
|
+
"2u1tszSeqaGNBXyMBCBMHXHNsJL83PbkSbB5CmEZi69W": "USDG"
|
|
382
|
+
};
|
|
383
|
+
const solBalance = await getSolBalance(walletAddress);
|
|
384
|
+
const tokenRes = await fetch(DEVNET_RPC, {
|
|
385
|
+
method: "POST",
|
|
386
|
+
headers: { "content-type": "application/json" },
|
|
387
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "getTokenAccountsByOwner", params: [walletAddress, { programId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { encoding: "jsonParsed" }] })
|
|
388
|
+
});
|
|
389
|
+
const tokenData = await tokenRes.json();
|
|
390
|
+
const tokens = (tokenData.result?.value ?? []).map((acc) => {
|
|
391
|
+
const info = acc.account.data.parsed.info;
|
|
392
|
+
return { symbol: KNOWN_TOKENS[info.mint] ?? info.mint.slice(0, 8) + "...", uiAmount: info.tokenAmount.uiAmount ?? 0 };
|
|
393
|
+
}).filter((t) => t.uiAmount > 0);
|
|
394
|
+
console.log(`
|
|
395
|
+
Balances for ${name} (${walletAddress})${local ? " [local]" : ""}:`);
|
|
396
|
+
console.log(` SOL ${solBalance.toFixed(6)}`);
|
|
397
|
+
for (const t of tokens) {
|
|
398
|
+
console.log(` ${t.symbol.padEnd(6)} ${t.uiAmount}`);
|
|
399
|
+
}
|
|
400
|
+
console.log();
|
|
401
|
+
}
|
|
402
|
+
async function sendLocalSol(nameOrId, to, amountSol, displayAmount) {
|
|
403
|
+
const wallet = loadLocalWalletByNameOrId(nameOrId);
|
|
404
|
+
if (!wallet) {
|
|
405
|
+
console.error(`Agent or local wallet not found: ${nameOrId}`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
const amountUsd = await solToUsd(amountSol);
|
|
409
|
+
try {
|
|
410
|
+
checkPolicy({ ...wallet.policy, allowedDomains: [] }, amountUsd, `solana:${to}`, wallet.spendHistory);
|
|
411
|
+
} catch (err) {
|
|
412
|
+
console.error("Policy rejected:", err?.message ?? String(err));
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
const signer = await getLocalWalletSigner(wallet);
|
|
416
|
+
const rpc = createSolanaRpc(devnet(DEVNET_RPC));
|
|
417
|
+
const rpcSubscriptions = createSolanaRpcSubscriptions(devnet(DEVNET_WS));
|
|
418
|
+
const sendAndConfirm = sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions });
|
|
419
|
+
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
|
|
420
|
+
const transactionMessage = pipe(
|
|
421
|
+
createTransactionMessage({ version: "legacy" }),
|
|
422
|
+
(m) => setTransactionMessageFeePayerSigner(signer, m),
|
|
423
|
+
(m) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, m),
|
|
424
|
+
(m) => appendTransactionMessageInstruction(
|
|
425
|
+
getTransferSolInstruction({
|
|
426
|
+
source: signer,
|
|
427
|
+
destination: address(to),
|
|
428
|
+
amount: BigInt(Math.round(amountSol * LAMPORTS_PER_SOL))
|
|
429
|
+
}),
|
|
430
|
+
m
|
|
431
|
+
)
|
|
432
|
+
);
|
|
433
|
+
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
|
|
434
|
+
await sendAndConfirm(signedTransaction, { commitment: "confirmed" });
|
|
435
|
+
const signature = getSignatureFromTransaction(signedTransaction);
|
|
436
|
+
recordLocalSpend(wallet, {
|
|
437
|
+
amount: amountUsd,
|
|
438
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
439
|
+
url: `solana:${to}`
|
|
440
|
+
});
|
|
441
|
+
console.log(`
|
|
442
|
+
Sent from local wallet ${wallet.name}!`);
|
|
443
|
+
console.log(` Amount: ${displayAmount}`);
|
|
444
|
+
console.log(` Signature: ${signature}`);
|
|
445
|
+
console.log(` Explorer: https://explorer.solana.com/tx/${signature}?cluster=devnet
|
|
446
|
+
`);
|
|
447
|
+
}
|
|
448
|
+
async function agentSend(args2) {
|
|
449
|
+
if (args2.length < 3) {
|
|
450
|
+
console.error("Usage: agentis agent send <name-or-id> <to> <amount> [--sol] [--token <mint>]");
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
const [nameOrId, to, amountStr, ...flags] = args2;
|
|
454
|
+
const isSol = flags.includes("--sol");
|
|
455
|
+
const tokenIdx = flags.indexOf("--token");
|
|
456
|
+
const tokenMint = tokenIdx !== -1 ? flags[tokenIdx + 1] : null;
|
|
457
|
+
const rawAmount = parseFloat(amountStr);
|
|
458
|
+
if (isNaN(rawAmount) || rawAmount <= 0) {
|
|
459
|
+
console.error("Invalid amount");
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
let amountSol;
|
|
463
|
+
const displayAmount = isSol ? `${rawAmount} SOL` : `${rawAmount} lamports`;
|
|
464
|
+
if (tokenMint) {
|
|
465
|
+
console.error("SPL token send not yet supported via CLI");
|
|
466
|
+
process.exit(1);
|
|
467
|
+
} else if (isSol) {
|
|
468
|
+
amountSol = rawAmount;
|
|
469
|
+
} else {
|
|
470
|
+
amountSol = rawAmount / 1e9;
|
|
471
|
+
}
|
|
472
|
+
const token = await getToken();
|
|
473
|
+
const agent = token ? await findHostedAgent(nameOrId, token) : null;
|
|
474
|
+
if (!agent) {
|
|
475
|
+
console.log(`
|
|
476
|
+
Sending ${displayAmount} to ${to} from local wallet...`);
|
|
477
|
+
await sendLocalSol(nameOrId, to, amountSol, displayAmount);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
console.log(`
|
|
481
|
+
Sending ${displayAmount} to ${to} from hosted agent...`);
|
|
482
|
+
const res = await apiFetch(`/agents/${agent.id}/send`, {
|
|
483
|
+
method: "POST",
|
|
484
|
+
body: JSON.stringify({ to, amountSol })
|
|
485
|
+
}, token);
|
|
486
|
+
if (!res.ok) {
|
|
487
|
+
const data2 = await res.json();
|
|
488
|
+
console.error("Send failed:", data2.error ?? res.statusText);
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
const data = await res.json();
|
|
492
|
+
console.log(`
|
|
493
|
+
Sent!`);
|
|
494
|
+
console.log(` Signature: ${data.signature}`);
|
|
495
|
+
console.log(` Explorer: https://explorer.solana.com/tx/${data.signature}?cluster=devnet
|
|
496
|
+
`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/commands/wallet.ts
|
|
500
|
+
async function walletCreate(args2) {
|
|
501
|
+
const nameIdx = args2.indexOf("--name");
|
|
502
|
+
const name = nameIdx !== -1 ? args2[nameIdx + 1] : void 0;
|
|
503
|
+
const isLocal = args2.includes("--local");
|
|
504
|
+
if (!name) {
|
|
505
|
+
console.error("Usage: agentis wallet create --name <name> [--local]");
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
const token = await getToken();
|
|
509
|
+
if (!token || isLocal) {
|
|
510
|
+
const { wallet } = createLocalWallet(name);
|
|
511
|
+
console.log(`
|
|
512
|
+
Wallet created (local)`);
|
|
513
|
+
console.log(` Name: ${wallet.name}`);
|
|
514
|
+
console.log(` ID: ${wallet.id}`);
|
|
515
|
+
console.log(` Solana: ${wallet.solanaAddress}`);
|
|
516
|
+
console.log(` Vault: ~/.agentis/wallets/${wallet.id}.json
|
|
517
|
+
`);
|
|
518
|
+
if (!token) {
|
|
519
|
+
console.log(` tip: run \`agentis login\` to create hosted wallets managed by Agentis.
|
|
520
|
+
`);
|
|
521
|
+
}
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const res = await apiFetch("/account/agents", {
|
|
525
|
+
method: "POST",
|
|
526
|
+
body: JSON.stringify({ name })
|
|
527
|
+
}, token);
|
|
528
|
+
if (!res.ok) {
|
|
529
|
+
const data = await res.json();
|
|
530
|
+
console.error("Failed to create hosted wallet:", data.error ?? res.statusText);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
const agent = await res.json();
|
|
534
|
+
console.log(`
|
|
535
|
+
Wallet created (hosted)`);
|
|
536
|
+
console.log(` Name: ${agent.name}`);
|
|
537
|
+
console.log(` ID: ${agent.id}`);
|
|
538
|
+
console.log(` Solana: ${agent.walletAddress}`);
|
|
539
|
+
console.log(` API Key: ${agent.apiKey}
|
|
540
|
+
`);
|
|
541
|
+
}
|
|
542
|
+
async function walletList() {
|
|
543
|
+
const token = await getToken();
|
|
544
|
+
const localWallets = listLocalWallets();
|
|
545
|
+
if (token) {
|
|
546
|
+
try {
|
|
547
|
+
const res = await apiFetch("/account/agents", {}, token);
|
|
548
|
+
if (res.ok) {
|
|
549
|
+
const hosted = await res.json();
|
|
550
|
+
if (hosted.length > 0) {
|
|
551
|
+
console.log("\nHosted wallets:");
|
|
552
|
+
for (const a of hosted) {
|
|
553
|
+
console.log(` ${a.name.padEnd(20)} ${a.walletAddress} [${a.id}]`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (localWallets.length > 0) {
|
|
561
|
+
console.log("\nLocal wallets:");
|
|
562
|
+
for (const w of localWallets) {
|
|
563
|
+
console.log(` ${w.name.padEnd(20)} ${w.solanaAddress} [${w.id}]`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (localWallets.length === 0 && !token) {
|
|
567
|
+
console.log("No wallets found. Run `agentis wallet create --name <name>` to create one.");
|
|
568
|
+
}
|
|
569
|
+
console.log();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/commands/policy.ts
|
|
573
|
+
async function findHostedAgent2(nameOrId, token) {
|
|
574
|
+
return findAccountAgent(nameOrId, token);
|
|
575
|
+
}
|
|
576
|
+
function printPolicy(name, p, scope) {
|
|
577
|
+
console.log(`
|
|
578
|
+
Policy for ${name} (${scope}):`);
|
|
579
|
+
if (scope === "hosted" && p.policyMode) {
|
|
580
|
+
console.log(` Mode: ${p.policyMode}${p.onchainPolicy?.initialized ? " (initialized)" : p.policyMode === "onchain" ? " (pending init)" : ""}`);
|
|
581
|
+
}
|
|
582
|
+
console.log(` Kill switch: ${p.killSwitch ? "ON (agent halted)" : "off"}`);
|
|
583
|
+
console.log(` Max per tx: ${p.maxPerTx !== null ? `$${p.maxPerTx}` : "unlimited"}`);
|
|
584
|
+
console.log(` Hourly limit: ${p.hourlyLimit !== null ? `$${p.hourlyLimit}` : "unlimited"}`);
|
|
585
|
+
console.log(` Daily limit: ${p.dailyLimit !== null ? `$${p.dailyLimit}` : "unlimited"}`);
|
|
586
|
+
console.log(` Monthly limit: ${p.monthlyLimit !== null ? `$${p.monthlyLimit}` : "unlimited"}`);
|
|
587
|
+
console.log(` Total budget: ${p.maxBudget !== null ? `$${p.maxBudget}` : "unlimited"}`);
|
|
588
|
+
console.log(` Allowed domains: ${p.allowedDomains?.length > 0 ? p.allowedDomains.join(", ") : "all"}`);
|
|
589
|
+
console.log();
|
|
590
|
+
}
|
|
591
|
+
function applyPolicyFlags(existingPolicy, args2) {
|
|
592
|
+
const policy = { ...DEFAULT_LOCAL_POLICY, ...existingPolicy ?? {} };
|
|
593
|
+
const get = (flag2) => {
|
|
594
|
+
const idx = args2.indexOf(flag2);
|
|
595
|
+
return idx !== -1 ? args2[idx + 1] : void 0;
|
|
596
|
+
};
|
|
597
|
+
if (args2.includes("--kill")) policy.killSwitch = true;
|
|
598
|
+
if (args2.includes("--resume")) policy.killSwitch = false;
|
|
599
|
+
if (get("--max-per-tx") !== void 0) policy.maxPerTx = parseFloat(get("--max-per-tx"));
|
|
600
|
+
if (get("--hourly") !== void 0) policy.hourlyLimit = parseFloat(get("--hourly"));
|
|
601
|
+
if (get("--daily") !== void 0) policy.dailyLimit = parseFloat(get("--daily"));
|
|
602
|
+
if (get("--monthly") !== void 0) policy.monthlyLimit = parseFloat(get("--monthly"));
|
|
603
|
+
if (get("--budget") !== void 0) policy.maxBudget = parseFloat(get("--budget"));
|
|
604
|
+
if (get("--allow")) {
|
|
605
|
+
const domain = get("--allow").replace(/^https?:\/\//, "").toLowerCase();
|
|
606
|
+
policy.allowedDomains = [.../* @__PURE__ */ new Set([...policy.allowedDomains ?? [], domain])];
|
|
607
|
+
}
|
|
608
|
+
if (get("--disallow")) {
|
|
609
|
+
const domain = get("--disallow").replace(/^https?:\/\//, "").toLowerCase();
|
|
610
|
+
policy.allowedDomains = (policy.allowedDomains ?? []).filter((d) => d !== domain);
|
|
611
|
+
}
|
|
612
|
+
return policy;
|
|
613
|
+
}
|
|
614
|
+
async function policyGet(nameOrId) {
|
|
615
|
+
if (!nameOrId) {
|
|
616
|
+
console.error("Usage: agentis policy get <name-or-id>");
|
|
617
|
+
process.exit(1);
|
|
618
|
+
}
|
|
619
|
+
const token = await getToken();
|
|
620
|
+
const hosted = token ? await findHostedAgent2(nameOrId, token) : null;
|
|
621
|
+
if (hosted) {
|
|
622
|
+
printPolicy(hosted.name, { ...DEFAULT_LOCAL_POLICY, ...hosted.policy ?? {}, policyMode: hosted.policyMode ?? "backend", onchainPolicy: hosted.onchainPolicy }, "hosted");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const local = loadLocalWalletByNameOrId(nameOrId);
|
|
626
|
+
if (local) {
|
|
627
|
+
printPolicy(local.name, local.policy, "local");
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
console.error(`Agent or local wallet not found: ${nameOrId}`);
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
async function policySet(nameOrId, args2) {
|
|
634
|
+
if (!nameOrId) {
|
|
635
|
+
console.error("Usage: agentis policy set <name-or-id> [flags]");
|
|
636
|
+
process.exit(1);
|
|
637
|
+
}
|
|
638
|
+
const token = await getToken();
|
|
639
|
+
const hosted = token ? await findHostedAgent2(nameOrId, token) : null;
|
|
640
|
+
if (hosted && token) {
|
|
641
|
+
const policy = applyPolicyFlags(hosted.policy, args2);
|
|
642
|
+
const res = await apiFetch(`/agents/${hosted.id}`, {
|
|
643
|
+
method: "PATCH",
|
|
644
|
+
body: JSON.stringify({ policy })
|
|
645
|
+
}, token);
|
|
646
|
+
if (!res.ok) {
|
|
647
|
+
const data = await res.json();
|
|
648
|
+
console.error("Failed to update policy:", data.error ?? res.statusText);
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
console.log(`
|
|
652
|
+
Policy updated for ${hosted.name} (hosted).
|
|
653
|
+
`);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const local = loadLocalWalletByNameOrId(nameOrId);
|
|
657
|
+
if (local) {
|
|
658
|
+
local.policy = applyPolicyFlags(local.policy, args2);
|
|
659
|
+
saveLocalWallet(local);
|
|
660
|
+
console.log(`
|
|
661
|
+
Policy updated for ${local.name} (local).
|
|
662
|
+
`);
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
console.error(`Agent or local wallet not found: ${nameOrId}`);
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
668
|
+
async function policyInitOnchain(nameOrId) {
|
|
669
|
+
if (!nameOrId) {
|
|
670
|
+
console.error("Usage: agentis policy init-onchain <name-or-id>");
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
const token = await getToken();
|
|
674
|
+
const hosted = token ? await findHostedAgent2(nameOrId, token) : null;
|
|
675
|
+
if (!hosted || !token) {
|
|
676
|
+
console.error(`Hosted agent not found: ${nameOrId}`);
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
const res = await apiFetch(`/agents/${hosted.id}/policy/onchain/initialize`, {
|
|
680
|
+
method: "POST"
|
|
681
|
+
}, token);
|
|
682
|
+
const data = await res.json().catch(() => ({}));
|
|
683
|
+
if (!res.ok) {
|
|
684
|
+
console.error("Failed to initialize on-chain policy:", data.error ?? res.statusText);
|
|
685
|
+
process.exit(1);
|
|
686
|
+
}
|
|
687
|
+
console.log(`
|
|
688
|
+
On-chain policy initialized for ${data.name}.`);
|
|
689
|
+
console.log(` Agent PDA: ${data.onchainPolicy?.agent}`);
|
|
690
|
+
console.log(` Policy PDA: ${data.onchainPolicy?.policy}`);
|
|
691
|
+
console.log(` Counter PDA: ${data.onchainPolicy?.spendCounter}`);
|
|
692
|
+
console.log(` Signature: ${data.onchainPolicy?.initializedSignature}
|
|
693
|
+
`);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/commands/fetch.ts
|
|
697
|
+
function getFlag(args2, flag2) {
|
|
698
|
+
const idx = args2.indexOf(flag2);
|
|
699
|
+
return idx === -1 ? void 0 : args2[idx + 1];
|
|
700
|
+
}
|
|
701
|
+
async function resolveHostedAgent(nameOrId, token) {
|
|
702
|
+
return resolveAccountAgent(nameOrId, token);
|
|
703
|
+
}
|
|
704
|
+
async function paidFetch(args2) {
|
|
705
|
+
const url = args2[0];
|
|
706
|
+
const agentName = getFlag(args2, "--agent");
|
|
707
|
+
const method = getFlag(args2, "--method") ?? "GET";
|
|
708
|
+
if (!url || !agentName) {
|
|
709
|
+
console.error("Usage: agentis fetch <url> --agent <name-or-id> [--method GET]");
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
const token = await getToken();
|
|
713
|
+
if (!token) {
|
|
714
|
+
console.error("Not logged in. Run `agentis login` first.");
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
const agent = await resolveHostedAgent(agentName, token);
|
|
718
|
+
const res = await apiFetch(`/agents/${agent.id}/fetch`, {
|
|
719
|
+
method: "POST",
|
|
720
|
+
body: JSON.stringify({ url, method })
|
|
721
|
+
}, token);
|
|
722
|
+
const data = await res.json().catch(() => ({}));
|
|
723
|
+
if (!res.ok) {
|
|
724
|
+
console.error("Fetch failed:", data.error ?? res.statusText);
|
|
725
|
+
process.exit(1);
|
|
726
|
+
}
|
|
727
|
+
console.log(`status ${data.status}`);
|
|
728
|
+
if (data.body) console.log(data.body);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/commands/privacy.ts
|
|
732
|
+
function getFlag2(args2, flag2) {
|
|
733
|
+
const idx = args2.indexOf(flag2);
|
|
734
|
+
return idx === -1 ? void 0 : args2[idx + 1];
|
|
735
|
+
}
|
|
736
|
+
function hasFlag(args2, flag2) {
|
|
737
|
+
return args2.includes(flag2);
|
|
738
|
+
}
|
|
739
|
+
async function resolveHostedAgent2(nameOrId, token) {
|
|
740
|
+
return resolveAccountAgent(nameOrId, token);
|
|
741
|
+
}
|
|
742
|
+
async function getPrivacyContext(args2) {
|
|
743
|
+
const agentName = getFlag2(args2, "--agent");
|
|
744
|
+
if (!agentName) {
|
|
745
|
+
console.error("Missing --agent <name-or-id>");
|
|
746
|
+
process.exit(1);
|
|
747
|
+
}
|
|
748
|
+
const token = await getToken();
|
|
749
|
+
if (!token) {
|
|
750
|
+
console.error("Not logged in. Run `agentis login` first.");
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
const agent = await resolveHostedAgent2(agentName, token);
|
|
754
|
+
return { token, agent };
|
|
755
|
+
}
|
|
756
|
+
function printJson(value) {
|
|
757
|
+
console.log(JSON.stringify(value, null, 2));
|
|
758
|
+
}
|
|
759
|
+
function getAmountOptions(args2) {
|
|
760
|
+
return {
|
|
761
|
+
amount: getFlag2(args2, "--amount"),
|
|
762
|
+
mint: getFlag2(args2, "--mint")
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
async function privacyFetch(context, path, body) {
|
|
766
|
+
const res = await apiFetch(`/agents/${context.agent.id}/umbra${path}`, body === void 0 ? {} : {
|
|
767
|
+
method: "POST",
|
|
768
|
+
body: JSON.stringify(body)
|
|
769
|
+
}, context.token);
|
|
770
|
+
const data = await res.json().catch(() => ({}));
|
|
771
|
+
if (!res.ok) {
|
|
772
|
+
console.error("Privacy request failed:", data.error ?? res.statusText);
|
|
773
|
+
process.exit(1);
|
|
774
|
+
}
|
|
775
|
+
return data;
|
|
776
|
+
}
|
|
777
|
+
async function privacyCommand(args2) {
|
|
778
|
+
const sub2 = args2[0];
|
|
779
|
+
const rest = args2.slice(1);
|
|
780
|
+
if (!sub2) {
|
|
781
|
+
console.log("Usage: agentis privacy <status|register|balance|deposit|withdraw|create-utxo|scan|claim-latest> --agent <name-or-id>");
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const context = await getPrivacyContext(rest);
|
|
785
|
+
switch (sub2) {
|
|
786
|
+
case "status":
|
|
787
|
+
printJson(await privacyFetch(context, "/status"));
|
|
788
|
+
break;
|
|
789
|
+
case "register":
|
|
790
|
+
printJson(await privacyFetch(context, "/register", {
|
|
791
|
+
confidential: !hasFlag(rest, "--no-confidential"),
|
|
792
|
+
anonymous: !hasFlag(rest, "--no-anonymous")
|
|
793
|
+
}));
|
|
794
|
+
break;
|
|
795
|
+
case "balance":
|
|
796
|
+
printJson(await privacyFetch(context, `/balance${getFlag2(rest, "--mint") ? `?mint=${encodeURIComponent(getFlag2(rest, "--mint"))}` : ""}`));
|
|
797
|
+
break;
|
|
798
|
+
case "deposit":
|
|
799
|
+
printJson(await privacyFetch(context, "/deposit", getAmountOptions(rest)));
|
|
800
|
+
break;
|
|
801
|
+
case "withdraw":
|
|
802
|
+
printJson(await privacyFetch(context, "/withdraw", getAmountOptions(rest)));
|
|
803
|
+
break;
|
|
804
|
+
case "create-utxo":
|
|
805
|
+
printJson(await privacyFetch(context, "/create-utxo", {
|
|
806
|
+
...getAmountOptions(rest),
|
|
807
|
+
to: getFlag2(rest, "--to")
|
|
808
|
+
}));
|
|
809
|
+
break;
|
|
810
|
+
case "scan":
|
|
811
|
+
printJson(await privacyFetch(context, "/scan"));
|
|
812
|
+
break;
|
|
813
|
+
case "claim-latest":
|
|
814
|
+
printJson(await privacyFetch(context, "/claim-latest", {}));
|
|
815
|
+
break;
|
|
816
|
+
default:
|
|
817
|
+
console.log("Usage: agentis privacy <status|register|balance|deposit|withdraw|create-utxo|scan|claim-latest> --agent <name-or-id>");
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/commands/facilitator.ts
|
|
822
|
+
import { mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
|
|
823
|
+
import { existsSync as existsSync2 } from "fs";
|
|
824
|
+
import { dirname, join as join2, resolve } from "path";
|
|
825
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
826
|
+
var DEVNET_USDC = "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU";
|
|
827
|
+
var SOLANA_DEVNET_NETWORK = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
|
|
828
|
+
async function requireAuth2() {
|
|
829
|
+
const token = await getToken();
|
|
830
|
+
if (!token) {
|
|
831
|
+
console.error("Not logged in. Run `agentis login` first.");
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
return token;
|
|
835
|
+
}
|
|
836
|
+
function flag(args2, name) {
|
|
837
|
+
const idx = args2.indexOf(name);
|
|
838
|
+
return idx === -1 ? void 0 : args2[idx + 1];
|
|
839
|
+
}
|
|
840
|
+
function hasFlag2(args2, name) {
|
|
841
|
+
return args2.includes(name);
|
|
842
|
+
}
|
|
843
|
+
function firstPositional(args2, valueFlags = []) {
|
|
844
|
+
const skip = /* @__PURE__ */ new Set();
|
|
845
|
+
for (const valueFlag of valueFlags) {
|
|
846
|
+
const idx = args2.indexOf(valueFlag);
|
|
847
|
+
if (idx !== -1) {
|
|
848
|
+
skip.add(idx);
|
|
849
|
+
skip.add(idx + 1);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return args2.find((part, idx) => !skip.has(idx) && !part.startsWith("--"));
|
|
853
|
+
}
|
|
854
|
+
function parseFeeBps(value) {
|
|
855
|
+
if (!value) return 500;
|
|
856
|
+
const parsed = Number(value);
|
|
857
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1e4) {
|
|
858
|
+
console.error("--fee-bps must be between 0 and 10000");
|
|
859
|
+
process.exit(1);
|
|
860
|
+
}
|
|
861
|
+
return parsed;
|
|
862
|
+
}
|
|
863
|
+
async function renderTemplateDir(sourceDir, targetDir, values) {
|
|
864
|
+
const entries = await readdir(sourceDir);
|
|
865
|
+
await mkdir(targetDir, { recursive: true });
|
|
866
|
+
for (const entry2 of entries) {
|
|
867
|
+
const sourcePath = join2(sourceDir, entry2);
|
|
868
|
+
const stats = await stat(sourcePath);
|
|
869
|
+
const targetName = entry2.endsWith(".tpl") ? entry2.slice(0, -4) : entry2;
|
|
870
|
+
const targetPath = join2(targetDir, targetName);
|
|
871
|
+
if (stats.isDirectory()) {
|
|
872
|
+
await renderTemplateDir(sourcePath, targetPath, values);
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
let content = await readFile(sourcePath, "utf8");
|
|
876
|
+
for (const [key, value] of Object.entries(values)) {
|
|
877
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
878
|
+
}
|
|
879
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
880
|
+
await writeFile(targetPath, content);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function facilitatorTemplateDir() {
|
|
884
|
+
const candidates = [
|
|
885
|
+
// Source layout: packages/cli/src/commands -> packages/cli/templates
|
|
886
|
+
join2(import.meta.dir, "../../templates/facilitator"),
|
|
887
|
+
// Published layout: packages/cli/dist -> packages/cli/templates
|
|
888
|
+
join2(import.meta.dir, "../templates/facilitator")
|
|
889
|
+
];
|
|
890
|
+
const found = candidates.find((candidate) => existsSync2(candidate));
|
|
891
|
+
if (!found) {
|
|
892
|
+
throw new Error("Could not locate facilitator template directory in the Agentis CLI package");
|
|
893
|
+
}
|
|
894
|
+
return found;
|
|
895
|
+
}
|
|
896
|
+
async function facilitatorCommand(args2) {
|
|
897
|
+
const sub2 = args2[0];
|
|
898
|
+
switch (sub2) {
|
|
899
|
+
case "create":
|
|
900
|
+
await facilitatorCreate(args2.slice(1));
|
|
901
|
+
break;
|
|
902
|
+
case "list":
|
|
903
|
+
await facilitatorList();
|
|
904
|
+
break;
|
|
905
|
+
case "publish":
|
|
906
|
+
await facilitatorPublish(args2.slice(1));
|
|
907
|
+
break;
|
|
908
|
+
default:
|
|
909
|
+
console.log("Usage: agentis facilitator <create|list|publish>");
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
async function facilitatorCreate(args2) {
|
|
913
|
+
const name = firstPositional(args2, ["--dir", "--network", "--mint", "--fee-bps"]);
|
|
914
|
+
if (!name) {
|
|
915
|
+
console.error("Usage: agentis facilitator create <name> [--dir <path>] [--network solana-devnet] [--mint <mint>] [--fee-bps <bps>] [--listed]");
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
const token = await requireAuth2();
|
|
919
|
+
const network = flag(args2, "--network") ?? SOLANA_DEVNET_NETWORK;
|
|
920
|
+
const acceptedMint = flag(args2, "--mint") ?? DEVNET_USDC;
|
|
921
|
+
const feeBps = parseFeeBps(flag(args2, "--fee-bps"));
|
|
922
|
+
const targetDir = resolve(flag(args2, "--dir") ?? `agentis-facilitator-${name}`);
|
|
923
|
+
if (existsSync2(targetDir)) {
|
|
924
|
+
console.error(`Target directory already exists: ${targetDir}`);
|
|
925
|
+
process.exit(1);
|
|
926
|
+
}
|
|
927
|
+
const res = await apiFetch("/account/facilitators", {
|
|
928
|
+
method: "POST",
|
|
929
|
+
body: JSON.stringify({
|
|
930
|
+
name,
|
|
931
|
+
network,
|
|
932
|
+
acceptedMint,
|
|
933
|
+
feeBps,
|
|
934
|
+
listed: hasFlag2(args2, "--listed")
|
|
935
|
+
})
|
|
936
|
+
}, token);
|
|
937
|
+
if (!res.ok) {
|
|
938
|
+
const data = await res.json().catch(() => ({}));
|
|
939
|
+
console.error("Failed to register facilitator:", data.error ?? res.statusText);
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
const facilitator = await res.json();
|
|
943
|
+
const templateDir = facilitatorTemplateDir();
|
|
944
|
+
const koraApiKey = "kora_" + randomBytes2(24).toString("hex");
|
|
945
|
+
await renderTemplateDir(templateDir, targetDir, {
|
|
946
|
+
NAME: name,
|
|
947
|
+
FACILITATOR_ID: facilitator.id,
|
|
948
|
+
HEARTBEAT_SECRET: facilitator.heartbeatSecret,
|
|
949
|
+
AGENTIS_API_URL: API_BASE,
|
|
950
|
+
NETWORK: network,
|
|
951
|
+
ACCEPTED_MINT: acceptedMint,
|
|
952
|
+
FEE_BPS: String(feeBps),
|
|
953
|
+
KORA_API_KEY: koraApiKey
|
|
954
|
+
});
|
|
955
|
+
console.log("\nFacilitator scaffold created");
|
|
956
|
+
console.log(` Name: ${name}`);
|
|
957
|
+
console.log(` ID: ${facilitator.id}`);
|
|
958
|
+
console.log(` Directory: ${targetDir}`);
|
|
959
|
+
console.log(` Fee: ${feeBps} bps`);
|
|
960
|
+
console.log("\nNext:");
|
|
961
|
+
console.log(` cd ${targetDir}`);
|
|
962
|
+
console.log(" bun install");
|
|
963
|
+
console.log(" cp .env.example .env");
|
|
964
|
+
console.log(" # fill KORA_PRIVATE_KEY and fund the Kora signer with SOL");
|
|
965
|
+
console.log(" bun run dev\n");
|
|
966
|
+
}
|
|
967
|
+
async function facilitatorList() {
|
|
968
|
+
const token = await requireAuth2();
|
|
969
|
+
const res = await apiFetch("/account/facilitators", {}, token);
|
|
970
|
+
if (!res.ok) {
|
|
971
|
+
console.error("Failed to fetch facilitators");
|
|
972
|
+
process.exit(1);
|
|
973
|
+
}
|
|
974
|
+
const facilitators = await res.json();
|
|
975
|
+
if (facilitators.length === 0) {
|
|
976
|
+
console.log("No facilitators found. Run `agentis facilitator create <name>`.");
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
console.log();
|
|
980
|
+
for (const f of facilitators) {
|
|
981
|
+
const url = f.publicUrl ? ` ${f.publicUrl}` : "";
|
|
982
|
+
const listed = f.listed ? " listed" : "";
|
|
983
|
+
console.log(` ${f.name.padEnd(24)} ${f.status.padEnd(10)} ${f.id}${listed}${url}`);
|
|
984
|
+
}
|
|
985
|
+
console.log();
|
|
986
|
+
}
|
|
987
|
+
async function facilitatorPublish(args2) {
|
|
988
|
+
const nameOrId = firstPositional(args2, ["--url"]);
|
|
989
|
+
const publicUrl = flag(args2, "--url");
|
|
990
|
+
if (!nameOrId || !publicUrl) {
|
|
991
|
+
console.error("Usage: agentis facilitator publish <name-or-id> --url <public-url> [--listed]");
|
|
992
|
+
process.exit(1);
|
|
993
|
+
}
|
|
994
|
+
const token = await requireAuth2();
|
|
995
|
+
const list = await apiFetch("/account/facilitators", {}, token);
|
|
996
|
+
if (!list.ok) {
|
|
997
|
+
console.error("Failed to fetch facilitators");
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
const facilitators = await list.json();
|
|
1001
|
+
const facilitator = facilitators.find((f) => f.id === nameOrId || f.name === nameOrId);
|
|
1002
|
+
if (!facilitator) {
|
|
1003
|
+
console.error(`Facilitator not found: ${nameOrId}`);
|
|
1004
|
+
process.exit(1);
|
|
1005
|
+
}
|
|
1006
|
+
const res = await apiFetch(`/account/facilitators/${facilitator.id}`, {
|
|
1007
|
+
method: "PATCH",
|
|
1008
|
+
body: JSON.stringify({
|
|
1009
|
+
publicUrl,
|
|
1010
|
+
listed: hasFlag2(args2, "--listed") ? true : facilitator.listed
|
|
1011
|
+
})
|
|
1012
|
+
}, token);
|
|
1013
|
+
if (!res.ok) {
|
|
1014
|
+
const data = await res.json().catch(() => ({}));
|
|
1015
|
+
console.error("Failed to publish facilitator:", data.error ?? res.statusText);
|
|
1016
|
+
process.exit(1);
|
|
1017
|
+
}
|
|
1018
|
+
const updated = await res.json();
|
|
1019
|
+
console.log(`Published ${updated.name}: ${updated.publicUrl}`);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// src/commands/earn.ts
|
|
1023
|
+
import { address as address2, getAddressEncoder, getProgramDerivedAddress } from "@solana/kit";
|
|
1024
|
+
var MAINNET_RPC = process.env.AGENTIS_MAINNET_RPC_URL ?? "https://api.mainnet-beta.solana.com";
|
|
1025
|
+
var USDC_MAINNET_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
1026
|
+
var TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
1027
|
+
var ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
1028
|
+
var addressEncoder = getAddressEncoder();
|
|
1029
|
+
function getFlag3(args2, flag2) {
|
|
1030
|
+
const idx = args2.indexOf(flag2);
|
|
1031
|
+
return idx === -1 ? void 0 : args2[idx + 1];
|
|
1032
|
+
}
|
|
1033
|
+
async function requireAuth3() {
|
|
1034
|
+
const token = await getToken();
|
|
1035
|
+
if (!token) {
|
|
1036
|
+
console.error("Not logged in. Run `agentis login` first.");
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
}
|
|
1039
|
+
return token;
|
|
1040
|
+
}
|
|
1041
|
+
async function earnCommand(args2) {
|
|
1042
|
+
const sub2 = args2[0];
|
|
1043
|
+
switch (sub2) {
|
|
1044
|
+
case "deposit":
|
|
1045
|
+
await earnDeposit(args2.slice(1));
|
|
1046
|
+
break;
|
|
1047
|
+
case "positions":
|
|
1048
|
+
await earnPositions(args2.slice(1));
|
|
1049
|
+
break;
|
|
1050
|
+
case "sweep":
|
|
1051
|
+
await earnSweep(args2.slice(1));
|
|
1052
|
+
break;
|
|
1053
|
+
default:
|
|
1054
|
+
console.log("Usage: agentis earn <deposit|positions|sweep>");
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async function earnDeposit(args2) {
|
|
1058
|
+
const agentName = args2[0];
|
|
1059
|
+
const asset = getFlag3(args2, "--asset") ?? "USDC";
|
|
1060
|
+
const amount = getFlag3(args2, "--amount");
|
|
1061
|
+
const mainnet = args2.includes("--mainnet");
|
|
1062
|
+
if (!agentName || !amount || !mainnet) {
|
|
1063
|
+
console.error("Usage: agentis earn deposit <agent> --asset USDC --amount <amount> --mainnet");
|
|
1064
|
+
process.exit(1);
|
|
1065
|
+
}
|
|
1066
|
+
const amountNum = Number(amount);
|
|
1067
|
+
if (!Number.isFinite(amountNum) || amountNum <= 0) {
|
|
1068
|
+
console.error("Invalid amount");
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
const token = await requireAuth3();
|
|
1072
|
+
const agent = await resolveAccountAgent(agentName, token);
|
|
1073
|
+
console.log(`
|
|
1074
|
+
Depositing ${amountNum} ${asset.toUpperCase()} into Jupiter Earn from ${agent.name} on mainnet...`);
|
|
1075
|
+
const res = await apiFetch(`/agents/${agent.id}/earn/deposit`, {
|
|
1076
|
+
method: "POST",
|
|
1077
|
+
body: JSON.stringify({
|
|
1078
|
+
network: "mainnet",
|
|
1079
|
+
asset,
|
|
1080
|
+
amount: amountNum
|
|
1081
|
+
})
|
|
1082
|
+
}, token);
|
|
1083
|
+
const data = await res.json().catch(() => ({}));
|
|
1084
|
+
if (!res.ok) {
|
|
1085
|
+
console.error("Earn deposit failed:", data.error ?? res.statusText);
|
|
1086
|
+
process.exit(1);
|
|
1087
|
+
}
|
|
1088
|
+
console.log("\nEarn deposit submitted.");
|
|
1089
|
+
console.log(` Signature: ${data.signature}`);
|
|
1090
|
+
console.log(` Amount: ${data.amount} ${asset.toUpperCase()}`);
|
|
1091
|
+
console.log(` Explorer: https://solscan.io/tx/${data.signature}
|
|
1092
|
+
`);
|
|
1093
|
+
}
|
|
1094
|
+
async function earnPositions(args2) {
|
|
1095
|
+
const agentName = args2[0];
|
|
1096
|
+
const mainnet = args2.includes("--mainnet");
|
|
1097
|
+
const showAll = args2.includes("--all");
|
|
1098
|
+
if (!agentName || !mainnet) {
|
|
1099
|
+
console.error("Usage: agentis earn positions <agent> --mainnet [--all]");
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
const token = await requireAuth3();
|
|
1103
|
+
const agent = await resolveAccountAgent(agentName, token);
|
|
1104
|
+
const res = await apiFetch(`/agents/${agent.id}/earn/positions?network=mainnet`, {}, token);
|
|
1105
|
+
const data = await res.json().catch(() => ({}));
|
|
1106
|
+
if (!res.ok) {
|
|
1107
|
+
console.error("Earn positions failed:", data.error ?? res.statusText);
|
|
1108
|
+
process.exit(1);
|
|
1109
|
+
}
|
|
1110
|
+
const positions = Array.isArray(data.positions) ? data.positions : [];
|
|
1111
|
+
const visible = showAll ? positions : positions.filter((p) => Number(p.underlyingAssets ?? 0) > 0 || Number(p.shares ?? 0) > 0);
|
|
1112
|
+
console.log(`
|
|
1113
|
+
Jupiter Earn positions for ${agent.name} (${data.walletAddress})`);
|
|
1114
|
+
if (visible.length === 0) {
|
|
1115
|
+
console.log(showAll ? " No positions returned." : " No non-zero positions. Use --all to show empty vaults.");
|
|
1116
|
+
console.log();
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
for (const p of visible) {
|
|
1120
|
+
const symbol = p.token?.asset?.uiSymbol ?? p.token?.asset?.symbol ?? p.token?.symbol ?? "UNKNOWN";
|
|
1121
|
+
const jlSymbol = p.token?.uiSymbol ?? p.token?.symbol ?? "jlToken";
|
|
1122
|
+
const decimals = Number(p.token?.asset?.decimals ?? p.token?.decimals ?? 6);
|
|
1123
|
+
const underlyingUi = Number(p.underlyingAssets ?? 0) / 10 ** decimals;
|
|
1124
|
+
const sharesUi = Number(p.shares ?? 0) / 10 ** Number(p.token?.decimals ?? decimals);
|
|
1125
|
+
console.log(` ${symbol.padEnd(8)} ${underlyingUi.toFixed(6)} supplied (${sharesUi.toFixed(6)} ${jlSymbol})`);
|
|
1126
|
+
console.log(` token: ${p.token?.address ?? "unknown"}`);
|
|
1127
|
+
}
|
|
1128
|
+
console.log();
|
|
1129
|
+
}
|
|
1130
|
+
async function mainnetRpc(method, params) {
|
|
1131
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
1132
|
+
const res = await fetch(MAINNET_RPC, {
|
|
1133
|
+
method: "POST",
|
|
1134
|
+
headers: { "content-type": "application/json" },
|
|
1135
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
|
|
1136
|
+
});
|
|
1137
|
+
const data = await res.json();
|
|
1138
|
+
if (!data.error) return data.result;
|
|
1139
|
+
const message = data.error.message ?? `RPC ${method} failed`;
|
|
1140
|
+
const isRateLimit = /too many requests|rate/i.test(message);
|
|
1141
|
+
if (!isRateLimit || attempt === 4) throw new Error(message);
|
|
1142
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500 * (attempt + 1)));
|
|
1143
|
+
}
|
|
1144
|
+
throw new Error(`RPC ${method} failed`);
|
|
1145
|
+
}
|
|
1146
|
+
async function getAssociatedTokenAddress(owner, mint) {
|
|
1147
|
+
const [ata] = await getProgramDerivedAddress({
|
|
1148
|
+
programAddress: address2(ASSOCIATED_TOKEN_PROGRAM),
|
|
1149
|
+
seeds: [
|
|
1150
|
+
addressEncoder.encode(address2(owner)),
|
|
1151
|
+
addressEncoder.encode(address2(TOKEN_PROGRAM)),
|
|
1152
|
+
addressEncoder.encode(address2(mint))
|
|
1153
|
+
]
|
|
1154
|
+
});
|
|
1155
|
+
return ata;
|
|
1156
|
+
}
|
|
1157
|
+
function readSplTokenAmountFromBase64(data) {
|
|
1158
|
+
const bytes = Buffer.from(data, "base64");
|
|
1159
|
+
if (bytes.length < 72) return 0n;
|
|
1160
|
+
return bytes.readBigUInt64LE(64);
|
|
1161
|
+
}
|
|
1162
|
+
async function getMainnetUsdcBalancesAtomic(walletAddresses) {
|
|
1163
|
+
const entries = await Promise.all(
|
|
1164
|
+
walletAddresses.map(async (wallet) => ({
|
|
1165
|
+
wallet,
|
|
1166
|
+
ata: await getAssociatedTokenAddress(wallet, USDC_MAINNET_MINT)
|
|
1167
|
+
}))
|
|
1168
|
+
);
|
|
1169
|
+
const balances = /* @__PURE__ */ new Map();
|
|
1170
|
+
for (let i = 0; i < entries.length; i += 100) {
|
|
1171
|
+
const chunk = entries.slice(i, i + 100);
|
|
1172
|
+
const result = await mainnetRpc("getMultipleAccounts", [
|
|
1173
|
+
chunk.map((entry2) => entry2.ata),
|
|
1174
|
+
{ encoding: "base64", commitment: "confirmed" }
|
|
1175
|
+
]);
|
|
1176
|
+
for (let j = 0; j < chunk.length; j++) {
|
|
1177
|
+
const account = result.value?.[j];
|
|
1178
|
+
const amount = account ? readSplTokenAmountFromBase64(account.data[0]) : 0n;
|
|
1179
|
+
balances.set(chunk[j].wallet, amount);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return balances;
|
|
1183
|
+
}
|
|
1184
|
+
function atomicToUiString(amount, decimals = 6) {
|
|
1185
|
+
const base = 10n ** BigInt(decimals);
|
|
1186
|
+
const whole = amount / base;
|
|
1187
|
+
const fraction = amount % base;
|
|
1188
|
+
if (fraction === 0n) return whole.toString();
|
|
1189
|
+
return `${whole}.${fraction.toString().padStart(decimals, "0").replace(/0+$/, "")}`;
|
|
1190
|
+
}
|
|
1191
|
+
async function buildSweepPlan(token) {
|
|
1192
|
+
const agents = await fetchAccountAgents(token);
|
|
1193
|
+
const plan = [];
|
|
1194
|
+
const balances = await getMainnetUsdcBalancesAtomic(agents.map((agent) => agent.walletAddress));
|
|
1195
|
+
for (const agent of agents) {
|
|
1196
|
+
const usdcAtomic = balances.get(agent.walletAddress) ?? 0n;
|
|
1197
|
+
plan.push({
|
|
1198
|
+
agent,
|
|
1199
|
+
usdcAtomic,
|
|
1200
|
+
amountUi: atomicToUiString(usdcAtomic, 6)
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return plan;
|
|
1204
|
+
}
|
|
1205
|
+
function printSweepPlan(plan) {
|
|
1206
|
+
const sweepable = plan.filter((item) => item.usdcAtomic > 0n);
|
|
1207
|
+
const totalAtomic = sweepable.reduce((sum, item) => sum + item.usdcAtomic, 0n);
|
|
1208
|
+
console.log("\nJupiter Earn sweep dry-run (mainnet USDC)");
|
|
1209
|
+
if (plan.length === 0) {
|
|
1210
|
+
console.log(" No hosted agents found.\n");
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
for (const item of plan) {
|
|
1214
|
+
const action = item.usdcAtomic > 0n ? "sweep" : "skip";
|
|
1215
|
+
const usdc = atomicToUiString(item.usdcAtomic, 6);
|
|
1216
|
+
console.log(` ${item.agent.name.padEnd(22)} ${action.padEnd(5)} ${usdc.padStart(12)} USDC`);
|
|
1217
|
+
console.log(` ${" ".repeat(22)} wallet ${item.agent.walletAddress}`);
|
|
1218
|
+
}
|
|
1219
|
+
console.log(`
|
|
1220
|
+
Total sweepable: ${atomicToUiString(totalAtomic, 6)} USDC across ${sweepable.length} agent(s).
|
|
1221
|
+
`);
|
|
1222
|
+
}
|
|
1223
|
+
async function executeSweep(token, plan) {
|
|
1224
|
+
const sweepable = plan.filter((item) => item.usdcAtomic > 0n);
|
|
1225
|
+
if (sweepable.length === 0) return;
|
|
1226
|
+
console.log("Executing Jupiter Earn sweep...");
|
|
1227
|
+
for (const item of sweepable) {
|
|
1228
|
+
console.log(`
|
|
1229
|
+
Depositing ${item.amountUi} USDC from ${item.agent.name}...`);
|
|
1230
|
+
const res = await apiFetch(`/agents/${item.agent.id}/earn/deposit`, {
|
|
1231
|
+
method: "POST",
|
|
1232
|
+
body: JSON.stringify({
|
|
1233
|
+
network: "mainnet",
|
|
1234
|
+
asset: "USDC",
|
|
1235
|
+
amount: item.amountUi
|
|
1236
|
+
})
|
|
1237
|
+
}, token);
|
|
1238
|
+
const data = await res.json().catch(() => ({}));
|
|
1239
|
+
if (!res.ok) {
|
|
1240
|
+
console.error(` Failed: ${data.error ?? res.statusText}`);
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
console.log(` Signature: ${data.signature}`);
|
|
1244
|
+
console.log(` Explorer: https://solscan.io/tx/${data.signature}`);
|
|
1245
|
+
}
|
|
1246
|
+
console.log();
|
|
1247
|
+
}
|
|
1248
|
+
async function earnSweep(args2) {
|
|
1249
|
+
const dryRun = args2.includes("--dry-run");
|
|
1250
|
+
const noDryRun = args2.includes("--no-dry-run");
|
|
1251
|
+
if (dryRun && noDryRun) {
|
|
1252
|
+
console.error("Use either --dry-run or --no-dry-run, not both.");
|
|
1253
|
+
process.exit(1);
|
|
1254
|
+
}
|
|
1255
|
+
const token = await requireAuth3();
|
|
1256
|
+
const plan = await buildSweepPlan(token);
|
|
1257
|
+
if (dryRun) {
|
|
1258
|
+
printSweepPlan(plan);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (!noDryRun) {
|
|
1262
|
+
printSweepPlan(plan);
|
|
1263
|
+
}
|
|
1264
|
+
await executeSweep(token, plan);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// src/index.ts
|
|
1268
|
+
var args = process.argv.slice(2);
|
|
1269
|
+
var cmd = args[0];
|
|
1270
|
+
var sub = args[1];
|
|
1271
|
+
var blue = "\x1B[38;5;117m";
|
|
1272
|
+
var green = "\x1B[38;5;114m";
|
|
1273
|
+
var muted = "\x1B[38;5;244m";
|
|
1274
|
+
var bold = "\x1B[1m";
|
|
1275
|
+
var reset = "\x1B[0m";
|
|
1276
|
+
function showHelp() {
|
|
1277
|
+
console.log(`${blue}${bold}
|
|
1278
|
+
\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
1279
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
1280
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
1281
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551
|
|
1282
|
+
\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551
|
|
1283
|
+
\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
1284
|
+
${reset}${muted}v0.1.0${reset}
|
|
1285
|
+
|
|
1286
|
+
${bold}Agentis${reset} \u2014 financial infrastructure for AI agents
|
|
1287
|
+
|
|
1288
|
+
${green}${bold}Commands:${reset}
|
|
1289
|
+
login authenticate with your Agentis account
|
|
1290
|
+
logout remove stored credentials
|
|
1291
|
+
whoami show current account
|
|
1292
|
+
|
|
1293
|
+
wallet create --name <name> create hosted wallet (requires login)
|
|
1294
|
+
wallet create --name <name> --local create local encrypted wallet
|
|
1295
|
+
wallet list list all wallets (hosted + local)
|
|
1296
|
+
|
|
1297
|
+
agent list list your hosted agents
|
|
1298
|
+
agent create <name> create a new hosted agent
|
|
1299
|
+
--onchain-policy create with Quasar on-chain policy mode
|
|
1300
|
+
agent send <name-or-id> <to> <amount> send SOL (amount in lamports)
|
|
1301
|
+
--sol treat amount as SOL instead of lamports
|
|
1302
|
+
--token <mint> send SPL token (amount in atomic units)
|
|
1303
|
+
|
|
1304
|
+
fetch <url> --agent <name-or-id> fetch a URL and auto-pay MPP/x402 402s
|
|
1305
|
+
--method <method> HTTP method (default GET)
|
|
1306
|
+
|
|
1307
|
+
earn deposit <agent> deposit into Jupiter Earn (mainnet only)
|
|
1308
|
+
--asset USDC asset to deposit
|
|
1309
|
+
--amount <amount> UI amount, e.g. 1 for 1 USDC
|
|
1310
|
+
--mainnet required safety flag
|
|
1311
|
+
earn positions <agent> --mainnet show Jupiter Earn positions
|
|
1312
|
+
earn sweep [--dry-run|--no-dry-run] sweep all agents' mainnet USDC into Earn
|
|
1313
|
+
|
|
1314
|
+
facilitator create <name> scaffold a Kora-backed x402 facilitator
|
|
1315
|
+
--dir <path> output directory
|
|
1316
|
+
--fee-bps <bps> prepaid seller fee rate (default 500)
|
|
1317
|
+
--listed opt into public facilitator discovery
|
|
1318
|
+
facilitator list list registered facilitators
|
|
1319
|
+
facilitator publish <name-or-id> --url set public URL and optional listing
|
|
1320
|
+
|
|
1321
|
+
privacy status --agent <name-or-id> show direct Umbra account status
|
|
1322
|
+
privacy register --agent <name-or-id> register server-side Privy wallet with Umbra
|
|
1323
|
+
privacy balance --agent <name-or-id> show encrypted balance
|
|
1324
|
+
privacy deposit --agent <name-or-id> deposit into encrypted balance
|
|
1325
|
+
--amount <atomic> token amount in atomic units
|
|
1326
|
+
--mint <mint> token mint (default: devnet wSOL/SOL)
|
|
1327
|
+
privacy withdraw --agent <name-or-id> withdraw encrypted balance to public balance
|
|
1328
|
+
privacy create-utxo --agent <name-or-id> create receiver-claimable UTXO
|
|
1329
|
+
--to <wallet> destination wallet
|
|
1330
|
+
privacy scan --agent <name-or-id> scan claimable UTXOs
|
|
1331
|
+
privacy claim-latest --agent <name-or-id> claim latest publicReceived UTXO
|
|
1332
|
+
|
|
1333
|
+
policy get <name-or-id> show agent policy
|
|
1334
|
+
policy set <name-or-id> [flags] update agent policy
|
|
1335
|
+
policy init-onchain <name-or-id> initialize Quasar policy PDAs after funding
|
|
1336
|
+
--kill activate kill switch
|
|
1337
|
+
--resume deactivate kill switch
|
|
1338
|
+
--max-per-tx <usd> max spend per transaction
|
|
1339
|
+
--hourly <usd> hourly spend limit
|
|
1340
|
+
--daily <usd> daily spend limit
|
|
1341
|
+
--monthly <usd> monthly spend limit
|
|
1342
|
+
--budget <usd> total lifetime budget cap
|
|
1343
|
+
--allow <domain> add domain to whitelist
|
|
1344
|
+
--disallow <domain> remove domain from whitelist
|
|
1345
|
+
`);
|
|
1346
|
+
}
|
|
1347
|
+
async function main() {
|
|
1348
|
+
switch (cmd) {
|
|
1349
|
+
case "login":
|
|
1350
|
+
await login();
|
|
1351
|
+
break;
|
|
1352
|
+
case "logout":
|
|
1353
|
+
await logout();
|
|
1354
|
+
break;
|
|
1355
|
+
case "whoami":
|
|
1356
|
+
await whoami();
|
|
1357
|
+
break;
|
|
1358
|
+
case "wallet":
|
|
1359
|
+
switch (sub) {
|
|
1360
|
+
case "create":
|
|
1361
|
+
await walletCreate(args.slice(2));
|
|
1362
|
+
break;
|
|
1363
|
+
case "list":
|
|
1364
|
+
await walletList();
|
|
1365
|
+
break;
|
|
1366
|
+
default:
|
|
1367
|
+
console.log("Usage: agentis wallet <create|list>");
|
|
1368
|
+
}
|
|
1369
|
+
break;
|
|
1370
|
+
case "agent":
|
|
1371
|
+
switch (sub) {
|
|
1372
|
+
case "list":
|
|
1373
|
+
await agentList();
|
|
1374
|
+
break;
|
|
1375
|
+
case "create":
|
|
1376
|
+
await agentCreate(args.slice(2));
|
|
1377
|
+
break;
|
|
1378
|
+
case "send":
|
|
1379
|
+
await agentSend(args.slice(2));
|
|
1380
|
+
break;
|
|
1381
|
+
case "balance":
|
|
1382
|
+
await agentBalance(args[2]);
|
|
1383
|
+
break;
|
|
1384
|
+
default:
|
|
1385
|
+
console.log("Usage: agentis agent <list|create|send|balance>");
|
|
1386
|
+
}
|
|
1387
|
+
break;
|
|
1388
|
+
case "policy":
|
|
1389
|
+
switch (sub) {
|
|
1390
|
+
case "get":
|
|
1391
|
+
await policyGet(args[2]);
|
|
1392
|
+
break;
|
|
1393
|
+
case "set":
|
|
1394
|
+
await policySet(args[2], args.slice(3));
|
|
1395
|
+
break;
|
|
1396
|
+
case "init-onchain":
|
|
1397
|
+
await policyInitOnchain(args[2]);
|
|
1398
|
+
break;
|
|
1399
|
+
default:
|
|
1400
|
+
console.log("Usage: agentis policy <get|set|init-onchain>");
|
|
1401
|
+
}
|
|
1402
|
+
break;
|
|
1403
|
+
case "fetch":
|
|
1404
|
+
await paidFetch(args.slice(1));
|
|
1405
|
+
break;
|
|
1406
|
+
case "privacy":
|
|
1407
|
+
await privacyCommand(args.slice(1));
|
|
1408
|
+
break;
|
|
1409
|
+
case "facilitator":
|
|
1410
|
+
await facilitatorCommand(args.slice(1));
|
|
1411
|
+
break;
|
|
1412
|
+
case "earn":
|
|
1413
|
+
await earnCommand(args.slice(1));
|
|
1414
|
+
break;
|
|
1415
|
+
default:
|
|
1416
|
+
showHelp();
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
main().catch((err) => {
|
|
1420
|
+
console.error("Error:", err.message);
|
|
1421
|
+
process.exit(1);
|
|
1422
|
+
});
|