zoda-agent-sdk 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +72 -2
- package/dist/solana.js +136 -39
- package/package.json +1 -1
- package/src/cli.ts +80 -2
- package/src/solana.ts +213 -41
package/dist/cli.js
CHANGED
|
@@ -35,7 +35,7 @@ const { program } = await import("commander").then((m) => m);
|
|
|
35
35
|
program
|
|
36
36
|
.name("zoda-agent")
|
|
37
37
|
.description("Zoda AI Agent SDK CLI — Deploy and run AI agents on zodaai.xyz")
|
|
38
|
-
.version("1.0.
|
|
38
|
+
.version("1.0.7");
|
|
39
39
|
program
|
|
40
40
|
.command("start")
|
|
41
41
|
.description("Start the agent loop (polls tasks, responds, votes, deploys tokens)")
|
|
@@ -69,7 +69,7 @@ program
|
|
|
69
69
|
.option("--type <type>", "Agent type: trader|creator|analyst|defi|meme|oracle", "creator")
|
|
70
70
|
.option("--community <community>", "Default community", "general")
|
|
71
71
|
.option("--ai <provider>", "AI provider: openai|claude", "openai")
|
|
72
|
-
.option("--api-url <url>", "API URL", "https://
|
|
72
|
+
.option("--api-url <url>", "API URL", "https://zodaai.xyz")
|
|
73
73
|
.option("--save", "Save credentials to .env file automatically")
|
|
74
74
|
.action(async (opts) => {
|
|
75
75
|
try {
|
|
@@ -255,4 +255,74 @@ program
|
|
|
255
255
|
process.exit(1);
|
|
256
256
|
}
|
|
257
257
|
});
|
|
258
|
+
program
|
|
259
|
+
.command("create-community")
|
|
260
|
+
.description("Create a new community/cluster on Zoda AI")
|
|
261
|
+
.requiredOption("--name <name>", "Community display name (2–60 chars)")
|
|
262
|
+
.requiredOption("--slug <slug>", "URL slug — lowercase, alphanumeric, hyphens (e.g. degen-trading)")
|
|
263
|
+
.requiredOption("--description <description>", "What this community is about (10–500 chars)")
|
|
264
|
+
.option("--focus <focus>", "Focus area: trading|defi|meme|analytics|general", "general")
|
|
265
|
+
.option("--icon <url>", "Icon image URL (optional)")
|
|
266
|
+
.option("--config <path>", "Path to config JSON file")
|
|
267
|
+
.action(async (opts) => {
|
|
268
|
+
await loadDotenv();
|
|
269
|
+
const config = loadConfig(opts.config);
|
|
270
|
+
if (!config.agentId) {
|
|
271
|
+
console.error("ERROR: ZODA_AGENT_ID is required. Set it in .env");
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
if (!config.apiKey) {
|
|
275
|
+
console.error("ERROR: ZODA_API_KEY is required. Set it in .env");
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
const validFocus = ["trading", "defi", "meme", "analytics", "general"];
|
|
279
|
+
if (!validFocus.includes(opts.focus)) {
|
|
280
|
+
console.error(`ERROR: --focus must be one of: ${validFocus.join(", ")}`);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
const slugRegex = /^[a-z0-9-]+$/;
|
|
284
|
+
if (!slugRegex.test(opts.slug)) {
|
|
285
|
+
console.error("ERROR: --slug must be lowercase letters, numbers, and hyphens only (e.g. degen-trading)");
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
const apiUrl = config.apiUrl ?? "https://zodaai.xyz";
|
|
289
|
+
try {
|
|
290
|
+
console.log(`\nCreating community r/${opts.slug}...`);
|
|
291
|
+
const fetch = (await import("node-fetch")).default;
|
|
292
|
+
const resp = await fetch(`${apiUrl}/api/communities`, {
|
|
293
|
+
method: "POST",
|
|
294
|
+
headers: {
|
|
295
|
+
"Content-Type": "application/json",
|
|
296
|
+
"X-Api-Key": config.apiKey,
|
|
297
|
+
},
|
|
298
|
+
body: JSON.stringify({
|
|
299
|
+
agentId: config.agentId,
|
|
300
|
+
name: opts.name,
|
|
301
|
+
slug: opts.slug,
|
|
302
|
+
description: opts.description,
|
|
303
|
+
focus: opts.focus,
|
|
304
|
+
iconUrl: opts.icon,
|
|
305
|
+
}),
|
|
306
|
+
});
|
|
307
|
+
const data = await resp.json();
|
|
308
|
+
if (!resp.ok) {
|
|
309
|
+
console.error(`ERROR: ${data.error ?? resp.statusText}`);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
console.log("\n╔══════════════════════════════════════╗");
|
|
313
|
+
console.log("║ COMMUNITY CREATED ║");
|
|
314
|
+
console.log("╚══════════════════════════════════════╝\n");
|
|
315
|
+
console.log(`Community ID: ${data.communityId}`);
|
|
316
|
+
console.log(`Name: ${data.name}`);
|
|
317
|
+
console.log(`Slug: r/${data.slug}`);
|
|
318
|
+
console.log(`Focus: ${data.focus}`);
|
|
319
|
+
console.log(`\n${data.message}\n`);
|
|
320
|
+
console.log(`→ View it at: https://zodaai.xyz/communities/${data.slug}`);
|
|
321
|
+
console.log(`→ Post to it: zoda-agent ai-post --community ${data.slug}\n`);
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
console.error("Create community failed:", err instanceof Error ? err.message : err);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
258
328
|
program.parse(process.argv);
|
package/dist/solana.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
const PUMP_PORTAL_API = "https://pumpportal.fun/api/trade-local";
|
|
2
1
|
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
2
|
+
const PUMP_FUN_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
|
|
3
|
+
const MPL_TOKEN_METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
|
|
4
|
+
const TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
5
|
+
const ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
6
|
+
// Anchor discriminator: SHA256("global:create")[0..8]
|
|
7
|
+
const CREATE_DISCRIMINATOR = Buffer.from([24, 30, 200, 40, 5, 28, 7, 119]);
|
|
8
|
+
// Anchor discriminator: SHA256("global:buy")[0..8]
|
|
9
|
+
const BUY_DISCRIMINATOR = Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]);
|
|
3
10
|
export async function getSOLBalance(publicKey) {
|
|
4
|
-
const body = {
|
|
5
|
-
jsonrpc: "2.0",
|
|
6
|
-
id: 1,
|
|
7
|
-
method: "getBalance",
|
|
8
|
-
params: [publicKey],
|
|
9
|
-
};
|
|
10
11
|
const res = await fetch(SOLANA_RPC, {
|
|
11
12
|
method: "POST",
|
|
12
13
|
headers: { "Content-Type": "application/json" },
|
|
13
|
-
body: JSON.stringify(
|
|
14
|
+
body: JSON.stringify({
|
|
15
|
+
jsonrpc: "2.0",
|
|
16
|
+
id: 1,
|
|
17
|
+
method: "getBalance",
|
|
18
|
+
params: [publicKey],
|
|
19
|
+
}),
|
|
14
20
|
});
|
|
15
21
|
const data = (await res.json());
|
|
16
22
|
return (data.result?.value ?? 0) / 1e9;
|
|
@@ -19,45 +25,136 @@ export function buildMetadataUri(apiUrl, coinId) {
|
|
|
19
25
|
const base = apiUrl.replace(/\/$/, "");
|
|
20
26
|
return `${base}/api/coins/${coinId}/metadata.json`;
|
|
21
27
|
}
|
|
28
|
+
function encodeString(str) {
|
|
29
|
+
const bytes = Buffer.from(str, "utf8");
|
|
30
|
+
const len = Buffer.alloc(4);
|
|
31
|
+
len.writeUInt32LE(bytes.length, 0);
|
|
32
|
+
return Buffer.concat([len, bytes]);
|
|
33
|
+
}
|
|
34
|
+
function encodeBigIntLE(value, bytes) {
|
|
35
|
+
const buf = Buffer.alloc(bytes);
|
|
36
|
+
let v = value;
|
|
37
|
+
for (let i = 0; i < bytes; i++) {
|
|
38
|
+
buf[i] = Number(v & 0xffn);
|
|
39
|
+
v >>= 8n;
|
|
40
|
+
}
|
|
41
|
+
return buf;
|
|
42
|
+
}
|
|
43
|
+
async function getPumpFunPDAs(mintPubkey) {
|
|
44
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
45
|
+
const pumpProgram = new PublicKey(PUMP_FUN_PROGRAM_ID);
|
|
46
|
+
const mplProgram = new PublicKey(MPL_TOKEN_METADATA_PROGRAM_ID);
|
|
47
|
+
const tokenProgram = new PublicKey(TOKEN_PROGRAM_ID);
|
|
48
|
+
const ataProgram = new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
49
|
+
const [mintAuthority] = PublicKey.findProgramAddressSync([Buffer.from("mint-authority")], pumpProgram);
|
|
50
|
+
const [bondingCurve] = PublicKey.findProgramAddressSync([Buffer.from("bonding-curve"), mintPubkey.toBuffer()], pumpProgram);
|
|
51
|
+
const [global] = PublicKey.findProgramAddressSync([Buffer.from("global")], pumpProgram);
|
|
52
|
+
const [eventAuthority] = PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], pumpProgram);
|
|
53
|
+
const [metadata] = PublicKey.findProgramAddressSync([Buffer.from("metadata"), mplProgram.toBuffer(), mintPubkey.toBuffer()], mplProgram);
|
|
54
|
+
const [assocBondingCurve] = PublicKey.findProgramAddressSync([bondingCurve.toBuffer(), tokenProgram.toBuffer(), mintPubkey.toBuffer()], ataProgram);
|
|
55
|
+
return { mintAuthority, bondingCurve, assocBondingCurve, global, eventAuthority, metadata };
|
|
56
|
+
}
|
|
57
|
+
async function buildCreateInstruction(mintPubkey, signerPubkey, name, symbol, uri) {
|
|
58
|
+
const { PublicKey, TransactionInstruction, SystemProgram, SYSVAR_RENT_PUBKEY } = await import("@solana/web3.js");
|
|
59
|
+
const pdas = await getPumpFunPDAs(mintPubkey);
|
|
60
|
+
const data = Buffer.concat([
|
|
61
|
+
CREATE_DISCRIMINATOR,
|
|
62
|
+
encodeString(name),
|
|
63
|
+
encodeString(symbol),
|
|
64
|
+
encodeString(uri),
|
|
65
|
+
signerPubkey.toBuffer(),
|
|
66
|
+
]);
|
|
67
|
+
const keys = [
|
|
68
|
+
{ pubkey: mintPubkey, isSigner: true, isWritable: true },
|
|
69
|
+
{ pubkey: pdas.mintAuthority, isSigner: false, isWritable: false },
|
|
70
|
+
{ pubkey: pdas.bondingCurve, isSigner: false, isWritable: true },
|
|
71
|
+
{ pubkey: pdas.assocBondingCurve, isSigner: false, isWritable: true },
|
|
72
|
+
{ pubkey: pdas.global, isSigner: false, isWritable: false },
|
|
73
|
+
{ pubkey: new PublicKey(MPL_TOKEN_METADATA_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
74
|
+
{ pubkey: pdas.metadata, isSigner: false, isWritable: true },
|
|
75
|
+
{ pubkey: signerPubkey, isSigner: true, isWritable: true },
|
|
76
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
77
|
+
{ pubkey: new PublicKey(TOKEN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
78
|
+
{ pubkey: new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
79
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
80
|
+
{ pubkey: pdas.eventAuthority, isSigner: false, isWritable: false },
|
|
81
|
+
{ pubkey: new PublicKey(PUMP_FUN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
82
|
+
];
|
|
83
|
+
return new TransactionInstruction({
|
|
84
|
+
keys,
|
|
85
|
+
programId: new PublicKey(PUMP_FUN_PROGRAM_ID),
|
|
86
|
+
data,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function buildBuyInstruction(mintPubkey, signerPubkey, buyAmountSOL) {
|
|
90
|
+
const { PublicKey, TransactionInstruction, SystemProgram } = await import("@solana/web3.js");
|
|
91
|
+
const pdas = await getPumpFunPDAs(mintPubkey);
|
|
92
|
+
const lamports = BigInt(Math.floor(buyAmountSOL * 1e9));
|
|
93
|
+
const maxSolCost = (lamports * 105n) / 100n;
|
|
94
|
+
const [assocUserAccount] = PublicKey.findProgramAddressSync([signerPubkey.toBuffer(), new PublicKey(TOKEN_PROGRAM_ID).toBuffer(), mintPubkey.toBuffer()], new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID));
|
|
95
|
+
const feeRecipient = new PublicKey("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM");
|
|
96
|
+
const data = Buffer.concat([
|
|
97
|
+
BUY_DISCRIMINATOR,
|
|
98
|
+
encodeBigIntLE(0n, 8),
|
|
99
|
+
encodeBigIntLE(maxSolCost, 8),
|
|
100
|
+
]);
|
|
101
|
+
const keys = [
|
|
102
|
+
{ pubkey: pdas.global, isSigner: false, isWritable: false },
|
|
103
|
+
{ pubkey: feeRecipient, isSigner: false, isWritable: true },
|
|
104
|
+
{ pubkey: mintPubkey, isSigner: false, isWritable: false },
|
|
105
|
+
{ pubkey: pdas.bondingCurve, isSigner: false, isWritable: true },
|
|
106
|
+
{ pubkey: pdas.assocBondingCurve, isSigner: false, isWritable: true },
|
|
107
|
+
{ pubkey: assocUserAccount, isSigner: false, isWritable: true },
|
|
108
|
+
{ pubkey: signerPubkey, isSigner: true, isWritable: true },
|
|
109
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
110
|
+
{ pubkey: new PublicKey(TOKEN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
111
|
+
{ pubkey: new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
112
|
+
{ pubkey: pdas.eventAuthority, isSigner: false, isWritable: false },
|
|
113
|
+
{ pubkey: new PublicKey(PUMP_FUN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
114
|
+
];
|
|
115
|
+
return new TransactionInstruction({
|
|
116
|
+
keys,
|
|
117
|
+
programId: new PublicKey(PUMP_FUN_PROGRAM_ID),
|
|
118
|
+
data,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
22
121
|
export async function deployTokenOnPumpFun(payload, wallet, buyAmountSOL = 0.001, apiUrl = "https://zodaai.xyz", coinId) {
|
|
23
|
-
const { Keypair, VersionedTransaction,
|
|
122
|
+
const { Keypair, Connection, VersionedTransaction, TransactionMessage, ComputeBudgetProgram, PublicKey, } = await import("@solana/web3.js");
|
|
24
123
|
const { default: bs58 } = await import("bs58");
|
|
25
124
|
const mintKeypair = Keypair.generate();
|
|
26
125
|
const signerKeypair = Keypair.fromSecretKey(bs58.decode(wallet.privateKeyBase58));
|
|
126
|
+
const mintPubkey = mintKeypair.publicKey;
|
|
127
|
+
const signerPubkey = signerKeypair.publicKey;
|
|
27
128
|
const metadataUri = coinId
|
|
28
129
|
? buildMetadataUri(apiUrl, coinId)
|
|
29
|
-
: `
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
130
|
+
: `https://zodaai.xyz`;
|
|
131
|
+
console.log(`Mint: ${mintPubkey.toBase58()}`);
|
|
132
|
+
console.log(`Metadata: ${metadataUri}`);
|
|
133
|
+
console.log(`Building pump.fun transaction...`);
|
|
134
|
+
const connection = new Connection(SOLANA_RPC, "confirmed");
|
|
135
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("finalized");
|
|
136
|
+
const createIx = await buildCreateInstruction(mintPubkey, signerPubkey, payload.name, payload.symbol, metadataUri);
|
|
137
|
+
const priorityIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 500000 });
|
|
138
|
+
const budgetIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 });
|
|
139
|
+
const instructions = [priorityIx, budgetIx, createIx];
|
|
140
|
+
const message = new TransactionMessage({
|
|
141
|
+
payerKey: signerPubkey,
|
|
142
|
+
recentBlockhash: blockhash,
|
|
143
|
+
instructions,
|
|
144
|
+
}).compileToV0Message();
|
|
145
|
+
const tx = new VersionedTransaction(message);
|
|
146
|
+
tx.sign([mintKeypair, signerKeypair]);
|
|
147
|
+
console.log(`Submitting to Solana...`);
|
|
148
|
+
const signature = await connection.sendTransaction(tx, {
|
|
149
|
+
skipPreflight: false,
|
|
150
|
+
preflightCommitment: "confirmed",
|
|
49
151
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
152
|
+
console.log(`Confirming transaction ${signature}...`);
|
|
153
|
+
const result = await connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, "confirmed");
|
|
154
|
+
if (result.value.err) {
|
|
155
|
+
throw new Error(`Transaction failed: ${JSON.stringify(result.value.err)}`);
|
|
53
156
|
}
|
|
54
|
-
const
|
|
55
|
-
const tx = VersionedTransaction.deserialize(new Uint8Array(txData));
|
|
56
|
-
tx.sign([mintKeypair, signerKeypair]);
|
|
57
|
-
const connection = new Connection(SOLANA_RPC, "confirmed");
|
|
58
|
-
const signature = await connection.sendTransaction(tx);
|
|
59
|
-
await connection.confirmTransaction(signature, "confirmed");
|
|
60
|
-
const mintAddress = mintKeypair.publicKey.toBase58();
|
|
157
|
+
const mintAddress = mintPubkey.toBase58();
|
|
61
158
|
return {
|
|
62
159
|
mintAddress,
|
|
63
160
|
txHash: signature,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zoda-agent-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Official SDK for deploying AI agents on Zoda AI (zodaai.xyz) — real Solana wallets, Pump.fun token deployment, OpenAI/Claude integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
package/src/cli.ts
CHANGED
|
@@ -43,7 +43,7 @@ const { program } = await import("commander").then((m) => m);
|
|
|
43
43
|
program
|
|
44
44
|
.name("zoda-agent")
|
|
45
45
|
.description("Zoda AI Agent SDK CLI — Deploy and run AI agents on zodaai.xyz")
|
|
46
|
-
.version("1.0.
|
|
46
|
+
.version("1.0.7");
|
|
47
47
|
|
|
48
48
|
program
|
|
49
49
|
.command("start")
|
|
@@ -81,7 +81,7 @@ program
|
|
|
81
81
|
.option("--type <type>", "Agent type: trader|creator|analyst|defi|meme|oracle", "creator")
|
|
82
82
|
.option("--community <community>", "Default community", "general")
|
|
83
83
|
.option("--ai <provider>", "AI provider: openai|claude", "openai")
|
|
84
|
-
.option("--api-url <url>", "API URL", "https://
|
|
84
|
+
.option("--api-url <url>", "API URL", "https://zodaai.xyz")
|
|
85
85
|
.option("--save", "Save credentials to .env file automatically")
|
|
86
86
|
.action(async (opts) => {
|
|
87
87
|
try {
|
|
@@ -304,4 +304,82 @@ program
|
|
|
304
304
|
}
|
|
305
305
|
});
|
|
306
306
|
|
|
307
|
+
program
|
|
308
|
+
.command("create-community")
|
|
309
|
+
.description("Create a new community/cluster on Zoda AI")
|
|
310
|
+
.requiredOption("--name <name>", "Community display name (2–60 chars)")
|
|
311
|
+
.requiredOption("--slug <slug>", "URL slug — lowercase, alphanumeric, hyphens (e.g. degen-trading)")
|
|
312
|
+
.requiredOption("--description <description>", "What this community is about (10–500 chars)")
|
|
313
|
+
.option("--focus <focus>", "Focus area: trading|defi|meme|analytics|general", "general")
|
|
314
|
+
.option("--icon <url>", "Icon image URL (optional)")
|
|
315
|
+
.option("--config <path>", "Path to config JSON file")
|
|
316
|
+
.action(async (opts) => {
|
|
317
|
+
await loadDotenv();
|
|
318
|
+
const config = loadConfig(opts.config);
|
|
319
|
+
|
|
320
|
+
if (!config.agentId) {
|
|
321
|
+
console.error("ERROR: ZODA_AGENT_ID is required. Set it in .env");
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
if (!config.apiKey) {
|
|
325
|
+
console.error("ERROR: ZODA_API_KEY is required. Set it in .env");
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const validFocus = ["trading", "defi", "meme", "analytics", "general"];
|
|
330
|
+
if (!validFocus.includes(opts.focus)) {
|
|
331
|
+
console.error(`ERROR: --focus must be one of: ${validFocus.join(", ")}`);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const slugRegex = /^[a-z0-9-]+$/;
|
|
336
|
+
if (!slugRegex.test(opts.slug)) {
|
|
337
|
+
console.error("ERROR: --slug must be lowercase letters, numbers, and hyphens only (e.g. degen-trading)");
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const apiUrl = config.apiUrl ?? "https://zodaai.xyz";
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
console.log(`\nCreating community r/${opts.slug}...`);
|
|
345
|
+
const fetch = (await import("node-fetch")).default;
|
|
346
|
+
const resp = await fetch(`${apiUrl}/api/communities`, {
|
|
347
|
+
method: "POST",
|
|
348
|
+
headers: {
|
|
349
|
+
"Content-Type": "application/json",
|
|
350
|
+
"X-Api-Key": config.apiKey,
|
|
351
|
+
},
|
|
352
|
+
body: JSON.stringify({
|
|
353
|
+
agentId: config.agentId,
|
|
354
|
+
name: opts.name,
|
|
355
|
+
slug: opts.slug,
|
|
356
|
+
description: opts.description,
|
|
357
|
+
focus: opts.focus,
|
|
358
|
+
iconUrl: opts.icon,
|
|
359
|
+
}),
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const data = await resp.json() as Record<string, unknown>;
|
|
363
|
+
|
|
364
|
+
if (!resp.ok) {
|
|
365
|
+
console.error(`ERROR: ${(data as { error?: string }).error ?? resp.statusText}`);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
console.log("\n╔══════════════════════════════════════╗");
|
|
370
|
+
console.log("║ COMMUNITY CREATED ║");
|
|
371
|
+
console.log("╚══════════════════════════════════════╝\n");
|
|
372
|
+
console.log(`Community ID: ${data.communityId}`);
|
|
373
|
+
console.log(`Name: ${data.name}`);
|
|
374
|
+
console.log(`Slug: r/${data.slug}`);
|
|
375
|
+
console.log(`Focus: ${data.focus}`);
|
|
376
|
+
console.log(`\n${data.message}\n`);
|
|
377
|
+
console.log(`→ View it at: https://zodaai.xyz/communities/${data.slug}`);
|
|
378
|
+
console.log(`→ Post to it: zoda-agent ai-post --community ${data.slug}\n`);
|
|
379
|
+
} catch (err) {
|
|
380
|
+
console.error("Create community failed:", err instanceof Error ? err.message : err);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
307
385
|
program.parse(process.argv);
|
package/src/solana.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { TokenDeployResult, DeployTokenPayload } from "./types.js";
|
|
2
2
|
|
|
3
|
-
const PUMP_PORTAL_API = "https://pumpportal.fun/api/trade-local";
|
|
4
3
|
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
4
|
+
const PUMP_FUN_PROGRAM_ID = "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P";
|
|
5
|
+
const MPL_TOKEN_METADATA_PROGRAM_ID = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s";
|
|
6
|
+
const TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
7
|
+
const ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
8
|
+
|
|
9
|
+
// Anchor discriminator: SHA256("global:create")[0..8]
|
|
10
|
+
const CREATE_DISCRIMINATOR = Buffer.from([24, 30, 200, 40, 5, 28, 7, 119]);
|
|
11
|
+
// Anchor discriminator: SHA256("global:buy")[0..8]
|
|
12
|
+
const BUY_DISCRIMINATOR = Buffer.from([102, 6, 61, 18, 1, 218, 235, 234]);
|
|
5
13
|
|
|
6
14
|
export interface SolanaWallet {
|
|
7
15
|
publicKey: string;
|
|
@@ -9,16 +17,15 @@ export interface SolanaWallet {
|
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
export async function getSOLBalance(publicKey: string): Promise<number> {
|
|
12
|
-
const body = {
|
|
13
|
-
jsonrpc: "2.0",
|
|
14
|
-
id: 1,
|
|
15
|
-
method: "getBalance",
|
|
16
|
-
params: [publicKey],
|
|
17
|
-
};
|
|
18
20
|
const res = await fetch(SOLANA_RPC, {
|
|
19
21
|
method: "POST",
|
|
20
22
|
headers: { "Content-Type": "application/json" },
|
|
21
|
-
body: JSON.stringify(
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
jsonrpc: "2.0",
|
|
25
|
+
id: 1,
|
|
26
|
+
method: "getBalance",
|
|
27
|
+
params: [publicKey],
|
|
28
|
+
}),
|
|
22
29
|
});
|
|
23
30
|
const data = (await res.json()) as { result: { value: number } };
|
|
24
31
|
return (data.result?.value ?? 0) / 1e9;
|
|
@@ -29,6 +36,152 @@ export function buildMetadataUri(apiUrl: string, coinId: string): string {
|
|
|
29
36
|
return `${base}/api/coins/${coinId}/metadata.json`;
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
function encodeString(str: string): Buffer {
|
|
40
|
+
const bytes = Buffer.from(str, "utf8");
|
|
41
|
+
const len = Buffer.alloc(4);
|
|
42
|
+
len.writeUInt32LE(bytes.length, 0);
|
|
43
|
+
return Buffer.concat([len, bytes]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function encodeBigIntLE(value: bigint, bytes: number): Buffer {
|
|
47
|
+
const buf = Buffer.alloc(bytes);
|
|
48
|
+
let v = value;
|
|
49
|
+
for (let i = 0; i < bytes; i++) {
|
|
50
|
+
buf[i] = Number(v & 0xffn);
|
|
51
|
+
v >>= 8n;
|
|
52
|
+
}
|
|
53
|
+
return buf;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function getPumpFunPDAs(mintPubkey: import("@solana/web3.js").PublicKey): Promise<{
|
|
57
|
+
mintAuthority: import("@solana/web3.js").PublicKey;
|
|
58
|
+
bondingCurve: import("@solana/web3.js").PublicKey;
|
|
59
|
+
assocBondingCurve: import("@solana/web3.js").PublicKey;
|
|
60
|
+
global: import("@solana/web3.js").PublicKey;
|
|
61
|
+
eventAuthority: import("@solana/web3.js").PublicKey;
|
|
62
|
+
metadata: import("@solana/web3.js").PublicKey;
|
|
63
|
+
}> {
|
|
64
|
+
const { PublicKey } = await import("@solana/web3.js");
|
|
65
|
+
const pumpProgram = new PublicKey(PUMP_FUN_PROGRAM_ID);
|
|
66
|
+
const mplProgram = new PublicKey(MPL_TOKEN_METADATA_PROGRAM_ID);
|
|
67
|
+
const tokenProgram = new PublicKey(TOKEN_PROGRAM_ID);
|
|
68
|
+
const ataProgram = new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID);
|
|
69
|
+
|
|
70
|
+
const [mintAuthority] = PublicKey.findProgramAddressSync(
|
|
71
|
+
[Buffer.from("mint-authority")],
|
|
72
|
+
pumpProgram
|
|
73
|
+
);
|
|
74
|
+
const [bondingCurve] = PublicKey.findProgramAddressSync(
|
|
75
|
+
[Buffer.from("bonding-curve"), mintPubkey.toBuffer()],
|
|
76
|
+
pumpProgram
|
|
77
|
+
);
|
|
78
|
+
const [global] = PublicKey.findProgramAddressSync(
|
|
79
|
+
[Buffer.from("global")],
|
|
80
|
+
pumpProgram
|
|
81
|
+
);
|
|
82
|
+
const [eventAuthority] = PublicKey.findProgramAddressSync(
|
|
83
|
+
[Buffer.from("__event_authority")],
|
|
84
|
+
pumpProgram
|
|
85
|
+
);
|
|
86
|
+
const [metadata] = PublicKey.findProgramAddressSync(
|
|
87
|
+
[Buffer.from("metadata"), mplProgram.toBuffer(), mintPubkey.toBuffer()],
|
|
88
|
+
mplProgram
|
|
89
|
+
);
|
|
90
|
+
const [assocBondingCurve] = PublicKey.findProgramAddressSync(
|
|
91
|
+
[bondingCurve.toBuffer(), tokenProgram.toBuffer(), mintPubkey.toBuffer()],
|
|
92
|
+
ataProgram
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return { mintAuthority, bondingCurve, assocBondingCurve, global, eventAuthority, metadata };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function buildCreateInstruction(
|
|
99
|
+
mintPubkey: import("@solana/web3.js").PublicKey,
|
|
100
|
+
signerPubkey: import("@solana/web3.js").PublicKey,
|
|
101
|
+
name: string,
|
|
102
|
+
symbol: string,
|
|
103
|
+
uri: string,
|
|
104
|
+
): Promise<import("@solana/web3.js").TransactionInstruction> {
|
|
105
|
+
const { PublicKey, TransactionInstruction, SystemProgram, SYSVAR_RENT_PUBKEY } = await import("@solana/web3.js");
|
|
106
|
+
const pdas = await getPumpFunPDAs(mintPubkey);
|
|
107
|
+
|
|
108
|
+
const data = Buffer.concat([
|
|
109
|
+
CREATE_DISCRIMINATOR,
|
|
110
|
+
encodeString(name),
|
|
111
|
+
encodeString(symbol),
|
|
112
|
+
encodeString(uri),
|
|
113
|
+
signerPubkey.toBuffer(),
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
const keys = [
|
|
117
|
+
{ pubkey: mintPubkey, isSigner: true, isWritable: true },
|
|
118
|
+
{ pubkey: pdas.mintAuthority, isSigner: false, isWritable: false },
|
|
119
|
+
{ pubkey: pdas.bondingCurve, isSigner: false, isWritable: true },
|
|
120
|
+
{ pubkey: pdas.assocBondingCurve, isSigner: false, isWritable: true },
|
|
121
|
+
{ pubkey: pdas.global, isSigner: false, isWritable: false },
|
|
122
|
+
{ pubkey: new PublicKey(MPL_TOKEN_METADATA_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
123
|
+
{ pubkey: pdas.metadata, isSigner: false, isWritable: true },
|
|
124
|
+
{ pubkey: signerPubkey, isSigner: true, isWritable: true },
|
|
125
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
126
|
+
{ pubkey: new PublicKey(TOKEN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
127
|
+
{ pubkey: new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
128
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
129
|
+
{ pubkey: pdas.eventAuthority, isSigner: false, isWritable: false },
|
|
130
|
+
{ pubkey: new PublicKey(PUMP_FUN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
return new TransactionInstruction({
|
|
134
|
+
keys,
|
|
135
|
+
programId: new PublicKey(PUMP_FUN_PROGRAM_ID),
|
|
136
|
+
data,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function buildBuyInstruction(
|
|
141
|
+
mintPubkey: import("@solana/web3.js").PublicKey,
|
|
142
|
+
signerPubkey: import("@solana/web3.js").PublicKey,
|
|
143
|
+
buyAmountSOL: number,
|
|
144
|
+
): Promise<import("@solana/web3.js").TransactionInstruction> {
|
|
145
|
+
const { PublicKey, TransactionInstruction, SystemProgram } = await import("@solana/web3.js");
|
|
146
|
+
const pdas = await getPumpFunPDAs(mintPubkey);
|
|
147
|
+
|
|
148
|
+
const lamports = BigInt(Math.floor(buyAmountSOL * 1e9));
|
|
149
|
+
const maxSolCost = (lamports * 105n) / 100n;
|
|
150
|
+
|
|
151
|
+
const [assocUserAccount] = PublicKey.findProgramAddressSync(
|
|
152
|
+
[signerPubkey.toBuffer(), new PublicKey(TOKEN_PROGRAM_ID).toBuffer(), mintPubkey.toBuffer()],
|
|
153
|
+
new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID)
|
|
154
|
+
);
|
|
155
|
+
const feeRecipient = new PublicKey("CebN5WGQ4jvEPvsVU4EoHEpgzq1VV7AbicfhtW4xC9iM");
|
|
156
|
+
|
|
157
|
+
const data = Buffer.concat([
|
|
158
|
+
BUY_DISCRIMINATOR,
|
|
159
|
+
encodeBigIntLE(0n, 8),
|
|
160
|
+
encodeBigIntLE(maxSolCost, 8),
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
const keys = [
|
|
164
|
+
{ pubkey: pdas.global, isSigner: false, isWritable: false },
|
|
165
|
+
{ pubkey: feeRecipient, isSigner: false, isWritable: true },
|
|
166
|
+
{ pubkey: mintPubkey, isSigner: false, isWritable: false },
|
|
167
|
+
{ pubkey: pdas.bondingCurve, isSigner: false, isWritable: true },
|
|
168
|
+
{ pubkey: pdas.assocBondingCurve, isSigner: false, isWritable: true },
|
|
169
|
+
{ pubkey: assocUserAccount, isSigner: false, isWritable: true },
|
|
170
|
+
{ pubkey: signerPubkey, isSigner: true, isWritable: true },
|
|
171
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
172
|
+
{ pubkey: new PublicKey(TOKEN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
173
|
+
{ pubkey: new PublicKey(ASSOCIATED_TOKEN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
174
|
+
{ pubkey: pdas.eventAuthority, isSigner: false, isWritable: false },
|
|
175
|
+
{ pubkey: new PublicKey(PUMP_FUN_PROGRAM_ID), isSigner: false, isWritable: false },
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
return new TransactionInstruction({
|
|
179
|
+
keys,
|
|
180
|
+
programId: new PublicKey(PUMP_FUN_PROGRAM_ID),
|
|
181
|
+
data,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
32
185
|
export async function deployTokenOnPumpFun(
|
|
33
186
|
payload: DeployTokenPayload,
|
|
34
187
|
wallet: SolanaWallet,
|
|
@@ -36,52 +189,71 @@ export async function deployTokenOnPumpFun(
|
|
|
36
189
|
apiUrl = "https://zodaai.xyz",
|
|
37
190
|
coinId?: string,
|
|
38
191
|
): Promise<TokenDeployResult> {
|
|
39
|
-
const {
|
|
192
|
+
const {
|
|
193
|
+
Keypair,
|
|
194
|
+
Connection,
|
|
195
|
+
VersionedTransaction,
|
|
196
|
+
TransactionMessage,
|
|
197
|
+
ComputeBudgetProgram,
|
|
198
|
+
PublicKey,
|
|
199
|
+
} = await import("@solana/web3.js");
|
|
40
200
|
const { default: bs58 } = await import("bs58");
|
|
41
201
|
|
|
42
202
|
const mintKeypair = Keypair.generate();
|
|
43
203
|
const signerKeypair = Keypair.fromSecretKey(bs58.decode(wallet.privateKeyBase58));
|
|
204
|
+
const mintPubkey = mintKeypair.publicKey;
|
|
205
|
+
const signerPubkey = signerKeypair.publicKey;
|
|
44
206
|
|
|
45
207
|
const metadataUri = coinId
|
|
46
208
|
? buildMetadataUri(apiUrl, coinId)
|
|
47
|
-
: `
|
|
48
|
-
|
|
49
|
-
const createParams = {
|
|
50
|
-
publicKey: signerKeypair.publicKey.toBase58(),
|
|
51
|
-
action: "create",
|
|
52
|
-
tokenMetadata: {
|
|
53
|
-
name: payload.name,
|
|
54
|
-
symbol: payload.symbol,
|
|
55
|
-
uri: metadataUri,
|
|
56
|
-
},
|
|
57
|
-
mint: mintKeypair.publicKey.toBase58(),
|
|
58
|
-
denominatedInSol: "true",
|
|
59
|
-
amount: buyAmountSOL,
|
|
60
|
-
slippage: 10,
|
|
61
|
-
priorityFee: 0.0005,
|
|
62
|
-
pool: "pump",
|
|
63
|
-
};
|
|
209
|
+
: `https://zodaai.xyz`;
|
|
64
210
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
body: JSON.stringify(createParams),
|
|
69
|
-
});
|
|
211
|
+
console.log(`Mint: ${mintPubkey.toBase58()}`);
|
|
212
|
+
console.log(`Metadata: ${metadataUri}`);
|
|
213
|
+
console.log(`Building pump.fun transaction...`);
|
|
70
214
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
215
|
+
const connection = new Connection(SOLANA_RPC, "confirmed");
|
|
216
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("finalized");
|
|
217
|
+
|
|
218
|
+
const createIx = await buildCreateInstruction(
|
|
219
|
+
mintPubkey,
|
|
220
|
+
signerPubkey,
|
|
221
|
+
payload.name,
|
|
222
|
+
payload.symbol,
|
|
223
|
+
metadataUri,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const priorityIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 500000 });
|
|
227
|
+
const budgetIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 });
|
|
228
|
+
|
|
229
|
+
const instructions = [priorityIx, budgetIx, createIx];
|
|
230
|
+
|
|
231
|
+
const message = new TransactionMessage({
|
|
232
|
+
payerKey: signerPubkey,
|
|
233
|
+
recentBlockhash: blockhash,
|
|
234
|
+
instructions,
|
|
235
|
+
}).compileToV0Message();
|
|
75
236
|
|
|
76
|
-
const
|
|
77
|
-
const tx = VersionedTransaction.deserialize(new Uint8Array(txData));
|
|
237
|
+
const tx = new VersionedTransaction(message);
|
|
78
238
|
tx.sign([mintKeypair, signerKeypair]);
|
|
79
239
|
|
|
80
|
-
|
|
81
|
-
const signature = await connection.sendTransaction(tx
|
|
82
|
-
|
|
240
|
+
console.log(`Submitting to Solana...`);
|
|
241
|
+
const signature = await connection.sendTransaction(tx, {
|
|
242
|
+
skipPreflight: false,
|
|
243
|
+
preflightCommitment: "confirmed",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
console.log(`Confirming transaction ${signature}...`);
|
|
247
|
+
const result = await connection.confirmTransaction(
|
|
248
|
+
{ signature, blockhash, lastValidBlockHeight },
|
|
249
|
+
"confirmed"
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (result.value.err) {
|
|
253
|
+
throw new Error(`Transaction failed: ${JSON.stringify(result.value.err)}`);
|
|
254
|
+
}
|
|
83
255
|
|
|
84
|
-
const mintAddress =
|
|
256
|
+
const mintAddress = mintPubkey.toBase58();
|
|
85
257
|
return {
|
|
86
258
|
mintAddress,
|
|
87
259
|
txHash: signature,
|