Skip to content

Commit 0ca14a3

Browse files
authored
Merge pull request #1011 from xwxtwd/feat/add_plugin-nft-generator
feat: Add plugin-nft-generation: create Solana NFT collections.
2 parents 7a12096 + 9325eb3 commit 0ca14a3

File tree

18 files changed

+1047
-1
lines changed

18 files changed

+1047
-1
lines changed

.env.example

+4
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ EVM_PROVIDER_URL=
157157
# Solana
158158
SOLANA_PRIVATE_KEY=
159159
SOLANA_PUBLIC_KEY=
160+
SOLANA_CLUSTER= # Default: devnet. Solana Cluster: 'devnet' | 'testnet' | 'mainnet-beta'
161+
SOLANA_ADMIN_PRIVATE_KEY= # This wallet is used to verify NFTs
162+
SOLANA_ADMIN_PUBLIC_KEY= # This wallet is used to verify NFTs
163+
SOLANA_VERIFY_TOKEN= # Authentication token for calling the verification API
160164

161165
# Fallback Wallet Configuration (deprecated)
162166
WALLET_PRIVATE_KEY=

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@ai16z/plugin-goat": "workspace:*",
4040
"@ai16z/plugin-icp": "workspace:*",
4141
"@ai16z/plugin-image-generation": "workspace:*",
42+
"@ai16z/plugin-nft-generation": "workspace:*",
4243
"@ai16z/plugin-node": "workspace:*",
4344
"@ai16z/plugin-solana": "workspace:*",
4445
"@ai16z/plugin-starknet": "workspace:*",

agent/src/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { solanaPlugin } from "@ai16z/plugin-solana";
5050
import { TEEMode, teePlugin } from "@ai16z/plugin-tee";
5151
import { tonPlugin } from "@ai16z/plugin-ton";
5252
import { zksyncEraPlugin } from "@ai16z/plugin-zksync-era";
53+
import { nftGenerationPlugin, createNFTApiRouter } from "@ai16z/plugin-nft-generation";
5354
import Database from "better-sqlite3";
5455
import fs from "fs";
5556
import path from "path";
@@ -493,6 +494,14 @@ export async function createAgent(
493494
getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x"))
494495
? evmPlugin
495496
: null,
497+
(getSecret(character, "SOLANA_PUBLIC_KEY") ||
498+
(getSecret(character, "WALLET_PUBLIC_KEY") &&
499+
!getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x"))) &&
500+
getSecret(character, "SOLANA_ADMIN_PUBLIC_KEY") &&
501+
getSecret(character, "SOLANA_PRIVATE_KEY") &&
502+
getSecret(character, "SOLANA_ADMIN_PRIVATE_KEY")
503+
? nftGenerationPlugin
504+
: null,
496505
getSecret(character, "ZEROG_PRIVATE_KEY") ? zgPlugin : null,
497506
getSecret(character, "COINBASE_COMMERCE_KEY")
498507
? coinbaseCommercePlugin

packages/core/src/environment.ts

+5
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ export const CharacterSchema = z.object({
124124
nicknames: z.array(z.string()).optional(),
125125
})
126126
.optional(),
127+
nft: z
128+
.object({
129+
prompt: z.string().optional(),
130+
})
131+
.optional(),
127132
});
128133

129134
// Type inference

packages/core/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,10 @@ export type Character = {
767767
bio: string;
768768
nicknames?: string[];
769769
};
770+
/** Optional NFT prompt */
771+
nft?: {
772+
prompt: string;
773+
}
770774
};
771775

772776
/**
@@ -1163,6 +1167,7 @@ export interface IPdfService extends Service {
11631167
export interface IAwsS3Service extends Service {
11641168
uploadFile(
11651169
imagePath: string,
1170+
subDirectory: string,
11661171
useSignedUrl: boolean,
11671172
expiresIn: number
11681173
): Promise<{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*
2+
3+
!dist/**
4+
!package.json
5+
!readme.md
6+
!tsup.config.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import eslintGlobalConfig from "../../eslint.config.mjs";
2+
3+
export default [...eslintGlobalConfig];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@ai16z/plugin-nft-generation",
3+
"version": "0.1.5-alpha.5",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@ai16z/eliza": "workspace:*",
9+
"@ai16z/plugin-image-generation": "workspace:*",
10+
"@ai16z/plugin-node": "workspace:*",
11+
"@metaplex-foundation/mpl-token-metadata": "^3.3.0",
12+
"@metaplex-foundation/mpl-toolbox": "^0.9.4",
13+
"@metaplex-foundation/umi": "^0.9.2",
14+
"@metaplex-foundation/umi-bundle-defaults": "^0.9.2",
15+
"@solana-developers/helpers": "^2.5.6",
16+
"@solana/web3.js": "1.95.5",
17+
"bs58": "6.0.0",
18+
"express": "4.21.1",
19+
"node-cache": "5.1.2",
20+
"tsup": "8.3.5"
21+
},
22+
"scripts": {
23+
"build": "tsup --format esm --dts",
24+
"dev": "tsup --format esm --dts --watch",
25+
"lint": "eslint . --fix"
26+
},
27+
"peerDependencies": {
28+
"whatwg-url": "7.1.0"
29+
}
30+
}
+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import express from "express";
2+
3+
import { AgentRuntime } from "@ai16z/eliza";
4+
import { createCollection } from "./handlers/createCollection.ts";
5+
import { createNFT, createNFTMetadata } from "./handlers/createNFT.ts";
6+
import { verifyNFT } from "./handlers/verifyNFT.ts";
7+
8+
export function createNFTApiRouter(agents: Map<string, AgentRuntime>) {
9+
const router = express.Router();
10+
11+
router.post(
12+
"/api/nft-generation/create-collection",
13+
async (req: express.Request, res: express.Response) => {
14+
const agentId = req.body.agentId;
15+
const fee = req.body.fee || 0;
16+
const runtime = agents.get(agentId);
17+
if (!runtime) {
18+
res.status(404).send("Agent not found");
19+
return;
20+
}
21+
try {
22+
const collectionAddressRes = await createCollection({
23+
runtime,
24+
collectionName: runtime.character.name,
25+
fee
26+
});
27+
28+
res.json({
29+
success: true,
30+
data: collectionAddressRes,
31+
});
32+
} catch (e: any) {
33+
console.log(e);
34+
res.json({
35+
success: false,
36+
data: JSON.stringify(e),
37+
});
38+
}
39+
}
40+
);
41+
42+
43+
router.post(
44+
"/api/nft-generation/create-nft-metadata",
45+
async (req: express.Request, res: express.Response) => {
46+
const agentId = req.body.agentId;
47+
const collectionName = req.body.collectionName;
48+
const collectionAddress = req.body.collectionAddress;
49+
const collectionAdminPublicKey = req.body.collectionAdminPublicKey;
50+
const collectionFee = req.body.collectionFee;
51+
const tokenId = req.body.tokenId;
52+
const runtime = agents.get(agentId);
53+
if (!runtime) {
54+
res.status(404).send("Agent not found");
55+
return;
56+
}
57+
58+
try {
59+
const nftInfo = await createNFTMetadata({
60+
runtime,
61+
collectionName,
62+
collectionAdminPublicKey,
63+
collectionFee,
64+
tokenId,
65+
});
66+
67+
res.json({
68+
success: true,
69+
data: {
70+
...nftInfo,
71+
collectionAddress,
72+
},
73+
});
74+
} catch (e: any) {
75+
console.log(e);
76+
res.json({
77+
success: false,
78+
data: JSON.stringify(e),
79+
});
80+
}
81+
}
82+
);
83+
84+
router.post(
85+
"/api/nft-generation/create-nft",
86+
async (req: express.Request, res: express.Response) => {
87+
const agentId = req.body.agentId;
88+
const collectionName = req.body.collectionName;
89+
const collectionAddress = req.body.collectionAddress;
90+
const collectionAdminPublicKey = req.body.collectionAdminPublicKey;
91+
const collectionFee = req.body.collectionFee;
92+
const tokenId = req.body.tokenId;
93+
const runtime = agents.get(agentId);
94+
if (!runtime) {
95+
res.status(404).send("Agent not found");
96+
return;
97+
}
98+
99+
try {
100+
const nftRes = await createNFT({
101+
runtime,
102+
collectionName,
103+
collectionAddress,
104+
collectionAdminPublicKey,
105+
collectionFee,
106+
tokenId,
107+
});
108+
109+
res.json({
110+
success: true,
111+
data: nftRes,
112+
});
113+
} catch (e: any) {
114+
console.log(e);
115+
res.json({
116+
success: false,
117+
data: JSON.stringify(e),
118+
});
119+
}
120+
}
121+
);
122+
123+
124+
router.post(
125+
"/api/nft-generation/verify-nft",
126+
async (req: express.Request, res: express.Response) => {
127+
const agentId = req.body.agentId;
128+
const collectionAddress = req.body.collectionAddress;
129+
const NFTAddress = req.body.nftAddress;
130+
const token = req.body.token;
131+
132+
const runtime = agents.get(agentId);
133+
if (!runtime) {
134+
res.status(404).send("Agent not found");
135+
return;
136+
}
137+
const verifyToken = runtime.getSetting('SOLANA_VERIFY_TOKEN')
138+
if (token !== verifyToken) {
139+
res.status(401).send(" Access denied for translation");
140+
return;
141+
}
142+
try {
143+
const {success} = await verifyNFT({
144+
runtime,
145+
collectionAddress,
146+
NFTAddress,
147+
});
148+
149+
res.json({
150+
success: true,
151+
data: success ? 'verified' : 'unverified',
152+
});
153+
} catch (e: any) {
154+
console.log(e);
155+
res.json({
156+
success: false,
157+
data: JSON.stringify(e),
158+
});
159+
}
160+
}
161+
);
162+
163+
164+
return router;
165+
}

0 commit comments

Comments
 (0)