Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add fuel plugin #1512

Merged
merged 5 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@elizaos/plugin-twitter": "workspace:*",
"@elizaos/plugin-cronoszkevm": "workspace:*",
"@elizaos/plugin-3d-generation": "workspace:*",
"@elizaos/plugin-fuel": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
Expand Down
2 changes: 2 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { confluxPlugin } from "@elizaos/plugin-conflux";
import { evmPlugin } from "@elizaos/plugin-evm";
import { storyPlugin } from "@elizaos/plugin-story";
import { flowPlugin } from "@elizaos/plugin-flow";
import { fuelPlugin } from "@elizaos/plugin-fuel";
import { imageGenerationPlugin } from "@elizaos/plugin-image-generation";
import { ThreeDGenerationPlugin } from "@elizaos/plugin-3d-generation";
import { multiversxPlugin } from "@elizaos/plugin-multiversx";
Expand Down Expand Up @@ -591,6 +592,7 @@ export async function createAgent(
getSecret(character, "TON_PRIVATE_KEY") ? tonPlugin : null,
getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
getSecret(character, "STORY_PRIVATE_KEY") ? storyPlugin : null,
getSecret(character, "FUEL_PRIVATE_KEY") ? fuelPlugin : null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
31 changes: 31 additions & 0 deletions docs/docs/packages/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,37 @@ console.log("Webhook creation response:", response);
- **Validation**: Always validate input parameters to ensure compliance with expected formats and supported networks.
- **Error Handling**: Monitor logs for errors during webhook creation and adjust retry logic as needed.

### 10. Fuel Plugin (`@elizaos/plugin-fuel`)

The Fuel plugin provides an interface to the Fuel Ignition blockchain.

**Actions:**

1. `TRANSFER_FUEL_ETH` - Transfer ETH to a given Fuel address. - **Inputs**: - `toAddress` (string): The Fuel address to transfer ETH to. - `amount` (string): The amount of ETH to transfer. - **Outputs**: Confirmation message with transaction details. - **Example**:
`json
{
"toAddress": "0x8F8afB12402C9a4bD9678Bec363E51360142f8443FB171655eEd55dB298828D1",
"amount": "0.00001"
}
`
**Setup and Configuration:**

1. **Configure the Plugin**
Add the plugin to your character's configuration:

```typescript
import { fuelPlugin } from "@eliza/plugin-fuel";

const character = {
plugins: [fuelPlugin],
};
```

1. **Required Configurations**
Set the following environment variables or runtime settings:

- `FUEL_WALLET_PRIVATE_KEY`: Private key for secure transactions

### Writing Custom Plugins

Create a new plugin by implementing the Plugin interface:
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-fuel/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FUEL_WALLET_PRIVATE_KEY=
6 changes: 6 additions & 0 deletions packages/plugin-fuel/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*

!dist/**
!package.json
!readme.md
!tsup.config.ts
3 changes: 3 additions & 0 deletions packages/plugin-fuel/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import eslintGlobalConfig from "../../eslint.config.mjs";

export default [...eslintGlobalConfig];
23 changes: 23 additions & 0 deletions packages/plugin-fuel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@elizaos/plugin-fuel",
"version": "0.1.7-alpha.1",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"fuels": "0.97.2",
"tsup": "8.3.5",
"vitest": "2.1.4"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache .",
"test": "vitest run"
},
"peerDependencies": {
"form-data": "4.0.1",
"whatwg-url": "7.1.0"
}
}
109 changes: 109 additions & 0 deletions packages/plugin-fuel/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
Action,
composeContext,
generateObjectDeprecated,
IAgentRuntime,
ModelClass,
State,
} from "@elizaos/core";
import { initWalletProvider, WalletProvider } from "../providers/wallet";
import { bn } from "fuels";
import { transferTemplate } from "../templates";

type TransferParams = {
toAddress: string;
amount: string;
};

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

async transfer(params: TransferParams) {
try {
const { toAddress, amount } = params;
const res = await this.walletProvider.wallet.transfer(
toAddress,
bn.parseUnits(amount)
);
const tx = await res.waitForResult();
return tx;
} catch (error) {
throw new Error(`Transfer failed: ${error.message}`);
}
}
}

const buildTransferDetails = async (state: State, runtime: IAgentRuntime) => {
const context = composeContext({
state,
template: transferTemplate,
});

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

return transferDetails;
};

export const transferAction: Action = {
name: "transfer",
description: "Transfer Fuel ETH between addresses on Fuel Ignition",
handler: async (runtime, message, state, options, callback) => {
const walletProvider = await initWalletProvider(runtime);
const action = new TransferAction(walletProvider);

const paramOptions = await buildTransferDetails(state, runtime);

try {
const transferResp = await action.transfer(paramOptions);
if (callback) {
callback({
text: `Successfully transferred ${paramOptions.amount} ETH to ${paramOptions.toAddress}\nTransaction Hash: ${transferResp.id}`,
content: {
success: true,
hash: transferResp.id,
amount: paramOptions.amount,
recipient: paramOptions.toAddress,
},
});
}
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) => {
const privateKey = runtime.getSetting("FUEL_PRIVATE_KEY");
return typeof privateKey === "string" && privateKey.startsWith("0x");
},
examples: [
[
{
user: "assistant",
content: {
text: "I'll help you transfer 1 ETH to 0x8F8afB12402C9a4bD9678Bec363E51360142f8443FB171655eEd55dB298828D1",
action: "SEND_TOKENS",
},
},
{
user: "user",
content: {
text: "Transfer 1 ETH to 0x8F8afB12402C9a4bD9678Bec363E51360142f8443FB171655eEd55dB298828D1",
action: "SEND_TOKENS",
},
},
],
],
similes: ["TRANSFER_FUEL_ETH"],
};
14 changes: 14 additions & 0 deletions packages/plugin-fuel/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Plugin } from "@elizaos/core";
import { transferAction } from "./actions/transfer";
import { fuelWalletProvider } from "./providers/wallet";

export const fuelPlugin: Plugin = {
name: "fuel",
description: "Fuel blockchain integration plugin",
providers: [fuelWalletProvider],
evaluators: [],
services: [],
actions: [transferAction],
};

export default fuelPlugin;
44 changes: 44 additions & 0 deletions packages/plugin-fuel/src/providers/wallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { IAgentRuntime, Provider, Memory, State } from "@elizaos/core";
import { Provider as FuelProvider, Wallet, WalletUnlocked } from "fuels";

export class WalletProvider {
wallet: WalletUnlocked;

constructor(privateKey: `0x${string}`, provider: FuelProvider) {
this.wallet = Wallet.fromPrivateKey(privateKey, provider);
}

getAddress(): string {
return this.wallet.address.toB256();
}

async getBalance() {
const balance = await this.wallet.getBalance();
return balance.format();
}
}

export const initWalletProvider = async (runtime: IAgentRuntime) => {
const privateKey = runtime.getSetting("FUEL_PRIVATE_KEY");
if (!privateKey) {
throw new Error("FUEL_PRIVATE_KEY is missing");
}
const fuelProviderUrl =
runtime.getSetting("FUEL_PROVIDER_URL") ||
"https://mainnet.fuel.network/v1/graphql";

const provider = await FuelProvider.create(fuelProviderUrl);
return new WalletProvider(privateKey as `0x${string}`, provider);
};

export const fuelWalletProvider: Provider = {
async get(
runtime: IAgentRuntime,
_message: Memory,
_state?: State
): Promise<string | null> {
const walletProvider = await initWalletProvider(runtime);
const balance = await walletProvider.getBalance();
return `Fuel Wallet Address: ${walletProvider.getAddress()}\nBalance: ${balance} ETH`;
},
};
19 changes: 19 additions & 0 deletions packages/plugin-fuel/src/templates/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const transferTemplate = `Given the recent messages and wallet information below:

{{recentMessages}}

{{walletInfo}}

Extract the following information about the requested transfer:
- Amount to transfer: Must be a string representing the amount in ETH (only number without coin symbol, e.g., "0.1")
- Recipient address: Must be a valid Fuel wallet address starting with "0x"

Respond with a JSON markdown block containing only the extracted values. All fields except 'token' are required:

\`\`\`json
{
"amount": string,
"toAddress": string,
}
\`\`\`
`;
57 changes: 57 additions & 0 deletions packages/plugin-fuel/src/tests/transfer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, it, expect, beforeEach } from "vitest";

import { TransferAction } from "../actions/transfer";
import { WalletProvider } from "../providers/wallet";
import { Provider, Wallet } from "fuels";

describe("Transfer Action", () => {
let wp: WalletProvider;

beforeEach(async () => {
const provider = await Provider.create(
"https://mainnet.fuel.network/v1/graphql"
);
wp = new WalletProvider(
process.env.FUEL_WALLET_PRIVATE_KEY as `0x${string}`,
provider
);
});
describe("Constructor", () => {
it("should initialize with wallet provider", () => {
const ta = new TransferAction(wp);

expect(ta).toBeDefined();
});
});
describe("Transfer", () => {
let ta: TransferAction;
let receiver: string;

beforeEach(async () => {
ta = new TransferAction(wp);
const provider = await Provider.create(
"https://mainnet.fuel.network/v1/graphql"
);
receiver = Wallet.generate({ provider }).address.toB256();
});

it("throws if not enough gas", async () => {
await expect(
ta.transfer({
toAddress: receiver,
amount: "1",
})
).rejects.toThrow(
`Transfer failed: The account(s) sending the transaction don't have enough funds to cover the transaction.`
);
});

it("should transfer funds if there is enough balance", async () => {
const tx = await ta.transfer({
toAddress: receiver,
amount: "0.00001",
});
expect(tx.status).toBe("success");
});
});
});
10 changes: 10 additions & 0 deletions packages/plugin-fuel/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "../core/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": [
"src/**/*.ts"
]
}
29 changes: 29 additions & 0 deletions packages/plugin-fuel/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts"],
outDir: "dist",
sourcemap: true,
clean: true,
format: ["esm"], // Ensure you're targeting CommonJS
external: [
"dotenv", // Externalize dotenv to prevent bundling
"fs", // Externalize fs to use Node.js built-in module
"path", // Externalize other built-ins if necessary
"@reflink/reflink",
"@node-llama-cpp",
"https",
"http",
"agentkeepalive",
"safe-buffer",
"base-x",
"bs58",
"borsh",
"@solana/buffer-layout",
"stream",
"buffer",
"querystring",
"amqplib",
// Add other modules you want to externalize
],
});
Loading
Loading