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

feat: new plugin Arthera Chain #1818

Merged
merged 6 commits into from
Jan 7, 2025
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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ EVM_PROVIDER_URL=
AVALANCHE_PRIVATE_KEY=
AVALANCHE_PUBLIC_KEY=

# Arthera
ARTHERA_PRIVATE_KEY=

# Solana
SOLANA_PRIVATE_KEY=
SOLANA_PUBLIC_KEY=
Expand Down
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"@elizaos/plugin-web-search": "workspace:*",
"@elizaos/plugin-genlayer": "workspace:*",
"@elizaos/plugin-open-weather": "workspace:*",
"@elizaos/plugin-arthera": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
Expand Down
6 changes: 6 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";

import { availPlugin } from "@elizaos/plugin-avail";
import { openWeatherPlugin } from "@elizaos/plugin-open-weather";

import { artheraPlugin } from "@elizaos/plugin-arthera";
import { stargazePlugin } from "@elizaos/plugin-stargaze";

import Database from "better-sqlite3";
import fs from "fs";
import net from "net";
Expand Down Expand Up @@ -653,6 +656,9 @@ export async function createAgent(
getSecret(character, "OPEN_WEATHER_API_KEY")
? openWeatherPlugin
: null,
getSecret(character, "ARTHERA_PRIVATE_KEY")?.startsWith("0x")
? artheraPlugin
: null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
68 changes: 68 additions & 0 deletions packages/plugin-arthera/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# `@elizaos/plugin-arthera`

This plugin provides actions and providers for interacting with Arthera.

---

## Configuration

### Default Setup

By default, **Arthera** is enabled. To use it, simply add your private key to the `.env` file:

```env
ARTHERA_PRIVATE_KEY=your-private-key-here
```

### Custom RPC URLs

By default, the RPC URL is inferred from the `viem/chains` config. To use a custom RPC URL for a specific chain, add the following to your `.env` file:

```env
ETHEREUM_PROVIDER_<CHAIN_NAME>=https://your-custom-rpc-url
```

**Example usage:**

```env
ETHEREUM_PROVIDER_ARTHERA=https://rpc.arthera.net
```

## Provider

The **Wallet Provider** initializes with Arthera. It:

- Provides the **context** of the currently connected address and its balance.
- Creates **Public** and **Wallet clients** to interact with the supported chain.

---

## Actions

### Transfer

Transfer tokens from one address to another on Arthera. Just specify the:

- **Amount**
- **Chain**
- **Recipient Address**

**Example usage:**

```bash
Transfer 1 AA to 0xRecipient on arthera.
```

---

## Contribution

The plugin contains tests. Whether you're using **TDD** or not, please make sure to run the tests before submitting a PR.

### Running Tests

Navigate to the `plugin-arthera` directory and run:

```bash
pnpm test
```
3 changes: 3 additions & 0 deletions packages/plugin-arthera/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];
24 changes: 24 additions & 0 deletions packages/plugin-arthera/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@elizaos/plugin-arthera",
"version": "0.1.8-alpha.1",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"tsup": "8.3.5",
"viem": "2.21.58"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"test": "vitest run",
"lint": "eslint --fix --cache ."
},
"devDependencies": {
"whatwg-url": "7.1.0"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
176 changes: 176 additions & 0 deletions packages/plugin-arthera/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { ByteArray, formatEther, parseEther, type Hex } from "viem";
import {
composeContext,
generateObjectDeprecated,
HandlerCallback,
ModelClass,
type IAgentRuntime,
type Memory,
type State,
} from "@elizaos/core";

import { initWalletProvider, WalletProvider } from "../providers/wallet";
import type { Transaction, TransferParams } from "../types";
import { transferTemplate } from "../templates";

export { transferTemplate };

// Exported for tests
export class TransferAction {
constructor(private walletProvider: WalletProvider) {}

async transfer(params: TransferParams): Promise<Transaction> {
const walletClient = this.walletProvider.getWalletClient(
params.fromChain
);

console.log(
`Transferring: ${params.amount} tokens from (${walletClient.account.address} to (${params.toAddress} on ${params.fromChain})`
);

if (!params.data) {
params.data = "0x";
}

try {
const hash = await walletClient.sendTransaction({
account: walletClient.account,
to: params.toAddress,
value: parseEther(params.amount),
data: params.data as Hex,
kzg: {
blobToKzgCommitment: function (_: ByteArray): ByteArray {
throw new Error("Function not implemented.");
},
computeBlobKzgProof: function (
_blob: ByteArray,
_commitment: ByteArray
): ByteArray {
throw new Error("Function not implemented.");
},
},
chain: undefined,
});

return {
hash,
from: walletClient.account.address,
to: params.toAddress,
value: parseEther(params.amount),
data: params.data as Hex,
};
} catch (error) {
throw new Error(`Transfer failed: ${error.message}`);
}
}
}

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

const chains = Object.keys(wp.chains);

const contextWithChains = context.replace(
"SUPPORTED_CHAINS",
chains.map((item) => `"${item}"`).join("|")
);

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

const existingChain = wp.chains[transferDetails.fromChain];

if (!existingChain) {
throw new Error(
"The chain " +
transferDetails.fromChain +
" not configured yet. Add the chain or choose one from configured: " +
chains.toString()
);
}

return transferDetails;
};

export const transferAction = {
name: "transfer",
description: "Transfer tokens between addresses on the same chain",
handler: async (
runtime: IAgentRuntime,
_message: Memory,
state: State,
_options: Record<string, unknown>,
callback?: HandlerCallback
) => {
console.log("Transfer action handler called");
const walletProvider = initWalletProvider(runtime);
const action = new TransferAction(walletProvider);

// Compose transfer context
const paramOptions = await buildTransferDetails(
state,
runtime,
walletProvider
);

try {
const transferResp = await action.transfer(paramOptions);
if (callback) {
callback({
text: `Successfully transferred ${paramOptions.amount} tokens to ${paramOptions.toAddress}\nTransaction Hash: ${transferResp.hash}`,
content: {
success: true,
hash: transferResp.hash,
amount: formatEther(transferResp.value),
recipient: transferResp.to,
chain: paramOptions.fromChain,
},
});
}
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("ARTHERA_PRIVATE_KEY");
return typeof privateKey === "string" && privateKey.startsWith("0x");
},
examples: [
[
{
user: "assistant",
content: {
text: "I'll help you transfer 1 AA to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
action: "SEND_TOKENS",
},
},
{
user: "user",
content: {
text: "Transfer 1 AA to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
action: "SEND_TOKENS",
},
},
],
],
similes: ["SEND_TOKENS", "TOKEN_TRANSFER", "MOVE_TOKENS"],
};
18 changes: 18 additions & 0 deletions packages/plugin-arthera/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export * from "./actions/transfer";
export * from "./providers/wallet";
export * from "./types";

import type { Plugin } from "@elizaos/core";
import { transferAction } from "./actions/transfer";
import { artheraWalletProvider } from "./providers/wallet";

export const artheraPlugin: Plugin = {
name: "arthera",
description: "Arthera blockchain integration plugin",
providers: [artheraWalletProvider],
evaluators: [],
services: [],
actions: [transferAction],
};

export default artheraPlugin;
Loading
Loading