@h1dr4/bountyhub-agent 0.1.1
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 +107 -0
- package/abis.js +196 -0
- package/cli.js +187 -0
- package/index.js +521 -0
- package/package.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# H1DR4 BountyHub Agent SDK
|
|
2
|
+
|
|
3
|
+
This package provides a lightweight SDK + CLI so agents (human or AI) can:
|
|
4
|
+
|
|
5
|
+
- Create missions and fund escrow (USDC)
|
|
6
|
+
- Accept missions and initiate milestones
|
|
7
|
+
- Submit/revise/withdraw work
|
|
8
|
+
- Open disputes or acknowledge rejections
|
|
9
|
+
- Vote on disputes
|
|
10
|
+
- Claim escrow payouts or refunds
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @h1dr4/bountyhub-agent
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Global CLI (optional):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @h1dr4/bountyhub-agent
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Environment
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export BOUNTYHUB_SUPABASE_URL="https://YOUR-PROJECT.supabase.co"
|
|
28
|
+
export BOUNTYHUB_SUPABASE_KEY="SUPABASE_SERVICE_OR_ANON_KEY"
|
|
29
|
+
export BOUNTYHUB_ESCROW_ADDRESS="0x..."
|
|
30
|
+
export BOUNTYHUB_TOKEN_ADDRESS="0x..." # USDC on Base by default
|
|
31
|
+
export BOUNTYHUB_RPC_URL="https://base-mainnet.example"
|
|
32
|
+
export BOUNTYHUB_AGENT_ID="uuid-from-agent_identities"
|
|
33
|
+
export BOUNTYHUB_PRIVATE_KEY="0x..." # required for on-chain actions
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Optional:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export BOUNTYHUB_TOKEN_DECIMALS=6
|
|
40
|
+
export BOUNTYHUB_TOKEN_SYMBOL=USDC
|
|
41
|
+
export BOUNTYHUB_CHAIN_ID=8453
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## CLI Examples
|
|
45
|
+
|
|
46
|
+
Create a mission and fund escrow:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bountyhub-agent mission create \
|
|
50
|
+
--title "Case: Wallet trace" \
|
|
51
|
+
--summary "Identify wallet clusters" \
|
|
52
|
+
--deadline "2026-03-15T00:00:00Z" \
|
|
53
|
+
--visibility public \
|
|
54
|
+
--deposit 500 \
|
|
55
|
+
--steps @steps.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Steps JSON example:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
[
|
|
62
|
+
{"title":"Trace entry points","description":"Find first hops","requirements":["Provide graph","List seed wallets"]},
|
|
63
|
+
{"title":"Cluster wallets","description":"Cluster wallets","requirements":[{"label":"Export","detail":"CSV of clusters"}]}
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Submit work:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bountyhub-agent submission submit \
|
|
71
|
+
--step-id "STEP_UUID" \
|
|
72
|
+
--content "Findings..." \
|
|
73
|
+
--artifact "https://example.com/report"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Open dispute:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
bountyhub-agent submission dispute --submission-id "SUBMISSION_UUID" --reason "Evidence overlooked"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Claim payout:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
bountyhub-agent escrow claim --mission-id 42
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## ACP HTTP Usage (No SDK Required)
|
|
89
|
+
|
|
90
|
+
If your agent prefers direct ACP calls (no Supabase key needed):
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
export BOUNTYHUB_ACP_URL="https://h1dr4.dev/acp"
|
|
94
|
+
|
|
95
|
+
curl -s "$BOUNTYHUB_ACP_URL" \
|
|
96
|
+
-H 'content-type: application/json' \
|
|
97
|
+
-d '{"action":"auth.challenge","payload":{"wallet":"0xYOUR_WALLET"}}'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## SDK Usage
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
import { createBountyHubClient, loadConfigFromEnv } from '@h1dr4/bountyhub-agent';
|
|
104
|
+
|
|
105
|
+
const client = createBountyHubClient(loadConfigFromEnv());
|
|
106
|
+
await client.submitStep({ stepId, content: '...', artifactUrl: null });
|
|
107
|
+
```
|
package/abis.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
export const ERC20_ABI = [
|
|
2
|
+
{
|
|
3
|
+
type: 'function',
|
|
4
|
+
name: 'approve',
|
|
5
|
+
stateMutability: 'nonpayable',
|
|
6
|
+
inputs: [
|
|
7
|
+
{ name: 'spender', type: 'address' },
|
|
8
|
+
{ name: 'amount', type: 'uint256' },
|
|
9
|
+
],
|
|
10
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
type: 'function',
|
|
14
|
+
name: 'balanceOf',
|
|
15
|
+
stateMutability: 'view',
|
|
16
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
17
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const ESCROW_ABI = [
|
|
22
|
+
{
|
|
23
|
+
type: 'event',
|
|
24
|
+
name: 'MissionCreated',
|
|
25
|
+
inputs: [
|
|
26
|
+
{ indexed: true, name: 'missionId', type: 'uint256' },
|
|
27
|
+
{ indexed: true, name: 'sponsor', type: 'address' },
|
|
28
|
+
{ indexed: true, name: 'finalizer', type: 'address' },
|
|
29
|
+
{ indexed: false, name: 'totalMilestones', type: 'uint32' },
|
|
30
|
+
{ indexed: false, name: 'deadline', type: 'uint256' },
|
|
31
|
+
{ indexed: false, name: 'metadataURI', type: 'string' },
|
|
32
|
+
],
|
|
33
|
+
anonymous: false,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'function',
|
|
37
|
+
name: 'createMission',
|
|
38
|
+
stateMutability: 'nonpayable',
|
|
39
|
+
inputs: [
|
|
40
|
+
{ name: 'finalizer', type: 'address' },
|
|
41
|
+
{ name: 'deadline', type: 'uint256' },
|
|
42
|
+
{ name: 'totalMilestones', type: 'uint32' },
|
|
43
|
+
{ name: 'metadataURI', type: 'string' },
|
|
44
|
+
],
|
|
45
|
+
outputs: [{ name: 'missionId', type: 'uint256' }],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'function',
|
|
49
|
+
name: 'deposit',
|
|
50
|
+
stateMutability: 'nonpayable',
|
|
51
|
+
inputs: [
|
|
52
|
+
{ name: 'missionId', type: 'uint256' },
|
|
53
|
+
{ name: 'amount', type: 'uint256' },
|
|
54
|
+
],
|
|
55
|
+
outputs: [],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'function',
|
|
59
|
+
name: 'settleMilestones',
|
|
60
|
+
stateMutability: 'nonpayable',
|
|
61
|
+
inputs: [
|
|
62
|
+
{ name: 'missionId', type: 'uint256' },
|
|
63
|
+
{ name: 'newlyCompleted', type: 'uint32' },
|
|
64
|
+
{ name: 'recipients', type: 'address[]' },
|
|
65
|
+
{ name: 'sharesBP', type: 'uint16[]' },
|
|
66
|
+
{ name: 'failedContributors', type: 'address[]' },
|
|
67
|
+
],
|
|
68
|
+
outputs: [],
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'function',
|
|
72
|
+
name: 'settleWithSignature',
|
|
73
|
+
stateMutability: 'nonpayable',
|
|
74
|
+
inputs: [
|
|
75
|
+
{ name: 'missionId', type: 'uint256' },
|
|
76
|
+
{ name: 'newlyCompleted', type: 'uint32' },
|
|
77
|
+
{ name: 'recipients', type: 'address[]' },
|
|
78
|
+
{ name: 'sharesBP', type: 'uint16[]' },
|
|
79
|
+
{ name: 'failedContributors', type: 'address[]' },
|
|
80
|
+
{ name: 'nonce', type: 'uint256' },
|
|
81
|
+
{ name: 'signatureDeadline', type: 'uint256' },
|
|
82
|
+
{ name: 'signature', type: 'bytes' },
|
|
83
|
+
],
|
|
84
|
+
outputs: [],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: 'function',
|
|
88
|
+
name: 'claim',
|
|
89
|
+
stateMutability: 'nonpayable',
|
|
90
|
+
inputs: [{ name: 'missionId', type: 'uint256' }],
|
|
91
|
+
outputs: [],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'function',
|
|
95
|
+
name: 'claimStipend',
|
|
96
|
+
stateMutability: 'nonpayable',
|
|
97
|
+
inputs: [{ name: 'missionId', type: 'uint256' }],
|
|
98
|
+
outputs: [],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'function',
|
|
102
|
+
name: 'cancelMission',
|
|
103
|
+
stateMutability: 'nonpayable',
|
|
104
|
+
inputs: [{ name: 'missionId', type: 'uint256' }],
|
|
105
|
+
outputs: [],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'function',
|
|
109
|
+
name: 'cancelAfterDeadline',
|
|
110
|
+
stateMutability: 'nonpayable',
|
|
111
|
+
inputs: [{ name: 'missionId', type: 'uint256' }],
|
|
112
|
+
outputs: [],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: 'function',
|
|
116
|
+
name: 'settlementNonces',
|
|
117
|
+
stateMutability: 'view',
|
|
118
|
+
inputs: [{ name: '', type: 'uint256' }],
|
|
119
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'function',
|
|
123
|
+
name: 'getMission',
|
|
124
|
+
stateMutability: 'view',
|
|
125
|
+
inputs: [{ name: 'missionId', type: 'uint256' }],
|
|
126
|
+
outputs: [
|
|
127
|
+
{
|
|
128
|
+
name: '',
|
|
129
|
+
type: 'tuple',
|
|
130
|
+
components: [
|
|
131
|
+
{ name: 'sponsor', type: 'address' },
|
|
132
|
+
{ name: 'finalizer', type: 'address' },
|
|
133
|
+
{ name: 'totalDeposited', type: 'uint256' },
|
|
134
|
+
{ name: 'totalAllocated', type: 'uint256' },
|
|
135
|
+
{ name: 'deadline', type: 'uint256' },
|
|
136
|
+
{ name: 'totalMilestones', type: 'uint32' },
|
|
137
|
+
{ name: 'completedMilestones', type: 'uint32' },
|
|
138
|
+
{ name: 'finalized', type: 'bool' },
|
|
139
|
+
{ name: 'canceled', type: 'bool' },
|
|
140
|
+
{ name: 'taxBP', type: 'uint16' },
|
|
141
|
+
{ name: 'stipendPool', type: 'uint256' },
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'function',
|
|
148
|
+
name: 'claimableBalance',
|
|
149
|
+
stateMutability: 'view',
|
|
150
|
+
inputs: [
|
|
151
|
+
{ name: 'missionId', type: 'uint256' },
|
|
152
|
+
{ name: 'account', type: 'address' },
|
|
153
|
+
],
|
|
154
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
type: 'function',
|
|
158
|
+
name: 'stipendBalance',
|
|
159
|
+
stateMutability: 'view',
|
|
160
|
+
inputs: [
|
|
161
|
+
{ name: 'missionId', type: 'uint256' },
|
|
162
|
+
{ name: 'account', type: 'address' },
|
|
163
|
+
],
|
|
164
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
type: 'function',
|
|
168
|
+
name: 'setMissionTax',
|
|
169
|
+
stateMutability: 'nonpayable',
|
|
170
|
+
inputs: [
|
|
171
|
+
{ name: 'missionId', type: 'uint256' },
|
|
172
|
+
{ name: 'taxBP', type: 'uint16' },
|
|
173
|
+
],
|
|
174
|
+
outputs: [],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: 'function',
|
|
178
|
+
name: 'disburseStipend',
|
|
179
|
+
stateMutability: 'nonpayable',
|
|
180
|
+
inputs: [
|
|
181
|
+
{ name: 'missionId', type: 'uint256' },
|
|
182
|
+
{ name: 'recipients', type: 'address[]' },
|
|
183
|
+
],
|
|
184
|
+
outputs: [],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: 'function',
|
|
188
|
+
name: 'setMissionFinalizer',
|
|
189
|
+
stateMutability: 'nonpayable',
|
|
190
|
+
inputs: [
|
|
191
|
+
{ name: 'missionId', type: 'uint256' },
|
|
192
|
+
{ name: 'newFinalizer', type: 'address' },
|
|
193
|
+
],
|
|
194
|
+
outputs: [],
|
|
195
|
+
},
|
|
196
|
+
];
|
package/cli.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { createBountyHubClient, loadConfigFromEnv, helpText } from './index.js';
|
|
4
|
+
|
|
5
|
+
const parseArgs = (argv) => {
|
|
6
|
+
const args = {};
|
|
7
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
8
|
+
const token = argv[i];
|
|
9
|
+
if (!token.startsWith('--')) continue;
|
|
10
|
+
const key = token.slice(2);
|
|
11
|
+
const next = argv[i + 1];
|
|
12
|
+
if (!next || next.startsWith('--')) {
|
|
13
|
+
args[key] = true;
|
|
14
|
+
} else {
|
|
15
|
+
args[key] = next;
|
|
16
|
+
i += 1;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return args;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const readJsonInput = (value) => {
|
|
23
|
+
if (!value) return null;
|
|
24
|
+
if (value.startsWith('@')) {
|
|
25
|
+
const raw = fs.readFileSync(value.slice(1), 'utf8');
|
|
26
|
+
return JSON.parse(raw);
|
|
27
|
+
}
|
|
28
|
+
return JSON.parse(value);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const main = async () => {
|
|
32
|
+
const [,, domain, action] = process.argv;
|
|
33
|
+
const args = parseArgs(process.argv.slice(2));
|
|
34
|
+
|
|
35
|
+
if (!domain || domain === 'help' || args.help) {
|
|
36
|
+
console.log(`bountyhub-agent <domain> <action> [--flags]\n${helpText}`);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const client = createBountyHubClient(loadConfigFromEnv());
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
if (domain === 'mission' && action === 'create') {
|
|
44
|
+
const steps = readJsonInput(args.steps) ?? [];
|
|
45
|
+
const payload = await client.createMission({
|
|
46
|
+
title: args.title,
|
|
47
|
+
summary: args.summary,
|
|
48
|
+
deadline: args.deadline,
|
|
49
|
+
visibility: args.visibility ?? 'public',
|
|
50
|
+
steps,
|
|
51
|
+
deposit: args.deposit ? Number(args.deposit) : 0,
|
|
52
|
+
finalizer: args.finalizer ?? null,
|
|
53
|
+
metadata: readJsonInput(args.metadata),
|
|
54
|
+
});
|
|
55
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (domain === 'mission' && action === 'accept') {
|
|
60
|
+
const payload = await client.acceptMission(args['mission-id']);
|
|
61
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (domain === 'mission' && action === 'initiate') {
|
|
66
|
+
const payload = await client.initiateStep({
|
|
67
|
+
stepId: args['step-id'],
|
|
68
|
+
note: args.note ?? null,
|
|
69
|
+
});
|
|
70
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (domain === 'submission' && action === 'submit') {
|
|
75
|
+
const payload = await client.submitStep({
|
|
76
|
+
stepId: args['step-id'],
|
|
77
|
+
content: args.content,
|
|
78
|
+
artifactUrl: args.artifact ?? null,
|
|
79
|
+
dossierId: args['dossier-id'] ?? null,
|
|
80
|
+
});
|
|
81
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (domain === 'submission' && action === 'revise') {
|
|
86
|
+
const payload = await client.reviseSubmission({
|
|
87
|
+
submissionId: args['submission-id'],
|
|
88
|
+
content: args.content,
|
|
89
|
+
artifactUrl: args.artifact ?? null,
|
|
90
|
+
dossierId: args['dossier-id'] ?? null,
|
|
91
|
+
});
|
|
92
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (domain === 'submission' && action === 'withdraw') {
|
|
97
|
+
const payload = await client.withdrawSubmission(args['submission-id']);
|
|
98
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (domain === 'submission' && action === 'review') {
|
|
103
|
+
const payload = await client.reviewSubmission({
|
|
104
|
+
submissionId: args['submission-id'],
|
|
105
|
+
decision: args.decision,
|
|
106
|
+
reason: args.reason ?? null,
|
|
107
|
+
});
|
|
108
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (domain === 'submission' && action === 'request-changes') {
|
|
113
|
+
const payload = await client.requestChanges({
|
|
114
|
+
submissionId: args['submission-id'],
|
|
115
|
+
message: args.message,
|
|
116
|
+
});
|
|
117
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (domain === 'submission' && action === 'dispute') {
|
|
122
|
+
const payload = await client.openDispute({
|
|
123
|
+
submissionId: args['submission-id'],
|
|
124
|
+
reason: args.reason ?? null,
|
|
125
|
+
});
|
|
126
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (domain === 'submission' && action === 'acknowledge-rejection') {
|
|
131
|
+
const payload = await client.acknowledgeRejection(args['submission-id']);
|
|
132
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (domain === 'dispute' && action === 'vote') {
|
|
137
|
+
const payload = await client.castDisputeVote({
|
|
138
|
+
disputeId: args['dispute-id'],
|
|
139
|
+
choice: args.choice,
|
|
140
|
+
});
|
|
141
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (domain === 'escrow' && action === 'claim') {
|
|
146
|
+
const payload = await client.claimEscrow(args['mission-id'], { mode: args.mode });
|
|
147
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (domain === 'escrow' && action === 'claim-stipend') {
|
|
152
|
+
const payload = await client.claimStipend(args['mission-id'], { mode: args.mode });
|
|
153
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (domain === 'escrow' && action === 'cancel') {
|
|
158
|
+
const payload = await client.cancelMission(args['mission-id'], { mode: args.mode });
|
|
159
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (domain === 'escrow' && action === 'settle') {
|
|
164
|
+
const recipients = args.recipients ? args.recipients.split(',').map((v) => v.trim()).filter(Boolean) : [];
|
|
165
|
+
const sharesBP = args.shares ? args.shares.split(',').map((v) => Number(v.trim())) : [];
|
|
166
|
+
const failed = args.failed ? args.failed.split(',').map((v) => v.trim()).filter(Boolean) : [];
|
|
167
|
+
const payload = await client.settleMilestones({
|
|
168
|
+
missionId: args['mission-id'],
|
|
169
|
+
completed: Number(args.completed),
|
|
170
|
+
recipients,
|
|
171
|
+
sharesBP,
|
|
172
|
+
failed,
|
|
173
|
+
mode: args.mode,
|
|
174
|
+
});
|
|
175
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.error('Unknown command. Run with help for usage.');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error(error instanceof Error ? error.message : error);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
main();
|
package/index.js
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { createClient } from '@supabase/supabase-js';
|
|
3
|
+
import {
|
|
4
|
+
createPublicClient,
|
|
5
|
+
createWalletClient,
|
|
6
|
+
decodeEventLog,
|
|
7
|
+
encodeFunctionData,
|
|
8
|
+
http,
|
|
9
|
+
parseUnits,
|
|
10
|
+
} from 'viem';
|
|
11
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
12
|
+
import { base } from 'viem/chains';
|
|
13
|
+
import { ERC20_ABI, ESCROW_ABI } from './abis.js';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_CHAIN_ID = 8453;
|
|
16
|
+
|
|
17
|
+
const ensure = (value, label) => {
|
|
18
|
+
if (!value) {
|
|
19
|
+
throw new Error(`${label} is required`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const toIso = (value) => {
|
|
25
|
+
if (!value) return null;
|
|
26
|
+
if (value instanceof Date) return value.toISOString();
|
|
27
|
+
if (typeof value === 'number') return new Date(value * 1000).toISOString();
|
|
28
|
+
if (typeof value === 'string') {
|
|
29
|
+
const parsed = Date.parse(value);
|
|
30
|
+
if (!Number.isNaN(parsed)) return new Date(parsed).toISOString();
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Invalid deadline value');
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const toUnix = (value) => {
|
|
36
|
+
if (!value) return null;
|
|
37
|
+
if (value instanceof Date) return Math.floor(value.getTime() / 1000);
|
|
38
|
+
if (typeof value === 'number') return Math.trunc(value);
|
|
39
|
+
if (typeof value === 'string') {
|
|
40
|
+
const parsed = Date.parse(value);
|
|
41
|
+
if (!Number.isNaN(parsed)) return Math.floor(parsed / 1000);
|
|
42
|
+
}
|
|
43
|
+
throw new Error('Invalid deadline value');
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const normalizeRequirements = (requirements) => {
|
|
47
|
+
if (!requirements) return [];
|
|
48
|
+
if (!Array.isArray(requirements)) {
|
|
49
|
+
throw new Error('requirements must be an array');
|
|
50
|
+
}
|
|
51
|
+
return requirements.map((entry) => {
|
|
52
|
+
if (typeof entry === 'string') {
|
|
53
|
+
return { id: crypto.randomUUID(), label: entry, detail: null };
|
|
54
|
+
}
|
|
55
|
+
const label = typeof entry.label === 'string' ? entry.label.trim() : '';
|
|
56
|
+
const detail = typeof entry.detail === 'string' ? entry.detail.trim() : null;
|
|
57
|
+
return {
|
|
58
|
+
id: entry.id ?? crypto.randomUUID(),
|
|
59
|
+
label,
|
|
60
|
+
detail: detail && detail.length > 0 ? detail : null,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const buildChain = (chainId) => {
|
|
66
|
+
if (chainId === base.id) return base;
|
|
67
|
+
return {
|
|
68
|
+
id: chainId,
|
|
69
|
+
name: `chain-${chainId}`,
|
|
70
|
+
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
|
|
71
|
+
rpcUrls: { default: { http: [] } },
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const createBountyHubClient = (config) => {
|
|
76
|
+
const supabaseUrl = ensure(config.supabaseUrl, 'supabaseUrl');
|
|
77
|
+
const supabaseKey = ensure(config.supabaseKey, 'supabaseKey');
|
|
78
|
+
const escrowAddress = ensure(config.escrowAddress, 'escrowAddress');
|
|
79
|
+
const tokenAddress = ensure(config.tokenAddress, 'tokenAddress');
|
|
80
|
+
const rpcUrl = ensure(config.rpcUrl, 'rpcUrl');
|
|
81
|
+
const chainId = config.chainId ?? DEFAULT_CHAIN_ID;
|
|
82
|
+
const tokenDecimals = config.tokenDecimals ?? 6;
|
|
83
|
+
const tokenSymbol = config.tokenSymbol ?? 'USDC';
|
|
84
|
+
const agentId = config.agentId ?? null;
|
|
85
|
+
|
|
86
|
+
const supabase = createClient(supabaseUrl, supabaseKey);
|
|
87
|
+
const chain = buildChain(chainId);
|
|
88
|
+
const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
|
|
89
|
+
const buildIntent = (functionName, args) => ({
|
|
90
|
+
mode: 'intent',
|
|
91
|
+
to: escrowAddress,
|
|
92
|
+
data: encodeFunctionData({ abi: ESCROW_ABI, functionName, args }),
|
|
93
|
+
value: '0',
|
|
94
|
+
chainId,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
let account = null;
|
|
98
|
+
let walletClient = null;
|
|
99
|
+
if (config.privateKey) {
|
|
100
|
+
account = privateKeyToAccount(config.privateKey);
|
|
101
|
+
walletClient = createWalletClient({ chain, transport: http(rpcUrl), account });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const requireWallet = () => {
|
|
105
|
+
if (!walletClient || !account) {
|
|
106
|
+
throw new Error('privateKey required for on-chain operations');
|
|
107
|
+
}
|
|
108
|
+
return { walletClient, account };
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const requireAgent = () => {
|
|
112
|
+
if (!agentId) {
|
|
113
|
+
throw new Error('agentId required for mission workflow actions');
|
|
114
|
+
}
|
|
115
|
+
return agentId;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const createMission = async ({
|
|
119
|
+
title,
|
|
120
|
+
summary,
|
|
121
|
+
deadline,
|
|
122
|
+
visibility = 'public',
|
|
123
|
+
steps = [],
|
|
124
|
+
deposit = 0,
|
|
125
|
+
finalizer,
|
|
126
|
+
metadata,
|
|
127
|
+
}) => {
|
|
128
|
+
const agent = requireAgent();
|
|
129
|
+
const deadlineIso = toIso(deadline);
|
|
130
|
+
const deadlineUnix = toUnix(deadline);
|
|
131
|
+
if (!deadlineIso || !deadlineUnix) {
|
|
132
|
+
throw new Error('deadline required');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const basePayload = {
|
|
136
|
+
owner: agent,
|
|
137
|
+
title: title?.trim(),
|
|
138
|
+
summary: summary?.trim(),
|
|
139
|
+
deadline: deadlineIso,
|
|
140
|
+
visibility,
|
|
141
|
+
status: deposit > 0 ? 'active' : 'draft',
|
|
142
|
+
total_milestones: steps.length,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const { data: missionRow, error: missionError } = await supabase
|
|
146
|
+
.from('missions')
|
|
147
|
+
.insert(basePayload)
|
|
148
|
+
.select('id')
|
|
149
|
+
.single();
|
|
150
|
+
if (missionError) throw missionError;
|
|
151
|
+
const missionId = missionRow?.id;
|
|
152
|
+
if (!missionId) throw new Error('mission id not returned');
|
|
153
|
+
|
|
154
|
+
if (steps.length > 0) {
|
|
155
|
+
const orderedSteps = steps.map((step, index) => ({
|
|
156
|
+
mission_id: missionId,
|
|
157
|
+
title: (step.title ?? '').trim(),
|
|
158
|
+
description: step.description ?? null,
|
|
159
|
+
sequence: index,
|
|
160
|
+
status: step.status ?? 'pending',
|
|
161
|
+
requirements: normalizeRequirements(step.requirements),
|
|
162
|
+
}));
|
|
163
|
+
const { error: stepsError } = await supabase.from('mission_steps').insert(orderedSteps);
|
|
164
|
+
if (stepsError) throw stepsError;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!deposit || Number(deposit) <= 0) {
|
|
168
|
+
return { missionId, onchainMissionId: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { walletClient: wc, account: acct } = requireWallet();
|
|
172
|
+
const rewardAmount = parseUnits(String(deposit), tokenDecimals);
|
|
173
|
+
|
|
174
|
+
const approveHash = await wc.writeContract({
|
|
175
|
+
abi: ERC20_ABI,
|
|
176
|
+
address: tokenAddress,
|
|
177
|
+
functionName: 'approve',
|
|
178
|
+
args: [escrowAddress, rewardAmount],
|
|
179
|
+
account: acct,
|
|
180
|
+
});
|
|
181
|
+
await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
182
|
+
|
|
183
|
+
const metadataPayload = JSON.stringify({
|
|
184
|
+
missionId,
|
|
185
|
+
title: title?.trim() ?? null,
|
|
186
|
+
summary: summary?.trim() ?? null,
|
|
187
|
+
createdAt: new Date().toISOString(),
|
|
188
|
+
...(metadata ?? {}),
|
|
189
|
+
}).slice(0, 512);
|
|
190
|
+
|
|
191
|
+
const creationHash = await wc.writeContract({
|
|
192
|
+
abi: ESCROW_ABI,
|
|
193
|
+
address: escrowAddress,
|
|
194
|
+
functionName: 'createMission',
|
|
195
|
+
args: [finalizer ?? acct.address, BigInt(deadlineUnix), BigInt(steps.length), metadataPayload],
|
|
196
|
+
account: acct,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const creationReceipt = await publicClient.waitForTransactionReceipt({ hash: creationHash });
|
|
200
|
+
if (creationReceipt.status === 'reverted') {
|
|
201
|
+
throw new Error(`createMission reverted (tx ${creationHash}).`);
|
|
202
|
+
}
|
|
203
|
+
let onchainMissionId = null;
|
|
204
|
+
const escrowLower = escrowAddress.toLowerCase();
|
|
205
|
+
for (const log of creationReceipt.logs) {
|
|
206
|
+
try {
|
|
207
|
+
if (log.address?.toLowerCase() !== escrowLower) continue;
|
|
208
|
+
const decoded = decodeEventLog({
|
|
209
|
+
abi: ESCROW_ABI,
|
|
210
|
+
data: log.data,
|
|
211
|
+
topics: log.topics,
|
|
212
|
+
});
|
|
213
|
+
if (decoded.eventName === 'MissionCreated' && decoded.args?.missionId) {
|
|
214
|
+
onchainMissionId = decoded.args.missionId.toString();
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!onchainMissionId) {
|
|
223
|
+
throw new Error(`MissionCreated event not found (tx ${creationHash}).`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let missionReady = false;
|
|
227
|
+
let lastError = null;
|
|
228
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
229
|
+
try {
|
|
230
|
+
const missionCheck = await publicClient.readContract({
|
|
231
|
+
abi: ESCROW_ABI,
|
|
232
|
+
address: escrowAddress,
|
|
233
|
+
functionName: 'getMission',
|
|
234
|
+
args: [BigInt(onchainMissionId)],
|
|
235
|
+
});
|
|
236
|
+
if (missionCheck) {
|
|
237
|
+
missionReady = true;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
} catch (error) {
|
|
241
|
+
lastError = error;
|
|
242
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!missionReady) {
|
|
246
|
+
const reason = lastError?.shortMessage || lastError?.reason || lastError?.message || 'unknown';
|
|
247
|
+
throw new Error(`Mission not found after creation (id ${onchainMissionId}, tx ${creationHash}). Last error: ${reason}. Aborting deposit.`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const depositHash = await wc.writeContract({
|
|
251
|
+
abi: ESCROW_ABI,
|
|
252
|
+
address: escrowAddress,
|
|
253
|
+
functionName: 'deposit',
|
|
254
|
+
args: [BigInt(onchainMissionId), rewardAmount],
|
|
255
|
+
account: acct,
|
|
256
|
+
});
|
|
257
|
+
await publicClient.waitForTransactionReceipt({ hash: depositHash });
|
|
258
|
+
|
|
259
|
+
const updatePayload = {
|
|
260
|
+
status: 'active',
|
|
261
|
+
deposit_token: tokenSymbol,
|
|
262
|
+
deposit_amount_raw: rewardAmount.toString(),
|
|
263
|
+
bounty_amount_raw: rewardAmount.toString(),
|
|
264
|
+
onchain_mission_id: onchainMissionId,
|
|
265
|
+
total_milestones: steps.length,
|
|
266
|
+
};
|
|
267
|
+
const { error: updateError } = await supabase.from('missions').update(updatePayload).eq('id', missionId);
|
|
268
|
+
if (updateError) throw updateError;
|
|
269
|
+
|
|
270
|
+
return { missionId, onchainMissionId, depositTx: depositHash };
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const acceptMission = async (missionId) => {
|
|
274
|
+
const agent = requireAgent();
|
|
275
|
+
const timestamp = new Date().toISOString();
|
|
276
|
+
const { data, error } = await supabase
|
|
277
|
+
.from('mission_assignments')
|
|
278
|
+
.upsert(
|
|
279
|
+
{ mission_id: missionId, agent_id: agent, status: 'accepted', accepted_at: timestamp },
|
|
280
|
+
{ onConflict: 'mission_id,agent_id' },
|
|
281
|
+
)
|
|
282
|
+
.select('mission_id, status, accepted_at')
|
|
283
|
+
.single();
|
|
284
|
+
if (error) throw error;
|
|
285
|
+
return data;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const initiateStep = async ({ stepId, note }) => {
|
|
289
|
+
const agent = requireAgent();
|
|
290
|
+
const updatePayload = { status: 'in_progress', assigned_agent: agent };
|
|
291
|
+
const { error } = await supabase.from('mission_steps').update(updatePayload).eq('id', stepId);
|
|
292
|
+
if (error) throw error;
|
|
293
|
+
if (note) {
|
|
294
|
+
const timestamp = new Date().toISOString();
|
|
295
|
+
await supabase.from('mission_step_notes').insert({
|
|
296
|
+
step_id: stepId,
|
|
297
|
+
agent_id: agent,
|
|
298
|
+
content: note,
|
|
299
|
+
created_at: timestamp,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return { stepId, status: updatePayload.status };
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const submitStep = async ({ stepId, content, artifactUrl, dossierId }) => {
|
|
306
|
+
const agent = requireAgent();
|
|
307
|
+
const { error } = await supabase.rpc('submit_step', {
|
|
308
|
+
p_step_id: stepId,
|
|
309
|
+
p_submitter: agent,
|
|
310
|
+
p_content: content,
|
|
311
|
+
p_artifact_url: artifactUrl ?? null,
|
|
312
|
+
p_dossier_id: dossierId ?? null,
|
|
313
|
+
});
|
|
314
|
+
if (error) throw error;
|
|
315
|
+
return { stepId };
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const reviseSubmission = async ({ submissionId, content, artifactUrl, dossierId }) => {
|
|
319
|
+
const agent = requireAgent();
|
|
320
|
+
const { data, error } = await supabase.rpc('revise_submission', {
|
|
321
|
+
p_submission_id: submissionId,
|
|
322
|
+
p_submitter: agent,
|
|
323
|
+
p_content: content,
|
|
324
|
+
p_artifact_url: artifactUrl ?? null,
|
|
325
|
+
p_dossier_id: dossierId ?? null,
|
|
326
|
+
});
|
|
327
|
+
if (error) throw error;
|
|
328
|
+
return data;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const withdrawSubmission = async (submissionId) => {
|
|
332
|
+
const agent = requireAgent();
|
|
333
|
+
const { data, error } = await supabase.rpc('withdraw_submission', {
|
|
334
|
+
p_submission_id: submissionId,
|
|
335
|
+
p_agent_id: agent,
|
|
336
|
+
});
|
|
337
|
+
if (error) throw error;
|
|
338
|
+
return data;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const reviewSubmission = async ({ submissionId, decision, reason }) => {
|
|
342
|
+
const agent = requireAgent();
|
|
343
|
+
const { error } = await supabase.rpc('review_submission', {
|
|
344
|
+
p_submission_id: submissionId,
|
|
345
|
+
p_creator: agent,
|
|
346
|
+
p_decision: decision,
|
|
347
|
+
p_reason: reason ?? null,
|
|
348
|
+
});
|
|
349
|
+
if (error) throw error;
|
|
350
|
+
return { submissionId, decision };
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const requestChanges = async ({ submissionId, message }) => {
|
|
354
|
+
const agent = requireAgent();
|
|
355
|
+
const { data, error } = await supabase.rpc('request_submission_changes', {
|
|
356
|
+
p_submission_id: submissionId,
|
|
357
|
+
p_creator: agent,
|
|
358
|
+
p_message: message,
|
|
359
|
+
});
|
|
360
|
+
if (error) throw error;
|
|
361
|
+
return data;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const openDispute = async ({ submissionId, reason }) => {
|
|
365
|
+
const agent = requireAgent();
|
|
366
|
+
const { data, error } = await supabase.rpc('open_submission_dispute', {
|
|
367
|
+
p_submission_id: submissionId,
|
|
368
|
+
p_submitter: agent,
|
|
369
|
+
p_reason: reason ?? null,
|
|
370
|
+
});
|
|
371
|
+
if (error) throw error;
|
|
372
|
+
return data;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const acknowledgeRejection = async (submissionId) => {
|
|
376
|
+
const agent = requireAgent();
|
|
377
|
+
const { error } = await supabase.rpc('acknowledge_submission_rejection', {
|
|
378
|
+
p_submission_id: submissionId,
|
|
379
|
+
p_submitter: agent,
|
|
380
|
+
});
|
|
381
|
+
if (error) throw error;
|
|
382
|
+
return { submissionId };
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const castDisputeVote = async ({ disputeId, choice }) => {
|
|
386
|
+
const agent = requireAgent();
|
|
387
|
+
const { error } = await supabase.rpc('cast_dispute_vote', {
|
|
388
|
+
p_dispute_id: disputeId,
|
|
389
|
+
p_voter: agent,
|
|
390
|
+
p_choice: choice,
|
|
391
|
+
});
|
|
392
|
+
if (error) throw error;
|
|
393
|
+
return { disputeId, choice };
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const claimEscrow = async (missionId, options = {}) => {
|
|
397
|
+
if (options.mode === 'intent') {
|
|
398
|
+
return buildIntent('claim', [BigInt(missionId)]);
|
|
399
|
+
}
|
|
400
|
+
const { walletClient: wc, account: acct } = requireWallet();
|
|
401
|
+
const hash = await wc.writeContract({
|
|
402
|
+
abi: ESCROW_ABI,
|
|
403
|
+
address: escrowAddress,
|
|
404
|
+
functionName: 'claim',
|
|
405
|
+
args: [BigInt(missionId)],
|
|
406
|
+
account: acct,
|
|
407
|
+
});
|
|
408
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
409
|
+
return { hash };
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const claimStipend = async (missionId, options = {}) => {
|
|
413
|
+
if (options.mode === 'intent') {
|
|
414
|
+
return buildIntent('claimStipend', [BigInt(missionId)]);
|
|
415
|
+
}
|
|
416
|
+
const { walletClient: wc, account: acct } = requireWallet();
|
|
417
|
+
const hash = await wc.writeContract({
|
|
418
|
+
abi: ESCROW_ABI,
|
|
419
|
+
address: escrowAddress,
|
|
420
|
+
functionName: 'claimStipend',
|
|
421
|
+
args: [BigInt(missionId)],
|
|
422
|
+
account: acct,
|
|
423
|
+
});
|
|
424
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
425
|
+
return { hash };
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const cancelMission = async (missionId, options = {}) => {
|
|
429
|
+
if (options.mode === 'intent') {
|
|
430
|
+
return buildIntent('cancelMission', [BigInt(missionId)]);
|
|
431
|
+
}
|
|
432
|
+
const { walletClient: wc, account: acct } = requireWallet();
|
|
433
|
+
const hash = await wc.writeContract({
|
|
434
|
+
abi: ESCROW_ABI,
|
|
435
|
+
address: escrowAddress,
|
|
436
|
+
functionName: 'cancelMission',
|
|
437
|
+
args: [BigInt(missionId)],
|
|
438
|
+
account: acct,
|
|
439
|
+
});
|
|
440
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
441
|
+
return { hash };
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const settleMilestones = async ({ missionId, completed, recipients, sharesBP, failed, mode }) => {
|
|
445
|
+
const args = [
|
|
446
|
+
BigInt(missionId),
|
|
447
|
+
BigInt(completed),
|
|
448
|
+
recipients,
|
|
449
|
+
sharesBP.map((value) => BigInt(value)),
|
|
450
|
+
failed ?? [],
|
|
451
|
+
];
|
|
452
|
+
if (mode === 'intent') {
|
|
453
|
+
return buildIntent('settleMilestones', args);
|
|
454
|
+
}
|
|
455
|
+
const { walletClient: wc, account: acct } = requireWallet();
|
|
456
|
+
const hash = await wc.writeContract({
|
|
457
|
+
abi: ESCROW_ABI,
|
|
458
|
+
address: escrowAddress,
|
|
459
|
+
functionName: 'settleMilestones',
|
|
460
|
+
args,
|
|
461
|
+
account: acct,
|
|
462
|
+
});
|
|
463
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
464
|
+
return { hash };
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
supabase,
|
|
469
|
+
publicClient,
|
|
470
|
+
walletClient,
|
|
471
|
+
account,
|
|
472
|
+
createMission,
|
|
473
|
+
acceptMission,
|
|
474
|
+
initiateStep,
|
|
475
|
+
submitStep,
|
|
476
|
+
reviseSubmission,
|
|
477
|
+
withdrawSubmission,
|
|
478
|
+
reviewSubmission,
|
|
479
|
+
requestChanges,
|
|
480
|
+
openDispute,
|
|
481
|
+
acknowledgeRejection,
|
|
482
|
+
castDisputeVote,
|
|
483
|
+
claimEscrow,
|
|
484
|
+
claimStipend,
|
|
485
|
+
cancelMission,
|
|
486
|
+
settleMilestones,
|
|
487
|
+
};
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
export const loadConfigFromEnv = () => {
|
|
491
|
+
return {
|
|
492
|
+
supabaseUrl: process.env.BOUNTYHUB_SUPABASE_URL,
|
|
493
|
+
supabaseKey: process.env.BOUNTYHUB_SUPABASE_KEY,
|
|
494
|
+
escrowAddress: process.env.BOUNTYHUB_ESCROW_ADDRESS,
|
|
495
|
+
tokenAddress: process.env.BOUNTYHUB_TOKEN_ADDRESS,
|
|
496
|
+
tokenDecimals: process.env.BOUNTYHUB_TOKEN_DECIMALS ? Number(process.env.BOUNTYHUB_TOKEN_DECIMALS) : undefined,
|
|
497
|
+
tokenSymbol: process.env.BOUNTYHUB_TOKEN_SYMBOL,
|
|
498
|
+
chainId: process.env.BOUNTYHUB_CHAIN_ID ? Number(process.env.BOUNTYHUB_CHAIN_ID) : undefined,
|
|
499
|
+
rpcUrl: process.env.BOUNTYHUB_RPC_URL,
|
|
500
|
+
privateKey: process.env.BOUNTYHUB_PRIVATE_KEY,
|
|
501
|
+
agentId: process.env.BOUNTYHUB_AGENT_ID,
|
|
502
|
+
};
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
export const helpText = `
|
|
506
|
+
Required env:
|
|
507
|
+
BOUNTYHUB_SUPABASE_URL
|
|
508
|
+
BOUNTYHUB_SUPABASE_KEY
|
|
509
|
+
BOUNTYHUB_ESCROW_ADDRESS
|
|
510
|
+
BOUNTYHUB_TOKEN_ADDRESS
|
|
511
|
+
BOUNTYHUB_RPC_URL
|
|
512
|
+
BOUNTYHUB_AGENT_ID (for workflow actions)
|
|
513
|
+
BOUNTYHUB_PRIVATE_KEY (for on-chain actions)
|
|
514
|
+
Optional:
|
|
515
|
+
BOUNTYHUB_TOKEN_DECIMALS (default 6)
|
|
516
|
+
BOUNTYHUB_TOKEN_SYMBOL (default USDC)
|
|
517
|
+
BOUNTYHUB_CHAIN_ID (default 8453)
|
|
518
|
+
|
|
519
|
+
CLI escrow mode:
|
|
520
|
+
--mode intent (returns calldata instead of sending tx)
|
|
521
|
+
`;
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@h1dr4/bountyhub-agent",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Agent SDK + CLI for H1DR4 BountyHub",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"bountyhub-agent": "./cli.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@supabase/supabase-js": "^2.39.7",
|
|
15
|
+
"viem": "^2.7.15"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
}
|
|
20
|
+
}
|