@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/README.md +239 -51
- package/index.d.ts +108 -21
- package/index.js +14 -6
- package/package.json +13 -6
- package/src/abi.js +8 -1
- package/src/contract.js +5 -0
- package/src/crypto.js +17 -4
- package/src/digest.js +74 -38
- package/src/factory.js +105 -1
- package/src/provider.js +344 -59
- package/src/utils.js +19 -0
- package/src/wallet.js +63 -13
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
217
|
-
|
|
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 };
|