@kwespay/widget 1.0.3 → 1.0.5
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/dist/esm/index-338l55OY.js +3040 -0
- package/dist/{kwespay-widget.esm.js → esm/index.js} +9 -3045
- package/dist/index.d.ts +19 -0
- package/dist/kwespay-widget.js +85679 -0
- package/package.json +3 -3
|
@@ -0,0 +1,3040 @@
|
|
|
1
|
+
class KwesPayError extends Error {
|
|
2
|
+
constructor(message, code, cause) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "KwesPayError";
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.cause = cause;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ENDPOINT = "https://d502-154-161-98-26.ngrok-free.app/graphql";
|
|
11
|
+
const TESTNET_CONTRACTS = {
|
|
12
|
+
sepolia: "0x39bE436D6A34d0990cb71c9cBD24a5361d85e00B",
|
|
13
|
+
baseSepolia: "0x7515b1b1BcA33E7a9ccBd5E2b93771884654De77",
|
|
14
|
+
polygonAmoy: "0xD31dF3eBd220Fd3e190A346F8927819295d28980",
|
|
15
|
+
liskTestnet: "0xd04A78a998146EBAD04c2b68E020C06Dc3b3717f",
|
|
16
|
+
};
|
|
17
|
+
const MAINNET_CONTRACTS = {
|
|
18
|
+
// lisk: "0x...",
|
|
19
|
+
};
|
|
20
|
+
const CONTRACT_ADDRESSES = {
|
|
21
|
+
...TESTNET_CONTRACTS,
|
|
22
|
+
...MAINNET_CONTRACTS,
|
|
23
|
+
};
|
|
24
|
+
function resolveContractAddress(network) {
|
|
25
|
+
const addr = CONTRACT_ADDRESSES[network];
|
|
26
|
+
if (!addr)
|
|
27
|
+
throw new Error(`No contract deployed on network: ${network}`);
|
|
28
|
+
return addr;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function gqlRequest(query, variables, apiKey) {
|
|
32
|
+
const headers = {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
};
|
|
35
|
+
if (apiKey)
|
|
36
|
+
headers["X-API-Key"] = apiKey;
|
|
37
|
+
let response;
|
|
38
|
+
try {
|
|
39
|
+
response = await fetch(ENDPOINT, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers,
|
|
42
|
+
body: JSON.stringify({ query, variables }),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new KwesPayError("Network request failed — check your connectivity", "NETWORK_ERROR", err);
|
|
47
|
+
}
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new KwesPayError(`HTTP ${response.status}: ${response.statusText}`, "NETWORK_ERROR");
|
|
50
|
+
}
|
|
51
|
+
const json = await response.json();
|
|
52
|
+
if (json.errors?.length) {
|
|
53
|
+
throw new KwesPayError(json.errors[0]?.message ?? "GraphQL error", "UNKNOWN");
|
|
54
|
+
}
|
|
55
|
+
if (!json.data) {
|
|
56
|
+
throw new KwesPayError("Empty response from server", "UNKNOWN");
|
|
57
|
+
}
|
|
58
|
+
return json.data;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const GQL_VALIDATE_KEY = `
|
|
62
|
+
query ValidateAccessKey($accessKey: String!) {
|
|
63
|
+
validateAccessKey(accessKey: $accessKey) {
|
|
64
|
+
isValid
|
|
65
|
+
keyId
|
|
66
|
+
keyLabel
|
|
67
|
+
activeFlag
|
|
68
|
+
expirationDate
|
|
69
|
+
vendorInfo {
|
|
70
|
+
vendorPk
|
|
71
|
+
vendorIdentifier
|
|
72
|
+
businessName
|
|
73
|
+
}
|
|
74
|
+
allowedVendors
|
|
75
|
+
allowedNetworks
|
|
76
|
+
allowedTokens
|
|
77
|
+
error
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
const GQL_CREATE_QUOTE = `
|
|
82
|
+
mutation CreateQuote($input: CreateQuoteInput!) {
|
|
83
|
+
createQuote(input: $input) {
|
|
84
|
+
success
|
|
85
|
+
message
|
|
86
|
+
quoteId
|
|
87
|
+
quoteReference
|
|
88
|
+
cryptoCurrency
|
|
89
|
+
tokenAddress
|
|
90
|
+
amountBaseUnits
|
|
91
|
+
displayAmount
|
|
92
|
+
network
|
|
93
|
+
chainId
|
|
94
|
+
expiresAt
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
`;
|
|
98
|
+
const GQL_CREATE_TRANSACTION = `
|
|
99
|
+
mutation CreateTransaction($input: CreateTransactionInput!) {
|
|
100
|
+
createTransaction(input: $input) {
|
|
101
|
+
success
|
|
102
|
+
message
|
|
103
|
+
paymentIdBytes32
|
|
104
|
+
backendSignature
|
|
105
|
+
tokenAddress
|
|
106
|
+
amountBaseUnits
|
|
107
|
+
chainId
|
|
108
|
+
expiresAt
|
|
109
|
+
transaction {
|
|
110
|
+
transactionReference
|
|
111
|
+
transactionStatus
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
const GQL_TRANSACTION_STATUS = `
|
|
117
|
+
query GetTransactionStatus($transactionReference: String!) {
|
|
118
|
+
getTransactionStatus(transactionReference: $transactionReference) {
|
|
119
|
+
transactionReference
|
|
120
|
+
transactionStatus
|
|
121
|
+
blockchainHash
|
|
122
|
+
blockchainNetwork
|
|
123
|
+
displayAmount
|
|
124
|
+
cryptoCurrency
|
|
125
|
+
payerWalletAddress
|
|
126
|
+
initiatedAt
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
const version$1 = '1.2.3';
|
|
132
|
+
|
|
133
|
+
let BaseError$1 = class BaseError extends Error {
|
|
134
|
+
constructor(shortMessage, args = {}) {
|
|
135
|
+
const details = args.cause instanceof BaseError
|
|
136
|
+
? args.cause.details
|
|
137
|
+
: args.cause?.message
|
|
138
|
+
? args.cause.message
|
|
139
|
+
: args.details;
|
|
140
|
+
const docsPath = args.cause instanceof BaseError
|
|
141
|
+
? args.cause.docsPath || args.docsPath
|
|
142
|
+
: args.docsPath;
|
|
143
|
+
const message = [
|
|
144
|
+
shortMessage || 'An error occurred.',
|
|
145
|
+
'',
|
|
146
|
+
...(args.metaMessages ? [...args.metaMessages, ''] : []),
|
|
147
|
+
...(docsPath ? [`Docs: https://abitype.dev${docsPath}`] : []),
|
|
148
|
+
...(details ? [`Details: ${details}`] : []),
|
|
149
|
+
`Version: abitype@${version$1}`,
|
|
150
|
+
].join('\n');
|
|
151
|
+
super(message);
|
|
152
|
+
Object.defineProperty(this, "details", {
|
|
153
|
+
enumerable: true,
|
|
154
|
+
configurable: true,
|
|
155
|
+
writable: true,
|
|
156
|
+
value: void 0
|
|
157
|
+
});
|
|
158
|
+
Object.defineProperty(this, "docsPath", {
|
|
159
|
+
enumerable: true,
|
|
160
|
+
configurable: true,
|
|
161
|
+
writable: true,
|
|
162
|
+
value: void 0
|
|
163
|
+
});
|
|
164
|
+
Object.defineProperty(this, "metaMessages", {
|
|
165
|
+
enumerable: true,
|
|
166
|
+
configurable: true,
|
|
167
|
+
writable: true,
|
|
168
|
+
value: void 0
|
|
169
|
+
});
|
|
170
|
+
Object.defineProperty(this, "shortMessage", {
|
|
171
|
+
enumerable: true,
|
|
172
|
+
configurable: true,
|
|
173
|
+
writable: true,
|
|
174
|
+
value: void 0
|
|
175
|
+
});
|
|
176
|
+
Object.defineProperty(this, "name", {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
configurable: true,
|
|
179
|
+
writable: true,
|
|
180
|
+
value: 'AbiTypeError'
|
|
181
|
+
});
|
|
182
|
+
if (args.cause)
|
|
183
|
+
this.cause = args.cause;
|
|
184
|
+
this.details = details;
|
|
185
|
+
this.docsPath = docsPath;
|
|
186
|
+
this.metaMessages = args.metaMessages;
|
|
187
|
+
this.shortMessage = shortMessage;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// TODO: This looks cool. Need to check the performance of `new RegExp` versus defined inline though.
|
|
192
|
+
// https://twitter.com/GabrielVergnaud/status/1622906834343366657
|
|
193
|
+
function execTyped(regex, string) {
|
|
194
|
+
const match = regex.exec(string);
|
|
195
|
+
return match?.groups;
|
|
196
|
+
}
|
|
197
|
+
// `bytes<M>`: binary type of `M` bytes, `0 < M <= 32`
|
|
198
|
+
// https://regexr.com/6va55
|
|
199
|
+
const bytesRegex = /^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/;
|
|
200
|
+
// `(u)int<M>`: (un)signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`
|
|
201
|
+
// https://regexr.com/6v8hp
|
|
202
|
+
const integerRegex$1 = /^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/;
|
|
203
|
+
const isTupleRegex = /^\(.+?\).*?$/;
|
|
204
|
+
|
|
205
|
+
// https://regexr.com/7f7rv
|
|
206
|
+
const tupleRegex = /^tuple(?<array>(\[(\d*)\])*)$/;
|
|
207
|
+
/**
|
|
208
|
+
* Formats {@link AbiParameter} to human-readable ABI parameter.
|
|
209
|
+
*
|
|
210
|
+
* @param abiParameter - ABI parameter
|
|
211
|
+
* @returns Human-readable ABI parameter
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* const result = formatAbiParameter({ type: 'address', name: 'from' })
|
|
215
|
+
* // ^? const result: 'address from'
|
|
216
|
+
*/
|
|
217
|
+
function formatAbiParameter(abiParameter) {
|
|
218
|
+
let type = abiParameter.type;
|
|
219
|
+
if (tupleRegex.test(abiParameter.type) && 'components' in abiParameter) {
|
|
220
|
+
type = '(';
|
|
221
|
+
const length = abiParameter.components.length;
|
|
222
|
+
for (let i = 0; i < length; i++) {
|
|
223
|
+
const component = abiParameter.components[i];
|
|
224
|
+
type += formatAbiParameter(component);
|
|
225
|
+
if (i < length - 1)
|
|
226
|
+
type += ', ';
|
|
227
|
+
}
|
|
228
|
+
const result = execTyped(tupleRegex, abiParameter.type);
|
|
229
|
+
type += `)${result?.array || ''}`;
|
|
230
|
+
return formatAbiParameter({
|
|
231
|
+
...abiParameter,
|
|
232
|
+
type,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// Add `indexed` to type if in `abiParameter`
|
|
236
|
+
if ('indexed' in abiParameter && abiParameter.indexed)
|
|
237
|
+
type = `${type} indexed`;
|
|
238
|
+
// Return human-readable ABI parameter
|
|
239
|
+
if (abiParameter.name)
|
|
240
|
+
return `${type} ${abiParameter.name}`;
|
|
241
|
+
return type;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Formats {@link AbiParameter}s to human-readable ABI parameters.
|
|
246
|
+
*
|
|
247
|
+
* @param abiParameters - ABI parameters
|
|
248
|
+
* @returns Human-readable ABI parameters
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* const result = formatAbiParameters([
|
|
252
|
+
* // ^? const result: 'address from, uint256 tokenId'
|
|
253
|
+
* { type: 'address', name: 'from' },
|
|
254
|
+
* { type: 'uint256', name: 'tokenId' },
|
|
255
|
+
* ])
|
|
256
|
+
*/
|
|
257
|
+
function formatAbiParameters(abiParameters) {
|
|
258
|
+
let params = '';
|
|
259
|
+
const length = abiParameters.length;
|
|
260
|
+
for (let i = 0; i < length; i++) {
|
|
261
|
+
const abiParameter = abiParameters[i];
|
|
262
|
+
params += formatAbiParameter(abiParameter);
|
|
263
|
+
if (i !== length - 1)
|
|
264
|
+
params += ', ';
|
|
265
|
+
}
|
|
266
|
+
return params;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Formats ABI item (e.g. error, event, function) into human-readable ABI item
|
|
271
|
+
*
|
|
272
|
+
* @param abiItem - ABI item
|
|
273
|
+
* @returns Human-readable ABI item
|
|
274
|
+
*/
|
|
275
|
+
function formatAbiItem$1(abiItem) {
|
|
276
|
+
if (abiItem.type === 'function')
|
|
277
|
+
return `function ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})${abiItem.stateMutability && abiItem.stateMutability !== 'nonpayable'
|
|
278
|
+
? ` ${abiItem.stateMutability}`
|
|
279
|
+
: ''}${abiItem.outputs?.length
|
|
280
|
+
? ` returns (${formatAbiParameters(abiItem.outputs)})`
|
|
281
|
+
: ''}`;
|
|
282
|
+
if (abiItem.type === 'event')
|
|
283
|
+
return `event ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})`;
|
|
284
|
+
if (abiItem.type === 'error')
|
|
285
|
+
return `error ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})`;
|
|
286
|
+
if (abiItem.type === 'constructor')
|
|
287
|
+
return `constructor(${formatAbiParameters(abiItem.inputs)})${abiItem.stateMutability === 'payable' ? ' payable' : ''}`;
|
|
288
|
+
if (abiItem.type === 'fallback')
|
|
289
|
+
return `fallback() external${abiItem.stateMutability === 'payable' ? ' payable' : ''}`;
|
|
290
|
+
return 'receive() external payable';
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// https://regexr.com/7gmok
|
|
294
|
+
const errorSignatureRegex = /^error (?<name>[a-zA-Z$_][a-zA-Z0-9$_]*)\((?<parameters>.*?)\)$/;
|
|
295
|
+
function isErrorSignature(signature) {
|
|
296
|
+
return errorSignatureRegex.test(signature);
|
|
297
|
+
}
|
|
298
|
+
function execErrorSignature(signature) {
|
|
299
|
+
return execTyped(errorSignatureRegex, signature);
|
|
300
|
+
}
|
|
301
|
+
// https://regexr.com/7gmoq
|
|
302
|
+
const eventSignatureRegex = /^event (?<name>[a-zA-Z$_][a-zA-Z0-9$_]*)\((?<parameters>.*?)\)$/;
|
|
303
|
+
function isEventSignature(signature) {
|
|
304
|
+
return eventSignatureRegex.test(signature);
|
|
305
|
+
}
|
|
306
|
+
function execEventSignature(signature) {
|
|
307
|
+
return execTyped(eventSignatureRegex, signature);
|
|
308
|
+
}
|
|
309
|
+
// https://regexr.com/7gmot
|
|
310
|
+
const functionSignatureRegex = /^function (?<name>[a-zA-Z$_][a-zA-Z0-9$_]*)\((?<parameters>.*?)\)(?: (?<scope>external|public{1}))?(?: (?<stateMutability>pure|view|nonpayable|payable{1}))?(?: returns\s?\((?<returns>.*?)\))?$/;
|
|
311
|
+
function isFunctionSignature(signature) {
|
|
312
|
+
return functionSignatureRegex.test(signature);
|
|
313
|
+
}
|
|
314
|
+
function execFunctionSignature(signature) {
|
|
315
|
+
return execTyped(functionSignatureRegex, signature);
|
|
316
|
+
}
|
|
317
|
+
// https://regexr.com/7gmp3
|
|
318
|
+
const structSignatureRegex = /^struct (?<name>[a-zA-Z$_][a-zA-Z0-9$_]*) \{(?<properties>.*?)\}$/;
|
|
319
|
+
function isStructSignature(signature) {
|
|
320
|
+
return structSignatureRegex.test(signature);
|
|
321
|
+
}
|
|
322
|
+
function execStructSignature(signature) {
|
|
323
|
+
return execTyped(structSignatureRegex, signature);
|
|
324
|
+
}
|
|
325
|
+
// https://regexr.com/78u01
|
|
326
|
+
const constructorSignatureRegex = /^constructor\((?<parameters>.*?)\)(?:\s(?<stateMutability>payable{1}))?$/;
|
|
327
|
+
function isConstructorSignature(signature) {
|
|
328
|
+
return constructorSignatureRegex.test(signature);
|
|
329
|
+
}
|
|
330
|
+
function execConstructorSignature(signature) {
|
|
331
|
+
return execTyped(constructorSignatureRegex, signature);
|
|
332
|
+
}
|
|
333
|
+
// https://regexr.com/7srtn
|
|
334
|
+
const fallbackSignatureRegex = /^fallback\(\) external(?:\s(?<stateMutability>payable{1}))?$/;
|
|
335
|
+
function isFallbackSignature(signature) {
|
|
336
|
+
return fallbackSignatureRegex.test(signature);
|
|
337
|
+
}
|
|
338
|
+
function execFallbackSignature(signature) {
|
|
339
|
+
return execTyped(fallbackSignatureRegex, signature);
|
|
340
|
+
}
|
|
341
|
+
// https://regexr.com/78u1k
|
|
342
|
+
const receiveSignatureRegex = /^receive\(\) external payable$/;
|
|
343
|
+
function isReceiveSignature(signature) {
|
|
344
|
+
return receiveSignatureRegex.test(signature);
|
|
345
|
+
}
|
|
346
|
+
const eventModifiers = new Set(['indexed']);
|
|
347
|
+
const functionModifiers = new Set([
|
|
348
|
+
'calldata',
|
|
349
|
+
'memory',
|
|
350
|
+
'storage',
|
|
351
|
+
]);
|
|
352
|
+
|
|
353
|
+
class UnknownTypeError extends BaseError$1 {
|
|
354
|
+
constructor({ type }) {
|
|
355
|
+
super('Unknown type.', {
|
|
356
|
+
metaMessages: [
|
|
357
|
+
`Type "${type}" is not a valid ABI type. Perhaps you forgot to include a struct signature?`,
|
|
358
|
+
],
|
|
359
|
+
});
|
|
360
|
+
Object.defineProperty(this, "name", {
|
|
361
|
+
enumerable: true,
|
|
362
|
+
configurable: true,
|
|
363
|
+
writable: true,
|
|
364
|
+
value: 'UnknownTypeError'
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
class UnknownSolidityTypeError extends BaseError$1 {
|
|
369
|
+
constructor({ type }) {
|
|
370
|
+
super('Unknown type.', {
|
|
371
|
+
metaMessages: [`Type "${type}" is not a valid ABI type.`],
|
|
372
|
+
});
|
|
373
|
+
Object.defineProperty(this, "name", {
|
|
374
|
+
enumerable: true,
|
|
375
|
+
configurable: true,
|
|
376
|
+
writable: true,
|
|
377
|
+
value: 'UnknownSolidityTypeError'
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
class InvalidParameterError extends BaseError$1 {
|
|
383
|
+
constructor({ param }) {
|
|
384
|
+
super('Invalid ABI parameter.', {
|
|
385
|
+
details: param,
|
|
386
|
+
});
|
|
387
|
+
Object.defineProperty(this, "name", {
|
|
388
|
+
enumerable: true,
|
|
389
|
+
configurable: true,
|
|
390
|
+
writable: true,
|
|
391
|
+
value: 'InvalidParameterError'
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
class SolidityProtectedKeywordError extends BaseError$1 {
|
|
396
|
+
constructor({ param, name }) {
|
|
397
|
+
super('Invalid ABI parameter.', {
|
|
398
|
+
details: param,
|
|
399
|
+
metaMessages: [
|
|
400
|
+
`"${name}" is a protected Solidity keyword. More info: https://docs.soliditylang.org/en/latest/cheatsheet.html`,
|
|
401
|
+
],
|
|
402
|
+
});
|
|
403
|
+
Object.defineProperty(this, "name", {
|
|
404
|
+
enumerable: true,
|
|
405
|
+
configurable: true,
|
|
406
|
+
writable: true,
|
|
407
|
+
value: 'SolidityProtectedKeywordError'
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
class InvalidModifierError extends BaseError$1 {
|
|
412
|
+
constructor({ param, type, modifier, }) {
|
|
413
|
+
super('Invalid ABI parameter.', {
|
|
414
|
+
details: param,
|
|
415
|
+
metaMessages: [
|
|
416
|
+
`Modifier "${modifier}" not allowed${type ? ` in "${type}" type` : ''}.`,
|
|
417
|
+
],
|
|
418
|
+
});
|
|
419
|
+
Object.defineProperty(this, "name", {
|
|
420
|
+
enumerable: true,
|
|
421
|
+
configurable: true,
|
|
422
|
+
writable: true,
|
|
423
|
+
value: 'InvalidModifierError'
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
class InvalidFunctionModifierError extends BaseError$1 {
|
|
428
|
+
constructor({ param, type, modifier, }) {
|
|
429
|
+
super('Invalid ABI parameter.', {
|
|
430
|
+
details: param,
|
|
431
|
+
metaMessages: [
|
|
432
|
+
`Modifier "${modifier}" not allowed${type ? ` in "${type}" type` : ''}.`,
|
|
433
|
+
`Data location can only be specified for array, struct, or mapping types, but "${modifier}" was given.`,
|
|
434
|
+
],
|
|
435
|
+
});
|
|
436
|
+
Object.defineProperty(this, "name", {
|
|
437
|
+
enumerable: true,
|
|
438
|
+
configurable: true,
|
|
439
|
+
writable: true,
|
|
440
|
+
value: 'InvalidFunctionModifierError'
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
class InvalidAbiTypeParameterError extends BaseError$1 {
|
|
445
|
+
constructor({ abiParameter, }) {
|
|
446
|
+
super('Invalid ABI parameter.', {
|
|
447
|
+
details: JSON.stringify(abiParameter, null, 2),
|
|
448
|
+
metaMessages: ['ABI parameter type is invalid.'],
|
|
449
|
+
});
|
|
450
|
+
Object.defineProperty(this, "name", {
|
|
451
|
+
enumerable: true,
|
|
452
|
+
configurable: true,
|
|
453
|
+
writable: true,
|
|
454
|
+
value: 'InvalidAbiTypeParameterError'
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
class InvalidSignatureError extends BaseError$1 {
|
|
460
|
+
constructor({ signature, type, }) {
|
|
461
|
+
super(`Invalid ${type} signature.`, {
|
|
462
|
+
details: signature,
|
|
463
|
+
});
|
|
464
|
+
Object.defineProperty(this, "name", {
|
|
465
|
+
enumerable: true,
|
|
466
|
+
configurable: true,
|
|
467
|
+
writable: true,
|
|
468
|
+
value: 'InvalidSignatureError'
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
class UnknownSignatureError extends BaseError$1 {
|
|
473
|
+
constructor({ signature }) {
|
|
474
|
+
super('Unknown signature.', {
|
|
475
|
+
details: signature,
|
|
476
|
+
});
|
|
477
|
+
Object.defineProperty(this, "name", {
|
|
478
|
+
enumerable: true,
|
|
479
|
+
configurable: true,
|
|
480
|
+
writable: true,
|
|
481
|
+
value: 'UnknownSignatureError'
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
class InvalidStructSignatureError extends BaseError$1 {
|
|
486
|
+
constructor({ signature }) {
|
|
487
|
+
super('Invalid struct signature.', {
|
|
488
|
+
details: signature,
|
|
489
|
+
metaMessages: ['No properties exist.'],
|
|
490
|
+
});
|
|
491
|
+
Object.defineProperty(this, "name", {
|
|
492
|
+
enumerable: true,
|
|
493
|
+
configurable: true,
|
|
494
|
+
writable: true,
|
|
495
|
+
value: 'InvalidStructSignatureError'
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
class CircularReferenceError extends BaseError$1 {
|
|
501
|
+
constructor({ type }) {
|
|
502
|
+
super('Circular reference detected.', {
|
|
503
|
+
metaMessages: [`Struct "${type}" is a circular reference.`],
|
|
504
|
+
});
|
|
505
|
+
Object.defineProperty(this, "name", {
|
|
506
|
+
enumerable: true,
|
|
507
|
+
configurable: true,
|
|
508
|
+
writable: true,
|
|
509
|
+
value: 'CircularReferenceError'
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
class InvalidParenthesisError extends BaseError$1 {
|
|
515
|
+
constructor({ current, depth }) {
|
|
516
|
+
super('Unbalanced parentheses.', {
|
|
517
|
+
metaMessages: [
|
|
518
|
+
`"${current.trim()}" has too many ${depth > 0 ? 'opening' : 'closing'} parentheses.`,
|
|
519
|
+
],
|
|
520
|
+
details: `Depth "${depth}"`,
|
|
521
|
+
});
|
|
522
|
+
Object.defineProperty(this, "name", {
|
|
523
|
+
enumerable: true,
|
|
524
|
+
configurable: true,
|
|
525
|
+
writable: true,
|
|
526
|
+
value: 'InvalidParenthesisError'
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Gets {@link parameterCache} cache key namespaced by {@link type} and {@link structs}. This prevents parameters from being accessible to types that don't allow them (e.g. `string indexed foo` not allowed outside of `type: 'event'`) and ensures different struct definitions with the same name are cached separately.
|
|
533
|
+
* @param param ABI parameter string
|
|
534
|
+
* @param type ABI parameter type
|
|
535
|
+
* @param structs Struct definitions to include in cache key
|
|
536
|
+
* @returns Cache key for {@link parameterCache}
|
|
537
|
+
*/
|
|
538
|
+
function getParameterCacheKey(param, type, structs) {
|
|
539
|
+
let structKey = '';
|
|
540
|
+
if (structs)
|
|
541
|
+
for (const struct of Object.entries(structs)) {
|
|
542
|
+
if (!struct)
|
|
543
|
+
continue;
|
|
544
|
+
let propertyKey = '';
|
|
545
|
+
for (const property of struct[1]) {
|
|
546
|
+
propertyKey += `[${property.type}${property.name ? `:${property.name}` : ''}]`;
|
|
547
|
+
}
|
|
548
|
+
structKey += `(${struct[0]}{${propertyKey}})`;
|
|
549
|
+
}
|
|
550
|
+
if (type)
|
|
551
|
+
return `${type}:${param}${structKey}`;
|
|
552
|
+
return `${param}${structKey}`;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Basic cache seeded with common ABI parameter strings.
|
|
556
|
+
*
|
|
557
|
+
* **Note: When seeding more parameters, make sure you benchmark performance. The current number is the ideal balance between performance and having an already existing cache.**
|
|
558
|
+
*/
|
|
559
|
+
const parameterCache = new Map([
|
|
560
|
+
// Unnamed
|
|
561
|
+
['address', { type: 'address' }],
|
|
562
|
+
['bool', { type: 'bool' }],
|
|
563
|
+
['bytes', { type: 'bytes' }],
|
|
564
|
+
['bytes32', { type: 'bytes32' }],
|
|
565
|
+
['int', { type: 'int256' }],
|
|
566
|
+
['int256', { type: 'int256' }],
|
|
567
|
+
['string', { type: 'string' }],
|
|
568
|
+
['uint', { type: 'uint256' }],
|
|
569
|
+
['uint8', { type: 'uint8' }],
|
|
570
|
+
['uint16', { type: 'uint16' }],
|
|
571
|
+
['uint24', { type: 'uint24' }],
|
|
572
|
+
['uint32', { type: 'uint32' }],
|
|
573
|
+
['uint64', { type: 'uint64' }],
|
|
574
|
+
['uint96', { type: 'uint96' }],
|
|
575
|
+
['uint112', { type: 'uint112' }],
|
|
576
|
+
['uint160', { type: 'uint160' }],
|
|
577
|
+
['uint192', { type: 'uint192' }],
|
|
578
|
+
['uint256', { type: 'uint256' }],
|
|
579
|
+
// Named
|
|
580
|
+
['address owner', { type: 'address', name: 'owner' }],
|
|
581
|
+
['address to', { type: 'address', name: 'to' }],
|
|
582
|
+
['bool approved', { type: 'bool', name: 'approved' }],
|
|
583
|
+
['bytes _data', { type: 'bytes', name: '_data' }],
|
|
584
|
+
['bytes data', { type: 'bytes', name: 'data' }],
|
|
585
|
+
['bytes signature', { type: 'bytes', name: 'signature' }],
|
|
586
|
+
['bytes32 hash', { type: 'bytes32', name: 'hash' }],
|
|
587
|
+
['bytes32 r', { type: 'bytes32', name: 'r' }],
|
|
588
|
+
['bytes32 root', { type: 'bytes32', name: 'root' }],
|
|
589
|
+
['bytes32 s', { type: 'bytes32', name: 's' }],
|
|
590
|
+
['string name', { type: 'string', name: 'name' }],
|
|
591
|
+
['string symbol', { type: 'string', name: 'symbol' }],
|
|
592
|
+
['string tokenURI', { type: 'string', name: 'tokenURI' }],
|
|
593
|
+
['uint tokenId', { type: 'uint256', name: 'tokenId' }],
|
|
594
|
+
['uint8 v', { type: 'uint8', name: 'v' }],
|
|
595
|
+
['uint256 balance', { type: 'uint256', name: 'balance' }],
|
|
596
|
+
['uint256 tokenId', { type: 'uint256', name: 'tokenId' }],
|
|
597
|
+
['uint256 value', { type: 'uint256', name: 'value' }],
|
|
598
|
+
// Indexed
|
|
599
|
+
[
|
|
600
|
+
'event:address indexed from',
|
|
601
|
+
{ type: 'address', name: 'from', indexed: true },
|
|
602
|
+
],
|
|
603
|
+
['event:address indexed to', { type: 'address', name: 'to', indexed: true }],
|
|
604
|
+
[
|
|
605
|
+
'event:uint indexed tokenId',
|
|
606
|
+
{ type: 'uint256', name: 'tokenId', indexed: true },
|
|
607
|
+
],
|
|
608
|
+
[
|
|
609
|
+
'event:uint256 indexed tokenId',
|
|
610
|
+
{ type: 'uint256', name: 'tokenId', indexed: true },
|
|
611
|
+
],
|
|
612
|
+
]);
|
|
613
|
+
|
|
614
|
+
function parseSignature(signature, structs = {}) {
|
|
615
|
+
if (isFunctionSignature(signature))
|
|
616
|
+
return parseFunctionSignature(signature, structs);
|
|
617
|
+
if (isEventSignature(signature))
|
|
618
|
+
return parseEventSignature(signature, structs);
|
|
619
|
+
if (isErrorSignature(signature))
|
|
620
|
+
return parseErrorSignature(signature, structs);
|
|
621
|
+
if (isConstructorSignature(signature))
|
|
622
|
+
return parseConstructorSignature(signature, structs);
|
|
623
|
+
if (isFallbackSignature(signature))
|
|
624
|
+
return parseFallbackSignature(signature);
|
|
625
|
+
if (isReceiveSignature(signature))
|
|
626
|
+
return {
|
|
627
|
+
type: 'receive',
|
|
628
|
+
stateMutability: 'payable',
|
|
629
|
+
};
|
|
630
|
+
throw new UnknownSignatureError({ signature });
|
|
631
|
+
}
|
|
632
|
+
function parseFunctionSignature(signature, structs = {}) {
|
|
633
|
+
const match = execFunctionSignature(signature);
|
|
634
|
+
if (!match)
|
|
635
|
+
throw new InvalidSignatureError({ signature, type: 'function' });
|
|
636
|
+
const inputParams = splitParameters(match.parameters);
|
|
637
|
+
const inputs = [];
|
|
638
|
+
const inputLength = inputParams.length;
|
|
639
|
+
for (let i = 0; i < inputLength; i++) {
|
|
640
|
+
inputs.push(parseAbiParameter(inputParams[i], {
|
|
641
|
+
modifiers: functionModifiers,
|
|
642
|
+
structs,
|
|
643
|
+
type: 'function',
|
|
644
|
+
}));
|
|
645
|
+
}
|
|
646
|
+
const outputs = [];
|
|
647
|
+
if (match.returns) {
|
|
648
|
+
const outputParams = splitParameters(match.returns);
|
|
649
|
+
const outputLength = outputParams.length;
|
|
650
|
+
for (let i = 0; i < outputLength; i++) {
|
|
651
|
+
outputs.push(parseAbiParameter(outputParams[i], {
|
|
652
|
+
modifiers: functionModifiers,
|
|
653
|
+
structs,
|
|
654
|
+
type: 'function',
|
|
655
|
+
}));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
name: match.name,
|
|
660
|
+
type: 'function',
|
|
661
|
+
stateMutability: match.stateMutability ?? 'nonpayable',
|
|
662
|
+
inputs,
|
|
663
|
+
outputs,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
function parseEventSignature(signature, structs = {}) {
|
|
667
|
+
const match = execEventSignature(signature);
|
|
668
|
+
if (!match)
|
|
669
|
+
throw new InvalidSignatureError({ signature, type: 'event' });
|
|
670
|
+
const params = splitParameters(match.parameters);
|
|
671
|
+
const abiParameters = [];
|
|
672
|
+
const length = params.length;
|
|
673
|
+
for (let i = 0; i < length; i++)
|
|
674
|
+
abiParameters.push(parseAbiParameter(params[i], {
|
|
675
|
+
modifiers: eventModifiers,
|
|
676
|
+
structs,
|
|
677
|
+
type: 'event',
|
|
678
|
+
}));
|
|
679
|
+
return { name: match.name, type: 'event', inputs: abiParameters };
|
|
680
|
+
}
|
|
681
|
+
function parseErrorSignature(signature, structs = {}) {
|
|
682
|
+
const match = execErrorSignature(signature);
|
|
683
|
+
if (!match)
|
|
684
|
+
throw new InvalidSignatureError({ signature, type: 'error' });
|
|
685
|
+
const params = splitParameters(match.parameters);
|
|
686
|
+
const abiParameters = [];
|
|
687
|
+
const length = params.length;
|
|
688
|
+
for (let i = 0; i < length; i++)
|
|
689
|
+
abiParameters.push(parseAbiParameter(params[i], { structs, type: 'error' }));
|
|
690
|
+
return { name: match.name, type: 'error', inputs: abiParameters };
|
|
691
|
+
}
|
|
692
|
+
function parseConstructorSignature(signature, structs = {}) {
|
|
693
|
+
const match = execConstructorSignature(signature);
|
|
694
|
+
if (!match)
|
|
695
|
+
throw new InvalidSignatureError({ signature, type: 'constructor' });
|
|
696
|
+
const params = splitParameters(match.parameters);
|
|
697
|
+
const abiParameters = [];
|
|
698
|
+
const length = params.length;
|
|
699
|
+
for (let i = 0; i < length; i++)
|
|
700
|
+
abiParameters.push(parseAbiParameter(params[i], { structs, type: 'constructor' }));
|
|
701
|
+
return {
|
|
702
|
+
type: 'constructor',
|
|
703
|
+
stateMutability: match.stateMutability ?? 'nonpayable',
|
|
704
|
+
inputs: abiParameters,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function parseFallbackSignature(signature) {
|
|
708
|
+
const match = execFallbackSignature(signature);
|
|
709
|
+
if (!match)
|
|
710
|
+
throw new InvalidSignatureError({ signature, type: 'fallback' });
|
|
711
|
+
return {
|
|
712
|
+
type: 'fallback',
|
|
713
|
+
stateMutability: match.stateMutability ?? 'nonpayable',
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
const abiParameterWithoutTupleRegex = /^(?<type>[a-zA-Z$_][a-zA-Z0-9$_]*(?:\spayable)?)(?<array>(?:\[\d*?\])+?)?(?:\s(?<modifier>calldata|indexed|memory|storage{1}))?(?:\s(?<name>[a-zA-Z$_][a-zA-Z0-9$_]*))?$/;
|
|
717
|
+
const abiParameterWithTupleRegex = /^\((?<type>.+?)\)(?<array>(?:\[\d*?\])+?)?(?:\s(?<modifier>calldata|indexed|memory|storage{1}))?(?:\s(?<name>[a-zA-Z$_][a-zA-Z0-9$_]*))?$/;
|
|
718
|
+
const dynamicIntegerRegex = /^u?int$/;
|
|
719
|
+
function parseAbiParameter(param, options) {
|
|
720
|
+
// optional namespace cache by `type`
|
|
721
|
+
const parameterCacheKey = getParameterCacheKey(param, options?.type, options?.structs);
|
|
722
|
+
if (parameterCache.has(parameterCacheKey))
|
|
723
|
+
return parameterCache.get(parameterCacheKey);
|
|
724
|
+
const isTuple = isTupleRegex.test(param);
|
|
725
|
+
const match = execTyped(isTuple ? abiParameterWithTupleRegex : abiParameterWithoutTupleRegex, param);
|
|
726
|
+
if (!match)
|
|
727
|
+
throw new InvalidParameterError({ param });
|
|
728
|
+
if (match.name && isSolidityKeyword(match.name))
|
|
729
|
+
throw new SolidityProtectedKeywordError({ param, name: match.name });
|
|
730
|
+
const name = match.name ? { name: match.name } : {};
|
|
731
|
+
const indexed = match.modifier === 'indexed' ? { indexed: true } : {};
|
|
732
|
+
const structs = options?.structs ?? {};
|
|
733
|
+
let type;
|
|
734
|
+
let components = {};
|
|
735
|
+
if (isTuple) {
|
|
736
|
+
type = 'tuple';
|
|
737
|
+
const params = splitParameters(match.type);
|
|
738
|
+
const components_ = [];
|
|
739
|
+
const length = params.length;
|
|
740
|
+
for (let i = 0; i < length; i++) {
|
|
741
|
+
// remove `modifiers` from `options` to prevent from being added to tuple components
|
|
742
|
+
components_.push(parseAbiParameter(params[i], { structs }));
|
|
743
|
+
}
|
|
744
|
+
components = { components: components_ };
|
|
745
|
+
}
|
|
746
|
+
else if (match.type in structs) {
|
|
747
|
+
type = 'tuple';
|
|
748
|
+
components = { components: structs[match.type] };
|
|
749
|
+
}
|
|
750
|
+
else if (dynamicIntegerRegex.test(match.type)) {
|
|
751
|
+
type = `${match.type}256`;
|
|
752
|
+
}
|
|
753
|
+
else if (match.type === 'address payable') {
|
|
754
|
+
type = 'address';
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
type = match.type;
|
|
758
|
+
if (!(options?.type === 'struct') && !isSolidityType(type))
|
|
759
|
+
throw new UnknownSolidityTypeError({ type });
|
|
760
|
+
}
|
|
761
|
+
if (match.modifier) {
|
|
762
|
+
// Check if modifier exists, but is not allowed (e.g. `indexed` in `functionModifiers`)
|
|
763
|
+
if (!options?.modifiers?.has?.(match.modifier))
|
|
764
|
+
throw new InvalidModifierError({
|
|
765
|
+
param,
|
|
766
|
+
type: options?.type,
|
|
767
|
+
modifier: match.modifier,
|
|
768
|
+
});
|
|
769
|
+
// Check if resolved `type` is valid if there is a function modifier
|
|
770
|
+
if (functionModifiers.has(match.modifier) &&
|
|
771
|
+
!isValidDataLocation(type, !!match.array))
|
|
772
|
+
throw new InvalidFunctionModifierError({
|
|
773
|
+
param,
|
|
774
|
+
type: options?.type,
|
|
775
|
+
modifier: match.modifier,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
const abiParameter = {
|
|
779
|
+
type: `${type}${match.array ?? ''}`,
|
|
780
|
+
...name,
|
|
781
|
+
...indexed,
|
|
782
|
+
...components,
|
|
783
|
+
};
|
|
784
|
+
parameterCache.set(parameterCacheKey, abiParameter);
|
|
785
|
+
return abiParameter;
|
|
786
|
+
}
|
|
787
|
+
// s/o latika for this
|
|
788
|
+
function splitParameters(params, result = [], current = '', depth = 0) {
|
|
789
|
+
const length = params.trim().length;
|
|
790
|
+
// biome-ignore lint/correctness/noUnreachable: recursive
|
|
791
|
+
for (let i = 0; i < length; i++) {
|
|
792
|
+
const char = params[i];
|
|
793
|
+
const tail = params.slice(i + 1);
|
|
794
|
+
switch (char) {
|
|
795
|
+
case ',':
|
|
796
|
+
return depth === 0
|
|
797
|
+
? splitParameters(tail, [...result, current.trim()])
|
|
798
|
+
: splitParameters(tail, result, `${current}${char}`, depth);
|
|
799
|
+
case '(':
|
|
800
|
+
return splitParameters(tail, result, `${current}${char}`, depth + 1);
|
|
801
|
+
case ')':
|
|
802
|
+
return splitParameters(tail, result, `${current}${char}`, depth - 1);
|
|
803
|
+
default:
|
|
804
|
+
return splitParameters(tail, result, `${current}${char}`, depth);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (current === '')
|
|
808
|
+
return result;
|
|
809
|
+
if (depth !== 0)
|
|
810
|
+
throw new InvalidParenthesisError({ current, depth });
|
|
811
|
+
result.push(current.trim());
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
function isSolidityType(type) {
|
|
815
|
+
return (type === 'address' ||
|
|
816
|
+
type === 'bool' ||
|
|
817
|
+
type === 'function' ||
|
|
818
|
+
type === 'string' ||
|
|
819
|
+
bytesRegex.test(type) ||
|
|
820
|
+
integerRegex$1.test(type));
|
|
821
|
+
}
|
|
822
|
+
const protectedKeywordsRegex = /^(?:after|alias|anonymous|apply|auto|byte|calldata|case|catch|constant|copyof|default|defined|error|event|external|false|final|function|immutable|implements|in|indexed|inline|internal|let|mapping|match|memory|mutable|null|of|override|partial|private|promise|public|pure|reference|relocatable|return|returns|sizeof|static|storage|struct|super|supports|switch|this|true|try|typedef|typeof|var|view|virtual)$/;
|
|
823
|
+
/** @internal */
|
|
824
|
+
function isSolidityKeyword(name) {
|
|
825
|
+
return (name === 'address' ||
|
|
826
|
+
name === 'bool' ||
|
|
827
|
+
name === 'function' ||
|
|
828
|
+
name === 'string' ||
|
|
829
|
+
name === 'tuple' ||
|
|
830
|
+
bytesRegex.test(name) ||
|
|
831
|
+
integerRegex$1.test(name) ||
|
|
832
|
+
protectedKeywordsRegex.test(name));
|
|
833
|
+
}
|
|
834
|
+
/** @internal */
|
|
835
|
+
function isValidDataLocation(type, isArray) {
|
|
836
|
+
return isArray || type === 'bytes' || type === 'string' || type === 'tuple';
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function parseStructs(signatures) {
|
|
840
|
+
// Create "shallow" version of each struct (and filter out non-structs or invalid structs)
|
|
841
|
+
const shallowStructs = {};
|
|
842
|
+
const signaturesLength = signatures.length;
|
|
843
|
+
for (let i = 0; i < signaturesLength; i++) {
|
|
844
|
+
const signature = signatures[i];
|
|
845
|
+
if (!isStructSignature(signature))
|
|
846
|
+
continue;
|
|
847
|
+
const match = execStructSignature(signature);
|
|
848
|
+
if (!match)
|
|
849
|
+
throw new InvalidSignatureError({ signature, type: 'struct' });
|
|
850
|
+
const properties = match.properties.split(';');
|
|
851
|
+
const components = [];
|
|
852
|
+
const propertiesLength = properties.length;
|
|
853
|
+
for (let k = 0; k < propertiesLength; k++) {
|
|
854
|
+
const property = properties[k];
|
|
855
|
+
const trimmed = property.trim();
|
|
856
|
+
if (!trimmed)
|
|
857
|
+
continue;
|
|
858
|
+
const abiParameter = parseAbiParameter(trimmed, {
|
|
859
|
+
type: 'struct',
|
|
860
|
+
});
|
|
861
|
+
components.push(abiParameter);
|
|
862
|
+
}
|
|
863
|
+
if (!components.length)
|
|
864
|
+
throw new InvalidStructSignatureError({ signature });
|
|
865
|
+
shallowStructs[match.name] = components;
|
|
866
|
+
}
|
|
867
|
+
// Resolve nested structs inside each parameter
|
|
868
|
+
const resolvedStructs = {};
|
|
869
|
+
const entries = Object.entries(shallowStructs);
|
|
870
|
+
const entriesLength = entries.length;
|
|
871
|
+
for (let i = 0; i < entriesLength; i++) {
|
|
872
|
+
const [name, parameters] = entries[i];
|
|
873
|
+
resolvedStructs[name] = resolveStructs(parameters, shallowStructs);
|
|
874
|
+
}
|
|
875
|
+
return resolvedStructs;
|
|
876
|
+
}
|
|
877
|
+
const typeWithoutTupleRegex = /^(?<type>[a-zA-Z$_][a-zA-Z0-9$_]*)(?<array>(?:\[\d*?\])+?)?$/;
|
|
878
|
+
function resolveStructs(abiParameters = [], structs = {}, ancestors = new Set()) {
|
|
879
|
+
const components = [];
|
|
880
|
+
const length = abiParameters.length;
|
|
881
|
+
for (let i = 0; i < length; i++) {
|
|
882
|
+
const abiParameter = abiParameters[i];
|
|
883
|
+
const isTuple = isTupleRegex.test(abiParameter.type);
|
|
884
|
+
if (isTuple)
|
|
885
|
+
components.push(abiParameter);
|
|
886
|
+
else {
|
|
887
|
+
const match = execTyped(typeWithoutTupleRegex, abiParameter.type);
|
|
888
|
+
if (!match?.type)
|
|
889
|
+
throw new InvalidAbiTypeParameterError({ abiParameter });
|
|
890
|
+
const { array, type } = match;
|
|
891
|
+
if (type in structs) {
|
|
892
|
+
if (ancestors.has(type))
|
|
893
|
+
throw new CircularReferenceError({ type });
|
|
894
|
+
components.push({
|
|
895
|
+
...abiParameter,
|
|
896
|
+
type: `tuple${array ?? ''}`,
|
|
897
|
+
components: resolveStructs(structs[type], structs, new Set([...ancestors, type])),
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
if (isSolidityType(type))
|
|
902
|
+
components.push(abiParameter);
|
|
903
|
+
else
|
|
904
|
+
throw new UnknownTypeError({ type });
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return components;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Parses human-readable ABI into JSON {@link Abi}
|
|
913
|
+
*
|
|
914
|
+
* @param signatures - Human-Readable ABI
|
|
915
|
+
* @returns Parsed {@link Abi}
|
|
916
|
+
*
|
|
917
|
+
* @example
|
|
918
|
+
* const abi = parseAbi([
|
|
919
|
+
* // ^? const abi: readonly [{ name: "balanceOf"; type: "function"; stateMutability:...
|
|
920
|
+
* 'function balanceOf(address owner) view returns (uint256)',
|
|
921
|
+
* 'event Transfer(address indexed from, address indexed to, uint256 amount)',
|
|
922
|
+
* ])
|
|
923
|
+
*/
|
|
924
|
+
function parseAbi(signatures) {
|
|
925
|
+
const structs = parseStructs(signatures);
|
|
926
|
+
const abi = [];
|
|
927
|
+
const length = signatures.length;
|
|
928
|
+
for (let i = 0; i < length; i++) {
|
|
929
|
+
const signature = signatures[i];
|
|
930
|
+
if (isStructSignature(signature))
|
|
931
|
+
continue;
|
|
932
|
+
abi.push(parseSignature(signature, structs));
|
|
933
|
+
}
|
|
934
|
+
return abi;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function formatAbiItem(abiItem, { includeName = false } = {}) {
|
|
938
|
+
if (abiItem.type !== 'function' &&
|
|
939
|
+
abiItem.type !== 'event' &&
|
|
940
|
+
abiItem.type !== 'error')
|
|
941
|
+
throw new InvalidDefinitionTypeError(abiItem.type);
|
|
942
|
+
return `${abiItem.name}(${formatAbiParams(abiItem.inputs, { includeName })})`;
|
|
943
|
+
}
|
|
944
|
+
function formatAbiParams(params, { includeName = false } = {}) {
|
|
945
|
+
if (!params)
|
|
946
|
+
return '';
|
|
947
|
+
return params
|
|
948
|
+
.map((param) => formatAbiParam(param, { includeName }))
|
|
949
|
+
.join(includeName ? ', ' : ',');
|
|
950
|
+
}
|
|
951
|
+
function formatAbiParam(param, { includeName }) {
|
|
952
|
+
if (param.type.startsWith('tuple')) {
|
|
953
|
+
return `(${formatAbiParams(param.components, { includeName })})${param.type.slice('tuple'.length)}`;
|
|
954
|
+
}
|
|
955
|
+
return param.type + (includeName && param.name ? ` ${param.name}` : '');
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function isHex(value, { strict = true } = {}) {
|
|
959
|
+
if (!value)
|
|
960
|
+
return false;
|
|
961
|
+
if (typeof value !== 'string')
|
|
962
|
+
return false;
|
|
963
|
+
return strict ? /^0x[0-9a-fA-F]*$/.test(value) : value.startsWith('0x');
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* @description Retrieves the size of the value (in bytes).
|
|
968
|
+
*
|
|
969
|
+
* @param value The value (hex or byte array) to retrieve the size of.
|
|
970
|
+
* @returns The size of the value (in bytes).
|
|
971
|
+
*/
|
|
972
|
+
function size(value) {
|
|
973
|
+
if (isHex(value, { strict: false }))
|
|
974
|
+
return Math.ceil((value.length - 2) / 2);
|
|
975
|
+
return value.length;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const version = '2.47.10';
|
|
979
|
+
|
|
980
|
+
let errorConfig = {
|
|
981
|
+
getDocsUrl: ({ docsBaseUrl, docsPath = '', docsSlug, }) => docsPath
|
|
982
|
+
? `${docsBaseUrl ?? 'https://viem.sh'}${docsPath}${docsSlug ? `#${docsSlug}` : ''}`
|
|
983
|
+
: undefined,
|
|
984
|
+
version: `viem@${version}`,
|
|
985
|
+
};
|
|
986
|
+
class BaseError extends Error {
|
|
987
|
+
constructor(shortMessage, args = {}) {
|
|
988
|
+
const details = (() => {
|
|
989
|
+
if (args.cause instanceof BaseError)
|
|
990
|
+
return args.cause.details;
|
|
991
|
+
if (args.cause?.message)
|
|
992
|
+
return args.cause.message;
|
|
993
|
+
return args.details;
|
|
994
|
+
})();
|
|
995
|
+
const docsPath = (() => {
|
|
996
|
+
if (args.cause instanceof BaseError)
|
|
997
|
+
return args.cause.docsPath || args.docsPath;
|
|
998
|
+
return args.docsPath;
|
|
999
|
+
})();
|
|
1000
|
+
const docsUrl = errorConfig.getDocsUrl?.({ ...args, docsPath });
|
|
1001
|
+
const message = [
|
|
1002
|
+
shortMessage || 'An error occurred.',
|
|
1003
|
+
'',
|
|
1004
|
+
...(args.metaMessages ? [...args.metaMessages, ''] : []),
|
|
1005
|
+
...(docsUrl ? [`Docs: ${docsUrl}`] : []),
|
|
1006
|
+
...(details ? [`Details: ${details}`] : []),
|
|
1007
|
+
...(errorConfig.version ? [`Version: ${errorConfig.version}`] : []),
|
|
1008
|
+
].join('\n');
|
|
1009
|
+
super(message, args.cause ? { cause: args.cause } : undefined);
|
|
1010
|
+
Object.defineProperty(this, "details", {
|
|
1011
|
+
enumerable: true,
|
|
1012
|
+
configurable: true,
|
|
1013
|
+
writable: true,
|
|
1014
|
+
value: void 0
|
|
1015
|
+
});
|
|
1016
|
+
Object.defineProperty(this, "docsPath", {
|
|
1017
|
+
enumerable: true,
|
|
1018
|
+
configurable: true,
|
|
1019
|
+
writable: true,
|
|
1020
|
+
value: void 0
|
|
1021
|
+
});
|
|
1022
|
+
Object.defineProperty(this, "metaMessages", {
|
|
1023
|
+
enumerable: true,
|
|
1024
|
+
configurable: true,
|
|
1025
|
+
writable: true,
|
|
1026
|
+
value: void 0
|
|
1027
|
+
});
|
|
1028
|
+
Object.defineProperty(this, "shortMessage", {
|
|
1029
|
+
enumerable: true,
|
|
1030
|
+
configurable: true,
|
|
1031
|
+
writable: true,
|
|
1032
|
+
value: void 0
|
|
1033
|
+
});
|
|
1034
|
+
Object.defineProperty(this, "version", {
|
|
1035
|
+
enumerable: true,
|
|
1036
|
+
configurable: true,
|
|
1037
|
+
writable: true,
|
|
1038
|
+
value: void 0
|
|
1039
|
+
});
|
|
1040
|
+
Object.defineProperty(this, "name", {
|
|
1041
|
+
enumerable: true,
|
|
1042
|
+
configurable: true,
|
|
1043
|
+
writable: true,
|
|
1044
|
+
value: 'BaseError'
|
|
1045
|
+
});
|
|
1046
|
+
this.details = details;
|
|
1047
|
+
this.docsPath = docsPath;
|
|
1048
|
+
this.metaMessages = args.metaMessages;
|
|
1049
|
+
this.name = args.name ?? this.name;
|
|
1050
|
+
this.shortMessage = shortMessage;
|
|
1051
|
+
this.version = version;
|
|
1052
|
+
}
|
|
1053
|
+
walk(fn) {
|
|
1054
|
+
return walk(this, fn);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function walk(err, fn) {
|
|
1058
|
+
if (fn?.(err))
|
|
1059
|
+
return err;
|
|
1060
|
+
if (err &&
|
|
1061
|
+
typeof err === 'object' &&
|
|
1062
|
+
'cause' in err &&
|
|
1063
|
+
err.cause !== undefined)
|
|
1064
|
+
return walk(err.cause, fn);
|
|
1065
|
+
return fn ? null : err;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
class AbiEncodingArrayLengthMismatchError extends BaseError {
|
|
1069
|
+
constructor({ expectedLength, givenLength, type, }) {
|
|
1070
|
+
super([
|
|
1071
|
+
`ABI encoding array length mismatch for type ${type}.`,
|
|
1072
|
+
`Expected length: ${expectedLength}`,
|
|
1073
|
+
`Given length: ${givenLength}`,
|
|
1074
|
+
].join('\n'), { name: 'AbiEncodingArrayLengthMismatchError' });
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
class AbiEncodingBytesSizeMismatchError extends BaseError {
|
|
1078
|
+
constructor({ expectedSize, value }) {
|
|
1079
|
+
super(`Size of bytes "${value}" (bytes${size(value)}) does not match expected size (bytes${expectedSize}).`, { name: 'AbiEncodingBytesSizeMismatchError' });
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
class AbiEncodingLengthMismatchError extends BaseError {
|
|
1083
|
+
constructor({ expectedLength, givenLength, }) {
|
|
1084
|
+
super([
|
|
1085
|
+
'ABI encoding params/values length mismatch.',
|
|
1086
|
+
`Expected length (params): ${expectedLength}`,
|
|
1087
|
+
`Given length (values): ${givenLength}`,
|
|
1088
|
+
].join('\n'), { name: 'AbiEncodingLengthMismatchError' });
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
class AbiFunctionNotFoundError extends BaseError {
|
|
1092
|
+
constructor(functionName, { docsPath } = {}) {
|
|
1093
|
+
super([
|
|
1094
|
+
`Function ${functionName ? `"${functionName}" ` : ''}not found on ABI.`,
|
|
1095
|
+
'Make sure you are using the correct ABI and that the function exists on it.',
|
|
1096
|
+
].join('\n'), {
|
|
1097
|
+
docsPath,
|
|
1098
|
+
name: 'AbiFunctionNotFoundError',
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
class AbiItemAmbiguityError extends BaseError {
|
|
1103
|
+
constructor(x, y) {
|
|
1104
|
+
super('Found ambiguous types in overloaded ABI items.', {
|
|
1105
|
+
metaMessages: [
|
|
1106
|
+
`\`${x.type}\` in \`${formatAbiItem(x.abiItem)}\`, and`,
|
|
1107
|
+
`\`${y.type}\` in \`${formatAbiItem(y.abiItem)}\``,
|
|
1108
|
+
'',
|
|
1109
|
+
'These types encode differently and cannot be distinguished at runtime.',
|
|
1110
|
+
'Remove one of the ambiguous items in the ABI.',
|
|
1111
|
+
],
|
|
1112
|
+
name: 'AbiItemAmbiguityError',
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
class InvalidAbiEncodingTypeError extends BaseError {
|
|
1117
|
+
constructor(type, { docsPath }) {
|
|
1118
|
+
super([
|
|
1119
|
+
`Type "${type}" is not a valid encoding type.`,
|
|
1120
|
+
'Please provide a valid ABI type.',
|
|
1121
|
+
].join('\n'), { docsPath, name: 'InvalidAbiEncodingType' });
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
class InvalidArrayError extends BaseError {
|
|
1125
|
+
constructor(value) {
|
|
1126
|
+
super([`Value "${value}" is not a valid array.`].join('\n'), {
|
|
1127
|
+
name: 'InvalidArrayError',
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
class InvalidDefinitionTypeError extends BaseError {
|
|
1132
|
+
constructor(type) {
|
|
1133
|
+
super([
|
|
1134
|
+
`"${type}" is not a valid definition type.`,
|
|
1135
|
+
'Valid types: "function", "event", "error"',
|
|
1136
|
+
].join('\n'), { name: 'InvalidDefinitionTypeError' });
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
class SliceOffsetOutOfBoundsError extends BaseError {
|
|
1141
|
+
constructor({ offset, position, size, }) {
|
|
1142
|
+
super(`Slice ${position === 'start' ? 'starting' : 'ending'} at offset "${offset}" is out-of-bounds (size: ${size}).`, { name: 'SliceOffsetOutOfBoundsError' });
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
class SizeExceedsPaddingSizeError extends BaseError {
|
|
1146
|
+
constructor({ size, targetSize, type, }) {
|
|
1147
|
+
super(`${type.charAt(0).toUpperCase()}${type
|
|
1148
|
+
.slice(1)
|
|
1149
|
+
.toLowerCase()} size (${size}) exceeds padding size (${targetSize}).`, { name: 'SizeExceedsPaddingSizeError' });
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function pad(hexOrBytes, { dir, size = 32 } = {}) {
|
|
1154
|
+
if (typeof hexOrBytes === 'string')
|
|
1155
|
+
return padHex(hexOrBytes, { dir, size });
|
|
1156
|
+
return padBytes(hexOrBytes, { dir, size });
|
|
1157
|
+
}
|
|
1158
|
+
function padHex(hex_, { dir, size = 32 } = {}) {
|
|
1159
|
+
if (size === null)
|
|
1160
|
+
return hex_;
|
|
1161
|
+
const hex = hex_.replace('0x', '');
|
|
1162
|
+
if (hex.length > size * 2)
|
|
1163
|
+
throw new SizeExceedsPaddingSizeError({
|
|
1164
|
+
size: Math.ceil(hex.length / 2),
|
|
1165
|
+
targetSize: size,
|
|
1166
|
+
type: 'hex',
|
|
1167
|
+
});
|
|
1168
|
+
return `0x${hex[dir === 'right' ? 'padEnd' : 'padStart'](size * 2, '0')}`;
|
|
1169
|
+
}
|
|
1170
|
+
function padBytes(bytes, { dir, size = 32 } = {}) {
|
|
1171
|
+
if (size === null)
|
|
1172
|
+
return bytes;
|
|
1173
|
+
if (bytes.length > size)
|
|
1174
|
+
throw new SizeExceedsPaddingSizeError({
|
|
1175
|
+
size: bytes.length,
|
|
1176
|
+
targetSize: size,
|
|
1177
|
+
type: 'bytes',
|
|
1178
|
+
});
|
|
1179
|
+
const paddedBytes = new Uint8Array(size);
|
|
1180
|
+
for (let i = 0; i < size; i++) {
|
|
1181
|
+
const padEnd = dir === 'right';
|
|
1182
|
+
paddedBytes[padEnd ? i : size - i - 1] =
|
|
1183
|
+
bytes[padEnd ? i : bytes.length - i - 1];
|
|
1184
|
+
}
|
|
1185
|
+
return paddedBytes;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
class IntegerOutOfRangeError extends BaseError {
|
|
1189
|
+
constructor({ max, min, signed, size, value, }) {
|
|
1190
|
+
super(`Number "${value}" is not in safe ${size ? `${size * 8}-bit ${signed ? 'signed' : 'unsigned'} ` : ''}integer range ${max ? `(${min} to ${max})` : `(above ${min})`}`, { name: 'IntegerOutOfRangeError' });
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
class SizeOverflowError extends BaseError {
|
|
1194
|
+
constructor({ givenSize, maxSize }) {
|
|
1195
|
+
super(`Size cannot exceed ${maxSize} bytes. Given size: ${givenSize} bytes.`, { name: 'SizeOverflowError' });
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function assertSize(hexOrBytes, { size: size$1 }) {
|
|
1200
|
+
if (size(hexOrBytes) > size$1)
|
|
1201
|
+
throw new SizeOverflowError({
|
|
1202
|
+
givenSize: size(hexOrBytes),
|
|
1203
|
+
maxSize: size$1,
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const hexes = /*#__PURE__*/ Array.from({ length: 256 }, (_v, i) => i.toString(16).padStart(2, '0'));
|
|
1208
|
+
/**
|
|
1209
|
+
* Encodes a string, number, bigint, or ByteArray into a hex string
|
|
1210
|
+
*
|
|
1211
|
+
* - Docs: https://viem.sh/docs/utilities/toHex
|
|
1212
|
+
* - Example: https://viem.sh/docs/utilities/toHex#usage
|
|
1213
|
+
*
|
|
1214
|
+
* @param value Value to encode.
|
|
1215
|
+
* @param opts Options.
|
|
1216
|
+
* @returns Hex value.
|
|
1217
|
+
*
|
|
1218
|
+
* @example
|
|
1219
|
+
* import { toHex } from 'viem'
|
|
1220
|
+
* const data = toHex('Hello world')
|
|
1221
|
+
* // '0x48656c6c6f20776f726c6421'
|
|
1222
|
+
*
|
|
1223
|
+
* @example
|
|
1224
|
+
* import { toHex } from 'viem'
|
|
1225
|
+
* const data = toHex(420)
|
|
1226
|
+
* // '0x1a4'
|
|
1227
|
+
*
|
|
1228
|
+
* @example
|
|
1229
|
+
* import { toHex } from 'viem'
|
|
1230
|
+
* const data = toHex('Hello world', { size: 32 })
|
|
1231
|
+
* // '0x48656c6c6f20776f726c64210000000000000000000000000000000000000000'
|
|
1232
|
+
*/
|
|
1233
|
+
function toHex(value, opts = {}) {
|
|
1234
|
+
if (typeof value === 'number' || typeof value === 'bigint')
|
|
1235
|
+
return numberToHex(value, opts);
|
|
1236
|
+
if (typeof value === 'string') {
|
|
1237
|
+
return stringToHex(value, opts);
|
|
1238
|
+
}
|
|
1239
|
+
if (typeof value === 'boolean')
|
|
1240
|
+
return boolToHex(value, opts);
|
|
1241
|
+
return bytesToHex(value, opts);
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Encodes a boolean into a hex string
|
|
1245
|
+
*
|
|
1246
|
+
* - Docs: https://viem.sh/docs/utilities/toHex#booltohex
|
|
1247
|
+
*
|
|
1248
|
+
* @param value Value to encode.
|
|
1249
|
+
* @param opts Options.
|
|
1250
|
+
* @returns Hex value.
|
|
1251
|
+
*
|
|
1252
|
+
* @example
|
|
1253
|
+
* import { boolToHex } from 'viem'
|
|
1254
|
+
* const data = boolToHex(true)
|
|
1255
|
+
* // '0x1'
|
|
1256
|
+
*
|
|
1257
|
+
* @example
|
|
1258
|
+
* import { boolToHex } from 'viem'
|
|
1259
|
+
* const data = boolToHex(false)
|
|
1260
|
+
* // '0x0'
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* import { boolToHex } from 'viem'
|
|
1264
|
+
* const data = boolToHex(true, { size: 32 })
|
|
1265
|
+
* // '0x0000000000000000000000000000000000000000000000000000000000000001'
|
|
1266
|
+
*/
|
|
1267
|
+
function boolToHex(value, opts = {}) {
|
|
1268
|
+
const hex = `0x${Number(value)}`;
|
|
1269
|
+
if (typeof opts.size === 'number') {
|
|
1270
|
+
assertSize(hex, { size: opts.size });
|
|
1271
|
+
return pad(hex, { size: opts.size });
|
|
1272
|
+
}
|
|
1273
|
+
return hex;
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Encodes a bytes array into a hex string
|
|
1277
|
+
*
|
|
1278
|
+
* - Docs: https://viem.sh/docs/utilities/toHex#bytestohex
|
|
1279
|
+
*
|
|
1280
|
+
* @param value Value to encode.
|
|
1281
|
+
* @param opts Options.
|
|
1282
|
+
* @returns Hex value.
|
|
1283
|
+
*
|
|
1284
|
+
* @example
|
|
1285
|
+
* import { bytesToHex } from 'viem'
|
|
1286
|
+
* const data = bytesToHex(Uint8Array.from([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])
|
|
1287
|
+
* // '0x48656c6c6f20576f726c6421'
|
|
1288
|
+
*
|
|
1289
|
+
* @example
|
|
1290
|
+
* import { bytesToHex } from 'viem'
|
|
1291
|
+
* const data = bytesToHex(Uint8Array.from([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]), { size: 32 })
|
|
1292
|
+
* // '0x48656c6c6f20576f726c64210000000000000000000000000000000000000000'
|
|
1293
|
+
*/
|
|
1294
|
+
function bytesToHex(value, opts = {}) {
|
|
1295
|
+
let string = '';
|
|
1296
|
+
for (let i = 0; i < value.length; i++) {
|
|
1297
|
+
string += hexes[value[i]];
|
|
1298
|
+
}
|
|
1299
|
+
const hex = `0x${string}`;
|
|
1300
|
+
if (typeof opts.size === 'number') {
|
|
1301
|
+
assertSize(hex, { size: opts.size });
|
|
1302
|
+
return pad(hex, { dir: 'right', size: opts.size });
|
|
1303
|
+
}
|
|
1304
|
+
return hex;
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Encodes a number or bigint into a hex string
|
|
1308
|
+
*
|
|
1309
|
+
* - Docs: https://viem.sh/docs/utilities/toHex#numbertohex
|
|
1310
|
+
*
|
|
1311
|
+
* @param value Value to encode.
|
|
1312
|
+
* @param opts Options.
|
|
1313
|
+
* @returns Hex value.
|
|
1314
|
+
*
|
|
1315
|
+
* @example
|
|
1316
|
+
* import { numberToHex } from 'viem'
|
|
1317
|
+
* const data = numberToHex(420)
|
|
1318
|
+
* // '0x1a4'
|
|
1319
|
+
*
|
|
1320
|
+
* @example
|
|
1321
|
+
* import { numberToHex } from 'viem'
|
|
1322
|
+
* const data = numberToHex(420, { size: 32 })
|
|
1323
|
+
* // '0x00000000000000000000000000000000000000000000000000000000000001a4'
|
|
1324
|
+
*/
|
|
1325
|
+
function numberToHex(value_, opts = {}) {
|
|
1326
|
+
const { signed, size } = opts;
|
|
1327
|
+
const value = BigInt(value_);
|
|
1328
|
+
let maxValue;
|
|
1329
|
+
if (size) {
|
|
1330
|
+
if (signed)
|
|
1331
|
+
maxValue = (1n << (BigInt(size) * 8n - 1n)) - 1n;
|
|
1332
|
+
else
|
|
1333
|
+
maxValue = 2n ** (BigInt(size) * 8n) - 1n;
|
|
1334
|
+
}
|
|
1335
|
+
else if (typeof value_ === 'number') {
|
|
1336
|
+
maxValue = BigInt(Number.MAX_SAFE_INTEGER);
|
|
1337
|
+
}
|
|
1338
|
+
const minValue = typeof maxValue === 'bigint' && signed ? -maxValue - 1n : 0;
|
|
1339
|
+
if ((maxValue && value > maxValue) || value < minValue) {
|
|
1340
|
+
const suffix = typeof value_ === 'bigint' ? 'n' : '';
|
|
1341
|
+
throw new IntegerOutOfRangeError({
|
|
1342
|
+
max: maxValue ? `${maxValue}${suffix}` : undefined,
|
|
1343
|
+
min: `${minValue}${suffix}`,
|
|
1344
|
+
signed,
|
|
1345
|
+
size,
|
|
1346
|
+
value: `${value_}${suffix}`,
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
const hex = `0x${(signed && value < 0 ? (1n << BigInt(size * 8)) + BigInt(value) : value).toString(16)}`;
|
|
1350
|
+
if (size)
|
|
1351
|
+
return pad(hex, { size });
|
|
1352
|
+
return hex;
|
|
1353
|
+
}
|
|
1354
|
+
const encoder$1 = /*#__PURE__*/ new TextEncoder();
|
|
1355
|
+
/**
|
|
1356
|
+
* Encodes a UTF-8 string into a hex string
|
|
1357
|
+
*
|
|
1358
|
+
* - Docs: https://viem.sh/docs/utilities/toHex#stringtohex
|
|
1359
|
+
*
|
|
1360
|
+
* @param value Value to encode.
|
|
1361
|
+
* @param opts Options.
|
|
1362
|
+
* @returns Hex value.
|
|
1363
|
+
*
|
|
1364
|
+
* @example
|
|
1365
|
+
* import { stringToHex } from 'viem'
|
|
1366
|
+
* const data = stringToHex('Hello World!')
|
|
1367
|
+
* // '0x48656c6c6f20576f726c6421'
|
|
1368
|
+
*
|
|
1369
|
+
* @example
|
|
1370
|
+
* import { stringToHex } from 'viem'
|
|
1371
|
+
* const data = stringToHex('Hello World!', { size: 32 })
|
|
1372
|
+
* // '0x48656c6c6f20576f726c64210000000000000000000000000000000000000000'
|
|
1373
|
+
*/
|
|
1374
|
+
function stringToHex(value_, opts = {}) {
|
|
1375
|
+
const value = encoder$1.encode(value_);
|
|
1376
|
+
return bytesToHex(value, opts);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
const encoder = /*#__PURE__*/ new TextEncoder();
|
|
1380
|
+
/**
|
|
1381
|
+
* Encodes a UTF-8 string, hex value, bigint, number or boolean to a byte array.
|
|
1382
|
+
*
|
|
1383
|
+
* - Docs: https://viem.sh/docs/utilities/toBytes
|
|
1384
|
+
* - Example: https://viem.sh/docs/utilities/toBytes#usage
|
|
1385
|
+
*
|
|
1386
|
+
* @param value Value to encode.
|
|
1387
|
+
* @param opts Options.
|
|
1388
|
+
* @returns Byte array value.
|
|
1389
|
+
*
|
|
1390
|
+
* @example
|
|
1391
|
+
* import { toBytes } from 'viem'
|
|
1392
|
+
* const data = toBytes('Hello world')
|
|
1393
|
+
* // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])
|
|
1394
|
+
*
|
|
1395
|
+
* @example
|
|
1396
|
+
* import { toBytes } from 'viem'
|
|
1397
|
+
* const data = toBytes(420)
|
|
1398
|
+
* // Uint8Array([1, 164])
|
|
1399
|
+
*
|
|
1400
|
+
* @example
|
|
1401
|
+
* import { toBytes } from 'viem'
|
|
1402
|
+
* const data = toBytes(420, { size: 4 })
|
|
1403
|
+
* // Uint8Array([0, 0, 1, 164])
|
|
1404
|
+
*/
|
|
1405
|
+
function toBytes$1(value, opts = {}) {
|
|
1406
|
+
if (typeof value === 'number' || typeof value === 'bigint')
|
|
1407
|
+
return numberToBytes(value, opts);
|
|
1408
|
+
if (typeof value === 'boolean')
|
|
1409
|
+
return boolToBytes(value, opts);
|
|
1410
|
+
if (isHex(value))
|
|
1411
|
+
return hexToBytes(value, opts);
|
|
1412
|
+
return stringToBytes(value, opts);
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Encodes a boolean into a byte array.
|
|
1416
|
+
*
|
|
1417
|
+
* - Docs: https://viem.sh/docs/utilities/toBytes#booltobytes
|
|
1418
|
+
*
|
|
1419
|
+
* @param value Boolean value to encode.
|
|
1420
|
+
* @param opts Options.
|
|
1421
|
+
* @returns Byte array value.
|
|
1422
|
+
*
|
|
1423
|
+
* @example
|
|
1424
|
+
* import { boolToBytes } from 'viem'
|
|
1425
|
+
* const data = boolToBytes(true)
|
|
1426
|
+
* // Uint8Array([1])
|
|
1427
|
+
*
|
|
1428
|
+
* @example
|
|
1429
|
+
* import { boolToBytes } from 'viem'
|
|
1430
|
+
* const data = boolToBytes(true, { size: 32 })
|
|
1431
|
+
* // Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
|
|
1432
|
+
*/
|
|
1433
|
+
function boolToBytes(value, opts = {}) {
|
|
1434
|
+
const bytes = new Uint8Array(1);
|
|
1435
|
+
bytes[0] = Number(value);
|
|
1436
|
+
if (typeof opts.size === 'number') {
|
|
1437
|
+
assertSize(bytes, { size: opts.size });
|
|
1438
|
+
return pad(bytes, { size: opts.size });
|
|
1439
|
+
}
|
|
1440
|
+
return bytes;
|
|
1441
|
+
}
|
|
1442
|
+
// We use very optimized technique to convert hex string to byte array
|
|
1443
|
+
const charCodeMap = {
|
|
1444
|
+
zero: 48,
|
|
1445
|
+
nine: 57,
|
|
1446
|
+
A: 65,
|
|
1447
|
+
F: 70,
|
|
1448
|
+
a: 97,
|
|
1449
|
+
f: 102,
|
|
1450
|
+
};
|
|
1451
|
+
function charCodeToBase16(char) {
|
|
1452
|
+
if (char >= charCodeMap.zero && char <= charCodeMap.nine)
|
|
1453
|
+
return char - charCodeMap.zero;
|
|
1454
|
+
if (char >= charCodeMap.A && char <= charCodeMap.F)
|
|
1455
|
+
return char - (charCodeMap.A - 10);
|
|
1456
|
+
if (char >= charCodeMap.a && char <= charCodeMap.f)
|
|
1457
|
+
return char - (charCodeMap.a - 10);
|
|
1458
|
+
return undefined;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Encodes a hex string into a byte array.
|
|
1462
|
+
*
|
|
1463
|
+
* - Docs: https://viem.sh/docs/utilities/toBytes#hextobytes
|
|
1464
|
+
*
|
|
1465
|
+
* @param hex Hex string to encode.
|
|
1466
|
+
* @param opts Options.
|
|
1467
|
+
* @returns Byte array value.
|
|
1468
|
+
*
|
|
1469
|
+
* @example
|
|
1470
|
+
* import { hexToBytes } from 'viem'
|
|
1471
|
+
* const data = hexToBytes('0x48656c6c6f20776f726c6421')
|
|
1472
|
+
* // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33])
|
|
1473
|
+
*
|
|
1474
|
+
* @example
|
|
1475
|
+
* import { hexToBytes } from 'viem'
|
|
1476
|
+
* const data = hexToBytes('0x48656c6c6f20776f726c6421', { size: 32 })
|
|
1477
|
+
* // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
1478
|
+
*/
|
|
1479
|
+
function hexToBytes(hex_, opts = {}) {
|
|
1480
|
+
let hex = hex_;
|
|
1481
|
+
if (opts.size) {
|
|
1482
|
+
assertSize(hex, { size: opts.size });
|
|
1483
|
+
hex = pad(hex, { dir: 'right', size: opts.size });
|
|
1484
|
+
}
|
|
1485
|
+
let hexString = hex.slice(2);
|
|
1486
|
+
if (hexString.length % 2)
|
|
1487
|
+
hexString = `0${hexString}`;
|
|
1488
|
+
const length = hexString.length / 2;
|
|
1489
|
+
const bytes = new Uint8Array(length);
|
|
1490
|
+
for (let index = 0, j = 0; index < length; index++) {
|
|
1491
|
+
const nibbleLeft = charCodeToBase16(hexString.charCodeAt(j++));
|
|
1492
|
+
const nibbleRight = charCodeToBase16(hexString.charCodeAt(j++));
|
|
1493
|
+
if (nibbleLeft === undefined || nibbleRight === undefined) {
|
|
1494
|
+
throw new BaseError(`Invalid byte sequence ("${hexString[j - 2]}${hexString[j - 1]}" in "${hexString}").`);
|
|
1495
|
+
}
|
|
1496
|
+
bytes[index] = nibbleLeft * 16 + nibbleRight;
|
|
1497
|
+
}
|
|
1498
|
+
return bytes;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Encodes a number into a byte array.
|
|
1502
|
+
*
|
|
1503
|
+
* - Docs: https://viem.sh/docs/utilities/toBytes#numbertobytes
|
|
1504
|
+
*
|
|
1505
|
+
* @param value Number to encode.
|
|
1506
|
+
* @param opts Options.
|
|
1507
|
+
* @returns Byte array value.
|
|
1508
|
+
*
|
|
1509
|
+
* @example
|
|
1510
|
+
* import { numberToBytes } from 'viem'
|
|
1511
|
+
* const data = numberToBytes(420)
|
|
1512
|
+
* // Uint8Array([1, 164])
|
|
1513
|
+
*
|
|
1514
|
+
* @example
|
|
1515
|
+
* import { numberToBytes } from 'viem'
|
|
1516
|
+
* const data = numberToBytes(420, { size: 4 })
|
|
1517
|
+
* // Uint8Array([0, 0, 1, 164])
|
|
1518
|
+
*/
|
|
1519
|
+
function numberToBytes(value, opts) {
|
|
1520
|
+
const hex = numberToHex(value, opts);
|
|
1521
|
+
return hexToBytes(hex);
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Encodes a UTF-8 string into a byte array.
|
|
1525
|
+
*
|
|
1526
|
+
* - Docs: https://viem.sh/docs/utilities/toBytes#stringtobytes
|
|
1527
|
+
*
|
|
1528
|
+
* @param value String to encode.
|
|
1529
|
+
* @param opts Options.
|
|
1530
|
+
* @returns Byte array value.
|
|
1531
|
+
*
|
|
1532
|
+
* @example
|
|
1533
|
+
* import { stringToBytes } from 'viem'
|
|
1534
|
+
* const data = stringToBytes('Hello world!')
|
|
1535
|
+
* // Uint8Array([72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33])
|
|
1536
|
+
*
|
|
1537
|
+
* @example
|
|
1538
|
+
* import { stringToBytes } from 'viem'
|
|
1539
|
+
* const data = stringToBytes('Hello world!', { size: 32 })
|
|
1540
|
+
* // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
|
1541
|
+
*/
|
|
1542
|
+
function stringToBytes(value, opts = {}) {
|
|
1543
|
+
const bytes = encoder.encode(value);
|
|
1544
|
+
if (typeof opts.size === 'number') {
|
|
1545
|
+
assertSize(bytes, { size: opts.size });
|
|
1546
|
+
return pad(bytes, { dir: 'right', size: opts.size });
|
|
1547
|
+
}
|
|
1548
|
+
return bytes;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* Internal helpers for u64. BigUint64Array is too slow as per 2025, so we implement it using Uint32Array.
|
|
1553
|
+
* @todo re-check https://issues.chromium.org/issues/42212588
|
|
1554
|
+
* @module
|
|
1555
|
+
*/
|
|
1556
|
+
const U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
1557
|
+
const _32n = /* @__PURE__ */ BigInt(32);
|
|
1558
|
+
function fromBig(n, le = false) {
|
|
1559
|
+
if (le)
|
|
1560
|
+
return { h: Number(n & U32_MASK64), l: Number((n >> _32n) & U32_MASK64) };
|
|
1561
|
+
return { h: Number((n >> _32n) & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
|
1562
|
+
}
|
|
1563
|
+
function split(lst, le = false) {
|
|
1564
|
+
const len = lst.length;
|
|
1565
|
+
let Ah = new Uint32Array(len);
|
|
1566
|
+
let Al = new Uint32Array(len);
|
|
1567
|
+
for (let i = 0; i < len; i++) {
|
|
1568
|
+
const { h, l } = fromBig(lst[i], le);
|
|
1569
|
+
[Ah[i], Al[i]] = [h, l];
|
|
1570
|
+
}
|
|
1571
|
+
return [Ah, Al];
|
|
1572
|
+
}
|
|
1573
|
+
// Left rotate for Shift in [1, 32)
|
|
1574
|
+
const rotlSH = (h, l, s) => (h << s) | (l >>> (32 - s));
|
|
1575
|
+
const rotlSL = (h, l, s) => (l << s) | (h >>> (32 - s));
|
|
1576
|
+
// Left rotate for Shift in (32, 64), NOTE: 32 is special case.
|
|
1577
|
+
const rotlBH = (h, l, s) => (l << (s - 32)) | (h >>> (64 - s));
|
|
1578
|
+
const rotlBL = (h, l, s) => (h << (s - 32)) | (l >>> (64 - s));
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* Utilities for hex, bytes, CSPRNG.
|
|
1582
|
+
* @module
|
|
1583
|
+
*/
|
|
1584
|
+
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
1585
|
+
// We use WebCrypto aka globalThis.crypto, which exists in browsers and node.js 16+.
|
|
1586
|
+
// node.js versions earlier than v19 don't declare it in global scope.
|
|
1587
|
+
// For node.js, package.json#exports field mapping rewrites import
|
|
1588
|
+
// from `crypto` to `cryptoNode`, which imports native module.
|
|
1589
|
+
// Makes the utils un-importable in browsers without a bundler.
|
|
1590
|
+
// Once node.js 18 is deprecated (2025-04-30), we can just drop the import.
|
|
1591
|
+
/** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
|
|
1592
|
+
function isBytes(a) {
|
|
1593
|
+
return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
|
|
1594
|
+
}
|
|
1595
|
+
/** Asserts something is positive integer. */
|
|
1596
|
+
function anumber(n) {
|
|
1597
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
1598
|
+
throw new Error('positive integer expected, got ' + n);
|
|
1599
|
+
}
|
|
1600
|
+
/** Asserts something is Uint8Array. */
|
|
1601
|
+
function abytes(b, ...lengths) {
|
|
1602
|
+
if (!isBytes(b))
|
|
1603
|
+
throw new Error('Uint8Array expected');
|
|
1604
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
1605
|
+
throw new Error('Uint8Array expected of length ' + lengths + ', got length=' + b.length);
|
|
1606
|
+
}
|
|
1607
|
+
/** Asserts a hash instance has not been destroyed / finished */
|
|
1608
|
+
function aexists(instance, checkFinished = true) {
|
|
1609
|
+
if (instance.destroyed)
|
|
1610
|
+
throw new Error('Hash instance has been destroyed');
|
|
1611
|
+
if (checkFinished && instance.finished)
|
|
1612
|
+
throw new Error('Hash#digest() has already been called');
|
|
1613
|
+
}
|
|
1614
|
+
/** Asserts output is properly-sized byte array */
|
|
1615
|
+
function aoutput(out, instance) {
|
|
1616
|
+
abytes(out);
|
|
1617
|
+
const min = instance.outputLen;
|
|
1618
|
+
if (out.length < min) {
|
|
1619
|
+
throw new Error('digestInto() expects output buffer of length at least ' + min);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
/** Cast u8 / u16 / u32 to u32. */
|
|
1623
|
+
function u32(arr) {
|
|
1624
|
+
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
1625
|
+
}
|
|
1626
|
+
/** Zeroize a byte array. Warning: JS provides no guarantees. */
|
|
1627
|
+
function clean(...arrays) {
|
|
1628
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
1629
|
+
arrays[i].fill(0);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
/** Is current platform little-endian? Most are. Big-Endian platform: IBM */
|
|
1633
|
+
const isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
|
|
1634
|
+
/** The byte swap operation for uint32 */
|
|
1635
|
+
function byteSwap(word) {
|
|
1636
|
+
return (((word << 24) & 0xff000000) |
|
|
1637
|
+
((word << 8) & 0xff0000) |
|
|
1638
|
+
((word >>> 8) & 0xff00) |
|
|
1639
|
+
((word >>> 24) & 0xff));
|
|
1640
|
+
}
|
|
1641
|
+
/** In place byte swap for Uint32Array */
|
|
1642
|
+
function byteSwap32(arr) {
|
|
1643
|
+
for (let i = 0; i < arr.length; i++) {
|
|
1644
|
+
arr[i] = byteSwap(arr[i]);
|
|
1645
|
+
}
|
|
1646
|
+
return arr;
|
|
1647
|
+
}
|
|
1648
|
+
const swap32IfBE = isLE
|
|
1649
|
+
? (u) => u
|
|
1650
|
+
: byteSwap32;
|
|
1651
|
+
/**
|
|
1652
|
+
* Converts string to bytes using UTF8 encoding.
|
|
1653
|
+
* @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
|
|
1654
|
+
*/
|
|
1655
|
+
function utf8ToBytes(str) {
|
|
1656
|
+
if (typeof str !== 'string')
|
|
1657
|
+
throw new Error('string expected');
|
|
1658
|
+
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
|
|
1662
|
+
* Warning: when Uint8Array is passed, it would NOT get copied.
|
|
1663
|
+
* Keep in mind for future mutable operations.
|
|
1664
|
+
*/
|
|
1665
|
+
function toBytes(data) {
|
|
1666
|
+
if (typeof data === 'string')
|
|
1667
|
+
data = utf8ToBytes(data);
|
|
1668
|
+
abytes(data);
|
|
1669
|
+
return data;
|
|
1670
|
+
}
|
|
1671
|
+
/** For runtime check if class implements interface */
|
|
1672
|
+
class Hash {
|
|
1673
|
+
}
|
|
1674
|
+
/** Wraps hash function, creating an interface on top of it */
|
|
1675
|
+
function createHasher(hashCons) {
|
|
1676
|
+
const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
|
|
1677
|
+
const tmp = hashCons();
|
|
1678
|
+
hashC.outputLen = tmp.outputLen;
|
|
1679
|
+
hashC.blockLen = tmp.blockLen;
|
|
1680
|
+
hashC.create = () => hashCons();
|
|
1681
|
+
return hashC;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* SHA3 (keccak) hash function, based on a new "Sponge function" design.
|
|
1686
|
+
* Different from older hashes, the internal state is bigger than output size.
|
|
1687
|
+
*
|
|
1688
|
+
* Check out [FIPS-202](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf),
|
|
1689
|
+
* [Website](https://keccak.team/keccak.html),
|
|
1690
|
+
* [the differences between SHA-3 and Keccak](https://crypto.stackexchange.com/questions/15727/what-are-the-key-differences-between-the-draft-sha-3-standard-and-the-keccak-sub).
|
|
1691
|
+
*
|
|
1692
|
+
* Check out `sha3-addons` module for cSHAKE, k12, and others.
|
|
1693
|
+
* @module
|
|
1694
|
+
*/
|
|
1695
|
+
// No __PURE__ annotations in sha3 header:
|
|
1696
|
+
// EVERYTHING is in fact used on every export.
|
|
1697
|
+
// Various per round constants calculations
|
|
1698
|
+
const _0n = BigInt(0);
|
|
1699
|
+
const _1n = BigInt(1);
|
|
1700
|
+
const _2n = BigInt(2);
|
|
1701
|
+
const _7n = BigInt(7);
|
|
1702
|
+
const _256n = BigInt(256);
|
|
1703
|
+
const _0x71n = BigInt(0x71);
|
|
1704
|
+
const SHA3_PI = [];
|
|
1705
|
+
const SHA3_ROTL = [];
|
|
1706
|
+
const _SHA3_IOTA = [];
|
|
1707
|
+
for (let round = 0, R = _1n, x = 1, y = 0; round < 24; round++) {
|
|
1708
|
+
// Pi
|
|
1709
|
+
[x, y] = [y, (2 * x + 3 * y) % 5];
|
|
1710
|
+
SHA3_PI.push(2 * (5 * y + x));
|
|
1711
|
+
// Rotational
|
|
1712
|
+
SHA3_ROTL.push((((round + 1) * (round + 2)) / 2) % 64);
|
|
1713
|
+
// Iota
|
|
1714
|
+
let t = _0n;
|
|
1715
|
+
for (let j = 0; j < 7; j++) {
|
|
1716
|
+
R = ((R << _1n) ^ ((R >> _7n) * _0x71n)) % _256n;
|
|
1717
|
+
if (R & _2n)
|
|
1718
|
+
t ^= _1n << ((_1n << /* @__PURE__ */ BigInt(j)) - _1n);
|
|
1719
|
+
}
|
|
1720
|
+
_SHA3_IOTA.push(t);
|
|
1721
|
+
}
|
|
1722
|
+
const IOTAS = split(_SHA3_IOTA, true);
|
|
1723
|
+
const SHA3_IOTA_H = IOTAS[0];
|
|
1724
|
+
const SHA3_IOTA_L = IOTAS[1];
|
|
1725
|
+
// Left rotation (without 0, 32, 64)
|
|
1726
|
+
const rotlH = (h, l, s) => (s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s));
|
|
1727
|
+
const rotlL = (h, l, s) => (s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s));
|
|
1728
|
+
/** `keccakf1600` internal function, additionally allows to adjust round count. */
|
|
1729
|
+
function keccakP(s, rounds = 24) {
|
|
1730
|
+
const B = new Uint32Array(5 * 2);
|
|
1731
|
+
// NOTE: all indices are x2 since we store state as u32 instead of u64 (bigints to slow in js)
|
|
1732
|
+
for (let round = 24 - rounds; round < 24; round++) {
|
|
1733
|
+
// Theta θ
|
|
1734
|
+
for (let x = 0; x < 10; x++)
|
|
1735
|
+
B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
|
|
1736
|
+
for (let x = 0; x < 10; x += 2) {
|
|
1737
|
+
const idx1 = (x + 8) % 10;
|
|
1738
|
+
const idx0 = (x + 2) % 10;
|
|
1739
|
+
const B0 = B[idx0];
|
|
1740
|
+
const B1 = B[idx0 + 1];
|
|
1741
|
+
const Th = rotlH(B0, B1, 1) ^ B[idx1];
|
|
1742
|
+
const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
|
|
1743
|
+
for (let y = 0; y < 50; y += 10) {
|
|
1744
|
+
s[x + y] ^= Th;
|
|
1745
|
+
s[x + y + 1] ^= Tl;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
// Rho (ρ) and Pi (π)
|
|
1749
|
+
let curH = s[2];
|
|
1750
|
+
let curL = s[3];
|
|
1751
|
+
for (let t = 0; t < 24; t++) {
|
|
1752
|
+
const shift = SHA3_ROTL[t];
|
|
1753
|
+
const Th = rotlH(curH, curL, shift);
|
|
1754
|
+
const Tl = rotlL(curH, curL, shift);
|
|
1755
|
+
const PI = SHA3_PI[t];
|
|
1756
|
+
curH = s[PI];
|
|
1757
|
+
curL = s[PI + 1];
|
|
1758
|
+
s[PI] = Th;
|
|
1759
|
+
s[PI + 1] = Tl;
|
|
1760
|
+
}
|
|
1761
|
+
// Chi (χ)
|
|
1762
|
+
for (let y = 0; y < 50; y += 10) {
|
|
1763
|
+
for (let x = 0; x < 10; x++)
|
|
1764
|
+
B[x] = s[y + x];
|
|
1765
|
+
for (let x = 0; x < 10; x++)
|
|
1766
|
+
s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
|
|
1767
|
+
}
|
|
1768
|
+
// Iota (ι)
|
|
1769
|
+
s[0] ^= SHA3_IOTA_H[round];
|
|
1770
|
+
s[1] ^= SHA3_IOTA_L[round];
|
|
1771
|
+
}
|
|
1772
|
+
clean(B);
|
|
1773
|
+
}
|
|
1774
|
+
/** Keccak sponge function. */
|
|
1775
|
+
class Keccak extends Hash {
|
|
1776
|
+
// NOTE: we accept arguments in bytes instead of bits here.
|
|
1777
|
+
constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
|
|
1778
|
+
super();
|
|
1779
|
+
this.pos = 0;
|
|
1780
|
+
this.posOut = 0;
|
|
1781
|
+
this.finished = false;
|
|
1782
|
+
this.destroyed = false;
|
|
1783
|
+
this.enableXOF = false;
|
|
1784
|
+
this.blockLen = blockLen;
|
|
1785
|
+
this.suffix = suffix;
|
|
1786
|
+
this.outputLen = outputLen;
|
|
1787
|
+
this.enableXOF = enableXOF;
|
|
1788
|
+
this.rounds = rounds;
|
|
1789
|
+
// Can be passed from user as dkLen
|
|
1790
|
+
anumber(outputLen);
|
|
1791
|
+
// 1600 = 5x5 matrix of 64bit. 1600 bits === 200 bytes
|
|
1792
|
+
// 0 < blockLen < 200
|
|
1793
|
+
if (!(0 < blockLen && blockLen < 200))
|
|
1794
|
+
throw new Error('only keccak-f1600 function is supported');
|
|
1795
|
+
this.state = new Uint8Array(200);
|
|
1796
|
+
this.state32 = u32(this.state);
|
|
1797
|
+
}
|
|
1798
|
+
clone() {
|
|
1799
|
+
return this._cloneInto();
|
|
1800
|
+
}
|
|
1801
|
+
keccak() {
|
|
1802
|
+
swap32IfBE(this.state32);
|
|
1803
|
+
keccakP(this.state32, this.rounds);
|
|
1804
|
+
swap32IfBE(this.state32);
|
|
1805
|
+
this.posOut = 0;
|
|
1806
|
+
this.pos = 0;
|
|
1807
|
+
}
|
|
1808
|
+
update(data) {
|
|
1809
|
+
aexists(this);
|
|
1810
|
+
data = toBytes(data);
|
|
1811
|
+
abytes(data);
|
|
1812
|
+
const { blockLen, state } = this;
|
|
1813
|
+
const len = data.length;
|
|
1814
|
+
for (let pos = 0; pos < len;) {
|
|
1815
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
|
1816
|
+
for (let i = 0; i < take; i++)
|
|
1817
|
+
state[this.pos++] ^= data[pos++];
|
|
1818
|
+
if (this.pos === blockLen)
|
|
1819
|
+
this.keccak();
|
|
1820
|
+
}
|
|
1821
|
+
return this;
|
|
1822
|
+
}
|
|
1823
|
+
finish() {
|
|
1824
|
+
if (this.finished)
|
|
1825
|
+
return;
|
|
1826
|
+
this.finished = true;
|
|
1827
|
+
const { state, suffix, pos, blockLen } = this;
|
|
1828
|
+
// Do the padding
|
|
1829
|
+
state[pos] ^= suffix;
|
|
1830
|
+
if ((suffix & 0x80) !== 0 && pos === blockLen - 1)
|
|
1831
|
+
this.keccak();
|
|
1832
|
+
state[blockLen - 1] ^= 0x80;
|
|
1833
|
+
this.keccak();
|
|
1834
|
+
}
|
|
1835
|
+
writeInto(out) {
|
|
1836
|
+
aexists(this, false);
|
|
1837
|
+
abytes(out);
|
|
1838
|
+
this.finish();
|
|
1839
|
+
const bufferOut = this.state;
|
|
1840
|
+
const { blockLen } = this;
|
|
1841
|
+
for (let pos = 0, len = out.length; pos < len;) {
|
|
1842
|
+
if (this.posOut >= blockLen)
|
|
1843
|
+
this.keccak();
|
|
1844
|
+
const take = Math.min(blockLen - this.posOut, len - pos);
|
|
1845
|
+
out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
|
|
1846
|
+
this.posOut += take;
|
|
1847
|
+
pos += take;
|
|
1848
|
+
}
|
|
1849
|
+
return out;
|
|
1850
|
+
}
|
|
1851
|
+
xofInto(out) {
|
|
1852
|
+
// Sha3/Keccak usage with XOF is probably mistake, only SHAKE instances can do XOF
|
|
1853
|
+
if (!this.enableXOF)
|
|
1854
|
+
throw new Error('XOF is not possible for this instance');
|
|
1855
|
+
return this.writeInto(out);
|
|
1856
|
+
}
|
|
1857
|
+
xof(bytes) {
|
|
1858
|
+
anumber(bytes);
|
|
1859
|
+
return this.xofInto(new Uint8Array(bytes));
|
|
1860
|
+
}
|
|
1861
|
+
digestInto(out) {
|
|
1862
|
+
aoutput(out, this);
|
|
1863
|
+
if (this.finished)
|
|
1864
|
+
throw new Error('digest() was already called');
|
|
1865
|
+
this.writeInto(out);
|
|
1866
|
+
this.destroy();
|
|
1867
|
+
return out;
|
|
1868
|
+
}
|
|
1869
|
+
digest() {
|
|
1870
|
+
return this.digestInto(new Uint8Array(this.outputLen));
|
|
1871
|
+
}
|
|
1872
|
+
destroy() {
|
|
1873
|
+
this.destroyed = true;
|
|
1874
|
+
clean(this.state);
|
|
1875
|
+
}
|
|
1876
|
+
_cloneInto(to) {
|
|
1877
|
+
const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
|
|
1878
|
+
to || (to = new Keccak(blockLen, suffix, outputLen, enableXOF, rounds));
|
|
1879
|
+
to.state32.set(this.state32);
|
|
1880
|
+
to.pos = this.pos;
|
|
1881
|
+
to.posOut = this.posOut;
|
|
1882
|
+
to.finished = this.finished;
|
|
1883
|
+
to.rounds = rounds;
|
|
1884
|
+
// Suffix can change in cSHAKE
|
|
1885
|
+
to.suffix = suffix;
|
|
1886
|
+
to.outputLen = outputLen;
|
|
1887
|
+
to.enableXOF = enableXOF;
|
|
1888
|
+
to.destroyed = this.destroyed;
|
|
1889
|
+
return to;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
const gen = (suffix, blockLen, outputLen) => createHasher(() => new Keccak(blockLen, suffix, outputLen));
|
|
1893
|
+
/** keccak-256 hash function. Different from SHA3-256. */
|
|
1894
|
+
const keccak_256 = /* @__PURE__ */ (() => gen(0x01, 136, 256 / 8))();
|
|
1895
|
+
|
|
1896
|
+
function keccak256(value, to_) {
|
|
1897
|
+
const to = to_ || 'hex';
|
|
1898
|
+
const bytes = keccak_256(isHex(value, { strict: false }) ? toBytes$1(value) : value);
|
|
1899
|
+
if (to === 'bytes')
|
|
1900
|
+
return bytes;
|
|
1901
|
+
return toHex(bytes);
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
const hash = (value) => keccak256(toBytes$1(value));
|
|
1905
|
+
function hashSignature(sig) {
|
|
1906
|
+
return hash(sig);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
function normalizeSignature(signature) {
|
|
1910
|
+
let active = true;
|
|
1911
|
+
let current = '';
|
|
1912
|
+
let level = 0;
|
|
1913
|
+
let result = '';
|
|
1914
|
+
let valid = false;
|
|
1915
|
+
for (let i = 0; i < signature.length; i++) {
|
|
1916
|
+
const char = signature[i];
|
|
1917
|
+
// If the character is a separator, we want to reactivate.
|
|
1918
|
+
if (['(', ')', ','].includes(char))
|
|
1919
|
+
active = true;
|
|
1920
|
+
// If the character is a "level" token, we want to increment/decrement.
|
|
1921
|
+
if (char === '(')
|
|
1922
|
+
level++;
|
|
1923
|
+
if (char === ')')
|
|
1924
|
+
level--;
|
|
1925
|
+
// If we aren't active, we don't want to mutate the result.
|
|
1926
|
+
if (!active)
|
|
1927
|
+
continue;
|
|
1928
|
+
// If level === 0, we are at the definition level.
|
|
1929
|
+
if (level === 0) {
|
|
1930
|
+
if (char === ' ' && ['event', 'function', ''].includes(result))
|
|
1931
|
+
result = '';
|
|
1932
|
+
else {
|
|
1933
|
+
result += char;
|
|
1934
|
+
// If we are at the end of the definition, we must be finished.
|
|
1935
|
+
if (char === ')') {
|
|
1936
|
+
valid = true;
|
|
1937
|
+
break;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
// Ignore spaces
|
|
1943
|
+
if (char === ' ') {
|
|
1944
|
+
// If the previous character is a separator, and the current section isn't empty, we want to deactivate.
|
|
1945
|
+
if (signature[i - 1] !== ',' && current !== ',' && current !== ',(') {
|
|
1946
|
+
current = '';
|
|
1947
|
+
active = false;
|
|
1948
|
+
}
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
result += char;
|
|
1952
|
+
current += char;
|
|
1953
|
+
}
|
|
1954
|
+
if (!valid)
|
|
1955
|
+
throw new BaseError('Unable to normalize signature.');
|
|
1956
|
+
return result;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
/**
|
|
1960
|
+
* Returns the signature for a given function or event definition.
|
|
1961
|
+
*
|
|
1962
|
+
* @example
|
|
1963
|
+
* const signature = toSignature('function ownerOf(uint256 tokenId)')
|
|
1964
|
+
* // 'ownerOf(uint256)'
|
|
1965
|
+
*
|
|
1966
|
+
* @example
|
|
1967
|
+
* const signature_3 = toSignature({
|
|
1968
|
+
* name: 'ownerOf',
|
|
1969
|
+
* type: 'function',
|
|
1970
|
+
* inputs: [{ name: 'tokenId', type: 'uint256' }],
|
|
1971
|
+
* outputs: [],
|
|
1972
|
+
* stateMutability: 'view',
|
|
1973
|
+
* })
|
|
1974
|
+
* // 'ownerOf(uint256)'
|
|
1975
|
+
*/
|
|
1976
|
+
const toSignature = (def) => {
|
|
1977
|
+
const def_ = (() => {
|
|
1978
|
+
if (typeof def === 'string')
|
|
1979
|
+
return def;
|
|
1980
|
+
return formatAbiItem$1(def);
|
|
1981
|
+
})();
|
|
1982
|
+
return normalizeSignature(def_);
|
|
1983
|
+
};
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Returns the hash (of the function/event signature) for a given event or function definition.
|
|
1987
|
+
*/
|
|
1988
|
+
function toSignatureHash(fn) {
|
|
1989
|
+
return hashSignature(toSignature(fn));
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
/**
|
|
1993
|
+
* Returns the event selector for a given event definition.
|
|
1994
|
+
*
|
|
1995
|
+
* @example
|
|
1996
|
+
* const selector = toEventSelector('Transfer(address indexed from, address indexed to, uint256 amount)')
|
|
1997
|
+
* // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
|
|
1998
|
+
*/
|
|
1999
|
+
const toEventSelector = toSignatureHash;
|
|
2000
|
+
|
|
2001
|
+
class InvalidAddressError extends BaseError {
|
|
2002
|
+
constructor({ address }) {
|
|
2003
|
+
super(`Address "${address}" is invalid.`, {
|
|
2004
|
+
metaMessages: [
|
|
2005
|
+
'- Address must be a hex value of 20 bytes (40 hex characters).',
|
|
2006
|
+
'- Address must match its checksum counterpart.',
|
|
2007
|
+
],
|
|
2008
|
+
name: 'InvalidAddressError',
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
/**
|
|
2014
|
+
* Map with a LRU (Least recently used) policy.
|
|
2015
|
+
*
|
|
2016
|
+
* @link https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU
|
|
2017
|
+
*/
|
|
2018
|
+
class LruMap extends Map {
|
|
2019
|
+
constructor(size) {
|
|
2020
|
+
super();
|
|
2021
|
+
Object.defineProperty(this, "maxSize", {
|
|
2022
|
+
enumerable: true,
|
|
2023
|
+
configurable: true,
|
|
2024
|
+
writable: true,
|
|
2025
|
+
value: void 0
|
|
2026
|
+
});
|
|
2027
|
+
this.maxSize = size;
|
|
2028
|
+
}
|
|
2029
|
+
get(key) {
|
|
2030
|
+
const value = super.get(key);
|
|
2031
|
+
if (super.has(key)) {
|
|
2032
|
+
super.delete(key);
|
|
2033
|
+
super.set(key, value);
|
|
2034
|
+
}
|
|
2035
|
+
return value;
|
|
2036
|
+
}
|
|
2037
|
+
set(key, value) {
|
|
2038
|
+
if (super.has(key))
|
|
2039
|
+
super.delete(key);
|
|
2040
|
+
super.set(key, value);
|
|
2041
|
+
if (this.maxSize && this.size > this.maxSize) {
|
|
2042
|
+
const firstKey = super.keys().next().value;
|
|
2043
|
+
if (firstKey !== undefined)
|
|
2044
|
+
super.delete(firstKey);
|
|
2045
|
+
}
|
|
2046
|
+
return this;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
const checksumAddressCache = /*#__PURE__*/ new LruMap(8192);
|
|
2051
|
+
function checksumAddress(address_,
|
|
2052
|
+
/**
|
|
2053
|
+
* Warning: EIP-1191 checksum addresses are generally not backwards compatible with the
|
|
2054
|
+
* wider Ethereum ecosystem, meaning it will break when validated against an application/tool
|
|
2055
|
+
* that relies on EIP-55 checksum encoding (checksum without chainId).
|
|
2056
|
+
*
|
|
2057
|
+
* It is highly recommended to not use this feature unless you
|
|
2058
|
+
* know what you are doing.
|
|
2059
|
+
*
|
|
2060
|
+
* See more: https://github.com/ethereum/EIPs/issues/1121
|
|
2061
|
+
*/
|
|
2062
|
+
chainId) {
|
|
2063
|
+
if (checksumAddressCache.has(`${address_}.${chainId}`))
|
|
2064
|
+
return checksumAddressCache.get(`${address_}.${chainId}`);
|
|
2065
|
+
const hexAddress = address_.substring(2).toLowerCase();
|
|
2066
|
+
const hash = keccak256(stringToBytes(hexAddress), 'bytes');
|
|
2067
|
+
const address = (hexAddress).split('');
|
|
2068
|
+
for (let i = 0; i < 40; i += 2) {
|
|
2069
|
+
if (hash[i >> 1] >> 4 >= 8 && address[i]) {
|
|
2070
|
+
address[i] = address[i].toUpperCase();
|
|
2071
|
+
}
|
|
2072
|
+
if ((hash[i >> 1] & 0x0f) >= 8 && address[i + 1]) {
|
|
2073
|
+
address[i + 1] = address[i + 1].toUpperCase();
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
const result = `0x${address.join('')}`;
|
|
2077
|
+
checksumAddressCache.set(`${address_}.${chainId}`, result);
|
|
2078
|
+
return result;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
const addressRegex = /^0x[a-fA-F0-9]{40}$/;
|
|
2082
|
+
/** @internal */
|
|
2083
|
+
const isAddressCache = /*#__PURE__*/ new LruMap(8192);
|
|
2084
|
+
function isAddress(address, options) {
|
|
2085
|
+
const { strict = true } = options ?? {};
|
|
2086
|
+
const cacheKey = `${address}.${strict}`;
|
|
2087
|
+
if (isAddressCache.has(cacheKey))
|
|
2088
|
+
return isAddressCache.get(cacheKey);
|
|
2089
|
+
const result = (() => {
|
|
2090
|
+
if (!addressRegex.test(address))
|
|
2091
|
+
return false;
|
|
2092
|
+
if (address.toLowerCase() === address)
|
|
2093
|
+
return true;
|
|
2094
|
+
if (strict)
|
|
2095
|
+
return checksumAddress(address) === address;
|
|
2096
|
+
return true;
|
|
2097
|
+
})();
|
|
2098
|
+
isAddressCache.set(cacheKey, result);
|
|
2099
|
+
return result;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
function concat(values) {
|
|
2103
|
+
if (typeof values[0] === 'string')
|
|
2104
|
+
return concatHex(values);
|
|
2105
|
+
return concatBytes(values);
|
|
2106
|
+
}
|
|
2107
|
+
function concatBytes(values) {
|
|
2108
|
+
let length = 0;
|
|
2109
|
+
for (const arr of values) {
|
|
2110
|
+
length += arr.length;
|
|
2111
|
+
}
|
|
2112
|
+
const result = new Uint8Array(length);
|
|
2113
|
+
let offset = 0;
|
|
2114
|
+
for (const arr of values) {
|
|
2115
|
+
result.set(arr, offset);
|
|
2116
|
+
offset += arr.length;
|
|
2117
|
+
}
|
|
2118
|
+
return result;
|
|
2119
|
+
}
|
|
2120
|
+
function concatHex(values) {
|
|
2121
|
+
return `0x${values.reduce((acc, x) => acc + x.replace('0x', ''), '')}`;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
/**
|
|
2125
|
+
* @description Returns a section of the hex or byte array given a start/end bytes offset.
|
|
2126
|
+
*
|
|
2127
|
+
* @param value The hex or byte array to slice.
|
|
2128
|
+
* @param start The start offset (in bytes).
|
|
2129
|
+
* @param end The end offset (in bytes).
|
|
2130
|
+
*/
|
|
2131
|
+
function slice(value, start, end, { strict } = {}) {
|
|
2132
|
+
if (isHex(value, { strict: false }))
|
|
2133
|
+
return sliceHex(value, start, end, {
|
|
2134
|
+
strict,
|
|
2135
|
+
});
|
|
2136
|
+
return sliceBytes(value, start, end, {
|
|
2137
|
+
strict,
|
|
2138
|
+
});
|
|
2139
|
+
}
|
|
2140
|
+
function assertStartOffset(value, start) {
|
|
2141
|
+
if (typeof start === 'number' && start > 0 && start > size(value) - 1)
|
|
2142
|
+
throw new SliceOffsetOutOfBoundsError({
|
|
2143
|
+
offset: start,
|
|
2144
|
+
position: 'start',
|
|
2145
|
+
size: size(value),
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
function assertEndOffset(value, start, end) {
|
|
2149
|
+
if (typeof start === 'number' &&
|
|
2150
|
+
typeof end === 'number' &&
|
|
2151
|
+
size(value) !== end - start) {
|
|
2152
|
+
throw new SliceOffsetOutOfBoundsError({
|
|
2153
|
+
offset: end,
|
|
2154
|
+
position: 'end',
|
|
2155
|
+
size: size(value),
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* @description Returns a section of the byte array given a start/end bytes offset.
|
|
2161
|
+
*
|
|
2162
|
+
* @param value The byte array to slice.
|
|
2163
|
+
* @param start The start offset (in bytes).
|
|
2164
|
+
* @param end The end offset (in bytes).
|
|
2165
|
+
*/
|
|
2166
|
+
function sliceBytes(value_, start, end, { strict } = {}) {
|
|
2167
|
+
assertStartOffset(value_, start);
|
|
2168
|
+
const value = value_.slice(start, end);
|
|
2169
|
+
if (strict)
|
|
2170
|
+
assertEndOffset(value, start, end);
|
|
2171
|
+
return value;
|
|
2172
|
+
}
|
|
2173
|
+
/**
|
|
2174
|
+
* @description Returns a section of the hex value given a start/end bytes offset.
|
|
2175
|
+
*
|
|
2176
|
+
* @param value The hex value to slice.
|
|
2177
|
+
* @param start The start offset (in bytes).
|
|
2178
|
+
* @param end The end offset (in bytes).
|
|
2179
|
+
*/
|
|
2180
|
+
function sliceHex(value_, start, end, { strict } = {}) {
|
|
2181
|
+
assertStartOffset(value_, start);
|
|
2182
|
+
const value = `0x${value_
|
|
2183
|
+
.replace('0x', '')
|
|
2184
|
+
.slice((start ?? 0) * 2, (end ?? value_.length) * 2)}`;
|
|
2185
|
+
if (strict)
|
|
2186
|
+
assertEndOffset(value, start, end);
|
|
2187
|
+
return value;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
// `(u)int<M>`: (un)signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`
|
|
2191
|
+
// https://regexr.com/6v8hp
|
|
2192
|
+
const integerRegex = /^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/;
|
|
2193
|
+
|
|
2194
|
+
/**
|
|
2195
|
+
* @description Encodes a list of primitive values into an ABI-encoded hex value.
|
|
2196
|
+
*
|
|
2197
|
+
* - Docs: https://viem.sh/docs/abi/encodeAbiParameters#encodeabiparameters
|
|
2198
|
+
*
|
|
2199
|
+
* Generates ABI encoded data using the [ABI specification](https://docs.soliditylang.org/en/latest/abi-spec), given a set of ABI parameters (inputs/outputs) and their corresponding values.
|
|
2200
|
+
*
|
|
2201
|
+
* @param params - a set of ABI Parameters (params), that can be in the shape of the inputs or outputs attribute of an ABI Item.
|
|
2202
|
+
* @param values - a set of values (values) that correspond to the given params.
|
|
2203
|
+
* @example
|
|
2204
|
+
* ```typescript
|
|
2205
|
+
* import { encodeAbiParameters } from 'viem'
|
|
2206
|
+
*
|
|
2207
|
+
* const encodedData = encodeAbiParameters(
|
|
2208
|
+
* [
|
|
2209
|
+
* { name: 'x', type: 'string' },
|
|
2210
|
+
* { name: 'y', type: 'uint' },
|
|
2211
|
+
* { name: 'z', type: 'bool' }
|
|
2212
|
+
* ],
|
|
2213
|
+
* ['wagmi', 420n, true]
|
|
2214
|
+
* )
|
|
2215
|
+
* ```
|
|
2216
|
+
*
|
|
2217
|
+
* You can also pass in Human Readable parameters with the parseAbiParameters utility.
|
|
2218
|
+
*
|
|
2219
|
+
* @example
|
|
2220
|
+
* ```typescript
|
|
2221
|
+
* import { encodeAbiParameters, parseAbiParameters } from 'viem'
|
|
2222
|
+
*
|
|
2223
|
+
* const encodedData = encodeAbiParameters(
|
|
2224
|
+
* parseAbiParameters('string x, uint y, bool z'),
|
|
2225
|
+
* ['wagmi', 420n, true]
|
|
2226
|
+
* )
|
|
2227
|
+
* ```
|
|
2228
|
+
*/
|
|
2229
|
+
function encodeAbiParameters(params, values) {
|
|
2230
|
+
if (params.length !== values.length)
|
|
2231
|
+
throw new AbiEncodingLengthMismatchError({
|
|
2232
|
+
expectedLength: params.length,
|
|
2233
|
+
givenLength: values.length,
|
|
2234
|
+
});
|
|
2235
|
+
// Prepare the parameters to determine dynamic types to encode.
|
|
2236
|
+
const preparedParams = prepareParams({
|
|
2237
|
+
params: params,
|
|
2238
|
+
values: values,
|
|
2239
|
+
});
|
|
2240
|
+
const data = encodeParams(preparedParams);
|
|
2241
|
+
if (data.length === 0)
|
|
2242
|
+
return '0x';
|
|
2243
|
+
return data;
|
|
2244
|
+
}
|
|
2245
|
+
function prepareParams({ params, values, }) {
|
|
2246
|
+
const preparedParams = [];
|
|
2247
|
+
for (let i = 0; i < params.length; i++) {
|
|
2248
|
+
preparedParams.push(prepareParam({ param: params[i], value: values[i] }));
|
|
2249
|
+
}
|
|
2250
|
+
return preparedParams;
|
|
2251
|
+
}
|
|
2252
|
+
function prepareParam({ param, value, }) {
|
|
2253
|
+
const arrayComponents = getArrayComponents(param.type);
|
|
2254
|
+
if (arrayComponents) {
|
|
2255
|
+
const [length, type] = arrayComponents;
|
|
2256
|
+
return encodeArray(value, { length, param: { ...param, type } });
|
|
2257
|
+
}
|
|
2258
|
+
if (param.type === 'tuple') {
|
|
2259
|
+
return encodeTuple(value, {
|
|
2260
|
+
param: param,
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
if (param.type === 'address') {
|
|
2264
|
+
return encodeAddress(value);
|
|
2265
|
+
}
|
|
2266
|
+
if (param.type === 'bool') {
|
|
2267
|
+
return encodeBool(value);
|
|
2268
|
+
}
|
|
2269
|
+
if (param.type.startsWith('uint') || param.type.startsWith('int')) {
|
|
2270
|
+
const signed = param.type.startsWith('int');
|
|
2271
|
+
const [, , size = '256'] = integerRegex.exec(param.type) ?? [];
|
|
2272
|
+
return encodeNumber(value, {
|
|
2273
|
+
signed,
|
|
2274
|
+
size: Number(size),
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
if (param.type.startsWith('bytes')) {
|
|
2278
|
+
return encodeBytes(value, { param });
|
|
2279
|
+
}
|
|
2280
|
+
if (param.type === 'string') {
|
|
2281
|
+
return encodeString(value);
|
|
2282
|
+
}
|
|
2283
|
+
throw new InvalidAbiEncodingTypeError(param.type, {
|
|
2284
|
+
docsPath: '/docs/contract/encodeAbiParameters',
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
function encodeParams(preparedParams) {
|
|
2288
|
+
// 1. Compute the size of the static part of the parameters.
|
|
2289
|
+
let staticSize = 0;
|
|
2290
|
+
for (let i = 0; i < preparedParams.length; i++) {
|
|
2291
|
+
const { dynamic, encoded } = preparedParams[i];
|
|
2292
|
+
if (dynamic)
|
|
2293
|
+
staticSize += 32;
|
|
2294
|
+
else
|
|
2295
|
+
staticSize += size(encoded);
|
|
2296
|
+
}
|
|
2297
|
+
// 2. Split the parameters into static and dynamic parts.
|
|
2298
|
+
const staticParams = [];
|
|
2299
|
+
const dynamicParams = [];
|
|
2300
|
+
let dynamicSize = 0;
|
|
2301
|
+
for (let i = 0; i < preparedParams.length; i++) {
|
|
2302
|
+
const { dynamic, encoded } = preparedParams[i];
|
|
2303
|
+
if (dynamic) {
|
|
2304
|
+
staticParams.push(numberToHex(staticSize + dynamicSize, { size: 32 }));
|
|
2305
|
+
dynamicParams.push(encoded);
|
|
2306
|
+
dynamicSize += size(encoded);
|
|
2307
|
+
}
|
|
2308
|
+
else {
|
|
2309
|
+
staticParams.push(encoded);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
// 3. Concatenate static and dynamic parts.
|
|
2313
|
+
return concat([...staticParams, ...dynamicParams]);
|
|
2314
|
+
}
|
|
2315
|
+
function encodeAddress(value) {
|
|
2316
|
+
if (!isAddress(value))
|
|
2317
|
+
throw new InvalidAddressError({ address: value });
|
|
2318
|
+
return { dynamic: false, encoded: padHex(value.toLowerCase()) };
|
|
2319
|
+
}
|
|
2320
|
+
function encodeArray(value, { length, param, }) {
|
|
2321
|
+
const dynamic = length === null;
|
|
2322
|
+
if (!Array.isArray(value))
|
|
2323
|
+
throw new InvalidArrayError(value);
|
|
2324
|
+
if (!dynamic && value.length !== length)
|
|
2325
|
+
throw new AbiEncodingArrayLengthMismatchError({
|
|
2326
|
+
expectedLength: length,
|
|
2327
|
+
givenLength: value.length,
|
|
2328
|
+
type: `${param.type}[${length}]`,
|
|
2329
|
+
});
|
|
2330
|
+
let dynamicChild = false;
|
|
2331
|
+
const preparedParams = [];
|
|
2332
|
+
for (let i = 0; i < value.length; i++) {
|
|
2333
|
+
const preparedParam = prepareParam({ param, value: value[i] });
|
|
2334
|
+
if (preparedParam.dynamic)
|
|
2335
|
+
dynamicChild = true;
|
|
2336
|
+
preparedParams.push(preparedParam);
|
|
2337
|
+
}
|
|
2338
|
+
if (dynamic || dynamicChild) {
|
|
2339
|
+
const data = encodeParams(preparedParams);
|
|
2340
|
+
if (dynamic) {
|
|
2341
|
+
const length = numberToHex(preparedParams.length, { size: 32 });
|
|
2342
|
+
return {
|
|
2343
|
+
dynamic: true,
|
|
2344
|
+
encoded: preparedParams.length > 0 ? concat([length, data]) : length,
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
if (dynamicChild)
|
|
2348
|
+
return { dynamic: true, encoded: data };
|
|
2349
|
+
}
|
|
2350
|
+
return {
|
|
2351
|
+
dynamic: false,
|
|
2352
|
+
encoded: concat(preparedParams.map(({ encoded }) => encoded)),
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
function encodeBytes(value, { param }) {
|
|
2356
|
+
const [, paramSize] = param.type.split('bytes');
|
|
2357
|
+
const bytesSize = size(value);
|
|
2358
|
+
if (!paramSize) {
|
|
2359
|
+
let value_ = value;
|
|
2360
|
+
// If the size is not divisible by 32 bytes, pad the end
|
|
2361
|
+
// with empty bytes to the ceiling 32 bytes.
|
|
2362
|
+
if (bytesSize % 32 !== 0)
|
|
2363
|
+
value_ = padHex(value_, {
|
|
2364
|
+
dir: 'right',
|
|
2365
|
+
size: Math.ceil((value.length - 2) / 2 / 32) * 32,
|
|
2366
|
+
});
|
|
2367
|
+
return {
|
|
2368
|
+
dynamic: true,
|
|
2369
|
+
encoded: concat([padHex(numberToHex(bytesSize, { size: 32 })), value_]),
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
if (bytesSize !== Number.parseInt(paramSize, 10))
|
|
2373
|
+
throw new AbiEncodingBytesSizeMismatchError({
|
|
2374
|
+
expectedSize: Number.parseInt(paramSize, 10),
|
|
2375
|
+
value,
|
|
2376
|
+
});
|
|
2377
|
+
return { dynamic: false, encoded: padHex(value, { dir: 'right' }) };
|
|
2378
|
+
}
|
|
2379
|
+
function encodeBool(value) {
|
|
2380
|
+
if (typeof value !== 'boolean')
|
|
2381
|
+
throw new BaseError(`Invalid boolean value: "${value}" (type: ${typeof value}). Expected: \`true\` or \`false\`.`);
|
|
2382
|
+
return { dynamic: false, encoded: padHex(boolToHex(value)) };
|
|
2383
|
+
}
|
|
2384
|
+
function encodeNumber(value, { signed, size = 256 }) {
|
|
2385
|
+
if (typeof size === 'number') {
|
|
2386
|
+
const max = 2n ** (BigInt(size) - (signed ? 1n : 0n)) - 1n;
|
|
2387
|
+
const min = signed ? -max - 1n : 0n;
|
|
2388
|
+
if (value > max || value < min)
|
|
2389
|
+
throw new IntegerOutOfRangeError({
|
|
2390
|
+
max: max.toString(),
|
|
2391
|
+
min: min.toString(),
|
|
2392
|
+
signed,
|
|
2393
|
+
size: size / 8,
|
|
2394
|
+
value: value.toString(),
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
return {
|
|
2398
|
+
dynamic: false,
|
|
2399
|
+
encoded: numberToHex(value, {
|
|
2400
|
+
size: 32,
|
|
2401
|
+
signed,
|
|
2402
|
+
}),
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
function encodeString(value) {
|
|
2406
|
+
const hexValue = stringToHex(value);
|
|
2407
|
+
const partsLength = Math.ceil(size(hexValue) / 32);
|
|
2408
|
+
const parts = [];
|
|
2409
|
+
for (let i = 0; i < partsLength; i++) {
|
|
2410
|
+
parts.push(padHex(slice(hexValue, i * 32, (i + 1) * 32), {
|
|
2411
|
+
dir: 'right',
|
|
2412
|
+
}));
|
|
2413
|
+
}
|
|
2414
|
+
return {
|
|
2415
|
+
dynamic: true,
|
|
2416
|
+
encoded: concat([
|
|
2417
|
+
padHex(numberToHex(size(hexValue), { size: 32 })),
|
|
2418
|
+
...parts,
|
|
2419
|
+
]),
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
function encodeTuple(value, { param }) {
|
|
2423
|
+
let dynamic = false;
|
|
2424
|
+
const preparedParams = [];
|
|
2425
|
+
for (let i = 0; i < param.components.length; i++) {
|
|
2426
|
+
const param_ = param.components[i];
|
|
2427
|
+
const index = Array.isArray(value) ? i : param_.name;
|
|
2428
|
+
const preparedParam = prepareParam({
|
|
2429
|
+
param: param_,
|
|
2430
|
+
value: value[index],
|
|
2431
|
+
});
|
|
2432
|
+
preparedParams.push(preparedParam);
|
|
2433
|
+
if (preparedParam.dynamic)
|
|
2434
|
+
dynamic = true;
|
|
2435
|
+
}
|
|
2436
|
+
return {
|
|
2437
|
+
dynamic,
|
|
2438
|
+
encoded: dynamic
|
|
2439
|
+
? encodeParams(preparedParams)
|
|
2440
|
+
: concat(preparedParams.map(({ encoded }) => encoded)),
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
function getArrayComponents(type) {
|
|
2444
|
+
const matches = type.match(/^(.*)\[(\d+)?\]$/);
|
|
2445
|
+
return matches
|
|
2446
|
+
? // Return `null` if the array is dynamic.
|
|
2447
|
+
[matches[2] ? Number(matches[2]) : null, matches[1]]
|
|
2448
|
+
: undefined;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
/**
|
|
2452
|
+
* Returns the function selector for a given function definition.
|
|
2453
|
+
*
|
|
2454
|
+
* @example
|
|
2455
|
+
* const selector = toFunctionSelector('function ownerOf(uint256 tokenId)')
|
|
2456
|
+
* // 0x6352211e
|
|
2457
|
+
*/
|
|
2458
|
+
const toFunctionSelector = (fn) => slice(toSignatureHash(fn), 0, 4);
|
|
2459
|
+
|
|
2460
|
+
function getAbiItem(parameters) {
|
|
2461
|
+
const { abi, args = [], name } = parameters;
|
|
2462
|
+
const isSelector = isHex(name, { strict: false });
|
|
2463
|
+
const abiItems = abi.filter((abiItem) => {
|
|
2464
|
+
if (isSelector) {
|
|
2465
|
+
if (abiItem.type === 'function')
|
|
2466
|
+
return toFunctionSelector(abiItem) === name;
|
|
2467
|
+
if (abiItem.type === 'event')
|
|
2468
|
+
return toEventSelector(abiItem) === name;
|
|
2469
|
+
return false;
|
|
2470
|
+
}
|
|
2471
|
+
return 'name' in abiItem && abiItem.name === name;
|
|
2472
|
+
});
|
|
2473
|
+
if (abiItems.length === 0)
|
|
2474
|
+
return undefined;
|
|
2475
|
+
if (abiItems.length === 1)
|
|
2476
|
+
return abiItems[0];
|
|
2477
|
+
let matchedAbiItem;
|
|
2478
|
+
for (const abiItem of abiItems) {
|
|
2479
|
+
if (!('inputs' in abiItem))
|
|
2480
|
+
continue;
|
|
2481
|
+
if (!args || args.length === 0) {
|
|
2482
|
+
if (!abiItem.inputs || abiItem.inputs.length === 0)
|
|
2483
|
+
return abiItem;
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
if (!abiItem.inputs)
|
|
2487
|
+
continue;
|
|
2488
|
+
if (abiItem.inputs.length === 0)
|
|
2489
|
+
continue;
|
|
2490
|
+
if (abiItem.inputs.length !== args.length)
|
|
2491
|
+
continue;
|
|
2492
|
+
const matched = args.every((arg, index) => {
|
|
2493
|
+
const abiParameter = 'inputs' in abiItem && abiItem.inputs[index];
|
|
2494
|
+
if (!abiParameter)
|
|
2495
|
+
return false;
|
|
2496
|
+
return isArgOfType(arg, abiParameter);
|
|
2497
|
+
});
|
|
2498
|
+
if (matched) {
|
|
2499
|
+
// Check for ambiguity against already matched parameters (e.g. `address` vs `bytes20`).
|
|
2500
|
+
if (matchedAbiItem &&
|
|
2501
|
+
'inputs' in matchedAbiItem &&
|
|
2502
|
+
matchedAbiItem.inputs) {
|
|
2503
|
+
const ambiguousTypes = getAmbiguousTypes(abiItem.inputs, matchedAbiItem.inputs, args);
|
|
2504
|
+
if (ambiguousTypes)
|
|
2505
|
+
throw new AbiItemAmbiguityError({
|
|
2506
|
+
abiItem,
|
|
2507
|
+
type: ambiguousTypes[0],
|
|
2508
|
+
}, {
|
|
2509
|
+
abiItem: matchedAbiItem,
|
|
2510
|
+
type: ambiguousTypes[1],
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
matchedAbiItem = abiItem;
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
if (matchedAbiItem)
|
|
2517
|
+
return matchedAbiItem;
|
|
2518
|
+
return abiItems[0];
|
|
2519
|
+
}
|
|
2520
|
+
/** @internal */
|
|
2521
|
+
function isArgOfType(arg, abiParameter) {
|
|
2522
|
+
const argType = typeof arg;
|
|
2523
|
+
const abiParameterType = abiParameter.type;
|
|
2524
|
+
switch (abiParameterType) {
|
|
2525
|
+
case 'address':
|
|
2526
|
+
return isAddress(arg, { strict: false });
|
|
2527
|
+
case 'bool':
|
|
2528
|
+
return argType === 'boolean';
|
|
2529
|
+
case 'function':
|
|
2530
|
+
return argType === 'string';
|
|
2531
|
+
case 'string':
|
|
2532
|
+
return argType === 'string';
|
|
2533
|
+
default: {
|
|
2534
|
+
if (abiParameterType === 'tuple' && 'components' in abiParameter)
|
|
2535
|
+
return Object.values(abiParameter.components).every((component, index) => {
|
|
2536
|
+
return (argType === 'object' &&
|
|
2537
|
+
isArgOfType(Object.values(arg)[index], component));
|
|
2538
|
+
});
|
|
2539
|
+
// `(u)int<M>`: (un)signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`
|
|
2540
|
+
// https://regexr.com/6v8hp
|
|
2541
|
+
if (/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(abiParameterType))
|
|
2542
|
+
return argType === 'number' || argType === 'bigint';
|
|
2543
|
+
// `bytes<M>`: binary type of `M` bytes, `0 < M <= 32`
|
|
2544
|
+
// https://regexr.com/6va55
|
|
2545
|
+
if (/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(abiParameterType))
|
|
2546
|
+
return argType === 'string' || arg instanceof Uint8Array;
|
|
2547
|
+
// fixed-length (`<type>[M]`) and dynamic (`<type>[]`) arrays
|
|
2548
|
+
// https://regexr.com/6va6i
|
|
2549
|
+
if (/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(abiParameterType)) {
|
|
2550
|
+
return (Array.isArray(arg) &&
|
|
2551
|
+
arg.every((x) => isArgOfType(x, {
|
|
2552
|
+
...abiParameter,
|
|
2553
|
+
// Pop off `[]` or `[M]` from end of type
|
|
2554
|
+
type: abiParameterType.replace(/(\[[0-9]{0,}\])$/, ''),
|
|
2555
|
+
})));
|
|
2556
|
+
}
|
|
2557
|
+
return false;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
/** @internal */
|
|
2562
|
+
function getAmbiguousTypes(sourceParameters, targetParameters, args) {
|
|
2563
|
+
for (const parameterIndex in sourceParameters) {
|
|
2564
|
+
const sourceParameter = sourceParameters[parameterIndex];
|
|
2565
|
+
const targetParameter = targetParameters[parameterIndex];
|
|
2566
|
+
if (sourceParameter.type === 'tuple' &&
|
|
2567
|
+
targetParameter.type === 'tuple' &&
|
|
2568
|
+
'components' in sourceParameter &&
|
|
2569
|
+
'components' in targetParameter)
|
|
2570
|
+
return getAmbiguousTypes(sourceParameter.components, targetParameter.components, args[parameterIndex]);
|
|
2571
|
+
const types = [sourceParameter.type, targetParameter.type];
|
|
2572
|
+
const ambiguous = (() => {
|
|
2573
|
+
if (types.includes('address') && types.includes('bytes20'))
|
|
2574
|
+
return true;
|
|
2575
|
+
if (types.includes('address') && types.includes('string'))
|
|
2576
|
+
return isAddress(args[parameterIndex], { strict: false });
|
|
2577
|
+
if (types.includes('address') && types.includes('bytes'))
|
|
2578
|
+
return isAddress(args[parameterIndex], { strict: false });
|
|
2579
|
+
return false;
|
|
2580
|
+
})();
|
|
2581
|
+
if (ambiguous)
|
|
2582
|
+
return types;
|
|
2583
|
+
}
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
const docsPath = '/docs/contract/encodeFunctionData';
|
|
2588
|
+
function prepareEncodeFunctionData(parameters) {
|
|
2589
|
+
const { abi, args, functionName } = parameters;
|
|
2590
|
+
let abiItem = abi[0];
|
|
2591
|
+
if (functionName) {
|
|
2592
|
+
const item = getAbiItem({
|
|
2593
|
+
abi,
|
|
2594
|
+
args,
|
|
2595
|
+
name: functionName,
|
|
2596
|
+
});
|
|
2597
|
+
if (!item)
|
|
2598
|
+
throw new AbiFunctionNotFoundError(functionName, { docsPath });
|
|
2599
|
+
abiItem = item;
|
|
2600
|
+
}
|
|
2601
|
+
if (abiItem.type !== 'function')
|
|
2602
|
+
throw new AbiFunctionNotFoundError(undefined, { docsPath });
|
|
2603
|
+
return {
|
|
2604
|
+
abi: [abiItem],
|
|
2605
|
+
functionName: toFunctionSelector(formatAbiItem(abiItem)),
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
function encodeFunctionData(parameters) {
|
|
2610
|
+
const { args } = parameters;
|
|
2611
|
+
const { abi, functionName } = (() => {
|
|
2612
|
+
if (parameters.abi.length === 1 &&
|
|
2613
|
+
parameters.functionName?.startsWith('0x'))
|
|
2614
|
+
return parameters;
|
|
2615
|
+
return prepareEncodeFunctionData(parameters);
|
|
2616
|
+
})();
|
|
2617
|
+
const abiItem = abi[0];
|
|
2618
|
+
const signature = functionName;
|
|
2619
|
+
const data = 'inputs' in abiItem && abiItem.inputs
|
|
2620
|
+
? encodeAbiParameters(abiItem.inputs, args ?? [])
|
|
2621
|
+
: undefined;
|
|
2622
|
+
return concatHex([signature, data ?? '0x']);
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
const ZERO_ADDRESS$1 = "0x0000000000000000000000000000000000000000";
|
|
2626
|
+
const PAYMENT_ABI = parseAbi([
|
|
2627
|
+
"function createPayment(bytes32 paymentId, string vendorId, address token, uint256 amount, bytes backendSignature) external payable",
|
|
2628
|
+
]);
|
|
2629
|
+
const ERC20_ABI = parseAbi([
|
|
2630
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
2631
|
+
"function approve(address spender, uint256 amount) returns (bool)",
|
|
2632
|
+
]);
|
|
2633
|
+
function ensure0x(hex) {
|
|
2634
|
+
return (hex.startsWith("0x") ? hex : `0x${hex}`);
|
|
2635
|
+
}
|
|
2636
|
+
class ContractService {
|
|
2637
|
+
constructor(provider) {
|
|
2638
|
+
this.provider = provider;
|
|
2639
|
+
}
|
|
2640
|
+
async getAddress() {
|
|
2641
|
+
const accounts = (await this.provider.request({
|
|
2642
|
+
method: "eth_requestAccounts",
|
|
2643
|
+
}));
|
|
2644
|
+
if (!accounts[0]) {
|
|
2645
|
+
throw new KwesPayError("No wallet account available", "WALLET_REJECTED");
|
|
2646
|
+
}
|
|
2647
|
+
return accounts[0];
|
|
2648
|
+
}
|
|
2649
|
+
async waitForReceipt(txHash, timeoutMs = 120000) {
|
|
2650
|
+
const deadline = Date.now() + timeoutMs;
|
|
2651
|
+
while (Date.now() < deadline) {
|
|
2652
|
+
const receipt = (await this.provider.request({
|
|
2653
|
+
method: "eth_getTransactionReceipt",
|
|
2654
|
+
params: [txHash],
|
|
2655
|
+
}));
|
|
2656
|
+
if (receipt) {
|
|
2657
|
+
return {
|
|
2658
|
+
blockNumber: parseInt(receipt.blockNumber, 16),
|
|
2659
|
+
status: parseInt(receipt.status, 16),
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
2663
|
+
}
|
|
2664
|
+
throw new KwesPayError("Transaction receipt timeout — check wallet or block explorer", "CONTRACT_ERROR");
|
|
2665
|
+
}
|
|
2666
|
+
async ensureApproval(tokenAddress, amountBaseUnits, contractAddress, chainId, onStatus) {
|
|
2667
|
+
if (tokenAddress === ZERO_ADDRESS$1)
|
|
2668
|
+
return;
|
|
2669
|
+
onStatus?.("Checking approval", "Verifying token allowance…");
|
|
2670
|
+
const owner = await this.getAddress();
|
|
2671
|
+
const amount = BigInt(amountBaseUnits);
|
|
2672
|
+
const allowanceData = encodeFunctionData({
|
|
2673
|
+
abi: ERC20_ABI,
|
|
2674
|
+
functionName: "allowance",
|
|
2675
|
+
args: [owner, ensure0x(contractAddress)],
|
|
2676
|
+
});
|
|
2677
|
+
const rawAllowance = (await this.provider.request({
|
|
2678
|
+
method: "eth_call",
|
|
2679
|
+
params: [{ to: ensure0x(tokenAddress), data: allowanceData }, "latest"],
|
|
2680
|
+
}));
|
|
2681
|
+
const allowance = rawAllowance && rawAllowance !== "0x" ? BigInt(rawAllowance) : 0n;
|
|
2682
|
+
if (allowance >= amount)
|
|
2683
|
+
return;
|
|
2684
|
+
onStatus?.("Approve token", "Please approve in your wallet");
|
|
2685
|
+
const approveData = encodeFunctionData({
|
|
2686
|
+
abi: ERC20_ABI,
|
|
2687
|
+
functionName: "approve",
|
|
2688
|
+
args: [ensure0x(contractAddress), amount * 2n],
|
|
2689
|
+
});
|
|
2690
|
+
let txHash;
|
|
2691
|
+
try {
|
|
2692
|
+
txHash = (await this.provider.request({
|
|
2693
|
+
method: "eth_sendTransaction",
|
|
2694
|
+
params: [
|
|
2695
|
+
{ from: owner, to: ensure0x(tokenAddress), data: approveData },
|
|
2696
|
+
],
|
|
2697
|
+
}));
|
|
2698
|
+
}
|
|
2699
|
+
catch (err) {
|
|
2700
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2701
|
+
if (msg.includes("rejected") ||
|
|
2702
|
+
msg.includes("denied") ||
|
|
2703
|
+
msg.includes("ACTION_REJECTED")) {
|
|
2704
|
+
throw new KwesPayError("Token approval cancelled by user", "APPROVAL_REJECTED", err);
|
|
2705
|
+
}
|
|
2706
|
+
throw new KwesPayError(`Token approval failed: ${msg}`, "CONTRACT_ERROR", err);
|
|
2707
|
+
}
|
|
2708
|
+
onStatus?.("Waiting for approval", `Tx: ${txHash.slice(0, 10)}…`);
|
|
2709
|
+
const receipt = await this.waitForReceipt(txHash);
|
|
2710
|
+
if (receipt.status !== 1) {
|
|
2711
|
+
throw new KwesPayError("Approval transaction reverted", "CONTRACT_ERROR");
|
|
2712
|
+
}
|
|
2713
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
2714
|
+
}
|
|
2715
|
+
async createPayment(params, onStatus) {
|
|
2716
|
+
const from = await this.getAddress();
|
|
2717
|
+
const amount = BigInt(params.amountBaseUnits);
|
|
2718
|
+
const isNative = params.tokenAddress === ZERO_ADDRESS$1;
|
|
2719
|
+
const data = encodeFunctionData({
|
|
2720
|
+
abi: PAYMENT_ABI,
|
|
2721
|
+
functionName: "createPayment",
|
|
2722
|
+
args: [
|
|
2723
|
+
ensure0x(params.paymentIdBytes32),
|
|
2724
|
+
params.vendorIdentifier,
|
|
2725
|
+
ensure0x(params.tokenAddress),
|
|
2726
|
+
amount,
|
|
2727
|
+
ensure0x(params.backendSignature),
|
|
2728
|
+
],
|
|
2729
|
+
});
|
|
2730
|
+
onStatus?.("Confirm payment", "Please approve in your wallet");
|
|
2731
|
+
const txParams = {
|
|
2732
|
+
from,
|
|
2733
|
+
to: ensure0x(params.contractAddress),
|
|
2734
|
+
data,
|
|
2735
|
+
};
|
|
2736
|
+
if (isNative)
|
|
2737
|
+
txParams["value"] = `0x${amount.toString(16)}`;
|
|
2738
|
+
let txHash;
|
|
2739
|
+
try {
|
|
2740
|
+
txHash = (await this.provider.request({
|
|
2741
|
+
method: "eth_sendTransaction",
|
|
2742
|
+
params: [txParams],
|
|
2743
|
+
}));
|
|
2744
|
+
}
|
|
2745
|
+
catch (err) {
|
|
2746
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2747
|
+
if (msg.includes("rejected") ||
|
|
2748
|
+
msg.includes("denied") ||
|
|
2749
|
+
msg.includes("ACTION_REJECTED") ||
|
|
2750
|
+
msg.includes("User denied")) {
|
|
2751
|
+
throw new KwesPayError("Transaction cancelled by user", "WALLET_REJECTED", err);
|
|
2752
|
+
}
|
|
2753
|
+
throw new KwesPayError(`Contract call failed: ${msg}`, "CONTRACT_ERROR", err);
|
|
2754
|
+
}
|
|
2755
|
+
onStatus?.("Waiting for confirmation", `Tx: ${txHash.slice(0, 10)}…`);
|
|
2756
|
+
const receipt = await this.waitForReceipt(txHash);
|
|
2757
|
+
if (receipt.status !== 1) {
|
|
2758
|
+
throw new KwesPayError("Payment transaction reverted", "CONTRACT_ERROR");
|
|
2759
|
+
}
|
|
2760
|
+
return { txHash, blockNumber: receipt.blockNumber };
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
2765
|
+
const GAS_BUFFER = 300000n * 2000000000n;
|
|
2766
|
+
class PaymentService {
|
|
2767
|
+
constructor(provider) {
|
|
2768
|
+
this.provider = provider;
|
|
2769
|
+
this.contractService = new ContractService(provider);
|
|
2770
|
+
}
|
|
2771
|
+
async ensureCorrectNetwork(expectedChainId, expectedNetwork, onStatus) {
|
|
2772
|
+
const raw = (await this.provider.request({
|
|
2773
|
+
method: "eth_chainId",
|
|
2774
|
+
}));
|
|
2775
|
+
const current = parseInt(raw, 16);
|
|
2776
|
+
if (current === expectedChainId)
|
|
2777
|
+
return;
|
|
2778
|
+
onStatus?.("Switching network", `Switching to ${expectedNetwork} (chain ${expectedChainId})…`);
|
|
2779
|
+
try {
|
|
2780
|
+
await this.provider.request({
|
|
2781
|
+
method: "wallet_switchEthereumChain",
|
|
2782
|
+
params: [{ chainId: "0x" + expectedChainId.toString(16) }],
|
|
2783
|
+
});
|
|
2784
|
+
}
|
|
2785
|
+
catch (switchErr) {
|
|
2786
|
+
const errAny = switchErr;
|
|
2787
|
+
if (errAny?.code === 4902) {
|
|
2788
|
+
throw new KwesPayError(`Network ${expectedNetwork} (chain ${expectedChainId}) is not added to your wallet. Please add it manually and retry.`, "WRONG_NETWORK");
|
|
2789
|
+
}
|
|
2790
|
+
const msg = errAny?.message ?? String(switchErr);
|
|
2791
|
+
if (msg.includes("rejected") ||
|
|
2792
|
+
msg.includes("denied") ||
|
|
2793
|
+
msg.includes("ACTION_REJECTED")) {
|
|
2794
|
+
throw new KwesPayError(`Network switch to ${expectedNetwork} was rejected. Please switch manually and retry.`, "WRONG_NETWORK");
|
|
2795
|
+
}
|
|
2796
|
+
throw new KwesPayError(`Failed to switch to ${expectedNetwork}: ${msg}`, "WRONG_NETWORK");
|
|
2797
|
+
}
|
|
2798
|
+
const confirmedRaw = (await this.provider.request({
|
|
2799
|
+
method: "eth_chainId",
|
|
2800
|
+
}));
|
|
2801
|
+
const confirmed = parseInt(confirmedRaw, 16);
|
|
2802
|
+
if (confirmed !== expectedChainId) {
|
|
2803
|
+
throw new KwesPayError(`Wallet is on chain ${confirmed} but payment requires chain ${expectedChainId} (${expectedNetwork}). Please switch your network and retry.`, "WRONG_NETWORK");
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
async ensureSufficientBalance(walletAddress, tokenAddress, amountBaseUnits, onStatus) {
|
|
2807
|
+
const amount = BigInt(amountBaseUnits);
|
|
2808
|
+
const isNative = tokenAddress === ZERO_ADDRESS;
|
|
2809
|
+
onStatus?.("Checking balance", "Verifying wallet balance…");
|
|
2810
|
+
const nativeRaw = (await this.provider.request({
|
|
2811
|
+
method: "eth_getBalance",
|
|
2812
|
+
params: [walletAddress, "latest"],
|
|
2813
|
+
}));
|
|
2814
|
+
const nativeBalance = nativeRaw && nativeRaw !== "0x" ? BigInt(nativeRaw) : 0n;
|
|
2815
|
+
if (isNative) {
|
|
2816
|
+
const required = amount + GAS_BUFFER;
|
|
2817
|
+
if (nativeBalance < required) {
|
|
2818
|
+
throw new KwesPayError(`Insufficient native balance. Required ~${(Number(required) / 1e18).toFixed(8)} ETH (payment + gas), available: ${(Number(nativeBalance) / 1e18).toFixed(8)} ETH.`, "INSUFFICIENT_BALANCE");
|
|
2819
|
+
}
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
if (nativeBalance < GAS_BUFFER) {
|
|
2823
|
+
throw new KwesPayError(`Insufficient gas balance. Need at least ${(Number(GAS_BUFFER) / 1e18).toFixed(8)} ETH for gas, available: ${(Number(nativeBalance) / 1e18).toFixed(8)} ETH.`, "INSUFFICIENT_BALANCE");
|
|
2824
|
+
}
|
|
2825
|
+
const balanceData = "0x70a08231" + walletAddress.slice(2).toLowerCase().padStart(64, "0");
|
|
2826
|
+
const raw = (await this.provider.request({
|
|
2827
|
+
method: "eth_call",
|
|
2828
|
+
params: [{ to: tokenAddress, data: balanceData }, "latest"],
|
|
2829
|
+
}));
|
|
2830
|
+
const tokenBalance = raw && raw !== "0x" ? BigInt(raw) : 0n;
|
|
2831
|
+
if (tokenBalance < amount) {
|
|
2832
|
+
throw new KwesPayError(`Insufficient token balance. Required: ${amount.toString()}, available: ${tokenBalance.toString()} (base units).`, "INSUFFICIENT_BALANCE");
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
async pay(params) {
|
|
2836
|
+
const { provider, payload, onStatus } = params;
|
|
2837
|
+
if (!payload.paymentIdBytes32 || !payload.backendSignature) {
|
|
2838
|
+
throw new KwesPayError("Invalid transaction payload — missing paymentIdBytes32 or backendSignature", "TRANSACTION_FAILED");
|
|
2839
|
+
}
|
|
2840
|
+
if (!payload.vendorIdentifier) {
|
|
2841
|
+
throw new KwesPayError("Invalid transaction payload — missing vendorIdentifier", "TRANSACTION_FAILED");
|
|
2842
|
+
}
|
|
2843
|
+
await this.ensureCorrectNetwork(payload.chainId, payload.network, onStatus);
|
|
2844
|
+
const accounts = (await this.provider.request({
|
|
2845
|
+
method: "eth_requestAccounts",
|
|
2846
|
+
}));
|
|
2847
|
+
const walletAddress = accounts[0];
|
|
2848
|
+
await this.ensureSufficientBalance(walletAddress, payload.tokenAddress, payload.amountBaseUnits, onStatus);
|
|
2849
|
+
const contractAddress = resolveContractAddress(payload.network);
|
|
2850
|
+
await this.contractService.ensureApproval(payload.tokenAddress, payload.amountBaseUnits, contractAddress, payload.chainId, onStatus);
|
|
2851
|
+
const { txHash, blockNumber } = await this.contractService.createPayment({
|
|
2852
|
+
paymentIdBytes32: payload.paymentIdBytes32,
|
|
2853
|
+
vendorIdentifier: payload.vendorIdentifier,
|
|
2854
|
+
tokenAddress: payload.tokenAddress,
|
|
2855
|
+
amountBaseUnits: payload.amountBaseUnits,
|
|
2856
|
+
backendSignature: payload.backendSignature,
|
|
2857
|
+
contractAddress,
|
|
2858
|
+
chainId: payload.chainId,
|
|
2859
|
+
}, onStatus);
|
|
2860
|
+
return {
|
|
2861
|
+
txHash,
|
|
2862
|
+
blockNumber,
|
|
2863
|
+
transactionReference: payload.transactionReference,
|
|
2864
|
+
paymentIdBytes32: payload.paymentIdBytes32,
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
class KwesPayClient {
|
|
2870
|
+
constructor(config) {
|
|
2871
|
+
if (!config.apiKey)
|
|
2872
|
+
throw new KwesPayError("apiKey is required", "INVALID_KEY");
|
|
2873
|
+
this.apiKey = config.apiKey;
|
|
2874
|
+
}
|
|
2875
|
+
async validateKey() {
|
|
2876
|
+
const data = await gqlRequest(GQL_VALIDATE_KEY, {
|
|
2877
|
+
accessKey: this.apiKey,
|
|
2878
|
+
});
|
|
2879
|
+
const r = data.validateAccessKey;
|
|
2880
|
+
if (!r.isValid) {
|
|
2881
|
+
return {
|
|
2882
|
+
isValid: false,
|
|
2883
|
+
error: r.error ?? "Invalid access key",
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
return {
|
|
2887
|
+
isValid: true,
|
|
2888
|
+
keyId: r.keyId,
|
|
2889
|
+
keyLabel: r.keyLabel,
|
|
2890
|
+
activeFlag: r.activeFlag,
|
|
2891
|
+
expirationDate: r.expirationDate,
|
|
2892
|
+
vendorInfo: r.vendorInfo,
|
|
2893
|
+
scope: {
|
|
2894
|
+
allowedVendors: r.allowedVendors ?? null,
|
|
2895
|
+
allowedNetworks: r.allowedNetworks ?? null,
|
|
2896
|
+
allowedTokens: r.allowedTokens ?? null,
|
|
2897
|
+
},
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
async getQuote(params) {
|
|
2901
|
+
const data = await gqlRequest(GQL_CREATE_QUOTE, {
|
|
2902
|
+
input: {
|
|
2903
|
+
vendorIdentifier: params.vendorIdentifier,
|
|
2904
|
+
fiatAmount: params.fiatAmount,
|
|
2905
|
+
fiatCurrency: params.fiatCurrency ?? "USD",
|
|
2906
|
+
cryptoCurrency: params.cryptoCurrency,
|
|
2907
|
+
network: params.network,
|
|
2908
|
+
},
|
|
2909
|
+
}, this.apiKey);
|
|
2910
|
+
const q = data.createQuote;
|
|
2911
|
+
if (!q.success) {
|
|
2912
|
+
const msg = q.message ?? "Quote creation failed";
|
|
2913
|
+
const code = msg.toLowerCase().includes("expired")
|
|
2914
|
+
? "QUOTE_EXPIRED"
|
|
2915
|
+
: msg.toLowerCase().includes("key")
|
|
2916
|
+
? "INVALID_KEY"
|
|
2917
|
+
: "UNKNOWN";
|
|
2918
|
+
throw new KwesPayError(msg, code);
|
|
2919
|
+
}
|
|
2920
|
+
return {
|
|
2921
|
+
quoteId: q.quoteId,
|
|
2922
|
+
cryptoCurrency: q.cryptoCurrency,
|
|
2923
|
+
tokenAddress: q.tokenAddress,
|
|
2924
|
+
amountBaseUnits: q.amountBaseUnits,
|
|
2925
|
+
displayAmount: q.displayAmount,
|
|
2926
|
+
network: q.network,
|
|
2927
|
+
chainId: q.chainId,
|
|
2928
|
+
expiresAt: q.expiresAt,
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
async quote(params) {
|
|
2932
|
+
const quoteData = await gqlRequest(GQL_CREATE_QUOTE, {
|
|
2933
|
+
input: {
|
|
2934
|
+
vendorIdentifier: params.vendorIdentifier,
|
|
2935
|
+
fiatAmount: params.fiatAmount,
|
|
2936
|
+
fiatCurrency: params.fiatCurrency ?? "USD",
|
|
2937
|
+
cryptoCurrency: params.cryptoCurrency,
|
|
2938
|
+
network: params.network,
|
|
2939
|
+
},
|
|
2940
|
+
}, this.apiKey);
|
|
2941
|
+
const q = quoteData.createQuote;
|
|
2942
|
+
if (!q.success) {
|
|
2943
|
+
const msg = q.message ?? "Quote creation failed";
|
|
2944
|
+
const code = msg.toLowerCase().includes("expired")
|
|
2945
|
+
? "QUOTE_EXPIRED"
|
|
2946
|
+
: msg.toLowerCase().includes("key")
|
|
2947
|
+
? "INVALID_KEY"
|
|
2948
|
+
: "UNKNOWN";
|
|
2949
|
+
throw new KwesPayError(msg, code);
|
|
2950
|
+
}
|
|
2951
|
+
const txData = await gqlRequest(GQL_CREATE_TRANSACTION, {
|
|
2952
|
+
input: {
|
|
2953
|
+
quoteId: q.quoteId,
|
|
2954
|
+
payerWalletAddress: params.payerWalletAddress,
|
|
2955
|
+
},
|
|
2956
|
+
}, this.apiKey);
|
|
2957
|
+
const t = txData.createTransaction;
|
|
2958
|
+
if (!t.success) {
|
|
2959
|
+
const msg = t.message ?? "Transaction creation failed";
|
|
2960
|
+
const code = msg.toLowerCase().includes("expired")
|
|
2961
|
+
? "QUOTE_EXPIRED"
|
|
2962
|
+
: msg.toLowerCase().includes("already been used")
|
|
2963
|
+
? "QUOTE_USED"
|
|
2964
|
+
: msg.toLowerCase().includes("not found")
|
|
2965
|
+
? "QUOTE_NOT_FOUND"
|
|
2966
|
+
: msg.toLowerCase().includes("key")
|
|
2967
|
+
? "INVALID_KEY"
|
|
2968
|
+
: "UNKNOWN";
|
|
2969
|
+
throw new KwesPayError(msg, code);
|
|
2970
|
+
}
|
|
2971
|
+
return {
|
|
2972
|
+
paymentIdBytes32: t.paymentIdBytes32,
|
|
2973
|
+
backendSignature: t.backendSignature,
|
|
2974
|
+
tokenAddress: t.tokenAddress,
|
|
2975
|
+
amountBaseUnits: t.amountBaseUnits,
|
|
2976
|
+
chainId: t.chainId,
|
|
2977
|
+
expiresAt: t.expiresAt,
|
|
2978
|
+
transactionReference: t.transaction.transactionReference,
|
|
2979
|
+
transactionStatus: t.transaction.transactionStatus,
|
|
2980
|
+
network: params.network,
|
|
2981
|
+
vendorIdentifier: params.vendorIdentifier,
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
async pay(params) {
|
|
2985
|
+
return new PaymentService(params.provider).pay(params);
|
|
2986
|
+
}
|
|
2987
|
+
async getTransactionStatus(transactionReference) {
|
|
2988
|
+
const data = await gqlRequest(GQL_TRANSACTION_STATUS, {
|
|
2989
|
+
transactionReference,
|
|
2990
|
+
});
|
|
2991
|
+
const r = data.getTransactionStatus;
|
|
2992
|
+
return {
|
|
2993
|
+
transactionReference: r.transactionReference,
|
|
2994
|
+
transactionStatus: r.transactionStatus,
|
|
2995
|
+
blockchainHash: r.blockchainHash,
|
|
2996
|
+
blockchainNetwork: r.blockchainNetwork,
|
|
2997
|
+
displayAmount: r.displayAmount,
|
|
2998
|
+
cryptoCurrency: r.cryptoCurrency,
|
|
2999
|
+
payerWalletAddress: r.payerWalletAddress,
|
|
3000
|
+
initiatedAt: r.initiatedAt,
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
async pollTransactionStatus(transactionReference, options = {}) {
|
|
3004
|
+
const { onStatus, intervalMs = 4000, maxAttempts = 60 } = options;
|
|
3005
|
+
let attempts = 0;
|
|
3006
|
+
const terminal = [
|
|
3007
|
+
"completed",
|
|
3008
|
+
"failed",
|
|
3009
|
+
"expired",
|
|
3010
|
+
"underpaid",
|
|
3011
|
+
"overpaid",
|
|
3012
|
+
"refunded",
|
|
3013
|
+
];
|
|
3014
|
+
return new Promise((resolve, reject) => {
|
|
3015
|
+
const id = setInterval(async () => {
|
|
3016
|
+
attempts++;
|
|
3017
|
+
try {
|
|
3018
|
+
const status = await this.getTransactionStatus(transactionReference);
|
|
3019
|
+
onStatus?.(status.transactionStatus);
|
|
3020
|
+
if (terminal.includes(status.transactionStatus)) {
|
|
3021
|
+
clearInterval(id);
|
|
3022
|
+
resolve(status);
|
|
3023
|
+
}
|
|
3024
|
+
else if (attempts >= maxAttempts) {
|
|
3025
|
+
clearInterval(id);
|
|
3026
|
+
reject(new KwesPayError("Status polling timed out", "UNKNOWN"));
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
catch (err) {
|
|
3030
|
+
if (attempts >= maxAttempts) {
|
|
3031
|
+
clearInterval(id);
|
|
3032
|
+
reject(err);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}, intervalMs);
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
export { KwesPayClient, KwesPayError };
|