clawmoney 0.15.36 → 0.15.38
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/commands/wallet.js +111 -22
- package/package.json +1 -1
package/dist/commands/wallet.js
CHANGED
|
@@ -3,6 +3,43 @@ import ora from 'ora';
|
|
|
3
3
|
import { awalExec } from '../utils/awal.js';
|
|
4
4
|
import { apiGet } from '../utils/api.js';
|
|
5
5
|
import { loadConfig } from '../utils/config.js';
|
|
6
|
+
// Base mainnet USDC contract + balanceOf(address) ABI selector.
|
|
7
|
+
// Keeping on-chain reads as a first-class path lets `wallet balance`
|
|
8
|
+
// skip the awal Electron bridge entirely, which is notorious for
|
|
9
|
+
// cold-starting slowly or hanging if the daemon isn't warm.
|
|
10
|
+
const BASE_RPC_URL = 'https://mainnet.base.org';
|
|
11
|
+
const BASE_USDC_CONTRACT = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
12
|
+
const BALANCE_OF_SELECTOR = '0x70a08231';
|
|
13
|
+
const USDC_DECIMALS = 1_000_000;
|
|
14
|
+
async function readBaseUsdcBalance(walletAddress, timeoutMs = 8000) {
|
|
15
|
+
const paddedAddr = walletAddress.toLowerCase().replace(/^0x/, '').padStart(64, '0');
|
|
16
|
+
const data = BALANCE_OF_SELECTOR + paddedAddr;
|
|
17
|
+
const ctrl = new AbortController();
|
|
18
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
19
|
+
try {
|
|
20
|
+
const resp = await fetch(BASE_RPC_URL, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'content-type': 'application/json' },
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
jsonrpc: '2.0',
|
|
25
|
+
id: 1,
|
|
26
|
+
method: 'eth_call',
|
|
27
|
+
params: [{ to: BASE_USDC_CONTRACT, data }, 'latest'],
|
|
28
|
+
}),
|
|
29
|
+
signal: ctrl.signal,
|
|
30
|
+
});
|
|
31
|
+
const json = (await resp.json());
|
|
32
|
+
if (json.error)
|
|
33
|
+
throw new Error(json.error.message || 'RPC error');
|
|
34
|
+
if (!json.result)
|
|
35
|
+
throw new Error('empty RPC result');
|
|
36
|
+
const atomic = BigInt(json.result);
|
|
37
|
+
return Number(atomic) / USDC_DECIMALS;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
6
43
|
export async function walletStatusCommand() {
|
|
7
44
|
const spinner = ora('Getting wallet status...').start();
|
|
8
45
|
try {
|
|
@@ -20,52 +57,104 @@ export async function walletStatusCommand() {
|
|
|
20
57
|
console.error(chalk.red(err.message));
|
|
21
58
|
}
|
|
22
59
|
}
|
|
60
|
+
// Wrap a promise in a hard timeout so a hung awal process can't
|
|
61
|
+
// swallow the whole command. On timeout we surface a specific
|
|
62
|
+
// error string the caller can tell apart from generic spawn errors.
|
|
63
|
+
function withTimeout(p, ms, label) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
reject(new Error(`${label} timed out after ${ms}ms`));
|
|
67
|
+
}, ms);
|
|
68
|
+
p.then((v) => {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
resolve(v);
|
|
71
|
+
}, (e) => {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
reject(e);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
23
77
|
export async function walletBalanceCommand() {
|
|
24
78
|
const spinner = ora('Getting wallet balance...').start();
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
79
|
+
// Source of truth for on-chain balance: direct JSON-RPC to Base
|
|
80
|
+
// mainnet USDC, not `awal balance`. Reasons:
|
|
81
|
+
// - awal is an Electron app; cold-starting it via `npx` takes 3-10s
|
|
82
|
+
// and occasionally wedges under load (see GH issues around
|
|
83
|
+
// DEP0190 / pipe buffers).
|
|
84
|
+
// - We already store the wallet address in ~/.clawmoney/config.yaml
|
|
85
|
+
// at setup time, so we don't need awal to look it up.
|
|
86
|
+
// - RPC reads are idempotent, cacheable, and cost nothing.
|
|
87
|
+
// Relay earnings are fetched in parallel from the clawmoney backend.
|
|
88
|
+
// Either half is allowed to fail — we print "(unavailable)" for the
|
|
89
|
+
// broken section and keep going.
|
|
30
90
|
const config = loadConfig();
|
|
91
|
+
let walletAddress = config?.wallet_address ?? null;
|
|
92
|
+
// Fall back to awal only if we don't have a wallet address cached
|
|
93
|
+
// in the config — e.g. for users who ran setup before we started
|
|
94
|
+
// saving wallet_address. Capped at 5s so it can't block the command.
|
|
95
|
+
let awalFallbackError = null;
|
|
96
|
+
if (!walletAddress) {
|
|
97
|
+
try {
|
|
98
|
+
const awalResult = await withTimeout(awalExec(['address']), 5_000, 'awal address');
|
|
99
|
+
const data = awalResult.data;
|
|
100
|
+
if (typeof data?.address === 'string' && data.address) {
|
|
101
|
+
walletAddress = data.address;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
awalFallbackError = err.message;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
31
108
|
const relayPromise = config?.api_key
|
|
32
109
|
? apiGet("/api/v1/relay/providers/me", config.api_key)
|
|
33
110
|
.then((resp) => (resp.ok && Array.isArray(resp.data) ? resp.data : null))
|
|
34
111
|
.catch(() => null)
|
|
35
112
|
: Promise.resolve(null);
|
|
36
|
-
let
|
|
37
|
-
|
|
38
|
-
|
|
113
|
+
let usdcBalance = null;
|
|
114
|
+
let onchainError = null;
|
|
115
|
+
if (walletAddress) {
|
|
116
|
+
try {
|
|
117
|
+
usdcBalance = await readBaseUsdcBalance(walletAddress);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
onchainError = err.message;
|
|
121
|
+
}
|
|
39
122
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
console.error(chalk.red(err.message));
|
|
43
|
-
return;
|
|
123
|
+
else {
|
|
124
|
+
onchainError = awalFallbackError ?? 'no wallet address in config';
|
|
44
125
|
}
|
|
45
126
|
const relayRows = await relayPromise;
|
|
46
|
-
spinner.
|
|
127
|
+
spinner.stop();
|
|
47
128
|
console.log('');
|
|
48
|
-
console.log(chalk.bold('
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
129
|
+
console.log(chalk.bold(' Wallet'));
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log(chalk.bold(' On-chain (Base USDC)'));
|
|
132
|
+
if (walletAddress) {
|
|
133
|
+
console.log(` ${chalk.dim('Address:').padEnd(22)} ${chalk.cyan(walletAddress)}`);
|
|
134
|
+
}
|
|
135
|
+
if (usdcBalance !== null) {
|
|
136
|
+
console.log(` ${chalk.dim('USDC:').padEnd(22)} ${chalk.green('$' + usdcBalance.toFixed(2))}`);
|
|
54
137
|
}
|
|
55
138
|
else {
|
|
56
|
-
console.log(` ${
|
|
139
|
+
console.log(` ${chalk.yellow('unavailable')} ${chalk.dim('(' + (onchainError ?? 'unknown') + ')')}`);
|
|
57
140
|
}
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log(chalk.bold(' Relay earnings'));
|
|
58
143
|
if (relayRows && relayRows.length > 0) {
|
|
59
144
|
const earned = relayRows.reduce((s, p) => s + (p.total_earned_usd ?? 0), 0);
|
|
60
145
|
const withdrawn = relayRows.reduce((s, p) => s + (p.total_withdrawn_usd ?? 0), 0);
|
|
61
146
|
const pending = Math.max(0, earned - withdrawn);
|
|
62
147
|
const requests = relayRows.reduce((s, p) => s + (p.total_requests ?? 0), 0);
|
|
63
|
-
console.log('');
|
|
64
|
-
console.log(chalk.bold(' Relay earnings'));
|
|
65
148
|
console.log(` ${chalk.dim('Earned:').padEnd(22)} ${chalk.green('$' + earned.toFixed(2))}`);
|
|
66
149
|
console.log(` ${chalk.dim('Pending payout:').padEnd(22)} ${chalk.green('$' + pending.toFixed(2))}`);
|
|
67
150
|
console.log(chalk.dim(` (${relayRows.length} provider${relayRows.length === 1 ? "" : "s"} · ${requests} request${requests === 1 ? "" : "s"} served)`));
|
|
68
151
|
}
|
|
152
|
+
else if (relayRows && relayRows.length === 0) {
|
|
153
|
+
console.log(` ${chalk.dim('No providers registered yet. Run `clawmoney relay setup` to start earning.')}`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
console.log(` ${chalk.yellow('unavailable')} ${chalk.dim('(relay backend unreachable)')}`);
|
|
157
|
+
}
|
|
69
158
|
console.log('');
|
|
70
159
|
}
|
|
71
160
|
export async function walletAddressCommand() {
|