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 +13 -0
- package/dist/promote/auto-verify.d.ts +1 -0
- package/dist/promote/auto-verify.js +166 -0
- package/package.json +1 -1
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
|
+
}
|