Skip to content

Instantly share code, notes, and snippets.

@lite
Forked from notwedtm/common.ts
Created January 8, 2025 08:50
Show Gist options
  • Save lite/2880daa5a79f3890270c69f89d20ae14 to your computer and use it in GitHub Desktop.
Save lite/2880daa5a79f3890270c69f89d20ae14 to your computer and use it in GitHub Desktop.
Solana Agent Kit + ElizaOS = 😍
import { IAgentRuntime, Memory } from "@ai16z/eliza";
import { SolanaAgentKit } from "solana-agent-kit";
export function getSakAgent(runtime: IAgentRuntime) {
return new SolanaAgentKit(
runtime.getSetting("SOLANA_PRIVATE_KEY"),
runtime.getSetting("SOLANA_RPC_URL"),
runtime.getSetting("OPENAI_API_KEY")
);
}
export async function getOrCreateGoal(runtime: IAgentRuntime, message: Memory, goalId: string) {
const goal = await runtime.databaseAdapter.getGoals({
agentId: runtime.agentId,
roomId: message.roomId,
userId: message.userId,
onlyInProgress: true,
count: 1,
});
if (goal.length > 0) {
return goal[0];
}
}
import { Action, composeContext, Content, elizaLogger, generateObject, GoalStatus, IAgentRuntime, Memory, ModelClass, State } from "@ai16z/eliza";
import { z } from "zod";
import { getSakAgent } from "../common";
import * as templates from "../templates/deployCollection";
export const deployCollectionSchema = z.object({
name: z.string().nullable(),
uri: z.string().nullable(),
royaltyBasisPoints: z.number().nullable(),
creators: z.array(z.object({
address: z.string().default(process.env.SOLANA_PUBLIC_KEY),
percentage: z.number().default(100),
})).nullable(),
})
export type DeployCollectionParams = z.infer<typeof deployCollectionSchema>;
export function isDeployCollectionParamsValid(params: any): params is DeployCollectionParams {
return deployCollectionSchema.safeParse(params).success;
}
export const deployCollectionAction: Action = {
name: 'DEPLOY_COLLECTION',
similes: ["DEPLOY_COLLECTION", "NFT_COLLECTION", "CREATE_NFT_COLLECTION", "CREATE_COLLECTION"],
description: 'Deploy a new collection on the Solana blockchain',
examples: templates.examples,
validate: async (runtime: IAgentRuntime, message: Memory, state?: State) => {
elizaLogger.info('DeployCollectionAction.validate');
if (await hasExistingGoal(runtime, message, 'Deploy NFT Collection')) {
elizaLogger.info('DeployCollectionAction.validate.hasExistingGoal');
return true;
}
elizaLogger.info('DeployCollectionAction.validate.noExistingGoal');
return true;
},
handler: async (runtime, message, state, options?, callback?) => {
elizaLogger.info('DeployCollectionAction.handler', message);
const goal = await getOrCreateGoal(runtime, message, 'Deploy NFT Collection');
const extractedParams = await extractParameters(runtime, message, state, callback);
if (!extractedParams) {
elizaLogger.info('DeployCollectionAction.handler.missingParams');
return true;
}
if (!isDeployCollectionParamsValid(extractedParams)) {
elizaLogger.info('DeployCollectionAction.handler.invalidParams');
callback({
text: 'Some parameters are invalid. Please check and try again.',
});
return true;
}
const objective = goal.objectives.find(o => o.id === 'gather_parameters');
if (objective && !objective.completed) {
elizaLogger.info('Updating goal objective status');
await runtime.databaseAdapter.updateGoal({
...goal,
objectives: goal.objectives.map(o =>
o.id === 'gather_parameters' ? { ...o, completed: true } : o
)
});
}
elizaLogger.info('Deploying collection...', {
params: extractedParams,
});
try {
const sakAgent = getSakAgent(runtime);
const collection = await sakAgent.deployCollection({
name: extractedParams.name,
uri: extractedParams.uri,
royaltyBasisPoints: extractedParams.royaltyBasisPoints,
creators: extractedParams.creators.map(c => ({
address: c.address,
percentage: c.percentage,
})),
});
elizaLogger.info('DeployCollectionAction.handler.success', { collection });
await runtime.databaseAdapter.updateGoal({
...goal,
status: GoalStatus.DONE,
objectives: goal.objectives.map(o =>
o.id === 'deploy_collection' ? { ...o, completed: true } : o
)
});
callback({
text: `Collection deployed successfully: ${collection.collectionAddress} via https://solana.fm/tx/${collection.signature}!`,
});
return true;
} catch (error) {
elizaLogger.error('DeployCollectionAction.handler.error', { error });
callback({
text: 'There was an error deploying the collection. Please try again.',
});
return true;
}
},
}
async function hasExistingGoal(runtime: IAgentRuntime, message: Memory, goalName: string) {
const existingGoals = await runtime.databaseAdapter.getGoals({
agentId: runtime.agentId,
roomId: message.roomId,
userId: message.userId,
onlyInProgress: true,
});
return existingGoals.some(g => g.name === goalName);
}
async function getOrCreateGoal(runtime: IAgentRuntime, message: Memory, goalName: string) {
if (await hasExistingGoal(runtime, message, goalName)) {
elizaLogger.info('DeployCollectionAction.getOrCreateGoal.hasExistingGoal');
return await runtime.databaseAdapter.getGoals({
agentId: runtime.agentId,
roomId: message.roomId,
userId: message.userId,
onlyInProgress: true,
}).then(goals => goals.find(g => g.name === goalName));
}
elizaLogger.info('DeployCollectionAction.getOrCreateGoal.noExistingGoal');
await runtime.databaseAdapter.createGoal({
roomId: message.roomId,
userId: message.userId,
name: goalName,
status: GoalStatus.IN_PROGRESS,
objectives: [
{
id: 'gather_parameters',
description: 'Gather the collection parameters',
completed: false,
},
{
id: 'deploy_collection',
description: 'Deploy the collection',
completed: false,
},
],
});
const goal = await runtime.databaseAdapter.getGoals({
agentId: runtime.agentId,
roomId: message.roomId,
userId: message.userId,
onlyInProgress: true,
});
return goal[0];
}
async function extractParameters(runtime: IAgentRuntime, message: Memory, state?: State, callback?: (content: Content) => void) {
let extractedParams: { object: DeployCollectionParams } | null = null;
const parameterExtractionContext = composeContext({
state,
template: templates.parameterExtractionTemplate
});
try {
extractedParams = await generateObject({
runtime,
context: parameterExtractionContext,
modelClass: ModelClass.SMALL,
schema: deployCollectionSchema as any,
}) as { object: DeployCollectionParams };
elizaLogger.info('Generated object from message', { params: extractedParams.object });
if (!isDeployCollectionParamsValid(extractedParams.object)) {
elizaLogger.error('Generated object is not DeployCollectionParams', { params: extractedParams.object });
return null;
}
} catch (error) {
elizaLogger.warn('Error generating object', { error });
callback({
text: 'Please provide the collection name, uri, royalty basis points, and creators.',
inReplyTo: message.id,
});
return null;
}
elizaLogger.info('Extracted token parameters', { params: extractedParams.object });
const missingParams = [];
if (!extractedParams.object.name) missingParams.push('name');
if (!extractedParams.object.uri) missingParams.push('uri');
if (extractedParams.object.royaltyBasisPoints === null) missingParams.push('royalty basis points');
if (!extractedParams.object.creators) missingParams.push('creators');
if (missingParams.length > 0) {
elizaLogger.info('Missing required parameters', { missingParams });
callback({
text: `Please provide the ${missingParams.length === 1 ? 'following parameter' : 'following parameters'}: ${missingParams.join(', ')}`,
inReplyTo: message.id,
});
return null;
}
return extractedParams.object;
}
import { ActionExample } from "@ai16z/eliza";
const isStartingGoalTemplate = `You must respond with ONLY the word "true" or "false".
Is this message about starting to deploy an NFT collection? "{{message.content.text}}"
Respond "true" if the message indicates:
- Deploying a new NFT collection
- Creating an NFT collection
- Setting up a collection
- Starting a new collection
Otherwise respond "false".`
const isContinuingGoalTemplate = `You must respond with ONLY the word "true" or "false".
Is this message continuing a conversation about deploying an NFT collection? "{{message.content.text}}"
Respond "true" if the message contains:
- Collection details (name, uri, royalties, creators)
- Answers about collection information
- Confirmations about collection details
Otherwise respond "false".`
const parameterExtractionTemplate = `
Extract the NFT collection details from this message and output them in JSON format.
Message: "{{message.content.text}}"
Rules:
- Name should be the full collection name (e.g., "Bored Ape Yacht Club")
- Symbol should be the collection symbol in uppercase (e.g., "BAYC")
- Royalty basis points should be a number between 0 and 10000 (100% = 10000)
- Creators should be an array of objects with address and percentage (percentages should sum to 100)
Example response:
\`\`\`json
{
"name": "Bored Ape Yacht Club",
"symbol": "BAYC",
"description": "A collection of 10,000 unique Bored Ape NFTs",
"imageDescription": "A cartoon ape with unique traits and accessories",
"uri": "https://agent.def345.prism.sh/api/collections/abc123-abc123-abc123-abc123/metadata.json",
"royaltyBasisPoints": 500,
"creators": [
{
"address": "ABC123...",
"percentage": 100
}
]
}
\`\`\`
{{recentMessages}}
Given the recent messages, extract the following information about the requested NFT collection creation. Do not make up any information, return null for any values that cannot be determined:
- Collection name
- Collection symbol
- Collection description
- Collection image description (Use the Collection description to generate if the image description is not provided)
- Collection uri
- Royalty basis points
- Creators array with addresses and percentages
Respond with a JSON markdown block containing only the extracted values.
`
const examples: ActionExample[][] = [
[
{
user: '{{user1}}',
content: {
text: 'Create a collection of 10,000 unique Bored Ape NFTs',
action: 'DEPLOY_COLLECTION',
params: {
name: 'Bored Ape Yacht Club',
symbol: 'BAYC',
description: 'A collection of 10,000 unique Bored Ape NFTs',
imageDescription: 'A cartoon ape with unique traits and accessories',
uri: 'https://agent.def345.prism.sh/api/collections/abc123-abc123-abc123-abc123/metadata.json',
royaltyBasisPoints: 500,
creators: [{ address: 'ABC123...', percentage: 100 }],
},
},
},
{
user: 'prism-agent',
content: {
text: "I've created that collection of 10,000 unique Bored Ape NFTs you asked for!",
},
},
],
];
export { examples, isContinuingGoalTemplate, isStartingGoalTemplate, parameterExtractionTemplate };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment