@carbonx3/cx3.js 0.1.1 → 0.3.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/wallet.js CHANGED
@@ -4,13 +4,21 @@ const {
4
4
  privateKeyToAddress,
5
5
  privateKeyToPublicKey,
6
6
  signMessage,
7
+ signPersonalMessage,
7
8
  createPrivateKey
8
9
  } = require("./crypto");
9
- const { normalizeAddress } = require("./utils");
10
+ const { normalizeAddress, toJsonSafe } = require("./utils");
10
11
  const { Cx3Error } = require("./errors");
11
12
  const { buildDeployDigest, buildSendDigest, buildOwnerActionDigest, buildTransferPayload, stripNativeAddress } = require("./digest");
12
13
  const { TransactionResponse } = require("./transaction");
13
14
 
15
+ /** Green Chain deploy rule: a token symbol is capital letters A-Z and digits 0-9 only. */
16
+ const TOKEN_SYMBOL_RE = /^[A-Z0-9]+$/;
17
+
18
+ function isValidTokenSymbol(symbol) {
19
+ return TOKEN_SYMBOL_RE.test(String(symbol == null ? "" : symbol));
20
+ }
21
+
14
22
  /**
15
23
  * Local secp256k1 wallet for Green Chain digests.
16
24
  */
@@ -39,11 +47,24 @@ class Cx3Wallet {
39
47
  return this.address;
40
48
  }
41
49
 
50
+ /** Sign raw bytes / a digest string as given (sha256 + secp256k1): the typed digests use this. */
42
51
  async signMessage(message) {
43
52
  return signMessage(message, this._privateKey);
44
53
  }
45
54
 
46
- async signDeploy(fields) {
55
+ /** Sign a site message the way the CX3Wallet extension and app do (with the site-message prefix). */
56
+ async signPersonalMessage(message) {
57
+ return signPersonalMessage(message, this._privateKey);
58
+ }
59
+
60
+ /** The network identity typed digests bind: `opts.chainId` + `opts.genesisHash`, else the provider's. */
61
+ async _identity(opts = {}) {
62
+ if (opts.chainId && opts.genesisHash) return { chainId: opts.chainId, genesisHash: opts.genesisHash };
63
+ if (!this.provider) throw new Cx3Error("chainId and genesisHash required without a provider", "NO_CHAIN_IDENTITY");
64
+ return this.provider.getChainIdentity();
65
+ }
66
+
67
+ async signDeploy(fields, opts = {}) {
47
68
  const owner = normalizeAddress(fields.owner || this.address);
48
69
  const timestamp = fields.timestamp || Date.now();
49
70
  const digest = buildDeployDigest({
@@ -54,7 +75,7 @@ class Cx3Wallet {
54
75
  decimals: fields.decimals,
55
76
  initialSupply: fields.initialSupply,
56
77
  timestamp
57
- });
78
+ }, await this._identity(opts));
58
79
  const signed = await this.signMessage(digest);
59
80
  return {
60
81
  ...signed,
@@ -64,23 +85,25 @@ class Cx3Wallet {
64
85
  };
65
86
  }
66
87
 
67
- async signSend(contractAddress, method, params) {
88
+ async signSend(contractAddress, method, params, opts = {}) {
68
89
  const fromAddress = this.address;
69
90
  const timestamp = Date.now();
70
- const digest = buildSendDigest(contractAddress, method, params || {}, fromAddress, timestamp);
91
+ const safeParams = toJsonSafe(params || {});
92
+ const digest = buildSendDigest(contractAddress, method, safeParams, fromAddress, timestamp, await this._identity(opts));
71
93
  const signed = await this.signMessage(digest);
72
94
  return {
73
95
  ...signed,
74
96
  fromAddress,
75
97
  timestamp,
98
+ params: safeParams,
76
99
  digest
77
100
  };
78
101
  }
79
102
 
80
- async signOwnerAction(action, address, owner = null) {
103
+ async signOwnerAction(action, address, owner = null, opts = {}) {
81
104
  const own = normalizeAddress(owner || this.address);
82
105
  const timestamp = Date.now();
83
- const digest = buildOwnerActionDigest(action, address, own, timestamp);
106
+ const digest = buildOwnerActionDigest(action, address, own, timestamp, await this._identity(opts));
84
107
  const signed = await this.signMessage(digest);
85
108
  return {
86
109
  ...signed,
@@ -91,10 +114,15 @@ class Cx3Wallet {
91
114
  }
92
115
 
93
116
  /**
94
- * Signed state-changing call via attached provider.
117
+ * Signed state-changing call via attached provider. A transfer / transferFrom / mint of a private
118
+ * token is checked first (PRIVATE_TOKEN_SENDER / PRIVATE_TOKEN_RECIPIENT); `privateCheck: false`
119
+ * skips that.
95
120
  */
96
121
  async sendTransaction(contractAddress, method, params = {}, opts = {}) {
97
122
  if (!this.provider) throw new Cx3Error("Wallet has no provider", "NO_PROVIDER");
123
+ if (opts.privateCheck !== false && typeof this.provider.checkPrivateTokenSend === "function") {
124
+ await this.provider.checkPrivateTokenSend(contractAddress, method, params, this.address);
125
+ }
98
126
  const signed = await this.signSend(contractAddress, method, params);
99
127
  const body = {
100
128
  fromAddress: signed.fromAddress,
@@ -103,7 +131,8 @@ class Cx3Wallet {
103
131
  timestamp: signed.timestamp
104
132
  };
105
133
  const asyncMode = opts.wait === 0 || opts.async === true;
106
- const result = await this.provider.send(contractAddress, method, params, {
134
+ // send exactly the params that were signed (BigInt already as strings)
135
+ const result = await this.provider.send(contractAddress, method, signed.params, {
107
136
  ...body,
108
137
  wait: asyncMode ? 0 : undefined
109
138
  });
@@ -111,10 +140,24 @@ class Cx3Wallet {
111
140
  }
112
141
 
113
142
  /**
114
- * Deploy via Green Chain `POST /deployContract`.
143
+ * Deploy via Green Chain `POST /deployContract`. A symbol outside A-Z / 0-9 is refused here,
144
+ * before signing (INVALID_TOKEN_SYMBOL); the chain also refuses a symbol already in use
145
+ * (Cx3HttpError code DUPLICATE_TOKEN_SYMBOL, status 409).
115
146
  */
116
147
  async deployContract(fields) {
117
148
  if (!this.provider) throw new Cx3Error("Wallet has no provider", "NO_PROVIDER");
149
+ fields = toJsonSafe(fields || {});
150
+ const ctor = fields.constructorArgs && typeof fields.constructorArgs === "object" && !Array.isArray(fields.constructorArgs) ? fields.constructorArgs : {};
151
+ for (const symbol of [fields.symbol, ctor.symbol]) {
152
+ if (symbol === undefined || symbol === null || symbol === "") continue;
153
+ if (!isValidTokenSymbol(symbol)) {
154
+ throw new Cx3Error(
155
+ "The token symbol may contain only capital letters A-Z and digits 0-9 (no lowercase or Turkish letters)",
156
+ "INVALID_TOKEN_SYMBOL",
157
+ { symbol: String(symbol) }
158
+ );
159
+ }
160
+ }
118
161
  const signed = await this.signDeploy({
119
162
  ...fields,
120
163
  owner: fields.owner || this.address
@@ -203,6 +246,10 @@ class Cx3Wallet {
203
246
  nonce = await this.provider.getNonce(this.address);
204
247
  }
205
248
  }
249
+ // the signature covers the network's chainId + genesis hash: asked from the network unless given
250
+ const identity = opts.chainId && opts.genesisHash
251
+ ? { chainId: opts.chainId, genesisHash: opts.genesisHash }
252
+ : await this.provider.getChainIdentity();
206
253
  const timestamp = opts.timestamp || Date.now();
207
254
  const payload = buildTransferPayload({
208
255
  fromAddress: this.address,
@@ -213,8 +260,8 @@ class Cx3Wallet {
213
260
  data: opts.data || null,
214
261
  gasLimit: opts.gasLimit,
215
262
  gasPrice: opts.gasPrice,
216
- chainId: opts.chainId,
217
- chainIdBind: opts.chainIdBind
263
+ chainId: identity.chainId,
264
+ genesisHash: identity.genesisHash
218
265
  });
219
266
  const signed = await this.signMessage(payload);
220
267
  const body = {
@@ -225,6 +272,9 @@ class Cx3Wallet {
225
272
  nonce,
226
273
  timestamp,
227
274
  data: opts.data || null,
275
+ // the node rebuilds the signed payload from these fields
276
+ ...(typeof opts.gasLimit === "number" && opts.gasLimit > 0 ? { gasLimit: opts.gasLimit } : {}),
277
+ ...(typeof opts.gasPrice === "number" && opts.gasPrice > 0 ? { gasPrice: opts.gasPrice } : {}),
228
278
  r: signed.signature,
229
279
  s: null,
230
280
  v: signed.v,
@@ -244,4 +294,4 @@ class Cx3Wallet {
244
294
  }
245
295
  }
246
296
 
247
- module.exports = { Cx3Wallet };
297
+ module.exports = { Cx3Wallet, isValidTokenSymbol };