Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: follow-up improvements for ICP token creation (PR #357) #757

Merged
merged 3 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@ai16z/plugin-solana": "workspace:*",
"@ai16z/plugin-0g": "workspace:*",
"@ai16z/plugin-starknet": "workspace:*",
"@ai16z/plugin-icp": "workspace:*",
"@ai16z/plugin-tee": "workspace:*",
"@ai16z/plugin-coinbase": "workspace:*",
"readline": "1.3.0",
Expand Down
205 changes: 135 additions & 70 deletions packages/plugin-icp/src/actions/createToken.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { composeContext } from "@ai16z/eliza/src/context";
import { generateObject } from "@ai16z/eliza/src/generation";
import {
composeContext,
generateImage,
generateText,
generateObject,
} from "@ai16z/eliza";
import {
ActionExample,
HandlerCallback,
Expand All @@ -8,56 +12,31 @@ import {
ModelClass,
State,
type Action,
} from "@ai16z/eliza/src/types";
} from "@ai16z/eliza";
import { idlFactory } from "../canisters/pick-pump/index.did";
import { _SERVICE } from "../canisters/pick-pump/index.did.d";
import { ActorCreator, CreateMemeTokenArg } from "../types";
import { unwrapOption, wrapOption } from "../utils/common/types/options";
import { unwrapRustResultMap } from "../utils/common/types/results";
import { icpWalletProvider } from "../providers/wallet";

const createTokenTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.

Based on the user's description, generate appropriate values for a new token:
- Create a suitable token name
- Generate a 3-4 letter symbol based on the name
- Write a clear description
- Use "https://icptoken.default.logo" as default logo URL
- Set other fields to null

Example response:
\`\`\`json
{
"name": "My ICP Token",
"symbol": "MIT",
"description": "A fun meme token on ICP",
"logo": "https://icptoken.default.logo",
"website": null,
"twitter": null,
"telegram": null
}
\`\`\`

{{recentMessages}}

Generate appropriate token information based on the user's description.
Respond with a JSON markdown block containing only the generated values.`;
import { uploadFileToWeb3Storage } from "../apis/uploadFile";
import { createTokenTemplate, logoPromptTemplate } from './prompts/token';
import { CANISTER_IDS } from '../constants/canisters';

async function createTokenTransaction(
creator: ActorCreator,
tokenInfo: CreateMemeTokenArg
): Promise<any> {
) {
const actor: _SERVICE = await creator(
idlFactory,
"bn4fo-iyaaa-aaaap-akp6a-cai"
CANISTER_IDS.PICK_PUMP
);

const result = await actor.create_token({
...tokenInfo,
name: tokenInfo.name ?? "My ICP Token",
symbol: tokenInfo.symbol ?? "MIT",
description: tokenInfo.description ?? "A fun meme token on ICP",
logo: "https://icptoken.default.logo",
name: tokenInfo.name,
symbol: tokenInfo.symbol,
description: tokenInfo.description,
logo: tokenInfo.logo,
twitter: wrapOption(tokenInfo.twitter),
website: wrapOption(tokenInfo.website),
telegram: wrapOption(tokenInfo.telegram),
Expand All @@ -67,6 +46,12 @@ async function createTokenTransaction(
result,
(ok) => ({
...ok,
id: ok.id.toString(),
created_at: ok.created_at.toString(),
available_token: ok.available_token.toString(),
volume_24h: ok.volume_24h.toString(),
last_tx_time: ok.last_tx_time.toString(),
market_cap_icp: ok.market_cap_icp.toString(),
twitter: unwrapOption(ok.twitter),
website: unwrapOption(ok.website),
telegram: unwrapOption(ok.telegram),
Expand All @@ -77,21 +62,82 @@ async function createTokenTransaction(
);
}

async function generateTokenLogo(
description: string,
runtime: IAgentRuntime
): Promise<string | null> {
const logoPrompt = `Create a fun and memorable logo for a cryptocurrency token with these characteristics: ${description}. The logo should be simple, iconic, and suitable for a meme token. Style: minimal, bold colors, crypto-themed.`;

const result = await generateImage(
{
prompt: logoPrompt,
width: 512,
height: 512,
count: 1,
},
runtime as any
);

if (result.success && result.data && result.data.length > 0) {
return result.data[0];
}

return null;
}

export const executeCreateToken: Action = {
name: "CREATE_TOKEN",
similes: ["CREATE_COIN", "MINT_TOKEN", "DEPLOY_TOKEN", "CREATE_ICP_TOKEN"],
similes: [
"CREATE_PICKPUMP_TOKEN",
"MINT_PICKPUMP",
"PICKPUMP_TOKEN",
"PP_TOKEN",
"PICKPUMP发币",
"PP发币",
"在PICKPUMP上发币",
"PICKPUMP代币",
],
description:
"Create a new meme token on PickPump platform (Internet Computer). This action helps users create and launch tokens specifically on the PickPump platform.",
validate: async (runtime: IAgentRuntime, message: Memory) => {
console.log("Message:", message);
return true;
const keywords = [
"pickpump",
"pp",
"皮克帮",
"token",
"coin",
"代币",
"币",
"create",
"mint",
"launch",
"deploy",
"创建",
"发行",
"铸造",
];

const messageText = (
typeof message.content === "string"
? message.content
: message.content.text || ""
).toLowerCase();

return keywords.some((keyword) => messageText.includes(keyword.toLowerCase()));
},
description: "Create a new token on Internet Computer.",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
state: State | undefined,
_options: { [key: string]: unknown } | undefined,
callback?: HandlerCallback
): Promise<boolean> => {
): Promise<void> => {
callback?.({
text: "🔄 Creating meme token...",
action: "CREATE_TOKEN",
type: "processing",
});

if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
Expand All @@ -109,20 +155,28 @@ export const executeCreateToken: Action = {
modelClass: ModelClass.LARGE,
});

console.log("Response:", response);
const logoPromptContext = composeContext({
state,
template: logoPromptTemplate.replace(
"{{description}}",
response.description
),
});

// Validate required fields
if (
!response.name ||
!response.symbol ||
!response.description ||
!response.logo
) {
const responseMsg = {
text: "I need the token name, symbol, description, and logo URL to create a token",
};
callback?.(responseMsg);
return true;
const logoPrompt = await generateText({
runtime,
context: logoPromptContext,
modelClass: ModelClass.SMALL,
});

const logo = await generateTokenLogo(logoPrompt, runtime);
if (!logo) {
throw new Error("Failed to generate token logo");
}

const logoUploadResult = await uploadFileToWeb3Storage(logo);
if (!logoUploadResult.urls?.gateway) {
throw new Error("Failed to upload logo to Web3Storage");
}

try {
Expand All @@ -131,50 +185,61 @@ export const executeCreateToken: Action = {
message,
state
);

const creator = wallet.createActor;
const createTokenResult = await createTokenTransaction(creator, {
name: response.name,
symbol: response.symbol,
description: response.description,
website: response.website,
twitter: response.twitter,
telegram: response.telegram,
logo: logoUploadResult.urls.gateway,
});

console.log("Token created successfully:", createTokenResult);
const responseMsg = {
text: `Token ${response.name} (${response.symbol}) created successfully on ICP!`,
text: `✨ Created new meme token:\n🪙 ${response.name} (${response.symbol})\n📝 ${response.description}`,
data: createTokenResult,
action: "CREATE_TOKEN",
type: "success",
};

callback?.(responseMsg);
return true;
} catch (error) {
console.error("Error creating token:", error);
} catch (error: any) {
const responseMsg = {
text: `Failed to create token: ${error.message}`,
action: "CREATE_TOKEN",
type: "error",
};
callback?.(responseMsg);
return false;
}
},
examples: [
[
{
user: "{{user1}}",
content: "I want to create a token for dog lovers",
content: "I want to create a space cat token on PickPump",
},
{
user: "{{user2}}",
content: {
text: "Creating new ICP token WOOF...",
text: "Creating space cat token on PickPump...",
action: "CREATE_TOKEN",
},
},
{
user: "{{user2}}",
content: {
text: "Token created successfully on Internet Computer!",
text: "✨ Token created successfully!",
},
},
],
[
{
user: "{{user1}}",
content: "Help me create a pizza-themed funny token on PP",
},
{
user: "{{user2}}",
content: {
text: "Creating pizza token on PickPump...",
action: "CREATE_TOKEN",
},
},
],
Expand Down
34 changes: 34 additions & 0 deletions packages/plugin-icp/src/actions/prompts/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const createTokenTemplate = `Based on the user's description, generate creative and memorable values for a new meme token on PickPump:

User's idea: "{{recentMessages}}"

Please generate:
1. A catchy and fun token name that reflects the theme
2. A 3-4 letter symbol based on the name (all caps)
3. An engaging and humorous description (include emojis)
4. Set other fields to null

Example response:
\`\`\`json
{
"name": "CatLaser",
"symbol": "PAWS",
"description": "The first meme token powered by feline laser-chasing energy! Watch your investment zoom around like a red dot! 😺🔴✨",
"logo": null,
"website": null,
"twitter": null,
"telegram": null
}
\`\`\`

Generate appropriate meme token information based on the user's description.
Respond with a JSON markdown block containing only the generated values.`;

export const logoPromptTemplate = `Based on this token idea: "{{description}}", create a detailed prompt for generating a logo image.
The prompt should describe visual elements, style, and mood for the logo.
Focus on making it memorable and suitable for a cryptocurrency token.
Keep the response short and specific.
Respond with only the prompt text, no additional formatting.

Example for a dog-themed token:
"A playful cartoon dog face with a cryptocurrency symbol on its collar, using vibrant colors and bold outlines, crypto-themed minimal style"`;
Loading
Loading