quantumcoin 8.0.1 → 8.0.2
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-SDK.md +75 -6
- package/README.md +42 -0
- package/package.json +1 -1
- package/src/abi/fragments.d.ts +18 -0
- package/src/abi/fragments.js +46 -3
- package/src/abi/interface.d.ts +74 -12
- package/src/abi/interface.js +252 -34
- package/src/abi/js-abi-coder.d.ts +2 -0
- package/src/abi/js-abi-coder.js +2 -0
package/README-SDK.md
CHANGED
|
@@ -610,24 +610,91 @@ ABI encoding/decoding compatibility layer.
|
|
|
610
610
|
|
|
611
611
|
- `new Interface(abi: any[] | Interface | null)`
|
|
612
612
|
|
|
613
|
-
**
|
|
613
|
+
**Fragment lookup** — each accepts a bare name, a canonical signature
|
|
614
|
+
(`name(type,...)`), or a hex identifier, mirroring ethers.js v6:
|
|
615
|
+
- `getFunction(nameOrSignatureOrSelector: string): FunctionFragment`
|
|
616
|
+
- Resolves by name, `transfer(address,uint256)`, or 4-byte selector `0xa9059cbb`.
|
|
617
|
+
- Throws `INVALID_ARGUMENT` when unresolved or when a bare name is ambiguous
|
|
618
|
+
across overloads (pass the full signature instead).
|
|
619
|
+
- `getEvent(nameOrSignatureOrTopic: string): EventFragment`
|
|
620
|
+
- Resolves by name, signature, or topic0 (`0x` + 64 hex).
|
|
621
|
+
- `getError(nameOrSignatureOrSelector: string): ErrorFragment`
|
|
622
|
+
- Resolves by name, signature, or 4-byte selector.
|
|
623
|
+
- `getConstructor(): ConstructorFragment | null`
|
|
624
|
+
- `getSighash(fragmentOrName): string` — 4-byte function selector.
|
|
614
625
|
- `formatJson(): string`
|
|
615
626
|
- `format(format?: string | null): string`
|
|
616
|
-
- `getFunction(name: string): FunctionFragment`
|
|
617
|
-
- `getEvent(name: string): EventFragment`
|
|
618
|
-
- `getError(name: string): ErrorFragment`
|
|
619
|
-
- `getConstructor(): ConstructorFragment | null`
|
|
620
627
|
|
|
621
|
-
|
|
628
|
+
`FunctionFragment` / `ErrorFragment` expose a `selector` getter and
|
|
629
|
+
`EventFragment` a `topicHash` getter (both require `Initialize()`).
|
|
630
|
+
|
|
631
|
+
**Encoding / decoding**
|
|
622
632
|
- `encodeFunctionData(functionFragmentOrName, values?: any[] | null): string`
|
|
633
|
+
- `decodeFunctionData(functionFragmentOrName, data: string): Result` — decode a
|
|
634
|
+
call's arguments (selector + args). The data's selector must match the resolved
|
|
635
|
+
function, otherwise it throws `INVALID_ARGUMENT`. `encodeFunctionData(fragment,
|
|
636
|
+
decodeFunctionData(fragment, data))` reproduces `data` byte-for-byte.
|
|
623
637
|
- `decodeFunctionResult(functionFragmentOrName, data: string): any`
|
|
638
|
+
- `encodeDeploy(values?: any[]): string` — ABI-encode constructor arguments (no
|
|
639
|
+
selector), or `"0x"` when the constructor takes no arguments. Append to the
|
|
640
|
+
creation bytecode for a deployment.
|
|
624
641
|
- `encodeEventLog(eventFragmentOrName, values?: any[] | null): { topics: string[], data: string }`
|
|
625
642
|
- `decodeEventLog(eventFragmentOrName, topics: string[], data: string): any`
|
|
626
643
|
|
|
627
644
|
**Parsing**
|
|
645
|
+
- `parseTransaction(tx: { data: string, value?: BigNumberish }): { fragment, name, signature, selector, args: Result, value: bigint }`
|
|
646
|
+
- Resolves the function by the 4-byte selector in `tx.data`, decodes the
|
|
647
|
+
arguments from the real bytes, and reports `value` as a `bigint`.
|
|
648
|
+
- `parseError(data: string): { fragment, name, signature, selector, args: Result }`
|
|
649
|
+
- Resolves a custom error by its 4-byte selector and decodes its arguments.
|
|
628
650
|
- `parseLog(log: { topics: string[], data: string }): { fragment, name, signature, topic, args }`
|
|
629
651
|
- Uses signature topic matching and `decodeEventLog(...)`
|
|
630
652
|
|
|
653
|
+
> **32-byte addresses:** QuantumCoin addresses are 32 bytes and occupy a full ABI
|
|
654
|
+
> word, whereas Ethereum left-pads a 20-byte address. Canonical signatures are
|
|
655
|
+
> identical strings, so keccak-derived selectors and event topics match Ethereum
|
|
656
|
+
> exactly (e.g. `transfer(address,uint256)` → `0xa9059cbb`); only the contents of
|
|
657
|
+
> an address-bearing calldata word differ.
|
|
658
|
+
|
|
659
|
+
**Example (JS):**
|
|
660
|
+
|
|
661
|
+
```js
|
|
662
|
+
const { Initialize } = require("quantumcoin/config");
|
|
663
|
+
const { Interface } = require("quantumcoin");
|
|
664
|
+
|
|
665
|
+
await Initialize(null);
|
|
666
|
+
const iface = new Interface([
|
|
667
|
+
{ type: "function", name: "transfer",
|
|
668
|
+
inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }],
|
|
669
|
+
outputs: [{ type: "bool" }] },
|
|
670
|
+
]);
|
|
671
|
+
|
|
672
|
+
const to = "0x" + "ab".repeat(32); // 32-byte address
|
|
673
|
+
const data = iface.encodeFunctionData("transfer", [to, 1000n]);
|
|
674
|
+
|
|
675
|
+
const tx = iface.parseTransaction({ data, value: "0x0" });
|
|
676
|
+
console.log(tx.name, tx.signature, tx.selector); // transfer transfer(address,uint256) 0xa9059cbb
|
|
677
|
+
console.log(tx.args.to, tx.args.amount); // <to> 1000n
|
|
678
|
+
|
|
679
|
+
// "What you see is what you sign": re-encode the decoded args and compare.
|
|
680
|
+
const ok = iface.encodeFunctionData(tx.fragment, Array.from(tx.args)) === data; // true
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
**Example (TS):**
|
|
684
|
+
|
|
685
|
+
```ts
|
|
686
|
+
import { Initialize } from "quantumcoin/config";
|
|
687
|
+
import qc from "quantumcoin";
|
|
688
|
+
|
|
689
|
+
await Initialize(null);
|
|
690
|
+
const iface = new qc.Interface([
|
|
691
|
+
{ type: "error", name: "InsufficientBalance",
|
|
692
|
+
inputs: [{ name: "have", type: "uint256" }, { name: "want", type: "uint256" }] },
|
|
693
|
+
]);
|
|
694
|
+
const selector: string = iface.getError("InsufficientBalance").selector;
|
|
695
|
+
// const desc = iface.parseError(revertData); // { name, args: { have, want }, ... }
|
|
696
|
+
```
|
|
697
|
+
|
|
631
698
|
### `AbiCoder`
|
|
632
699
|
|
|
633
700
|
Minimal ABI coder for encoding/decoding tuples of values.
|
|
@@ -635,6 +702,8 @@ Minimal ABI coder for encoding/decoding tuples of values.
|
|
|
635
702
|
- `encode(types: (string|any)[], values: any[]): string`
|
|
636
703
|
- `decode(types: (string|any)[], data: string): any`
|
|
637
704
|
- `getDefaultValue(types: (string|any)[]): any`
|
|
705
|
+
- `static defaultAbiCoder(): AbiCoder` — shared singleton instance (ethers.js v6
|
|
706
|
+
shape): `AbiCoder.defaultAbiCoder().encode(["uint256"], [1n])`.
|
|
638
707
|
|
|
639
708
|
## Utilities
|
|
640
709
|
|
package/README.md
CHANGED
|
@@ -148,6 +148,48 @@ const staking = new Contract(address, abi, provider);
|
|
|
148
148
|
console.log(await staking.getDepositorCount());
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
+
## ABI encoding & decoding
|
|
152
|
+
|
|
153
|
+
`Interface` and `AbiCoder` provide ethers.js v6-compatible ABI handling. Beyond
|
|
154
|
+
encoding, you can resolve fragments by name, canonical signature, or hex
|
|
155
|
+
identifier, and decode/parse raw calldata — useful for a strict "what you see is
|
|
156
|
+
what you sign" verification (decode the real bytes, then re-encode and compare).
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
const { Initialize } = require("quantumcoin/config");
|
|
160
|
+
const { Interface, AbiCoder } = require("quantumcoin");
|
|
161
|
+
|
|
162
|
+
await Initialize(null);
|
|
163
|
+
const iface = new Interface([
|
|
164
|
+
{ type: "function", name: "transfer",
|
|
165
|
+
inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }],
|
|
166
|
+
outputs: [{ type: "bool" }] },
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
const to = "0x" + "ab".repeat(32); // 32-byte QuantumCoin address
|
|
170
|
+
const data = iface.encodeFunctionData("transfer", [to, 1000n]);
|
|
171
|
+
|
|
172
|
+
const tx = iface.parseTransaction({ data }); // resolves by selector, decodes args
|
|
173
|
+
// tx.name === "transfer", tx.selector === "0xa9059cbb", tx.args.amount === 1000n
|
|
174
|
+
|
|
175
|
+
// Re-encode the decoded args and confirm they reproduce the original calldata.
|
|
176
|
+
const verified = iface.encodeFunctionData(tx.fragment, Array.from(tx.args)) === data;
|
|
177
|
+
|
|
178
|
+
const coder = AbiCoder.defaultAbiCoder();
|
|
179
|
+
coder.encode(["uint256"], [1n]);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Highlights: `getFunction`/`getError` (by name, signature, or 4-byte selector),
|
|
183
|
+
`getEvent` (by name, signature, or topic0), `decodeFunctionData`,
|
|
184
|
+
`parseTransaction`, `parseError`, `getSighash`, `FunctionFragment.selector`,
|
|
185
|
+
`EventFragment.topicHash`, `Interface.encodeDeploy`, and
|
|
186
|
+
`AbiCoder.defaultAbiCoder()`.
|
|
187
|
+
|
|
188
|
+
> QuantumCoin addresses are 32 bytes and fill a full ABI word (Ethereum left-pads
|
|
189
|
+
> a 20-byte address). Canonical signatures are identical, so selectors and event
|
|
190
|
+
> topics match Ethereum exactly; only address-bearing calldata words differ. See
|
|
191
|
+
> [README-SDK.md](./README-SDK.md#interface) for the full API.
|
|
192
|
+
|
|
151
193
|
## Typed contract generator
|
|
152
194
|
|
|
153
195
|
This repo includes a generator described in `SPEC.md` section 15.
|
package/package.json
CHANGED
package/src/abi/fragments.d.ts
CHANGED
|
@@ -29,10 +29,28 @@ export class Fragment {
|
|
|
29
29
|
export class NamedFragment extends Fragment {
|
|
30
30
|
}
|
|
31
31
|
export class FunctionFragment extends NamedFragment {
|
|
32
|
+
/**
|
|
33
|
+
* The 4-byte function selector (sighash), e.g. "0xa9059cbb". Requires the SDK
|
|
34
|
+
* to be initialized (uses keccak256). Mirrors ethers.js v6.
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
get selector(): string;
|
|
32
38
|
}
|
|
33
39
|
export class EventFragment extends NamedFragment {
|
|
40
|
+
/**
|
|
41
|
+
* The event topic hash (topic0). Requires the SDK to be initialized (uses
|
|
42
|
+
* keccak256). Mirrors ethers.js v6.
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
get topicHash(): string;
|
|
34
46
|
}
|
|
35
47
|
export class ErrorFragment extends NamedFragment {
|
|
48
|
+
/**
|
|
49
|
+
* The 4-byte error selector. Requires the SDK to be initialized (uses
|
|
50
|
+
* keccak256). Mirrors ethers.js v6.
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
get selector(): string;
|
|
36
54
|
}
|
|
37
55
|
export class ConstructorFragment extends Fragment {
|
|
38
56
|
}
|
package/src/abi/fragments.js
CHANGED
|
@@ -43,9 +43,52 @@ class Fragment {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
class NamedFragment extends Fragment {}
|
|
46
|
-
|
|
47
|
-
class
|
|
48
|
-
|
|
46
|
+
|
|
47
|
+
class FunctionFragment extends NamedFragment {
|
|
48
|
+
/**
|
|
49
|
+
* The 4-byte function selector (sighash), e.g. "0xa9059cbb". Requires the SDK
|
|
50
|
+
* to be initialized (uses keccak256). Mirrors ethers.js v6.
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
get selector() {
|
|
54
|
+
// Lazy require to keep fragment construction dependency-free.
|
|
55
|
+
// eslint-disable-next-line global-require
|
|
56
|
+
const { functionSelectorHex } = require("./js-abi-coder");
|
|
57
|
+
return functionSelectorHex(this.name, Array.isArray(this.inputs) ? this.inputs : []);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class EventFragment extends NamedFragment {
|
|
62
|
+
/**
|
|
63
|
+
* The event topic hash (topic0). Requires the SDK to be initialized (uses
|
|
64
|
+
* keccak256). Mirrors ethers.js v6.
|
|
65
|
+
* @returns {string}
|
|
66
|
+
*/
|
|
67
|
+
get topicHash() {
|
|
68
|
+
// eslint-disable-next-line global-require
|
|
69
|
+
const { canonicalType } = require("./js-abi-coder");
|
|
70
|
+
// eslint-disable-next-line global-require
|
|
71
|
+
const { id } = require("../utils/hashing");
|
|
72
|
+
// eslint-disable-next-line global-require
|
|
73
|
+
const { normalizeHex } = require("../internal/hex");
|
|
74
|
+
const inputs = Array.isArray(this.inputs) ? this.inputs : [];
|
|
75
|
+
return normalizeHex(id(`${this.name}(${inputs.map((i) => canonicalType(i)).join(",")})`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class ErrorFragment extends NamedFragment {
|
|
80
|
+
/**
|
|
81
|
+
* The 4-byte error selector. Requires the SDK to be initialized (uses
|
|
82
|
+
* keccak256). Mirrors ethers.js v6.
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
get selector() {
|
|
86
|
+
// eslint-disable-next-line global-require
|
|
87
|
+
const { functionSelectorHex } = require("./js-abi-coder");
|
|
88
|
+
return functionSelectorHex(this.name, Array.isArray(this.inputs) ? this.inputs : []);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
49
92
|
class ConstructorFragment extends Fragment {}
|
|
50
93
|
class StructFragment extends Fragment {}
|
|
51
94
|
class FallbackFragment extends Fragment {}
|
package/src/abi/interface.d.ts
CHANGED
|
@@ -31,23 +31,26 @@ export class Interface {
|
|
|
31
31
|
*/
|
|
32
32
|
format(format?: string | undefined): string;
|
|
33
33
|
/**
|
|
34
|
-
* Get a function fragment by name
|
|
35
|
-
*
|
|
34
|
+
* Get a function fragment by bare name, canonical signature
|
|
35
|
+
* (`name(type,...)`), or 4-byte selector (`0x` + 8 hex). Mirrors ethers.js v6.
|
|
36
|
+
* @param {string} nameOrSignatureOrSelector
|
|
36
37
|
* @returns {FunctionFragment}
|
|
37
38
|
*/
|
|
38
|
-
getFunction(
|
|
39
|
+
getFunction(nameOrSignatureOrSelector: string): FunctionFragment;
|
|
39
40
|
/**
|
|
40
|
-
* Get an event fragment by name
|
|
41
|
-
*
|
|
41
|
+
* Get an event fragment by bare name, canonical signature, or topic0
|
|
42
|
+
* (`0x` + 64 hex). Mirrors ethers.js v6.
|
|
43
|
+
* @param {string} nameOrSignatureOrTopic
|
|
42
44
|
* @returns {EventFragment}
|
|
43
45
|
*/
|
|
44
|
-
getEvent(
|
|
46
|
+
getEvent(nameOrSignatureOrTopic: string): EventFragment;
|
|
45
47
|
/**
|
|
46
|
-
* Get an error fragment by name
|
|
47
|
-
*
|
|
48
|
+
* Get an error fragment by bare name, canonical signature, or 4-byte selector.
|
|
49
|
+
* Mirrors ethers.js v6.
|
|
50
|
+
* @param {string} nameOrSignatureOrSelector
|
|
48
51
|
* @returns {ErrorFragment}
|
|
49
52
|
*/
|
|
50
|
-
getError(
|
|
53
|
+
getError(nameOrSignatureOrSelector: string): ErrorFragment;
|
|
51
54
|
/**
|
|
52
55
|
* Returns the constructor fragment if present.
|
|
53
56
|
* @returns {ConstructorFragment|null}
|
|
@@ -60,6 +63,23 @@ export class Interface {
|
|
|
60
63
|
* @returns {string}
|
|
61
64
|
*/
|
|
62
65
|
encodeFunctionData(functionFragment: FunctionFragment | string, values: any[]): string;
|
|
66
|
+
/**
|
|
67
|
+
* Encode ABI constructor arguments for a contract deployment (no selector).
|
|
68
|
+
* Returns "0x" when the constructor takes no arguments. The caller appends
|
|
69
|
+
* this to the creation bytecode. Mirrors ethers.js v6.
|
|
70
|
+
* @param {any[]=} values
|
|
71
|
+
* @returns {string}
|
|
72
|
+
*/
|
|
73
|
+
encodeDeploy(values?: any[] | undefined): string;
|
|
74
|
+
/**
|
|
75
|
+
* Decode the calldata of a function call (4-byte selector + ABI-encoded
|
|
76
|
+
* arguments), returning the decoded input arguments as a Result. The data's
|
|
77
|
+
* selector must match the resolved function. Mirrors ethers.js v6.
|
|
78
|
+
* @param {FunctionFragment|string} fragment
|
|
79
|
+
* @param {string} data
|
|
80
|
+
* @returns {Result}
|
|
81
|
+
*/
|
|
82
|
+
decodeFunctionData(fragment: FunctionFragment | string, data: string): Result;
|
|
63
83
|
/**
|
|
64
84
|
* Decode function result using quantum-coin-js-sdk.
|
|
65
85
|
* @param {FunctionFragment|string} functionFragment
|
|
@@ -85,7 +105,25 @@ export class Interface {
|
|
|
85
105
|
* @returns {any}
|
|
86
106
|
*/
|
|
87
107
|
decodeEventLog(eventFragment: EventFragment | any, topics: string[], data: string): any;
|
|
88
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Parse a transaction's calldata into its function + decoded arguments.
|
|
110
|
+
* Resolves the function by the 4-byte selector in `tx.data`, decodes the
|
|
111
|
+
* arguments from the real bytes, and reports `value` as a bigint. Mirrors
|
|
112
|
+
* ethers.js v6 (returns a TransactionDescription-shaped object).
|
|
113
|
+
* @param {{ data: string, value?: any }} tx
|
|
114
|
+
* @returns {{ fragment: FunctionFragment, name: string, signature: string, selector: string, args: Result, value: bigint }}
|
|
115
|
+
*/
|
|
116
|
+
parseTransaction(tx: {
|
|
117
|
+
data: string;
|
|
118
|
+
value?: any;
|
|
119
|
+
}): {
|
|
120
|
+
fragment: FunctionFragment;
|
|
121
|
+
name: string;
|
|
122
|
+
signature: string;
|
|
123
|
+
selector: string;
|
|
124
|
+
args: Result;
|
|
125
|
+
value: bigint;
|
|
126
|
+
};
|
|
89
127
|
parseLog(...args: any[]): {
|
|
90
128
|
fragment: EventFragment;
|
|
91
129
|
name: any;
|
|
@@ -93,8 +131,27 @@ export class Interface {
|
|
|
93
131
|
topic: string;
|
|
94
132
|
args: Result;
|
|
95
133
|
};
|
|
96
|
-
|
|
97
|
-
|
|
134
|
+
/**
|
|
135
|
+
* Parse custom-error return data into its error fragment + decoded arguments.
|
|
136
|
+
* Resolves the error by the 4-byte selector in `data`. Mirrors ethers.js v6
|
|
137
|
+
* (returns an ErrorDescription-shaped object).
|
|
138
|
+
* @param {string} data
|
|
139
|
+
* @returns {{ fragment: ErrorFragment, name: string, signature: string, selector: string, args: Result }}
|
|
140
|
+
*/
|
|
141
|
+
parseError(data: string): {
|
|
142
|
+
fragment: ErrorFragment;
|
|
143
|
+
name: string;
|
|
144
|
+
signature: string;
|
|
145
|
+
selector: string;
|
|
146
|
+
args: Result;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Return the 4-byte function selector (sighash) for a function fragment or
|
|
150
|
+
* name. Mirrors ethers.js v5 getSighash / v6 FunctionFragment.selector.
|
|
151
|
+
* @param {FunctionFragment|string} fragmentOrName
|
|
152
|
+
* @returns {string}
|
|
153
|
+
*/
|
|
154
|
+
getSighash(fragmentOrName: FunctionFragment | string): string;
|
|
98
155
|
/**
|
|
99
156
|
* Compute the topic0 (event signature hash) for an event.
|
|
100
157
|
* @param {string|EventFragment|any} nameOrFragment
|
|
@@ -105,6 +162,11 @@ export class Interface {
|
|
|
105
162
|
getReceive(): null;
|
|
106
163
|
}
|
|
107
164
|
export class AbiCoder {
|
|
165
|
+
/**
|
|
166
|
+
* Return the shared default AbiCoder instance (ethers.js v6 shape).
|
|
167
|
+
* @returns {AbiCoder}
|
|
168
|
+
*/
|
|
169
|
+
static defaultAbiCoder(): AbiCoder;
|
|
108
170
|
/**
|
|
109
171
|
* Encode values by types into ABI data.
|
|
110
172
|
* @param {(string|any)[]} types
|
package/src/abi/interface.js
CHANGED
|
@@ -8,12 +8,45 @@
|
|
|
8
8
|
|
|
9
9
|
const qcsdk = require("quantum-coin-js-sdk");
|
|
10
10
|
const { makeError, assertArgument } = require("../errors");
|
|
11
|
-
const { normalizeHex, arrayify, bytesToHex } = require("../internal/hex");
|
|
11
|
+
const { normalizeHex, arrayify, bytesToHex, isHexString } = require("../internal/hex");
|
|
12
12
|
const { EventFragment, FunctionFragment, ErrorFragment, ConstructorFragment } = require("./fragments");
|
|
13
13
|
const { Result } = require("../utils/result");
|
|
14
14
|
const { id } = require("../utils/hashing");
|
|
15
15
|
const jsAbi = require("./js-abi-coder");
|
|
16
16
|
|
|
17
|
+
// Remove all whitespace (used to normalize a caller-supplied canonical signature
|
|
18
|
+
// like "transfer(address, uint256)" before comparing against the ABI).
|
|
19
|
+
function _stripWhitespace(s) {
|
|
20
|
+
return String(s).replace(/\s+/g, "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Canonical Solidity signature for a fragment, e.g. "transfer(address,uint256)".
|
|
24
|
+
// Tuples/arrays are expanded via the shared js-abi-coder canonicalizer so it
|
|
25
|
+
// matches the selector/topic hashing used everywhere else.
|
|
26
|
+
function _canonicalSignature(frag) {
|
|
27
|
+
const inputs = Array.isArray(frag.inputs) ? frag.inputs : [];
|
|
28
|
+
return `${frag.name}(${inputs.map((i) => jsAbi.canonicalType(i)).join(",")})`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 0x + 8 hex => a 4-byte function/error selector.
|
|
32
|
+
function _isSelectorHex(s) {
|
|
33
|
+
return typeof s === "string" && /^0x[0-9a-fA-F]{8}$/.test(s.trim());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 0x + 64 hex => a 32-byte event topic (topic0).
|
|
37
|
+
function _isTopicHex(s) {
|
|
38
|
+
return typeof s === "string" && /^0x[0-9a-fA-F]{64}$/.test(s.trim());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Resolve a function fragment for decode/encode. A fragment object (e.g. the one
|
|
42
|
+
// returned by parseTransaction/getFunction) is used AS-IS so an overloaded name
|
|
43
|
+
// is never re-resolved ambiguously; a string resolves through the ABI.
|
|
44
|
+
function _resolveFunctionFragment(iface, fragment) {
|
|
45
|
+
if (fragment instanceof FunctionFragment) return fragment;
|
|
46
|
+
if (fragment && typeof fragment === "object" && fragment.name) return new FunctionFragment(fragment);
|
|
47
|
+
return iface.getFunction(fragment);
|
|
48
|
+
}
|
|
49
|
+
|
|
17
50
|
function _requireInitialized() {
|
|
18
51
|
// eslint-disable-next-line global-require
|
|
19
52
|
const { isInitialized, getInitializationPromise } = require("../../config");
|
|
@@ -289,39 +322,96 @@ class Interface {
|
|
|
289
322
|
}
|
|
290
323
|
|
|
291
324
|
/**
|
|
292
|
-
* Get a function fragment by name
|
|
293
|
-
*
|
|
325
|
+
* Get a function fragment by bare name, canonical signature
|
|
326
|
+
* (`name(type,...)`), or 4-byte selector (`0x` + 8 hex). Mirrors ethers.js v6.
|
|
327
|
+
* @param {string} nameOrSignatureOrSelector
|
|
294
328
|
* @returns {FunctionFragment}
|
|
295
329
|
*/
|
|
296
|
-
getFunction(
|
|
297
|
-
assertArgument(typeof
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
330
|
+
getFunction(nameOrSignatureOrSelector) {
|
|
331
|
+
assertArgument(typeof nameOrSignatureOrSelector === "string", "name must be a string", "nameOrSignatureOrSelector", nameOrSignatureOrSelector);
|
|
332
|
+
const key = nameOrSignatureOrSelector.trim();
|
|
333
|
+
const fns = this.abi.filter((f) => f && f.type === "function" && f.name);
|
|
334
|
+
|
|
335
|
+
if (_isSelectorHex(key)) {
|
|
336
|
+
const sel = normalizeHex(key).toLowerCase();
|
|
337
|
+
const found = fns.find((f) => jsAbi.functionSelectorHex(f.name, f.inputs || []).toLowerCase() === sel);
|
|
338
|
+
if (!found) throw makeError("no matching function for selector", "INVALID_ARGUMENT", { selector: key });
|
|
339
|
+
return new FunctionFragment(found);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (key.indexOf("(") >= 0) {
|
|
343
|
+
const norm = _stripWhitespace(key);
|
|
344
|
+
const found = fns.find((f) => _canonicalSignature(f) === norm);
|
|
345
|
+
if (!found) throw makeError("no matching function for signature", "INVALID_ARGUMENT", { signature: key });
|
|
346
|
+
return new FunctionFragment(found);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const matches = fns.filter((f) => f.name === key);
|
|
350
|
+
if (matches.length === 0) throw makeError("function not found", "INVALID_ARGUMENT", { nameOrSignatureOrSelector });
|
|
351
|
+
if (matches.length > 1) {
|
|
352
|
+
throw makeError("ambiguous function name; specify the full signature", "INVALID_ARGUMENT", { nameOrSignatureOrSelector });
|
|
353
|
+
}
|
|
354
|
+
return new FunctionFragment(matches[0]);
|
|
301
355
|
}
|
|
302
356
|
|
|
303
357
|
/**
|
|
304
|
-
* Get an event fragment by name
|
|
305
|
-
*
|
|
358
|
+
* Get an event fragment by bare name, canonical signature, or topic0
|
|
359
|
+
* (`0x` + 64 hex). Mirrors ethers.js v6.
|
|
360
|
+
* @param {string} nameOrSignatureOrTopic
|
|
306
361
|
* @returns {EventFragment}
|
|
307
362
|
*/
|
|
308
|
-
getEvent(
|
|
309
|
-
assertArgument(typeof
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
363
|
+
getEvent(nameOrSignatureOrTopic) {
|
|
364
|
+
assertArgument(typeof nameOrSignatureOrTopic === "string", "name must be a string", "nameOrSignatureOrTopic", nameOrSignatureOrTopic);
|
|
365
|
+
const key = nameOrSignatureOrTopic.trim();
|
|
366
|
+
const evs = this.abi.filter((f) => f && f.type === "event" && f.name);
|
|
367
|
+
|
|
368
|
+
if (_isTopicHex(key)) {
|
|
369
|
+
const topic = normalizeHex(key);
|
|
370
|
+
const found = evs.find((f) => !f.anonymous && normalizeHex(id(_canonicalSignature(f))) === topic);
|
|
371
|
+
if (!found) throw makeError("no matching event for topic", "INVALID_ARGUMENT", { topic: key });
|
|
372
|
+
return new EventFragment(found);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (key.indexOf("(") >= 0) {
|
|
376
|
+
const norm = _stripWhitespace(key);
|
|
377
|
+
const found = evs.find((f) => _canonicalSignature(f) === norm);
|
|
378
|
+
if (!found) throw makeError("no matching event for signature", "INVALID_ARGUMENT", { signature: key });
|
|
379
|
+
return new EventFragment(found);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const matches = evs.filter((f) => f.name === key);
|
|
383
|
+
if (matches.length === 0) throw makeError("event not found", "INVALID_ARGUMENT", { nameOrSignatureOrTopic });
|
|
384
|
+
return new EventFragment(matches[0]);
|
|
313
385
|
}
|
|
314
386
|
|
|
315
387
|
/**
|
|
316
|
-
* Get an error fragment by name
|
|
317
|
-
*
|
|
388
|
+
* Get an error fragment by bare name, canonical signature, or 4-byte selector.
|
|
389
|
+
* Mirrors ethers.js v6.
|
|
390
|
+
* @param {string} nameOrSignatureOrSelector
|
|
318
391
|
* @returns {ErrorFragment}
|
|
319
392
|
*/
|
|
320
|
-
getError(
|
|
321
|
-
assertArgument(typeof
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
393
|
+
getError(nameOrSignatureOrSelector) {
|
|
394
|
+
assertArgument(typeof nameOrSignatureOrSelector === "string", "name must be a string", "nameOrSignatureOrSelector", nameOrSignatureOrSelector);
|
|
395
|
+
const key = nameOrSignatureOrSelector.trim();
|
|
396
|
+
const errs = this.abi.filter((f) => f && f.type === "error" && f.name);
|
|
397
|
+
|
|
398
|
+
if (_isSelectorHex(key)) {
|
|
399
|
+
const sel = normalizeHex(key).toLowerCase();
|
|
400
|
+
const found = errs.find((f) => jsAbi.functionSelectorHex(f.name, f.inputs || []).toLowerCase() === sel);
|
|
401
|
+
if (!found) throw makeError("no matching error for selector", "INVALID_ARGUMENT", { selector: key });
|
|
402
|
+
return new ErrorFragment(found);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (key.indexOf("(") >= 0) {
|
|
406
|
+
const norm = _stripWhitespace(key);
|
|
407
|
+
const found = errs.find((f) => _canonicalSignature(f) === norm);
|
|
408
|
+
if (!found) throw makeError("no matching error for signature", "INVALID_ARGUMENT", { signature: key });
|
|
409
|
+
return new ErrorFragment(found);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const matches = errs.filter((f) => f.name === key);
|
|
413
|
+
if (matches.length === 0) throw makeError("error not found", "INVALID_ARGUMENT", { nameOrSignatureOrSelector });
|
|
414
|
+
return new ErrorFragment(matches[0]);
|
|
325
415
|
}
|
|
326
416
|
|
|
327
417
|
/**
|
|
@@ -341,24 +431,76 @@ class Interface {
|
|
|
341
431
|
*/
|
|
342
432
|
encodeFunctionData(functionFragment, values) {
|
|
343
433
|
_requireInitialized();
|
|
344
|
-
const
|
|
434
|
+
const rawArgs = Array.isArray(values) ? values : [];
|
|
435
|
+
|
|
436
|
+
// When a fragment object is supplied (e.g. from parseTransaction /
|
|
437
|
+
// getFunction), encode directly from its exact inputs via the pure-JS coder.
|
|
438
|
+
// This guarantees encode/decode is a faithful round-trip (including
|
|
439
|
+
// overloaded functions) and matches decodeFunctionData byte-for-byte.
|
|
440
|
+
if (functionFragment && typeof functionFragment === "object") {
|
|
441
|
+
const fname = functionFragment.name;
|
|
442
|
+
assertArgument(typeof fname === "string" && fname.length > 0, "invalid function", "functionFragment", functionFragment);
|
|
443
|
+
const finputs = Array.isArray(functionFragment.inputs) ? functionFragment.inputs : [];
|
|
444
|
+
return jsAbi.encodeFunctionData(fname, finputs, rawArgs);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const name = functionFragment;
|
|
345
448
|
assertArgument(typeof name === "string" && name.length > 0, "invalid function", "functionFragment", functionFragment);
|
|
346
449
|
const frag = this.getFunction(name);
|
|
347
450
|
const inputs = Array.isArray(frag.inputs) ? frag.inputs : [];
|
|
348
|
-
const rawArgs = Array.isArray(values) ? values : [];
|
|
349
451
|
|
|
350
452
|
// Fallback for complex ABI surfaces where qcsdk packing is unreliable.
|
|
351
453
|
if (jsAbi.needsJsAbi(inputs)) {
|
|
352
|
-
return jsAbi.encodeFunctionData(name, inputs, rawArgs);
|
|
454
|
+
return jsAbi.encodeFunctionData(frag.name, inputs, rawArgs);
|
|
353
455
|
}
|
|
354
456
|
|
|
355
457
|
const args = inputs.map((p, idx) => _convertInputValueForQcsdk(p, rawArgs[idx]));
|
|
356
|
-
const res = qcsdk.packMethodData(this._qcsdkAbiJson, name, ...args);
|
|
458
|
+
const res = qcsdk.packMethodData(this._qcsdkAbiJson, frag.name, ...args);
|
|
357
459
|
if (!res || typeof res.error !== "string") throw makeError("packMethodData failed", "UNKNOWN_ERROR", {});
|
|
358
|
-
if (res.error) throw makeError(res.error, "UNKNOWN_ERROR", { operation: "packMethodData", function: name });
|
|
460
|
+
if (res.error) throw makeError(res.error, "UNKNOWN_ERROR", { operation: "packMethodData", function: frag.name });
|
|
359
461
|
return normalizeHex(res.result);
|
|
360
462
|
}
|
|
361
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Encode ABI constructor arguments for a contract deployment (no selector).
|
|
466
|
+
* Returns "0x" when the constructor takes no arguments. The caller appends
|
|
467
|
+
* this to the creation bytecode. Mirrors ethers.js v6.
|
|
468
|
+
* @param {any[]=} values
|
|
469
|
+
* @returns {string}
|
|
470
|
+
*/
|
|
471
|
+
encodeDeploy(values) {
|
|
472
|
+
_requireInitialized();
|
|
473
|
+
const ctor = this.getConstructor();
|
|
474
|
+
const inputs = ctor && Array.isArray(ctor.inputs) ? ctor.inputs : [];
|
|
475
|
+
const rawArgs = Array.isArray(values) ? values : [];
|
|
476
|
+
if (inputs.length === 0) return "0x";
|
|
477
|
+
return normalizeHex(bytesToHex(jsAbi.encodeTupleLike(inputs, rawArgs)));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Decode the calldata of a function call (4-byte selector + ABI-encoded
|
|
482
|
+
* arguments), returning the decoded input arguments as a Result. The data's
|
|
483
|
+
* selector must match the resolved function. Mirrors ethers.js v6.
|
|
484
|
+
* @param {FunctionFragment|string} fragment
|
|
485
|
+
* @param {string} data
|
|
486
|
+
* @returns {Result}
|
|
487
|
+
*/
|
|
488
|
+
decodeFunctionData(fragment, data) {
|
|
489
|
+
_requireInitialized();
|
|
490
|
+
assertArgument(typeof data === "string" && isHexString(data), "data must be a hex string", "data", data);
|
|
491
|
+
const frag = _resolveFunctionFragment(this, fragment);
|
|
492
|
+
const inputs = Array.isArray(frag.inputs) ? frag.inputs : [];
|
|
493
|
+
const selector = jsAbi.functionSelectorHex(frag.name, inputs).toLowerCase();
|
|
494
|
+
const norm = normalizeHex(data);
|
|
495
|
+
const bytes = arrayify(norm);
|
|
496
|
+
if (bytes.length < 4 || norm.slice(0, 10).toLowerCase() !== selector) {
|
|
497
|
+
throw makeError("data selector does not match function", "INVALID_ARGUMENT", { function: frag.name, data });
|
|
498
|
+
}
|
|
499
|
+
const items = jsAbi.decodeTupleLike(inputs, bytes.slice(4), 0);
|
|
500
|
+
const keys = inputs.map((i) => (i && typeof i.name === "string" && i.name.length ? i.name : null));
|
|
501
|
+
return Result.fromItems(items, keys);
|
|
502
|
+
}
|
|
503
|
+
|
|
362
504
|
/**
|
|
363
505
|
* Decode function result using quantum-coin-js-sdk.
|
|
364
506
|
* @param {FunctionFragment|string} functionFragment
|
|
@@ -442,9 +584,40 @@ class Interface {
|
|
|
442
584
|
}
|
|
443
585
|
}
|
|
444
586
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
587
|
+
/**
|
|
588
|
+
* Parse a transaction's calldata into its function + decoded arguments.
|
|
589
|
+
* Resolves the function by the 4-byte selector in `tx.data`, decodes the
|
|
590
|
+
* arguments from the real bytes, and reports `value` as a bigint. Mirrors
|
|
591
|
+
* ethers.js v6 (returns a TransactionDescription-shaped object).
|
|
592
|
+
* @param {{ data: string, value?: any }} tx
|
|
593
|
+
* @returns {{ fragment: FunctionFragment, name: string, signature: string, selector: string, args: Result, value: bigint }}
|
|
594
|
+
*/
|
|
595
|
+
parseTransaction(tx) {
|
|
596
|
+
_requireInitialized();
|
|
597
|
+
assertArgument(tx && typeof tx === "object", "tx must be an object", "tx", tx);
|
|
598
|
+
const data = tx.data;
|
|
599
|
+
assertArgument(typeof data === "string" && isHexString(data), "tx.data must be a hex string", "tx.data", data);
|
|
600
|
+
const norm = normalizeHex(data);
|
|
601
|
+
if (arrayify(norm).length < 4) throw makeError("data too short for a function selector", "INVALID_ARGUMENT", { data });
|
|
602
|
+
const selector = norm.slice(0, 10);
|
|
603
|
+
const frag = this.getFunction(selector);
|
|
604
|
+
const args = this.decodeFunctionData(frag, norm);
|
|
605
|
+
let value = 0n;
|
|
606
|
+
if (tx.value != null) {
|
|
607
|
+
try {
|
|
608
|
+
value = BigInt(tx.value);
|
|
609
|
+
} catch {
|
|
610
|
+
value = 0n;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
fragment: frag,
|
|
615
|
+
name: frag.name,
|
|
616
|
+
signature: _canonicalSignature(frag),
|
|
617
|
+
selector: jsAbi.functionSelectorHex(frag.name, Array.isArray(frag.inputs) ? frag.inputs : []),
|
|
618
|
+
args,
|
|
619
|
+
value,
|
|
620
|
+
};
|
|
448
621
|
}
|
|
449
622
|
parseLog() {
|
|
450
623
|
_requireInitialized();
|
|
@@ -512,11 +685,47 @@ class Interface {
|
|
|
512
685
|
args,
|
|
513
686
|
};
|
|
514
687
|
}
|
|
515
|
-
|
|
516
|
-
|
|
688
|
+
/**
|
|
689
|
+
* Parse custom-error return data into its error fragment + decoded arguments.
|
|
690
|
+
* Resolves the error by the 4-byte selector in `data`. Mirrors ethers.js v6
|
|
691
|
+
* (returns an ErrorDescription-shaped object).
|
|
692
|
+
* @param {string} data
|
|
693
|
+
* @returns {{ fragment: ErrorFragment, name: string, signature: string, selector: string, args: Result }}
|
|
694
|
+
*/
|
|
695
|
+
parseError(data) {
|
|
696
|
+
_requireInitialized();
|
|
697
|
+
assertArgument(typeof data === "string" && isHexString(data), "data must be a hex string", "data", data);
|
|
698
|
+
const norm = normalizeHex(data);
|
|
699
|
+
const bytes = arrayify(norm);
|
|
700
|
+
if (bytes.length < 4) throw makeError("data too short for an error selector", "INVALID_ARGUMENT", { data });
|
|
701
|
+
const selector = norm.slice(0, 10).toLowerCase();
|
|
702
|
+
const errs = this.abi.filter((f) => f && f.type === "error" && f.name);
|
|
703
|
+
const found = errs.find((f) => jsAbi.functionSelectorHex(f.name, f.inputs || []).toLowerCase() === selector);
|
|
704
|
+
if (!found) throw makeError("no matching error for selector", "INVALID_ARGUMENT", { selector });
|
|
705
|
+
const inputs = Array.isArray(found.inputs) ? found.inputs : [];
|
|
706
|
+
const items = jsAbi.decodeTupleLike(inputs, bytes.slice(4), 0);
|
|
707
|
+
const keys = inputs.map((i) => (i && typeof i.name === "string" && i.name.length ? i.name : null));
|
|
708
|
+
return {
|
|
709
|
+
fragment: new ErrorFragment(found),
|
|
710
|
+
name: found.name,
|
|
711
|
+
signature: _canonicalSignature(found),
|
|
712
|
+
selector: jsAbi.functionSelectorHex(found.name, inputs),
|
|
713
|
+
args: Result.fromItems(items, keys),
|
|
714
|
+
};
|
|
517
715
|
}
|
|
518
|
-
|
|
519
|
-
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Return the 4-byte function selector (sighash) for a function fragment or
|
|
719
|
+
* name. Mirrors ethers.js v5 getSighash / v6 FunctionFragment.selector.
|
|
720
|
+
* @param {FunctionFragment|string} fragmentOrName
|
|
721
|
+
* @returns {string}
|
|
722
|
+
*/
|
|
723
|
+
getSighash(fragmentOrName) {
|
|
724
|
+
const frag =
|
|
725
|
+
fragmentOrName && typeof fragmentOrName === "object" && fragmentOrName.name
|
|
726
|
+
? fragmentOrName
|
|
727
|
+
: this.getFunction(fragmentOrName);
|
|
728
|
+
return jsAbi.functionSelectorHex(frag.name, Array.isArray(frag.inputs) ? frag.inputs : []);
|
|
520
729
|
}
|
|
521
730
|
/**
|
|
522
731
|
* Compute the topic0 (event signature hash) for an event.
|
|
@@ -599,6 +808,15 @@ class AbiCoder {
|
|
|
599
808
|
assertArgument(Array.isArray(types), "types must be an array", "types", types);
|
|
600
809
|
return types.map(() => null);
|
|
601
810
|
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Return the shared default AbiCoder instance (ethers.js v6 shape).
|
|
814
|
+
* @returns {AbiCoder}
|
|
815
|
+
*/
|
|
816
|
+
static defaultAbiCoder() {
|
|
817
|
+
if (!AbiCoder._instance) AbiCoder._instance = new AbiCoder();
|
|
818
|
+
return AbiCoder._instance;
|
|
819
|
+
}
|
|
602
820
|
}
|
|
603
821
|
|
|
604
822
|
module.exports = { Interface, AbiCoder };
|
|
@@ -6,3 +6,5 @@ export function functionSelectorHex(name: any, inputs: any): string;
|
|
|
6
6
|
export function encodeFunctionData(name: any, inputs: any, values: any): string;
|
|
7
7
|
export function encodeTupleLike(params: any, values: any, depth: any): Uint8Array<any>;
|
|
8
8
|
export function decodeFunctionResult(outputs: any, dataHex: any): any[];
|
|
9
|
+
export function decodeTupleLike(params: any, data: any, baseOffset: any, depth: any): any[];
|
|
10
|
+
export function decodeParam(param: any, data: any, offset: any, depth: any): any;
|