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,252 @@
|
|
|
1
|
+
import { CKB_SK_PATH, readKey, error, success, writeJsonFile } from './common.js';
|
|
2
|
+
import { ccc } from '@ckb-ccc/ccc';
|
|
3
|
+
import { base32 } from '@scure/base';
|
|
4
|
+
function getNetwork() {
|
|
5
|
+
const network = process.env.CKB_NETWORK;
|
|
6
|
+
if (network === 'ckb')
|
|
7
|
+
return 'ckb';
|
|
8
|
+
return 'ckb_testnet';
|
|
9
|
+
}
|
|
10
|
+
async function getClient() {
|
|
11
|
+
const network = getNetwork();
|
|
12
|
+
if (network === 'ckb') {
|
|
13
|
+
return new ccc.ClientPublicMainnet();
|
|
14
|
+
}
|
|
15
|
+
return new ccc.ClientPublicTestnet();
|
|
16
|
+
}
|
|
17
|
+
async function getSigner() {
|
|
18
|
+
const privateKey = await readKey(CKB_SK_PATH);
|
|
19
|
+
if (!privateKey)
|
|
20
|
+
return null;
|
|
21
|
+
const client = await getClient();
|
|
22
|
+
return new ccc.SignerCkbPrivateKey(client, privateKey);
|
|
23
|
+
}
|
|
24
|
+
function didFromArgs(args) {
|
|
25
|
+
const argsBytes = ccc.bytesFrom(args.slice(0, 42));
|
|
26
|
+
return `did:ckb:${base32.encode(argsBytes).toLowerCase()}`;
|
|
27
|
+
}
|
|
28
|
+
export const didManager = {
|
|
29
|
+
async buildCreateTx(username, pds, didkey, outputPath) {
|
|
30
|
+
const signer = await getSigner();
|
|
31
|
+
if (!signer) {
|
|
32
|
+
error('No wallet found. Use "wallet new" or "wallet import" first.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const address = await signer.getRecommendedAddressObj();
|
|
37
|
+
const handle = `${username.toLowerCase()}.${pds}`;
|
|
38
|
+
const metadata = {
|
|
39
|
+
document: {
|
|
40
|
+
alsoKnownAs: [`at://${handle}`],
|
|
41
|
+
verificationMethods: {
|
|
42
|
+
atproto: didkey
|
|
43
|
+
},
|
|
44
|
+
services: {
|
|
45
|
+
atproto_pds: {
|
|
46
|
+
type: 'AtprotoPersonalDataServer',
|
|
47
|
+
endpoint: `https://${pds}`
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const { tx, id } = await ccc.didCkb.createDidCkb({
|
|
53
|
+
signer,
|
|
54
|
+
data: { value: metadata },
|
|
55
|
+
receiver: address.script,
|
|
56
|
+
});
|
|
57
|
+
await tx.completeInputsByCapacity(signer);
|
|
58
|
+
await tx.completeFeeBy(signer);
|
|
59
|
+
const rawTx = ccc.stringify(tx);
|
|
60
|
+
const did = didFromArgs(id);
|
|
61
|
+
await writeJsonFile(outputPath, JSON.parse(rawTx));
|
|
62
|
+
success({ did, txPath: outputPath });
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
error(`Failed to build create transaction: ${e instanceof Error ? e.message : String(e)}`);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async buildDestroyTx(args, outputPath) {
|
|
69
|
+
const signer = await getSigner();
|
|
70
|
+
if (!signer) {
|
|
71
|
+
error('No wallet found. Use "wallet new" or "wallet import" first.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const { tx } = await ccc.didCkb.destroyDidCkb({ client: signer.client, id: args });
|
|
76
|
+
await tx.completeInputsByCapacity(signer);
|
|
77
|
+
await tx.completeFeeBy(signer);
|
|
78
|
+
const rawTx = ccc.stringify(tx);
|
|
79
|
+
await writeJsonFile(outputPath, JSON.parse(rawTx));
|
|
80
|
+
success({ txPath: outputPath });
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
error(`Failed to build destroy transaction: ${e instanceof Error ? e.message : String(e)}`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
async buildUpdateDidKeyTx(args, newDidKey, outputPath) {
|
|
87
|
+
const signer = await getSigner();
|
|
88
|
+
if (!signer) {
|
|
89
|
+
error('No wallet found. Use "wallet new" or "wallet import" first.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const address = await signer.getRecommendedAddressObj();
|
|
94
|
+
const { tx } = await ccc.didCkb.transferDidCkb({
|
|
95
|
+
client: signer.client,
|
|
96
|
+
id: args,
|
|
97
|
+
receiver: address.script,
|
|
98
|
+
data: (_, data) => {
|
|
99
|
+
if (!data)
|
|
100
|
+
throw new Error('data is undefined');
|
|
101
|
+
const doc = data.value.document;
|
|
102
|
+
if (!doc.verificationMethods)
|
|
103
|
+
doc.verificationMethods = {};
|
|
104
|
+
doc.verificationMethods.atproto = newDidKey;
|
|
105
|
+
return data;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
await tx.completeInputsByCapacity(signer);
|
|
109
|
+
await tx.completeFeeBy(signer);
|
|
110
|
+
const rawTx = ccc.stringify(tx);
|
|
111
|
+
await writeJsonFile(outputPath, JSON.parse(rawTx));
|
|
112
|
+
success({ txPath: outputPath });
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
error(`Failed to build update didKey transaction: ${e instanceof Error ? e.message : String(e)}`);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async buildUpdateHandleTx(args, newHandle, outputPath) {
|
|
119
|
+
const signer = await getSigner();
|
|
120
|
+
if (!signer) {
|
|
121
|
+
error('No wallet found. Use "wallet new" or "wallet import" first.');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const address = await signer.getRecommendedAddressObj();
|
|
126
|
+
const parts = newHandle.split('.');
|
|
127
|
+
const pdsHost = parts.slice(1).join('.');
|
|
128
|
+
const serviceEndpoint = `https://${pdsHost}`;
|
|
129
|
+
const { tx } = await ccc.didCkb.transferDidCkb({
|
|
130
|
+
client: signer.client,
|
|
131
|
+
id: args,
|
|
132
|
+
receiver: address.script,
|
|
133
|
+
data: (_, data) => {
|
|
134
|
+
if (!data)
|
|
135
|
+
throw new Error('data is undefined');
|
|
136
|
+
const doc = data.value.document;
|
|
137
|
+
doc.alsoKnownAs = [`at://${newHandle}`];
|
|
138
|
+
if (!doc.services)
|
|
139
|
+
doc.services = {};
|
|
140
|
+
if (!doc.services.atproto_pds)
|
|
141
|
+
doc.services.atproto_pds = { type: 'AtprotoPersonalDataServer' };
|
|
142
|
+
doc.services.atproto_pds.endpoint = serviceEndpoint;
|
|
143
|
+
return data;
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
await tx.completeInputsByCapacity(signer);
|
|
147
|
+
await tx.completeFeeBy(signer);
|
|
148
|
+
const rawTx = ccc.stringify(tx);
|
|
149
|
+
await writeJsonFile(outputPath, JSON.parse(rawTx));
|
|
150
|
+
success({ txPath: outputPath });
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
error(`Failed to build update handle transaction: ${e instanceof Error ? e.message : String(e)}`);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
async buildTransferTx(args, receiver, outputPath) {
|
|
157
|
+
const signer = await getSigner();
|
|
158
|
+
if (!signer) {
|
|
159
|
+
error('No wallet found. Use "wallet new" or "wallet import" first.');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const receiverAddr = await ccc.Address.fromString(receiver.trim(), signer.client);
|
|
164
|
+
const { tx } = await ccc.didCkb.transferDidCkb({
|
|
165
|
+
client: signer.client,
|
|
166
|
+
id: args,
|
|
167
|
+
receiver: receiverAddr.script,
|
|
168
|
+
});
|
|
169
|
+
await tx.completeInputsByCapacity(signer);
|
|
170
|
+
await tx.completeFeeBy(signer);
|
|
171
|
+
const rawTx = ccc.stringify(tx);
|
|
172
|
+
await writeJsonFile(outputPath, JSON.parse(rawTx));
|
|
173
|
+
success({ txPath: outputPath });
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
error(`Failed to build transfer transaction: ${e instanceof Error ? e.message : String(e)}`);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
async list(ckbAddr) {
|
|
180
|
+
try {
|
|
181
|
+
const client = await getClient();
|
|
182
|
+
const address = await ccc.Address.fromString(ckbAddr, client);
|
|
183
|
+
const didScriptInfo = await client.getKnownScript(ccc.KnownScript.DidCkb);
|
|
184
|
+
const didCodeHash = didScriptInfo?.codeHash;
|
|
185
|
+
if (!didCodeHash) {
|
|
186
|
+
error('DidCkb script codeHash not found');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const signer = await new ccc.SignerCkbScriptReadonly(client, address.script);
|
|
190
|
+
if (!signer) {
|
|
191
|
+
error('No wallet found. Use "wallet new" or "wallet import" first.');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const cells = await signer.findCells({
|
|
195
|
+
script: {
|
|
196
|
+
codeHash: didCodeHash,
|
|
197
|
+
hashType: 'type',
|
|
198
|
+
args: "0x",
|
|
199
|
+
},
|
|
200
|
+
}, true, 'desc', 10);
|
|
201
|
+
const result = [];
|
|
202
|
+
for await (const cell of cells) {
|
|
203
|
+
const txHash = cell.outPoint.txHash;
|
|
204
|
+
const index = Number(cell.outPoint.index);
|
|
205
|
+
try {
|
|
206
|
+
const data = cell.outputData ?? '0x';
|
|
207
|
+
const didData = ccc.didCkb.DidCkbData.decode(data);
|
|
208
|
+
const didDoc = didData.value.document;
|
|
209
|
+
const didMetadata = JSON.stringify(didDoc);
|
|
210
|
+
if (!cell.cellOutput.type)
|
|
211
|
+
throw new Error('cell.cellOutput.type is undefined');
|
|
212
|
+
const args = ccc.bytesFrom(cell.cellOutput.type.args.slice(0, 42)); // 20 bytes Type args
|
|
213
|
+
const did = `did:ckb:${base32.encode(args).toLowerCase()}`;
|
|
214
|
+
result.push({
|
|
215
|
+
txHash,
|
|
216
|
+
index,
|
|
217
|
+
capacity: ccc.fixedPointToString(cell.cellOutput.capacity),
|
|
218
|
+
args: cell.cellOutput.type.args,
|
|
219
|
+
did,
|
|
220
|
+
didMetadata,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error(`Error processing cell ${txHash}:${index}:`, error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
success(result);
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
error(`Failed to list DID cells: ${e instanceof Error ? e.message : String(e)}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
// Wrap methods that need process exit
|
|
235
|
+
function exitAfter(fn) {
|
|
236
|
+
return async (...args) => {
|
|
237
|
+
try {
|
|
238
|
+
await fn(...args);
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
process.exit(0);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export const didManagerWithExit = {
|
|
246
|
+
buildCreateTx: exitAfter(didManager.buildCreateTx),
|
|
247
|
+
buildDestroyTx: exitAfter(didManager.buildDestroyTx),
|
|
248
|
+
buildUpdateDidKeyTx: exitAfter(didManager.buildUpdateDidKeyTx),
|
|
249
|
+
buildUpdateHandleTx: exitAfter(didManager.buildUpdateHandleTx),
|
|
250
|
+
buildTransferTx: exitAfter(didManager.buildTransferTx),
|
|
251
|
+
list: exitAfter(didManager.list),
|
|
252
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface KeystoreManager {
|
|
2
|
+
newKey(): Promise<void>;
|
|
3
|
+
importKey(sk: string): Promise<void>;
|
|
4
|
+
clean(): Promise<void>;
|
|
5
|
+
getDIDKey(): Promise<void>;
|
|
6
|
+
sign(message: string): Promise<void>;
|
|
7
|
+
verify(message: string, signature: string, didKey?: string): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare const keystoreManager: KeystoreManager;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { SIGNKEY_PATH, keyExists, readKey, writeKey, removeKey, error, success, bytesFromHex, hexFromBytes } from './common.js';
|
|
2
|
+
import { Secp256k1Keypair, verifySignature as verifySignatureAtproto } from '@atproto/crypto';
|
|
3
|
+
export const keystoreManager = {
|
|
4
|
+
async newKey() {
|
|
5
|
+
if (keyExists(SIGNKEY_PATH)) {
|
|
6
|
+
error('Keypair already exists. Use "clean" first to remove it.');
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
const keypair = await Secp256k1Keypair.create({ exportable: true });
|
|
11
|
+
const exportedBytes = await keypair.export();
|
|
12
|
+
const privateKey = hexFromBytes(exportedBytes);
|
|
13
|
+
await writeKey(SIGNKEY_PATH, privateKey);
|
|
14
|
+
const didKey = keypair.did();
|
|
15
|
+
success(didKey);
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
error(`Failed to create keypair: ${e instanceof Error ? e.message : String(e)}`);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
async importKey(sk) {
|
|
22
|
+
if (keyExists(SIGNKEY_PATH)) {
|
|
23
|
+
error('Keypair already exists. Use "clean" first to remove it.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const keypair = await Secp256k1Keypair.import(bytesFromHex(sk), { exportable: true });
|
|
28
|
+
await writeKey(SIGNKEY_PATH, sk);
|
|
29
|
+
const didKey = keypair.did();
|
|
30
|
+
success(didKey);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
error(`Failed to import key: ${e instanceof Error ? e.message : String(e)}`);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
async clean() {
|
|
37
|
+
const result = await removeKey(SIGNKEY_PATH);
|
|
38
|
+
success(result);
|
|
39
|
+
},
|
|
40
|
+
async getDIDKey() {
|
|
41
|
+
const privateKey = await readKey(SIGNKEY_PATH);
|
|
42
|
+
if (!privateKey) {
|
|
43
|
+
error('No keypair found. Use "new" or "import" first.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const keypair = await Secp256k1Keypair.import(bytesFromHex(privateKey));
|
|
48
|
+
success(keypair.did());
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
error(`Failed to get DID key: ${e instanceof Error ? e.message : String(e)}`);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
async sign(message) {
|
|
55
|
+
const privateKey = await readKey(SIGNKEY_PATH);
|
|
56
|
+
if (!privateKey) {
|
|
57
|
+
error('No keypair found. Use "new" or "import" first.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const keypair = await Secp256k1Keypair.import(bytesFromHex(privateKey));
|
|
62
|
+
const messageBytes = bytesFromHex(message);
|
|
63
|
+
const signature = await keypair.sign(messageBytes);
|
|
64
|
+
success(hexFromBytes(signature));
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
error(`Failed to sign message: ${e instanceof Error ? e.message : String(e)}`);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
async verify(message, signature, didKey) {
|
|
71
|
+
try {
|
|
72
|
+
let pubDidKey;
|
|
73
|
+
if (didKey) {
|
|
74
|
+
pubDidKey = didKey;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const privateKey = await readKey(SIGNKEY_PATH);
|
|
78
|
+
if (!privateKey) {
|
|
79
|
+
error('No keypair found and no didKey provided.');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const keypair = await Secp256k1Keypair.import(bytesFromHex(privateKey));
|
|
83
|
+
pubDidKey = keypair.did();
|
|
84
|
+
}
|
|
85
|
+
const messageBytes = bytesFromHex(message);
|
|
86
|
+
const signatureBytes = bytesFromHex(signature);
|
|
87
|
+
const verified = await verifySignatureAtproto(pubDidKey, messageBytes, signatureBytes);
|
|
88
|
+
success(verified);
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
success(false);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type userInfo = {
|
|
2
|
+
accessJwt: string;
|
|
3
|
+
refreshJwt: string;
|
|
4
|
+
handle: string;
|
|
5
|
+
did: string;
|
|
6
|
+
};
|
|
7
|
+
export type sessionInfo = {
|
|
8
|
+
accessJwt: string;
|
|
9
|
+
refreshJwt: string;
|
|
10
|
+
handle: string;
|
|
11
|
+
did: string;
|
|
12
|
+
didMetadata: string;
|
|
13
|
+
};
|
|
14
|
+
export type RepoInfo = {
|
|
15
|
+
handle: string;
|
|
16
|
+
did: string;
|
|
17
|
+
didDoc: {
|
|
18
|
+
verificationMethods: Record<string, string>;
|
|
19
|
+
alsoKnownAs: string[];
|
|
20
|
+
services: Record<string, {
|
|
21
|
+
type: string;
|
|
22
|
+
endpoint: string;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
collections: string[];
|
|
26
|
+
handleIsCorrect: boolean;
|
|
27
|
+
};
|
|
28
|
+
export type RepoRecords = {
|
|
29
|
+
cursor?: string;
|
|
30
|
+
records: {
|
|
31
|
+
uri: string;
|
|
32
|
+
cid: string;
|
|
33
|
+
value: Record<string, any>;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
export type RepoBlobs = {
|
|
37
|
+
cursor?: string;
|
|
38
|
+
cids: string[];
|
|
39
|
+
};
|
|
40
|
+
export interface PdsManager {
|
|
41
|
+
checkUsername(username: string): Promise<void>;
|
|
42
|
+
getDidByUsername(username: string, pds: string): Promise<void>;
|
|
43
|
+
createAccount(pds: string, username: string, didKey: string, did: string, ckbAddress: string, signKey: string): Promise<void>;
|
|
44
|
+
deleteAccount(pds: string, didKey: string, did: string, ckbAddress: string, signKey: string): Promise<void>;
|
|
45
|
+
login(pds: string, didKey: string, did: string, ckbAddress: string, signKey: string): Promise<void>;
|
|
46
|
+
write(pds: string, accessJwt: string, didKey: string, did: string, data: string, signKey: string, rkey?: string, type?: 'update' | 'create' | 'delete'): Promise<void>;
|
|
47
|
+
repo(pds: string, did: string): Promise<void>;
|
|
48
|
+
records(pds: string, did: string, collection: string, limit?: number, cursor?: string): Promise<void>;
|
|
49
|
+
blobs(pds: string, did: string, limit?: number, cursor?: string): Promise<void>;
|
|
50
|
+
exportRepo(pds: string, did: string, dataFile: string, since?: string): Promise<void>;
|
|
51
|
+
importRepo(pds: string, did: string, accessJwt: string, dataFile: string): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
export declare const pdsManager: PdsManager;
|