@carbonx3/cx3.js 0.1.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 +138 -0
- package/index.d.ts +157 -0
- package/index.js +67 -0
- package/package.json +45 -0
- package/src/abi.js +117 -0
- package/src/contract.js +113 -0
- package/src/crypto.js +90 -0
- package/src/digest.js +80 -0
- package/src/errors.js +29 -0
- package/src/factory.js +130 -0
- package/src/provider.js +361 -0
- package/src/transaction.js +53 -0
- package/src/utils.js +70 -0
- package/src/wallet.js +247 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# @carbonx3/cx3.js — CarbonX3 Green Chain SDK
|
|
2
|
+
|
|
3
|
+
Official JavaScript client (**CX3js**) for **CarbonX3 Green Chain**.
|
|
4
|
+
|
|
5
|
+
Talks to the node over **REST + JSON** (`/api/v1/...`) with named method params.
|
|
6
|
+
|
|
7
|
+
| `@carbonx3/cx3.js` | Role | Green Chain API |
|
|
8
|
+
|----------|------|-----------------|
|
|
9
|
+
| `Cx3Provider` | HTTP client | `CX3_NODE_URL` + `/api/v1` |
|
|
10
|
+
| `Cx3Wallet` | secp256k1 signing | Deploy / send digests |
|
|
11
|
+
| `Contract` / `provider.getContract` | ABI-bound calls | `/contract/call` + `/contract/send` |
|
|
12
|
+
| `Cx3TokenFactory` | Standard deploys | `POST /deployContract` (CX30–CX3192, CX337–CX341) |
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# when published
|
|
18
|
+
npm install @carbonx3/cx3.js
|
|
19
|
+
|
|
20
|
+
# from monorepo root
|
|
21
|
+
npm install ./CX3Web3JS
|
|
22
|
+
|
|
23
|
+
# or inside another package
|
|
24
|
+
npm install file:../CX3Web3JS
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Node.js **18+** (native `fetch`).
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const {
|
|
33
|
+
Cx3Provider,
|
|
34
|
+
Cx3Wallet,
|
|
35
|
+
Cx3TokenFactory,
|
|
36
|
+
readCallValue
|
|
37
|
+
} = require("@carbonx3/cx3.js");
|
|
38
|
+
|
|
39
|
+
const provider = new Cx3Provider(process.env.CX3_NODE_URL, {
|
|
40
|
+
auth: {
|
|
41
|
+
user: process.env.CX3_INTERNAL_AUTH_USER,
|
|
42
|
+
pass: process.env.CX3_INTERNAL_AUTH_PASS
|
|
43
|
+
}
|
|
44
|
+
// or: apiKey: process.env.CX3_API_KEY // empty = public (any wallet); cx3wk_ = that wallet only
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const wallet = new Cx3Wallet(process.env.CX3_PRIVATE_KEY, provider);
|
|
48
|
+
await wallet.bindApiKey();
|
|
49
|
+
|
|
50
|
+
// Read
|
|
51
|
+
const token = await provider.getContract(process.env.CONTRACT_ADDRESS, null, wallet);
|
|
52
|
+
const balance = await token.balanceOf(wallet.address);
|
|
53
|
+
console.log("balance", balance);
|
|
54
|
+
|
|
55
|
+
const cx3Balance = await provider.getBalance(wallet.address);
|
|
56
|
+
|
|
57
|
+
// Write
|
|
58
|
+
const tx = await token.transfer("0xRecipient…", "1000000000000000000");
|
|
59
|
+
await tx.wait();
|
|
60
|
+
|
|
61
|
+
await wallet.pauseContract(process.env.CONTRACT_ADDRESS);
|
|
62
|
+
await wallet.transferNative("0xRecipient…", 1);
|
|
63
|
+
|
|
64
|
+
// Deploy CX30
|
|
65
|
+
const factory = new Cx3TokenFactory(wallet);
|
|
66
|
+
const deployed = await factory.deployCX30({
|
|
67
|
+
name: "My Token",
|
|
68
|
+
symbol: "MTK",
|
|
69
|
+
initialSupply: "0",
|
|
70
|
+
target: "CX3WASM"
|
|
71
|
+
});
|
|
72
|
+
console.log(deployed.contractAddress);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Signing (important)
|
|
76
|
+
|
|
77
|
+
Digests are **UTF-8 strings**, then `sha256` + **secp256k1** (same as CX3Wallet):
|
|
78
|
+
|
|
79
|
+
- Deploy: `deploy|{owner}|{standard}|{name}|{symbol}|{decimals}|{initialSupply}|{timestamp}`
|
|
80
|
+
- Send: `{contractAddress}|{method}|{JSON.stringify(params)}|{fromAddress}`
|
|
81
|
+
- Owner: `{action}|{address}|{owner}|{timestamp}`
|
|
82
|
+
- Native CX3: canonical JSON payload (`buildTransferPayload`) then `wallet.transferNative(to, amount)`
|
|
83
|
+
|
|
84
|
+
Address = `0x` + last 20 bytes of `sha256(uncompressedPublicKey)`.
|
|
85
|
+
|
|
86
|
+
## ABI-bound methods
|
|
87
|
+
|
|
88
|
+
Supports:
|
|
89
|
+
|
|
90
|
+
1. Standard interface JSON (`methods.balanceOf.type = "view"`)
|
|
91
|
+
2. Compiler flat ABI (`[{ type: "function", name, inputs, stateMutability }]`)
|
|
92
|
+
|
|
93
|
+
Positional args map to ABI input names:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
await token.balanceOf(owner); // → { address: owner } or { account: owner }
|
|
97
|
+
await token.transfer(to, amount); // → { to, amount }
|
|
98
|
+
await token.call("balanceOf", { account: owner });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Environment
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
CX3_NODE_URL=https://your-node
|
|
105
|
+
CX3_INTERNAL_AUTH_USER=cx3-internal
|
|
106
|
+
CX3_INTERNAL_AUTH_PASS=…
|
|
107
|
+
CX3_API_KEY=cx3wk_… # optional; empty = public (any wallet); set = that wallet only (X-Api-Key)
|
|
108
|
+
CX3_PRIVATE_KEY=… # 64-hex or 0x-prefixed
|
|
109
|
+
CX3_CHAIN_ID=cx3-dev # native transfer payload (default)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Package layout
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
CX3Web3JS/
|
|
116
|
+
index.js # public API
|
|
117
|
+
index.d.ts
|
|
118
|
+
src/
|
|
119
|
+
provider.js
|
|
120
|
+
wallet.js
|
|
121
|
+
contract.js
|
|
122
|
+
factory.js
|
|
123
|
+
crypto.js
|
|
124
|
+
digest.js
|
|
125
|
+
…
|
|
126
|
+
test/
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
UNLICENSED — Mersolution Technology Ltd.
|
|
132
|
+
|
|
133
|
+
## Docs
|
|
134
|
+
|
|
135
|
+
CX3Script documentation site (sidebar **cx3.js SDK**):
|
|
136
|
+
|
|
137
|
+
- Monorepo: `CX3VM-WebSites/cx3docs/docs/cx3js-sdk.md`
|
|
138
|
+
- Local: `/CX3VM-WebSites/docs?doc=cx3js-sdk`
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export const VERSION: string;
|
|
2
|
+
|
|
3
|
+
export class Cx3Error extends Error {
|
|
4
|
+
code: string;
|
|
5
|
+
details: unknown;
|
|
6
|
+
constructor(message: string, code?: string, details?: unknown);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Cx3HttpError extends Cx3Error {
|
|
10
|
+
status: number;
|
|
11
|
+
body: unknown;
|
|
12
|
+
constructor(message: string, status: number, body?: unknown);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class Cx3TxError extends Cx3Error {
|
|
16
|
+
receipt: unknown;
|
|
17
|
+
txHash?: string;
|
|
18
|
+
constructor(message: string, code?: string, receipt?: unknown);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Cx3Auth {
|
|
22
|
+
user: string;
|
|
23
|
+
pass: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Cx3ProviderOptions {
|
|
27
|
+
apiPrefix?: string;
|
|
28
|
+
auth?: Cx3Auth;
|
|
29
|
+
apiKey?: string;
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
fetch?: typeof fetch;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface Cx3PageQuery {
|
|
36
|
+
type?: string;
|
|
37
|
+
limit?: number;
|
|
38
|
+
offset?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class Cx3Provider {
|
|
42
|
+
baseUrl: string;
|
|
43
|
+
apiPrefix: string;
|
|
44
|
+
apiKeyMode: "public" | "private" | "invalid";
|
|
45
|
+
constructor(baseUrl: string, options?: Cx3ProviderOptions);
|
|
46
|
+
request(path: string, opts?: { method?: string; body?: unknown; query?: Record<string, string | number> }): Promise<any>;
|
|
47
|
+
call(contractAddress: string, method: string, params?: object, sender?: string): Promise<any>;
|
|
48
|
+
send(contractAddress: string, method: string, params?: object, extras?: object): Promise<any>;
|
|
49
|
+
getAbi(address: string): Promise<any>;
|
|
50
|
+
getContractInfo(address: string): Promise<any>;
|
|
51
|
+
getContractState(address: string): Promise<any>;
|
|
52
|
+
getContractSource(address: string): Promise<any>;
|
|
53
|
+
getContractBytecode(address: string): Promise<any>;
|
|
54
|
+
getEvents(address: string, typeOrOpts?: string | Cx3PageQuery, opts?: Cx3PageQuery): Promise<any>;
|
|
55
|
+
getHolders(address: string, opts?: Cx3PageQuery): Promise<any>;
|
|
56
|
+
getTransfers(address: string, limitOrOpts?: number | Cx3PageQuery, opts?: Cx3PageQuery): Promise<any>;
|
|
57
|
+
getTransferByHash(address: string, hash: string): Promise<any>;
|
|
58
|
+
getAddressTransactions(address: string, limit?: number): Promise<any>;
|
|
59
|
+
getBalanceAndNonce(address: string): Promise<any>;
|
|
60
|
+
getBalance(address: string): Promise<any>;
|
|
61
|
+
getNonce(address: string): Promise<number>;
|
|
62
|
+
getPendingNonce(address: string): Promise<any>;
|
|
63
|
+
bindApiKey(address: string): Promise<any>;
|
|
64
|
+
listContracts(opts?: { limit?: number; offset?: number }): Promise<any>;
|
|
65
|
+
getTxStatus(txHash: string): Promise<any>;
|
|
66
|
+
waitForTransaction(txHash: string, opts?: object): Promise<any>;
|
|
67
|
+
getContract(address: string, abi?: unknown, signer?: Cx3Wallet | null): Promise<Contract>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class Cx3Wallet {
|
|
71
|
+
address: string;
|
|
72
|
+
publicKey: string;
|
|
73
|
+
provider: Cx3Provider | null;
|
|
74
|
+
constructor(privateKey: string | Uint8Array | Buffer, provider?: Cx3Provider | null);
|
|
75
|
+
static createRandom(provider?: Cx3Provider | null): Cx3Wallet;
|
|
76
|
+
connect(provider: Cx3Provider): Cx3Wallet;
|
|
77
|
+
getAddress(): string;
|
|
78
|
+
signMessage(message: string | Uint8Array): Promise<{
|
|
79
|
+
signature: string;
|
|
80
|
+
r: string;
|
|
81
|
+
s: null;
|
|
82
|
+
v: number;
|
|
83
|
+
publicKey: string;
|
|
84
|
+
senderPublicKey: string;
|
|
85
|
+
publicKeyUncompressed: string;
|
|
86
|
+
derivedAddress: string;
|
|
87
|
+
hash: string;
|
|
88
|
+
}>;
|
|
89
|
+
signDeploy(fields: object): Promise<any>;
|
|
90
|
+
signSend(contractAddress: string, method: string, params?: object): Promise<any>;
|
|
91
|
+
signOwnerAction(action: string, address: string, owner?: string | null): Promise<any>;
|
|
92
|
+
sendTransaction(contractAddress: string, method: string, params?: object, opts?: object): Promise<TransactionResponse>;
|
|
93
|
+
deployContract(fields: object): Promise<any>;
|
|
94
|
+
pauseContract(address: string, owner?: string | null): Promise<any>;
|
|
95
|
+
unpauseContract(address: string, owner?: string | null): Promise<any>;
|
|
96
|
+
deleteContract(address: string, owner?: string | null): Promise<any>;
|
|
97
|
+
transferNative(to: string, amount: number | string, opts?: object): Promise<TransactionResponse>;
|
|
98
|
+
bindApiKey(address?: string): Promise<any>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class Contract {
|
|
102
|
+
address: string;
|
|
103
|
+
target: string | null;
|
|
104
|
+
interface: Record<string, { name: string; kind: "view" | "tx"; inputs: any[] }>;
|
|
105
|
+
constructor(address: string, abi: unknown, runner: Cx3Provider | Cx3Wallet);
|
|
106
|
+
connect(runner: Cx3Provider | Cx3Wallet): Contract;
|
|
107
|
+
call(method: string, params?: object): Promise<any>;
|
|
108
|
+
send(method: string, params?: object, opts?: object): Promise<TransactionResponse>;
|
|
109
|
+
getEvents(type?: string): Promise<any>;
|
|
110
|
+
getInfo(): Promise<any>;
|
|
111
|
+
getTransfers(opts?: Cx3PageQuery): Promise<any>;
|
|
112
|
+
getHolders(opts?: Cx3PageQuery): Promise<any>;
|
|
113
|
+
getState(): Promise<any>;
|
|
114
|
+
[method: string]: any;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class Cx3TokenFactory {
|
|
118
|
+
constructor(wallet: Cx3Wallet);
|
|
119
|
+
deployCX30(fields?: object): Promise<any>;
|
|
120
|
+
deployCX31(fields?: object): Promise<any>;
|
|
121
|
+
deployCX300(fields?: object): Promise<any>;
|
|
122
|
+
deployCX315(fields?: object): Promise<any>;
|
|
123
|
+
deployCX3192(fields?: object): Promise<any>;
|
|
124
|
+
deployCX337(fields?: object): Promise<any>;
|
|
125
|
+
deployCX338(fields?: object): Promise<any>;
|
|
126
|
+
deployCX339(fields?: object): Promise<any>;
|
|
127
|
+
deployCX340(fields?: object): Promise<any>;
|
|
128
|
+
deployCX341(fields?: object): Promise<any>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class TransactionResponse {
|
|
132
|
+
raw: any;
|
|
133
|
+
success: boolean;
|
|
134
|
+
txHash: string | null;
|
|
135
|
+
hash: string | null;
|
|
136
|
+
blockHeight?: number;
|
|
137
|
+
contractAddress: string | null;
|
|
138
|
+
async: boolean;
|
|
139
|
+
status: string;
|
|
140
|
+
wait(opts?: object): Promise<any>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getDefaultProvider(options?: Cx3ProviderOptions & { url?: string }): Cx3Provider;
|
|
144
|
+
export function readCallValue(response: any): unknown;
|
|
145
|
+
export function normalizeAddress(addr: string): string;
|
|
146
|
+
export function walletApiKeyMode(key?: string | null): "public" | "private" | "invalid";
|
|
147
|
+
export function ensure0x(hex: string): string;
|
|
148
|
+
export function strip0x(hex: string): string;
|
|
149
|
+
export function buildDeployDigest(fields: object): string;
|
|
150
|
+
export function buildSendDigest(contractAddress: string, method: string, params: object, fromAddress: string, timestamp?: number | string): string;
|
|
151
|
+
export function buildOwnerActionDigest(action: string, address: string, owner: string, timestamp: number): string;
|
|
152
|
+
export function buildTransferPayload(fields: object): string;
|
|
153
|
+
export function signMessage(message: string | Uint8Array, privateKey: string | Uint8Array | Buffer): Promise<any>;
|
|
154
|
+
export function privateKeyToAddress(privateKey: string | Uint8Array | Buffer): string;
|
|
155
|
+
export function createPrivateKey(): string;
|
|
156
|
+
export function normalizeAbi(abi: unknown): Record<string, any>;
|
|
157
|
+
export function argsToParams(inputs: any[], args: any[]): object;
|
package/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { Cx3Provider } = require("./src/provider");
|
|
4
|
+
const { Cx3Wallet } = require("./src/wallet");
|
|
5
|
+
const { Contract } = require("./src/contract");
|
|
6
|
+
const { Cx3TokenFactory } = require("./src/factory");
|
|
7
|
+
const { TransactionResponse } = require("./src/transaction");
|
|
8
|
+
const { Cx3Error, Cx3HttpError, Cx3TxError } = require("./src/errors");
|
|
9
|
+
const {
|
|
10
|
+
readCallValue,
|
|
11
|
+
normalizeAddress,
|
|
12
|
+
ensure0x,
|
|
13
|
+
strip0x,
|
|
14
|
+
walletApiKeyMode
|
|
15
|
+
} = require("./src/utils");
|
|
16
|
+
const {
|
|
17
|
+
buildDeployDigest,
|
|
18
|
+
buildSendDigest,
|
|
19
|
+
buildOwnerActionDigest,
|
|
20
|
+
buildTransferPayload
|
|
21
|
+
} = require("./src/digest");
|
|
22
|
+
const {
|
|
23
|
+
signMessage,
|
|
24
|
+
privateKeyToAddress,
|
|
25
|
+
createPrivateKey
|
|
26
|
+
} = require("./src/crypto");
|
|
27
|
+
const { normalizeAbi, argsToParams } = require("./src/abi");
|
|
28
|
+
|
|
29
|
+
const VERSION = "0.1.0";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convenience: provider from env `CX3_NODE_URL` (+ optional Basic auth env).
|
|
33
|
+
*/
|
|
34
|
+
function getDefaultProvider(options = {}) {
|
|
35
|
+
const url = options.url || process.env.CX3_NODE_URL;
|
|
36
|
+
return new Cx3Provider(url, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
VERSION,
|
|
41
|
+
// Core
|
|
42
|
+
Cx3Provider,
|
|
43
|
+
Cx3Wallet,
|
|
44
|
+
Contract,
|
|
45
|
+
Cx3TokenFactory,
|
|
46
|
+
TransactionResponse,
|
|
47
|
+
// Errors
|
|
48
|
+
Cx3Error,
|
|
49
|
+
Cx3HttpError,
|
|
50
|
+
Cx3TxError,
|
|
51
|
+
// Helpers
|
|
52
|
+
getDefaultProvider,
|
|
53
|
+
readCallValue,
|
|
54
|
+
normalizeAddress,
|
|
55
|
+
ensure0x,
|
|
56
|
+
strip0x,
|
|
57
|
+
walletApiKeyMode,
|
|
58
|
+
buildDeployDigest,
|
|
59
|
+
buildSendDigest,
|
|
60
|
+
buildOwnerActionDigest,
|
|
61
|
+
buildTransferPayload,
|
|
62
|
+
signMessage,
|
|
63
|
+
privateKeyToAddress,
|
|
64
|
+
createPrivateKey,
|
|
65
|
+
normalizeAbi,
|
|
66
|
+
argsToParams
|
|
67
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@carbonx3/cx3.js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CarbonX3 Green Chain JavaScript SDK (CX3js) - Provider / Wallet / Contract over REST + JSON",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"types": "index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"index.d.ts",
|
|
13
|
+
"src/",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node test/unit.test.js",
|
|
18
|
+
"test:live": "node test/live.smoke.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"carbonx3",
|
|
22
|
+
"cx3",
|
|
23
|
+
"green-chain",
|
|
24
|
+
"cx3js",
|
|
25
|
+
"cx3wasm",
|
|
26
|
+
"sdk"
|
|
27
|
+
],
|
|
28
|
+
"author": "Mersolution Technology Ltd.",
|
|
29
|
+
"license": "UNLICENSED",
|
|
30
|
+
"type": "commonjs",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@noble/secp256k1": "^1.7.1"
|
|
36
|
+
},
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./index.d.ts",
|
|
40
|
+
"require": "./index.js",
|
|
41
|
+
"import": "./index.js",
|
|
42
|
+
"default": "./index.js"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/abi.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { Cx3Error } = require("./errors");
|
|
4
|
+
const { isPlainObject } = require("./utils");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Normalize CX3 ABI variants into a map:
|
|
8
|
+
* { [methodName]: { name, kind: "view"|"tx", inputs: [{name,type}] } }
|
|
9
|
+
*
|
|
10
|
+
* Supported shapes:
|
|
11
|
+
* 1) Interface JSON: `{ methods: { balanceOf: { type:"view", inputs:[...] } } }`
|
|
12
|
+
* 2) Compiler flat ABI: `[{ type:"function", name, inputs, stateMutability }]`
|
|
13
|
+
* 3) WASM object: `{ functions: [...] }`
|
|
14
|
+
*/
|
|
15
|
+
function normalizeAbi(abi) {
|
|
16
|
+
const map = Object.create(null);
|
|
17
|
+
if (!abi) return map;
|
|
18
|
+
|
|
19
|
+
if (Array.isArray(abi)) {
|
|
20
|
+
for (const entry of abi) {
|
|
21
|
+
if (!entry || !entry.name) continue;
|
|
22
|
+
if (entry.type && entry.type !== "function" && entry.type !== "view" && entry.type !== "tx") {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
map[entry.name] = {
|
|
26
|
+
name: entry.name,
|
|
27
|
+
kind: inferKind(entry),
|
|
28
|
+
inputs: entry.inputs || entry.params || []
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return map;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (abi.methods && isPlainObject(abi.methods)) {
|
|
35
|
+
for (const [name, def] of Object.entries(abi.methods)) {
|
|
36
|
+
map[name] = {
|
|
37
|
+
name,
|
|
38
|
+
kind: inferKind(def),
|
|
39
|
+
inputs: (def && def.inputs) || []
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return map;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(abi.functions)) {
|
|
46
|
+
return normalizeAbi(abi.functions);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Bare object of method defs
|
|
50
|
+
if (isPlainObject(abi)) {
|
|
51
|
+
for (const [name, def] of Object.entries(abi)) {
|
|
52
|
+
if (!def || typeof def !== "object") continue;
|
|
53
|
+
if (name === "name" || name === "version" || name === "description") continue;
|
|
54
|
+
map[name] = {
|
|
55
|
+
name,
|
|
56
|
+
kind: inferKind(def),
|
|
57
|
+
inputs: def.inputs || def.params || []
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return map;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function inferKind(def) {
|
|
65
|
+
if (!def) return "tx";
|
|
66
|
+
if (isViewMutability(def)) return "view";
|
|
67
|
+
const t = String(def.type || def.kind || "").toLowerCase();
|
|
68
|
+
if (t === "view" || t === "pure") return "view";
|
|
69
|
+
if (t === "tx" || t === "nonpayable" || t === "payable") return "tx";
|
|
70
|
+
if (t === "function") return isViewMutability(def) ? "view" : "tx";
|
|
71
|
+
// WASM compiler ABI shape (`{ functions: [{ name, params, view, ... }] }`,
|
|
72
|
+
// see CX3VM WITGenerator.extractFunctions) has no `type`/`stateMutability`
|
|
73
|
+
// string at all — read-only methods are flagged with a plain boolean
|
|
74
|
+
// `view` field instead. Without this check every WASM function (including
|
|
75
|
+
// balanceOf/allowance/etc.) falls through to "tx", so Contract._invoke()
|
|
76
|
+
// routes reads through wallet.sendTransaction() instead of provider.call().
|
|
77
|
+
if (typeof def.view === "boolean") return def.view ? "view" : "tx";
|
|
78
|
+
return "tx";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isViewMutability(def) {
|
|
82
|
+
const m = String(def.stateMutability || def.mutability || "").toLowerCase();
|
|
83
|
+
return m === "view" || m === "pure" || m === "readonly";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Map positional args → named params object using ABI inputs.
|
|
88
|
+
* Also accepts a single plain object as the only argument.
|
|
89
|
+
*/
|
|
90
|
+
function argsToParams(inputs, args) {
|
|
91
|
+
const list = Array.isArray(inputs) ? inputs : [];
|
|
92
|
+
if (args.length === 1 && isPlainObject(args[0])) {
|
|
93
|
+
return { ...args[0] };
|
|
94
|
+
}
|
|
95
|
+
if (list.length === 0) {
|
|
96
|
+
if (args.length === 0) return {};
|
|
97
|
+
throw new Cx3Error(
|
|
98
|
+
`Method expects named params object or ABI inputs; got ${args.length} positional arg(s)`,
|
|
99
|
+
"BAD_ARGS"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (args.length > list.length) {
|
|
103
|
+
throw new Cx3Error(
|
|
104
|
+
`Too many arguments: expected ${list.length}, got ${args.length}`,
|
|
105
|
+
"BAD_ARGS"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const params = {};
|
|
109
|
+
for (let i = 0; i < args.length; i++) {
|
|
110
|
+
const input = list[i];
|
|
111
|
+
const key = input.name || input.key || `arg${i}`;
|
|
112
|
+
params[key] = args[i];
|
|
113
|
+
}
|
|
114
|
+
return params;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { normalizeAbi, argsToParams, inferKind };
|
package/src/contract.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { normalizeAbi, argsToParams } = require("./abi");
|
|
4
|
+
const { Cx3Error } = require("./errors");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Contract handle with ABI-bound methods.
|
|
8
|
+
*
|
|
9
|
+
* const token = new Contract(addr, abi, wallet);
|
|
10
|
+
* await token.balanceOf(owner); // view → /contract/call
|
|
11
|
+
* const tx = await token.transfer(to, amount); // tx → signed /contract/send
|
|
12
|
+
* await tx.wait();
|
|
13
|
+
*/
|
|
14
|
+
class Contract {
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} address
|
|
17
|
+
* @param {object|array} abi
|
|
18
|
+
* @param {import('./provider').Cx3Provider|import('./wallet').Cx3Wallet} runner provider or wallet
|
|
19
|
+
*/
|
|
20
|
+
constructor(address, abi, runner) {
|
|
21
|
+
if (!address) throw new Cx3Error("contract address required", "NO_ADDRESS");
|
|
22
|
+
if (!runner) throw new Cx3Error("provider or wallet required", "NO_RUNNER");
|
|
23
|
+
|
|
24
|
+
this.address = address;
|
|
25
|
+
this.target = null;
|
|
26
|
+
this.interface = normalizeAbi(abi);
|
|
27
|
+
this._runner = runner;
|
|
28
|
+
|
|
29
|
+
const self = this;
|
|
30
|
+
for (const name of Object.keys(this.interface)) {
|
|
31
|
+
if (name in this) continue;
|
|
32
|
+
this[name] = async function (...args) {
|
|
33
|
+
return self._invoke(name, args);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get provider() {
|
|
39
|
+
return this._runner.provider || this._runner;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get signer() {
|
|
43
|
+
return this._runner.signMessage ? this._runner : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
connect(runner) {
|
|
47
|
+
const c = new Contract(this.address, { methods: rebuildMethods(this.interface) }, runner);
|
|
48
|
+
c.target = this.target;
|
|
49
|
+
return c;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async _invoke(methodName, args) {
|
|
53
|
+
const def = this.interface[methodName];
|
|
54
|
+
if (!def) throw new Cx3Error(`Unknown method: ${methodName}`, "UNKNOWN_METHOD");
|
|
55
|
+
const params = argsToParams(def.inputs, args);
|
|
56
|
+
|
|
57
|
+
if (def.kind === "view") {
|
|
58
|
+
return this.call(methodName, params);
|
|
59
|
+
}
|
|
60
|
+
return this.send(methodName, params);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async call(method, params = {}) {
|
|
64
|
+
const provider = this.provider;
|
|
65
|
+
const sender = this.signer ? this.signer.address : "";
|
|
66
|
+
const res = await provider.call(this.address, method, params, sender);
|
|
67
|
+
return res.value !== undefined ? res.value : res;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async send(method, params = {}, opts = {}) {
|
|
71
|
+
const signer = this.signer;
|
|
72
|
+
if (!signer) {
|
|
73
|
+
throw new Cx3Error(
|
|
74
|
+
`Method ${method} requires a Cx3Wallet signer (pass wallet as runner)`,
|
|
75
|
+
"NO_SIGNER"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return signer.sendTransaction(this.address, method, params, opts);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async getEvents(type = "") {
|
|
82
|
+
return this.provider.getEvents(this.address, type);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getInfo() {
|
|
86
|
+
return this.provider.getContractInfo(this.address);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getTransfers(opts) {
|
|
90
|
+
return this.provider.getTransfers(this.address, opts);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async getHolders(opts) {
|
|
94
|
+
return this.provider.getHolders(this.address, opts);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getState() {
|
|
98
|
+
return this.provider.getContractState(this.address);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function rebuildMethods(iface) {
|
|
103
|
+
const methods = {};
|
|
104
|
+
for (const [name, def] of Object.entries(iface)) {
|
|
105
|
+
methods[name] = {
|
|
106
|
+
type: def.kind,
|
|
107
|
+
inputs: def.inputs
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return methods;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { Contract };
|