@carbonx3/cx3.js 0.3.7 → 0.3.9
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 +3 -3
- package/index.d.ts +6 -1
- package/index.js +3 -1
- package/package.json +1 -1
- package/src/digest.js +44 -1
- package/src/wallet.js +25 -5
package/README.md
CHANGED
|
@@ -219,7 +219,7 @@ A private CX31, CX300, CX315 or CX3192 takes its CX3WebUI source as `sourceCode`
|
|
|
219
219
|
|
|
220
220
|
Digests are UTF-8 strings, hashed with SHA-256 and signed with secp256k1, byte-identical to what the Green Chain node verifies. Each kind starts with its own prefix (signing domains), so a signature of one kind never verifies as another; `chainId` and `genesisHash` are the network's (`provider.getChainIdentity()`, fetched by the wallet methods themselves) and addresses are lower-case `0x` + 40 hex:
|
|
221
221
|
|
|
222
|
-
- **Deploy:** `
|
|
222
|
+
- **Deploy:** `CX3_DEPLOY_V2|` + `JSON.stringify([chainId, genesisHash, owner, standard, name, symbol, decimals, initialSupply, contentHash, timestamp])`. `contentHash` is `Cx3Wallet.deployContentHash(request)`: the SHA-256 of the code, the constructor arguments, the target and the template switches of the request, so the signature is good for that content only. `deployContract` computes it, signs it and sends it. `signDeploy` without a `contentHash` signs the older `CX3_DEPLOY_V1` (the same list without it), which the network still accepts.
|
|
223
223
|
- **Contract send:** `CX3_CALL_V1|` + `JSON.stringify([chainId, genesisHash, contractAddress, method, params, fromAddress, timestamp])`. The chain checks this signature again inside the relayed transaction: a call runs as your wallet only with it.
|
|
224
224
|
- **Owner action:** `CX3_OWNER_V1|` + `JSON.stringify([chainId, genesisHash, action, address, owner, timestamp])`
|
|
225
225
|
- **Site message:** `0x19` + `"CX3 Signed Message:\n"` + UTF-8 length + message (`signPersonalMessage`), the way the CX3Wallet extension and app sign `cx3_sign_message`
|
|
@@ -270,7 +270,7 @@ try {
|
|
|
270
270
|
|
|
271
271
|
## Browser applications
|
|
272
272
|
|
|
273
|
-
This package is for Node.js servers. Web pages use the **CX3 IIFE** (`window.cx3`) together with the CX3Wallet extension: [`@carbonx3/cx3-iife`](https://www.npmjs.com/package/@carbonx3/cx3-iife) on npm and jsDelivr, or [download it](https://cx3script.com/download/cx3-0.2.
|
|
273
|
+
This package is for Node.js servers. Web pages use the **CX3 IIFE** (`window.cx3`) together with the CX3Wallet extension: [`@carbonx3/cx3-iife`](https://www.npmjs.com/package/@carbonx3/cx3-iife) on npm and jsDelivr, or [download it](https://cx3script.com/download/cx3-0.2.9.min.js); see the [CX3 IIFE guide](https://cx3script.com/docs?doc=cx3iife). Keep the application's credentials on your server behind a same-origin proxy, and never put a client secret, an API key or a private key in browser JavaScript.
|
|
274
274
|
|
|
275
275
|
## Environment variables
|
|
276
276
|
|
|
@@ -286,7 +286,7 @@ Type declarations (`index.d.ts`) ship with the package.
|
|
|
286
286
|
|
|
287
287
|
## Upgrading from 0.2.x
|
|
288
288
|
|
|
289
|
-
Version 0.3.0 signs native transfers for the network's new signing format (0.3.1 to 0.3.4 change only this README, the file headers and comments, not the code). In 0.3.5, `bindApiKey` with a wallet API key asks the gateway's `/wallet/api-key/bind` (before, it asked `/whoami`, which the gateway refuses, so it always failed). In 0.3.6, `wait()` follows a send or deploy the gateway reports as not final (`dropped`) until its `finalAfter` instead of failing after 60 s, a `TX_TIMEOUT` answer with a hash is pending, `getRequest()` looks up a signed request, and `transferNative` refuses more than 6 decimals. 0.3.7 changes only this README: a native transfer's gas price floor is 0.0001 (the 0.3.6 README said 0.000001).
|
|
289
|
+
Version 0.3.0 signs native transfers for the network's new signing format (0.3.1 to 0.3.4 change only this README, the file headers and comments, not the code). In 0.3.5, `bindApiKey` with a wallet API key asks the gateway's `/wallet/api-key/bind` (before, it asked `/whoami`, which the gateway refuses, so it always failed). In 0.3.6, `wait()` follows a send or deploy the gateway reports as not final (`dropped`) until its `finalAfter` instead of failing after 60 s, a `TX_TIMEOUT` answer with a hash is pending, `getRequest()` looks up a signed request, and `transferNative` refuses more than 6 decimals. 0.3.7 changes only this README: a native transfer's gas price floor is 0.0001 (the 0.3.6 README said 0.000001). 0.3.8 changes only this README too: the CX3 IIFE download link names `cx3-0.2.9.min.js`. In 0.3.9, `deployContract` signs `CX3_DEPLOY_V2`: the code and the constructor arguments of the request are under the signature (`Cx3Wallet.deployContentHash`, `signDeploy({ contentHash })`, `deployContentText`).
|
|
290
290
|
|
|
291
291
|
- **The genesis hash is signed.** `transferNative` asks the network for its chain id and genesis hash (`provider.getChainIdentity()`) unless both are given as `opts.chainId` and `opts.genesisHash`. 0.2.x transfers no longer verify on the chain.
|
|
292
292
|
- **No fallback chain id.** `buildTransferPayload` throws `NO_CHAIN_IDENTITY` without `chainId` and `genesisHash`; the `CX3_CHAIN_ID` variable and the `chainIdBind` option are gone.
|
package/index.d.ts
CHANGED
|
@@ -149,7 +149,10 @@ export class Cx3Wallet {
|
|
|
149
149
|
/** A site message the way the CX3Wallet extension and app sign it (0x19 "CX3 Signed Message:\n" prefix) */
|
|
150
150
|
signPersonalMessage(message: string): Promise<any>;
|
|
151
151
|
/** Typed digests (CX3_DEPLOY_V1 / CX3_CALL_V1 / CX3_OWNER_V1) bound to the network's chainId + genesisHash (from the provider unless given in opts) */
|
|
152
|
+
/** With fields.contentHash (Cx3Wallet.deployContentHash(request)) the digest is CX3_DEPLOY_V2; the answer carries digestVersion 2 | 1 */
|
|
152
153
|
signDeploy(fields: object, opts?: Cx3ChainIdentity): Promise<any>;
|
|
154
|
+
/** sha256 (hex) of the code, constructor arguments, target and template switches of a deploy request */
|
|
155
|
+
static deployContentHash(request: object): string;
|
|
153
156
|
signSend(contractAddress: string, method: string, params?: object, opts?: Cx3ChainIdentity): Promise<any>;
|
|
154
157
|
signOwnerAction(action: string, address: string, owner?: string | null, opts?: Cx3ChainIdentity): Promise<any>;
|
|
155
158
|
/** A private token's transfer / transferFrom / mint is checked first (PRIVATE_TOKEN_SENDER / _RECIPIENT); `privateCheck: false` skips it */
|
|
@@ -263,8 +266,10 @@ export function ensure0x(hex: string): string;
|
|
|
263
266
|
export function strip0x(hex: string): string;
|
|
264
267
|
/** The network a typed digest or a native transfer is bound to (provider.getChainIdentity()) */
|
|
265
268
|
export interface Cx3ChainIdentity { chainId?: string; genesisHash?: string }
|
|
266
|
-
/**
|
|
269
|
+
/** With fields.contentHash: CX3_DEPLOY_V2|[chainId, genesisHash, owner, standard, name, symbol, decimals, initialSupply, contentHash, timestamp]; without: CX3_DEPLOY_V1 (the same list without it) */
|
|
267
270
|
export function buildDeployDigest(fields: object, identity: Cx3ChainIdentity): string;
|
|
271
|
+
/** The text whose sha256 (hex) is a deploy request's contentHash */
|
|
272
|
+
export function deployContentText(request: object): string;
|
|
268
273
|
/** CX3_CALL_V1|[chainId, genesisHash, contract, method, params, from, timestamp] */
|
|
269
274
|
export function buildSendDigest(contractAddress: string, method: string, params: object, fromAddress: string, timestamp: number, identity: Cx3ChainIdentity): string;
|
|
270
275
|
/** CX3_OWNER_V1|[chainId, genesisHash, action, address, owner, timestamp] */
|
package/index.js
CHANGED
|
@@ -20,6 +20,7 @@ const {
|
|
|
20
20
|
} = require("./src/utils");
|
|
21
21
|
const {
|
|
22
22
|
buildDeployDigest,
|
|
23
|
+
deployContentText,
|
|
23
24
|
buildSendDigest,
|
|
24
25
|
buildOwnerActionDigest,
|
|
25
26
|
buildTransferPayload,
|
|
@@ -33,7 +34,7 @@ const {
|
|
|
33
34
|
} = require("./src/crypto");
|
|
34
35
|
const { normalizeAbi, argsToParams } = require("./src/abi");
|
|
35
36
|
|
|
36
|
-
const VERSION = "0.3.
|
|
37
|
+
const VERSION = "0.3.9";
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Convenience: provider from `options.url` or env `CX3_NODE_URL` — the CX3Api gateway
|
|
@@ -65,6 +66,7 @@ module.exports = {
|
|
|
65
66
|
walletApiKeyMode,
|
|
66
67
|
toJsonSafe,
|
|
67
68
|
buildDeployDigest,
|
|
69
|
+
deployContentText,
|
|
68
70
|
buildSendDigest,
|
|
69
71
|
buildOwnerActionDigest,
|
|
70
72
|
buildTransferPayload,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carbonx3/cx3.js",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "Official Node.js SDK (CX3js) for CarbonX3 Green Chain: OAuth2 app access, local secp256k1 signing, ABI-bound contracts and token deploys over REST + JSON",
|
|
5
5
|
"homepage": "https://cx3script.com",
|
|
6
6
|
"publishConfig": {
|
package/src/digest.js
CHANGED
|
@@ -34,7 +34,48 @@ function requireIdentity(identity) {
|
|
|
34
34
|
return [chainId, genesisHash];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
/** JSON with object keys in order, undefined as null (the node's signingDomains.canonicalJson). */
|
|
38
|
+
function canonicalJson(v) {
|
|
39
|
+
if (v === undefined || v === null || typeof v === "function" || typeof v === "symbol") return "null";
|
|
40
|
+
if (typeof v === "bigint") return JSON.stringify(v.toString());
|
|
41
|
+
if (typeof v !== "object") return JSON.stringify(v) === undefined ? "null" : JSON.stringify(v);
|
|
42
|
+
if (Array.isArray(v)) return `[${v.map(canonicalJson).join(",")}]`;
|
|
43
|
+
return `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canonicalJson(v[k])}`).join(",")}}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The text whose sha256 (hex) is a deploy request's contentHash — what the request deploys beyond the
|
|
48
|
+
* CX3_DEPLOY_V1 fields: the code as sent, the constructor arguments, the target, the template switches.
|
|
49
|
+
* Byte-identical to the node's signingDomains.deployContentHash input.
|
|
50
|
+
*/
|
|
51
|
+
function deployContentText(body) {
|
|
52
|
+
const b = body && typeof body === "object" ? body : {};
|
|
53
|
+
const flag = (v) => (v === undefined || v === null ? null : (v === true || v === "true"));
|
|
54
|
+
return canonicalJson({
|
|
55
|
+
sourceCode: String(b.sourceCode || b.bytecode || ""),
|
|
56
|
+
constructorArgs: b.constructorArgs === undefined ? null : b.constructorArgs,
|
|
57
|
+
target: String(b.target || ""),
|
|
58
|
+
mintable: flag(b.mintable),
|
|
59
|
+
burnable: flag(b.burnable),
|
|
60
|
+
pausable: flag(b.pausable),
|
|
61
|
+
totalSupply: b.totalSupply === undefined || b.totalSupply === null ? null : String(b.totalSupply)
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* With `contentHash` (64 hex, sha256 of deployContentText) the digest is CX3_DEPLOY_V2: the code and
|
|
67
|
+
* the constructor arguments are under the signature too. Without it, CX3_DEPLOY_V1.
|
|
68
|
+
*/
|
|
69
|
+
function buildDeployDigest({ owner, standard, name, symbol, decimals, initialSupply, contentHash, timestamp }, identity) {
|
|
70
|
+
const hash = String(contentHash == null ? "" : contentHash).toLowerCase();
|
|
71
|
+
if (hash) {
|
|
72
|
+
if (!/^[0-9a-f]{64}$/.test(hash)) throw new Cx3Error("contentHash must be 64 hex characters", "INVALID_CONTENT_HASH");
|
|
73
|
+
return `CX3_DEPLOY_V2|${JSON.stringify([
|
|
74
|
+
...requireIdentity(identity), normalizeDigestAddress(owner), String(standard || ""), String(name || ""),
|
|
75
|
+
String(symbol || ""), decimals == null ? 18 : Number(decimals),
|
|
76
|
+
initialSupply == null ? "0" : String(initialSupply), hash, Number(timestamp)
|
|
77
|
+
])}`;
|
|
78
|
+
}
|
|
38
79
|
return `CX3_DEPLOY_V1|${JSON.stringify([
|
|
39
80
|
...requireIdentity(identity), normalizeDigestAddress(owner), String(standard || ""), String(name || ""),
|
|
40
81
|
String(symbol || ""), decimals == null ? 18 : Number(decimals),
|
|
@@ -109,6 +150,8 @@ function buildTransferPayload(fields = {}) {
|
|
|
109
150
|
}
|
|
110
151
|
|
|
111
152
|
module.exports = {
|
|
153
|
+
canonicalJson,
|
|
154
|
+
deployContentText,
|
|
112
155
|
buildDeployDigest,
|
|
113
156
|
buildSendDigest,
|
|
114
157
|
buildOwnerActionDigest,
|
package/src/wallet.js
CHANGED
|
@@ -9,11 +9,12 @@ const {
|
|
|
9
9
|
privateKeyToPublicKey,
|
|
10
10
|
signMessage,
|
|
11
11
|
signPersonalMessage,
|
|
12
|
-
createPrivateKey
|
|
12
|
+
createPrivateKey,
|
|
13
|
+
sha256
|
|
13
14
|
} = require("./crypto");
|
|
14
15
|
const { normalizeAddress, toJsonSafe } = require("./utils");
|
|
15
16
|
const { Cx3Error } = require("./errors");
|
|
16
|
-
const { buildDeployDigest, buildSendDigest, buildOwnerActionDigest, buildTransferPayload, stripNativeAddress } = require("./digest");
|
|
17
|
+
const { buildDeployDigest, buildSendDigest, buildOwnerActionDigest, buildTransferPayload, stripNativeAddress, deployContentText } = require("./digest");
|
|
17
18
|
const { TransactionResponse } = require("./transaction");
|
|
18
19
|
|
|
19
20
|
/** Green Chain deploy rule: a token symbol is capital letters A-Z and digits 0-9 only. */
|
|
@@ -101,9 +102,15 @@ class Cx3Wallet {
|
|
|
101
102
|
return this.provider.getChainIdentity();
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Sign a deploy request. With `fields.contentHash` (Cx3Wallet.deployContentHash(request)) the digest is
|
|
107
|
+
* CX3_DEPLOY_V2: the code and the constructor arguments are under the signature, and the request
|
|
108
|
+
* must carry the same `contentHash`. Without it, CX3_DEPLOY_V1.
|
|
109
|
+
*/
|
|
104
110
|
async signDeploy(fields, opts = {}) {
|
|
105
111
|
const owner = normalizeAddress(fields.owner || this.address);
|
|
106
112
|
const timestamp = fields.timestamp || Date.now();
|
|
113
|
+
const contentHash = fields.contentHash ? String(fields.contentHash).toLowerCase() : undefined;
|
|
107
114
|
const digest = buildDeployDigest({
|
|
108
115
|
owner,
|
|
109
116
|
standard: fields.standard,
|
|
@@ -111,6 +118,7 @@ class Cx3Wallet {
|
|
|
111
118
|
symbol: fields.symbol,
|
|
112
119
|
decimals: fields.decimals,
|
|
113
120
|
initialSupply: fields.initialSupply,
|
|
121
|
+
contentHash,
|
|
114
122
|
timestamp
|
|
115
123
|
}, await this._identity(opts));
|
|
116
124
|
const signed = await this.signMessage(digest);
|
|
@@ -118,10 +126,18 @@ class Cx3Wallet {
|
|
|
118
126
|
...signed,
|
|
119
127
|
owner,
|
|
120
128
|
timestamp,
|
|
121
|
-
digest
|
|
129
|
+
digest,
|
|
130
|
+
digestVersion: contentHash ? 2 : 1,
|
|
131
|
+
...(contentHash ? { contentHash } : {})
|
|
122
132
|
};
|
|
123
133
|
}
|
|
124
134
|
|
|
135
|
+
/** sha256 (hex) of what a deploy request deploys beyond the named fields — the node's deployContentHash. */
|
|
136
|
+
static deployContentHash(request) {
|
|
137
|
+
const asSent = JSON.parse(JSON.stringify(toJsonSafe(request || {})));
|
|
138
|
+
return Buffer.from(sha256(deployContentText(asSent))).toString("hex");
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
async signSend(contractAddress, method, params, opts = {}) {
|
|
126
142
|
const fromAddress = this.address;
|
|
127
143
|
const timestamp = Date.now();
|
|
@@ -196,9 +212,12 @@ class Cx3Wallet {
|
|
|
196
212
|
);
|
|
197
213
|
}
|
|
198
214
|
}
|
|
215
|
+
// CX3_DEPLOY_V2: the code and the constructor arguments of this request are signed too
|
|
216
|
+
const contentHash = Cx3Wallet.deployContentHash(fields);
|
|
199
217
|
const signed = await this.signDeploy({
|
|
200
218
|
...fields,
|
|
201
|
-
owner: fields.owner || this.address
|
|
219
|
+
owner: fields.owner || this.address,
|
|
220
|
+
contentHash
|
|
202
221
|
});
|
|
203
222
|
const body = {
|
|
204
223
|
...fields,
|
|
@@ -207,7 +226,8 @@ class Cx3Wallet {
|
|
|
207
226
|
wallet: fields.wallet || signed.owner,
|
|
208
227
|
timestamp: signed.timestamp,
|
|
209
228
|
signature: signed.signature,
|
|
210
|
-
senderPublicKey: signed.senderPublicKey
|
|
229
|
+
senderPublicKey: signed.senderPublicKey,
|
|
230
|
+
contentHash
|
|
211
231
|
};
|
|
212
232
|
const result = await this.provider.request("/deployContract", {
|
|
213
233
|
method: "POST",
|