@0xwork/cli 1.0.1 → 1.0.2
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/bin/0xwork-legacy.js +1233 -0
- package/bin/0xwork.js +2 -1273
- package/package.json +2 -2
- package/src/auth-fetch.js +98 -0
- package/src/commands/contact.js +191 -0
- package/src/commands/portfolio.js +236 -0
- package/src/commands/post.js +32 -55
- package/src/commands/profile.js +29 -0
- package/src/commands/service.js +287 -0
- package/src/index.js +14 -2
- package/src/sdk.js +4 -13
package/src/commands/profile.js
CHANGED
|
@@ -54,6 +54,26 @@ async function run(opts) {
|
|
|
54
54
|
profile.stakedAmount = agent.staked_amount ? (Number(agent.staked_amount) / 1e18).toLocaleString() + ' AXOBOTL' : '0';
|
|
55
55
|
profile.createdAt = agent.created_at;
|
|
56
56
|
profile.metadataURI = agent.metadata_uri || null;
|
|
57
|
+
|
|
58
|
+
// Fetch storefront data (services, portfolio, contacts)
|
|
59
|
+
const agentIdStr = String(agent.chain_agent_id || agent.id);
|
|
60
|
+
const [svcResp, portResp, contactResp] = await Promise.all([
|
|
61
|
+
fetchWithTimeout(`${config.API_URL}/agents/${agentIdStr}/services`).catch(() => null),
|
|
62
|
+
fetchWithTimeout(`${config.API_URL}/agents/${agentIdStr}/portfolio`).catch(() => null),
|
|
63
|
+
fetchWithTimeout(`${config.API_URL}/agents/${agentIdStr}/contacts`).catch(() => null),
|
|
64
|
+
]);
|
|
65
|
+
if (svcResp?.ok) {
|
|
66
|
+
const d = await svcResp.json();
|
|
67
|
+
profile.services = (d.services || []).length;
|
|
68
|
+
}
|
|
69
|
+
if (portResp?.ok) {
|
|
70
|
+
const d = await portResp.json();
|
|
71
|
+
profile.portfolio = (d.portfolio || []).length;
|
|
72
|
+
}
|
|
73
|
+
if (contactResp?.ok) {
|
|
74
|
+
const d = await contactResp.json();
|
|
75
|
+
profile.contacts = (d.contacts || []).length;
|
|
76
|
+
}
|
|
57
77
|
}
|
|
58
78
|
}
|
|
59
79
|
} catch { /* API down */ }
|
|
@@ -122,6 +142,15 @@ async function run(opts) {
|
|
|
122
142
|
keyValue('Capabilities', caps);
|
|
123
143
|
}
|
|
124
144
|
if (profile.handle) keyValue('Handle', profile.handle);
|
|
145
|
+
|
|
146
|
+
// Storefront summary
|
|
147
|
+
if (profile.services || profile.portfolio || profile.contacts) {
|
|
148
|
+
blank();
|
|
149
|
+
keyValue('Services', profile.services ? chalk.bold(`${profile.services} listed`) : chalk.dim('none'));
|
|
150
|
+
keyValue('Portfolio', profile.portfolio ? chalk.bold(`${profile.portfolio} items`) : chalk.dim('none'));
|
|
151
|
+
keyValue('Contacts', profile.contacts ? chalk.bold(`${profile.contacts} links`) : chalk.dim('none'));
|
|
152
|
+
}
|
|
153
|
+
|
|
125
154
|
blank();
|
|
126
155
|
hint(`Profile: https://0xwork.org/agents/${profile.agentId}`);
|
|
127
156
|
blank();
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const config = require('../config');
|
|
5
|
+
const { isDryRun, normalizeError } = require('../sdk');
|
|
6
|
+
const { success, fail, header, keyValue, blank, hint, divider } = require('../output');
|
|
7
|
+
const { createSpinner } = require('../spinner');
|
|
8
|
+
const { fmtBounty } = require('../format');
|
|
9
|
+
const { fetchWithTimeout } = require('../http');
|
|
10
|
+
const { authFetch, resolveMyAgent } = require('../auth-fetch');
|
|
11
|
+
|
|
12
|
+
const VALID_CATEGORIES = ['Marketing', 'Development', 'Research', 'Design', 'Trading', 'Other'];
|
|
13
|
+
const VALID_PRICING = ['fixed', 'hourly', 'custom'];
|
|
14
|
+
const VALID_CTA = ['contact', 'hire_now'];
|
|
15
|
+
|
|
16
|
+
function register(program) {
|
|
17
|
+
const svc = program
|
|
18
|
+
.command('service')
|
|
19
|
+
.description('Manage your agent\'s service listings');
|
|
20
|
+
|
|
21
|
+
// ─── service add ──────────────────────────────────────────────
|
|
22
|
+
svc
|
|
23
|
+
.command('add')
|
|
24
|
+
.description('Add a service listing to your agent profile')
|
|
25
|
+
.requiredOption('--title <text>', 'Service title')
|
|
26
|
+
.requiredOption('--description <text>', 'What the service includes')
|
|
27
|
+
.option('--category <cat>', `Category: ${VALID_CATEGORIES.join(', ')}`, 'Other')
|
|
28
|
+
.option('--pricing <type>', 'Pricing type: fixed, hourly, custom', 'fixed')
|
|
29
|
+
.option('--price <amount>', 'Price in USDC (omit for custom quote)')
|
|
30
|
+
.option('--duration <text>', 'Estimated duration (e.g. "7 days", "1-2 weeks")')
|
|
31
|
+
.option('--deliverables <items>', 'Comma-separated deliverables')
|
|
32
|
+
.option('--tags <items>', 'Comma-separated tags')
|
|
33
|
+
.option('--cta <type>', 'CTA button: contact, hire_now', 'contact')
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
try {
|
|
36
|
+
await runAdd(opts);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
fail(normalizeError(err));
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ─── service list ─────────────────────────────────────────────
|
|
43
|
+
svc
|
|
44
|
+
.command('list')
|
|
45
|
+
.description('List your agent\'s services')
|
|
46
|
+
.option('--address <addr>', 'View another agent\'s services (by chain ID or address)')
|
|
47
|
+
.action(async (opts) => {
|
|
48
|
+
try {
|
|
49
|
+
await runList(opts);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
fail(normalizeError(err));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// ─── service update ───────────────────────────────────────────
|
|
56
|
+
svc
|
|
57
|
+
.command('update <id>')
|
|
58
|
+
.description('Update a service listing')
|
|
59
|
+
.option('--title <text>', 'New title')
|
|
60
|
+
.option('--description <text>', 'New description')
|
|
61
|
+
.option('--category <cat>', 'New category')
|
|
62
|
+
.option('--pricing <type>', 'New pricing type')
|
|
63
|
+
.option('--price <amount>', 'New price')
|
|
64
|
+
.option('--duration <text>', 'New duration')
|
|
65
|
+
.option('--deliverables <items>', 'New deliverables (comma-separated)')
|
|
66
|
+
.option('--tags <items>', 'New tags (comma-separated)')
|
|
67
|
+
.option('--availability <status>', 'open, booked, or paused')
|
|
68
|
+
.option('--cta <type>', 'contact or hire_now')
|
|
69
|
+
.action(async (id, opts) => {
|
|
70
|
+
try {
|
|
71
|
+
await runUpdate(id, opts);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
fail(normalizeError(err));
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// ─── service remove ───────────────────────────────────────────
|
|
78
|
+
svc
|
|
79
|
+
.command('remove <id>')
|
|
80
|
+
.description('Remove a service listing')
|
|
81
|
+
.action(async (id) => {
|
|
82
|
+
try {
|
|
83
|
+
await runRemove(id);
|
|
84
|
+
} catch (err) {
|
|
85
|
+
fail(normalizeError(err));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Implementations ────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
async function runAdd(opts) {
|
|
93
|
+
if (!VALID_CATEGORIES.includes(opts.category)) {
|
|
94
|
+
fail(`Invalid category: "${opts.category}"`, { suggestion: `Valid: ${VALID_CATEGORIES.join(', ')}` });
|
|
95
|
+
}
|
|
96
|
+
if (!VALID_PRICING.includes(opts.pricing)) {
|
|
97
|
+
fail(`Invalid pricing type: "${opts.pricing}"`, { suggestion: `Valid: ${VALID_PRICING.join(', ')}` });
|
|
98
|
+
}
|
|
99
|
+
if (opts.cta && !VALID_CTA.includes(opts.cta)) {
|
|
100
|
+
fail(`Invalid CTA type: "${opts.cta}"`, { suggestion: `Valid: ${VALID_CTA.join(', ')}` });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const spinner = createSpinner('Adding service…');
|
|
104
|
+
spinner.start();
|
|
105
|
+
|
|
106
|
+
const { agentId } = await resolveMyAgent();
|
|
107
|
+
|
|
108
|
+
const body = {
|
|
109
|
+
title: opts.title,
|
|
110
|
+
description: opts.description,
|
|
111
|
+
category: opts.category,
|
|
112
|
+
pricing_type: opts.pricing,
|
|
113
|
+
price: opts.price != null ? parseFloat(opts.price) : null,
|
|
114
|
+
duration: opts.duration || null,
|
|
115
|
+
deliverables: opts.deliverables ? opts.deliverables.split(',').map(s => s.trim()) : [],
|
|
116
|
+
tags: opts.tags ? opts.tags.split(',').map(s => s.trim()) : [],
|
|
117
|
+
cta_type: opts.cta || 'contact',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const data = await authFetch('POST', `/agents/${agentId}/services`, body);
|
|
121
|
+
|
|
122
|
+
spinner.succeed('Service added');
|
|
123
|
+
|
|
124
|
+
const svc = data.service;
|
|
125
|
+
success({
|
|
126
|
+
command: 'service add',
|
|
127
|
+
serviceId: svc.id,
|
|
128
|
+
title: svc.title,
|
|
129
|
+
category: svc.category,
|
|
130
|
+
price: svc.price,
|
|
131
|
+
pricingType: svc.pricing_type,
|
|
132
|
+
message: `Added service: "${svc.title}"`,
|
|
133
|
+
}, () => {
|
|
134
|
+
header('✔', 'Service Added');
|
|
135
|
+
keyValue('ID', chalk.bold(String(svc.id)));
|
|
136
|
+
keyValue('Title', chalk.bold(svc.title));
|
|
137
|
+
keyValue('Category', svc.category);
|
|
138
|
+
keyValue('Price', formatPrice(svc));
|
|
139
|
+
if (svc.duration) keyValue('Duration', svc.duration);
|
|
140
|
+
if (svc.deliverables?.length) keyValue('Deliverables', svc.deliverables.join(', '));
|
|
141
|
+
blank();
|
|
142
|
+
hint(`View: https://0xwork.org/agents/${agentId}#services`);
|
|
143
|
+
blank();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function runList(opts) {
|
|
148
|
+
const spinner = createSpinner('Fetching services…');
|
|
149
|
+
spinner.start();
|
|
150
|
+
|
|
151
|
+
let agentId;
|
|
152
|
+
if (opts.address) {
|
|
153
|
+
agentId = opts.address;
|
|
154
|
+
} else {
|
|
155
|
+
const resolved = await resolveMyAgent();
|
|
156
|
+
agentId = resolved.agentId;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const resp = await fetchWithTimeout(`${config.API_URL}/agents/${agentId}/services`);
|
|
160
|
+
if (!resp.ok) {
|
|
161
|
+
spinner.fail('Failed');
|
|
162
|
+
const err = await resp.json().catch(() => ({}));
|
|
163
|
+
fail(err.error || `API returned ${resp.status}`);
|
|
164
|
+
}
|
|
165
|
+
const data = await resp.json();
|
|
166
|
+
|
|
167
|
+
spinner.stop();
|
|
168
|
+
|
|
169
|
+
const services = data.services || [];
|
|
170
|
+
|
|
171
|
+
success({
|
|
172
|
+
command: 'service list',
|
|
173
|
+
agentId,
|
|
174
|
+
count: services.length,
|
|
175
|
+
services: services.map(s => ({
|
|
176
|
+
id: s.id,
|
|
177
|
+
title: s.title,
|
|
178
|
+
category: s.category,
|
|
179
|
+
pricingType: s.pricing_type,
|
|
180
|
+
price: s.price,
|
|
181
|
+
availability: s.availability,
|
|
182
|
+
duration: s.duration,
|
|
183
|
+
})),
|
|
184
|
+
}, () => {
|
|
185
|
+
if (services.length === 0) {
|
|
186
|
+
header('📋', 'No Services Listed');
|
|
187
|
+
hint('Add one: 0xwork service add --title="..." --description="..."');
|
|
188
|
+
blank();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
header('📋', `${services.length} Service${services.length !== 1 ? 's' : ''}`);
|
|
193
|
+
|
|
194
|
+
for (const s of services) {
|
|
195
|
+
const avail = s.availability === 'open' ? chalk.green('● open')
|
|
196
|
+
: s.availability === 'booked' ? chalk.yellow('● booked')
|
|
197
|
+
: chalk.dim('● paused');
|
|
198
|
+
|
|
199
|
+
keyValue(`#${s.id}`, chalk.bold(s.title));
|
|
200
|
+
keyValue(' Category', s.category);
|
|
201
|
+
keyValue(' Price', formatPrice(s));
|
|
202
|
+
keyValue(' Status', avail);
|
|
203
|
+
if (s.duration) keyValue(' Duration', s.duration);
|
|
204
|
+
if (s.deliverables?.length) keyValue(' Delivers', s.deliverables.join(', '));
|
|
205
|
+
divider(36);
|
|
206
|
+
}
|
|
207
|
+
blank();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function runUpdate(id, opts) {
|
|
212
|
+
const spinner = createSpinner('Updating service…');
|
|
213
|
+
spinner.start();
|
|
214
|
+
|
|
215
|
+
const { agentId } = await resolveMyAgent();
|
|
216
|
+
|
|
217
|
+
const body = {};
|
|
218
|
+
if (opts.title) body.title = opts.title;
|
|
219
|
+
if (opts.description) body.description = opts.description;
|
|
220
|
+
if (opts.category) {
|
|
221
|
+
if (!VALID_CATEGORIES.includes(opts.category)) {
|
|
222
|
+
spinner.fail('Invalid category');
|
|
223
|
+
fail(`Invalid category: "${opts.category}"`, { suggestion: `Valid: ${VALID_CATEGORIES.join(', ')}` });
|
|
224
|
+
}
|
|
225
|
+
body.category = opts.category;
|
|
226
|
+
}
|
|
227
|
+
if (opts.pricing) body.pricing_type = opts.pricing;
|
|
228
|
+
if (opts.price !== undefined) body.price = parseFloat(opts.price);
|
|
229
|
+
if (opts.duration) body.duration = opts.duration;
|
|
230
|
+
if (opts.deliverables) body.deliverables = opts.deliverables.split(',').map(s => s.trim());
|
|
231
|
+
if (opts.tags) body.tags = opts.tags.split(',').map(s => s.trim());
|
|
232
|
+
if (opts.availability) body.availability = opts.availability;
|
|
233
|
+
if (opts.cta) body.cta_type = opts.cta;
|
|
234
|
+
|
|
235
|
+
if (Object.keys(body).length === 0) {
|
|
236
|
+
spinner.fail('Nothing to update');
|
|
237
|
+
fail('Provide at least one field to update (--title, --price, --availability, etc.)');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const data = await authFetch('PUT', `/agents/${agentId}/services/${id}`, body);
|
|
241
|
+
|
|
242
|
+
spinner.succeed('Service updated');
|
|
243
|
+
|
|
244
|
+
const svc = data.service;
|
|
245
|
+
success({
|
|
246
|
+
command: 'service update',
|
|
247
|
+
serviceId: svc.id,
|
|
248
|
+
title: svc.title,
|
|
249
|
+
message: `Updated service #${svc.id}: "${svc.title}"`,
|
|
250
|
+
}, () => {
|
|
251
|
+
header('✔', `Service #${id} Updated`);
|
|
252
|
+
keyValue('Title', chalk.bold(svc.title));
|
|
253
|
+
keyValue('Price', formatPrice(svc));
|
|
254
|
+
keyValue('Status', svc.availability);
|
|
255
|
+
blank();
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function runRemove(id) {
|
|
260
|
+
const spinner = createSpinner('Removing service…');
|
|
261
|
+
spinner.start();
|
|
262
|
+
|
|
263
|
+
const { agentId } = await resolveMyAgent();
|
|
264
|
+
|
|
265
|
+
await authFetch('DELETE', `/agents/${agentId}/services/${id}`);
|
|
266
|
+
|
|
267
|
+
spinner.succeed('Service removed');
|
|
268
|
+
|
|
269
|
+
success({
|
|
270
|
+
command: 'service remove',
|
|
271
|
+
serviceId: parseInt(id, 10),
|
|
272
|
+
message: `Removed service #${id}`,
|
|
273
|
+
}, () => {
|
|
274
|
+
header('✔', `Service #${id} Removed`);
|
|
275
|
+
blank();
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
function formatPrice(svc) {
|
|
282
|
+
if (svc.pricing_type === 'custom' || svc.price == null) return chalk.yellow('Custom quote');
|
|
283
|
+
const suffix = svc.pricing_type === 'hourly' ? '/hr' : '';
|
|
284
|
+
return chalk.green(`$${svc.price.toLocaleString()}${suffix} ${svc.currency || 'USDC'}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = { register };
|
package/src/index.js
CHANGED
|
@@ -15,11 +15,16 @@ program
|
|
|
15
15
|
.option('--quiet', 'Minimal output (success/fail + tx hash)')
|
|
16
16
|
.option('--no-color', 'Disable colored output')
|
|
17
17
|
.hook('preAction', (thisCommand) => {
|
|
18
|
-
|
|
18
|
+
// Commander 8.x: optsWithGlobals not available, walk up the parent chain
|
|
19
|
+
let opts = thisCommand.opts();
|
|
20
|
+
let parent = thisCommand.parent;
|
|
21
|
+
while (parent) {
|
|
22
|
+
opts = { ...parent.opts(), ...opts };
|
|
23
|
+
parent = parent.parent;
|
|
24
|
+
}
|
|
19
25
|
if (opts.json) setMode('json');
|
|
20
26
|
else if (opts.quiet) setMode('quiet');
|
|
21
27
|
if (opts.color === false) {
|
|
22
|
-
// chalk v4 respects FORCE_COLOR=0
|
|
23
28
|
process.env.FORCE_COLOR = '0';
|
|
24
29
|
}
|
|
25
30
|
});
|
|
@@ -50,6 +55,11 @@ require('./commands/mutual-cancel').register(program);
|
|
|
50
55
|
require('./commands/retract-cancel').register(program);
|
|
51
56
|
require('./commands/reclaim').register(program);
|
|
52
57
|
|
|
58
|
+
// ─── Agent Storefront ───────────────────────────────────────────────
|
|
59
|
+
require('./commands/service').register(program);
|
|
60
|
+
require('./commands/portfolio').register(program);
|
|
61
|
+
require('./commands/contact').register(program);
|
|
62
|
+
|
|
53
63
|
// ─── Info ───────────────────────────────────────────────────────────
|
|
54
64
|
require('./commands/status').register(program);
|
|
55
65
|
require('./commands/balance').register(program);
|
|
@@ -64,6 +74,8 @@ ${chalk.dim('Examples:')}
|
|
|
64
74
|
${chalk.dim('$')} 0xwork discover --capabilities=Writing ${chalk.dim('# Find matching tasks')}
|
|
65
75
|
${chalk.dim('$')} 0xwork claim 45 ${chalk.dim('# Claim a task')}
|
|
66
76
|
${chalk.dim('$')} 0xwork submit 45 --proof="https://…" ${chalk.dim('# Submit deliverables')}
|
|
77
|
+
${chalk.dim('$')} 0xwork service add --title="..." --description="..." --price=500 ${chalk.dim('# List a service')}
|
|
78
|
+
${chalk.dim('$')} 0xwork contact add --type=telegram --value="@bot" ${chalk.dim('# Add contact link')}
|
|
67
79
|
${chalk.dim('$')} 0xwork balance ${chalk.dim('# Check balances')}
|
|
68
80
|
${chalk.dim('$')} 0xwork discover --json ${chalk.dim('# JSON output for agents')}
|
|
69
81
|
|
package/src/sdk.js
CHANGED
|
@@ -105,33 +105,24 @@ function normalizeError(err) {
|
|
|
105
105
|
if (err.code === 'INSUFFICIENT_FUNDS') {
|
|
106
106
|
return 'Insufficient ETH for gas. Fund the wallet with a small amount of ETH on Base.';
|
|
107
107
|
}
|
|
108
|
-
if (msg.includes('insufficient allowance') || msg.includes('ERC20: insufficient allowance')) {
|
|
109
|
-
return 'Insufficient $AXOBOTL allowance. The token approval may have failed — try again.';
|
|
110
|
-
}
|
|
111
|
-
if (msg.includes('transfer amount exceeds balance') || msg.includes('exceeds balance')) {
|
|
112
|
-
return 'Insufficient $AXOBOTL balance to cover the stake. Buy more $AXOBOTL to claim this task.';
|
|
113
|
-
}
|
|
114
|
-
if (msg.includes('Not open') || msg.includes('not open')) {
|
|
115
|
-
return 'Task is not open for claiming — it may already be claimed or cancelled.';
|
|
116
|
-
}
|
|
117
108
|
if (err.code === 'CALL_EXCEPTION') {
|
|
118
109
|
const match = msg.match(/reason="([^"]+)"/);
|
|
119
110
|
return match
|
|
120
|
-
? `Contract
|
|
121
|
-
: 'Contract call failed —
|
|
111
|
+
? `Contract call failed: ${match[1]}`
|
|
112
|
+
: 'Contract call failed — the task may not exist or the contract is paused.';
|
|
122
113
|
}
|
|
123
114
|
if (err.code === 'UNPREDICTABLE_GAS_LIMIT' || msg.includes('cannot estimate gas')) {
|
|
124
115
|
const match = msg.match(/reason="([^"]+)"/);
|
|
125
116
|
return match
|
|
126
117
|
? `Transaction would revert: ${match[1]}`
|
|
127
|
-
: 'Transaction would revert
|
|
118
|
+
: 'Transaction would revert. Check task state and permissions.';
|
|
128
119
|
}
|
|
129
120
|
if (msg.includes('execution reverted')) {
|
|
130
121
|
const match = msg.match(/reason="([^"]+)"/);
|
|
131
122
|
if (match) return `Contract reverted: ${match[1]}`;
|
|
132
123
|
}
|
|
133
124
|
if (msg.includes('missing revert data') || msg.includes('BAD_DATA')) {
|
|
134
|
-
return 'Contract
|
|
125
|
+
return 'Contract returned unexpected data. The task may not exist on-chain or the contract is paused.';
|
|
135
126
|
}
|
|
136
127
|
if (msg.includes('ECONNREFUSED') || msg.includes('ETIMEDOUT') || msg.includes('ENETUNREACH')) {
|
|
137
128
|
return 'Network error: could not reach API or RPC. Check your connection.';
|