quorum-eliza-plugin 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/dist/actions/createMultisig.d.ts +13 -0
- package/dist/actions/createMultisig.js +61 -0
- package/dist/actions/createProposal.d.ts +13 -0
- package/dist/actions/createProposal.js +79 -0
- package/dist/actions/joinMultisig.d.ts +13 -0
- package/dist/actions/joinMultisig.js +49 -0
- package/dist/actions/listProposals.d.ts +13 -0
- package/dist/actions/listProposals.js +47 -0
- package/dist/actions/signProposal.d.ts +13 -0
- package/dist/actions/signProposal.js +72 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +36 -0
- package/dist/providers/multisigInfo.d.ts +5 -0
- package/dist/providers/multisigInfo.js +39 -0
- package/dist/services/quorum.d.ts +73 -0
- package/dist/services/quorum.js +179 -0
- package/package.json +38 -0
- package/src/actions/createMultisig.ts +74 -0
- package/src/actions/createProposal.ts +99 -0
- package/src/actions/joinMultisig.ts +65 -0
- package/src/actions/listProposals.ts +62 -0
- package/src/actions/signProposal.ts +92 -0
- package/src/index.ts +43 -0
- package/src/providers/multisigInfo.ts +46 -0
- package/src/services/quorum.ts +246 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# @quorum/eliza-plugin
|
|
2
|
+
|
|
3
|
+
Multi-agent wallet coordination for [Eliza](https://github.com/ai16z/eliza) agents via [Quorum](https://quorumclaw.com).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Create multi-agent wallets** - Set up m-of-n multisigs across agents
|
|
8
|
+
- 🤝 **Join existing wallets** - Accept invites from other agents
|
|
9
|
+
- ✍️ **Sign proposals** - Approve spending from shared treasuries
|
|
10
|
+
- 📋 **Track proposals** - See pending transactions needing signatures
|
|
11
|
+
- ⚡ **Auto-broadcast** - Transactions broadcast when threshold is met
|
|
12
|
+
|
|
13
|
+
## Supported Chains
|
|
14
|
+
|
|
15
|
+
- Bitcoin (Taproot multisig)
|
|
16
|
+
- Ethereum (Safe)
|
|
17
|
+
- Solana (Squads)
|
|
18
|
+
- Base
|
|
19
|
+
- Stacks
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @quorum/eliza-plugin
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { quorumPlugin } from '@quorum/eliza-plugin';
|
|
31
|
+
|
|
32
|
+
export const agent = {
|
|
33
|
+
name: 'MyAgent',
|
|
34
|
+
plugins: [quorumPlugin],
|
|
35
|
+
settings: {
|
|
36
|
+
// Your agent's private key (Schnorr-compatible)
|
|
37
|
+
QUORUM_PRIVATE_KEY: process.env.AGENT_PRIVATE_KEY,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Actions
|
|
43
|
+
|
|
44
|
+
### Create a multisig
|
|
45
|
+
```
|
|
46
|
+
"Create a 2-of-3 Bitcoin multisig called Team Treasury"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Join a multisig
|
|
50
|
+
```
|
|
51
|
+
"Join multisig with code abc12345"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### List pending proposals
|
|
55
|
+
```
|
|
56
|
+
"Show pending proposals"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Sign a proposal
|
|
60
|
+
```
|
|
61
|
+
"Sign proposal 7c34ae57-cb79-4195-a2a0-d225dd18e598"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Create a spending proposal
|
|
65
|
+
```
|
|
66
|
+
"Send 5000 sats to bc1q... from our treasury"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Environment Variables
|
|
70
|
+
|
|
71
|
+
| Variable | Description |
|
|
72
|
+
|----------|-------------|
|
|
73
|
+
| `QUORUM_PRIVATE_KEY` | Your agent's private key (hex, 32 bytes) |
|
|
74
|
+
| `QUORUM_API_URL` | API endpoint (default: https://quorumclaw.com) |
|
|
75
|
+
|
|
76
|
+
## How It Works
|
|
77
|
+
|
|
78
|
+
1. **Registration**: On startup, the plugin registers your agent with Quorum using your public key
|
|
79
|
+
2. **Coordination**: Agents create/join multisigs via invite codes
|
|
80
|
+
3. **Proposals**: Any agent can propose spending from the shared wallet
|
|
81
|
+
4. **Signing**: Each agent signs proposals independently (no MuSig2 rounds needed)
|
|
82
|
+
5. **Broadcast**: When threshold is met, the transaction auto-broadcasts
|
|
83
|
+
|
|
84
|
+
## Architecture
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
88
|
+
│ Eliza Agent A │ │ Eliza Agent B │
|
|
89
|
+
│ (plugin-quorum)│ │ (plugin-quorum)│
|
|
90
|
+
└────────┬────────┘ └────────┬────────┘
|
|
91
|
+
│ │
|
|
92
|
+
└───────────┬───────────┘
|
|
93
|
+
│
|
|
94
|
+
┌──────▼──────┐
|
|
95
|
+
│ Quorum │
|
|
96
|
+
│ API │
|
|
97
|
+
└──────┬──────┘
|
|
98
|
+
│
|
|
99
|
+
┌──────▼──────┐
|
|
100
|
+
│ Blockchain │
|
|
101
|
+
│ (Bitcoin) │
|
|
102
|
+
└─────────────┘
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Links
|
|
106
|
+
|
|
107
|
+
- [Quorum API Docs](https://quorumclaw.com/docs)
|
|
108
|
+
- [npm: quorum-sdk](https://www.npmjs.com/package/quorum-sdk)
|
|
109
|
+
- [GitHub](https://github.com/aetos53t/agent-multisig-api)
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const createMultisigAction: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
similes: string[];
|
|
5
|
+
examples: {
|
|
6
|
+
user: string;
|
|
7
|
+
content: {
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
}[][];
|
|
11
|
+
validate: (runtime: any, message: any) => Promise<any>;
|
|
12
|
+
handler: (runtime: any, message: any, state: any, options: Record<string, unknown>, callback?: any) => Promise<any>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { quorumService } from '../services/quorum.js';
|
|
2
|
+
export const createMultisigAction = {
|
|
3
|
+
name: 'QUORUM_CREATE_MULTISIG',
|
|
4
|
+
description: 'Create a new multi-agent wallet via Quorum',
|
|
5
|
+
similes: [
|
|
6
|
+
'create multisig',
|
|
7
|
+
'create multi-agent wallet',
|
|
8
|
+
'setup shared wallet',
|
|
9
|
+
'create treasury',
|
|
10
|
+
'create quorum wallet',
|
|
11
|
+
],
|
|
12
|
+
examples: [
|
|
13
|
+
[
|
|
14
|
+
{ user: '{{user1}}', content: { text: 'Create a 2-of-3 Bitcoin multisig called "Team Treasury"' } },
|
|
15
|
+
{ user: '{{agent}}', content: { text: 'Created multisig "Team Treasury" (2-of-3). Invite code: abc123. Share this with other signers to join.' } },
|
|
16
|
+
],
|
|
17
|
+
],
|
|
18
|
+
validate: async (runtime, message) => {
|
|
19
|
+
const text = message.content?.text?.toLowerCase() || '';
|
|
20
|
+
return text.includes('create') && (text.includes('multisig') || text.includes('wallet') || text.includes('treasury'));
|
|
21
|
+
},
|
|
22
|
+
handler: async (runtime, message, state, options, callback) => {
|
|
23
|
+
try {
|
|
24
|
+
const text = message.content?.text || '';
|
|
25
|
+
// Parse parameters from message
|
|
26
|
+
const thresholdMatch = text.match(/(\d+)[- ]of[- ](\d+)/i);
|
|
27
|
+
const threshold = thresholdMatch ? parseInt(thresholdMatch[1]) : 2;
|
|
28
|
+
const totalSigners = thresholdMatch ? parseInt(thresholdMatch[2]) : 3;
|
|
29
|
+
const nameMatch = text.match(/(?:called|named)\s+["']?([^"']+)["']?/i);
|
|
30
|
+
const name = nameMatch ? nameMatch[1].trim() : `Multisig ${Date.now()}`;
|
|
31
|
+
// Detect chain
|
|
32
|
+
let chainId = 'bitcoin-mainnet';
|
|
33
|
+
if (text.includes('ethereum') || text.includes('eth'))
|
|
34
|
+
chainId = 'ethereum';
|
|
35
|
+
if (text.includes('solana') || text.includes('sol'))
|
|
36
|
+
chainId = 'solana-mainnet';
|
|
37
|
+
if (text.includes('base'))
|
|
38
|
+
chainId = 'base';
|
|
39
|
+
if (text.includes('stacks') || text.includes('stx'))
|
|
40
|
+
chainId = 'stacks-mainnet';
|
|
41
|
+
const result = await quorumService.createMultisig({
|
|
42
|
+
name,
|
|
43
|
+
chainId,
|
|
44
|
+
threshold,
|
|
45
|
+
totalSigners,
|
|
46
|
+
});
|
|
47
|
+
const response = `✅ Created multisig "${name}" (${threshold}-of-${totalSigners} on ${chainId})
|
|
48
|
+
|
|
49
|
+
**Invite Code:** \`${result.inviteCode}\`
|
|
50
|
+
**Join Link:** https://quorumclaw.com/join/${result.inviteCode}
|
|
51
|
+
|
|
52
|
+
Share this with other signers to join the wallet.`;
|
|
53
|
+
callback?.({ text: response });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
callback?.({ text: `❌ Failed to create multisig: ${err.message}` });
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const createProposalAction: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
similes: string[];
|
|
5
|
+
examples: {
|
|
6
|
+
user: string;
|
|
7
|
+
content: {
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
}[][];
|
|
11
|
+
validate: (runtime: any, message: any) => Promise<any>;
|
|
12
|
+
handler: (runtime: any, message: any, state: any, options: Record<string, unknown>, callback?: any) => Promise<any>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { quorumService } from '../services/quorum.js';
|
|
2
|
+
export const createProposalAction = {
|
|
3
|
+
name: 'QUORUM_CREATE_PROPOSAL',
|
|
4
|
+
description: 'Create a new spending proposal in a multi-agent wallet',
|
|
5
|
+
similes: [
|
|
6
|
+
'send from multisig',
|
|
7
|
+
'create proposal',
|
|
8
|
+
'propose spend',
|
|
9
|
+
'propose transaction',
|
|
10
|
+
'send from treasury',
|
|
11
|
+
],
|
|
12
|
+
examples: [
|
|
13
|
+
[
|
|
14
|
+
{ user: '{{user1}}', content: { text: 'Send 5000 sats to bc1q... from our treasury' } },
|
|
15
|
+
{ user: '{{agent}}', content: { text: 'Created proposal! ID: abc123. Waiting for 2 more signatures.' } },
|
|
16
|
+
],
|
|
17
|
+
],
|
|
18
|
+
validate: async (runtime, message) => {
|
|
19
|
+
const text = message.content?.text?.toLowerCase() || '';
|
|
20
|
+
return (text.includes('send') || text.includes('propose') || text.includes('transfer')) &&
|
|
21
|
+
(text.includes('multisig') || text.includes('treasury') || text.includes('proposal') || text.includes('sats'));
|
|
22
|
+
},
|
|
23
|
+
handler: async (runtime, message, state, options, callback) => {
|
|
24
|
+
try {
|
|
25
|
+
const text = message.content?.text || '';
|
|
26
|
+
// Parse amount
|
|
27
|
+
const amountMatch = text.match(/(\d+(?:,\d+)?)\s*(?:sats?|satoshis?)/i);
|
|
28
|
+
if (!amountMatch) {
|
|
29
|
+
callback?.({ text: '❌ Please specify an amount in sats. Example: "Send 5000 sats to bc1q..."' });
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const amount = parseInt(amountMatch[1].replace(/,/g, ''));
|
|
33
|
+
// Parse recipient address
|
|
34
|
+
const addressMatch = text.match(/(bc1[a-z0-9]{39,87})/i) || // Bech32
|
|
35
|
+
text.match(/(tb1[a-z0-9]{39,87})/i) || // Testnet
|
|
36
|
+
text.match(/(0x[a-fA-F0-9]{40})/i); // EVM
|
|
37
|
+
if (!addressMatch) {
|
|
38
|
+
callback?.({ text: '❌ Please provide a recipient address. Example: "Send 5000 sats to bc1q..."' });
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const recipient = addressMatch[1];
|
|
42
|
+
// Get multisigs
|
|
43
|
+
const multisigs = await quorumService.listMultisigs();
|
|
44
|
+
if (multisigs.length === 0) {
|
|
45
|
+
callback?.({ text: '❌ You are not part of any multisigs. Create or join one first.' });
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
// Use first multisig or parse from message
|
|
49
|
+
// TODO: Allow specifying which multisig
|
|
50
|
+
const multisig = multisigs[0];
|
|
51
|
+
// Parse note
|
|
52
|
+
const noteMatch = text.match(/(?:note|memo|for|reason)[:\s]+["']?([^"']+)["']?/i);
|
|
53
|
+
const note = noteMatch ? noteMatch[1].trim() : undefined;
|
|
54
|
+
const proposal = await quorumService.createProposal({
|
|
55
|
+
multisigId: multisig.id,
|
|
56
|
+
recipient,
|
|
57
|
+
amount,
|
|
58
|
+
note,
|
|
59
|
+
});
|
|
60
|
+
callback?.({
|
|
61
|
+
text: `✅ **Proposal Created**
|
|
62
|
+
|
|
63
|
+
**ID:** \`${proposal.id}\`
|
|
64
|
+
**Amount:** ${amount.toLocaleString()} sats
|
|
65
|
+
**To:** ${recipient}
|
|
66
|
+
**From:** ${multisig.name}
|
|
67
|
+
|
|
68
|
+
Proposal needs ${multisig.threshold} signatures. Share the proposal ID with other signers.
|
|
69
|
+
|
|
70
|
+
https://quorumclaw.com/p/${proposal.id}`
|
|
71
|
+
});
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
callback?.({ text: `❌ Failed to create proposal: ${err.message}` });
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const joinMultisigAction: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
similes: string[];
|
|
5
|
+
examples: {
|
|
6
|
+
user: string;
|
|
7
|
+
content: {
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
}[][];
|
|
11
|
+
validate: (runtime: any, message: any) => Promise<any>;
|
|
12
|
+
handler: (runtime: any, message: any, state: any, options: Record<string, unknown>, callback?: any) => Promise<any>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { quorumService } from '../services/quorum.js';
|
|
2
|
+
export const joinMultisigAction = {
|
|
3
|
+
name: 'QUORUM_JOIN_MULTISIG',
|
|
4
|
+
description: 'Join an existing multi-agent wallet via invite code',
|
|
5
|
+
similes: [
|
|
6
|
+
'join multisig',
|
|
7
|
+
'join wallet',
|
|
8
|
+
'accept invite',
|
|
9
|
+
'join treasury',
|
|
10
|
+
],
|
|
11
|
+
examples: [
|
|
12
|
+
[
|
|
13
|
+
{ user: '{{user1}}', content: { text: 'Join multisig with code abc123' } },
|
|
14
|
+
{ user: '{{agent}}', content: { text: 'Joined multisig "Team Treasury"! Address: bc1p...' } },
|
|
15
|
+
],
|
|
16
|
+
],
|
|
17
|
+
validate: async (runtime, message) => {
|
|
18
|
+
const text = message.content?.text?.toLowerCase() || '';
|
|
19
|
+
return text.includes('join') && (text.includes('multisig') || text.includes('wallet') || text.includes('code'));
|
|
20
|
+
},
|
|
21
|
+
handler: async (runtime, message, state, options, callback) => {
|
|
22
|
+
try {
|
|
23
|
+
const text = message.content?.text || '';
|
|
24
|
+
// Extract invite code
|
|
25
|
+
const codeMatch = text.match(/(?:code|invite)?\s*([a-f0-9]{8})/i) ||
|
|
26
|
+
text.match(/join\/([a-f0-9]{8})/i);
|
|
27
|
+
if (!codeMatch) {
|
|
28
|
+
callback?.({ text: '❌ Please provide an invite code. Example: "Join multisig with code abc12345"' });
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const inviteCode = codeMatch[1];
|
|
32
|
+
const multisig = await quorumService.joinMultisig(inviteCode);
|
|
33
|
+
const response = `✅ Joined multisig "${multisig.name}"!
|
|
34
|
+
|
|
35
|
+
**Address:** \`${multisig.address}\`
|
|
36
|
+
**Threshold:** ${multisig.threshold}-of-${multisig.agents.length}
|
|
37
|
+
**Chain:** ${multisig.chainId}
|
|
38
|
+
**Signers:** ${multisig.agents.map(a => a.name).join(', ')}
|
|
39
|
+
|
|
40
|
+
The wallet is ready to receive funds.`;
|
|
41
|
+
callback?.({ text: response });
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
callback?.({ text: `❌ Failed to join multisig: ${err.message}` });
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const listProposalsAction: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
similes: string[];
|
|
5
|
+
examples: {
|
|
6
|
+
user: string;
|
|
7
|
+
content: {
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
}[][];
|
|
11
|
+
validate: (runtime: any, message: any) => Promise<any>;
|
|
12
|
+
handler: (runtime: any, message: any, state: any, options: Record<string, unknown>, callback?: any) => Promise<any>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { quorumService } from '../services/quorum.js';
|
|
2
|
+
export const listProposalsAction = {
|
|
3
|
+
name: 'QUORUM_LIST_PROPOSALS',
|
|
4
|
+
description: 'List pending proposals across all multi-agent wallets',
|
|
5
|
+
similes: [
|
|
6
|
+
'list proposals',
|
|
7
|
+
'show proposals',
|
|
8
|
+
'pending transactions',
|
|
9
|
+
'what needs signing',
|
|
10
|
+
'check proposals',
|
|
11
|
+
],
|
|
12
|
+
examples: [
|
|
13
|
+
[
|
|
14
|
+
{ user: '{{user1}}', content: { text: 'Show pending proposals' } },
|
|
15
|
+
{ user: '{{agent}}', content: { text: 'You have 2 pending proposals:\n- 5000 sats to bc1q... (1/2 sigs)\n- 10000 sats to bc1p... (0/3 sigs)' } },
|
|
16
|
+
],
|
|
17
|
+
],
|
|
18
|
+
validate: async (runtime, message) => {
|
|
19
|
+
const text = message.content?.text?.toLowerCase() || '';
|
|
20
|
+
return (text.includes('list') || text.includes('show') || text.includes('pending') || text.includes('check')) &&
|
|
21
|
+
(text.includes('proposal') || text.includes('transaction') || text.includes('signing'));
|
|
22
|
+
},
|
|
23
|
+
handler: async (runtime, message, state, options, callback) => {
|
|
24
|
+
try {
|
|
25
|
+
const proposals = await quorumService.listPendingProposals();
|
|
26
|
+
if (proposals.length === 0) {
|
|
27
|
+
callback?.({ text: '✅ No pending proposals. All caught up!' });
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const list = proposals.map(p => {
|
|
31
|
+
const amount = p.outputs.reduce((sum, o) => sum + parseInt(o.amount), 0);
|
|
32
|
+
const recipient = p.outputs[0]?.address || 'unknown';
|
|
33
|
+
const shortRecipient = `${recipient.slice(0, 8)}...${recipient.slice(-6)}`;
|
|
34
|
+
return `• **${amount.toLocaleString()} sats** → ${shortRecipient}
|
|
35
|
+
ID: \`${p.id.slice(0, 8)}...\` | Sigs: ${p.signatures.length}/? | ${p.note || 'No note'}`;
|
|
36
|
+
}).join('\n\n');
|
|
37
|
+
callback?.({
|
|
38
|
+
text: `📋 **Pending Proposals (${proposals.length})**\n\n${list}\n\nSay "sign proposal <id>" to approve.`
|
|
39
|
+
});
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
callback?.({ text: `❌ Failed to list proposals: ${err.message}` });
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const signProposalAction: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
similes: string[];
|
|
5
|
+
examples: {
|
|
6
|
+
user: string;
|
|
7
|
+
content: {
|
|
8
|
+
text: string;
|
|
9
|
+
};
|
|
10
|
+
}[][];
|
|
11
|
+
validate: (runtime: any, message: any) => Promise<any>;
|
|
12
|
+
handler: (runtime: any, message: any, state: any, options: Record<string, unknown>, callback?: any) => Promise<any>;
|
|
13
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { quorumService } from '../services/quorum.js';
|
|
2
|
+
export const signProposalAction = {
|
|
3
|
+
name: 'QUORUM_SIGN_PROPOSAL',
|
|
4
|
+
description: 'Sign a pending proposal in a multi-agent wallet',
|
|
5
|
+
similes: [
|
|
6
|
+
'sign proposal',
|
|
7
|
+
'approve transaction',
|
|
8
|
+
'sign tx',
|
|
9
|
+
'approve proposal',
|
|
10
|
+
'co-sign',
|
|
11
|
+
],
|
|
12
|
+
examples: [
|
|
13
|
+
[
|
|
14
|
+
{ user: '{{user1}}', content: { text: 'Sign proposal abc123' } },
|
|
15
|
+
{ user: '{{agent}}', content: { text: 'Signed! 2/3 signatures collected. Waiting for one more signer.' } },
|
|
16
|
+
],
|
|
17
|
+
],
|
|
18
|
+
validate: async (runtime, message) => {
|
|
19
|
+
const text = message.content?.text?.toLowerCase() || '';
|
|
20
|
+
return (text.includes('sign') || text.includes('approve')) &&
|
|
21
|
+
(text.includes('proposal') || text.includes('transaction') || text.includes('tx'));
|
|
22
|
+
},
|
|
23
|
+
handler: async (runtime, message, state, options, callback) => {
|
|
24
|
+
try {
|
|
25
|
+
const text = message.content?.text || '';
|
|
26
|
+
// Extract proposal ID (UUID format)
|
|
27
|
+
const idMatch = text.match(/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i) ||
|
|
28
|
+
text.match(/proposal\s+([a-f0-9-]+)/i);
|
|
29
|
+
if (!idMatch) {
|
|
30
|
+
// Try to find pending proposals
|
|
31
|
+
const pending = await quorumService.listPendingProposals();
|
|
32
|
+
if (pending.length === 0) {
|
|
33
|
+
callback?.({ text: '❌ No pending proposals found. Provide a proposal ID or create a new proposal.' });
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (pending.length === 1) {
|
|
37
|
+
// Auto-sign the only pending proposal
|
|
38
|
+
const result = await quorumService.signProposal(pending[0].id);
|
|
39
|
+
if (result.txid) {
|
|
40
|
+
callback?.({ text: `✅ Signed and broadcast! txid: ${result.txid}\n\nhttps://mempool.space/tx/${result.txid}` });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
callback?.({ text: `✅ Signed! Proposal status: ${result.status}. Waiting for more signatures.` });
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
// List pending proposals
|
|
48
|
+
const list = pending.map(p => `- \`${p.id.slice(0, 8)}...\`: ${p.outputs.map(o => `${o.amount} sats`).join(', ')} (${p.signatures.length} sigs)`).join('\n');
|
|
49
|
+
callback?.({ text: `Multiple pending proposals. Please specify which one:\n\n${list}` });
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const proposalId = idMatch[1];
|
|
53
|
+
const result = await quorumService.signProposal(proposalId);
|
|
54
|
+
if (result.txid) {
|
|
55
|
+
callback?.({
|
|
56
|
+
text: `✅ **Threshold met! Transaction broadcast.**\n\n**txid:** \`${result.txid}\`\n\nhttps://mempool.space/tx/${result.txid}`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const proposal = await quorumService.getProposal(proposalId);
|
|
61
|
+
callback?.({
|
|
62
|
+
text: `✅ Signed! ${proposal.signatures.length}/${proposal.sighashes.length + 1} signatures collected. Waiting for more signers.`
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
callback?.({ text: `❌ Failed to sign: ${err.message}` });
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quorum Plugin for Eliza
|
|
3
|
+
*
|
|
4
|
+
* Enables any Eliza agent to participate in multi-agent wallets via Quorum.
|
|
5
|
+
*/
|
|
6
|
+
export declare const quorumPlugin: {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
init(runtime: any): Promise<void>;
|
|
10
|
+
actions: {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
similes: string[];
|
|
14
|
+
examples: {
|
|
15
|
+
user: string;
|
|
16
|
+
content: {
|
|
17
|
+
text: string;
|
|
18
|
+
};
|
|
19
|
+
}[][];
|
|
20
|
+
validate: (runtime: any, message: any) => Promise<any>;
|
|
21
|
+
handler: (runtime: any, message: any, state: any, options: Record<string, unknown>, callback?: any) => Promise<any>;
|
|
22
|
+
}[];
|
|
23
|
+
providers: {
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
get: (runtime: any, message: any, state?: any) => Promise<any>;
|
|
27
|
+
}[];
|
|
28
|
+
};
|
|
29
|
+
export default quorumPlugin;
|
|
30
|
+
export { quorumService } from './services/quorum.js';
|
|
31
|
+
export * from './actions/createMultisig.js';
|
|
32
|
+
export * from './actions/joinMultisig.js';
|
|
33
|
+
export * from './actions/signProposal.js';
|
|
34
|
+
export * from './actions/listProposals.js';
|
|
35
|
+
export * from './actions/createProposal.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quorum Plugin for Eliza
|
|
3
|
+
*
|
|
4
|
+
* Enables any Eliza agent to participate in multi-agent wallets via Quorum.
|
|
5
|
+
*/
|
|
6
|
+
import { quorumService } from './services/quorum.js';
|
|
7
|
+
import { createMultisigAction } from './actions/createMultisig.js';
|
|
8
|
+
import { joinMultisigAction } from './actions/joinMultisig.js';
|
|
9
|
+
import { signProposalAction } from './actions/signProposal.js';
|
|
10
|
+
import { listProposalsAction } from './actions/listProposals.js';
|
|
11
|
+
import { createProposalAction } from './actions/createProposal.js';
|
|
12
|
+
import { multisigProvider } from './providers/multisigInfo.js';
|
|
13
|
+
export const quorumPlugin = {
|
|
14
|
+
name: 'quorum',
|
|
15
|
+
description: 'Multi-agent wallet coordination via Quorum',
|
|
16
|
+
// Initialize on plugin load
|
|
17
|
+
async init(runtime) {
|
|
18
|
+
await quorumService.initialize(runtime);
|
|
19
|
+
},
|
|
20
|
+
actions: [
|
|
21
|
+
createMultisigAction,
|
|
22
|
+
joinMultisigAction,
|
|
23
|
+
signProposalAction,
|
|
24
|
+
listProposalsAction,
|
|
25
|
+
createProposalAction,
|
|
26
|
+
],
|
|
27
|
+
providers: [multisigProvider],
|
|
28
|
+
};
|
|
29
|
+
export default quorumPlugin;
|
|
30
|
+
// Re-export for convenience
|
|
31
|
+
export { quorumService } from './services/quorum.js';
|
|
32
|
+
export * from './actions/createMultisig.js';
|
|
33
|
+
export * from './actions/joinMultisig.js';
|
|
34
|
+
export * from './actions/signProposal.js';
|
|
35
|
+
export * from './actions/listProposals.js';
|
|
36
|
+
export * from './actions/createProposal.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { quorumService } from '../services/quorum.js';
|
|
2
|
+
export const multisigProvider = {
|
|
3
|
+
name: 'QUORUM_MULTISIG_INFO',
|
|
4
|
+
description: 'Provides context about multi-agent wallets and pending proposals',
|
|
5
|
+
get: async (runtime, message, state) => {
|
|
6
|
+
try {
|
|
7
|
+
const agentId = quorumService.getAgentId();
|
|
8
|
+
if (!agentId) {
|
|
9
|
+
return ''; // Not initialized
|
|
10
|
+
}
|
|
11
|
+
const multisigs = await quorumService.listMultisigs();
|
|
12
|
+
const proposals = await quorumService.listPendingProposals();
|
|
13
|
+
if (multisigs.length === 0 && proposals.length === 0) {
|
|
14
|
+
return ''; // No relevant context
|
|
15
|
+
}
|
|
16
|
+
let context = '## Quorum Multi-Agent Wallets\n\n';
|
|
17
|
+
if (multisigs.length > 0) {
|
|
18
|
+
context += '### My Wallets\n';
|
|
19
|
+
for (const ms of multisigs) {
|
|
20
|
+
context += `- **${ms.name}**: ${ms.address.slice(0, 12)}... (${ms.threshold}-of-${ms.agents.length} on ${ms.chainId})\n`;
|
|
21
|
+
}
|
|
22
|
+
context += '\n';
|
|
23
|
+
}
|
|
24
|
+
if (proposals.length > 0) {
|
|
25
|
+
context += '### ⚠️ Pending Proposals Requiring Signature\n';
|
|
26
|
+
for (const p of proposals) {
|
|
27
|
+
const amount = p.outputs.reduce((sum, o) => sum + parseInt(o.amount), 0);
|
|
28
|
+
context += `- **${amount} sats** to ${p.outputs[0]?.address.slice(0, 12)}... [${p.signatures.length} sigs] - ID: ${p.id.slice(0, 8)}...\n`;
|
|
29
|
+
}
|
|
30
|
+
context += '\nUse "sign proposal <id>" to approve these transactions.\n';
|
|
31
|
+
}
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.error('[Quorum Provider] Error:', err);
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|