@iamoberlin/chorus 1.3.9 → 2.0.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 +80 -41
- package/index.ts +257 -110
- package/package.json +22 -5
- package/src/choirs.ts +3 -20
- package/src/config.ts +47 -0
- package/src/prayers/cli.ts +400 -261
- package/src/prayers/solana.ts +389 -0
- package/src/purposes.ts +5 -0
- package/target/idl/chorus_prayers.json +1198 -0
package/src/prayers/cli.ts
CHANGED
|
@@ -1,330 +1,469 @@
|
|
|
1
|
-
#!/usr/bin/env npx
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
2
|
/**
|
|
3
|
-
* Prayer
|
|
4
|
-
*
|
|
3
|
+
* Prayer Chain — On-Chain CLI
|
|
4
|
+
*
|
|
5
|
+
* Human-in-the-loop commands for the Solana prayer chain.
|
|
6
|
+
* Choirs suggest prayers; humans approve and send them on-chain.
|
|
5
7
|
*
|
|
6
8
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* chorus pray post "What is the current SOFR rate?" --type knowledge --bounty 0.01
|
|
10
|
+
* chorus pray list
|
|
11
|
+
* chorus pray show <id>
|
|
12
|
+
* chorus pray claim <id>
|
|
13
|
+
* chorus pray answer <id> "SOFR is 4.55%, down 2bps this week"
|
|
14
|
+
* chorus pray confirm <id>
|
|
15
|
+
* chorus pray cancel <id>
|
|
16
|
+
* chorus pray agent # Show my on-chain agent
|
|
17
|
+
* chorus pray register "oberlin" "macro analysis, research"
|
|
18
|
+
* chorus pray chain # Show prayer chain stats
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
+
import { ChorusPrayerClient, PrayerType, getPrayerChainPDA, getAgentPDA, getPrayerPDA } from "./solana.js";
|
|
22
|
+
import { PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
|
23
|
+
import { createHash } from "crypto";
|
|
24
|
+
import * as fs from "fs";
|
|
25
|
+
import * as path from "path";
|
|
26
|
+
import { fileURLToPath } from "url";
|
|
21
27
|
|
|
22
|
-
const
|
|
23
|
-
const
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = path.dirname(__filename);
|
|
30
|
+
|
|
31
|
+
// Default to localhost; override with SOLANA_RPC_URL env
|
|
32
|
+
const RPC_URL = process.env.SOLANA_RPC_URL || "http://localhost:8899";
|
|
33
|
+
|
|
34
|
+
// ── Local text store (off-chain content cache) ────────────
|
|
35
|
+
const STORE_PATH = path.join(__dirname, "../../.prayer-texts.json");
|
|
36
|
+
|
|
37
|
+
interface TextStore {
|
|
38
|
+
[prayerId: string]: { content?: string; answer?: string };
|
|
39
|
+
}
|
|
24
40
|
|
|
25
|
-
function
|
|
26
|
-
|
|
41
|
+
function loadTextStore(): TextStore {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(fs.readFileSync(STORE_PATH, "utf-8"));
|
|
44
|
+
} catch {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
27
47
|
}
|
|
28
48
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
const { request: req, responses } = r;
|
|
32
|
-
|
|
33
|
-
return `
|
|
34
|
-
ID: ${req.id}
|
|
35
|
-
From: ${req.from.name || req.from.id}
|
|
36
|
-
Type: ${req.type}
|
|
37
|
-
Category: ${req.category}
|
|
38
|
-
Status: ${req.status}
|
|
39
|
-
Created: ${formatDate(req.createdAt)}
|
|
40
|
-
${req.expiresAt ? `Expires: ${formatDate(req.expiresAt)}` : ''}
|
|
41
|
-
${req.deadline ? `Deadline: ${formatDate(req.deadline)}` : ''}
|
|
42
|
-
${req.reward ? `Reward: ${req.reward.amount} ${req.reward.token}` : ''}
|
|
43
|
-
${req.acceptedBy ? `Accepted: ${req.acceptedBy.name || req.acceptedBy.id}` : ''}
|
|
44
|
-
|
|
45
|
-
Title:
|
|
46
|
-
${req.title}
|
|
47
|
-
|
|
48
|
-
Content:
|
|
49
|
-
${req.content}
|
|
50
|
-
|
|
51
|
-
Responses: ${responses.length}
|
|
52
|
-
${responses.map(r => ` - [${r.action}] ${r.from.name || r.from.id} @ ${formatDate(r.createdAt)}${r.result ? `\n Result: ${r.result.slice(0, 100)}...` : ''}`).join('\n')}
|
|
53
|
-
`.trim();
|
|
49
|
+
function saveTextStore(store: TextStore): void {
|
|
50
|
+
fs.writeFileSync(STORE_PATH, JSON.stringify(store, null, 2));
|
|
54
51
|
}
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
function storeContent(prayerId: number, content: string): void {
|
|
54
|
+
const store = loadTextStore();
|
|
55
|
+
if (!store[prayerId]) store[prayerId] = {};
|
|
56
|
+
store[prayerId].content = content;
|
|
57
|
+
saveTextStore(store);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function storeAnswer(prayerId: number, answer: string): void {
|
|
61
|
+
const store = loadTextStore();
|
|
62
|
+
if (!store[prayerId]) store[prayerId] = {};
|
|
63
|
+
store[prayerId].answer = answer;
|
|
64
|
+
saveTextStore(store);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getStoredText(prayerId: number): { content?: string; answer?: string } {
|
|
68
|
+
const store = loadTextStore();
|
|
69
|
+
return store[prayerId] || {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hashToHex(hash: number[]): string {
|
|
73
|
+
return Buffer.from(hash).toString("hex").slice(0, 16) + "…";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getClient(): ChorusPrayerClient {
|
|
77
|
+
return ChorusPrayerClient.fromDefaultKeypair(RPC_URL);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parsePrayerType(t: string): { [key: string]: object } {
|
|
81
|
+
const types: Record<string, object> = {
|
|
82
|
+
knowledge: { knowledge: {} },
|
|
83
|
+
compute: { compute: {} },
|
|
84
|
+
review: { review: {} },
|
|
85
|
+
signal: { signal: {} },
|
|
86
|
+
collaboration: { collaboration: {} },
|
|
87
|
+
};
|
|
88
|
+
const normalized = t.toLowerCase();
|
|
89
|
+
if (!types[normalized]) {
|
|
90
|
+
throw new Error(`Unknown prayer type: ${t}. Valid: ${Object.keys(types).join(", ")}`);
|
|
91
|
+
}
|
|
92
|
+
return types[normalized];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatStatus(status: any): string {
|
|
96
|
+
if (typeof status === "object") {
|
|
97
|
+
return Object.keys(status)[0].toUpperCase();
|
|
98
|
+
}
|
|
99
|
+
return String(status).toUpperCase();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function formatType(prayerType: any): string {
|
|
103
|
+
if (typeof prayerType === "object") {
|
|
104
|
+
return Object.keys(prayerType)[0];
|
|
65
105
|
}
|
|
66
|
-
|
|
106
|
+
return String(prayerType);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function formatTime(ts: number): string {
|
|
110
|
+
if (!ts) return "—";
|
|
111
|
+
return new Date(ts * 1000).toLocaleString();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function formatSOL(lamports: number): string {
|
|
115
|
+
if (!lamports) return "none";
|
|
116
|
+
return `${(lamports / LAMPORTS_PER_SOL).toFixed(4)} SOL`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function shortKey(key: PublicKey): string {
|
|
120
|
+
const s = key.toBase58();
|
|
121
|
+
if (s === "11111111111111111111111111111111") return "(none)";
|
|
122
|
+
return `${s.slice(0, 4)}...${s.slice(-4)}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const args = process.argv.slice(2);
|
|
126
|
+
const command = args[0];
|
|
127
|
+
|
|
128
|
+
async function main() {
|
|
129
|
+
const client = getClient();
|
|
130
|
+
|
|
67
131
|
switch (command) {
|
|
68
|
-
case
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
132
|
+
case "chain":
|
|
133
|
+
case "status": {
|
|
134
|
+
const chain = await client.getPrayerChain();
|
|
135
|
+
if (!chain) {
|
|
136
|
+
console.log("\n⛓️ Prayer Chain not initialized.");
|
|
137
|
+
console.log(" Run: chorus pray init\n");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
console.log("");
|
|
141
|
+
console.log("⛓️ Prayer Chain");
|
|
142
|
+
console.log("═".repeat(40));
|
|
143
|
+
console.log(` Authority: ${shortKey(chain.authority)}`);
|
|
144
|
+
console.log(` Total Prayers: ${chain.totalPrayers}`);
|
|
145
|
+
console.log(` Total Answered: ${chain.totalAnswered}`);
|
|
146
|
+
console.log(` Total Agents: ${chain.totalAgents}`);
|
|
147
|
+
console.log(` RPC: ${RPC_URL}`);
|
|
148
|
+
console.log("");
|
|
73
149
|
break;
|
|
74
150
|
}
|
|
75
|
-
|
|
76
|
-
case
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
151
|
+
|
|
152
|
+
case "init": {
|
|
153
|
+
console.log("\n⛓️ Initializing Prayer Chain...");
|
|
154
|
+
try {
|
|
155
|
+
const tx = await client.initialize();
|
|
156
|
+
console.log(` ✓ Initialized (tx: ${tx.slice(0, 16)}...)`);
|
|
157
|
+
} catch (err: any) {
|
|
158
|
+
if (err.message?.includes("already in use")) {
|
|
159
|
+
console.log(" Already initialized.");
|
|
160
|
+
} else {
|
|
161
|
+
console.error(` ✗ ${err.message}`);
|
|
162
|
+
}
|
|
81
163
|
}
|
|
82
|
-
|
|
83
|
-
const categoryIdx = args.indexOf('--category');
|
|
84
|
-
const titleIdx = args.indexOf('--title');
|
|
85
|
-
|
|
86
|
-
const category = (categoryIdx > -1 ? args[categoryIdx + 1] : 'other') as PrayerCategory;
|
|
87
|
-
const title = titleIdx > -1 ? args[titleIdx + 1] : content.slice(0, 50);
|
|
88
|
-
|
|
89
|
-
const request = prayers.createRequest({
|
|
90
|
-
type: 'ask',
|
|
91
|
-
category,
|
|
92
|
-
title,
|
|
93
|
-
content,
|
|
94
|
-
expiresIn: 24 * 60 * 60 * 1000 // 24 hours
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
console.log(`Created prayer request: ${request.id}`);
|
|
98
|
-
console.log(`Title: ${request.title}`);
|
|
99
|
-
console.log(`Status: ${request.status}`);
|
|
164
|
+
console.log("");
|
|
100
165
|
break;
|
|
101
166
|
}
|
|
102
|
-
|
|
103
|
-
case
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
167
|
+
|
|
168
|
+
case "register": {
|
|
169
|
+
const name = args[1];
|
|
170
|
+
const skills = args[2];
|
|
171
|
+
if (!name || !skills) {
|
|
172
|
+
console.error('Usage: register "<name>" "<skills>"');
|
|
107
173
|
process.exit(1);
|
|
108
174
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
console.log(
|
|
175
|
+
console.log(`\n🤖 Registering agent "${name}"...`);
|
|
176
|
+
try {
|
|
177
|
+
const tx = await client.registerAgent(name, skills);
|
|
178
|
+
console.log(` ✓ Registered (tx: ${tx.slice(0, 16)}...)`);
|
|
179
|
+
} catch (err: any) {
|
|
180
|
+
if (err.message?.includes("already in use")) {
|
|
181
|
+
console.log(" Already registered.");
|
|
182
|
+
} else {
|
|
183
|
+
console.error(` ✗ ${err.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
console.log("");
|
|
121
187
|
break;
|
|
122
188
|
}
|
|
123
|
-
|
|
124
|
-
case
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
category: categoryIdx > -1 ? args[categoryIdx + 1] as PrayerCategory : undefined,
|
|
132
|
-
mine: mineFlag
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
if (requests.length === 0) {
|
|
136
|
-
console.log('No prayer requests found.');
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
console.log(`Found ${requests.length} request(s):\n`);
|
|
141
|
-
for (const req of requests) {
|
|
142
|
-
console.log(`[${req.status.toUpperCase()}] ${req.id.slice(0, 8)}...`);
|
|
143
|
-
console.log(` ${req.type === 'ask' ? '🙏' : '✋'} ${req.title}`);
|
|
144
|
-
console.log(` From: ${req.from.name || req.from.id} | Category: ${req.category}`);
|
|
145
|
-
console.log(` Created: ${formatDate(req.createdAt)}`);
|
|
146
|
-
console.log();
|
|
189
|
+
|
|
190
|
+
case "agent": {
|
|
191
|
+
const wallet = args[1] ? new PublicKey(args[1]) : client.wallet;
|
|
192
|
+
const agent = await client.getAgent(wallet);
|
|
193
|
+
if (!agent) {
|
|
194
|
+
console.log("\n🤖 Agent not registered.");
|
|
195
|
+
console.log(' Run: chorus pray register "<name>" "<skills>"\n');
|
|
196
|
+
return;
|
|
147
197
|
}
|
|
198
|
+
console.log("");
|
|
199
|
+
console.log("🤖 Agent");
|
|
200
|
+
console.log("═".repeat(40));
|
|
201
|
+
console.log(` Wallet: ${shortKey(agent.wallet)}`);
|
|
202
|
+
console.log(` Name: ${agent.name}`);
|
|
203
|
+
console.log(` Skills: ${agent.skills}`);
|
|
204
|
+
console.log(` Reputation: ${agent.reputation}`);
|
|
205
|
+
console.log(` Prayers Posted: ${agent.prayersPosted}`);
|
|
206
|
+
console.log(` Prayers Answered: ${agent.prayersAnswered}`);
|
|
207
|
+
console.log(` Prayers Confirmed: ${agent.prayersConfirmed}`);
|
|
208
|
+
console.log(` Registered: ${formatTime(agent.registeredAt)}`);
|
|
209
|
+
console.log("");
|
|
148
210
|
break;
|
|
149
211
|
}
|
|
150
|
-
|
|
151
|
-
case
|
|
152
|
-
const
|
|
153
|
-
if (!
|
|
154
|
-
console.error('Usage:
|
|
212
|
+
|
|
213
|
+
case "post": {
|
|
214
|
+
const content = args[1];
|
|
215
|
+
if (!content) {
|
|
216
|
+
console.error('Usage: post "<content>" [--type knowledge] [--bounty 0.01] [--ttl 86400]');
|
|
155
217
|
process.exit(1);
|
|
156
218
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
219
|
+
|
|
220
|
+
const typeIdx = args.indexOf("--type");
|
|
221
|
+
const bountyIdx = args.indexOf("--bounty");
|
|
222
|
+
const ttlIdx = args.indexOf("--ttl");
|
|
223
|
+
|
|
224
|
+
const prayerType = typeIdx > -1 ? args[typeIdx + 1] : "knowledge";
|
|
225
|
+
const bountySOL = bountyIdx > -1 ? parseFloat(args[bountyIdx + 1]) : 0;
|
|
226
|
+
const ttl = ttlIdx > -1 ? parseInt(args[ttlIdx + 1]) : 86400;
|
|
227
|
+
const bountyLamports = Math.round(bountySOL * LAMPORTS_PER_SOL);
|
|
228
|
+
|
|
229
|
+
console.log("");
|
|
230
|
+
console.log("🙏 Posting prayer...");
|
|
231
|
+
console.log(` Type: ${prayerType}`);
|
|
232
|
+
console.log(` Content: ${content.slice(0, 80)}${content.length > 80 ? "..." : ""}`);
|
|
233
|
+
console.log(` Bounty: ${bountySOL > 0 ? `${bountySOL} SOL` : "none"}`);
|
|
234
|
+
console.log(` TTL: ${ttl}s (${(ttl / 3600).toFixed(1)}h)`);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const { tx, prayerId } = await client.postPrayer(
|
|
238
|
+
prayerType as unknown as PrayerType,
|
|
239
|
+
content,
|
|
240
|
+
bountyLamports,
|
|
241
|
+
ttl
|
|
242
|
+
);
|
|
243
|
+
console.log(` ✓ Prayer #${prayerId} posted (tx: ${tx.slice(0, 16)}...)`);
|
|
244
|
+
storeContent(prayerId, content);
|
|
245
|
+
} catch (err: any) {
|
|
246
|
+
console.error(` ✗ ${err.message}`);
|
|
165
247
|
}
|
|
166
|
-
|
|
167
|
-
console.log(formatRequest(prayers.getRequest(match.id)));
|
|
248
|
+
console.log("");
|
|
168
249
|
break;
|
|
169
250
|
}
|
|
170
|
-
|
|
171
|
-
case
|
|
172
|
-
const
|
|
173
|
-
if (!
|
|
174
|
-
console.
|
|
175
|
-
|
|
251
|
+
|
|
252
|
+
case "list": {
|
|
253
|
+
const chain = await client.getPrayerChain();
|
|
254
|
+
if (!chain) {
|
|
255
|
+
console.log("\n⛓️ Prayer Chain not initialized.\n");
|
|
256
|
+
return;
|
|
176
257
|
}
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
console.error('Request not found');
|
|
183
|
-
process.exit(1);
|
|
258
|
+
|
|
259
|
+
const total = chain.totalPrayers;
|
|
260
|
+
if (total === 0) {
|
|
261
|
+
console.log("\n🙏 No prayers yet.\n");
|
|
262
|
+
return;
|
|
184
263
|
}
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
264
|
+
|
|
265
|
+
const statusFilter = args.indexOf("--status") > -1 ? args[args.indexOf("--status") + 1]?.toLowerCase() : null;
|
|
266
|
+
const limit = args.indexOf("--limit") > -1 ? parseInt(args[args.indexOf("--limit") + 1]) : 20;
|
|
267
|
+
|
|
268
|
+
console.log("");
|
|
269
|
+
console.log(`🙏 Prayers (${total} total)`);
|
|
270
|
+
console.log("═".repeat(60));
|
|
271
|
+
|
|
272
|
+
let shown = 0;
|
|
273
|
+
for (let i = total - 1; i >= 0 && shown < limit; i--) {
|
|
274
|
+
const prayer = await client.getPrayer(i);
|
|
275
|
+
if (!prayer) continue;
|
|
276
|
+
|
|
277
|
+
const status = formatStatus(prayer.status);
|
|
278
|
+
if (statusFilter && status.toLowerCase() !== statusFilter) continue;
|
|
279
|
+
|
|
280
|
+
const type = formatType(prayer.prayerType);
|
|
281
|
+
const bounty = prayer.rewardLamports > 0 ? ` 💰${formatSOL(prayer.rewardLamports)}` : "";
|
|
282
|
+
const statusIcon = {
|
|
283
|
+
OPEN: "🟢",
|
|
284
|
+
CLAIMED: "🟡",
|
|
285
|
+
FULFILLED: "🔵",
|
|
286
|
+
CONFIRMED: "✅",
|
|
287
|
+
EXPIRED: "⏰",
|
|
288
|
+
CANCELLED: "❌",
|
|
289
|
+
}[status] || "❓";
|
|
290
|
+
|
|
291
|
+
const texts = getStoredText(prayer.id);
|
|
292
|
+
const contentDisplay = texts.content || `[hash: ${hashToHex(prayer.contentHash)}]`;
|
|
293
|
+
|
|
294
|
+
console.log(` ${statusIcon} #${prayer.id} [${status}] (${type})${bounty}`);
|
|
295
|
+
console.log(` ${contentDisplay.slice(0, 70)}${contentDisplay.length > 70 ? "..." : ""}`);
|
|
296
|
+
console.log(` From: ${shortKey(prayer.requester)} | Created: ${formatTime(prayer.createdAt)}`);
|
|
297
|
+
if (texts.answer) {
|
|
298
|
+
console.log(` 💬 ${texts.answer.slice(0, 70)}${texts.answer.length > 70 ? "..." : ""}`);
|
|
299
|
+
}
|
|
300
|
+
shown++;
|
|
193
301
|
}
|
|
302
|
+
console.log("");
|
|
194
303
|
break;
|
|
195
304
|
}
|
|
196
|
-
|
|
197
|
-
case
|
|
198
|
-
const id = args[1];
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
console.error('Usage: complete <request-id> "<result>"');
|
|
305
|
+
|
|
306
|
+
case "show": {
|
|
307
|
+
const id = parseInt(args[1]);
|
|
308
|
+
if (isNaN(id)) {
|
|
309
|
+
console.error("Usage: show <prayer-id>");
|
|
202
310
|
process.exit(1);
|
|
203
311
|
}
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
console.error('Request not found');
|
|
210
|
-
process.exit(1);
|
|
312
|
+
|
|
313
|
+
const prayer = await client.getPrayer(id);
|
|
314
|
+
if (!prayer) {
|
|
315
|
+
console.error(`\n✗ Prayer #${id} not found\n`);
|
|
316
|
+
return;
|
|
211
317
|
}
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
318
|
+
|
|
319
|
+
const texts = getStoredText(id);
|
|
320
|
+
|
|
321
|
+
console.log("");
|
|
322
|
+
console.log(`🙏 Prayer #${prayer.id}`);
|
|
323
|
+
console.log("═".repeat(50));
|
|
324
|
+
console.log(` Status: ${formatStatus(prayer.status)}`);
|
|
325
|
+
console.log(` Type: ${formatType(prayer.prayerType)}`);
|
|
326
|
+
console.log(` Requester: ${shortKey(prayer.requester)}`);
|
|
327
|
+
console.log(` Bounty: ${formatSOL(prayer.rewardLamports)}`);
|
|
328
|
+
console.log(` Created: ${formatTime(prayer.createdAt)}`);
|
|
329
|
+
console.log(` Expires: ${formatTime(prayer.expiresAt)}`);
|
|
330
|
+
console.log(` Content Hash: ${hashToHex(prayer.contentHash)}`);
|
|
331
|
+
console.log("");
|
|
332
|
+
console.log(" Content:");
|
|
333
|
+
console.log(` ${texts.content || "(off-chain — not in local cache)"}`);
|
|
334
|
+
if (prayer.claimer.toBase58() !== "11111111111111111111111111111111") {
|
|
335
|
+
console.log("");
|
|
336
|
+
console.log(` Claimer: ${shortKey(prayer.claimer)}`);
|
|
337
|
+
console.log(` Claimed at: ${formatTime(prayer.claimedAt)}`);
|
|
220
338
|
}
|
|
339
|
+
const zeroHash = prayer.answerHash.every((b: number) => b === 0);
|
|
340
|
+
if (!zeroHash) {
|
|
341
|
+
console.log("");
|
|
342
|
+
console.log(` Answer Hash: ${hashToHex(prayer.answerHash)}`);
|
|
343
|
+
console.log(" Answer:");
|
|
344
|
+
console.log(` ${texts.answer || "(off-chain — not in local cache)"}`);
|
|
345
|
+
console.log(` Fulfilled: ${formatTime(prayer.fulfilledAt)}`);
|
|
346
|
+
}
|
|
347
|
+
console.log("");
|
|
221
348
|
break;
|
|
222
349
|
}
|
|
223
|
-
|
|
224
|
-
case
|
|
225
|
-
const id = args[1];
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (!id) {
|
|
229
|
-
console.error('Usage: confirm <request-id> [--reject]');
|
|
350
|
+
|
|
351
|
+
case "claim": {
|
|
352
|
+
const id = parseInt(args[1]);
|
|
353
|
+
if (isNaN(id)) {
|
|
354
|
+
console.error("Usage: claim <prayer-id>");
|
|
230
355
|
process.exit(1);
|
|
231
356
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
console.error(
|
|
238
|
-
process.exit(1);
|
|
357
|
+
console.log(`\n🤝 Claiming prayer #${id}...`);
|
|
358
|
+
try {
|
|
359
|
+
const tx = await client.claimPrayer(id);
|
|
360
|
+
console.log(` ✓ Claimed (tx: ${tx.slice(0, 16)}...)`);
|
|
361
|
+
} catch (err: any) {
|
|
362
|
+
console.error(` ✗ ${err.message}`);
|
|
239
363
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
364
|
+
console.log("");
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
case "answer": {
|
|
369
|
+
const id = parseInt(args[1]);
|
|
370
|
+
const answer = args[2];
|
|
371
|
+
if (isNaN(id) || !answer) {
|
|
372
|
+
console.error('Usage: answer <prayer-id> "<answer>"');
|
|
246
373
|
process.exit(1);
|
|
247
374
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
console.log(`
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
375
|
+
console.log(`\n💬 Answering prayer #${id}...`);
|
|
376
|
+
console.log(` Answer: ${answer.slice(0, 80)}${answer.length > 80 ? "..." : ""}`);
|
|
377
|
+
try {
|
|
378
|
+
const tx = await client.answerPrayer(id, answer);
|
|
379
|
+
console.log(` ✓ Answered (tx: ${tx.slice(0, 16)}...)`);
|
|
380
|
+
storeAnswer(id, answer);
|
|
381
|
+
} catch (err: any) {
|
|
382
|
+
console.error(` ✗ ${err.message}`);
|
|
256
383
|
}
|
|
384
|
+
console.log("");
|
|
257
385
|
break;
|
|
258
386
|
}
|
|
259
|
-
|
|
260
|
-
case
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
console.log(
|
|
267
|
-
|
|
268
|
-
|
|
387
|
+
|
|
388
|
+
case "confirm": {
|
|
389
|
+
const id = parseInt(args[1]);
|
|
390
|
+
if (isNaN(id)) {
|
|
391
|
+
console.error("Usage: confirm <prayer-id>");
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
console.log(`\n✅ Confirming prayer #${id}...`);
|
|
395
|
+
try {
|
|
396
|
+
const tx = await client.confirmPrayer(id);
|
|
397
|
+
console.log(` ✓ Confirmed (tx: ${tx.slice(0, 16)}...)`);
|
|
398
|
+
} catch (err: any) {
|
|
399
|
+
console.error(` ✗ ${err.message}`);
|
|
400
|
+
}
|
|
401
|
+
console.log("");
|
|
269
402
|
break;
|
|
270
403
|
}
|
|
271
|
-
|
|
272
|
-
case
|
|
273
|
-
const
|
|
274
|
-
if (
|
|
275
|
-
console.
|
|
276
|
-
|
|
404
|
+
|
|
405
|
+
case "cancel": {
|
|
406
|
+
const id = parseInt(args[1]);
|
|
407
|
+
if (isNaN(id)) {
|
|
408
|
+
console.error("Usage: cancel <prayer-id>");
|
|
409
|
+
process.exit(1);
|
|
277
410
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
console.log(` ${
|
|
282
|
-
|
|
283
|
-
console.
|
|
284
|
-
console.log();
|
|
411
|
+
console.log(`\n❌ Cancelling prayer #${id}...`);
|
|
412
|
+
try {
|
|
413
|
+
const tx = await client.cancelPrayer(id);
|
|
414
|
+
console.log(` ✓ Cancelled (tx: ${tx.slice(0, 16)}...)`);
|
|
415
|
+
} catch (err: any) {
|
|
416
|
+
console.error(` ✗ ${err.message}`);
|
|
285
417
|
}
|
|
418
|
+
console.log("");
|
|
286
419
|
break;
|
|
287
420
|
}
|
|
288
|
-
|
|
289
|
-
case
|
|
290
|
-
const id = args[1];
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (!id) {
|
|
295
|
-
console.error('Usage: add-peer <id> [endpoint] [name]');
|
|
421
|
+
|
|
422
|
+
case "unclaim": {
|
|
423
|
+
const id = parseInt(args[1]);
|
|
424
|
+
if (isNaN(id)) {
|
|
425
|
+
console.error("Usage: unclaim <prayer-id>");
|
|
296
426
|
process.exit(1);
|
|
297
427
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
428
|
+
console.log(`\n🔓 Unclaiming prayer #${id}...`);
|
|
429
|
+
try {
|
|
430
|
+
const tx = await client.unclaimPrayer(id);
|
|
431
|
+
console.log(` ✓ Unclaimed (tx: ${tx.slice(0, 16)}...)`);
|
|
432
|
+
} catch (err: any) {
|
|
433
|
+
console.error(` ✗ ${err.message}`);
|
|
434
|
+
}
|
|
435
|
+
console.log("");
|
|
301
436
|
break;
|
|
302
437
|
}
|
|
303
|
-
|
|
438
|
+
|
|
304
439
|
default:
|
|
305
440
|
console.log(`
|
|
306
|
-
Prayer
|
|
441
|
+
🙏 Prayer Chain CLI (On-Chain)
|
|
307
442
|
|
|
308
443
|
Commands:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
444
|
+
chain Show prayer chain stats
|
|
445
|
+
init Initialize the prayer chain
|
|
446
|
+
register "<name>" "<skills>" Register as an agent
|
|
447
|
+
agent [wallet] Show agent profile
|
|
448
|
+
post "<content>" [options] Post a prayer
|
|
449
|
+
--type <type> knowledge|compute|review|signal|collaboration
|
|
450
|
+
--bounty <SOL> SOL bounty (e.g. 0.01)
|
|
451
|
+
--ttl <seconds> Time to live (default 86400)
|
|
452
|
+
list [--status <s>] [--limit <n>] List prayers
|
|
453
|
+
show <id> Show prayer details
|
|
454
|
+
claim <id> Claim a prayer
|
|
455
|
+
answer <id> "<answer>" Answer a claimed prayer
|
|
456
|
+
confirm <id> Confirm an answer (requester only)
|
|
457
|
+
cancel <id> Cancel an open prayer
|
|
458
|
+
unclaim <id> Unclaim a prayer
|
|
320
459
|
|
|
321
460
|
Environment:
|
|
322
|
-
|
|
323
|
-
AGENT_NAME Your agent name
|
|
324
|
-
AGENT_ADDRESS Your signing address
|
|
325
|
-
AGENT_ENDPOINT Your gateway URL
|
|
461
|
+
SOLANA_RPC_URL RPC endpoint (default: http://localhost:8899)
|
|
326
462
|
`.trim());
|
|
327
463
|
}
|
|
328
464
|
}
|
|
329
465
|
|
|
330
|
-
main().catch(
|
|
466
|
+
main().catch((err) => {
|
|
467
|
+
console.error(`\n✗ ${err.message}\n`);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
});
|