Solana Agent Building

Quickstart building your agent on Solana.

Prerequisite: Complete Agent Setup

Adding Solana tools to your agent

You can change your agent's plugin settings by altering the manifest returned by the GET /api/ai-plugin route. Here you can find a tools array in (manifest).'x-mb'.assistant.tools . Add the 'generate-sol-tx' primitive to the tools array like so:

{
  //...
  'x-mb': {
    'account-id': ACCOUNT_ID,
    assistant: {
      name: 'Solana Assistant',
      description:
        "An assistant that generates solana transactions.",
      instructions:
        "You create solana transactions by using the generate-sol-tx tool, which takes a fromPubKey, a toPubKey and an amountInSol and creates a simple SOL transfer.",
      tools: [
        // Add 'generate-sol-tx' primitive here:
        { type: 'generate-sol-tx' },
        //...
      ],
    },
  },
  //...
}

Using the generate-sol-tx tool

The generate-sol-tx tool takes either a base64 encoded transaction or, if you wish to generate a simple SOL transfer, it can also take a fromPubKey(string), a toPubKey(string) and an amountInSol(number) as parameters. To use the second option, simply instruct the agent to use the generate-sol-tx tool when given these three parameters (see instructions in example above).

If you want more flexibility in the types of possible transactions, the base64 encoded transaction is better suited for the job. You can add the transaction building logic to a route and then add the route to the manifest. Here's an example from our agent-boilerplate:

Add a new path for solana transactions.
paths:{
  '/api/tools/create-sol-transaction': {
    get: {
      operationId: 'solTransfer',
      summary: 'Construct a Native Asset Solana Transfer',
      description:
        'Constructs an unsigned Solana transfer of {amount} to {recipient} from {solAddress}',
      parameters: [
        {
          name: 'solAddress',
          in: 'query',
          required: true,
          schema: {
            type: 'string',
          },
          description: "The connected user's solAddress (base58 encoded)",
        },
        {
          name: 'recipient',
          in: 'query',
          required: true,
          schema: {
            type: 'string',
          },
          description:
            'The transfer recipient address (base58 encoded) or token symbol',
        },
        {
          name: 'amount',
          in: 'query',
          required: true,
          schema: {
            type: 'number',
          },
          description:
            'The amount of SOL to transfer in token units (not lamports)',
        },
      ],
      responses: {
        '200': {
          description: 'Serialized Solana Transaction Bytes',
          content: {
            'application/json': {
              schema: {
                type: 'object',
                properties: {
                  transactionBytes: {
                    type: 'string',
                    description:
                      'The unsigned base64 encoded solana transaction',
                  },
                },
                required: ['transactionBytes'],
              },
            },
          },
        },
      },
    },
  },
  //...
}
Create the route for that path with the transaction creation logic.
// in ./api/tools/create-sol-transaction/route.ts
import { Connection, SystemProgram, Transaction } from '@solana/web3.js';
import { type NextRequest, NextResponse } from 'next/server';
import { TransferQuerySchema } from './schema';

const SOLANA_RPC =
  process.env.SOLANA_RPC ?? 'http://api.mainnet-beta.solana.com/';

export async function GET(request: NextRequest): Promise<NextResponse> {
  try {
    const { searchParams } = new URL(request.url);

    const { success, data, error } = TransferQuerySchema.safeParse(
      Object.fromEntries(searchParams.entries()),
    );
    if (!success) {
      return NextResponse.json(
        { type: 'InvalidInput', ...error },
        { status: 400 },
      );
    }
    const { solAddress, recipient, amount } = data;

    const transferParams = {
      fromPubkey: solAddress,
      toPubkey: recipient,
      lamports: amount,
    };

    const ix = SystemProgram.transfer(transferParams);

    const tx = new Transaction().add(ix);
    tx.feePayer = solAddress;

    const connection = new Connection(SOLANA_RPC, 'confirmed');
    const { blockhash } = await connection.getLatestBlockhash();
    tx.recentBlockhash = blockhash;

    const transactionBytes = tx
      .serialize({ requireAllSignatures: false })
      .toString('base64');

    return NextResponse.json({ transactionBytes }, { status: 200 });
  } catch (error) {
    const message = `${error}`;
    console.error(message);
    return NextResponse.json({ error: message }, { status: 500 });
  }
}

Test and Deploy

Now you can run your agent locally with:

pnpm dev

This should open a chat UI where you can ask the agent to make a solana transaction, providing it with the necessary parameters. If the agent isn't calling your tool like expected or isn't picking up on the parameters correctly, try editing the agent's instructions. After, if you wish to have your agent go live, deploy it to your preferred cloud platform (e.g. vercel) and update your agent url in .env:

BITTE_AGENT_URL="https://my-solana-agent.vercel.app"

Alternatively you can add it directly in the manifest's servers list:

{
  openapi: '3.0.0',
  info: {
    title: 'Boilerplate',
    description: 'API for the boilerplate',
    version: '1.0.0',
  },
  servers: [
    {
      url: "https://my-solana-agent.vercel.app",
    },
  ],
  //...
}

Finally, deploy to bitte with make-agent:

make-agent deploy

This will validate your key, register your agent URL, and then you'll be live!

See also

Last updated