@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/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
- const signed = await secp.sign(msgHash, priv, { der: false, recovered: true });
52
- const sigBytes = Array.isArray(signed) ? signed[0] : signed;
53
- const recovery = Array.isArray(signed) ? signed[1] : (signed.recovery != null ? signed.recovery : 0);
54
- const signature = Buffer.from(sigBytes).toString("hex");
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
- return [
18
- "deploy",
19
- owner || "",
20
- standard || "",
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
- ...fields,
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 };