Skip to content

Commit 7e455cf

Browse files
authored
Merge pull request #2083 from 0xalank/quai-integration
feat: add Quai integration
2 parents 1eb98bf + 59e55ec commit 7e455cf

File tree

10 files changed

+470
-1
lines changed

10 files changed

+470
-1
lines changed

.env.example

+5-1
Original file line numberDiff line numberDiff line change
@@ -541,4 +541,8 @@ AKASH_MANIFEST_MODE=auto
541541
# Default: Will use the SDL directory
542542
AKASH_MANIFEST_PATH=
543543
# Values: "strict" | "lenient" | "none" - Default: "strict"
544-
AKASH_MANIFEST_VALIDATION_LEVEL=strict
544+
AKASH_MANIFEST_VALIDATION_LEVEL=strict
545+
546+
# Quai Network Ecosystem
547+
QUAI_PRIVATE_KEY=
548+
QUAI_RPC_URL=https://rpc.quai.network

agent/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"@elizaos/plugin-opacity": "workspace:*",
9090
"@elizaos/plugin-hyperliquid": "workspace:*",
9191
"@elizaos/plugin-akash": "workspace:*",
92+
"@elizaos/plugin-quai": "workspace:*",
9293
"readline": "1.3.0",
9394
"ws": "8.18.0",
9495
"yargs": "17.7.2"

agent/src/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ import { OpacityAdapter } from "@elizaos/plugin-opacity";
9191
import { openWeatherPlugin } from "@elizaos/plugin-open-weather";
9292
import { stargazePlugin } from "@elizaos/plugin-stargaze";
9393
import { akashPlugin } from "@elizaos/plugin-akash";
94+
import { quaiPlugin } from "@elizaos/plugin-quai";
9495
import Database from "better-sqlite3";
9596
import fs from "fs";
9697
import net from "net";
@@ -773,6 +774,12 @@ export async function createAgent(
773774
getSecret(character, "AKASH_WALLET_ADDRESS")
774775
? akashPlugin
775776
: null,
777+
getSecret(character, "QUAI_PRIVATE_KEY")
778+
? quaiPlugin
779+
: null,
780+
getSecret(character, "QUAI_PRIVATE_KEY")
781+
? quaiPlugin
782+
: null,
776783
].filter(Boolean),
777784
providers: [],
778785
actions: [],

packages/plugin-quai/package.json

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@elizaos/plugin-quai",
3+
"version": "0.0.1",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"types": "dist/index.d.ts",
7+
"dependencies": {
8+
"@elizaos/core": "workspace:*",
9+
"quais": "1.0.0-alpha.25",
10+
"tsup": "^8.3.5",
11+
"vitest": "^2.1.4",
12+
"@avnu/avnu-sdk": "^2.1.1",
13+
"@elizaos/plugin-trustdb": "workspace:*"
14+
},
15+
"scripts": {
16+
"build": "tsup --format esm --dts",
17+
"test": "vitest"
18+
},
19+
"peerDependencies": {
20+
"whatwg-url": "7.1.0"
21+
}
22+
}

packages/plugin-quai/readme.md

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# @elizaos/plugin-quai
2+
3+
Quai Network integration plugin for Eliza OS that enables native token transfers and interactions with the Quai blockchain.
4+
5+
## Overview
6+
7+
This plugin provides core functionality for interacting with the Quai Network, offering native token transfer capabilities and blockchain interactions through a simple interface.
8+
9+
## Features
10+
11+
- Native QUAI token transfers
12+
- Multiple network support
13+
- Secure transaction signing
14+
- Comprehensive error handling
15+
- Built-in address validation
16+
- Automatic gas estimation
17+
- Real-time transaction status
18+
19+
## Installation
20+
21+
```bash
22+
pnpm install @elizaos/plugin-quai
23+
```
24+
25+
## Configuration
26+
27+
The plugin requires the following environment variables:
28+
29+
```env
30+
QUAI_PRIVATE_KEY=your-private-key
31+
QUAI_RPC_URL=https://rpc.quai.network # or your preferred RPC endpoint
32+
```
33+
34+
## Usage
35+
36+
### Token Transfer
37+
38+
```typescript
39+
import { quaiPlugin } from '@elizaos/plugin-quai';
40+
41+
// Send QUAI
42+
const result = await eliza.execute({
43+
action: 'SEND_TOKEN',
44+
content: {
45+
recipient: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7',
46+
amount: '10'
47+
}
48+
});
49+
```
50+
51+
## API Reference
52+
53+
### Actions
54+
55+
#### `SEND_TOKEN`
56+
Transfers QUAI tokens to another address.
57+
58+
```typescript
59+
{
60+
action: 'SEND_TOKEN',
61+
content: {
62+
recipient: string, // Recipient's Quai address (42 characters, 0x prefix)
63+
amount: string, // Amount to send (in QUAI)
64+
tokenAddress?: string // Optional: for QRC20 tokens (not implemented yet)
65+
}
66+
}
67+
```
68+
69+
### Providers
70+
71+
The plugin uses Quai's native JsonRpcProvider for blockchain interactions:
72+
73+
```typescript
74+
const provider = getQuaiProvider(runtime);
75+
// Returns configured JsonRpcProvider instance
76+
```
77+
78+
## Troubleshooting
79+
80+
### Common Issues
81+
82+
1. **Transaction Failures**
83+
- Check account balance
84+
- Verify recipient address format
85+
- Ensure sufficient gas
86+
- Confirm network connection
87+
88+
2. **Connection Problems**
89+
- Verify RPC endpoint
90+
- Check network status
91+
- Ensure valid credentials
92+
- Monitor API availability
93+
94+
3. **Configuration Issues**
95+
- Verify environment variables
96+
- Check address format
97+
- Confirm private key format
98+
- Validate RPC URL
99+
100+
## Security Best Practices
101+
102+
1. **Key Management**
103+
- Store private keys securely
104+
- Use environment variables
105+
- Never expose private keys in code
106+
- Monitor account activity
107+
108+
2. **Transaction Safety**
109+
- Validate all addresses
110+
- Implement amount validation
111+
- Double-check recipients
112+
- Monitor transaction status
113+
114+
3. **Error Handling**
115+
- Log all transaction attempts
116+
- Handle timeouts gracefully
117+
- Validate all user inputs
118+
- Provide clear error messages
119+
120+
## Testing
121+
122+
Run the test suite:
123+
124+
```bash
125+
pnpm test
126+
```
127+
128+
## Dependencies
129+
130+
- quais: ^1.0.0-alpha.25
131+
- @elizaos/core: workspace:*
132+
133+
## Contributing
134+
135+
Contributions are welcome! Please ensure your code follows the existing patterns and includes appropriate tests.
136+
137+
## Credits
138+
139+
This plugin integrates with:
140+
- [Quai Network](https://qu.ai/)
141+
- [Quai JavaScript API](https://www.npmjs.com/package/quais)
142+
143+
For more information about Quai Network capabilities:
144+
- [Quai Documentation](https://docs.qu.ai/)
145+
- [Quai Network GitHub](https://github.com/dominant-strategies)
146+
147+
## License
148+
149+
This plugin is part of the Eliza project. See the main project repository for license information.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import {
2+
ActionExample,
3+
HandlerCallback,
4+
IAgentRuntime,
5+
Memory,
6+
ModelClass,
7+
State,
8+
type Action,
9+
composeContext,
10+
generateObject,
11+
} from "@elizaos/core";
12+
import {
13+
getQuaiAccount,
14+
isTransferContent,
15+
validateSettings,
16+
} from "../utils";
17+
import { formatUnits, TransactionRequest } from "quais";
18+
19+
const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
20+
21+
Example response:
22+
\`\`\`json
23+
{
24+
"tokenAddress": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
25+
"recipient": "0x0005C06bD1339c79700a8DAb35DE0a1b61dFBD71",
26+
"amount": "0.001"
27+
}
28+
\`\`\`
29+
30+
{{recentMessages}}
31+
32+
Given the recent messages, extract the following information about the requested token transfer:
33+
- Token contract address (if available)
34+
- Recipient wallet address
35+
- Amount to send
36+
37+
Respond with a JSON markdown block containing only the extracted values.`;
38+
39+
export default {
40+
name: "SEND_TOKEN",
41+
similes: [
42+
"TRANSFER_TOKEN_ON_QUAI",
43+
"TRANSFER_TOKENS_ON_QUAI",
44+
"SEND_TOKENS_ON_QUAI",
45+
"SEND_QUAI",
46+
"PAY_ON_QUAI",
47+
],
48+
validate: async (runtime: IAgentRuntime, message: Memory) => {
49+
return validateSettings(runtime);
50+
},
51+
description:
52+
"MUST use this action if the user requests send a token or transfer a token, the request might be varied, but it will always be a token transfer. If the user requests a transfer of lords, use this action.",
53+
handler: async (
54+
runtime: IAgentRuntime,
55+
message: Memory,
56+
state: State,
57+
_options: { [key: string]: unknown },
58+
callback?: HandlerCallback
59+
): Promise<boolean> => {
60+
console.log("Starting TRANSFER_TOKEN handler...");
61+
62+
// Initialize or update state
63+
if (!state) {
64+
state = (await runtime.composeState(message)) as State;
65+
} else {
66+
state = await runtime.updateRecentMessageState(state);
67+
}
68+
69+
// Compose transfer context
70+
const transferContext = composeContext({
71+
state,
72+
template: transferTemplate,
73+
});
74+
75+
// Generate transfer content
76+
const content = await generateObject({
77+
runtime,
78+
context: transferContext,
79+
modelClass: ModelClass.MEDIUM,
80+
});
81+
82+
console.log("Transfer content:", content);
83+
84+
// Validate transfer content
85+
if (!isTransferContent(content)) {
86+
console.error("Invalid content for TRANSFER_TOKEN action.");
87+
if (callback) {
88+
callback({
89+
text: "Not enough information to transfer tokens. Please respond with token address, recipient, and amount.",
90+
content: { error: "Invalid transfer content" },
91+
});
92+
}
93+
return false;
94+
}
95+
96+
try {
97+
const account = getQuaiAccount(runtime);
98+
const amount = formatUnits(content.amount, "wei");
99+
100+
var txObj: TransactionRequest = {};
101+
if (content.tokenAddress) {
102+
// TODO: transfer QRC20s
103+
} else {
104+
txObj = {
105+
to: content.recipient,
106+
value: amount,
107+
from: account.address,
108+
};
109+
110+
console.log(
111+
"Transferring",
112+
amount,
113+
"QUAI",
114+
"to",
115+
content.recipient
116+
);
117+
}
118+
119+
const tx = await account.sendTransaction(txObj)
120+
121+
console.log(
122+
"Transfer completed successfully! tx: " + tx.hash
123+
);
124+
if (callback) {
125+
callback({
126+
text:
127+
"Transfer completed successfully! tx: " +
128+
tx.hash,
129+
content: {},
130+
});
131+
}
132+
133+
return true;
134+
} catch (error) {
135+
console.error("Error during token transfer:", error);
136+
if (callback) {
137+
callback({
138+
text: `Error transferring tokens: ${error.message}`,
139+
content: { error: error.message },
140+
});
141+
}
142+
return false;
143+
}
144+
},
145+
146+
examples: [
147+
[
148+
{
149+
user: "{{user1}}",
150+
content: {
151+
text: "Send 10 QUAI to 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
152+
},
153+
},
154+
{
155+
user: "{{agent}}",
156+
content: {
157+
text: "I'll transfer 10 QUAI to that address right away. Let me process that for you.",
158+
},
159+
},
160+
],
161+
[
162+
{
163+
user: "{{user1}}",
164+
content: {
165+
text: "Please send 0.5 QUAI to 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
166+
},
167+
},
168+
{
169+
user: "{{agent}}",
170+
content: {
171+
text: "Got it, initiating transfer of 0.5 QUAI to the provided address. I'll confirm once it's complete.",
172+
},
173+
},
174+
],
175+
] as ActionExample[][],
176+
} as Action;

0 commit comments

Comments
 (0)