clawmoney 0.10.7 → 0.10.8

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/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { Command } from 'commander';
3
3
  import { setupCommand } from './commands/setup.js';
4
4
  import { browseCommand } from './commands/browse.js';
5
5
  import { promoteSubmitCommand, promoteVerifyCommand } from './commands/promote.js';
6
+ import { startAutoVerify } from './promote/auto-verify.js';
6
7
  import { walletStatusCommand, walletBalanceCommand, walletAddressCommand, walletSendCommand, } from './commands/wallet.js';
7
8
  import { tweetCommand } from './commands/tweet.js';
8
9
  import { gigCreateCommand, gigBrowseCommand, gigDetailCommand, gigAcceptCommand, gigDeliverCommand, gigApproveCommand, gigDisputeCommand, } from './commands/gig.js';
@@ -77,6 +78,18 @@ promote
77
78
  process.exit(1);
78
79
  }
79
80
  });
81
+ promote
82
+ .command('auto-verify')
83
+ .description('Start auto-verifier daemon (witness mode, $0.01/verification)')
84
+ .action(async () => {
85
+ try {
86
+ await startAutoVerify();
87
+ }
88
+ catch (err) {
89
+ console.error(err.message);
90
+ process.exit(1);
91
+ }
92
+ });
80
93
  // wallet
81
94
  const wallet = program.command('wallet').description('Wallet commands (via awal)');
82
95
  wallet
@@ -0,0 +1 @@
1
+ export declare function startAutoVerify(): Promise<void>;
@@ -0,0 +1,166 @@
1
+ import { apiGet, apiPost } from "../utils/api.js";
2
+ import { awalExec } from "../utils/awal.js";
3
+ import { requireConfig } from "../utils/config.js";
4
+ const POLL_INTERVAL_MS = 15 * 60 * 1000; // 15 minutes
5
+ const MAX_PER_CYCLE = 3;
6
+ const MIN_BALANCE_USD = 0.05;
7
+ const WITNESS_COST_USD = 0.01;
8
+ function log(msg) {
9
+ const ts = new Date().toISOString().replace("T", " ").slice(0, 19);
10
+ console.log(`[${ts}] ${msg}`);
11
+ }
12
+ async function getUsdcBalance() {
13
+ try {
14
+ const result = await awalExec(["balance"]);
15
+ const base = result.data.base;
16
+ const balances = base?.balances;
17
+ const usdc = balances?.USDC;
18
+ return parseFloat(String(usdc?.formatted || "0"));
19
+ }
20
+ catch {
21
+ return 0;
22
+ }
23
+ }
24
+ async function getActivePromoteTasks(apiKey) {
25
+ const resp = await apiGet("/api/v1/promote?status=active&limit=20", apiKey);
26
+ if (!resp.ok)
27
+ return [];
28
+ return resp.data.data ?? [];
29
+ }
30
+ async function getNextToVerify(taskId, apiKey) {
31
+ const resp = await apiGet(`/api/v1/promote/${taskId}/next-to-verify`, apiKey);
32
+ if (!resp.ok)
33
+ return null;
34
+ return resp.data;
35
+ }
36
+ async function scoreSubmission(task, submission) {
37
+ // Simple heuristic scoring — if content exists and is non-trivial, approve
38
+ const content = submission.content_text || "";
39
+ const hasContent = content.length > 20;
40
+ const hasProof = !!submission.proof_url;
41
+ if (!hasProof) {
42
+ return { vote: "reject", relevance: 2, quality: 1 };
43
+ }
44
+ if (!hasContent) {
45
+ // Has proof but no content text — moderate approval
46
+ return { vote: "approve", relevance: 5, quality: 5 };
47
+ }
48
+ // Check relevance to task description/requirements
49
+ const taskWords = (task.description + " " + task.requirements).toLowerCase().split(/\s+/);
50
+ const contentWords = content.toLowerCase().split(/\s+/);
51
+ const overlap = contentWords.filter((w) => taskWords.includes(w) && w.length > 3).length;
52
+ const relevance = Math.min(10, Math.max(3, Math.round(overlap * 1.5 + 4)));
53
+ const quality = Math.min(10, Math.max(3, Math.round(content.length / 50 + 4)));
54
+ return { vote: "approve", relevance, quality };
55
+ }
56
+ async function verifySubmission(task, submission, apiKey) {
57
+ // 1. Get witness proof via x402
58
+ const tweetMatch = submission.proof_url.match(/status\/(\d+)/);
59
+ if (!tweetMatch) {
60
+ log(` Skip: cannot extract tweet ID from ${submission.proof_url}`);
61
+ return false;
62
+ }
63
+ const tweetId = tweetMatch[1];
64
+ let witnessData;
65
+ try {
66
+ witnessData = await awalExec([
67
+ "x402",
68
+ "pay",
69
+ `https://witness.bnbot.ai/x/${tweetId}`,
70
+ ]);
71
+ }
72
+ catch (err) {
73
+ log(` Witness failed: ${err.message}`);
74
+ return false;
75
+ }
76
+ const wd = witnessData.data;
77
+ const wdInner = wd?.data;
78
+ const proof = (wdInner?.proof ?? wd?.proof ?? null);
79
+ if (!proof) {
80
+ log(` No proof in witness response`);
81
+ return false;
82
+ }
83
+ // 2. Score with heuristic
84
+ const { vote, relevance, quality } = await scoreSubmission(task, submission);
85
+ // 3. Submit verification
86
+ const resp = await apiPost(`/api/v1/promote/submissions/${submission.submission_id}/verify`, {
87
+ vote,
88
+ relevance_score: relevance,
89
+ quality_score: quality,
90
+ tweet_proof: {
91
+ payload: proof.payload,
92
+ signature: proof.signature,
93
+ signer: proof.signer,
94
+ timestamp: proof.timestamp,
95
+ },
96
+ }, apiKey);
97
+ if (!resp.ok) {
98
+ const detail = typeof resp.data === "object" && resp.data
99
+ ? resp.data.detail || JSON.stringify(resp.data)
100
+ : String(resp.data);
101
+ log(` Verify failed: ${detail}`);
102
+ return false;
103
+ }
104
+ log(` Verified: ${vote} R:${relevance} Q:${quality}`);
105
+ return true;
106
+ }
107
+ async function runCycle(apiKey) {
108
+ // Check balance
109
+ const balance = await getUsdcBalance();
110
+ if (balance < MIN_BALANCE_USD) {
111
+ log(`Balance $${balance.toFixed(3)} below minimum $${MIN_BALANCE_USD}. Pausing.`);
112
+ return;
113
+ }
114
+ const maxAffordable = Math.floor(balance / WITNESS_COST_USD);
115
+ const maxThisCycle = Math.min(MAX_PER_CYCLE, maxAffordable);
116
+ log(`Balance: $${balance.toFixed(3)} — can verify up to ${maxThisCycle} this cycle`);
117
+ // Get active promote tasks
118
+ const tasks = await getActivePromoteTasks(apiKey);
119
+ if (tasks.length === 0) {
120
+ log("No active promote tasks found.");
121
+ return;
122
+ }
123
+ log(`Found ${tasks.length} active promote task(s)`);
124
+ let verified = 0;
125
+ for (const task of tasks) {
126
+ if (verified >= maxThisCycle)
127
+ break;
128
+ const submission = await getNextToVerify(task.id, apiKey);
129
+ if (!submission)
130
+ continue;
131
+ log(`Verifying: task="${task.title.slice(0, 40)}" sub=${submission.submission_id.slice(0, 8)} (phase ${submission.phase})`);
132
+ const ok = await verifySubmission(task, submission, apiKey);
133
+ if (ok)
134
+ verified++;
135
+ }
136
+ log(`Cycle complete: ${verified} verification(s) submitted.`);
137
+ }
138
+ export async function startAutoVerify() {
139
+ const config = requireConfig();
140
+ const apiKey = config.api_key;
141
+ log("Auto-verify started. Polling every 15 minutes, max 3/cycle.");
142
+ log(`Balance protection: pause below $${MIN_BALANCE_USD}`);
143
+ log("Press Ctrl+C to stop.\n");
144
+ // Run immediately
145
+ await runCycle(apiKey);
146
+ // Schedule recurring
147
+ const timer = setInterval(async () => {
148
+ try {
149
+ await runCycle(apiKey);
150
+ }
151
+ catch (err) {
152
+ log(`Cycle error: ${err.message}`);
153
+ }
154
+ }, POLL_INTERVAL_MS);
155
+ // Graceful shutdown
156
+ process.on("SIGINT", () => {
157
+ clearInterval(timer);
158
+ log("Auto-verify stopped.");
159
+ process.exit(0);
160
+ });
161
+ process.on("SIGTERM", () => {
162
+ clearInterval(timer);
163
+ log("Auto-verify stopped.");
164
+ process.exit(0);
165
+ });
166
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.10.7",
3
+ "version": "0.10.8",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {