@carbonx3/cx3.js 0.3.10 → 0.3.11
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 +7 -4
- package/index.d.ts +12 -7
- package/index.js +1 -1
- package/package.json +2 -2
- package/src/provider.js +39 -3
- package/src/wallet.js +26 -14
package/README.md
CHANGED
|
@@ -313,7 +313,8 @@ try {
|
|
|
313
313
|
| `insufficient_scope`, `ROUTE_NOT_ALLOWED` | The route needs another scope, or is not open to applications (403) |
|
|
314
314
|
| `RATE_LIMITED` | The application's rate limit was reached (429) |
|
|
315
315
|
| `INSUFFICIENT_BALANCE`, `GAS_PRICE_TOO_LOW` | A native transfer's sender cannot pay the amount plus gas, or the gas price is below the chain floor |
|
|
316
|
-
| `INVALID_AMOUNT`, `INVALID_AMOUNT_PRECISION` | A native amount is not greater than 0, or has more than 6 decimal places (CX3 has 6); `transferNative` checks
|
|
316
|
+
| `INVALID_AMOUNT`, `INVALID_AMOUNT_PRECISION` | A native amount is not greater than 0, is a text that is not plain decimal digits (`"0x10"`, `"1e3"`), or has more than 6 decimal places (CX3 has 6); `transferNative` checks before signing |
|
|
317
|
+
| `INVALID_RECIPIENT` | `transferNative` was given a recipient that is not a full address (`0x` or `cx3:` and 40 hex digits); nothing is signed |
|
|
317
318
|
| `GAS_LIMIT_TOO_LOW`, `GAS_LIMIT_TOO_HIGH` | A native transfer's gas limit is outside 21,000–100,000 |
|
|
318
319
|
| `GAS_LIMIT_EXCEEDED` | A contract send's `gasLimit` is above the chain's maximum (400, `max` in the body) |
|
|
319
320
|
| `INVALID_NONCE_ORDER` | A native transfer's nonce is not the next one (`expectedNonce` in the answer) |
|
|
@@ -333,16 +334,18 @@ try {
|
|
|
333
334
|
| `LEDGER_UNAVAILABLE`, `LEDGER_FULL` | The gateway's `/dapp/v1` send could not record the request (503): it was not sent, and the same signed body may be sent again later, without a new signature |
|
|
334
335
|
| `BASIC_AUTH_UNSUPPORTED` | The `auth` option was used |
|
|
335
336
|
| `NO_CHAIN_IDENTITY` | The network's chain id or genesis hash is unavailable; a native transfer is not signed without them |
|
|
337
|
+
| `CHAIN_IDENTITY_MISMATCH` | The provider was made for one network (`chainId` and `genesisHash` options) and the endpoint names another; nothing is signed (`err.details.expected`, `err.details.got`) |
|
|
338
|
+
| `INSECURE_BASE_URL` | Credentials would travel over plain `http` to another machine; use `https` (or `allowInsecure: true` on a network you trust) |
|
|
336
339
|
|
|
337
340
|
## Browser applications
|
|
338
341
|
|
|
339
|
-
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.
|
|
342
|
+
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.11.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.
|
|
340
343
|
|
|
341
344
|
## Environment variables
|
|
342
345
|
|
|
343
346
|
| Variable | Used for |
|
|
344
347
|
|----------|----------|
|
|
345
|
-
| `CX3_CLIENT_ID`, `CX3_CLIENT_SECRET` | Application credentials when the options are omitted |
|
|
348
|
+
| `CX3_CLIENT_ID`, `CX3_CLIENT_SECRET` | Application credentials when the options are omitted (not read with `devNode: true`) |
|
|
346
349
|
| `CX3_API_KEY` | Legacy wallet API key |
|
|
347
350
|
| `CX3_NODE_URL` | Gateway URL for `getDefaultProvider()` |
|
|
348
351
|
|
|
@@ -352,7 +355,7 @@ Type declarations (`index.d.ts`) ship with the package.
|
|
|
352
355
|
|
|
353
356
|
## Upgrading from 0.2.x
|
|
354
357
|
|
|
355
|
-
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.10.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`). In 0.3.10, a refused native transfer (HTTP 200 with `error`) is `success: false` / `status: "failed"` and `wait()` rejects (before, it was reported as confirmed); an accepted transfer without a block is `"pending"`; `wait()` rejects with `NO_TX_HASH` when the answer names no transaction; the private key, client secret, API key and access token are private fields (`wallet._privateKey`, `provider.apiKey` and `provider._clientSecret` are gone; use `wallet.exportPrivateKey()`), and results no longer carry the provider into JSON or logs.
|
|
358
|
+
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.10.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`). In 0.3.10, a refused native transfer (HTTP 200 with `error`) is `success: false` / `status: "failed"` and `wait()` rejects (before, it was reported as confirmed); an accepted transfer without a block is `"pending"`; `wait()` rejects with `NO_TX_HASH` when the answer names no transaction; the private key, client secret, API key and access token are private fields (`wallet._privateKey`, `provider.apiKey` and `provider._clientSecret` are gone; use `wallet.exportPrivateKey()`), and results no longer carry the provider into JSON or logs. In 0.3.11, `transferNative` refuses a recipient that is not a full address (`INVALID_RECIPIENT`) and a text amount that is not plain decimal digits; `sendTransaction`, `deployContract` and the owner actions sign for `opts.chainId` and `opts.genesisHash` when both are given; a provider can be made for one network (`chainId` and `genesisHash` options, `CHAIN_IDENTITY_MISMATCH`); credentials are not sent over plain `http` to another machine (`INSECURE_BASE_URL`); with `devNode: true` the credentials of the environment are not read; and `@noble/secp256k1` is pinned to one exact version.
|
|
356
359
|
|
|
357
360
|
- **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.
|
|
358
361
|
- **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
|
@@ -31,8 +31,13 @@ export interface Cx3ProviderOptions {
|
|
|
31
31
|
scope?: string | Array<"read" | "write" | "deploy">;
|
|
32
32
|
/** wallet API key (cx3wk_…), until apps move to OAuth2; env CX3_API_KEY */
|
|
33
33
|
apiKey?: string;
|
|
34
|
-
/** a development node that runs without auth (/api/v1)
|
|
34
|
+
/** a development node that runs without auth (/api/v1); the credentials of the environment are not read for it */
|
|
35
35
|
devNode?: boolean;
|
|
36
|
+
/** The network this provider is for (both together): an endpoint on another network is refused with CHAIN_IDENTITY_MISMATCH and nothing is signed */
|
|
37
|
+
chainId?: string;
|
|
38
|
+
genesisHash?: string;
|
|
39
|
+
/** Credentials over plain http to another machine throw INSECURE_BASE_URL unless this is true (localhost is always allowed) */
|
|
40
|
+
allowInsecure?: boolean;
|
|
36
41
|
/** "" on the gateway, "/api/v1" on a development node */
|
|
37
42
|
apiPrefix?: string;
|
|
38
43
|
/** Not supported: throws BASIC_AUTH_UNSUPPORTED (HTTP Basic is the nodes' internal login) */
|
|
@@ -165,15 +170,15 @@ export class Cx3Wallet {
|
|
|
165
170
|
signSend(contractAddress: string, method: string, params?: object, opts?: Cx3ChainIdentity): Promise<any>;
|
|
166
171
|
signOwnerAction(action: string, address: string, owner?: string | null, opts?: Cx3ChainIdentity): Promise<any>;
|
|
167
172
|
/** A private token's transfer / transferFrom / mint is checked first (PRIVATE_TOKEN_SENDER / _RECIPIENT); `privateCheck: false` skips it */
|
|
168
|
-
sendTransaction(contractAddress: string, method: string, params?: object, opts?: { wait?: number; async?: boolean; privateCheck?: boolean; [k: string]: unknown }): Promise<TransactionResponse>;
|
|
173
|
+
sendTransaction(contractAddress: string, method: string, params?: object, opts?: { wait?: number; async?: boolean; privateCheck?: boolean; chainId?: string; genesisHash?: string; [k: string]: unknown }): Promise<TransactionResponse>;
|
|
169
174
|
/** Throws Cx3Error INVALID_TOKEN_SYMBOL (before signing) for a symbol outside A-Z / 0-9; the chain answers DUPLICATE_TOKEN_SYMBOL (409) for a symbol in use. A dropped deploy is pending: result.wait() follows it. */
|
|
170
175
|
/** Sends an earlier send's signed request again, unchanged (no new signature): after SEND_OUTCOME_UNKNOWN or a lost answer */
|
|
171
176
|
resendTransaction(signedRequest: Cx3SignedRequest, opts?: { wait?: number; async?: boolean }): Promise<TransactionResponse>;
|
|
172
|
-
deployContract(fields: object): Promise<Cx3DeployResult>;
|
|
173
|
-
pauseContract(address: string, owner?: string | null): Promise<any>;
|
|
174
|
-
unpauseContract(address: string, owner?: string | null): Promise<any>;
|
|
175
|
-
deleteContract(address: string, owner?: string | null): Promise<any>;
|
|
176
|
-
/**
|
|
177
|
+
deployContract(fields: object, opts?: Cx3ChainIdentity): Promise<Cx3DeployResult>;
|
|
178
|
+
pauseContract(address: string, owner?: string | null, opts?: Cx3ChainIdentity): Promise<any>;
|
|
179
|
+
unpauseContract(address: string, owner?: string | null, opts?: Cx3ChainIdentity): Promise<any>;
|
|
180
|
+
deleteContract(address: string, owner?: string | null, opts?: Cx3ChainIdentity): Promise<any>;
|
|
181
|
+
/** Before signing, throws Cx3Error INVALID_RECIPIENT (not 0x / cx3: + 40 hex), INVALID_AMOUNT (not greater than 0, or a text that is not plain decimal digits) or INVALID_AMOUNT_PRECISION (more than 6 decimals) */
|
|
177
182
|
transferNative(to: string, amount: number | string, opts?: { nonce?: number; timestamp?: number; data?: unknown; gasLimit?: number; gasPrice?: number; chainId?: string; genesisHash?: string; wait?: number }): Promise<TransactionResponse>;
|
|
178
183
|
bindApiKey(address?: string): Promise<any>;
|
|
179
184
|
}
|
package/index.js
CHANGED
|
@@ -34,7 +34,7 @@ const {
|
|
|
34
34
|
} = require("./src/crypto");
|
|
35
35
|
const { normalizeAbi, argsToParams } = require("./src/abi");
|
|
36
36
|
|
|
37
|
-
const VERSION = "0.3.
|
|
37
|
+
const VERSION = "0.3.11";
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Convenience: provider from `options.url` or env `CX3_NODE_URL` — the CX3Api gateway
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carbonx3/cx3.js",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
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": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"node": ">=20.19.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@noble/secp256k1": "
|
|
43
|
+
"@noble/secp256k1": "3.2.0"
|
|
44
44
|
},
|
|
45
45
|
"exports": {
|
|
46
46
|
".": {
|
package/src/provider.js
CHANGED
|
@@ -65,7 +65,16 @@ function withoutCredentialHeaders(headers) {
|
|
|
65
65
|
return out;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
// http to another machine (not localhost / 127.x / ::1)
|
|
69
|
+
function isRemotePlainHttp(baseUrl) {
|
|
70
|
+
let u;
|
|
71
|
+
try { u = new URL(baseUrl); } catch (_) { return false; }
|
|
72
|
+
if (u.protocol !== "http:") return false;
|
|
73
|
+
const host = u.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
74
|
+
return !(host === "localhost" || host.endsWith(".localhost") || host === "::1" || /^127(\.\d{1,3}){3}$/.test(host));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const CLIENT_ID_RE =/^cx3app_[a-f0-9]{24}$/i;
|
|
69
78
|
const CLIENT_SECRET_RE = /^cx3sec_[a-f0-9]{64}$/i;
|
|
70
79
|
|
|
71
80
|
// a dropped tx is failed for good after its finalAfter: wait this much past it (clock skew)
|
|
@@ -106,12 +115,13 @@ class Cx3Provider {
|
|
|
106
115
|
"BASIC_AUTH_UNSUPPORTED"
|
|
107
116
|
);
|
|
108
117
|
}
|
|
109
|
-
|
|
118
|
+
this.devNode = options.devNode === true;
|
|
119
|
+
// a development node gets only what the caller passes: the gateway credentials of the environment never go to it
|
|
120
|
+
const env = (name) => (this.devNode ? "" : (process.env[name] || "").trim());
|
|
110
121
|
this.clientId = String(options.clientId != null ? options.clientId : env("CX3_CLIENT_ID")).trim();
|
|
111
122
|
this.#clientSecret = String(options.clientSecret != null ? options.clientSecret : env("CX3_CLIENT_SECRET")).trim();
|
|
112
123
|
this.scope = Array.isArray(options.scope) ? options.scope.join(" ") : String(options.scope || "").trim();
|
|
113
124
|
this.#apiKey = String(options.apiKey != null ? options.apiKey : env("CX3_API_KEY")).trim();
|
|
114
|
-
this.devNode = options.devNode === true;
|
|
115
125
|
this.oauth = !!(this.clientId || this.#clientSecret);
|
|
116
126
|
if (this.oauth && (!CLIENT_ID_RE.test(this.clientId) || !CLIENT_SECRET_RE.test(this.#clientSecret))) {
|
|
117
127
|
throw new Cx3Error("clientId must be cx3app_… and clientSecret cx3sec_… (CX3WebUI → Settings → API → Applications)", "INVALID_CLIENT_CREDENTIALS");
|
|
@@ -123,6 +133,24 @@ class Cx3Provider {
|
|
|
123
133
|
"CREDENTIALS_REQUIRED"
|
|
124
134
|
);
|
|
125
135
|
}
|
|
136
|
+
// credentials over plain http can be read on the way: only this machine, or the caller's own word
|
|
137
|
+
if ((this.oauth || hasKey) && options.allowInsecure !== true && isRemotePlainHttp(this.baseUrl)) {
|
|
138
|
+
throw new Cx3Error(
|
|
139
|
+
"Credentials are not sent over plain http: use an https URL (or allowInsecure: true on a network you trust)",
|
|
140
|
+
"INSECURE_BASE_URL"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
// The network this provider is for: with both given, signatures are made for this network only and
|
|
144
|
+
// an endpoint that names another one is refused (CHAIN_IDENTITY_MISMATCH).
|
|
145
|
+
this.pinnedIdentity = null;
|
|
146
|
+
if (options.chainId != null || options.genesisHash != null) {
|
|
147
|
+
const chainId = String(options.chainId == null ? "" : options.chainId).trim();
|
|
148
|
+
const genesisHash = normalizeGenesisHash(options.genesisHash);
|
|
149
|
+
if (!chainId || !genesisHash) {
|
|
150
|
+
throw new Cx3Error("chainId and genesisHash are given together (genesisHash: 64 hex)", "NO_CHAIN_IDENTITY");
|
|
151
|
+
}
|
|
152
|
+
this.pinnedIdentity = { chainId, genesisHash };
|
|
153
|
+
}
|
|
126
154
|
this.gateway = !this.devNode;
|
|
127
155
|
this.apiPrefix = options.apiPrefix != null ? options.apiPrefix : (this.gateway ? "" : "/api/v1");
|
|
128
156
|
this.extraHeaders = withoutCredentialHeaders(options.headers);
|
|
@@ -432,6 +460,14 @@ class Cx3Provider {
|
|
|
432
460
|
const chainId = typeof r.chainId === "string" ? r.chainId.trim() : "";
|
|
433
461
|
const genesisHash = normalizeGenesisHash(r.genesisHash);
|
|
434
462
|
if (!chainId || !genesisHash) throw new Cx3Error("network identity unavailable", "NO_CHAIN_IDENTITY");
|
|
463
|
+
const pin = this.pinnedIdentity;
|
|
464
|
+
if (pin && (pin.chainId !== chainId || pin.genesisHash !== genesisHash)) {
|
|
465
|
+
throw new Cx3Error(
|
|
466
|
+
"The endpoint is on another network than the one this provider was made for: nothing is signed",
|
|
467
|
+
"CHAIN_IDENTITY_MISMATCH",
|
|
468
|
+
{ expected: { ...pin }, got: { chainId, genesisHash } }
|
|
469
|
+
);
|
|
470
|
+
}
|
|
435
471
|
this._chainIdentity = { at: Date.now(), value: { chainId, genesisHash } };
|
|
436
472
|
return { chainId, genesisHash };
|
|
437
473
|
}
|
package/src/wallet.js
CHANGED
|
@@ -235,7 +235,7 @@ class Cx3Wallet {
|
|
|
235
235
|
if (opts.privateCheck !== false && typeof this.provider.checkPrivateTokenSend === "function") {
|
|
236
236
|
await this.provider.checkPrivateTokenSend(contractAddress, method, params, this.address);
|
|
237
237
|
}
|
|
238
|
-
const signed = await this.signSend(contractAddress, method, params);
|
|
238
|
+
const signed = await this.signSend(contractAddress, method, params, opts);
|
|
239
239
|
// everything the gateway needs to take the same signed call again: no key, safe to store
|
|
240
240
|
const signedRequest = {
|
|
241
241
|
contractAddress,
|
|
@@ -309,7 +309,7 @@ class Cx3Wallet {
|
|
|
309
309
|
* (Cx3HttpError code DUPLICATE_TOKEN_SYMBOL, status 409). A dropped deploy (202, `dropped`,
|
|
310
310
|
* `finalAfter`) is pending, not deployed: `result.wait()` follows it (withDeployWait).
|
|
311
311
|
*/
|
|
312
|
-
async deployContract(fields) {
|
|
312
|
+
async deployContract(fields, opts = {}) {
|
|
313
313
|
if (!this.provider) throw new Cx3Error("Wallet has no provider", "NO_PROVIDER");
|
|
314
314
|
fields = toJsonSafe(fields || {});
|
|
315
315
|
const ctor = fields.constructorArgs && typeof fields.constructorArgs === "object" && !Array.isArray(fields.constructorArgs) ? fields.constructorArgs : {};
|
|
@@ -329,7 +329,7 @@ class Cx3Wallet {
|
|
|
329
329
|
...fields,
|
|
330
330
|
owner: fields.owner || this.address,
|
|
331
331
|
contentHash
|
|
332
|
-
});
|
|
332
|
+
}, opts);
|
|
333
333
|
const body = {
|
|
334
334
|
...fields,
|
|
335
335
|
owner: signed.owner,
|
|
@@ -347,9 +347,9 @@ class Cx3Wallet {
|
|
|
347
347
|
return withDeployWait(result, this.provider);
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
async _ownerActionRequest(action, address, owner, method, path) {
|
|
350
|
+
async _ownerActionRequest(action, address, owner, method, path, opts = {}) {
|
|
351
351
|
if (!this.provider) throw new Cx3Error("Wallet has no provider", "NO_PROVIDER");
|
|
352
|
-
const signed = await this.signOwnerAction(action, address, owner);
|
|
352
|
+
const signed = await this.signOwnerAction(action, address, owner, opts);
|
|
353
353
|
return this.provider.request(path, {
|
|
354
354
|
method,
|
|
355
355
|
body: {
|
|
@@ -361,33 +361,36 @@ class Cx3Wallet {
|
|
|
361
361
|
});
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
-
async pauseContract(address, owner = null) {
|
|
364
|
+
async pauseContract(address, owner = null, opts = {}) {
|
|
365
365
|
return this._ownerActionRequest(
|
|
366
366
|
"pause",
|
|
367
367
|
address,
|
|
368
368
|
owner,
|
|
369
369
|
"PUT",
|
|
370
|
-
`/contracts/${encodeURIComponent(address)}/pause
|
|
370
|
+
`/contracts/${encodeURIComponent(address)}/pause`,
|
|
371
|
+
opts
|
|
371
372
|
);
|
|
372
373
|
}
|
|
373
374
|
|
|
374
|
-
async unpauseContract(address, owner = null) {
|
|
375
|
+
async unpauseContract(address, owner = null, opts = {}) {
|
|
375
376
|
return this._ownerActionRequest(
|
|
376
377
|
"unpause",
|
|
377
378
|
address,
|
|
378
379
|
owner,
|
|
379
380
|
"PUT",
|
|
380
|
-
`/contracts/${encodeURIComponent(address)}/unpause
|
|
381
|
+
`/contracts/${encodeURIComponent(address)}/unpause`,
|
|
382
|
+
opts
|
|
381
383
|
);
|
|
382
384
|
}
|
|
383
385
|
|
|
384
|
-
async deleteContract(address, owner = null) {
|
|
386
|
+
async deleteContract(address, owner = null, opts = {}) {
|
|
385
387
|
return this._ownerActionRequest(
|
|
386
388
|
"delete",
|
|
387
389
|
address,
|
|
388
390
|
owner,
|
|
389
391
|
"DELETE",
|
|
390
|
-
`/contracts/${encodeURIComponent(address)}
|
|
392
|
+
`/contracts/${encodeURIComponent(address)}`,
|
|
393
|
+
opts
|
|
391
394
|
);
|
|
392
395
|
}
|
|
393
396
|
|
|
@@ -399,9 +402,18 @@ class Cx3Wallet {
|
|
|
399
402
|
*/
|
|
400
403
|
async transferNative(to, amount, opts = {}) {
|
|
401
404
|
if (!this.provider) throw new Cx3Error("Wallet has no provider", "NO_PROVIDER");
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
+
if (to == null || String(to).trim() === "") throw new Cx3Error("recipient required", "NO_RECIPIENT");
|
|
406
|
+
// a full address only (0x / cx3: + 40 hex): anything else is not signed
|
|
407
|
+
const toAddress = normalizeAddress(to);
|
|
408
|
+
if (!/^0x[0-9a-f]{40}$/.test(toAddress)) {
|
|
409
|
+
throw new Cx3Error("The recipient is not a CX3 address (0x + 40 hex)", "INVALID_RECIPIENT");
|
|
410
|
+
}
|
|
411
|
+
// a text amount is plain decimal digits: "0x10" or "1e3" would be read as another number
|
|
412
|
+
if (typeof amount === "string" && !/^\d{1,15}(\.\d+)?$/.test(amount.trim())) {
|
|
413
|
+
throw new Cx3Error("Invalid amount", "INVALID_AMOUNT");
|
|
414
|
+
}
|
|
415
|
+
if (typeof amount !== "number" && typeof amount !== "string") throw new Cx3Error("Invalid amount", "INVALID_AMOUNT");
|
|
416
|
+
const amt = typeof amount === "number" ? amount : Number(amount.trim());
|
|
405
417
|
if (!Number.isFinite(amt) || amt <= 0) {
|
|
406
418
|
throw new Cx3Error("Invalid amount", "INVALID_AMOUNT");
|
|
407
419
|
}
|