@carbonx3/cx3.js 0.1.0 → 0.2.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/README.md +232 -92
- package/index.d.ts +88 -15
- package/index.js +9 -5
- package/package.json +13 -5
- package/src/abi.js +8 -1
- package/src/contract.js +5 -0
- package/src/crypto.js +6 -4
- package/src/digest.js +4 -10
- package/src/factory.js +105 -1
- package/src/provider.js +325 -59
- package/src/utils.js +19 -0
- package/src/wallet.js +38 -6
package/src/contract.js
CHANGED
|
@@ -64,6 +64,11 @@ class Contract {
|
|
|
64
64
|
const provider = this.provider;
|
|
65
65
|
const sender = this.signer ? this.signer.address : "";
|
|
66
66
|
const res = await provider.call(this.address, method, params, sender);
|
|
67
|
+
// the node answers a failed read with HTTP 200 + { success: false, error }
|
|
68
|
+
if (res && res.success === false) {
|
|
69
|
+
const code = String(res.error || res.message || "CALL_FAILED");
|
|
70
|
+
throw new Cx3Error(code, code, res);
|
|
71
|
+
}
|
|
67
72
|
return res.value !== undefined ? res.value : res;
|
|
68
73
|
}
|
|
69
74
|
|
package/src/crypto.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const crypto = require("crypto");
|
|
4
|
+
// @noble/secp256k1 3.x is an ES module: require() loads it on Node 20.19+ / 22.12+ (package.json "engines").
|
|
4
5
|
const secp = require("@noble/secp256k1");
|
|
5
6
|
const { strip0x, ensure0x } = require("./utils");
|
|
6
7
|
const { Cx3Error } = require("./errors");
|
|
@@ -48,10 +49,11 @@ async function signMessage(message, privateKeyInput) {
|
|
|
48
49
|
? Buffer.from(message, "utf8")
|
|
49
50
|
: Buffer.from(message);
|
|
50
51
|
const msgHash = sha256(msgBytes);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const
|
|
52
|
+
// msgHash is already the digest (prehash: false); deterministic RFC 6979, low-S — the same bytes 1.7.x produced.
|
|
53
|
+
// "recovered" = 65 bytes: recovery id, then r || s.
|
|
54
|
+
const signed = await secp.signAsync(msgHash, priv, { prehash: false, format: "recovered" });
|
|
55
|
+
const recovery = signed[0];
|
|
56
|
+
const signature = Buffer.from(signed.subarray(1)).toString("hex");
|
|
55
57
|
const publicKey = privateKeyToPublicKey(priv, true).toString("hex");
|
|
56
58
|
const publicKeyUncompressed = privateKeyToPublicKey(priv, false).toString("hex");
|
|
57
59
|
return {
|
package/src/digest.js
CHANGED
|
@@ -14,16 +14,10 @@ function buildDeployDigest({
|
|
|
14
14
|
initialSupply = "0",
|
|
15
15
|
timestamp
|
|
16
16
|
}) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
name || "",
|
|
22
|
-
symbol || "",
|
|
23
|
-
decimals !== undefined ? decimals : 18,
|
|
24
|
-
initialSupply !== undefined ? String(initialSupply) : "0",
|
|
25
|
-
timestamp || 0
|
|
26
|
-
].join("|");
|
|
17
|
+
// Template literal like the node (join() would turn null into "")
|
|
18
|
+
const dec = decimals !== undefined ? decimals : 18;
|
|
19
|
+
const supply = initialSupply !== undefined ? String(initialSupply) : "0";
|
|
20
|
+
return `deploy|${owner || ""}|${standard || ""}|${name || ""}|${symbol || ""}|${dec}|${supply}|${timestamp || 0}`;
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
function buildSendDigest(contractAddress, method, params, fromAddress, timestamp) {
|
package/src/factory.js
CHANGED
|
@@ -2,9 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
const { Cx3Error } = require("./errors");
|
|
4
4
|
const { Contract } = require("./contract");
|
|
5
|
+
const { normalizeAddress, isPlainObject } = require("./utils");
|
|
6
|
+
|
|
7
|
+
// the token standards the CX3WebUI deploys as Public or Private
|
|
8
|
+
const PRIVATE_STANDARDS = new Set(["CX30", "CX31", "CX300", "CX315", "CX3192"]);
|
|
9
|
+
const MAX_AUTHORIZED_WALLETS = 200;
|
|
10
|
+
|
|
11
|
+
function authorizedWalletList(list) {
|
|
12
|
+
if (list == null) return [];
|
|
13
|
+
if (!Array.isArray(list)) {
|
|
14
|
+
throw new Cx3Error("authorizedWallets is an array of wallet addresses", "BAD_DEPLOY_ARGS");
|
|
15
|
+
}
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const raw of list) {
|
|
18
|
+
const a = normalizeAddress(raw);
|
|
19
|
+
if (!/^0x[0-9a-f]{40}$/.test(a)) {
|
|
20
|
+
throw new Cx3Error("authorizedWallets: every entry is a wallet address (0x + 40 hex)", "INVALID_AUTHORIZED_WALLET", { wallet: String(raw) });
|
|
21
|
+
}
|
|
22
|
+
if (!out.includes(a)) out.push(a);
|
|
23
|
+
}
|
|
24
|
+
if (out.length > MAX_AUTHORIZED_WALLETS) {
|
|
25
|
+
throw new Cx3Error(`authorizedWallets takes at most ${MAX_AUTHORIZED_WALLETS} wallets`, "TOO_MANY_AUTHORIZED_WALLETS");
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// addToWhitelist params by the contract's ABI names (addr or account, then kycHash / invType = "")
|
|
31
|
+
function whitelistParams(def, wallet) {
|
|
32
|
+
const names = (def && Array.isArray(def.inputs) ? def.inputs : [])
|
|
33
|
+
.map((i) => (typeof i === "string" ? i : i && i.name))
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
if (!names.length) return { addr: wallet, kycHash: "" };
|
|
36
|
+
const params = {};
|
|
37
|
+
names.forEach((n, i) => { params[n] = i === 0 ? wallet : ""; });
|
|
38
|
+
return params;
|
|
39
|
+
}
|
|
5
40
|
|
|
6
41
|
/**
|
|
7
42
|
* Standard token deploy helpers.
|
|
43
|
+
*
|
|
44
|
+
* `visibility: "private"` deploys a private token, like the CX3WebUI "Private" choice: only the owner
|
|
45
|
+
* and `authorizedWallets` send, receive and see it. The chain renders a private CX30 itself; CX31,
|
|
46
|
+
* CX300, CX315 and CX3192 take their CX3WebUI source (`sourceCode`). Each authorized wallet is added
|
|
47
|
+
* with one signed `addToWhitelist` after the deploy (`result.authorized`).
|
|
8
48
|
*/
|
|
9
49
|
class Cx3TokenFactory {
|
|
10
50
|
/**
|
|
@@ -104,8 +144,18 @@ class Cx3TokenFactory {
|
|
|
104
144
|
if (!fields.name || !fields.symbol) {
|
|
105
145
|
throw new Cx3Error("name and symbol are required", "BAD_DEPLOY_ARGS");
|
|
106
146
|
}
|
|
147
|
+
const { visibility, authorizedWallets, ...rest } = fields;
|
|
148
|
+
let body = rest;
|
|
149
|
+
let wallets = [];
|
|
150
|
+
if (visibility === "private") {
|
|
151
|
+
({ body, wallets } = this._privateFields(standard, rest, authorizedWallets));
|
|
152
|
+
} else if (visibility != null && visibility !== "public") {
|
|
153
|
+
throw new Cx3Error('visibility is "public" or "private"', "BAD_DEPLOY_ARGS");
|
|
154
|
+
} else if (authorizedWallets != null) {
|
|
155
|
+
throw new Cx3Error('authorizedWallets belongs to visibility: "private"', "BAD_DEPLOY_ARGS");
|
|
156
|
+
}
|
|
107
157
|
const result = await this.wallet.deployContract({
|
|
108
|
-
...
|
|
158
|
+
...body,
|
|
109
159
|
standard
|
|
110
160
|
});
|
|
111
161
|
if (result && result.success !== false && result.contractAddress) {
|
|
@@ -122,9 +172,63 @@ class Cx3TokenFactory {
|
|
|
122
172
|
this.wallet
|
|
123
173
|
);
|
|
124
174
|
}
|
|
175
|
+
if (wallets.length) result.authorized = await this._authorize(result.contract, wallets);
|
|
125
176
|
}
|
|
126
177
|
return result;
|
|
127
178
|
}
|
|
179
|
+
|
|
180
|
+
_privateFields(standard, fields, list) {
|
|
181
|
+
if (!PRIVATE_STANDARDS.has(standard)) {
|
|
182
|
+
throw new Cx3Error(`${standard} has no private variant (CX30, CX31, CX300, CX315 and CX3192 do)`, "PRIVATE_NOT_SUPPORTED");
|
|
183
|
+
}
|
|
184
|
+
const wallets = authorizedWalletList(list);
|
|
185
|
+
const hasSource = !!(fields.sourceCode || fields.bytecode);
|
|
186
|
+
if (!hasSource && (standard !== "CX30" || (fields.target && fields.target !== "CX3WASM"))) {
|
|
187
|
+
throw new Cx3Error(
|
|
188
|
+
`A private ${standard} ${standard === "CX30" ? "on CX3JS " : ""}is deployed from its CX3WebUI source (sourceCode); the chain renders only a CX3WASM CX30 itself`,
|
|
189
|
+
"PRIVATE_NEEDS_SOURCE"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const metadata = {
|
|
193
|
+
...(isPlainObject(fields.metadata) ? fields.metadata : {}),
|
|
194
|
+
visibility: "private",
|
|
195
|
+
authorizedWallets: wallets.length
|
|
196
|
+
};
|
|
197
|
+
const body = { ...fields, whitelistable: true, metadata };
|
|
198
|
+
if (!hasSource) {
|
|
199
|
+
// the chain renders CX30 from constructorArgs; whitelistable adds the authorized list
|
|
200
|
+
// (isWhitelisted, addToWhitelist / removeFromWhitelist) and the transfer guards
|
|
201
|
+
body.constructorArgs = {
|
|
202
|
+
name: fields.name,
|
|
203
|
+
symbol: fields.symbol,
|
|
204
|
+
decimals: fields.decimals !== undefined ? Number(fields.decimals) : 18,
|
|
205
|
+
totalSupply: fields.initialSupply || fields.totalSupply || "0",
|
|
206
|
+
owner: fields.owner || this.wallet.address,
|
|
207
|
+
mintable: fields.mintable !== undefined ? fields.mintable : true,
|
|
208
|
+
burnable: fields.burnable !== undefined ? fields.burnable : true,
|
|
209
|
+
pausable: fields.pausable !== undefined ? fields.pausable : true,
|
|
210
|
+
blacklistable: !!fields.blacklistable,
|
|
211
|
+
...(isPlainObject(fields.constructorArgs) ? fields.constructorArgs : {}),
|
|
212
|
+
whitelistable: true
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return { body, wallets };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// one signed addToWhitelist per wallet (the CX3WebUI writes them into the constructor instead)
|
|
219
|
+
async _authorize(contract, wallets) {
|
|
220
|
+
const def = contract.interface && contract.interface.addToWhitelist;
|
|
221
|
+
const out = [];
|
|
222
|
+
for (const w of wallets) {
|
|
223
|
+
try {
|
|
224
|
+
const tx = await this.wallet.sendTransaction(contract.address, "addToWhitelist", whitelistParams(def, w));
|
|
225
|
+
out.push({ wallet: w, success: tx.success !== false, txHash: tx.txHash || null });
|
|
226
|
+
} catch (e) {
|
|
227
|
+
out.push({ wallet: w, success: false, error: (e && (e.code || e.message)) || "SEND_FAILED" });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
128
232
|
}
|
|
129
233
|
|
|
130
234
|
module.exports = { Cx3TokenFactory };
|