web5-cli 0.1.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/bin/web5-cli.js +3 -0
- package/dist/commands/did.d.ts +2 -0
- package/dist/commands/did.js +59 -0
- package/dist/commands/keystore.d.ts +2 -0
- package/dist/commands/keystore.js +48 -0
- package/dist/commands/pds.d.ts +2 -0
- package/dist/commands/pds.js +144 -0
- package/dist/commands/wallet.d.ts +2 -0
- package/dist/commands/wallet.js +52 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +16 -0
- package/dist/utils/common.d.ts +20 -0
- package/dist/utils/common.js +75 -0
- package/dist/utils/did.d.ts +18 -0
- package/dist/utils/did.js +252 -0
- package/dist/utils/keystore.d.ts +9 -0
- package/dist/utils/keystore.js +94 -0
- package/dist/utils/pds.d.ts +53 -0
- package/dist/utils/pds.js +331 -0
- package/dist/utils/wallet.d.ts +12 -0
- package/dist/utils/wallet.js +148 -0
- package/package.json +40 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { error, success, hexFromBytes, bytesFromHex } from './common.js';
|
|
2
|
+
import { AtpAgent } from 'web5-api';
|
|
3
|
+
import { CID } from 'multiformats';
|
|
4
|
+
import * as cbor from '@ipld/dag-cbor';
|
|
5
|
+
import { Secp256k1Keypair } from '@atproto/crypto';
|
|
6
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
7
|
+
function createAgent(pds) {
|
|
8
|
+
return new AtpAgent({ service: `https://${pds}` });
|
|
9
|
+
}
|
|
10
|
+
function checkUsernameFormat(username) {
|
|
11
|
+
if (username.length < 4 || username.length > 18) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const usernameRegex = /^[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]$/;
|
|
15
|
+
return usernameRegex.test(username);
|
|
16
|
+
}
|
|
17
|
+
export const pdsManager = {
|
|
18
|
+
async checkUsername(username) {
|
|
19
|
+
const isValid = checkUsernameFormat(username);
|
|
20
|
+
success(isValid ? 'valid' : 'invalid');
|
|
21
|
+
},
|
|
22
|
+
async getDidByUsername(username, pds) {
|
|
23
|
+
try {
|
|
24
|
+
const handle = `${username.toLowerCase()}.${pds}`;
|
|
25
|
+
const url = `https://${handle}/.well-known/atproto-did`;
|
|
26
|
+
const response = await fetch(url);
|
|
27
|
+
const text = await response.text();
|
|
28
|
+
if (text.trim().startsWith('did:ckb')) {
|
|
29
|
+
success(text.trim());
|
|
30
|
+
}
|
|
31
|
+
else if (text.includes('User not found')) {
|
|
32
|
+
success('');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
error('Failed to fetch DID');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
error(`Failed to get DID: ${e instanceof Error ? e.message : String(e)}`);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
async createAccount(pds, username, didKey, did, ckbAddress, signKey) {
|
|
43
|
+
try {
|
|
44
|
+
const agent = createAgent(pds);
|
|
45
|
+
const handle = `${username.toLowerCase()}.${pds}`;
|
|
46
|
+
const res = await agent.fans.web5.ckb.preCreateAccount({
|
|
47
|
+
handle,
|
|
48
|
+
signingKey: didKey,
|
|
49
|
+
did,
|
|
50
|
+
});
|
|
51
|
+
if (!res.success) {
|
|
52
|
+
error('Pre-create account failed');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const uncommit = {
|
|
56
|
+
did: res.data.did,
|
|
57
|
+
version: 3,
|
|
58
|
+
rev: res.data.rev,
|
|
59
|
+
prev: null,
|
|
60
|
+
data: CID.parse(res.data.data),
|
|
61
|
+
};
|
|
62
|
+
const encoded = cbor.encode(uncommit);
|
|
63
|
+
const unSignBytesHex = hexFromBytes(encoded).slice(2);
|
|
64
|
+
if (unSignBytesHex !== res.data.unSignBytes) {
|
|
65
|
+
error('Sign bytes not consistent');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const keypair = await Secp256k1Keypair.import(bytesFromHex(signKey));
|
|
69
|
+
const sig = await keypair.sign(encoded);
|
|
70
|
+
const params = {
|
|
71
|
+
handle,
|
|
72
|
+
password: '',
|
|
73
|
+
signingKey: didKey,
|
|
74
|
+
ckbAddr: ckbAddress,
|
|
75
|
+
root: {
|
|
76
|
+
did: res.data.did,
|
|
77
|
+
version: 3,
|
|
78
|
+
rev: res.data.rev,
|
|
79
|
+
prev: res.data.prev,
|
|
80
|
+
data: res.data.data,
|
|
81
|
+
signedBytes: hexFromBytes(sig),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
const createRes = await agent.web5CreateAccount(params);
|
|
85
|
+
if (!createRes.success) {
|
|
86
|
+
error('Create account failed');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const userInfo = {
|
|
90
|
+
accessJwt: createRes.data.accessJwt,
|
|
91
|
+
refreshJwt: createRes.data.refreshJwt,
|
|
92
|
+
handle: createRes.data.handle,
|
|
93
|
+
did: createRes.data.did,
|
|
94
|
+
};
|
|
95
|
+
success(userInfo);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
error(`Failed to create account: ${e instanceof Error ? e.message : String(e)}`);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
async deleteAccount(pds, didKey, did, ckbAddress, signKey) {
|
|
102
|
+
try {
|
|
103
|
+
const agent = createAgent(pds);
|
|
104
|
+
const preDelete = await agent.fans.web5.ckb.preIndexAction({
|
|
105
|
+
did,
|
|
106
|
+
ckbAddr: ckbAddress,
|
|
107
|
+
index: { $type: 'fans.web5.ckb.preIndexAction#deleteAccount' },
|
|
108
|
+
});
|
|
109
|
+
const keypair = await Secp256k1Keypair.import(bytesFromHex(signKey));
|
|
110
|
+
const sig = await keypair.sign(new TextEncoder().encode(preDelete.data.message));
|
|
111
|
+
const deleteInfo = await agent.fans.web5.ckb.indexAction({
|
|
112
|
+
did,
|
|
113
|
+
message: preDelete.data.message,
|
|
114
|
+
signingKey: didKey,
|
|
115
|
+
signedBytes: hexFromBytes(sig),
|
|
116
|
+
ckbAddr: ckbAddress,
|
|
117
|
+
index: { $type: 'fans.web5.ckb.indexAction#deleteAccount' },
|
|
118
|
+
});
|
|
119
|
+
success(deleteInfo.success);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
error(`Failed to delete account: ${e instanceof Error ? e.message : String(e)}`);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
async login(pds, didKey, did, ckbAddress, signKey) {
|
|
126
|
+
try {
|
|
127
|
+
const agent = createAgent(pds);
|
|
128
|
+
const preLogin = await agent.fans.web5.ckb.preIndexAction({
|
|
129
|
+
did,
|
|
130
|
+
ckbAddr: ckbAddress,
|
|
131
|
+
index: { $type: 'fans.web5.ckb.preIndexAction#createSession' },
|
|
132
|
+
});
|
|
133
|
+
const keypair = await Secp256k1Keypair.import(bytesFromHex(signKey));
|
|
134
|
+
const sig = await keypair.sign(new TextEncoder().encode(preLogin.data.message));
|
|
135
|
+
const loginInfo = await agent.web5Login({
|
|
136
|
+
did,
|
|
137
|
+
message: preLogin.data.message,
|
|
138
|
+
signingKey: didKey,
|
|
139
|
+
signedBytes: hexFromBytes(sig),
|
|
140
|
+
ckbAddr: ckbAddress,
|
|
141
|
+
index: { $type: 'fans.web5.ckb.indexAction#createSession' },
|
|
142
|
+
});
|
|
143
|
+
if (!loginInfo.success) {
|
|
144
|
+
error('Login failed');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const loginInfoData = loginInfo.data.result;
|
|
148
|
+
const sessionInfo = {
|
|
149
|
+
accessJwt: loginInfoData.accessJwt,
|
|
150
|
+
refreshJwt: loginInfoData.refreshJwt,
|
|
151
|
+
handle: loginInfoData.handle,
|
|
152
|
+
did: loginInfoData.did,
|
|
153
|
+
didMetadata: JSON.stringify(loginInfoData.didDoc),
|
|
154
|
+
};
|
|
155
|
+
success(sessionInfo);
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
error(`Failed to login: ${e instanceof Error ? e.message : String(e)}`);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
async write(pds, accessJwt, didKey, did, data, signKey, rkey, type) {
|
|
162
|
+
try {
|
|
163
|
+
const agent = createAgent(pds);
|
|
164
|
+
agent.setHeader('Authorization', `Bearer ${accessJwt}`);
|
|
165
|
+
const record = JSON.parse(data);
|
|
166
|
+
const newRecord = {
|
|
167
|
+
created: new Date().toISOString(),
|
|
168
|
+
...record,
|
|
169
|
+
};
|
|
170
|
+
const { TID } = await import('@atproto/common-web');
|
|
171
|
+
const recordRkey = rkey || TID.next().toString();
|
|
172
|
+
// Determine write type, default to 'create'
|
|
173
|
+
const writeType = type || 'create';
|
|
174
|
+
const preWriteType = `fans.web5.ckb.preDirectWrites#${writeType}`;
|
|
175
|
+
const directWriteType = `fans.web5.ckb.directWrites#${writeType}`;
|
|
176
|
+
const preWriteRes = await agent.fans.web5.ckb.preDirectWrites({
|
|
177
|
+
repo: did,
|
|
178
|
+
writes: [{
|
|
179
|
+
$type: preWriteType,
|
|
180
|
+
collection: newRecord.$type,
|
|
181
|
+
rkey: recordRkey,
|
|
182
|
+
value: newRecord,
|
|
183
|
+
}],
|
|
184
|
+
validate: false,
|
|
185
|
+
});
|
|
186
|
+
const preWriterData = preWriteRes.data;
|
|
187
|
+
const uncommit = {
|
|
188
|
+
did: preWriterData.did,
|
|
189
|
+
version: 3,
|
|
190
|
+
rev: preWriterData.rev,
|
|
191
|
+
prev: preWriterData.prev ? CID.parse(preWriterData.prev) : null,
|
|
192
|
+
data: CID.parse(preWriterData.data),
|
|
193
|
+
};
|
|
194
|
+
const unSignBytes = cbor.encode(uncommit);
|
|
195
|
+
const unSignBytesHex = hexFromBytes(unSignBytes).slice(2);
|
|
196
|
+
if (unSignBytesHex !== preWriterData.unSignBytes) {
|
|
197
|
+
error('Sign bytes not consistent');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const keypair = await Secp256k1Keypair.import(bytesFromHex(signKey));
|
|
201
|
+
const sig = await keypair.sign(unSignBytes);
|
|
202
|
+
const writeRes = await agent.fans.web5.ckb.directWrites({
|
|
203
|
+
repo: did,
|
|
204
|
+
validate: false,
|
|
205
|
+
signingKey: didKey,
|
|
206
|
+
writes: [{
|
|
207
|
+
$type: directWriteType,
|
|
208
|
+
collection: newRecord.$type,
|
|
209
|
+
rkey: recordRkey,
|
|
210
|
+
value: newRecord,
|
|
211
|
+
}],
|
|
212
|
+
root: {
|
|
213
|
+
did: preWriterData.did,
|
|
214
|
+
version: 3,
|
|
215
|
+
rev: preWriterData.rev,
|
|
216
|
+
prev: preWriterData.prev,
|
|
217
|
+
data: preWriterData.data,
|
|
218
|
+
signedBytes: hexFromBytes(sig),
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
success(writeRes.success);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
error(`Failed to write data: ${e instanceof Error ? e.message : String(e)}`);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
async repo(pds, did) {
|
|
228
|
+
try {
|
|
229
|
+
const url = new URL(`https://${pds}/xrpc/com.atproto.repo.describeRepo`);
|
|
230
|
+
url.searchParams.append('repo', did);
|
|
231
|
+
const response = await fetch(url.toString(), {
|
|
232
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
233
|
+
});
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
error(`Failed to fetch repo info: ${response.status} ${response.statusText}`);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const repoInfo = await response.json();
|
|
239
|
+
success(repoInfo);
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
error(`Failed to fetch repo info: ${e instanceof Error ? e.message : String(e)}`);
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
async records(pds, did, collection, limit, cursor) {
|
|
246
|
+
try {
|
|
247
|
+
const url = new URL(`https://${pds}/xrpc/com.atproto.repo.listRecords`);
|
|
248
|
+
url.searchParams.append('repo', did);
|
|
249
|
+
url.searchParams.append('collection', collection);
|
|
250
|
+
url.searchParams.append('limit', (limit || 10).toString());
|
|
251
|
+
if (cursor) {
|
|
252
|
+
url.searchParams.append('cursor', cursor);
|
|
253
|
+
}
|
|
254
|
+
const response = await fetch(url.toString(), {
|
|
255
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
256
|
+
});
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
error(`Failed to fetch records: ${response.status} ${response.statusText}`);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const data = await response.json();
|
|
262
|
+
success(data);
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
error(`Failed to fetch records: ${e instanceof Error ? e.message : String(e)}`);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
async blobs(pds, did, limit, cursor) {
|
|
269
|
+
try {
|
|
270
|
+
const url = new URL(`https://${pds}/xrpc/com.atproto.sync.listBlobs`);
|
|
271
|
+
url.searchParams.append('did', did);
|
|
272
|
+
url.searchParams.append('limit', (limit || 10).toString());
|
|
273
|
+
if (cursor) {
|
|
274
|
+
url.searchParams.append('cursor', cursor);
|
|
275
|
+
}
|
|
276
|
+
const response = await fetch(url.toString(), {
|
|
277
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
278
|
+
});
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
error(`Failed to fetch blobs: ${response.status} ${response.statusText}`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const data = await response.json();
|
|
284
|
+
success(data);
|
|
285
|
+
}
|
|
286
|
+
catch (e) {
|
|
287
|
+
error(`Failed to fetch blobs: ${e instanceof Error ? e.message : String(e)}`);
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
async exportRepo(pds, did, dataFile, since) {
|
|
291
|
+
try {
|
|
292
|
+
const url = new URL(`https://${pds}/xrpc/com.atproto.sync.getRepo`);
|
|
293
|
+
url.searchParams.append('did', did);
|
|
294
|
+
if (since) {
|
|
295
|
+
url.searchParams.append('since', since);
|
|
296
|
+
}
|
|
297
|
+
const response = await fetch(url.toString(), {
|
|
298
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
299
|
+
});
|
|
300
|
+
if (!response.ok) {
|
|
301
|
+
error(`Failed to export repo: ${response.status} ${response.statusText}`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const data = await response.arrayBuffer();
|
|
305
|
+
await writeFile(dataFile, Buffer.from(data));
|
|
306
|
+
success(true);
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
error(`Failed to export repo: ${e instanceof Error ? e.message : String(e)}`);
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
async importRepo(pds, did, accessJwt, dataFile) {
|
|
313
|
+
try {
|
|
314
|
+
const car = await readFile(dataFile);
|
|
315
|
+
const url = new URL(`https://${pds}/xrpc/com.atproto.repo.importRepo`);
|
|
316
|
+
url.searchParams.append('did', did);
|
|
317
|
+
const response = await fetch(url.toString(), {
|
|
318
|
+
method: 'POST',
|
|
319
|
+
headers: {
|
|
320
|
+
'Authorization': `Bearer ${accessJwt}`,
|
|
321
|
+
'Content-Type': 'application/vnd.ipld.car',
|
|
322
|
+
},
|
|
323
|
+
body: car,
|
|
324
|
+
});
|
|
325
|
+
success(response.ok);
|
|
326
|
+
}
|
|
327
|
+
catch (e) {
|
|
328
|
+
error(`Failed to import repo: ${e instanceof Error ? e.message : String(e)}`);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type TransactionStatus = 'sent' | 'pending' | 'proposed' | 'committed' | 'unknown' | 'rejected';
|
|
2
|
+
export interface WalletManager {
|
|
3
|
+
newWallet(): Promise<void>;
|
|
4
|
+
importWallet(sk: string): Promise<void>;
|
|
5
|
+
clean(): Promise<void>;
|
|
6
|
+
getAddress(): Promise<void>;
|
|
7
|
+
sendTx(txPath: string): Promise<void>;
|
|
8
|
+
checkTx(txHash: string): Promise<void>;
|
|
9
|
+
balance(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare const walletManager: WalletManager;
|
|
12
|
+
export declare const walletManagerWithExit: WalletManager;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { CKB_SK_PATH, keyExists, readKey, writeKey, removeKey, error, success, hexFromBytes, readJsonFile } from './common.js';
|
|
2
|
+
import { ccc } from '@ckb-ccc/ccc';
|
|
3
|
+
function getNetwork() {
|
|
4
|
+
const network = process.env.CKB_NETWORK;
|
|
5
|
+
if (network === 'ckb')
|
|
6
|
+
return 'ckb';
|
|
7
|
+
return 'ckb_testnet';
|
|
8
|
+
}
|
|
9
|
+
async function getClient() {
|
|
10
|
+
const network = getNetwork();
|
|
11
|
+
if (network === 'ckb') {
|
|
12
|
+
return new ccc.ClientPublicMainnet();
|
|
13
|
+
}
|
|
14
|
+
return new ccc.ClientPublicTestnet();
|
|
15
|
+
}
|
|
16
|
+
async function getSigner() {
|
|
17
|
+
const privateKey = await readKey(CKB_SK_PATH);
|
|
18
|
+
if (!privateKey)
|
|
19
|
+
return null;
|
|
20
|
+
const client = await getClient();
|
|
21
|
+
return new ccc.SignerCkbPrivateKey(client, privateKey);
|
|
22
|
+
}
|
|
23
|
+
function generatePrivateKey() {
|
|
24
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
25
|
+
return hexFromBytes(bytes);
|
|
26
|
+
}
|
|
27
|
+
function exitAfter(fn) {
|
|
28
|
+
return async (...args) => {
|
|
29
|
+
try {
|
|
30
|
+
await fn(...args);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export const walletManager = {
|
|
38
|
+
async newWallet() {
|
|
39
|
+
if (keyExists(CKB_SK_PATH)) {
|
|
40
|
+
error('Wallet already exists. Use "clean" first to remove it.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const privateKey = generatePrivateKey();
|
|
45
|
+
const client = await getClient();
|
|
46
|
+
const signer = new ccc.SignerCkbPrivateKey(client, privateKey);
|
|
47
|
+
const address = await signer.getRecommendedAddress();
|
|
48
|
+
await writeKey(CKB_SK_PATH, privateKey);
|
|
49
|
+
success(address);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
error(`Failed to create wallet: ${e instanceof Error ? e.message : String(e)}`);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async importWallet(sk) {
|
|
56
|
+
if (keyExists(CKB_SK_PATH)) {
|
|
57
|
+
error('Wallet already exists. Use "clean" first to remove it.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const client = await getClient();
|
|
62
|
+
const signer = new ccc.SignerCkbPrivateKey(client, sk);
|
|
63
|
+
const address = await signer.getRecommendedAddress();
|
|
64
|
+
await writeKey(CKB_SK_PATH, sk);
|
|
65
|
+
success(address);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
error(`Failed to import wallet: ${e instanceof Error ? e.message : String(e)}`);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
async clean() {
|
|
72
|
+
const result = await removeKey(CKB_SK_PATH);
|
|
73
|
+
success(result);
|
|
74
|
+
},
|
|
75
|
+
async getAddress() {
|
|
76
|
+
const signer = await getSigner();
|
|
77
|
+
if (!signer) {
|
|
78
|
+
error('No wallet found. Use "new" or "import" first.');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const address = await signer.getRecommendedAddress();
|
|
83
|
+
success(address);
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
error(`Failed to get address: ${e instanceof Error ? e.message : String(e)}`);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
async sendTx(txPath) {
|
|
90
|
+
const signer = await getSigner();
|
|
91
|
+
if (!signer) {
|
|
92
|
+
error('No wallet found. Use "new" or "import" first.');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const txData = await readJsonFile(txPath);
|
|
97
|
+
if (!txData) {
|
|
98
|
+
error('Failed to read transaction file');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const tx = ccc.Transaction.from(txData);
|
|
102
|
+
const txHash = await signer.sendTransaction(tx);
|
|
103
|
+
success(txHash);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
error(`Failed to send transaction: ${e instanceof Error ? e.message : String(e)}`);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
async checkTx(txHash) {
|
|
110
|
+
const signer = await getSigner();
|
|
111
|
+
if (!signer) {
|
|
112
|
+
error('No wallet found. Use "new" or "import" first.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const tx = await signer.client.getTransaction(txHash);
|
|
117
|
+
success(tx?.status ?? 'unknown');
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
error(`Failed to check transaction: ${e instanceof Error ? e.message : String(e)}`);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
async balance() {
|
|
124
|
+
const signer = await getSigner();
|
|
125
|
+
if (!signer) {
|
|
126
|
+
error('No wallet found. Use "new" or "import" first.');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const balance = await signer.getBalance();
|
|
131
|
+
const balanceInCKB = ccc.fixedPointToString(balance);
|
|
132
|
+
success(`${balanceInCKB} CKB`);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
error(`Failed to get balance: ${e instanceof Error ? e.message : String(e)}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
// Wrap methods that need process exit
|
|
140
|
+
export const walletManagerWithExit = {
|
|
141
|
+
newWallet: exitAfter(walletManager.newWallet),
|
|
142
|
+
importWallet: exitAfter(walletManager.importWallet),
|
|
143
|
+
clean: exitAfter(walletManager.clean),
|
|
144
|
+
getAddress: exitAfter(walletManager.getAddress),
|
|
145
|
+
sendTx: exitAfter(walletManager.sendTx),
|
|
146
|
+
checkTx: exitAfter(walletManager.checkTx),
|
|
147
|
+
balance: exitAfter(walletManager.balance),
|
|
148
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "web5-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Web5 CLI tool for decentralized identity and data management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"web5-cli": "./bin/web5-cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"lint": "eslint .",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@atproto/crypto": "^0.4.5",
|
|
22
|
+
"@ckb-ccc/ccc": "0.0.0-canary-20260109065952",
|
|
23
|
+
"@scure/base": "^2.0.0",
|
|
24
|
+
"@atproto/common-web": "^0.4.13",
|
|
25
|
+
"@atproto/repo": "^0.8.12",
|
|
26
|
+
"@ipld/dag-cbor": "^9.2.5",
|
|
27
|
+
"multiformats": "^13.4.2",
|
|
28
|
+
"web5-api": "^0.0.27",
|
|
29
|
+
"commander": "^12.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@eslint/js": "^9.39.1",
|
|
33
|
+
"@types/node": "^24.10.1",
|
|
34
|
+
"@types/commander": "^2.12.5",
|
|
35
|
+
"eslint": "^9.39.1",
|
|
36
|
+
"globals": "^16.5.0",
|
|
37
|
+
"typescript": "~5.9.3",
|
|
38
|
+
"typescript-eslint": "^8.46.4"
|
|
39
|
+
}
|
|
40
|
+
}
|