Skip to content

Commit

Permalink
add Cardano Blockchain Plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
luna committed Jan 3, 2025
1 parent 76d4f42 commit bbe7cfe
Show file tree
Hide file tree
Showing 12 changed files with 3,700 additions and 50 deletions.
23 changes: 12 additions & 11 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,40 @@
"@elizaos/client-discord": "workspace:*",
"@elizaos/client-farcaster": "workspace:*",
"@elizaos/client-lens": "workspace:*",
"@elizaos/client-slack": "workspace:*",
"@elizaos/client-telegram": "workspace:*",
"@elizaos/client-twitter": "workspace:*",
"@elizaos/client-slack": "workspace:*",
"@elizaos/core": "workspace:*",
"@elizaos/plugin-0g": "workspace:*",
"@elizaos/plugin-3d-generation": "workspace:*",
"@elizaos/plugin-abstract": "workspace:*",
"@elizaos/plugin-aptos": "workspace:*",
"@elizaos/plugin-avalanche": "workspace:*",
"@elizaos/plugin-bootstrap": "workspace:*",
"@elizaos/plugin-intiface": "workspace:*",
"@elizaos/plugin-cardano": "workspace:*",
"@elizaos/plugin-coinbase": "workspace:*",
"@elizaos/plugin-conflux": "workspace:*",
"@elizaos/plugin-cronoszkevm": "workspace:*",
"@elizaos/plugin-evm": "workspace:*",
"@elizaos/plugin-flow": "workspace:*",
"@elizaos/plugin-fuel": "workspace:*",
"@elizaos/plugin-gitbook": "workspace:*",
"@elizaos/plugin-story": "workspace:*",
"@elizaos/plugin-goat": "workspace:*",
"@elizaos/plugin-icp": "workspace:*",
"@elizaos/plugin-image-generation": "workspace:*",
"@elizaos/plugin-intiface": "workspace:*",
"@elizaos/plugin-multiversx": "workspace:*",
"@elizaos/plugin-near": "workspace:*",
"@elizaos/plugin-nft-generation": "workspace:*",
"@elizaos/plugin-node": "workspace:*",
"@elizaos/plugin-solana": "workspace:*",
"@elizaos/plugin-starknet": "workspace:*",
"@elizaos/plugin-ton": "workspace:*",
"@elizaos/plugin-story": "workspace:*",
"@elizaos/plugin-sui": "workspace:*",
"@elizaos/plugin-tee": "workspace:*",
"@elizaos/plugin-multiversx": "workspace:*",
"@elizaos/plugin-near": "workspace:*",
"@elizaos/plugin-zksync-era": "workspace:*",
"@elizaos/plugin-ton": "workspace:*",
"@elizaos/plugin-twitter": "workspace:*",
"@elizaos/plugin-cronoszkevm": "workspace:*",
"@elizaos/plugin-3d-generation": "workspace:*",
"@elizaos/plugin-fuel": "workspace:*",
"@elizaos/plugin-avalanche": "workspace:*",
"@elizaos/plugin-zksync-era": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
Expand Down
4 changes: 4 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
import { cronosZkEVMPlugin } from "@elizaos/plugin-cronoszkevm";
import { abstractPlugin } from "@elizaos/plugin-abstract";
import { avalanchePlugin } from "@elizaos/plugin-avalanche";
import { cardanoPlugin } from "@elizaos/plugin-cardano";
import Database from "better-sqlite3";
import fs from "fs";
import path from "path";
Expand Down Expand Up @@ -600,6 +601,9 @@ export async function createAgent(
getSecret(character, "AVALANCHE_PRIVATE_KEY")
? avalanchePlugin
: null,
getSecret(character, "CARDANO_PRIVATE_KEY")
? cardanoPlugin
: null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
43 changes: 43 additions & 0 deletions packages/plugin-cardano/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Plugin Cardano

A plugin for handling Cardano blockchain operations, such as wallet management and transfers.

## Overview and Purpose

The Plugin provides a streamlined interface to interact with the Cardano blockchain. It simplifies wallet management and facilitates secure, efficient transfers while maintaining compatibility with TypeScript and modern JavaScript development practices.

## Installation

Install the plugin using npm:

```bash
npm install plugin-cardano
```

## Configuration Requirements

Ensure your environment is set up with the necessary configuration files and environment variables. Update the `src/enviroment.ts` file or set environment variables directly for sensitive information.

### Environment Variables

| Variable Name | Description |
| ------------------------ | ---------------------------------------------------------------- |
| `CARDANO_NETWORK` | Cardano network (Mainnet/Preprod/Preview) |
| `CARDANO_PRIVATE_KEY` | Cardano mnemonics |
| `CARDANO_MAESTRO_APIKEY` | [Maestro api key(daily limit)](https://dashboard.gomaestro.org/) |
| `CARDANO_RPC_URL` | RPC endpoint |

* `CARDANO_NETWORK` default value is `Mainnet`
* `CARDANO_RPC_URL` Not supported at the moment

## Usage Examples

### Testing Guide Expansion

Run tests using the following command:

```bash
pnpm run test
```

The `src/tests/wallet.test.ts` file provides unit tests for wallet functionality. Add tests for additional features as needed.
24 changes: 24 additions & 0 deletions packages/plugin-cardano/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@elizaos/plugin-cardano",
"version": "0.1.7-alpha.2",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"@elizaos/plugin-trustdb": "workspace:*",
"@meshsdk/core": "^1.8.8",
"bignumber": "1.1.0",
"bignumber.js": "9.1.2",
"node-cache": "5.1.2",
"tsup": "8.3.5"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"test": "vitest run"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
206 changes: 206 additions & 0 deletions packages/plugin-cardano/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import {
elizaLogger,
composeContext,
Content,
HandlerCallback,
ModelClass,
generateObject,
ActionExample,
Action,
type IAgentRuntime,
type Memory,
type State,
} from "@elizaos/core";
import { z } from "zod";

import {
initWalletProvider,
WalletProvider,
nativeWalletProvider,
} from "../providers/wallet";

import { generateObjectDeprecated } from "@elizaos/core";


export interface TransferContent extends Content {
recipient: string;
amount: string | number;
}

function isTransferContent(content: Content): content is TransferContent {
console.log("Content for transfer", content);
return (
typeof content.recipient === "string" &&
(typeof content.amount === "string" ||
typeof content.amount === "number")
);
}

const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
Example response:
\`\`\`json
{
"recipient": "addr_test1qq2234h0kv3rmeq0aze7t0n588l87r4erkfnux2zgm4aa8s2vj6ht7wkjr37r57y2ygjgdlyx2kf7n0ytwt9al2qw28sep0k7y",
"amount": "1"
}
\`\`\`
{{recentMessages}}
Given the recent messages, extract the following information about the requested token transfer:
- Recipient wallet address
- Amount to transfer
Respond with a JSON markdown block containing only the extracted values.`;

export class TransferAction {
constructor(private walletProvider: WalletProvider) { }

async transfer(params: TransferContent): Promise<string> {
console.log(
`Transferring: ${params.amount} ada tokens to (${params.recipient})`
);
try {
const walletClient = await this.walletProvider.sendAda(params.recipient, params.amount);
return walletClient;
} catch (error) {
throw new Error(`Transfer failed: ${error.message}`);
}
}
}

const buildTransferDetails = async (
runtime: IAgentRuntime,
message: Memory,
state: State
): Promise<TransferContent> => {
const walletInfo = await nativeWalletProvider.get(runtime, message, state);
state.walletInfo = walletInfo;

// Initialize or update state
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

// Define the schema for the expected output
// const transferSchema = z.object({
// recipient: z.string(),
// amount: z.union([z.string(), z.number()]),
// });

// Compose transfer context
const transferContext = composeContext({
state,
template: transferTemplate,
});

// Generate transfer content with the schema
// const content = await generateObjectDeprecated({
// runtime,
// context: transferContext,
// schema: transferSchema,
// modelClass: ModelClass.SMALL,
// });
const content = await generateObjectDeprecated({
runtime,
context: transferContext,
modelClass: ModelClass.SMALL,
});

// const transferContent = content.object as TransferContent;

return content;
};

export default {
name: "SEND_TOKEN",
similes: ["SEND_TOKENS", "TOKEN_TRANSFER", "TRANSFER_TOKEN", "TRANSFER_TOKENS", "SEND_ADA"],
description: "Transfer tokens from the agent's wallet to another",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
options: any,
callback?: HandlerCallback
) => {
elizaLogger.log("Starting SEND_TOKEN handler...");

const transferDetails = await buildTransferDetails(
runtime,
message,
state
);

// Validate transfer content
if (!isTransferContent(transferDetails)) {
console.error("Invalid content for TRANSFER_TOKEN action.");
if (callback) {
callback({
text: "Unable to process transfer request. Invalid content provided.",
content: { error: "Invalid transfer content" },
});
}
return false;
}

try {
const walletProvider = await initWalletProvider(runtime);
const action = new TransferAction(walletProvider);
const hash = await action.transfer(transferDetails);

if (callback) {
callback({
text: `Successfully transferred ${transferDetails.amount} ADA to ${transferDetails.recipient}, Transaction: ${hash}`,
content: {
success: true,
hash: hash,
amount: transferDetails.amount,
recipient: transferDetails.recipient,
},
});
}

return true;
} catch (error) {
console.error("Error during token transfer:", error);
if (callback) {
callback({
text: `Error transferring tokens: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},
template: transferTemplate,
validate: async (runtime: IAgentRuntime) => {
//console.log("Validating ADA transfer from user:", message.userId);
return true;
},
examples: [
[
{
user: "{{user1}}",
content: {
text: "Send 1 ADA tokens to addr_test1qq2234h0kv3rmeq0aze7t0n588l87r4erkfnux2zgm4aa8s2vj6ht7wkjr37r57y2ygjgdlyx2kf7n0ytwt9al2qw28sep0k7y",
},
},
{
user: "{{user2}}",
content: {
text: "I'll send 1 ADA tokens now...",
action: "SEND_TOKEN",
},
},
{
user: "{{user2}}",
content: {
text: "Successfully sent 1 ADA tokens to addr_test1qq2234h0kv3rmeq0aze7t0n588l87r4erkfnux2zgm4aa8s2vj6ht7wkjr37r57y2ygjgdlyx2kf7n0ytwt9al2qw28sep0k7y, Transaction: a26e4adfe3dc57b5fa8fea515cdcba1315ee8c8fe25d5864419bf2a1378b3d91",
},
},
],
] as ActionExample[][],
} as Action;
40 changes: 40 additions & 0 deletions packages/plugin-cardano/src/enviroment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { IAgentRuntime } from "@elizaos/core";
import { z } from "zod";

export const envSchema = z.object({
CARDANO_PRIVATE_KEY: z.string().min(1, "Cardano mnemonic is required"),
CARDANO_RPC_URL: z.string(),
CARDANO_NETWORK: z.enum(["mainnet", "preprod", "preview"]),
CARDANO_MAESTRO_APIKEY: z.string(),
});

export type EnvConfig = z.infer<typeof envSchema>;

export async function validateEnvConfig(
runtime: IAgentRuntime
): Promise<EnvConfig> {
try {
const config = {
CARDANO_PRIVATE_KEY:
runtime.getSetting("CARDANO_PRIVATE_KEY") || process.env.CARDANO_PRIVATE_KEY,
CARDANO_RPC_URL:
runtime.getSetting("CARDANO_RPC_URL") || process.env.CARDANO_RPC_URL,
CARDANO_NETWORK:
runtime.getSetting("CARDANO_NETWORK") || process.env.CARDANO_NETWORK,
CARDANO_MAESTRO_APIKEY:
runtime.getSetting("CARDANO_MAESTRO_APIKEY") || process.env.CARDANO_MAESTRO_APIKEY
};

return envSchema.parse(config);
} catch (error) {
if (error instanceof z.ZodError) {
const errorMessages = error.errors
.map((err) => `${err.path.join(".")}: ${err.message}`)
.join("\n");
throw new Error(
`Cardano configuration validation failed:\n${errorMessages}`
);
}
throw error;
}
}
Loading

0 comments on commit bbe7cfe

Please sign in to comment.