@crewhaus/tool-onchain 0.7.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 ADDED
@@ -0,0 +1,111 @@
1
+ # @crewhaus/tool-onchain
2
+
3
+ The onchain arithmetic, offline.
4
+
5
+ | Tool | Answers |
6
+ |---|---|
7
+ | `AbiEncodeCall` | what are the exact bytes of this contract call |
8
+ | `AbiDecode` | what does this returned hex actually say |
9
+ | `FunctionSelector` | what is this function's selector, or this event's topic |
10
+ | `AddressCheck` | is this address well-formed, and does its checksum hold |
11
+ | `TypedDataHash` | what digest would a wallet sign for this message |
12
+ | `TokenUnits` | what is this amount in base units, exactly |
13
+ | `DefiMath` | minimum out, maximum in, price impact, share, health factor |
14
+
15
+ No RPC, no network, no keys. These are the calculations that must be right
16
+ *before* a transaction is composed — and a transaction is irreversible, so
17
+ "plausible" is not good enough.
18
+
19
+ ## Nothing here signs
20
+
21
+ No private key is accepted, read or handled anywhere in this package, and a
22
+ test asserts that no schema has a field one could be passed to.
23
+
24
+ `TypedDataHash` computes the digest a wallet *would* sign. That is the useful
25
+ half, because it lets a signature request be checked **before** it is
26
+ approved: hash the message a human was actually shown and compare it against
27
+ what the dapp asked for. A malicious dapp's whole trick is the gap between
28
+ those two.
29
+
30
+ ## Verified against published vectors, not against itself
31
+
32
+ A codec tested only against its own output agrees with itself and with
33
+ nothing on a chain. So:
34
+
35
+ - ABI encoding is checked against real ERC-20 `transfer` calldata, byte for
36
+ byte, and selectors against the ones every block explorer shows.
37
+ - EIP-712 is checked against the specification's own worked example — the
38
+ type hash, the domain separator, the struct hash and the final digest.
39
+ - EIP-55 is checked against the addresses in that specification.
40
+ - Keccak-256, in `@crewhaus/tool-encode`, is checked against the standard
41
+ vectors. It is **not** SHA3-256: same permutation, different padding, and
42
+ substituting one gives a plausible digest that is wrong for every Ethereum
43
+ purpose. WebCrypto offers neither, which is exactly the trap.
44
+
45
+ ## The mistakes it exists to prevent
46
+
47
+ - **A misplaced offset.** Dynamic values move to a tail and leave an offset
48
+ behind, relative to the start of the enclosing tuple — not the call. A
49
+ wrong offset produces calldata a node accepts and a contract misreads.
50
+ - **Alignment.** Fixed bytes are left-aligned, integers right-aligned.
51
+ `bytes4` and `uint32` holding "the same" value are different words.
52
+ - **A canonical signature that differs from what was typed.** `uint` is
53
+ `uint256` in the string the selector is hashed over, so writing `uint` and
54
+ hashing it literally gives a selector no contract answers to.
55
+ - **Precision.** A `uint256` does not fit in a double; a balance read as a
56
+ number loses its low digits, and two balances one wei apart become the same
57
+ value. Integers are `bigint` or decimal strings throughout, and a number
58
+ past the safe range is refused rather than accepted after it has already
59
+ lost precision.
60
+ - **`0.1 * 1e18`.** `TokenUnits` is string arithmetic, and it refuses an
61
+ amount with more decimal places than the token has rather than dropping the
62
+ extra digits.
63
+ - **The direction of a rounding error in a bound.** A minimum is rounded
64
+ down and a maximum up; the other way round rejects a swap that was inside
65
+ tolerance or accepts one that was not. Slippage is basis points, never a
66
+ percentage, because a percentage invites a decimal and a decimal invites a
67
+ float.
68
+
69
+ An address with no checksum is reported as valid **and** as unverifiable,
70
+ rather than letting `valid: true` be read as "no typo". A position with no
71
+ debt has no health factor rather than an infinite one — `Infinity` would read
72
+ as safe to a caller comparing against a threshold.
73
+
74
+ ## Multicall3, as library code
75
+
76
+ `encodeAggregate3`, `decodeAggregate3` and `decodeRevertData` are exported
77
+ from the package but register no tool: packing a batch is only useful to
78
+ something that can dial a chain, and nothing here dials anything. They pack
79
+ `aggregate3((address,bool,bytes)[])` and read `(bool,bytes)[]` back.
80
+
81
+ Two properties are the reason they exist:
82
+
83
+ - **A failed sub-call is data.** With `allowFailure` — which defaults to true
84
+ — the batch succeeds and the failed entry comes back with `success: false`
85
+ and its revert bytes. A batch whose own answer cannot be read throws
86
+ instead, because that is a different event: there are no partial results.
87
+ - **Results are matched by position**, and nothing in the return blob names
88
+ the call it answers. A blob holding a different number of results than the
89
+ batch sent is refused rather than zipped against the calls, since a balance
90
+ read against the wrong token is worse than no balance.
91
+
92
+ Revert bytes are decoded where they say something: `Error(string)` gives its
93
+ reason, `Panic(uint256)` the documented meaning of its code. An unrecognised
94
+ selector keeps its hex and gets no message — a guess at a custom error is
95
+ worse than the bytes, which are at least searchable.
96
+
97
+ The canonical Multicall3 address is a **default**, not a constant: the
98
+ deterministic deploy is at the same address on most chains, which is not the
99
+ same as all of them, so every entry point takes it as an argument.
100
+
101
+ ## What it does not do
102
+
103
+ - **It does not reach a chain.** No RPC calls, no balances, no gas estimates,
104
+ no broadcasting. Those need a node and belong elsewhere.
105
+ - **It does not sign or hold keys.**
106
+ - **It does not know what a contract does.** `AbiDecode` decodes the types
107
+ you name; naming the wrong ones produces confident nonsense, which is why
108
+ data that ends early is an error rather than a short answer.
109
+ - **It does not fetch an ABI.** Signatures come from the caller.
110
+ - **It does not price anything.** `DefiMath` computes against a reference you
111
+ supply; where that reference came from is your problem.
@@ -0,0 +1,16 @@
1
+ import type { RegisteredTool } from "@crewhaus/tool-catalog";
2
+ /**
3
+ * Multicall3 packing, as library code for other packages to build on. It
4
+ * registers no tool of its own: a batch read is only useful to something that
5
+ * can dial a chain, and nothing in this package dials anything.
6
+ */
7
+ export { AGGREGATE3_RETURN_TYPE, AGGREGATE3_SELECTOR, AGGREGATE3_SIGNATURE, type Aggregate3Request, type Aggregate3Result, type Call3, ERROR_STRING_SELECTOR, MULTICALL3_ADDRESS, PANIC_REASONS, PANIC_SELECTOR, type RevertKind, type RevertReason, decodeAggregate3, decodeRevertData, encodeAggregate3, } from "./lib/multicall";
8
+ export declare const abiEncodeCall: RegisteredTool;
9
+ export declare const abiDecode: RegisteredTool;
10
+ export declare const functionSelector: RegisteredTool;
11
+ export declare const addressCheck: RegisteredTool;
12
+ export declare const typedDataHash: RegisteredTool;
13
+ export declare const tokenUnits: RegisteredTool;
14
+ export declare const defiMath: RegisteredTool;
15
+ /** Every tool this package registers, in the order a catalog should list them. */
16
+ export declare const ONCHAIN_TOOLS: ReadonlyArray<RegisteredTool>;
package/dist/index.js ADDED
@@ -0,0 +1,305 @@
1
+ /**
2
+ * @crewhaus/tool-onchain — the onchain arithmetic, offline.
3
+ *
4
+ * Everything here is pure: no RPC, no network, no keys. These are the
5
+ * calculations that must be right before a transaction is composed, and that
6
+ * a model will sometimes get plausibly wrong — calldata with a misplaced
7
+ * offset, an address with a typo no checksum caught, a `minOut` computed in
8
+ * floating point, an EIP-712 digest over the wrong type string.
9
+ *
10
+ * **Nothing here signs anything.** No private key is accepted, read or
11
+ * handled. `TypedDataHash` computes the digest a wallet would sign, which is
12
+ * what lets a signature request be CHECKED before it is approved: hash the
13
+ * message a human was actually shown and compare it against what the dapp
14
+ * asked for.
15
+ *
16
+ * Integers are `bigint` or decimal strings. A uint256 does not fit in a
17
+ * double, and reading a balance as a number silently loses its low bits.
18
+ */
19
+ import { buildTool } from "@crewhaus/tool-builder";
20
+ import { keccak256Hex } from "@crewhaus/tool-encode";
21
+ import { z } from "zod";
22
+ import { decodeData, encodeCall, parseSignature, selectorOf } from "./lib/abi";
23
+ import { formatUnits, parseUnits, validateAddress } from "./lib/address";
24
+ import { healthFactorBps, maximumIn, minimumOut, priceImpactBps, rescaleDecimals, shareBps, } from "./lib/defi";
25
+ import { personalSignHash, typedDataDigest } from "./lib/typed";
26
+ /**
27
+ * Multicall3 packing, as library code for other packages to build on. It
28
+ * registers no tool of its own: a batch read is only useful to something that
29
+ * can dial a chain, and nothing in this package dials anything.
30
+ */
31
+ export { AGGREGATE3_RETURN_TYPE, AGGREGATE3_SELECTOR, AGGREGATE3_SIGNATURE, ERROR_STRING_SELECTOR, MULTICALL3_ADDRESS, PANIC_REASONS, PANIC_SELECTOR, decodeAggregate3, decodeRevertData, encodeAggregate3, } from "./lib/multicall";
32
+ const json = (value) => JSON.stringify(value);
33
+ const LIMITS = { args: 256, types: 64, hexChars: 2_000_000 };
34
+ /** Accepts a bigint-safe integer: a decimal or hex string, or a safe number. */
35
+ const integerish = z.union([z.string(), z.number().int(), z.bigint()]);
36
+ const bigintFrom = (value, what) => {
37
+ if (typeof value === "bigint")
38
+ return value;
39
+ if (typeof value === "number") {
40
+ if (!Number.isSafeInteger(value)) {
41
+ throw new Error(`${what}: ${value} is past the safe integer range, so it has already lost precision — pass it as a string`);
42
+ }
43
+ return BigInt(value);
44
+ }
45
+ const text = value.trim();
46
+ if (!/^-?(0x[0-9a-fA-F]+|\d+)$/.test(text))
47
+ throw new Error(`${what}: "${value}" is not an integer`);
48
+ return BigInt(text);
49
+ };
50
+ // ---------------------------------------------------------------------------
51
+ export const abiEncodeCall = buildTool({
52
+ name: "AbiEncodeCall",
53
+ description: "Build the calldata for a contract call from a function signature and its arguments, returning the 0x hex and the four-byte selector. Use it instead of having a model assemble words by hand: every value is padded into a 32-byte slot, dynamic values move to a tail and leave an offset behind, and a wrong offset produces calldata a node accepts and a contract misreads. Handles uint/int of any width, address, bool, fixed and dynamic bytes, string, arrays and tuples. It composes calldata and never sends it.",
54
+ inputSchema: z.object({
55
+ signature: z.string().min(3).describe("e.g. transfer(address,uint256)"),
56
+ args: z
57
+ .array(z.unknown())
58
+ .max(LIMITS.args)
59
+ .describe("one per parameter; integers as strings or numbers, bytes as 0x hex"),
60
+ }),
61
+ readOnly: true,
62
+ concurrencySafe: true,
63
+ execute: async (input) => {
64
+ const { name, types } = parseSignature(input.signature);
65
+ const canonical = `${name}(${types.map((t) => t.canonical).join(",")})`;
66
+ const data = encodeCall(input.signature, input.args);
67
+ return json({
68
+ data,
69
+ selector: `0x${selectorOf(canonical)}`,
70
+ // The canonical form is what the selector is hashed over, and it can
71
+ // differ from what was written: `uint` is `uint256` here.
72
+ canonicalSignature: canonical,
73
+ argumentTypes: types.map((t) => t.canonical),
74
+ bytes: (data.length - 2) / 2,
75
+ });
76
+ },
77
+ });
78
+ export const abiDecode = buildTool({
79
+ name: "AbiDecode",
80
+ description: "Decode ABI-encoded hex — an eth_call result, a log's data, a transaction's arguments, revert data — into named, typed values. Use it to read a contract's answer without a model squinting at 32-byte words. Integers come back as decimal STRINGS so a uint256 survives JSON intact; addresses come back lowercase and hex-prefixed. Data that ends early is an error rather than a plausible short answer.",
81
+ inputSchema: z.object({
82
+ data: z.string().max(LIMITS.hexChars).describe("0x hex, without a function selector"),
83
+ types: z.array(z.string()).min(1).max(LIMITS.types).describe('e.g. ["uint256", "address"]'),
84
+ names: z.array(z.string()).max(LIMITS.types).optional().describe("labels for the values"),
85
+ }),
86
+ readOnly: true,
87
+ concurrencySafe: true,
88
+ execute: async (input) => {
89
+ const values = decodeData(input.types, input.data);
90
+ const names = input.names ?? [];
91
+ return json({
92
+ values,
93
+ named: names.length === 0
94
+ ? undefined
95
+ : Object.fromEntries(values.map((v, i) => [names[i] ?? `arg${i}`, v])),
96
+ types: input.types,
97
+ });
98
+ },
99
+ });
100
+ export const functionSelector = buildTool({
101
+ name: "FunctionSelector",
102
+ description: "Compute the four-byte selector of a function signature, or the 32-byte topic of an event signature, with Keccak-256. Use it to identify what a transaction calls, to build a log filter, or to check that a selector in a block explorer is what you think. It canonicalises the signature first — `uint` becomes `uint256`, which changes the answer — so it agrees with what a compiler produces rather than with what was typed.",
103
+ inputSchema: z.object({
104
+ signature: z
105
+ .string()
106
+ .min(3)
107
+ .describe("e.g. transfer(address,uint256) or Transfer(address,address,uint256)"),
108
+ kind: z
109
+ .enum(["function", "event"])
110
+ .optional()
111
+ .describe("function (4 bytes, default) or event (32)"),
112
+ }),
113
+ readOnly: true,
114
+ concurrencySafe: true,
115
+ execute: async (input) => {
116
+ const { name, types } = parseSignature(input.signature);
117
+ const canonical = `${name}(${types.map((t) => t.canonical).join(",")})`;
118
+ const full = keccak256Hex(canonical);
119
+ return json({
120
+ canonicalSignature: canonical,
121
+ selector: `0x${full.slice(0, 8)}`,
122
+ topic: `0x${full}`,
123
+ value: input.kind === "event" ? `0x${full}` : `0x${full.slice(0, 8)}`,
124
+ canonicalised: canonical !== input.signature.trim(),
125
+ });
126
+ },
127
+ });
128
+ export const addressCheck = buildTool({
129
+ name: "AddressCheck",
130
+ description: "Validate an Ethereum address and return its EIP-55 checksummed form. Use it before composing any transfer: an address is 20 raw bytes with no check digits of its own, and EIP-55 adds a checksum by varying the case of the hex — so a mixed-case address can be verified and a typo caught, while an all-lowercase one cannot be. The result says which of those happened rather than letting a bare 'valid' be read as 'verified', and flags the zero address.",
131
+ inputSchema: z.object({
132
+ address: z.string().min(1),
133
+ }),
134
+ readOnly: true,
135
+ concurrencySafe: true,
136
+ execute: async (input) => json(validateAddress(input.address)),
137
+ });
138
+ export const typedDataHash = buildTool({
139
+ name: "TypedDataHash",
140
+ description: "Compute the EIP-712 digest a wallet would sign, together with the domain separator, the struct hash and the encoded type string. Use it to CHECK a signature request before approving it: hash the message a human was actually shown and compare it against what the dapp asked for. It also computes the EIP-191 personal_sign hash. It computes digests and never signs — no private key is accepted anywhere in this package.",
141
+ inputSchema: z
142
+ .object({
143
+ domain: z
144
+ .object({
145
+ name: z.string().optional(),
146
+ version: z.string().optional(),
147
+ chainId: integerish.optional(),
148
+ verifyingContract: z.string().optional(),
149
+ salt: z.string().optional(),
150
+ })
151
+ .optional(),
152
+ types: z
153
+ .record(z.array(z.object({ name: z.string(), type: z.string() })))
154
+ .optional()
155
+ .describe("the EIP-712 type definitions; EIP712Domain is ignored if present"),
156
+ primaryType: z.string().optional(),
157
+ message: z.record(z.unknown()).optional(),
158
+ /** The EIP-191 path, which needs none of the above. */
159
+ personalSignMessage: z
160
+ .string()
161
+ .optional()
162
+ .describe("hash this as a personal_sign message instead"),
163
+ })
164
+ .refine((v) => v.personalSignMessage !== undefined ||
165
+ (v.domain !== undefined &&
166
+ v.types !== undefined &&
167
+ v.primaryType !== undefined &&
168
+ v.message !== undefined), {
169
+ message: "give either personalSignMessage, or all of domain, types, primaryType and message",
170
+ }),
171
+ readOnly: true,
172
+ concurrencySafe: true,
173
+ execute: async (input) => {
174
+ if (input.personalSignMessage !== undefined) {
175
+ return json({
176
+ scheme: "eip191",
177
+ digest: personalSignHash(input.personalSignMessage),
178
+ messageBytes: new TextEncoder().encode(input.personalSignMessage).length,
179
+ });
180
+ }
181
+ return json({
182
+ scheme: "eip712",
183
+ ...typedDataDigest(input.domain, input.types, input.primaryType, input.message),
184
+ note: "this is the digest a wallet would sign; nothing here signs it",
185
+ });
186
+ },
187
+ });
188
+ export const tokenUnits = buildTool({
189
+ name: "TokenUnits",
190
+ description: "Convert between a decimal amount and a token's base units, exactly, in either direction. This is token decimals, not physical units — `UnitConvert` in @crewhaus/tool-math converts metres and kilograms. Use it for every amount that goes into a transaction: `0.1 * 1e18` in floating point is not 10^17, and the difference is real money. It refuses an amount with more decimal places than the token has rather than dropping the extra digits, and it converts between two different decimalisations — six-decimal USDC and eighteen-decimal DAI hold the same amount as integers a trillion apart.",
191
+ inputSchema: z
192
+ .object({
193
+ amount: z.string().min(1).describe("a decimal amount, or base units when toDecimal is set"),
194
+ decimals: z
195
+ .number()
196
+ .int()
197
+ .min(0)
198
+ .max(77)
199
+ .describe("the token's decimals; 18 for ether, 6 for USDC"),
200
+ toDecimal: z
201
+ .boolean()
202
+ .optional()
203
+ .describe("treat `amount` as base units and render it as a decimal"),
204
+ toDecimals: z
205
+ .number()
206
+ .int()
207
+ .min(0)
208
+ .max(77)
209
+ .optional()
210
+ .describe("rescale base units to another token's decimals"),
211
+ })
212
+ .strict(),
213
+ readOnly: true,
214
+ concurrencySafe: true,
215
+ execute: async (input) => {
216
+ if (input.toDecimals !== undefined) {
217
+ const base = bigintFrom(input.amount, "amount");
218
+ return json({
219
+ baseUnits: rescaleDecimals(base, input.decimals, input.toDecimals),
220
+ fromDecimals: input.decimals,
221
+ toDecimals: input.toDecimals,
222
+ });
223
+ }
224
+ if (input.toDecimal) {
225
+ const base = bigintFrom(input.amount, "amount");
226
+ return json({
227
+ decimal: formatUnits(base, input.decimals),
228
+ baseUnits: base.toString(),
229
+ decimals: input.decimals,
230
+ });
231
+ }
232
+ const base = parseUnits(input.amount, input.decimals);
233
+ return json({ baseUnits: base.toString(), decimal: input.amount, decimals: input.decimals });
234
+ },
235
+ });
236
+ export const defiMath = buildTool({
237
+ name: "DefiMath",
238
+ description: "Do the fixed-point arithmetic a swap or a lending position needs: the minimum output for a slippage tolerance, the maximum input for an exact-output swap, price impact against a reference mid, a pool share, and a health factor. Use it because these are integer calculations in base units where the DIRECTION of a rounding error matters — a minimum is rounded down and a maximum up, since rounding a bound the wrong way rejects a swap that was inside tolerance or accepts one that was not. Slippage is basis points, never a percentage, because a percentage invites a decimal and a decimal invites a float.",
239
+ inputSchema: z.discriminatedUnion("operation", [
240
+ z.object({
241
+ operation: z.literal("minimumOut"),
242
+ quotedOut: integerish.describe("base units the quote promises"),
243
+ slippageBps: z.number().int().min(0).max(10_000),
244
+ }),
245
+ z.object({
246
+ operation: z.literal("maximumIn"),
247
+ quotedIn: integerish,
248
+ slippageBps: z.number().int().min(0).max(10_000),
249
+ }),
250
+ z.object({
251
+ operation: z.literal("priceImpact"),
252
+ executionPrice: integerish,
253
+ referencePrice: integerish,
254
+ }),
255
+ z.object({ operation: z.literal("share"), part: integerish, total: integerish }),
256
+ z.object({
257
+ operation: z.literal("healthFactor"),
258
+ collateralBase: integerish,
259
+ debtBase: integerish,
260
+ liquidationThresholdBps: z.number().int().min(0).max(10_000),
261
+ }),
262
+ ]),
263
+ readOnly: true,
264
+ concurrencySafe: true,
265
+ execute: async (input) => {
266
+ switch (input.operation) {
267
+ case "minimumOut":
268
+ return json(minimumOut(bigintFrom(input.quotedOut, "quotedOut"), input.slippageBps));
269
+ case "maximumIn":
270
+ return json({
271
+ quotedIn: bigintFrom(input.quotedIn, "quotedIn").toString(),
272
+ slippageBps: input.slippageBps,
273
+ maxIn: maximumIn(bigintFrom(input.quotedIn, "quotedIn"), input.slippageBps),
274
+ });
275
+ case "priceImpact":
276
+ return json({
277
+ impactBps: priceImpactBps(bigintFrom(input.executionPrice, "executionPrice"), bigintFrom(input.referencePrice, "referencePrice")),
278
+ });
279
+ case "share":
280
+ return json({
281
+ shareBps: shareBps(bigintFrom(input.part, "part"), bigintFrom(input.total, "total")),
282
+ });
283
+ default: {
284
+ const health = healthFactorBps(bigintFrom(input.collateralBase, "collateralBase"), bigintFrom(input.debtBase, "debtBase"), input.liquidationThresholdBps);
285
+ return json({
286
+ healthFactorBps: health,
287
+ liquidatable: health === null ? false : health < 10_000,
288
+ // No debt has no health factor. Reporting Infinity would be read as
289
+ // safe by a caller comparing against a threshold, and NaN as unsafe.
290
+ note: health === null ? "no debt, so there is no health factor" : "",
291
+ });
292
+ }
293
+ }
294
+ },
295
+ });
296
+ /** Every tool this package registers, in the order a catalog should list them. */
297
+ export const ONCHAIN_TOOLS = Object.freeze([
298
+ abiDecode,
299
+ abiEncodeCall,
300
+ addressCheck,
301
+ defiMath,
302
+ functionSelector,
303
+ typedDataHash,
304
+ tokenUnits,
305
+ ]);
@@ -0,0 +1,29 @@
1
+ export type AbiValue = string | bigint | boolean | number | AbiValue[];
2
+ /** A parsed ABI type. Tuples carry their components. */
3
+ export type AbiType = {
4
+ readonly base: string;
5
+ readonly bits: number;
6
+ /** -1 for a dynamic array, a length for a fixed one, null when not an array. */
7
+ readonly arrayLength: number | null;
8
+ readonly child: AbiType | null;
9
+ readonly components: ReadonlyArray<AbiType>;
10
+ readonly dynamic: boolean;
11
+ /** Canonical form, which is what the selector is computed over. */
12
+ readonly canonical: string;
13
+ };
14
+ export declare function parseType(raw: string): AbiType;
15
+ /** Head/tail encoding for a list of types, which is what a tuple is. */
16
+ export declare function encodeTuple(types: ReadonlyArray<AbiType>, values: ReadonlyArray<AbiValue>, what?: string): Uint8Array;
17
+ /** The canonical signature a selector is hashed over. */
18
+ export declare function canonicalSignature(name: string, types: ReadonlyArray<AbiType>): string;
19
+ export declare function selectorOf(signature: string): string;
20
+ /** Split `transfer(address,uint256)` into its name and parsed argument types. */
21
+ export declare function parseSignature(signature: string): {
22
+ name: string;
23
+ types: AbiType[];
24
+ };
25
+ export declare function encodeCall(signature: string, args: ReadonlyArray<AbiValue>): string;
26
+ /** Decoded values use strings for integers, so a uint256 survives JSON. */
27
+ export type Decoded = string | boolean | Decoded[];
28
+ export declare function decodeTuple(types: ReadonlyArray<AbiType>, data: Uint8Array, base?: number, what?: string): Decoded[];
29
+ export declare function decodeData(typeList: ReadonlyArray<string>, hex: string): Decoded[];