@wiimdy/openfunderse-agents 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/.env.example +51 -0
- package/README.md +252 -0
- package/dist/clawbot-cli.d.ts +1 -0
- package/dist/clawbot-cli.js +114 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +34 -0
- package/dist/lib/aa-client.d.ts +67 -0
- package/dist/lib/aa-client.js +353 -0
- package/dist/lib/relayer-client.d.ts +171 -0
- package/dist/lib/relayer-client.js +486 -0
- package/dist/lib/signer.d.ts +38 -0
- package/dist/lib/signer.js +103 -0
- package/dist/participant-cli.d.ts +1 -0
- package/dist/participant-cli.js +399 -0
- package/dist/reddit-mvp.d.ts +1 -0
- package/dist/reddit-mvp.js +546 -0
- package/dist/skills/participant/index.d.ts +116 -0
- package/dist/skills/participant/index.js +462 -0
- package/dist/skills/strategy/index.d.ts +117 -0
- package/dist/skills/strategy/index.js +879 -0
- package/dist/strategy-cli.d.ts +1 -0
- package/dist/strategy-cli.js +867 -0
- package/package.json +42 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { isAddress } from 'viem';
|
|
4
|
+
import { attestClaim, mineClaim, submitMinedClaim, verifyClaim } from './skills/participant/index.js';
|
|
5
|
+
const parseCli = (argv) => {
|
|
6
|
+
const [command, ...rest] = argv;
|
|
7
|
+
const options = new Map();
|
|
8
|
+
const flags = new Set();
|
|
9
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
10
|
+
const token = rest[i];
|
|
11
|
+
if (!token.startsWith('--')) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const key = token.slice(2);
|
|
15
|
+
if (key.includes('=')) {
|
|
16
|
+
const [left, ...right] = key.split('=');
|
|
17
|
+
options.set(left, right.join('='));
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const next = rest[i + 1];
|
|
21
|
+
if (next && !next.startsWith('--')) {
|
|
22
|
+
options.set(key, next);
|
|
23
|
+
i += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
flags.add(key);
|
|
27
|
+
}
|
|
28
|
+
return { command, options, flags };
|
|
29
|
+
};
|
|
30
|
+
const requiredOption = (parsed, key) => {
|
|
31
|
+
const value = parsed.options.get(key);
|
|
32
|
+
if (!value) {
|
|
33
|
+
throw new Error(`missing required option --${key}`);
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
};
|
|
37
|
+
const optionOrDefault = (parsed, key, fallback) => {
|
|
38
|
+
return parsed.options.get(key) ?? fallback;
|
|
39
|
+
};
|
|
40
|
+
const toNumberOption = (parsed, key, fallback) => {
|
|
41
|
+
const raw = parsed.options.get(key);
|
|
42
|
+
if (!raw)
|
|
43
|
+
return fallback;
|
|
44
|
+
const value = Number(raw);
|
|
45
|
+
if (!Number.isFinite(value)) {
|
|
46
|
+
throw new Error(`--${key} must be a number`);
|
|
47
|
+
}
|
|
48
|
+
return Math.trunc(value);
|
|
49
|
+
};
|
|
50
|
+
const jsonStringify = (value) => {
|
|
51
|
+
return JSON.stringify(value, (_key, inner) => (typeof inner === 'bigint' ? inner.toString() : inner), 2);
|
|
52
|
+
};
|
|
53
|
+
const writeJsonFile = async (filePath, payload) => {
|
|
54
|
+
const absolute = resolve(filePath);
|
|
55
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
56
|
+
await writeFile(absolute, jsonStringify(payload));
|
|
57
|
+
};
|
|
58
|
+
const readJsonFile = async (filePath) => {
|
|
59
|
+
const absolute = resolve(filePath);
|
|
60
|
+
const raw = await readFile(absolute, 'utf8');
|
|
61
|
+
return JSON.parse(raw);
|
|
62
|
+
};
|
|
63
|
+
const buildClientOptionsForPrefix = (prefix) => {
|
|
64
|
+
const botId = process.env[`${prefix}_BOT_ID`];
|
|
65
|
+
const botApiKey = process.env[`${prefix}_BOT_API_KEY`];
|
|
66
|
+
const botAddress = process.env[`${prefix}_BOT_ADDRESS`];
|
|
67
|
+
if (!botId && !botApiKey && !botAddress) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (!botId || !botApiKey) {
|
|
71
|
+
throw new Error(`${prefix}_BOT_ID and ${prefix}_BOT_API_KEY must be set together`);
|
|
72
|
+
}
|
|
73
|
+
if (botAddress && !isAddress(botAddress)) {
|
|
74
|
+
throw new Error(`${prefix}_BOT_ADDRESS must be a valid address`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
botId,
|
|
78
|
+
botApiKey,
|
|
79
|
+
botAddress: botAddress
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
const buildDefaultBotClientOptions = () => {
|
|
83
|
+
const botId = process.env.BOT_ID;
|
|
84
|
+
const botApiKey = process.env.BOT_API_KEY;
|
|
85
|
+
const botAddress = process.env.BOT_ADDRESS;
|
|
86
|
+
if (!botId && !botApiKey && !botAddress) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
if (!botId || !botApiKey) {
|
|
90
|
+
throw new Error('BOT_ID and BOT_API_KEY must be set together');
|
|
91
|
+
}
|
|
92
|
+
if (botAddress && !isAddress(botAddress)) {
|
|
93
|
+
throw new Error('BOT_ADDRESS must be a valid address');
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
botId,
|
|
97
|
+
botApiKey,
|
|
98
|
+
botAddress: botAddress
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
const buildParticipantClientOptions = () => {
|
|
102
|
+
const scoped = buildClientOptionsForPrefix('PARTICIPANT');
|
|
103
|
+
if (scoped) {
|
|
104
|
+
return scoped;
|
|
105
|
+
}
|
|
106
|
+
return buildDefaultBotClientOptions();
|
|
107
|
+
};
|
|
108
|
+
const participantCrawlerAddress = () => {
|
|
109
|
+
const raw = process.env.PARTICIPANT_BOT_ADDRESS ?? process.env.BOT_ADDRESS;
|
|
110
|
+
if (!raw || raw.trim().length === 0) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
if (!isAddress(raw)) {
|
|
114
|
+
throw new Error('PARTICIPANT_BOT_ADDRESS (or BOT_ADDRESS fallback) must be a valid address');
|
|
115
|
+
}
|
|
116
|
+
return raw;
|
|
117
|
+
};
|
|
118
|
+
const buildVerifierSignerOptions = () => {
|
|
119
|
+
const options = {};
|
|
120
|
+
if (process.env.PARTICIPANT_PRIVATE_KEY) {
|
|
121
|
+
options.privateKey = process.env.PARTICIPANT_PRIVATE_KEY;
|
|
122
|
+
}
|
|
123
|
+
else if (process.env.VERIFIER_PRIVATE_KEY) {
|
|
124
|
+
options.privateKey = process.env.VERIFIER_PRIVATE_KEY;
|
|
125
|
+
}
|
|
126
|
+
if (process.env.CLAIM_ATTESTATION_VERIFIER_ADDRESS && isAddress(process.env.CLAIM_ATTESTATION_VERIFIER_ADDRESS)) {
|
|
127
|
+
options.claimAttestationVerifierAddress =
|
|
128
|
+
process.env.CLAIM_ATTESTATION_VERIFIER_ADDRESS;
|
|
129
|
+
}
|
|
130
|
+
if (process.env.CHAIN_ID) {
|
|
131
|
+
options.chainId = BigInt(process.env.CHAIN_ID);
|
|
132
|
+
}
|
|
133
|
+
return options;
|
|
134
|
+
};
|
|
135
|
+
const observationToClaimPayload = (observation) => {
|
|
136
|
+
return {
|
|
137
|
+
sourceRef: observation.canonicalPayload.sourceRef,
|
|
138
|
+
extracted: observation.canonicalPayload.extracted,
|
|
139
|
+
responseHash: observation.canonicalPayload.responseHash,
|
|
140
|
+
evidenceURI: observation.canonicalPayload.evidenceURI,
|
|
141
|
+
timestamp: Number(observation.canonicalPayload.timestamp)
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const readObservationFromFile = async (claimFile) => {
|
|
145
|
+
const parsed = await readJsonFile(claimFile);
|
|
146
|
+
if (parsed.observation) {
|
|
147
|
+
const observation = parsed.observation;
|
|
148
|
+
const fundId = String(parsed.fundId ?? '');
|
|
149
|
+
const epochId = Number(parsed.epochId ?? 0);
|
|
150
|
+
if (!fundId || !Number.isFinite(epochId)) {
|
|
151
|
+
throw new Error('claim file is missing fundId/epochId');
|
|
152
|
+
}
|
|
153
|
+
return { fundId, epochId: Math.trunc(epochId), observation };
|
|
154
|
+
}
|
|
155
|
+
if (parsed.claimHash && parsed.canonicalPayload) {
|
|
156
|
+
const observation = parsed;
|
|
157
|
+
const fundId = String(parsed.fundId ?? '');
|
|
158
|
+
const epochId = Number(parsed.epochId ?? 0);
|
|
159
|
+
if (!fundId || !Number.isFinite(epochId)) {
|
|
160
|
+
throw new Error('observation file is missing fundId/epochId');
|
|
161
|
+
}
|
|
162
|
+
return { fundId, epochId: Math.trunc(epochId), observation };
|
|
163
|
+
}
|
|
164
|
+
throw new Error('unsupported claim file format');
|
|
165
|
+
};
|
|
166
|
+
const runParticipantMine = async (parsed) => {
|
|
167
|
+
const fundId = requiredOption(parsed, 'fund-id');
|
|
168
|
+
const epochId = Number(requiredOption(parsed, 'epoch-id'));
|
|
169
|
+
if (!Number.isFinite(epochId)) {
|
|
170
|
+
throw new Error('--epoch-id must be a number');
|
|
171
|
+
}
|
|
172
|
+
const sourceRef = requiredOption(parsed, 'source-ref');
|
|
173
|
+
const tokenAddress = requiredOption(parsed, 'token-address');
|
|
174
|
+
const output = await mineClaim({
|
|
175
|
+
taskType: 'mine_claim',
|
|
176
|
+
fundId,
|
|
177
|
+
roomId: optionOrDefault(parsed, 'room-id', 'participant-room'),
|
|
178
|
+
epochId: Math.trunc(epochId),
|
|
179
|
+
sourceSpec: {
|
|
180
|
+
sourceSpecId: optionOrDefault(parsed, 'source-spec-id', 'participant-source'),
|
|
181
|
+
sourceRef,
|
|
182
|
+
extractor: { mode: 'raw-slice-256' },
|
|
183
|
+
freshnessSeconds: toNumberOption(parsed, 'freshness-seconds', 15)
|
|
184
|
+
},
|
|
185
|
+
tokenContext: {
|
|
186
|
+
symbol: optionOrDefault(parsed, 'token-symbol', 'TOKEN'),
|
|
187
|
+
address: tokenAddress
|
|
188
|
+
},
|
|
189
|
+
crawlerAddress: participantCrawlerAddress()
|
|
190
|
+
});
|
|
191
|
+
console.log(jsonStringify(output));
|
|
192
|
+
const outFile = parsed.options.get('out-file');
|
|
193
|
+
if (outFile) {
|
|
194
|
+
await writeJsonFile(outFile, output);
|
|
195
|
+
}
|
|
196
|
+
if (output.status !== 'OK') {
|
|
197
|
+
process.exitCode = 2;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const runParticipantVerify = async (parsed) => {
|
|
201
|
+
const claimFile = requiredOption(parsed, 'claim-file');
|
|
202
|
+
const bundle = await readObservationFromFile(claimFile);
|
|
203
|
+
const output = await verifyClaim({
|
|
204
|
+
taskType: 'verify_claim_or_intent_validity',
|
|
205
|
+
fundId: bundle.fundId,
|
|
206
|
+
roomId: optionOrDefault(parsed, 'room-id', 'participant-room'),
|
|
207
|
+
epochId: bundle.epochId,
|
|
208
|
+
subjectType: 'CLAIM',
|
|
209
|
+
subjectHash: bundle.observation.claimHash,
|
|
210
|
+
subjectPayload: observationToClaimPayload(bundle.observation),
|
|
211
|
+
validationPolicy: {
|
|
212
|
+
reproducible: optionOrDefault(parsed, 'reproducible', 'true') !== 'false',
|
|
213
|
+
maxDataAgeSeconds: toNumberOption(parsed, 'max-data-age-seconds', 300)
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
console.log(jsonStringify(output));
|
|
217
|
+
const outFile = parsed.options.get('out-file');
|
|
218
|
+
if (outFile) {
|
|
219
|
+
await writeJsonFile(outFile, output);
|
|
220
|
+
}
|
|
221
|
+
if (output.verdict !== 'PASS') {
|
|
222
|
+
process.exitCode = 2;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const runParticipantSubmit = async (parsed) => {
|
|
226
|
+
const claimFile = requiredOption(parsed, 'claim-file');
|
|
227
|
+
const bundle = await readObservationFromFile(claimFile);
|
|
228
|
+
const output = await submitMinedClaim({
|
|
229
|
+
fundId: bundle.fundId,
|
|
230
|
+
epochId: bundle.epochId,
|
|
231
|
+
observation: bundle.observation,
|
|
232
|
+
clientOptions: buildParticipantClientOptions()
|
|
233
|
+
});
|
|
234
|
+
console.log(jsonStringify(output));
|
|
235
|
+
if (output.status !== 'OK') {
|
|
236
|
+
process.exitCode = 2;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const runParticipantAttest = async (parsed) => {
|
|
240
|
+
const fundId = requiredOption(parsed, 'fund-id');
|
|
241
|
+
const epochId = Number(requiredOption(parsed, 'epoch-id'));
|
|
242
|
+
if (!Number.isFinite(epochId)) {
|
|
243
|
+
throw new Error('--epoch-id must be a number');
|
|
244
|
+
}
|
|
245
|
+
const claimHash = requiredOption(parsed, 'claim-hash');
|
|
246
|
+
const output = await attestClaim({
|
|
247
|
+
fundId,
|
|
248
|
+
claimHash: claimHash,
|
|
249
|
+
epochId: Math.trunc(epochId),
|
|
250
|
+
expiresInSeconds: toNumberOption(parsed, 'expires-in-seconds', 900),
|
|
251
|
+
clientOptions: buildParticipantClientOptions(),
|
|
252
|
+
signerOptions: buildVerifierSignerOptions()
|
|
253
|
+
});
|
|
254
|
+
console.log(jsonStringify(output));
|
|
255
|
+
if (output.status !== 'OK') {
|
|
256
|
+
process.exitCode = 2;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const runParticipantE2E = async (parsed) => {
|
|
260
|
+
const fundId = requiredOption(parsed, 'fund-id');
|
|
261
|
+
const epochId = Number(requiredOption(parsed, 'epoch-id'));
|
|
262
|
+
if (!Number.isFinite(epochId)) {
|
|
263
|
+
throw new Error('--epoch-id must be a number');
|
|
264
|
+
}
|
|
265
|
+
const mine = await mineClaim({
|
|
266
|
+
taskType: 'mine_claim',
|
|
267
|
+
fundId,
|
|
268
|
+
roomId: optionOrDefault(parsed, 'room-id', 'participant-room'),
|
|
269
|
+
epochId: Math.trunc(epochId),
|
|
270
|
+
sourceSpec: {
|
|
271
|
+
sourceSpecId: optionOrDefault(parsed, 'source-spec-id', 'participant-source'),
|
|
272
|
+
sourceRef: requiredOption(parsed, 'source-ref'),
|
|
273
|
+
extractor: { mode: 'raw-slice-256' },
|
|
274
|
+
freshnessSeconds: toNumberOption(parsed, 'freshness-seconds', 15)
|
|
275
|
+
},
|
|
276
|
+
tokenContext: {
|
|
277
|
+
symbol: optionOrDefault(parsed, 'token-symbol', 'TOKEN'),
|
|
278
|
+
address: requiredOption(parsed, 'token-address')
|
|
279
|
+
},
|
|
280
|
+
crawlerAddress: participantCrawlerAddress()
|
|
281
|
+
});
|
|
282
|
+
if (mine.status !== 'OK' || !mine.observation) {
|
|
283
|
+
console.log(jsonStringify({ step: 'mine', result: mine }));
|
|
284
|
+
process.exitCode = 2;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const verify = await verifyClaim({
|
|
288
|
+
taskType: 'verify_claim_or_intent_validity',
|
|
289
|
+
fundId,
|
|
290
|
+
roomId: optionOrDefault(parsed, 'room-id', 'participant-room'),
|
|
291
|
+
epochId: Math.trunc(epochId),
|
|
292
|
+
subjectType: 'CLAIM',
|
|
293
|
+
subjectHash: mine.observation.claimHash,
|
|
294
|
+
subjectPayload: observationToClaimPayload(mine.observation),
|
|
295
|
+
validationPolicy: {
|
|
296
|
+
reproducible: optionOrDefault(parsed, 'reproducible', 'true') !== 'false',
|
|
297
|
+
maxDataAgeSeconds: toNumberOption(parsed, 'max-data-age-seconds', 300)
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
if (verify.verdict !== 'PASS') {
|
|
301
|
+
console.log(jsonStringify({ step: 'verify', result: verify }));
|
|
302
|
+
process.exitCode = 2;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const submit = await submitMinedClaim({
|
|
306
|
+
fundId,
|
|
307
|
+
epochId: Math.trunc(epochId),
|
|
308
|
+
observation: mine.observation,
|
|
309
|
+
clientOptions: buildParticipantClientOptions()
|
|
310
|
+
});
|
|
311
|
+
if (submit.status !== 'OK' || !submit.claimHash) {
|
|
312
|
+
console.log(jsonStringify({ step: 'submit', result: submit }));
|
|
313
|
+
process.exitCode = 2;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const attest = await attestClaim({
|
|
317
|
+
fundId,
|
|
318
|
+
claimHash: submit.claimHash,
|
|
319
|
+
epochId: Math.trunc(epochId),
|
|
320
|
+
expiresInSeconds: toNumberOption(parsed, 'expires-in-seconds', 900),
|
|
321
|
+
clientOptions: buildParticipantClientOptions(),
|
|
322
|
+
signerOptions: buildVerifierSignerOptions()
|
|
323
|
+
});
|
|
324
|
+
const report = {
|
|
325
|
+
step: 'participant-e2e',
|
|
326
|
+
fundId,
|
|
327
|
+
epochId: Math.trunc(epochId),
|
|
328
|
+
claimHash: submit.claimHash,
|
|
329
|
+
mine,
|
|
330
|
+
verify,
|
|
331
|
+
submit,
|
|
332
|
+
attest
|
|
333
|
+
};
|
|
334
|
+
console.log(jsonStringify(report));
|
|
335
|
+
const reportFile = parsed.options.get('report-file');
|
|
336
|
+
if (reportFile) {
|
|
337
|
+
await writeJsonFile(reportFile, report);
|
|
338
|
+
}
|
|
339
|
+
if (attest.status !== 'OK') {
|
|
340
|
+
process.exitCode = 2;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
const printUsage = () => {
|
|
344
|
+
console.log(`
|
|
345
|
+
[agents] participant commands
|
|
346
|
+
|
|
347
|
+
participant-mine
|
|
348
|
+
--fund-id <id> --epoch-id <n> --source-ref <url> --token-address <0x...>
|
|
349
|
+
[--source-spec-id <id>] [--token-symbol <sym>] [--room-id <id>]
|
|
350
|
+
[--freshness-seconds <n>] [--out-file <path>]
|
|
351
|
+
|
|
352
|
+
participant-verify
|
|
353
|
+
--claim-file <path>
|
|
354
|
+
[--reproducible true|false] [--max-data-age-seconds <n>] [--out-file <path>]
|
|
355
|
+
|
|
356
|
+
participant-submit
|
|
357
|
+
--claim-file <path>
|
|
358
|
+
|
|
359
|
+
participant-attest
|
|
360
|
+
--fund-id <id> --epoch-id <n> --claim-hash <0x...>
|
|
361
|
+
[--expires-in-seconds <n>]
|
|
362
|
+
|
|
363
|
+
participant-e2e
|
|
364
|
+
--fund-id <id> --epoch-id <n> --source-ref <url> --token-address <0x...>
|
|
365
|
+
[--token-symbol <sym>] [--reproducible true|false] [--report-file <path>]
|
|
366
|
+
`);
|
|
367
|
+
};
|
|
368
|
+
export const runParticipantCli = async (argv) => {
|
|
369
|
+
const parsed = parseCli(argv);
|
|
370
|
+
const command = parsed.command ?? '';
|
|
371
|
+
if (!command.startsWith('participant-')) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
if (parsed.flags.has('help') || command === 'participant-help') {
|
|
375
|
+
printUsage();
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
if (command === 'participant-mine') {
|
|
379
|
+
await runParticipantMine(parsed);
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
if (command === 'participant-verify') {
|
|
383
|
+
await runParticipantVerify(parsed);
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
if (command === 'participant-submit') {
|
|
387
|
+
await runParticipantSubmit(parsed);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
if (command === 'participant-attest') {
|
|
391
|
+
await runParticipantAttest(parsed);
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
if (command === 'participant-e2e') {
|
|
395
|
+
await runParticipantE2E(parsed);
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
throw new Error(`unknown participant command: ${command}`);
|
|
399
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runRedditMvpCli(argv: string[]): Promise<void>;
|