LinkLabs (ElizaOS+Functions)
  • Chainlink Workshop: Use AI agent to mint an NFT gift
    • Use an AI agent to interact with Blockchains
  • 1. Create table in Supabase
  • 2. Deploy the GetGift.sol
  • 3 Register A CL Functions Subscription
  • 4. Prepare DON hosted secrets
  • 5. Setup and start Eliza agent
  • 6. Use twitter client
Powered by GitBook
On this page
  • Where are we?
  • Steps to setup and start Eliza
  • Let's understand the code
Export as PDF

5. Setup and start Eliza agent

Previous4. Prepare DON hosted secretsNext6. Use twitter client

Last updated 3 days ago

Where are we?

Eliza-Starter is a template from Eliza framework to allow developers to create characters, clients and plugins. In this starter, a plugin called GetGift is created to call functions consumer smart contract deployed in previous steps.

Steps to setup and start Eliza

Let's get ready to use the Eliza AI Agent Framework with the GetGift Eliza plugin by following steps:

cd Eliza-Twitter-Chainlink-Functions

// Install dependencies
<<npm/pnpm/yarn>> install
  1. Check the .env.example file and make sure your .env file has all the indicated environment variables. We will need the Google Gemini key, evm private key and avalanche fuji rpc url set correctly in the .env.

...
GEMINI_API_KEY= # fill this field with GOOGLE GEMINI API Key

...
EVM_PRIVATE_KEY= # fill the field with private key (start with 0x) on evm chains
ETHEREUM_PROVIDER_AVALANCHEFUJI= # fill the field with RPC urls to avalanche fuji

Open the file src/custom-plugins/actions/getGift.ts, and paste in the necessary configurations which are marked as TODOs. To make the actions to send transactions as we requested, these constants must be updated:

  • contractAddress: the address of Functions consumer smart contract. Update the value with contract you added to the the Chainlink Functions subscription.

  • donHostedSecretsSlotId: the slot ID for secret saved in the DON. If you did not modify the script in last step, assign the default one to the the constant. The default value is 0.

/**
 * @fileoverview This file contains the implementation of the GetGiftAction class and the getGiftAction handler.
 * It interacts with a smart contract on the Avalanche Fuji testnet to send a gift request.
 */

import { formatEther, parseEther, getContract } from "viem";
import {
    Action,
    composeContext,
    generateObjectDeprecated,
    HandlerCallback,
    ModelClass,
    type IAgentRuntime,
    type Memory,
    type State,
} from "@elizaos/core";

import { initWalletProvider, WalletProvider } from "../providers/wallet.ts";
import type { GetGiftParams, Transaction } from "../types/index.ts";
import { getGiftTemplate } from "../templates/index.ts";
import getGiftJson from "../artifacts/GetGift.json" with { type: "json" };

/**
 * Class representing the GetGiftAction.
 */
export class GetGiftAction {
    /**
     * Creates an instance of GetGiftAction.
     * @param {WalletProvider} walletProvider - The wallet provider instance.
     */
    constructor(private walletProvider: WalletProvider) {}

    /**
     * Sends a gift request to the smart contract.
     * @param {GetGiftParams} params - The parameters for the gift request.
     * @returns {Promise<Transaction>} The transaction details.
     * @throws Will throw an error if contract address, slot ID, version, or subscription ID is not set.
     */
    async getGift(params: GetGiftParams): Promise<Transaction> {
        const chainName = "avalancheFuji";
        const contractAddress: `0x${string}` =  "0x00" // dev TODO
        const donHostedSecretsSlotID:number = Infinity // dev TODO
        const donHostedSecretsVersion:number = Infinity // dev TODO
        const clSubId:number = Infinity // dev TODO

        if (contractAddress === "0x00" || donHostedSecretsSlotID === Infinity || donHostedSecretsVersion === Infinity || clSubId === Infinity) {
            throw new Error("Contract address, slot ID, version, or subscription ID is not set");
        }

        console.log(
            `Get gift with Id: ${params.code} and address (${params.address})`
        );

        this.walletProvider.switchChain(chainName);

        const walletClient = this.walletProvider.getWalletClient(
            chainName
        );

        try {
            const { abi } = getGiftJson["contracts"]["GetGift.sol:GetGift"]
            const getGiftContract = getContract({
                address: contractAddress,
                abi,
                client: walletClient
            })

            const args: string[] = [params.code];
            const userAddr = params.address;

            const hash = await getGiftContract.write.sendRequest([
                donHostedSecretsSlotID,
                donHostedSecretsVersion,
                args,
                clSubId,
                userAddr
            ])

            return {
                hash,
                from: walletClient.account!.address,
                to: contractAddress,
                value: parseEther("0"),
                data: "0x",
            };
        } catch (error) {
            if(error instanceof Error) {
                throw new Error(`Function call failed: ${error.message}`);
            } else {
                throw new Error(`Function call failed: unknown error`);
            }
        }
    }
}

/**
 * Builds the function call details required for the getGift action.
 * @param {State} state - The current state.
 * @param {IAgentRuntime} runtime - The agent runtime.
 * @param {WalletProvider} wp - The wallet provider.
 * @returns {Promise<GetGiftParams>} The parameters for the gift request.
 */
const buildFunctionCallDetails = async (
    state: State,
    runtime: IAgentRuntime,
    wp: WalletProvider
): Promise<GetGiftParams> => {
    const chains = Object.keys(wp.chains);
    state.supportedChains = chains.map((item) => `"${item}"`).join("|");

    const context = composeContext({
        state,
        template: getGiftTemplate,
    });

    const functionCallDetails = (await generateObjectDeprecated({
        runtime,
        context,
        modelClass: ModelClass.SMALL,
    })) as GetGiftParams;

    return functionCallDetails;
};

/**
 * The getGiftAction handler.
 * @type {Action}
 */
export const getGiftAction: Action = {
    name: "get gift",
    description: "Call a function on Functions consumer and send request",
    handler: async (
        runtime: IAgentRuntime,
        message: Memory,
        state?: State,
        _options?: any,
        callback?: HandlerCallback
    ) => {
        if (!state) {
            state = (await runtime.composeState(message)) as State;
        } else {
            state = await runtime.updateRecentMessageState(state);
        }

        console.log("Get gift action handler called");
        const walletProvider = await initWalletProvider(runtime);
        const action = new GetGiftAction(walletProvider);

        // Compose functionCall context
        const giftParams:GetGiftParams = await buildFunctionCallDetails(
            state,
            runtime,
            walletProvider
        );


        try {
            const callFunctionResp = await action.getGift(giftParams);
            if (callback) {
                callback({
                    text: `Successfully called function with params of gift code: ${giftParams.code} and address: ${giftParams.address}\nTransaction Hash: ${callFunctionResp.hash}`,
                    content: {
                        success: true,
                        hash: callFunctionResp.hash,
                        amount: formatEther(callFunctionResp.value),
                        recipient: callFunctionResp.to,
                        chain: "avalanchefuji",
                    },
                });
            }
            return true;
        } catch (error) {
            console.error("Error during get gift call:", error);
            if(error instanceof Error) {
                if (callback) {
                    callback({
                        text: `Error get gift calling: ${error.message}`,
                        content: { error: error.message },
                    });
                }
            } else {
                console.error("unknow error")
            }
            return false;
        }
    },
    validate: async (runtime: IAgentRuntime) => {
        const privateKey = runtime.getSetting("EVM_PRIVATE_KEY");
        return typeof privateKey === "string" && privateKey.startsWith("0x");
    },
    examples: [
        [
            {
                user: "assistant",
                content: {
                    text: "I'll help you call function on contract",
                    action: "GET_GIFT",
                },
            },
            {
                user: "user",
                content: {
                    text: "Give me the gift to address 0x1234567890123456789012345678901234567890, ID for gift is 1010",
                    action: "GET_GIFT",
                },
            },
            {
                user: "user",
                content: {
                    text: "Can I get the gift to address 0x1234567890123456789012345678901234567890, my gift ID is 898770",
                    action: "GET_GIFT",
                },
            },
        ],
    ],
    similes: ["GET_GIFT", "GIFT_GIVE", "SEND_GIFT"],
};

Let's understand the code

In GetGiftAction Class, core functionalities to interact with the smart contract are implemented, and wallet provider which is a wrapper of viem used to handle blockchain interactions. Within the class, the function getGift parses parameters and extract gift and grantee's address and then make a call to the hardcoded smart contract with necessary data.

The other exported object is Action getGiftAction which is the "configuration" of the plugin. The plugin is registered with agent as an entry to make the action.

  1. After config info updated, run the agent with default character using the following command (⚠️ remember to check that your using node V23 or higher!)

pnpm start

If your AI agent start successfully, you will see the info below:

  1. Ask for a NFT gift

Have your wallet address and one of the 3 gift codes ready. Then type your request in natural language - use can use the following template:

please send the gift to: <<YOUR_WALLET>> My gift code is: <<GIFT_CODE_IN_DATABASE>>

Type this into your terminal as show below:

  1. Check the responses

The AI agent will process the request and return the message and transaction hash. You will the see the info below:

  1. Check the Chainlink Functions app

  1. Check if a NFT minted for a specific address

Please be noticed that the same gift codes cannot be redeemed twice which means you cannot have the duplicated gift on the address.

Git clone the repo: `git clone ` Then change directory into the Eliza-Twitter-Chainlink-Functions project folder and run.

Update variables in action implementation file getGift.ts ()

donHostedSecretsVersion: the version of secret saved in the DON you uploaded in last step. You can find the value in your Remix IDE terminal. In last step, you have already uploaded secret to the DON, when making the request, you must pass the donHostedSecretsSlotId and donHostedSecretsVersion to tell the DON where to fetch the encrypted secrets. Both of these things should be printed to your Remix IDE terminal from the.

clSubId: Chainlink Functions Subscription ID. Update the value to the subscription ID you created on the .

getGift.ts implements a custom for interacting with a Chainlink Functions consumer contract on the Avalanche Fuji testnet. Here's a breakdown of its main components:

After the action in AI agent is processed, a transaction is sent to the Functions consumer smart contract deployed before. The contract constructs a new Functions request with gift ID and send to the DON, and the the request is displayed on .

Go to the to check if you have a new NFT. Paste the address of your GetGiftcontract into the OpenSea search bar, and you can see the NFT(s) minted to your address!

https://github.com/smartcontractkit/Eliza-Twitter-Chainlink-Functions.git
repo link
upload secrets step
Functions Dashboard App
ElizaOS Action
Functions.app
Opensea for testnet
The AI agent successfully running
Type your request directly in terminal
Replies from Agents
latest successful fulfillment to the request
A specific NFT is dropped to the address based on the gift ID in request