Skip to content

Commit 0e007f2

Browse files
authored
Merge pull request elizaOS#1818 from bertux/plugin-arthera-alpha
feat: new plugin Arthera Chain
2 parents ae56659 + 069a643 commit 0e007f2

16 files changed

+910
-0
lines changed

.env.example

+3
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ EVM_PROVIDER_URL=
197197
AVALANCHE_PRIVATE_KEY=
198198
AVALANCHE_PUBLIC_KEY=
199199

200+
# Arthera
201+
ARTHERA_PRIVATE_KEY=
202+
200203
# Solana
201204
SOLANA_PRIVATE_KEY=
202205
SOLANA_PUBLIC_KEY=

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"@elizaos/plugin-web-search": "workspace:*",
7070
"@elizaos/plugin-genlayer": "workspace:*",
7171
"@elizaos/plugin-open-weather": "workspace:*",
72+
"@elizaos/plugin-arthera": "workspace:*",
7273
"readline": "1.3.0",
7374
"ws": "8.18.0",
7475
"yargs": "17.7.2"

agent/src/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
7171

7272
import { availPlugin } from "@elizaos/plugin-avail";
7373
import { openWeatherPlugin } from "@elizaos/plugin-open-weather";
74+
75+
import { artheraPlugin } from "@elizaos/plugin-arthera";
7476
import { stargazePlugin } from "@elizaos/plugin-stargaze";
77+
7578
import Database from "better-sqlite3";
7679
import fs from "fs";
7780
import net from "net";
@@ -653,6 +656,9 @@ export async function createAgent(
653656
getSecret(character, "OPEN_WEATHER_API_KEY")
654657
? openWeatherPlugin
655658
: null,
659+
getSecret(character, "ARTHERA_PRIVATE_KEY")?.startsWith("0x")
660+
? artheraPlugin
661+
: null,
656662
].filter(Boolean),
657663
providers: [],
658664
actions: [],

packages/plugin-arthera/README.md

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# `@elizaos/plugin-arthera`
2+
3+
This plugin provides actions and providers for interacting with Arthera.
4+
5+
---
6+
7+
## Configuration
8+
9+
### Default Setup
10+
11+
By default, **Arthera** is enabled. To use it, simply add your private key to the `.env` file:
12+
13+
```env
14+
ARTHERA_PRIVATE_KEY=your-private-key-here
15+
```
16+
17+
### Custom RPC URLs
18+
19+
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:
20+
21+
```env
22+
ETHEREUM_PROVIDER_<CHAIN_NAME>=https://your-custom-rpc-url
23+
```
24+
25+
**Example usage:**
26+
27+
```env
28+
ETHEREUM_PROVIDER_ARTHERA=https://rpc.arthera.net
29+
```
30+
31+
## Provider
32+
33+
The **Wallet Provider** initializes with Arthera. It:
34+
35+
- Provides the **context** of the currently connected address and its balance.
36+
- Creates **Public** and **Wallet clients** to interact with the supported chain.
37+
38+
---
39+
40+
## Actions
41+
42+
### Transfer
43+
44+
Transfer tokens from one address to another on Arthera. Just specify the:
45+
46+
- **Amount**
47+
- **Chain**
48+
- **Recipient Address**
49+
50+
**Example usage:**
51+
52+
```bash
53+
Transfer 1 AA to 0xRecipient on arthera.
54+
```
55+
56+
---
57+
58+
## Contribution
59+
60+
The plugin contains tests. Whether you're using **TDD** or not, please make sure to run the tests before submitting a PR.
61+
62+
### Running Tests
63+
64+
Navigate to the `plugin-arthera` directory and run:
65+
66+
```bash
67+
pnpm test
68+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import eslintGlobalConfig from "../../eslint.config.mjs";
2+
3+
export default [...eslintGlobalConfig];

packages/plugin-arthera/package.json

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@elizaos/plugin-arthera",
3+
"version": "0.1.8-alpha.1",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@elizaos/core": "workspace:*",
9+
"tsup": "8.3.5",
10+
"viem": "2.21.58"
11+
},
12+
"scripts": {
13+
"build": "tsup --format esm --dts",
14+
"dev": "tsup --format esm --dts --watch",
15+
"test": "vitest run",
16+
"lint": "eslint --fix --cache ."
17+
},
18+
"devDependencies": {
19+
"whatwg-url": "7.1.0"
20+
},
21+
"peerDependencies": {
22+
"whatwg-url": "7.1.0"
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { ByteArray, formatEther, parseEther, type Hex } from "viem";
2+
import {
3+
composeContext,
4+
generateObjectDeprecated,
5+
HandlerCallback,
6+
ModelClass,
7+
type IAgentRuntime,
8+
type Memory,
9+
type State,
10+
} from "@elizaos/core";
11+
12+
import { initWalletProvider, WalletProvider } from "../providers/wallet";
13+
import type { Transaction, TransferParams } from "../types";
14+
import { transferTemplate } from "../templates";
15+
16+
export { transferTemplate };
17+
18+
// Exported for tests
19+
export class TransferAction {
20+
constructor(private walletProvider: WalletProvider) {}
21+
22+
async transfer(params: TransferParams): Promise<Transaction> {
23+
const walletClient = this.walletProvider.getWalletClient(
24+
params.fromChain
25+
);
26+
27+
console.log(
28+
`Transferring: ${params.amount} tokens from (${walletClient.account.address} to (${params.toAddress} on ${params.fromChain})`
29+
);
30+
31+
if (!params.data) {
32+
params.data = "0x";
33+
}
34+
35+
try {
36+
const hash = await walletClient.sendTransaction({
37+
account: walletClient.account,
38+
to: params.toAddress,
39+
value: parseEther(params.amount),
40+
data: params.data as Hex,
41+
kzg: {
42+
blobToKzgCommitment: function (_: ByteArray): ByteArray {
43+
throw new Error("Function not implemented.");
44+
},
45+
computeBlobKzgProof: function (
46+
_blob: ByteArray,
47+
_commitment: ByteArray
48+
): ByteArray {
49+
throw new Error("Function not implemented.");
50+
},
51+
},
52+
chain: undefined,
53+
});
54+
55+
return {
56+
hash,
57+
from: walletClient.account.address,
58+
to: params.toAddress,
59+
value: parseEther(params.amount),
60+
data: params.data as Hex,
61+
};
62+
} catch (error) {
63+
throw new Error(`Transfer failed: ${error.message}`);
64+
}
65+
}
66+
}
67+
68+
const buildTransferDetails = async (
69+
state: State,
70+
runtime: IAgentRuntime,
71+
wp: WalletProvider
72+
): Promise<TransferParams> => {
73+
const context = composeContext({
74+
state,
75+
template: transferTemplate,
76+
});
77+
78+
const chains = Object.keys(wp.chains);
79+
80+
const contextWithChains = context.replace(
81+
"SUPPORTED_CHAINS",
82+
chains.map((item) => `"${item}"`).join("|")
83+
);
84+
85+
const transferDetails = (await generateObjectDeprecated({
86+
runtime,
87+
context: contextWithChains,
88+
modelClass: ModelClass.SMALL,
89+
})) as TransferParams;
90+
91+
const existingChain = wp.chains[transferDetails.fromChain];
92+
93+
if (!existingChain) {
94+
throw new Error(
95+
"The chain " +
96+
transferDetails.fromChain +
97+
" not configured yet. Add the chain or choose one from configured: " +
98+
chains.toString()
99+
);
100+
}
101+
102+
return transferDetails;
103+
};
104+
105+
export const transferAction = {
106+
name: "transfer",
107+
description: "Transfer tokens between addresses on the same chain",
108+
handler: async (
109+
runtime: IAgentRuntime,
110+
_message: Memory,
111+
state: State,
112+
_options: Record<string, unknown>,
113+
callback?: HandlerCallback
114+
) => {
115+
console.log("Transfer action handler called");
116+
const walletProvider = initWalletProvider(runtime);
117+
const action = new TransferAction(walletProvider);
118+
119+
// Compose transfer context
120+
const paramOptions = await buildTransferDetails(
121+
state,
122+
runtime,
123+
walletProvider
124+
);
125+
126+
try {
127+
const transferResp = await action.transfer(paramOptions);
128+
if (callback) {
129+
callback({
130+
text: `Successfully transferred ${paramOptions.amount} tokens to ${paramOptions.toAddress}\nTransaction Hash: ${transferResp.hash}`,
131+
content: {
132+
success: true,
133+
hash: transferResp.hash,
134+
amount: formatEther(transferResp.value),
135+
recipient: transferResp.to,
136+
chain: paramOptions.fromChain,
137+
},
138+
});
139+
}
140+
return true;
141+
} catch (error) {
142+
console.error("Error during token transfer:", error);
143+
if (callback) {
144+
callback({
145+
text: `Error transferring tokens: ${error.message}`,
146+
content: { error: error.message },
147+
});
148+
}
149+
return false;
150+
}
151+
},
152+
template: transferTemplate,
153+
validate: async (runtime: IAgentRuntime) => {
154+
const privateKey = runtime.getSetting("ARTHERA_PRIVATE_KEY");
155+
return typeof privateKey === "string" && privateKey.startsWith("0x");
156+
},
157+
examples: [
158+
[
159+
{
160+
user: "assistant",
161+
content: {
162+
text: "I'll help you transfer 1 AA to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
163+
action: "SEND_TOKENS",
164+
},
165+
},
166+
{
167+
user: "user",
168+
content: {
169+
text: "Transfer 1 AA to 0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
170+
action: "SEND_TOKENS",
171+
},
172+
},
173+
],
174+
],
175+
similes: ["SEND_TOKENS", "TOKEN_TRANSFER", "MOVE_TOKENS"],
176+
};

packages/plugin-arthera/src/index.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export * from "./actions/transfer";
2+
export * from "./providers/wallet";
3+
export * from "./types";
4+
5+
import type { Plugin } from "@elizaos/core";
6+
import { transferAction } from "./actions/transfer";
7+
import { artheraWalletProvider } from "./providers/wallet";
8+
9+
export const artheraPlugin: Plugin = {
10+
name: "arthera",
11+
description: "Arthera blockchain integration plugin",
12+
providers: [artheraWalletProvider],
13+
evaluators: [],
14+
services: [],
15+
actions: [transferAction],
16+
};
17+
18+
export default artheraPlugin;

0 commit comments

Comments
 (0)