From 12fd58de607c68bebf1721f08c7adbb21f9352e2 Mon Sep 17 00:00:00 2001 From: asDNSK <1789588812@qq.com> Date: Sun, 1 Dec 2024 22:47:09 +0800 Subject: [PATCH 1/2] feat(icp): implement PickPump token creation with AI-powered logo generation - Add ICP plugin with token creation functionality - Integrate AI image generation for automatic logo creation - Add Web3 storage for logo hosting - Improve wallet provider with better identity management - Add multilingual support for Chinese users - Update build scripts and dependencies BREAKING CHANGE: New ICP plugin requires additional environment variables for wallet setup --- packages/agent/package.json | 1 + .../plugin-icp/src/actions/createToken.ts | 216 +++++++++++++----- packages/plugin-icp/src/index.ts | 2 +- packages/plugin-icp/src/providers/wallet.ts | 53 ++--- packages/plugin-icp/src/types.ts | 5 +- packages/plugin-icp/tsconfig.json | 15 ++ packages/plugin-icp/tsup.config.ts | 13 ++ scripts/build.sh | 1 + 8 files changed, 215 insertions(+), 91 deletions(-) create mode 100644 packages/plugin-icp/tsup.config.ts diff --git a/packages/agent/package.json b/packages/agent/package.json index 0d56585109..53dee408f1 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -24,6 +24,7 @@ "@ai16z/plugin-image-generation": "workspace:*", "@ai16z/plugin-node": "workspace:*", "@ai16z/plugin-solana": "workspace:*", + "@ai16z/plugin-icp": "workspace:*", "readline": "^1.3.0", "ws": "^8.18.0", "yargs": "17.7.2" diff --git a/packages/plugin-icp/src/actions/createToken.ts b/packages/plugin-icp/src/actions/createToken.ts index 5a583a5071..f4c82d8390 100644 --- a/packages/plugin-icp/src/actions/createToken.ts +++ b/packages/plugin-icp/src/actions/createToken.ts @@ -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, @@ -8,56 +12,65 @@ 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"; +import {} from "@ai16z/eliza"; +import { uploadFileToWeb3Storage } from "../utils/uploadFile"; + +const createTokenTemplate = `Based on the user's description, generate creative and memorable values for a new meme token on PickPump: -const createTokenTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. +User's idea: "{{recentMessages}}" -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 +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": "My ICP Token", - "symbol": "MIT", - "description": "A fun meme token on ICP", - "logo": "https://icptoken.default.logo", + "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 } \`\`\` -{{recentMessages}} - -Generate appropriate token information based on the user's description. +Generate appropriate meme token information based on the user's description. Respond with a JSON markdown block containing only the generated values.`; +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"`; + async function createTokenTransaction( creator: ActorCreator, tokenInfo: CreateMemeTokenArg -): Promise { +) { const actor: _SERVICE = await creator( idlFactory, - "bn4fo-iyaaa-aaaap-akp6a-cai" + "tl65e-yyaaa-aaaah-aq2pa-cai" ); - 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), @@ -67,6 +80,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), @@ -77,21 +96,82 @@ async function createTokenTransaction( ); } +async function generateTokenLogo( + description: string, + runtime: IAgentRuntime +): Promise { + 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 => { + ): Promise => { + callback?.({ + text: "🔄 Creating meme token...", + action: "CREATE_TOKEN", + type: "processing", + }); + if (!state) { state = (await runtime.composeState(message)) as State; } else { @@ -109,20 +189,29 @@ 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) { + console.error("❌ Logo generation failed"); + 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 { @@ -131,50 +220,63 @@ 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) { + console.error("❌ Error creating token:", error); + console.error("Error stack:", error.stack); 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: "我想在 PickPump 上发行一个关于太空猫的代币", }, { user: "{{user2}}", content: { - text: "Creating new ICP token WOOF...", + text: "正在 PickPump 上创建太空猫代币...", action: "CREATE_TOKEN", }, }, { user: "{{user2}}", content: { - text: "Token created successfully on Internet Computer!", + text: "✨ 代币创建成功!", + }, + }, + ], + [ + { + user: "{{user1}}", + content: "帮我在 PP 上创建一个披萨主题的搞笑代币", + }, + { + user: "{{user2}}", + content: { + text: "正在 PickPump 上创建披萨代币...", + action: "CREATE_TOKEN", }, }, ], diff --git a/packages/plugin-icp/src/index.ts b/packages/plugin-icp/src/index.ts index f54d66875c..1466de272a 100644 --- a/packages/plugin-icp/src/index.ts +++ b/packages/plugin-icp/src/index.ts @@ -1,4 +1,4 @@ -import { Plugin } from "@ai16z/eliza/src/types"; +import { Plugin } from "@ai16z/eliza"; import { icpWalletProvider } from "./providers/wallet"; import { executeCreateToken } from "./actions/createToken"; diff --git a/packages/plugin-icp/src/providers/wallet.ts b/packages/plugin-icp/src/providers/wallet.ts index ac41dc7f8f..2fba52fdf6 100644 --- a/packages/plugin-icp/src/providers/wallet.ts +++ b/packages/plugin-icp/src/providers/wallet.ts @@ -3,70 +3,62 @@ import { Actor, ActorSubclass, HttpAgent } from "@dfinity/agent"; import { Ed25519KeyIdentity } from "@dfinity/identity"; import { IDL } from "@dfinity/candid"; import { Principal } from "@dfinity/principal"; -import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza/src/types"; +import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza"; export class WalletProvider { private privateKey: string; private identity: Ed25519KeyIdentity; - private agent: HttpAgent; private host: string; constructor(privateKey: string, host: string = "https://ic0.app") { this.privateKey = privateKey; this.host = host; this.identity = this.createIdentity(); - this.agent = this.createAgent(); } - private createIdentity(): Ed25519KeyIdentity { + private createIdentity = (): Ed25519KeyIdentity => { if (!this.privateKey) { throw new Error("Private key is required"); } - try { - return Ed25519KeyIdentity.fromSecretKey( - Buffer.from(this.privateKey, "hex") - ); + const rawKey = Buffer.from(this.privateKey, "base64"); + const privateKeyBytes = rawKey.subarray(16, 48); + return Ed25519KeyIdentity.fromSecretKey(privateKeyBytes); } catch (error) { console.error("Error creating identity:", error); throw new Error("Failed to create ICP identity"); } - } + }; - private createAgent(): HttpAgent { - return HttpAgent.createSync({ + public createAgent = async (): Promise => { + return HttpAgent.create({ identity: this.identity, host: this.host, }); - } + }; - getIdentity(): Ed25519KeyIdentity { + public getIdentity = (): Ed25519KeyIdentity => { return this.identity; - } + }; - getAgent(): HttpAgent { - return this.agent; - } - - getPrincipal(): Principal { + public getPrincipal = (): Principal => { return this.identity.getPrincipal(); - } + }; - // Create an Actor with identity - async createActor( + public createActor = async ( idlFactory: IDL.InterfaceFactory, canisterId: string, fetchRootKey = false - ): Promise> { + ): Promise> => { + const agent = await this.createAgent(); if (fetchRootKey) { - await this.agent.fetchRootKey(); + await agent.fetchRootKey(); } - return Actor.createActor(idlFactory, { - agent: this.agent, + agent, canisterId, }); - } + }; } // Add the new provider instance @@ -74,7 +66,7 @@ export const icpWalletProvider: Provider = { async get( runtime: IAgentRuntime, message: Memory, - state: State + state?: State ): Promise { try { const privateKey = runtime.getSetting( @@ -89,16 +81,15 @@ export const icpWalletProvider: Provider = { return { wallet, identity: wallet.getIdentity(), - agent: wallet.getAgent(), principal: wallet.getPrincipal().toString(), isAuthenticated: true, + createActor: wallet.createActor, }; - } catch (error) { + } catch (error: any) { console.error("Error in wallet provider:", error); return { wallet: null, identity: null, - agent: null, principal: null, isAuthenticated: false, error: error.message, diff --git a/packages/plugin-icp/src/types.ts b/packages/plugin-icp/src/types.ts index aa57ba3479..1cd4b718d8 100644 --- a/packages/plugin-icp/src/types.ts +++ b/packages/plugin-icp/src/types.ts @@ -33,9 +33,10 @@ export type ActorCreator = ( ) => Promise>; export type CreateMemeTokenArg = { - name?: string; - symbol?: string; + name: string; + symbol: string; description: string; + logo: string; twitter?: string; website?: string; telegram?: string; diff --git a/packages/plugin-icp/tsconfig.json b/packages/plugin-icp/tsconfig.json index e69de29bb2..0499152466 100644 --- a/packages/plugin-icp/tsconfig.json +++ b/packages/plugin-icp/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/plugin-icp/tsup.config.ts b/packages/plugin-icp/tsup.config.ts new file mode 100644 index 0000000000..3ea8bd252a --- /dev/null +++ b/packages/plugin-icp/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + outDir: "dist", + sourcemap: true, + clean: true, + format: ["esm"], + dts: true, + external: [ + // Add other modules you want to externalize + ], +}); diff --git a/scripts/build.sh b/scripts/build.sh index 1a93101dcc..5894b1b2ac 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -23,6 +23,7 @@ PACKAGES=( "core" "plugin-trustdb" "plugin-solana" + "plugin-icp" "plugin-starknet" "adapter-postgres" "adapter-sqlite" From 2d1ad6f0429e3537da37862b1d89468aef9d9bd4 Mon Sep 17 00:00:00 2001 From: asDNSK <1789588812@qq.com> Date: Sun, 1 Dec 2024 23:20:01 +0800 Subject: [PATCH 2/2] refactor: consolidate utility functions and improve error handling - Move token templates to separate prompts file - Consolidate hex/array conversion utilities into arrays.ts - Remove redundant utility files (hex.ts, shrink.ts, etc.) - Improve private key validation in wallet provider - Replace console.error with proper error throwing - Update i18n strings to English - Add CANISTER_IDS constant for better maintainability --- .../plugin-icp/src/actions/createToken.ts | 55 +++-------------- .../plugin-icp/src/actions/prompts/token.ts | 34 +++++++++++ packages/plugin-icp/src/apis/uploadFile.ts | 59 +++++++++++++++++++ packages/plugin-icp/src/constants/apis.ts | 2 + .../plugin-icp/src/constants/canisters.ts | 3 + packages/plugin-icp/src/providers/wallet.ts | 8 +-- packages/plugin-icp/src/utils/arrays.ts | 22 +++++++ .../plugin-icp/src/utils/common/data/json.ts | 10 ++-- .../plugin-icp/src/utils/common/data/price.ts | 7 --- .../plugin-icp/src/utils/common/data/text.ts | 21 ------- .../src/utils/common/types/principal.ts | 7 --- packages/plugin-icp/src/utils/hex.ts | 21 ------- packages/plugin-icp/src/utils/ic/index.ts | 3 +- 13 files changed, 139 insertions(+), 113 deletions(-) create mode 100644 packages/plugin-icp/src/actions/prompts/token.ts create mode 100644 packages/plugin-icp/src/apis/uploadFile.ts create mode 100644 packages/plugin-icp/src/constants/apis.ts create mode 100644 packages/plugin-icp/src/constants/canisters.ts delete mode 100644 packages/plugin-icp/src/utils/common/data/price.ts delete mode 100644 packages/plugin-icp/src/utils/common/data/text.ts delete mode 100644 packages/plugin-icp/src/utils/common/types/principal.ts delete mode 100644 packages/plugin-icp/src/utils/hex.ts diff --git a/packages/plugin-icp/src/actions/createToken.ts b/packages/plugin-icp/src/actions/createToken.ts index f4c82d8390..b7d523ef7d 100644 --- a/packages/plugin-icp/src/actions/createToken.ts +++ b/packages/plugin-icp/src/actions/createToken.ts @@ -19,43 +19,9 @@ 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"; -import {} from "@ai16z/eliza"; -import { uploadFileToWeb3Storage } from "../utils/uploadFile"; - -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.`; - -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"`; +import { uploadFileToWeb3Storage } from "../apis/uploadFile"; +import { createTokenTemplate, logoPromptTemplate } from './prompts/token'; +import { CANISTER_IDS } from '../constants/canisters'; async function createTokenTransaction( creator: ActorCreator, @@ -63,7 +29,7 @@ async function createTokenTransaction( ) { const actor: _SERVICE = await creator( idlFactory, - "tl65e-yyaaa-aaaah-aq2pa-cai" + CANISTER_IDS.PICK_PUMP ); const result = await actor.create_token({ ...tokenInfo, @@ -205,7 +171,6 @@ export const executeCreateToken: Action = { const logo = await generateTokenLogo(logoPrompt, runtime); if (!logo) { - console.error("❌ Logo generation failed"); throw new Error("Failed to generate token logo"); } @@ -237,8 +202,6 @@ export const executeCreateToken: Action = { }; callback?.(responseMsg); } catch (error: any) { - console.error("❌ Error creating token:", error); - console.error("Error stack:", error.stack); const responseMsg = { text: `Failed to create token: ${error.message}`, action: "CREATE_TOKEN", @@ -251,31 +214,31 @@ export const executeCreateToken: Action = { [ { user: "{{user1}}", - content: "我想在 PickPump 上发行一个关于太空猫的代币", + content: "I want to create a space cat token on PickPump", }, { user: "{{user2}}", content: { - text: "正在 PickPump 上创建太空猫代币...", + text: "Creating space cat token on PickPump...", action: "CREATE_TOKEN", }, }, { user: "{{user2}}", content: { - text: "✨ 代币创建成功!", + text: "✨ Token created successfully!", }, }, ], [ { user: "{{user1}}", - content: "帮我在 PP 上创建一个披萨主题的搞笑代币", + content: "Help me create a pizza-themed funny token on PP", }, { user: "{{user2}}", content: { - text: "正在 PickPump 上创建披萨代币...", + text: "Creating pizza token on PickPump...", action: "CREATE_TOKEN", }, }, diff --git a/packages/plugin-icp/src/actions/prompts/token.ts b/packages/plugin-icp/src/actions/prompts/token.ts new file mode 100644 index 0000000000..8e183eb0b2 --- /dev/null +++ b/packages/plugin-icp/src/actions/prompts/token.ts @@ -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"`; diff --git a/packages/plugin-icp/src/apis/uploadFile.ts b/packages/plugin-icp/src/apis/uploadFile.ts new file mode 100644 index 0000000000..9ae17c8b90 --- /dev/null +++ b/packages/plugin-icp/src/apis/uploadFile.ts @@ -0,0 +1,59 @@ +import { WEB3_STORAGE_API_HOST } from '../constants/apis'; + +interface UploadResponse { + success: boolean; + cid?: string; + urls?: { + direct: string; + raw: string; + gateway: string; + }; + type?: string; + name?: string; + size?: number; + error?: string; +} + +export async function uploadFileToWeb3Storage( + base64Data: string, + fileName: string = "image.png" +): Promise { + try { + // Remove base64 URL prefix (if exists) + const cleanBase64 = base64Data.replace(/^data:image\/\w+;base64,/, ""); + + // Convert base64 to Blob + const byteCharacters = atob(cleanBase64); + const byteNumbers = new Array(byteCharacters.length); + + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + + const byteArray = new Uint8Array(byteNumbers); + const blob = new Blob([byteArray], { type: "image/png" }); + + // Create file object + const file = new File([blob], fileName, { type: "image/png" }); + + const formData = new FormData(); + formData.append("file", file); + + const response = await fetch(WEB3_STORAGE_API_HOST, { + method: "POST", + body: formData, + }); + + if (!response.ok) { + throw new Error(`Upload failed with status: ${response.status}`); + } + + const result: UploadResponse = await response.json(); + return result; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : "upload failed", + }; + } +} diff --git a/packages/plugin-icp/src/constants/apis.ts b/packages/plugin-icp/src/constants/apis.ts new file mode 100644 index 0000000000..dac935f745 --- /dev/null +++ b/packages/plugin-icp/src/constants/apis.ts @@ -0,0 +1,2 @@ +// use your own web3 storage api host +export const WEB3_STORAGE_API_HOST = ""; \ No newline at end of file diff --git a/packages/plugin-icp/src/constants/canisters.ts b/packages/plugin-icp/src/constants/canisters.ts new file mode 100644 index 0000000000..8718f33aff --- /dev/null +++ b/packages/plugin-icp/src/constants/canisters.ts @@ -0,0 +1,3 @@ +export const CANISTER_IDS = { + PICK_PUMP: "tl65e-yyaaa-aaaah-aq2pa-cai" +} as const; \ No newline at end of file diff --git a/packages/plugin-icp/src/providers/wallet.ts b/packages/plugin-icp/src/providers/wallet.ts index 2fba52fdf6..0aca8e41dd 100644 --- a/packages/plugin-icp/src/providers/wallet.ts +++ b/packages/plugin-icp/src/providers/wallet.ts @@ -21,11 +21,12 @@ export class WalletProvider { throw new Error("Private key is required"); } try { - const rawKey = Buffer.from(this.privateKey, "base64"); - const privateKeyBytes = rawKey.subarray(16, 48); + const privateKeyBytes = Buffer.from(this.privateKey, "hex"); + if (privateKeyBytes.length !== 32) { + throw new Error("Invalid private key length"); + } return Ed25519KeyIdentity.fromSecretKey(privateKeyBytes); } catch (error) { - console.error("Error creating identity:", error); throw new Error("Failed to create ICP identity"); } }; @@ -86,7 +87,6 @@ export const icpWalletProvider: Provider = { createActor: wallet.createActor, }; } catch (error: any) { - console.error("Error in wallet provider:", error); return { wallet: null, identity: null, diff --git a/packages/plugin-icp/src/utils/arrays.ts b/packages/plugin-icp/src/utils/arrays.ts index ff9a568aa3..ebf605c791 100644 --- a/packages/plugin-icp/src/utils/arrays.ts +++ b/packages/plugin-icp/src/utils/arrays.ts @@ -13,3 +13,25 @@ export const string2array = (text: string): number[] => { const encoder = new TextEncoder(); return Array.from(encoder.encode(text)); }; + +// hex text -> number array +export const hex2array = (hex: string): number[] => { + const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex; + if (cleanHex.length === 0) return []; + if (cleanHex.length % 2 !== 0) throw new Error("Invalid hex text"); + const value: number[] = []; + for (let i = 0; i < cleanHex.length; i += 2) { + value.push(Number.parseInt(cleanHex.slice(i, i + 2), 16)); + } + return value; +}; + +// number array -> hex text +export const array2hex = (value: number[]): string => { + return value + .map((v) => { + if (v < 0 || 255 < v) throw new Error("number must between 0~255"); + return v.toString(16).padStart(2, "0"); + }) + .join(""); +}; diff --git a/packages/plugin-icp/src/utils/common/data/json.ts b/packages/plugin-icp/src/utils/common/data/json.ts index 4900028e33..d95dfdefdc 100644 --- a/packages/plugin-icp/src/utils/common/data/json.ts +++ b/packages/plugin-icp/src/utils/common/data/json.ts @@ -4,11 +4,11 @@ import { isPrincipalText } from "../../ic/principals"; export const customStringify = (v: any): string => JSON.stringify(v, (_key, value) => { - if (typeof value === "bigint") return `${value}`; - if (value && typeof value === "object" && value._isPrincipal === true) { + if (typeof value === "bigint") { + return `${value}`; + } else if (value && typeof value === "object" && value._isPrincipal === true) { return value.toText(); - } - if ( + } else if ( value && typeof value === "object" && value.__principal__ && @@ -17,4 +17,4 @@ export const customStringify = (v: any): string => return value.__principal__; } return value; - }); + }); \ No newline at end of file diff --git a/packages/plugin-icp/src/utils/common/data/price.ts b/packages/plugin-icp/src/utils/common/data/price.ts deleted file mode 100644 index aeb4ba3546..0000000000 --- a/packages/plugin-icp/src/utils/common/data/price.ts +++ /dev/null @@ -1,7 +0,0 @@ -import _ from "lodash"; - -export const handleShowPrice = (value: number | string, fixed = 8) => { - if (!value || value === "0") return 0; - return _.round(Number(value), fixed); - // return value / 1e18; -}; diff --git a/packages/plugin-icp/src/utils/common/data/text.ts b/packages/plugin-icp/src/utils/common/data/text.ts deleted file mode 100644 index 2b7fe08640..0000000000 --- a/packages/plugin-icp/src/utils/common/data/text.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Display shortened text -export const shrinkText = ( - text: string | undefined, - prefix = 5, - suffix = 5 -): string | undefined => { - if (!text) return text; - const max_length = prefix + 3 + suffix; // Length of text to keep - if (text.length <= max_length) return text; - const prefix_text = prefix === 0 ? "" : text.slice(0, prefix); // Get prefix - - const suffix_text = suffix === 0 ? "" : text.slice(-suffix); // Get suffix - return `${prefix_text}...${suffix_text}`; // Add ellipsis in the middle -}; - -// Display shortened text for principal IDs -export const shrinkPrincipal = (text: string | undefined): string | undefined => - shrinkText(text, 5, 3); -// Display shortened text for account IDs -export const shrinkAccount = (text: string | undefined): string | undefined => - shrinkText(text, 4, 4); diff --git a/packages/plugin-icp/src/utils/common/types/principal.ts b/packages/plugin-icp/src/utils/common/types/principal.ts deleted file mode 100644 index 49e30daab3..0000000000 --- a/packages/plugin-icp/src/utils/common/types/principal.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Principal } from "@dfinity/principal"; - -// Principal -> string -export const principal2string = (p: Principal): string => p.toText(); - -// string -> Principal -export const string2principal = (p: string): Principal => Principal.fromText(p); diff --git a/packages/plugin-icp/src/utils/hex.ts b/packages/plugin-icp/src/utils/hex.ts deleted file mode 100644 index 0874e9b64c..0000000000 --- a/packages/plugin-icp/src/utils/hex.ts +++ /dev/null @@ -1,21 +0,0 @@ -// hex text -> number array -export const hex2array = (hex: string): number[] => { - const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex; - if (cleanHex.length === 0) return []; - if (cleanHex.length % 2 !== 0) throw new Error("Invalid hex text"); - const value: number[] = []; - for (let i = 0; i < cleanHex.length; i += 2) { - value.push(Number.parseInt(cleanHex.slice(i, i + 2), 16)); - } - return value; -}; - -// number array -> hex text -export const array2hex = (value: number[]): string => { - return value - .map((v) => { - if (v < 0 || 255 < v) throw new Error("number must between 0~255"); - return v.toString(16).padStart(2, "0"); - }) - .join(""); -}; diff --git a/packages/plugin-icp/src/utils/ic/index.ts b/packages/plugin-icp/src/utils/ic/index.ts index 2b5fdbd6d3..eca7916b4e 100644 --- a/packages/plugin-icp/src/utils/ic/index.ts +++ b/packages/plugin-icp/src/utils/ic/index.ts @@ -2,8 +2,7 @@ import { getCrc32 } from "@dfinity/principal/lib/esm/utils/getCrc"; import { sha224 } from "@dfinity/principal/lib/esm/utils/sha224"; import { Principal } from "@dfinity/principal"; -import { array2hex, hex2array } from "../hex"; -import { string2array } from "../arrays"; +import { array2hex, hex2array, string2array } from "../arrays"; // Principal -> string export const principal2string = (p: Principal): string => p.toText();