Skip to content

Commit

Permalink
Merge pull request elizaOS#2083 from 0xalank/quai-integration
Browse files Browse the repository at this point in the history
feat: add Quai integration
  • Loading branch information
wtfsayo authored Jan 11, 2025
2 parents 0d9297c + 8640152 commit 78bf0c0
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 1 deletion.
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -541,4 +541,8 @@ AKASH_MANIFEST_MODE=auto
# Default: Will use the SDL directory
AKASH_MANIFEST_PATH=
# Values: "strict" | "lenient" | "none" - Default: "strict"
AKASH_MANIFEST_VALIDATION_LEVEL=strict
AKASH_MANIFEST_VALIDATION_LEVEL=strict

# Quai Network Ecosystem
QUAI_PRIVATE_KEY=
QUAI_RPC_URL=https://rpc.quai.network
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"@elizaos/plugin-opacity": "workspace:*",
"@elizaos/plugin-hyperliquid": "workspace:*",
"@elizaos/plugin-akash": "workspace:*",
"@elizaos/plugin-quai": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
Expand Down
7 changes: 7 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import { OpacityAdapter } from "@elizaos/plugin-opacity";
import { openWeatherPlugin } from "@elizaos/plugin-open-weather";
import { stargazePlugin } from "@elizaos/plugin-stargaze";
import { akashPlugin } from "@elizaos/plugin-akash";
import { quaiPlugin } from "@elizaos/plugin-quai";
import Database from "better-sqlite3";
import fs from "fs";
import net from "net";
Expand Down Expand Up @@ -773,6 +774,12 @@ export async function createAgent(
getSecret(character, "AKASH_WALLET_ADDRESS")
? akashPlugin
: null,
getSecret(character, "QUAI_PRIVATE_KEY")
? quaiPlugin
: null,
getSecret(character, "QUAI_PRIVATE_KEY")
? quaiPlugin
: null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
22 changes: 22 additions & 0 deletions packages/plugin-quai/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@elizaos/plugin-quai",
"version": "0.0.1",
"main": "dist/index.js",
"type": "module",
"types": "dist/index.d.ts",
"dependencies": {
"@elizaos/core": "workspace:*",
"quais": "1.0.0-alpha.25",
"tsup": "^8.3.5",
"vitest": "^2.1.4",
"@avnu/avnu-sdk": "^2.1.1",
"@elizaos/plugin-trustdb": "workspace:*"
},
"scripts": {
"build": "tsup --format esm --dts",
"test": "vitest"
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
149 changes: 149 additions & 0 deletions packages/plugin-quai/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# @elizaos/plugin-quai

Quai Network integration plugin for Eliza OS that enables native token transfers and interactions with the Quai blockchain.

## Overview

This plugin provides core functionality for interacting with the Quai Network, offering native token transfer capabilities and blockchain interactions through a simple interface.

## Features

- Native QUAI token transfers
- Multiple network support
- Secure transaction signing
- Comprehensive error handling
- Built-in address validation
- Automatic gas estimation
- Real-time transaction status

## Installation

```bash
pnpm install @elizaos/plugin-quai
```

## Configuration

The plugin requires the following environment variables:

```env
QUAI_PRIVATE_KEY=your-private-key
QUAI_RPC_URL=https://rpc.quai.network # or your preferred RPC endpoint
```

## Usage

### Token Transfer

```typescript
import { quaiPlugin } from '@elizaos/plugin-quai';

// Send QUAI
const result = await eliza.execute({
action: 'SEND_TOKEN',
content: {
recipient: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7',
amount: '10'
}
});
```

## API Reference

### Actions

#### `SEND_TOKEN`
Transfers QUAI tokens to another address.

```typescript
{
action: 'SEND_TOKEN',
content: {
recipient: string, // Recipient's Quai address (42 characters, 0x prefix)
amount: string, // Amount to send (in QUAI)
tokenAddress?: string // Optional: for QRC20 tokens (not implemented yet)
}
}
```

### Providers

The plugin uses Quai's native JsonRpcProvider for blockchain interactions:

```typescript
const provider = getQuaiProvider(runtime);
// Returns configured JsonRpcProvider instance
```

## Troubleshooting

### Common Issues

1. **Transaction Failures**
- Check account balance
- Verify recipient address format
- Ensure sufficient gas
- Confirm network connection

2. **Connection Problems**
- Verify RPC endpoint
- Check network status
- Ensure valid credentials
- Monitor API availability

3. **Configuration Issues**
- Verify environment variables
- Check address format
- Confirm private key format
- Validate RPC URL

## Security Best Practices

1. **Key Management**
- Store private keys securely
- Use environment variables
- Never expose private keys in code
- Monitor account activity

2. **Transaction Safety**
- Validate all addresses
- Implement amount validation
- Double-check recipients
- Monitor transaction status

3. **Error Handling**
- Log all transaction attempts
- Handle timeouts gracefully
- Validate all user inputs
- Provide clear error messages

## Testing

Run the test suite:

```bash
pnpm test
```

## Dependencies

- quais: ^1.0.0-alpha.25
- @elizaos/core: workspace:*

## Contributing

Contributions are welcome! Please ensure your code follows the existing patterns and includes appropriate tests.

## Credits

This plugin integrates with:
- [Quai Network](https://qu.ai/)
- [Quai JavaScript API](https://www.npmjs.com/package/quais)

For more information about Quai Network capabilities:
- [Quai Documentation](https://docs.qu.ai/)
- [Quai Network GitHub](https://github.com/dominant-strategies)

## License

This plugin is part of the Eliza project. See the main project repository for license information.
176 changes: 176 additions & 0 deletions packages/plugin-quai/src/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import {
ActionExample,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
type Action,
composeContext,
generateObject,
} from "@elizaos/core";
import {
getQuaiAccount,
isTransferContent,
validateSettings,
} from "../utils";
import { formatUnits, TransactionRequest } from "quais";

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
{
"tokenAddress": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"recipient": "0x0005C06bD1339c79700a8DAb35DE0a1b61dFBD71",
"amount": "0.001"
}
\`\`\`
{{recentMessages}}
Given the recent messages, extract the following information about the requested token transfer:
- Token contract address (if available)
- Recipient wallet address
- Amount to send
Respond with a JSON markdown block containing only the extracted values.`;

export default {
name: "SEND_TOKEN",
similes: [
"TRANSFER_TOKEN_ON_QUAI",
"TRANSFER_TOKENS_ON_QUAI",
"SEND_TOKENS_ON_QUAI",
"SEND_QUAI",
"PAY_ON_QUAI",
],
validate: async (runtime: IAgentRuntime, message: Memory) => {
return validateSettings(runtime);
},
description:
"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.",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
): Promise<boolean> => {
console.log("Starting TRANSFER_TOKEN handler...");

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

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

// Generate transfer content
const content = await generateObject({
runtime,
context: transferContext,
modelClass: ModelClass.MEDIUM,
});

console.log("Transfer content:", content);

// Validate transfer content
if (!isTransferContent(content)) {
console.error("Invalid content for TRANSFER_TOKEN action.");
if (callback) {
callback({
text: "Not enough information to transfer tokens. Please respond with token address, recipient, and amount.",
content: { error: "Invalid transfer content" },
});
}
return false;
}

try {
const account = getQuaiAccount(runtime);
const amount = formatUnits(content.amount, "wei");

var txObj: TransactionRequest = {};
if (content.tokenAddress) {
// TODO: transfer QRC20s
} else {
txObj = {
to: content.recipient,
value: amount,
from: account.address,
};

console.log(
"Transferring",
amount,
"QUAI",
"to",
content.recipient
);
}

const tx = await account.sendTransaction(txObj)

console.log(
"Transfer completed successfully! tx: " + tx.hash
);
if (callback) {
callback({
text:
"Transfer completed successfully! tx: " +
tx.hash,
content: {},
});
}

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;
}
},

examples: [
[
{
user: "{{user1}}",
content: {
text: "Send 10 QUAI to 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
},
},
{
user: "{{agent}}",
content: {
text: "I'll transfer 10 QUAI to that address right away. Let me process that for you.",
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Please send 0.5 QUAI to 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
},
},
{
user: "{{agent}}",
content: {
text: "Got it, initiating transfer of 0.5 QUAI to the provided address. I'll confirm once it's complete.",
},
},
],
] as ActionExample[][],
} as Action;
Loading

0 comments on commit 78bf0c0

Please sign in to comment.