@crewhaus/tool-token 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 +172 -0
- package/dist/fixtures.d.ts +117 -0
- package/dist/fixtures.js +222 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1045 -0
- package/dist/lib/chain.d.ts +127 -0
- package/dist/lib/chain.js +202 -0
- package/dist/lib/erc.d.ts +122 -0
- package/dist/lib/erc.js +238 -0
- package/dist/lib/lists.d.ts +78 -0
- package/dist/lib/lists.js +290 -0
- package/dist/lib/onchain.d.ts +26 -0
- package/dist/lib/onchain.js +48 -0
- package/dist/lib/uri.d.ts +66 -0
- package/dist/lib/uri.js +203 -0
- package/package.json +48 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one way this package learns anything from a chain — and the reason it
|
|
3
|
+
* is the only way.
|
|
4
|
+
*
|
|
5
|
+
* **There is no dialling code in `@crewhaus/tool-token`.** No URL, no fetch,
|
|
6
|
+
* no socket. Every chain read leaves through `_setChainReader`, which the
|
|
7
|
+
* runtime binds at boot and every test in this package drives from recorded
|
|
8
|
+
* answers. Two things follow, and both are the point:
|
|
9
|
+
*
|
|
10
|
+
* 1. A test cannot reach a public RPC endpoint even by accident. There is
|
|
11
|
+
* nothing under the seam to reach it with. A suite that dialled a real
|
|
12
|
+
* node would fail on a CI runner with no egress and flake on somebody
|
|
13
|
+
* else's rate limit.
|
|
14
|
+
* 2. The set of JSON-RPC methods this package can emit is three, they are
|
|
15
|
+
* listed below, and {@link assertReadMethod} refuses anything else
|
|
16
|
+
* before it reaches the reader. `eth_sendTransaction` and
|
|
17
|
+
* `eth_sendRawTransaction` are not reachable from any code path here;
|
|
18
|
+
* `index.test.ts` asserts it.
|
|
19
|
+
*
|
|
20
|
+
* Reads are batched through Multicall3 by default, because a hundred balances
|
|
21
|
+
* read one at a time are a hundred balances from up to a hundred different
|
|
22
|
+
* blocks. `getBlockNumber()` rides along in the same batch so the answer says
|
|
23
|
+
* which block it is a snapshot of.
|
|
24
|
+
*/
|
|
25
|
+
import type { ChainAdapter } from "@crewhaus/chain-adapter-base";
|
|
26
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
27
|
+
import { type RevertReason } from "@crewhaus/tool-onchain";
|
|
28
|
+
/** A refusal this package makes: a bad input, a missing seam, an unreadable answer. */
|
|
29
|
+
export declare class TokenError extends CrewhausError {
|
|
30
|
+
readonly name = "TokenError";
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Every JSON-RPC method this package is allowed to emit. All three are reads
|
|
35
|
+
* of public state. Nothing on this list can move a token, and nothing that
|
|
36
|
+
* could is reachable from here.
|
|
37
|
+
*/
|
|
38
|
+
export declare const READ_METHODS: readonly ["eth_call", "eth_getBalance", "eth_getCode"];
|
|
39
|
+
export type ChainReadMethod = (typeof READ_METHODS)[number];
|
|
40
|
+
export type ChainRead = {
|
|
41
|
+
readonly chainId: number;
|
|
42
|
+
readonly method: ChainReadMethod;
|
|
43
|
+
readonly params: ReadonlyArray<unknown>;
|
|
44
|
+
};
|
|
45
|
+
/** What the runtime binds: one function, one read, one answer. */
|
|
46
|
+
export type ChainReader = (read: ChainRead) => Promise<unknown>;
|
|
47
|
+
/**
|
|
48
|
+
* Bind the chain reader. The runtime calls this at boot; tests call it with a
|
|
49
|
+
* stub over recorded answers. `undefined` restores the unbound state, and a
|
|
50
|
+
* suite that installs a stub must restore it or the next file in the same bun
|
|
51
|
+
* process inherits it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function _setChainReader(fn: ChainReader | undefined): void;
|
|
54
|
+
/** True when something is bound — the tools report this rather than failing late. */
|
|
55
|
+
export declare function hasChainReader(): boolean;
|
|
56
|
+
export declare function assertReadMethod(method: string): asserts method is ChainReadMethod;
|
|
57
|
+
/**
|
|
58
|
+
* Wire the seam to `chain-adapter-base` adapters, which is what a compiled
|
|
59
|
+
* bundle has. The adapter applies its own read-only allowlist underneath, so
|
|
60
|
+
* a write method would be refused twice.
|
|
61
|
+
*/
|
|
62
|
+
export declare function chainReaderFromAdapters(resolve: (chainId: string) => ChainAdapter | undefined): ChainReader;
|
|
63
|
+
/** One call in a read, keyed so results are never matched up by position here. */
|
|
64
|
+
export type BatchCall = {
|
|
65
|
+
/** The caller's own label. Results come back in a map under it. */
|
|
66
|
+
readonly key: string;
|
|
67
|
+
readonly target: string;
|
|
68
|
+
readonly callData: string;
|
|
69
|
+
};
|
|
70
|
+
/** What one call answered. A revert is an outcome, not an exception. */
|
|
71
|
+
export type CallOutcome = {
|
|
72
|
+
readonly ok: boolean;
|
|
73
|
+
/** Return data on success, revert data on failure. */
|
|
74
|
+
readonly data: string;
|
|
75
|
+
readonly revert: RevertReason | null;
|
|
76
|
+
/**
|
|
77
|
+
* What the node said when it refused the call outright, on the unbatched
|
|
78
|
+
* path. Null on the batched path, where a revert arrives as data and
|
|
79
|
+
* `revert` carries it.
|
|
80
|
+
*/
|
|
81
|
+
readonly error: string | null;
|
|
82
|
+
};
|
|
83
|
+
export type BatchResult = {
|
|
84
|
+
readonly outcomes: ReadonlyMap<string, CallOutcome>;
|
|
85
|
+
/** The block the batch was read at, when Multicall3 was asked. */
|
|
86
|
+
readonly blockNumber: string | null;
|
|
87
|
+
readonly batched: boolean;
|
|
88
|
+
readonly callCount: number;
|
|
89
|
+
};
|
|
90
|
+
export declare function checkBlockTag(tag: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Run a set of calls, batched through Multicall3 when asked.
|
|
93
|
+
*
|
|
94
|
+
* Batched is the default because it is the only way the answers are
|
|
95
|
+
* consistent with each other: one `eth_call` evaluates every sub-call at one
|
|
96
|
+
* block, so a balance and the decimals it is scaled by cannot come from
|
|
97
|
+
* either side of a rebase. The unbatched path exists for chains where
|
|
98
|
+
* Multicall3 is not deployed, and it says so in the result rather than
|
|
99
|
+
* quietly producing a snapshot that is not one.
|
|
100
|
+
*
|
|
101
|
+
* A sub-call that reverts comes back as an outcome with `ok: false`. The
|
|
102
|
+
* BATCH failing is a different event — a Multicall3 address with no contract
|
|
103
|
+
* at it, a node that answered something else — and that throws, because
|
|
104
|
+
* nothing in the answer can be trusted to be about the calls that were sent.
|
|
105
|
+
*/
|
|
106
|
+
export declare function readCalls(opts: {
|
|
107
|
+
readonly chainId: number;
|
|
108
|
+
readonly calls: ReadonlyArray<BatchCall>;
|
|
109
|
+
readonly blockTag: string;
|
|
110
|
+
readonly batch: boolean;
|
|
111
|
+
readonly multicall3Address?: string;
|
|
112
|
+
}): Promise<BatchResult>;
|
|
113
|
+
/** The native balance, which is not a contract call and so is never in the batch. */
|
|
114
|
+
export declare function readNativeBalance(chainId: number, address: string, blockTag: string): Promise<bigint>;
|
|
115
|
+
/** The native balance inside the batch, via Multicall3's own `getEthBalance`. */
|
|
116
|
+
export declare function nativeBalanceCall(key: string, account: string, multicall: string): BatchCall;
|
|
117
|
+
/**
|
|
118
|
+
* Whether there is code at an address.
|
|
119
|
+
*
|
|
120
|
+
* An address with no code is not a token — it is somebody's wallet, or a
|
|
121
|
+
* typo. Every `eth_call` against it answers `0x`, which a decoder reads as
|
|
122
|
+
* "this token has no symbol" rather than as "this is not a token".
|
|
123
|
+
*/
|
|
124
|
+
export declare function readHasCode(chainId: number, address: string, blockTag: string): Promise<{
|
|
125
|
+
hasCode: boolean;
|
|
126
|
+
codeSize: number;
|
|
127
|
+
}>;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
2
|
+
import { MULTICALL3_ADDRESS, decodeAggregate3, encodeAggregate3, } from "@crewhaus/tool-onchain";
|
|
3
|
+
import { SELECTOR, addressWord, callData, decodeUint } from "./erc";
|
|
4
|
+
/** A refusal this package makes: a bad input, a missing seam, an unreadable answer. */
|
|
5
|
+
export class TokenError extends CrewhausError {
|
|
6
|
+
name = "TokenError";
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super("tool", message);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Every JSON-RPC method this package is allowed to emit. All three are reads
|
|
13
|
+
* of public state. Nothing on this list can move a token, and nothing that
|
|
14
|
+
* could is reachable from here.
|
|
15
|
+
*/
|
|
16
|
+
export const READ_METHODS = Object.freeze(["eth_call", "eth_getBalance", "eth_getCode"]);
|
|
17
|
+
const unbound = async (read) => {
|
|
18
|
+
throw new TokenError(`no chain reader is bound, so chain ${read.chainId} cannot be read. This package contains no RPC client of its own: the runtime binds one with _setChainReader(), and chainReaderFromAdapters() wires it from the spec's chains[] block in one line.`);
|
|
19
|
+
};
|
|
20
|
+
let reader = unbound;
|
|
21
|
+
/**
|
|
22
|
+
* Bind the chain reader. The runtime calls this at boot; tests call it with a
|
|
23
|
+
* stub over recorded answers. `undefined` restores the unbound state, and a
|
|
24
|
+
* suite that installs a stub must restore it or the next file in the same bun
|
|
25
|
+
* process inherits it.
|
|
26
|
+
*/
|
|
27
|
+
export function _setChainReader(fn) {
|
|
28
|
+
reader = fn ?? unbound;
|
|
29
|
+
}
|
|
30
|
+
/** True when something is bound — the tools report this rather than failing late. */
|
|
31
|
+
export function hasChainReader() {
|
|
32
|
+
return reader !== unbound;
|
|
33
|
+
}
|
|
34
|
+
export function assertReadMethod(method) {
|
|
35
|
+
if (!READ_METHODS.includes(method)) {
|
|
36
|
+
throw new TokenError(`${method} is not one of the read methods this package emits (${READ_METHODS.join(", ")}). Nothing here signs or submits anything.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wire the seam to `chain-adapter-base` adapters, which is what a compiled
|
|
41
|
+
* bundle has. The adapter applies its own read-only allowlist underneath, so
|
|
42
|
+
* a write method would be refused twice.
|
|
43
|
+
*/
|
|
44
|
+
export function chainReaderFromAdapters(resolve) {
|
|
45
|
+
return async (read) => {
|
|
46
|
+
assertReadMethod(read.method);
|
|
47
|
+
const adapter = resolve(String(read.chainId));
|
|
48
|
+
if (adapter === undefined) {
|
|
49
|
+
throw new TokenError(`no chain adapter is registered for chain ${read.chainId}; declare it in the spec's chains[] block`);
|
|
50
|
+
}
|
|
51
|
+
return adapter.rpcRead(read.method, read.params);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async function rpc(read) {
|
|
55
|
+
assertReadMethod(read.method);
|
|
56
|
+
return reader(read);
|
|
57
|
+
}
|
|
58
|
+
function asHex(value, what) {
|
|
59
|
+
if (typeof value !== "string" || !/^0x([0-9a-fA-F]{2})*$/.test(value)) {
|
|
60
|
+
throw new TokenError(`${what}: expected 0x-prefixed hex from the node, got ${JSON.stringify(value)?.slice(0, 120) ?? "undefined"}`);
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
const BLOCK_TAG = /^(latest|earliest|pending|safe|finalized|0x[0-9a-fA-F]+)$/;
|
|
65
|
+
export function checkBlockTag(tag) {
|
|
66
|
+
if (!BLOCK_TAG.test(tag)) {
|
|
67
|
+
throw new TokenError(`"${tag}" is not a block tag; use latest, safe, finalized, earliest, pending, or a 0x hex block number`);
|
|
68
|
+
}
|
|
69
|
+
return tag;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Run a set of calls, batched through Multicall3 when asked.
|
|
73
|
+
*
|
|
74
|
+
* Batched is the default because it is the only way the answers are
|
|
75
|
+
* consistent with each other: one `eth_call` evaluates every sub-call at one
|
|
76
|
+
* block, so a balance and the decimals it is scaled by cannot come from
|
|
77
|
+
* either side of a rebase. The unbatched path exists for chains where
|
|
78
|
+
* Multicall3 is not deployed, and it says so in the result rather than
|
|
79
|
+
* quietly producing a snapshot that is not one.
|
|
80
|
+
*
|
|
81
|
+
* A sub-call that reverts comes back as an outcome with `ok: false`. The
|
|
82
|
+
* BATCH failing is a different event — a Multicall3 address with no contract
|
|
83
|
+
* at it, a node that answered something else — and that throws, because
|
|
84
|
+
* nothing in the answer can be trusted to be about the calls that were sent.
|
|
85
|
+
*/
|
|
86
|
+
export async function readCalls(opts) {
|
|
87
|
+
const blockTag = checkBlockTag(opts.blockTag);
|
|
88
|
+
const outcomes = new Map();
|
|
89
|
+
if (opts.calls.length === 0) {
|
|
90
|
+
return { outcomes, blockNumber: null, batched: false, callCount: 0 };
|
|
91
|
+
}
|
|
92
|
+
if (!opts.batch) {
|
|
93
|
+
for (const call of opts.calls) {
|
|
94
|
+
try {
|
|
95
|
+
const answer = await rpc({
|
|
96
|
+
chainId: opts.chainId,
|
|
97
|
+
method: "eth_call",
|
|
98
|
+
params: [{ to: call.target, data: call.callData }, blockTag],
|
|
99
|
+
});
|
|
100
|
+
outcomes.set(call.key, {
|
|
101
|
+
ok: true,
|
|
102
|
+
data: asHex(answer, call.key),
|
|
103
|
+
revert: null,
|
|
104
|
+
error: null,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
// A refusal from this package — an unbound seam, a method that is not
|
|
109
|
+
// a read — is about the whole call, not about this one sub-call, and
|
|
110
|
+
// burying it as one of N per-call errors hides the one line that says
|
|
111
|
+
// what to fix.
|
|
112
|
+
if (err instanceof TokenError)
|
|
113
|
+
throw err;
|
|
114
|
+
// An unbatched eth_call cannot report a revert as data — the node
|
|
115
|
+
// errors instead. Recording it as this one call's failure is what
|
|
116
|
+
// keeps a token whose `decimals()` reverts from taking its balances
|
|
117
|
+
// down with it, which is the whole reason the batched path sets
|
|
118
|
+
// allowFailure.
|
|
119
|
+
outcomes.set(call.key, {
|
|
120
|
+
ok: false,
|
|
121
|
+
data: "0x",
|
|
122
|
+
revert: null,
|
|
123
|
+
error: err.message,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { outcomes, blockNumber: null, batched: false, callCount: opts.calls.length };
|
|
128
|
+
}
|
|
129
|
+
const multicall = opts.multicall3Address ?? MULTICALL3_ADDRESS;
|
|
130
|
+
const blockKey = "\u0000blockNumber";
|
|
131
|
+
const packed = [
|
|
132
|
+
{ key: blockKey, target: multicall, callData: callData(SELECTOR.getBlockNumber) },
|
|
133
|
+
...opts.calls,
|
|
134
|
+
];
|
|
135
|
+
const request = encodeAggregate3(packed.map((c) => ({ target: c.target, callData: c.callData, allowFailure: true })), multicall);
|
|
136
|
+
const answer = await rpc({
|
|
137
|
+
chainId: opts.chainId,
|
|
138
|
+
method: "eth_call",
|
|
139
|
+
params: [{ to: request.to, data: request.data }, blockTag],
|
|
140
|
+
});
|
|
141
|
+
let rows;
|
|
142
|
+
try {
|
|
143
|
+
rows = decodeAggregate3(asHex(answer, "the Multicall3 batch"), request.callCount);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
throw new TokenError(`the Multicall3 batch at ${request.to} did not answer with results: ${err.message}. If this chain has no Multicall3 deployment at that address, pass multicall3Address, or batch:false to read the calls one at a time.`);
|
|
147
|
+
}
|
|
148
|
+
for (const [i, call] of packed.entries()) {
|
|
149
|
+
const row = rows[i];
|
|
150
|
+
outcomes.set(call.key, {
|
|
151
|
+
ok: row.success,
|
|
152
|
+
data: row.returnData,
|
|
153
|
+
revert: row.revert,
|
|
154
|
+
error: null,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
const block = outcomes.get(blockKey);
|
|
158
|
+
outcomes.delete(blockKey);
|
|
159
|
+
const blockNumber = block?.ok === true ? (decodeUint(block.data)?.toString() ?? null) : null;
|
|
160
|
+
return { outcomes, blockNumber, batched: true, callCount: opts.calls.length };
|
|
161
|
+
}
|
|
162
|
+
/** The native balance, which is not a contract call and so is never in the batch. */
|
|
163
|
+
export async function readNativeBalance(chainId, address, blockTag) {
|
|
164
|
+
const answer = await rpc({
|
|
165
|
+
chainId,
|
|
166
|
+
method: "eth_getBalance",
|
|
167
|
+
params: [address, checkBlockTag(blockTag)],
|
|
168
|
+
});
|
|
169
|
+
// A quantity is at least `0x0`. Bare `0x` is what a node returns when it
|
|
170
|
+
// has nothing to say, and reading it as zero is the mistake this whole
|
|
171
|
+
// package exists to stop: an unanswered balance becoming "this account is
|
|
172
|
+
// empty", which is a sentence a gas check or a drain alarm acts on.
|
|
173
|
+
if (typeof answer !== "string" || !/^0x[0-9a-fA-F]+$/.test(answer)) {
|
|
174
|
+
throw new TokenError(`eth_getBalance answered ${JSON.stringify(answer)}, which is not a quantity — a balance is at least 0x0, and "0x" is an absent answer rather than a zero one`);
|
|
175
|
+
}
|
|
176
|
+
return BigInt(answer);
|
|
177
|
+
}
|
|
178
|
+
/** The native balance inside the batch, via Multicall3's own `getEthBalance`. */
|
|
179
|
+
export function nativeBalanceCall(key, account, multicall) {
|
|
180
|
+
return {
|
|
181
|
+
key,
|
|
182
|
+
target: multicall,
|
|
183
|
+
callData: callData(SELECTOR.getEthBalance, addressWord(account)),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Whether there is code at an address.
|
|
188
|
+
*
|
|
189
|
+
* An address with no code is not a token — it is somebody's wallet, or a
|
|
190
|
+
* typo. Every `eth_call` against it answers `0x`, which a decoder reads as
|
|
191
|
+
* "this token has no symbol" rather than as "this is not a token".
|
|
192
|
+
*/
|
|
193
|
+
export async function readHasCode(chainId, address, blockTag) {
|
|
194
|
+
const answer = await rpc({
|
|
195
|
+
chainId,
|
|
196
|
+
method: "eth_getCode",
|
|
197
|
+
params: [address, checkBlockTag(blockTag)],
|
|
198
|
+
});
|
|
199
|
+
const hex = asHex(answer, "eth_getCode");
|
|
200
|
+
const codeSize = (hex.length - 2) / 2;
|
|
201
|
+
return { hasCode: codeSize > 0, codeSize };
|
|
202
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The token-standard call surface: the selectors, the interface ids, and the
|
|
3
|
+
* reading of what comes back.
|
|
4
|
+
*
|
|
5
|
+
* This is a narrow codec, not a second copy of `tool-onchain`'s general ABI
|
|
6
|
+
* coder, and the reason is the whole point of this file. A general decoder is
|
|
7
|
+
* told what type to expect and fails when the bytes are not that type. The
|
|
8
|
+
* tokens that break harnesses are exactly the ones whose bytes are not that
|
|
9
|
+
* type: `symbol()` on MKR returns a `bytes32`, not a `string`; `decimals()`
|
|
10
|
+
* on some very old tokens reverts or is simply absent; an EOA answers every
|
|
11
|
+
* call with `0x`. Reading those needs the raw words and a documented
|
|
12
|
+
* fallback, which is a different job from "decode this as a string".
|
|
13
|
+
*
|
|
14
|
+
* Every selector and interface id below is a literal, and every one of them
|
|
15
|
+
* is derived from its signature in `lib.test.ts` through `tool-onchain`'s
|
|
16
|
+
* `FunctionSelector`. A literal that drifts fails a test rather than
|
|
17
|
+
* producing calldata a node accepts and a contract misreads.
|
|
18
|
+
*/
|
|
19
|
+
/** Four-byte selectors for the calls this package makes. */
|
|
20
|
+
export declare const SELECTOR: Readonly<{
|
|
21
|
+
/** ERC-20 `balanceOf(address)`. */
|
|
22
|
+
readonly balanceOf: "0x70a08231";
|
|
23
|
+
/** ERC-20 `allowance(address,address)`. */
|
|
24
|
+
readonly allowance: "0xdd62ed3e";
|
|
25
|
+
readonly decimals: "0x313ce567";
|
|
26
|
+
readonly symbol: "0x95d89b41";
|
|
27
|
+
readonly name: "0x06fdde03";
|
|
28
|
+
readonly totalSupply: "0x18160ddd";
|
|
29
|
+
readonly ownerOf: "0x6352211e";
|
|
30
|
+
readonly tokenURI: "0xc87b56dd";
|
|
31
|
+
/** ERC-1155 `uri(uint256)`. */
|
|
32
|
+
readonly uri: "0x0e89341c";
|
|
33
|
+
/** ERC-1155 `balanceOf(address,uint256)` — a different selector from ERC-20's. */
|
|
34
|
+
readonly balanceOf1155: "0x00fdd58e";
|
|
35
|
+
readonly supportsInterface: "0x01ffc9a7";
|
|
36
|
+
/** Multicall3's own helpers, callable inside the same batch. */
|
|
37
|
+
readonly getEthBalance: "0x4d2301cc";
|
|
38
|
+
readonly getBlockNumber: "0x42cbb15c";
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* ERC-165 interface ids. Each is the XOR of the selectors of the functions in
|
|
42
|
+
* its interface, and `lib.test.ts` recomputes each one from that function
|
|
43
|
+
* list rather than trusting the literal.
|
|
44
|
+
*/
|
|
45
|
+
export declare const INTERFACE_ID: Readonly<{
|
|
46
|
+
readonly erc165: "0x01ffc9a7";
|
|
47
|
+
readonly erc721: "0x80ac58cd";
|
|
48
|
+
readonly erc721Metadata: "0x5b5e139f";
|
|
49
|
+
readonly erc721Enumerable: "0x780e9d63";
|
|
50
|
+
readonly erc1155: "0xd9b67a26";
|
|
51
|
+
readonly erc1155MetadataUri: "0x0e89341c";
|
|
52
|
+
/**
|
|
53
|
+
* Not an interface. ERC-165 requires `supportsInterface(0xffffffff)` to
|
|
54
|
+
* answer FALSE, so a contract that says true here answers true to
|
|
55
|
+
* everything and its other answers mean nothing.
|
|
56
|
+
*/
|
|
57
|
+
readonly invalid: "0xffffffff";
|
|
58
|
+
}>;
|
|
59
|
+
/** A decoded string longer than this is not a symbol or a name. */
|
|
60
|
+
export declare const MAX_STRING_BYTES = 4096;
|
|
61
|
+
export declare function hexToBytes(hex: string, what?: string): Uint8Array;
|
|
62
|
+
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
63
|
+
/** A 32-byte word holding a right-aligned address. */
|
|
64
|
+
export declare function addressWord(address: string): string;
|
|
65
|
+
/** A 32-byte word holding a right-aligned unsigned integer. */
|
|
66
|
+
export declare function uintWord(value: bigint): string;
|
|
67
|
+
/**
|
|
68
|
+
* A 32-byte word holding a LEFT-aligned `bytes4`.
|
|
69
|
+
*
|
|
70
|
+
* Fixed bytes are left-aligned and integers right-aligned. Getting this
|
|
71
|
+
* backwards makes every `supportsInterface` answer false, which reads as "not
|
|
72
|
+
* an NFT" rather than as a bug.
|
|
73
|
+
*/
|
|
74
|
+
export declare function bytes4Word(id: string): string;
|
|
75
|
+
/** Assemble calldata from a selector and pre-padded words. */
|
|
76
|
+
export declare function callData(selector: string, ...words: ReadonlyArray<string>): string;
|
|
77
|
+
/** Read one 32-byte word as an unsigned integer, or null when it is not there. */
|
|
78
|
+
export declare function decodeUint(data: string): bigint | null;
|
|
79
|
+
/**
|
|
80
|
+
* Read one word as an address.
|
|
81
|
+
*
|
|
82
|
+
* The top 12 bytes must be zero. A word with anything up there is not an
|
|
83
|
+
* address the caller can use, and masking it off would turn a contract's
|
|
84
|
+
* garbage into a plausible destination.
|
|
85
|
+
*/
|
|
86
|
+
export declare function decodeAddressValue(data: string): string | null;
|
|
87
|
+
/** Read one word as a bool. Anything other than 0 or 1 is not a bool. */
|
|
88
|
+
export declare function decodeBool(data: string): boolean | null;
|
|
89
|
+
/** How a text field was actually encoded on the wire. */
|
|
90
|
+
export type StringEncoding = "string" | "bytes32" | "absent" | "undecodable";
|
|
91
|
+
export type StringRead = {
|
|
92
|
+
readonly value: string | null;
|
|
93
|
+
readonly encoding: StringEncoding;
|
|
94
|
+
/** Why it came out this way. Empty when the answer was an ordinary string. */
|
|
95
|
+
readonly note: string;
|
|
96
|
+
/** The raw bytes, always, so an unreadable answer is still actionable. */
|
|
97
|
+
readonly raw: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Read `symbol()` or `name()` from whatever the token actually returned.
|
|
101
|
+
*
|
|
102
|
+
* Three shapes are real and all three are in production:
|
|
103
|
+
*
|
|
104
|
+
* - a normal dynamic `string` (offset, length, bytes);
|
|
105
|
+
* - exactly one word, which is the `bytes32` form MKR and several other
|
|
106
|
+
* 2017-era tokens use — the ABI was not settled when they deployed;
|
|
107
|
+
* - nothing at all, because the function is absent or reverted.
|
|
108
|
+
*
|
|
109
|
+
* The third is reported as `absent`, never as an empty name. "" and "we could
|
|
110
|
+
* not read it" are different facts and a caller displaying the first as the
|
|
111
|
+
* second shows a blank where a warning belongs.
|
|
112
|
+
*/
|
|
113
|
+
export declare function decodeStringish(data: string): StringRead;
|
|
114
|
+
/**
|
|
115
|
+
* The 64-hex-digit, zero-padded, LOWERCASE form of a token id, for the `{id}`
|
|
116
|
+
* placeholder in an ERC-1155 URI.
|
|
117
|
+
*
|
|
118
|
+
* The spec is explicit and almost everyone gets it wrong: it is not the
|
|
119
|
+
* decimal id and not `0x`-prefixed. A gateway handed `{id}` replaced with "1"
|
|
120
|
+
* returns a 404, or worse, somebody else's metadata.
|
|
121
|
+
*/
|
|
122
|
+
export declare function padTokenIdHex(tokenId: bigint): string;
|