UNPKG

abstractionkit

Version:

Account Abstraction 4337 SDK by Candidelabs

1,423 lines 511 kB
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs"; import { keccak_256 } from "@noble/hashes/sha3"; import { secp256k1 } from "@noble/curves/secp256k1"; //#region src/ethereUtils.ts /** * ethereUtils — copying related code from ethers v6 with some modification, * covering exactly the surface area used by `abstractionkit`. * * Sources (ethers v6.13.x): * src.ts/utils/{data,maths,utf8,rlp-encode}.ts * src.ts/crypto/{keccak,signing-key,signature}.ts * src.ts/hash/{id,message,solidity,typed-data}.ts * src.ts/address/{address,checks}.ts * src.ts/abi/{abi-coder, coders/*}.ts * src.ts/transaction/address.ts */ function redactArgumentValue(name, value) { if (value === null || value === void 0) return String(value); if (typeof value === "string") { const n = name.toLowerCase(); if (n.includes("key") || n.includes("secret") || n.includes("token") || n.includes("password") || n.includes("mnemonic")) return "[REDACTED]"; if (/^0x[0-9a-f]+$/i.test(value) && value.length > 18) return `${value.slice(0, 10)}…${value.slice(-6)}`; if (value.length > 64) return `[REDACTED string length=${value.length}]`; return value; } if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value); if (value instanceof Uint8Array) return `[REDACTED Uint8Array length=${value.length}]`; return `[REDACTED ${typeof value}]`; } function assertArgument(check, message, name, value) { if (!check) throw new Error(`invalid argument (${name}=${redactArgumentValue(name, value)}, message=${message})`); } function assertCheck(check, message) { if (!check) throw new Error(message); } const HexCharacters = "0123456789abcdef"; function _getBytes(value, name, copy) { if (value instanceof Uint8Array) return copy ? new Uint8Array(value) : value; if (typeof value === "string" && value.length % 2 === 0 && value.match(/^0x[0-9a-f]*$/i)) { const result = new Uint8Array((value.length - 2) / 2); let offset = 2; for (let i = 0; i < result.length; i++) { result[i] = parseInt(value.substring(offset, offset + 2), 16); offset += 2; } return result; } assertArgument(false, "invalid BytesLike value", name || "value", value); } function getBytes(value, name) { return _getBytes(value, name, false); } function getBytesCopy(value, name) { return _getBytes(value, name, true); } function isHexString(value, length) { if (typeof value !== "string" || !value.match(/^0x[0-9A-Fa-f]*$/)) return false; if (typeof length === "number" && value.length !== 2 + 2 * length) return false; if (length === true && value.length % 2 !== 0) return false; return true; } function hexlify(data) { const bytes = getBytes(data); let result = "0x"; for (let i = 0; i < bytes.length; i++) { const v = bytes[i]; result += HexCharacters[(v & 240) >> 4] + HexCharacters[v & 15]; } return result; } function concat(datas) { return "0x" + datas.map((d) => hexlify(d).substring(2)).join(""); } function dataLength(data) { if (isHexString(data, true)) return (data.length - 2) / 2; return getBytes(data).length; } function zeroPad(data, length, left) { const bytes = getBytes(data); assertCheck(length >= bytes.length, "padding exceeds data length"); const result = new Uint8Array(length); result.fill(0); if (left) result.set(bytes, length - bytes.length); else result.set(bytes, 0); return hexlify(result); } function zeroPadValue(data, length) { return zeroPad(data, length, true); } function zeroPadBytes(data, length) { return zeroPad(data, length, false); } const BN_0 = 0n; const BN_1 = 1n; const maxValue = 9007199254740991; function getBigInt(value, name) { switch (typeof value) { case "bigint": return value; case "number": assertArgument(Number.isInteger(value), "underflow", name || "value", value); assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); return BigInt(value); case "string": try { if (value === "") throw new Error("empty string"); if (value[0] === "-" && value[1] !== "-") return -BigInt(value.substring(1)); return BigInt(value); } catch (e) { assertArgument(false, `invalid BigNumberish string: ${e.message}`, name || "value", value); } } assertArgument(false, "invalid BigNumberish value", name || "value", value); } function getUint(value, name) { const result = getBigInt(value, name); assertCheck(result >= BN_0, "unsigned value cannot be negative"); return result; } function getNumber(value, name) { switch (typeof value) { case "bigint": assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); return Number(value); case "number": assertArgument(Number.isInteger(value), "underflow", name || "value", value); assertArgument(value >= -maxValue && value <= maxValue, "overflow", name || "value", value); return value; case "string": try { if (value === "") throw new Error("empty string"); return getNumber(BigInt(value), name); } catch (e) { assertArgument(false, `invalid numeric string: ${e.message}`, name || "value", value); } } assertArgument(false, "invalid numeric value", name || "value", value); } const Nibbles = "0123456789abcdef"; function toBigInt(value) { if (value instanceof Uint8Array) { let result = "0x0"; for (const v of value) { result += Nibbles[v >> 4]; result += Nibbles[v & 15]; } return BigInt(result); } return getBigInt(value); } function toNumber(value) { return getNumber(toBigInt(value)); } function mask(_value, _bits) { return getUint(_value, "value") & (BN_1 << BigInt(getNumber(_bits, "bits"))) - BN_1; } function toTwos(_value, _width) { let value = getBigInt(_value, "value"); const width = BigInt(getNumber(_width, "width")); const limit = BN_1 << width - BN_1; if (value < BN_0) { value = -value; assertCheck(value <= limit, "toTwos: too low"); const m = (BN_1 << width) - BN_1; return (~value & m) + BN_1; } assertCheck(value < limit, "toTwos: too high"); return value; } function fromTwos(_value, _width) { const value = getUint(_value, "value"); const width = BigInt(getNumber(_width, "width")); assertCheck(value >> width === BN_0, "fromTwos: overflow"); if (value >> width - BN_1) { const m = (BN_1 << width) - BN_1; return -((~value & m) + BN_1); } return value; } function toBeHex(_value, _width) { const value = getUint(_value, "value"); let result = value.toString(16); if (_width == null) { if (result.length % 2) result = "0" + result; } else { const width = getNumber(_width, "width"); if (width === 0 && value === BN_0) return "0x"; assertCheck(width * 2 >= result.length, `value exceeds width (${width} bytes)`); while (result.length < width * 2) result = "0" + result; } return "0x" + result; } function toBeArray(_value, _width) { const value = getUint(_value, "value"); if (value === BN_0) { const width = _width != null ? getNumber(_width, "width") : 0; return new Uint8Array(width); } let hex = value.toString(16); if (hex.length % 2) hex = "0" + hex; if (_width != null) { const width = getNumber(_width, "width"); while (hex.length < width * 2) hex = "00" + hex; assertCheck(width * 2 === hex.length, `value exceeds width (${width} bytes)`); } const result = new Uint8Array(hex.length / 2); for (let i = 0; i < result.length; i++) { const offset = i * 2; result[i] = parseInt(hex.substring(offset, offset + 2), 16); } return result; } function toUtf8Bytes(str) { assertArgument(typeof str === "string", "invalid string value", "str", str); const result = []; for (let i = 0; i < str.length; i++) { const c = str.charCodeAt(i); if (c < 128) result.push(c); else if (c < 2048) { result.push(c >> 6 | 192); result.push(c & 63 | 128); } else if ((c & 64512) === 55296) { i++; const c2 = str.charCodeAt(i); assertArgument(i < str.length && (c2 & 64512) === 56320, "invalid surrogate pair", "str", str); const pair = 65536 + ((c & 1023) << 10) + (c2 & 1023); result.push(pair >> 18 | 240); result.push(pair >> 12 & 63 | 128); result.push(pair >> 6 & 63 | 128); result.push(pair & 63 | 128); } else { result.push(c >> 12 | 224); result.push(c >> 6 & 63 | 128); result.push(c & 63 | 128); } } return new Uint8Array(result); } /** * Decode UTF-8 bytes to a string. Pure JS so it works in every runtime, * including React Native / Hermes where `TextDecoder` is not defined by default. * * Throws on bad prefix, truncated sequences, missing or unexpected continuation bytes, * overlong encodings, surrogate code points (U+D800..U+DFFF), and code * points above U+10FFFF. For ABI string payloads this is the right policy — * well-formed contracts never emit broken UTF-8, so an error signals a * real problem rather than silently corrupting the decoded value. * * @throws Error when `bytes` is not a valid UTF-8 sequence. */ function fromUtf8Bytes(bytes) { const codepoints = []; let i = 0; while (i < bytes.length) { const c = bytes[i++]; if ((c & 128) === 0) { codepoints.push(c); continue; } let extraLength; let overlongMask; if ((c & 224) === 192) { extraLength = 1; overlongMask = 127; } else if ((c & 240) === 224) { extraLength = 2; overlongMask = 2047; } else if ((c & 248) === 240) { extraLength = 3; overlongMask = 65535; } else { const kind = (c & 192) === 128 ? "unexpected continuation" : "bad prefix"; throw new Error(`invalid UTF-8: ${kind} byte 0x${c.toString(16)} at index ${i - 1}`); } if (i + extraLength > bytes.length) throw new Error(`invalid UTF-8: truncated sequence at index ${i - 1}`); let res = c & (1 << 8 - extraLength - 1) - 1; for (let j = 0; j < extraLength; j++) { const next = bytes[i++]; if ((next & 192) !== 128) throw new Error(`invalid UTF-8: missing continuation byte 0x${next.toString(16)} at index ${i - 1}`); res = res << 6 | next & 63; } if (res <= overlongMask) throw new Error(`invalid UTF-8: overlong encoding of U+${res.toString(16).toUpperCase()}`); if (res >= 55296 && res <= 57343) throw new Error(`invalid UTF-8: surrogate code point U+${res.toString(16).toUpperCase()}`); if (res > 1114111) throw new Error(`invalid UTF-8: code point U+${res.toString(16).toUpperCase()} out of range`); codepoints.push(res); } let result = ""; for (const cp of codepoints) result += String.fromCodePoint(cp); return result; } function keccak256(_data) { return hexlify(keccak_256(getBytes(_data, "data"))); } function id(value) { return keccak256(toUtf8Bytes(value)); } const MessagePrefix = "Ethereum Signed Message:\n"; function hashMessage(message) { if (typeof message === "string") message = toUtf8Bytes(message); return keccak256(concat([ toUtf8Bytes(MessagePrefix), toUtf8Bytes(String(message.length)), message ])); } function getChecksumAddress(address) { address = address.toLowerCase(); const chars = address.substring(2).split(""); const expanded = new Uint8Array(40); for (let i = 0; i < 40; i++) expanded[i] = chars[i].charCodeAt(0); const hashed = getBytes(keccak256(expanded)); for (let i = 0; i < 40; i += 2) { if (hashed[i >> 1] >> 4 >= 8) chars[i] = chars[i].toUpperCase(); if ((hashed[i >> 1] & 15) >= 8) chars[i + 1] = chars[i + 1].toUpperCase(); } return "0x" + chars.join(""); } function getAddress(address) { assertArgument(typeof address === "string", "invalid address", "address", address); if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) { if (!address.startsWith("0x")) address = "0x" + address; const result = getChecksumAddress(address); assertArgument(!address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) || result === address, "bad address checksum", "address", address); return result; } assertArgument(false, "invalid address", "address", address); } function isAddress(value) { try { getAddress(value); return true; } catch { return false; } } const regexBytes = /^bytes([0-9]+)$/; const regexNumber = /^(u?int)([0-9]*)$/; const regexArray = /^(.*)\[([0-9]*)\]$/; function _pack(type, value, isArray) { switch (type) { case "address": if (isArray) return getBytes(zeroPadValue(value, 32)); return getBytes(getAddress(value)); case "string": return toUtf8Bytes(value); case "bytes": return getBytes(value); case "bool": { const v = value ? "0x01" : "0x00"; if (isArray) return getBytes(zeroPadValue(v, 32)); return getBytes(v); } } let match = type.match(regexNumber); if (match) { const signed = match[1] === "int"; let size = parseInt(match[2] || "256"); assertArgument((!match[2] || match[2] === String(size)) && size % 8 === 0 && size !== 0 && size <= 256, "invalid number type", "type", type); if (isArray) size = 256; return getBytes(zeroPadValue(toBeArray(signed ? toTwos(value, size) : value), size / 8)); } match = type.match(regexBytes); if (match) { const size = parseInt(match[1]); assertArgument(String(size) === match[1] && size !== 0 && size <= 32, "invalid bytes type", "type", type); assertArgument(dataLength(value) === size, `invalid value for ${type}`, "value", value); if (isArray) return getBytes(zeroPadBytes(value, 32)); return getBytes(value); } match = type.match(regexArray); if (match && Array.isArray(value)) { const baseType = match[1]; assertArgument(parseInt(match[2] || String(value.length)) === value.length, `invalid array length for ${type}`, "value", value); const result = []; for (const v of value) result.push(_pack(baseType, v, true)); return getBytes(concat(result)); } assertArgument(false, "invalid type", "type", type); } function solidityPacked(types, values) { assertArgument(types.length === values.length, "wrong number of values", "values", values); const tight = []; for (let i = 0; i < types.length; i++) tight.push(_pack(types[i], values[i])); return hexlify(concat(tight)); } function solidityPackedKeccak256(types, values) { return keccak256(solidityPacked(types, values)); } function arrayifyInteger(value) { const result = []; while (value) { result.unshift(value & 255); value >>= 8; } return result; } function _rlpEncode(object) { if (Array.isArray(object)) { let payload = []; object.forEach((child) => { payload = payload.concat(_rlpEncode(child)); }); if (payload.length <= 55) { payload.unshift(192 + payload.length); return payload; } const length = arrayifyInteger(payload.length); length.unshift(247 + length.length); return length.concat(payload); } const data = Array.prototype.slice.call(getBytes(object, "object")); if (data.length === 1 && data[0] <= 127) return data; if (data.length <= 55) { data.unshift(128 + data.length); return data; } const length = arrayifyInteger(data.length); length.unshift(183 + length.length); return length.concat(data); } function encodeRlp(object) { let result = "0x"; for (const v of _rlpEncode(object)) { result += Nibbles[v >> 4]; result += Nibbles[v & 15]; } return result; } function splitTopLevel(s) { const out = []; let depth = 0, start = 0; for (let i = 0; i < s.length; i++) { const c = s[i]; if (c === "(" || c === "[") depth++; else if (c === ")" || c === "]") depth--; else if (c === "," && depth === 0) { out.push(s.slice(start, i)); start = i + 1; } } if (start < s.length) out.push(s.slice(start)); return out.map((x) => x.trim()).filter((x) => x.length > 0); } function parseType(s) { s = s.trim(); const m = s.match(/^(.+?)((?:\[\d*\])*)$/); assertArgument(!!m, `invalid type`, "type", s); const base = m[1], suffix = m[2]; let t; if (base.startsWith("(")) { assertArgument(base.endsWith(")"), `unbalanced tuple`, "type", s); t = { kind: "tuple", components: splitTopLevel(base.slice(1, -1)).map(parseType) }; } else if (base === "address") t = { kind: "address" }; else if (base === "bool") t = { kind: "bool" }; else if (base === "string") t = { kind: "string" }; else if (base === "bytes") t = { kind: "bytes" }; else if (base === "uint" || base === "int") t = { kind: "uint", bits: 256, signed: base === "int" }; else { const um = base.match(regexNumber); if (um) { const bits = parseInt(um[2] || "256"); assertArgument(bits !== 0 && bits <= 256 && bits % 8 === 0, "invalid number type", "type", base); t = { kind: "uint", bits, signed: um[1] === "int" }; } else { const bm = base.match(regexBytes); assertArgument(!!bm, `unknown type ${base}`, "type", base); const size = parseInt(bm[1]); assertArgument(size !== 0 && size <= 32, "invalid bytes type", "type", base); t = { kind: "bytesN", size }; } } for (const ap of suffix.matchAll(/\[(\d*)\]/g)) t = { kind: "array", child: t, size: ap[1] ? parseInt(ap[1]) : -1 }; return t; } function isDynamic(t) { switch (t.kind) { case "bytes": case "string": return true; case "array": return t.size === -1 || isDynamic(t.child); case "tuple": return t.components.some(isDynamic); default: return false; } } function staticSize(t) { switch (t.kind) { case "uint": case "address": case "bool": case "bytesN": return 32; case "array": return t.size * staticSize(t.child); case "tuple": return t.components.reduce((s, c) => s + staticSize(c), 0); default: throw new Error(`staticSize: dynamic type ${t.kind}`); } } const WordSize = 32; const Padding = new Uint8Array(WordSize); const MaxZeroFootprintArrayLen = 1 << 20; function padLeft(b, size) { assertCheck(b.length <= size, "padLeft: overflow"); const out = new Uint8Array(size); out.set(b, size - b.length); return out; } function padRight(b, size) { assertCheck(b.length <= size, "padRight: overflow"); const out = new Uint8Array(size); out.set(b, 0); return out; } function padTo32(len) { return Math.ceil(len / WordSize) * WordSize; } const BN_MAX_UINT256 = (1n << 256n) - 1n; function encodeValue(t, v) { switch (t.kind) { case "uint": { let value = getBigInt(v, "value"); const maxUintValue = mask(BN_MAX_UINT256, WordSize * 8); if (t.signed) { const bounds = mask(maxUintValue, t.bits - 1); assertCheck(value <= bounds && value >= -(bounds + 1n), `int${t.bits}: value out-of-bounds`); value = toTwos(value, 8 * WordSize); } else assertCheck(value >= 0n && value <= mask(maxUintValue, t.bits), `uint${t.bits}: out-of-bounds`); return padLeft(toBeArray(value), WordSize); } case "address": return padLeft(getBytes(getAddress(v)), WordSize); case "bool": return padLeft(new Uint8Array([v ? 1 : 0]), WordSize); case "bytesN": { const bytes = getBytes(v); assertCheck(bytes.length === t.size, `bytes${t.size}: wrong length`); return padRight(bytes, WordSize); } case "bytes": { const bytes = getBytes(v); return concatBytes([padLeft(toBeArray(bytes.length), WordSize), padRight(bytes, padTo32(bytes.length))]); } case "string": { const bytes = toUtf8Bytes(v); return concatBytes([padLeft(toBeArray(bytes.length), WordSize), padRight(bytes, padTo32(bytes.length))]); } case "array": { const arr = v; assertCheck(t.size === -1 || arr.length === t.size, "array: length mismatch"); const inner = encodeTuple(Array(arr.length).fill(t.child), arr); return t.size === -1 ? concatBytes([padLeft(toBeArray(arr.length), WordSize), inner]) : inner; } case "tuple": return encodeTuple(t.components, v); } } function concatBytes(parts) { const total = parts.reduce((s, p) => s + p.length, 0); const out = new Uint8Array(total); let off = 0; for (const p of parts) { out.set(p, off); off += p.length; } return out; } function encodeTuple(types, values) { assertCheck(types.length === values.length, "tuple: length mismatch"); const heads = []; const tails = []; let tailOff = types.reduce((s, t) => s + (isDynamic(t) ? WordSize : staticSize(t)), 0); for (let i = 0; i < types.length; i++) { const enc = encodeValue(types[i], values[i]); if (isDynamic(types[i])) { heads.push(padLeft(toBeArray(tailOff), WordSize)); tails.push(enc); tailOff += enc.length; } else heads.push(enc); } return concatBytes([...heads, ...tails]); } function ensureRange(data, offset, len, what) { if (offset < 0 || len < 0 || offset + len > data.length) throw new Error(`ABI decode: out-of-bounds read for ${what} (offset=${offset}, length=${len}, data.length=${data.length})`); } function decodeValue(t, data, base) { switch (t.kind) { case "uint": { ensureRange(data, base, WordSize, `${t.signed ? "int" : "uint"}${t.bits}`); const masked = mask(toBigInt(data.slice(base, base + WordSize)), t.bits); return t.signed ? fromTwos(masked, t.bits) : masked; } case "address": ensureRange(data, base, WordSize, "address"); return getAddress(hexlify(data.slice(base + 12, base + WordSize))); case "bool": ensureRange(data, base, WordSize, "bool"); return data[base + 31] !== 0; case "bytesN": ensureRange(data, base, WordSize, `bytes${t.size}`); return hexlify(data.slice(base, base + t.size)); case "bytes": { ensureRange(data, base, WordSize, "bytes length"); const len = toNumber(data.slice(base, base + WordSize)); ensureRange(data, base + WordSize, len, "bytes payload"); return hexlify(data.slice(base + WordSize, base + WordSize + len)); } case "string": { ensureRange(data, base, WordSize, "string length"); const len = toNumber(data.slice(base, base + WordSize)); ensureRange(data, base + WordSize, len, "string payload"); return fromUtf8Bytes(data.slice(base + WordSize, base + WordSize + len)); } case "array": if (t.size === -1) { ensureRange(data, base, WordSize, "array length"); const len = toNumber(data.slice(base, base + WordSize)); const elementHeadSize = isDynamic(t.child) ? WordSize : staticSize(t.child); const bodyBytes = data.length - (base + WordSize); const maxElements = elementHeadSize > 0 ? Math.floor(bodyBytes / elementHeadSize) : MaxZeroFootprintArrayLen; if (len > maxElements) throw new Error(`ABI decode: array length ${len} exceeds payload capacity (maxElements=${maxElements}, data.length=${data.length})`); return decodeTupleAt(Array(len).fill(t.child), data, base + WordSize); } return decodeTupleAt(Array(t.size).fill(t.child), data, base); case "tuple": return decodeTupleAt(t.components, data, base); } } function decodeTupleAt(types, data, base) { const out = []; let head = 0; for (const t of types) if (isDynamic(t)) { ensureRange(data, base + head, WordSize, "tuple offset slot"); const off = toNumber(data.slice(base + head, base + head + WordSize)); if (off < 0 || base + off > data.length) throw new Error(`ABI decode: tuple dynamic offset out-of-bounds (offset=${off}, base=${base}, data.length=${data.length})`); out.push(decodeValue(t, data, base + off)); head += WordSize; } else { const size = staticSize(t); ensureRange(data, base + head, size, `tuple static slot (${t.kind})`); out.push(decodeValue(t, data, base + head)); head += size; } return out; } function encodeAbiParameters(types, values) { assertCheck(types.length === values.length, "encodeAbiParameters: length mismatch"); return hexlify(encodeTuple(types.map(parseType), values)); } function decodeAbiParameters(types, data) { return decodeTupleAt(types.map(parseType), getBytes(data), 0); } const hexTrue = toBeHex(BN_1, 32); const hexFalse = toBeHex(BN_0, 32); function hexPadRight(value) { const bytes = getBytes(value); const padOffset = bytes.length % WordSize; if (padOffset) return concat([bytes, Padding.slice(padOffset)]); return hexlify(bytes); } function getBaseEncoder(type) { { const match = type.match(/^(u?)int(\d+)$/); if (match) { const signed = match[1] === ""; const width = parseInt(match[2]); assertArgument(width % 8 === 0 && width !== 0 && width <= 256 && match[2] === String(width), "invalid numeric width", "type", type); const boundsUpper = mask(BN_MAX_UINT256, signed ? width - 1 : width); const boundsLower = signed ? (boundsUpper + BN_1) * -1n : BN_0; return (v) => { const value = getBigInt(v, "value"); assertCheck(value >= boundsLower && value <= boundsUpper, `value out-of-bounds for ${type}`); return toBeHex(signed ? toTwos(value, 256) : value, 32); }; } } { const match = type.match(/^bytes(\d+)$/); if (match) { const width = parseInt(match[1]); assertArgument(width !== 0 && width <= 32 && match[1] === String(width), "invalid bytes width", "type", type); return (v) => { assertCheck(getBytes(v).length === width, `invalid length for ${type}`); return hexPadRight(v); }; } } switch (type) { case "address": return (v) => zeroPadValue(getAddress(v), 32); case "bool": return (v) => !v ? hexFalse : hexTrue; case "bytes": return (v) => keccak256(v); case "string": return (v) => id(v); } return null; } function splitArray(type) { const match = type.match(/^([^\x5b]*)((\x5b\d*\x5d)*)(\x5b(\d*)\x5d)$/); if (match) return { base: match[1], index: match[2] + match[4], array: { base: match[1], prefix: match[1] + match[2], count: match[5] ? parseInt(match[5]) : -1 } }; return { base: type }; } function encodeType(name, fields) { return `${name}(${fields.map(({ name, type }) => type + " " + name).join(",")})`; } /** * Build a recursive encoder for `primaryType` that produces the EIP-712 * `encodeData` for a struct value. Internal `fullTypes` map captures the * fully-described type string for each struct (`MyStruct(...)NestedStruct(...)`). */ function buildTypedDataState(_types) { const fullTypes = /* @__PURE__ */ new Map(); const links = /* @__PURE__ */ new Map(); const parents = /* @__PURE__ */ new Map(); const subtypes = /* @__PURE__ */ new Map(); const types = {}; for (const type of Object.keys(_types)) { types[type] = _types[type].map(({ name, type }) => { let { base, index } = splitArray(type); if (base === "int" && !_types["int"]) base = "int256"; if (base === "uint" && !_types["uint"]) base = "uint256"; return { name, type: base + (index || "") }; }); links.set(type, /* @__PURE__ */ new Set()); parents.set(type, []); subtypes.set(type, /* @__PURE__ */ new Set()); } for (const name in types) { const uniqueNames = /* @__PURE__ */ new Set(); for (const field of types[name]) { assertArgument(!uniqueNames.has(field.name), `duplicate variable name ${JSON.stringify(field.name)} in ${JSON.stringify(name)}`, "types", _types); uniqueNames.add(field.name); const baseType = splitArray(field.type).base; assertArgument(baseType !== name, `circular type reference to ${JSON.stringify(baseType)}`, "types", _types); if (getBaseEncoder(baseType)) continue; assertArgument(parents.has(baseType), `unknown type ${JSON.stringify(baseType)}`, "types", _types); parents.get(baseType).push(name); links.get(name).add(baseType); } } const primaryTypes = Array.from(parents.keys()).filter((n) => parents.get(n).length === 0); assertArgument(primaryTypes.length !== 0, "missing primary type", "types", _types); assertArgument(primaryTypes.length === 1, `ambiguous primary types or unused types: ${primaryTypes.map((t) => JSON.stringify(t)).join(", ")}`, "types", _types); const primaryType = primaryTypes[0]; function checkCircular(type, found) { assertArgument(!found.has(type), `circular type reference to ${JSON.stringify(type)}`, "types", _types); found.add(type); for (const child of links.get(type)) { if (!parents.has(child)) continue; checkCircular(child, found); for (const subtype of found) subtypes.get(subtype).add(child); } found.delete(type); } checkCircular(primaryType, /* @__PURE__ */ new Set()); for (const [name, set] of subtypes) { const st = Array.from(set); st.sort(); fullTypes.set(name, encodeType(name, types[name]) + st.map((t) => encodeType(t, types[t])).join("")); } const encoderCache = /* @__PURE__ */ new Map(); function getEncoder(type) { const cached = encoderCache.get(type); if (cached) return cached; const built = buildEncoder(type); encoderCache.set(type, built); return built; } function buildEncoder(type) { const base = getBaseEncoder(type); if (base) return base; const arr = splitArray(type).array; if (arr) { const subtype = arr.prefix; const subEncoder = getEncoder(subtype); return (value) => { const arrVal = value; assertCheck(arr.count === -1 || arr.count === arrVal.length, `array length mismatch; expected ${arr.count}`); let result = arrVal.map(subEncoder); if (fullTypes.has(subtype)) result = result.map(keccak256); return keccak256(concat(result)); }; } const fields = types[type]; if (fields) { const encodedType = id(fullTypes.get(type)); return (value) => { const obj = value; const values = fields.map(({ name, type }) => { const r = getEncoder(type)(obj[name]); return fullTypes.has(type) ? keccak256(r) : r; }); values.unshift(encodedType); return concat(values); }; } assertArgument(false, `unknown type: ${type}`, "type", type); } function hashStruct(name, value) { return keccak256(getEncoder(name)(value)); } return { primaryType, hashStruct }; } const domainFieldTypes = { name: "string", version: "string", chainId: "uint256", verifyingContract: "address", salt: "bytes32" }; const domainFieldNames = [ "name", "version", "chainId", "verifyingContract", "salt" ]; function hashDomain(domain) { const domainFields = []; for (const name in domain) { if (domain[name] == null) continue; const type = domainFieldTypes[name]; assertArgument(!!type, `invalid typed-data domain key: ${JSON.stringify(name)}`, "domain", domain); domainFields.push({ name, type }); } domainFields.sort((a, b) => domainFieldNames.indexOf(a.name) - domainFieldNames.indexOf(b.name)); const { hashStruct } = buildTypedDataState({ EIP712Domain: domainFields }); return hashStruct("EIP712Domain", domain); } function hashTypedData(domain, types, value) { const userTypes = {}; for (const [k, v] of Object.entries(types)) if (k !== "EIP712Domain") userTypes[k] = v; const { primaryType, hashStruct } = buildTypedDataState(userTypes); return keccak256(concat([ "0x1901", hashDomain(domain), hashStruct(primaryType, value) ])); } function computePublicKey(privateKey) { const bytes = getBytes(privateKey, "key"); assertCheck(bytes.length === 32, "invalid private key"); return hexlify(secp256k1.getPublicKey(bytes, false)); } function normalizePrivateKey(key) { return key.startsWith("0x") ? key : "0x" + key; } function privateKeyToAddress(privateKey) { return getAddress(keccak256("0x" + computePublicKey(normalizePrivateKey(privateKey)).substring(4)).substring(26)); } function signHash$1(privateKey, hash) { assertCheck(dataLength(hash) === 32, "invalid digest length"); const sig = secp256k1.sign(getBytesCopy(hash), getBytesCopy(normalizePrivateKey(privateKey)), { lowS: true }); const r = toBeHex(sig.r, 32); const s = toBeHex(sig.s, 32); const yParity = sig.recovery & 1; const v = 27 + yParity; return { r, s, v, yParity, serialized: concat([ r, s, new Uint8Array([v]) ]) }; } function signTypedData(privateKey, domain, types, message) { return signHash$1(privateKey, hashTypedData(domain, types, message)).serialized; } //#endregion //#region src/errors.ts /** * Maps JSON-RPC numeric error codes to human-readable {@link BundlerErrorCode} values. */ const BundlerErrorCodeDict = { "-32602": "INVALID_FIELDS", "-32500": "SIMULATE_VALIDATION", "-32501": "SIMULATE_PAYMASTER_VALIDATION", "-32502": "OPCODE_VALIDATION", "-32503": "EXPIRE_SHORTLY", "-32504": "REPUTATION", "-32505": "INSUFFICIENT_STAKE", "-32506": "UNSUPPORTED_SIGNATURE_AGGREGATOR", "-32507": "INVALID_SIGNATURE", "-32508": "PAYMASTER_DEPOSIT_TOO_LOW", "-32521": "EXECUTION_REVERTED" }; /** * Maps JSON-RPC numeric error codes to human-readable {@link JsonRpcErrorCode} values. */ const JsonRpcErrorDict = { "-32700": "PARSE_ERROR", "-32600": "INVALID_REQUEST", "-32601": "METHOD_NOT_FOUND", "-32602": "INVALID_PARAMS", "-32603": "INTERNAL_ERROR" }; /** * Custom error class for the AbstractionKit SDK. Wraps bundler, JSON-RPC, * and general errors with a structured code, optional numeric errno, and * arbitrary JSON-serializable context. */ var AbstractionKitError = class extends Error { code; context; errno; /** * The EntryPoint FailedOp revert code parsed from the message (e.g. "AA21"), * when present. EntryPoint codes are contract-defined and stable across * bundlers, so they are a reliable signal for classifying a failure without * matching the human-readable message text. */ aaCode; /** * @param code - Error code identifying the category of failure * @param message - Human-readable error description * @param options - Optional cause, numeric errno, JSON-serializable context, and AAxx code */ constructor(code, message, options = {}) { const { cause, errno, context, aaCode } = options; super(message, { cause }); this.name = this.constructor.name; this.code = code; this.errno = errno; this.context = context; this.aaCode = aaCode; } /** * Returns a JSON string representation of this error including name, code, * message, cause, errno, context, and aaCode. Useful in React Native where * the Error "cause" property is not shown in stack traces. * @returns JSON string of the error */ stringify() { return JSON.stringify(this, [ "name", "code", "message", "cause", "errno", "context", "aaCode" ]); } }; /** * Parse the EntryPoint FailedOp revert code (e.g. "AA21") from a bundler error * message, if one is present. Returns the uppercased code or undefined. */ function parseAaCode(message) { return message.match(/\bAA\d{2}\b/i)?.[0].toUpperCase(); } /** * Coerces an unknown thrown value into an Error instance. * If the value is already an Error it is returned as-is; otherwise it is * stringified and wrapped in a new Error. * @param value - The caught value to normalize * @returns An Error instance */ function ensureError(value) { if (value instanceof Error) return value; let stringified = "[Unable to stringify the thrown value]"; try { stringified = JSON.stringify(value); } catch {} return /* @__PURE__ */ new Error(`This value was thrown as is, not through an Error: ${stringified}`); } //#endregion //#region src/transport/Transport.ts /** * Default {@link ProviderRpcError} implementation. {@link BaseRpcTransport} * throws this automatically when a JSON-RPC envelope returns * `{ error: { code, message, data } }`. */ var TransportRpcError = class extends Error { code; data; /** * @param code - Numeric JSON-RPC error code (e.g. -32601 for METHOD_NOT_FOUND) * @param message - Human-readable error description * @param data - Optional additional data returned by the RPC server */ constructor(code, message, data) { super(message); this.name = "TransportRpcError"; this.code = code; this.data = data; } }; /** * Narrowing helper for {@link EventfulTransport}. * * @param transport - Any transport to test * @returns `true` when both `on` and `removeListener` methods are present */ function isEventfulTransport(transport) { const t = transport; return typeof t.on === "function" && typeof t.removeListener === "function"; } //#endregion //#region src/transport/BaseRpcTransport.ts /** * Optional convenience base class for users writing new wire-level * {@link Transport} backends (WebSocket, IPC, custom HTTP, in-process mock, * etc.). Handles JSON-RPC framing, id assignment, bigint serialization, and * standard error parsing so subclasses implement only the byte-level * `send(envelope, options)` hook. * * Users who already have a {@link Transport} in hand (window.ethereum, viem * `WalletClient`, ethers `Eip1193Provider`-shaped object, etc.) do NOT need * this class — they pass their object directly. * * @example * ```ts * class WebSocketTransport extends BaseRpcTransport { * protected async send(envelope) { * // serialize envelope to JSON, send over the socket, await response * return JSON.parse(await this.socket.sendAndAwait(JSON.stringify(envelope))); * } * } * ``` */ var BaseRpcTransport = class BaseRpcTransport { nextId = 1; /** * Build a JSON-RPC envelope, delegate the wire I/O to the subclass's * {@link BaseRpcTransport.send} method, and parse the response. * * @throws {@link TransportRpcError} when the response contains an `error` field * @throws {@link TransportRpcError} (code -32603) when the response is malformed */ async request(args, options) { const envelope = { jsonrpc: "2.0", id: this.nextId++, method: args.method, params: args.params }; const raw = await this.send(envelope, options); return BaseRpcTransport.parseResponse(raw); } /** * Serialize a JSON-RPC envelope to a string, converting bigint values to * `0x`-prefixed hex strings (preserving the historical SDK behavior). */ static serializeEnvelope(envelope) { return JSON.stringify(envelope, (_key, value) => typeof value === "bigint" ? `0x${value.toString(16)}` : value); } /** * Parse a decoded JSON-RPC response. Returns the `result` field on success * or throws a {@link TransportRpcError} on error / malformed response. */ static parseResponse(raw) { if (raw == null || typeof raw !== "object") throw new TransportRpcError(-32603, "malformed JSON-RPC response", raw); const response = raw; if (response.error != null) { if (typeof response.error === "object") { const { code, message, data } = response.error; throw new TransportRpcError(code, message, data); } throw new TransportRpcError(-32603, "malformed JSON-RPC response", raw); } if ("result" in response) return response.result; throw new TransportRpcError(-32603, "malformed JSON-RPC response", raw); } }; //#endregion //#region src/transport/HttpTransport.ts /** * Default concrete {@link Transport}: POSTs JSON-RPC envelopes to an HTTP * endpoint. Used by every URL-string call site once a string is normalized * into a transport. * * @example * ```ts * const t = new HttpTransport("https://api.candide.dev/public/v3/11155111"); * const chainId = await t.request<string>({ method: "eth_chainId" }); * * // With auth headers and a custom fetch: * const t2 = new HttpTransport("https://...", { * headers: { Authorization: `Bearer ${token}` }, * fetch: myFetchWithRetry, * }); * ``` */ var HttpTransport = class HttpTransport extends BaseRpcTransport { /** Endpoint URL this transport POSTs to. */ url; /** Options passed at construction time. */ options; /** * @param url - JSON-RPC endpoint URL (bundler, paymaster, or node) * @param options - Optional fetch override and static headers */ constructor(url, options = {}) { super(); this.url = url; this.options = options; } async send(envelope, options) { const headers = { ...this.options.headers ?? {}, "Content-Type": "application/json" }; const body = HttpTransport.serializeEnvelope(envelope); const response = await (this.options.fetch ?? globalThis.fetch)(this.url, { method: "POST", headers, body, redirect: "follow", signal: options?.signal }); const responseText = await response.text(); let parsed; try { parsed = JSON.parse(responseText); } catch { throw new TransportRpcError(-32603, `HTTP ${response.status} ${response.statusText}: response body is not JSON`.trim(), responseText.slice(0, 1e3)); } const rpcError = typeof parsed === "object" && parsed != null && "error" in parsed ? parsed.error : null; const hasRpcError = typeof rpcError === "object" && rpcError != null && typeof rpcError.code === "number" && typeof rpcError.message === "string"; if (!response.ok && !hasRpcError) throw new TransportRpcError(-32603, `HTTP ${response.status} ${response.statusText}`.trim(), parsed); return parsed; } }; /** * Narrowing helper for {@link HttpTransport}. Useful for code that wants to * read the underlying URL — e.g. when serializing a configured Bundler for * logging or diagnostics. * * @example * ```ts * if (isHttpTransport(bundler.transport)) { * console.log("Bundler URL:", bundler.transport.url); * } * ``` */ function isHttpTransport(transport) { return transport instanceof HttpTransport; } //#endregion //#region src/types.ts /** * Specifies whether a transaction is a regular call or a delegatecall. */ let Operation = /* @__PURE__ */ function(Operation) { /** Standard call to the target address */ Operation[Operation["Call"] = 0] = "Call"; /** Delegatecall (executes target code in caller's context) */ Operation[Operation["Delegate"] = 1] = "Delegate"; return Operation; }({}); /** * Multiplier to determine the gas price. Higher values result in faster inclusion but higher cost. */ let GasOption = /* @__PURE__ */ function(GasOption) { /** 1x multiplier -- lowest cost, slowest inclusion */ GasOption[GasOption["Slow"] = 1] = "Slow"; /** 1.2x multiplier -- balanced cost and speed */ GasOption[GasOption["Medium"] = 1.2] = "Medium"; /** 1.5x multiplier -- highest cost, fastest inclusion */ GasOption[GasOption["Fast"] = 1.5] = "Fast"; return GasOption; }({}); /** Polygon network identifier used to select the correct Polygon Gas Station endpoint. */ let PolygonChain = /* @__PURE__ */ function(PolygonChain) { PolygonChain["Mainnet"] = "v2"; PolygonChain["ZkMainnet"] = "zkevm"; PolygonChain["Amoy"] = "amoy"; PolygonChain["Cardona"] = "cardona"; return PolygonChain; }({}); //#endregion //#region src/transport/normalize.ts /** * Recursively convert any `bigint` values inside an RPC param to `0x`-prefixed * hex strings, descending into arrays and plain objects. Other values pass * through unchanged. * * Required for user-supplied {@link Transport} implementations that don't go * through {@link BaseRpcTransport.serializeEnvelope} (EIP-1193 providers, viem * clients, etc.) and would otherwise see raw `bigint`s in `params`. Mirrors the * normalization done in {@link sendJsonRpcRequest}. * * @internal */ function normalizeRpcValue(value) { if (typeof value === "bigint") return `0x${value.toString(16)}`; if (Array.isArray(value)) return value.map(normalizeRpcValue); if (value !== null && typeof value === "object") { const out = {}; for (const [k, v] of Object.entries(value)) out[k] = normalizeRpcValue(v); return out; } return value; } /** * Wrap a {@link Transport} so every outbound `request` has its `params` * normalized (bigints → 0x-hex) before delegation. Idempotent: wrapping an * already-normalizing transport is harmless because the second pass sees only * strings. * * Applied once at each service boundary (Bundler, CandidePaymaster, * Erc7677Paymaster, JsonRpcNode, …) so normalization is impossible to forget * per-method. Required for user-supplied transports (EIP-1193 providers, viem * clients, etc.) that don't route through {@link BaseRpcTransport.serializeEnvelope}. * * @internal */ function normalizingTransport(inner) { return { request(args, options) { const params = args.params == null ? args.params : normalizeRpcValue(args.params); return inner.request({ method: args.method, params }, options); } }; } //#endregion //#region src/transport/JsonRpcNode.ts const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; /** * High-level service class for the JSON-RPC node methods abstractionkit reads * from. Intentionally NOT a general Ethereum client — only exposes the * methods the SDK actually uses. For broader functionality, drop down to * `.transport` and call `request({ method, params })` directly, or use a * dedicated library like viem / ethers. * * Like {@link Bundler} and the paymaster classes, `JsonRpcNode` itself * implements {@link Transport} so it can be passed back into any Transport * position. * * @example * ```ts * const node = new JsonRpcNode("https://ethereum-sepolia.publicnode.com"); * const id = await node.chainId(); * const code = await node.getCode("0x..."); * * // Also a Transport — can be slotted in: * const bundler = new Bundler(node); // bundler will speak through this node * ``` */ var JsonRpcNode = class JsonRpcNode { /** * The raw transport the user passed in (or {@link HttpTransport} when a URL * string was passed). Exposed for introspection — reading `.url`, * `isHttpTransport(...)` checks, passing it back into another service. * * Calls made directly on this field (`node.transport.request(...)`) go * to the raw transport and skip SDK-level behavior like bigint param * normalization. For SDK-pipeline behavior, use {@link JsonRpcNode.request} * or the typed methods. */ transport; /** Normalizing wrapper around {@link transport}, used for every SDK-outbound call. */ outbound; /** * @param rpc - Node JSON-RPC endpoint URL, or any {@link Transport} */ constructor(rpc) { this.transport = typeof rpc === "string" ? new HttpTransport(rpc) : rpc; this.outbound = normalizingTransport(this.transport); } /** * Normalize any acceptable input into a {@link JsonRpcNode}. Used at every * public-API widening site (in account / paymaster classes). When the * input is already a `JsonRpcNode`, the same instance is returned by * reference, so a user's preconstructed `JsonRpcNode` is never re-wrapped. * * @param input - URL string, Transport, or existing JsonRpcNode */ static from(input) { return input instanceof JsonRpcNode ? input : new JsonRpcNode(input); } /** * Transport delegate. Routes through the normalizing wrapper so `bigint` * values in `params` are converted to `0x`-hex before reaching * user-supplied transports that don't go through * {@link BaseRpcTransport.serializeEnvelope}. Lets a `JsonRpcNode` itself * slot into any other transport position. */ request(args, options) { return this.outbound.request(args, options); } /** * `eth_chainId`. Returns the hex-encoded chain id (e.g. `"0xaa36a7"` for * Sepolia). */ async chainId(options) { try { const result = await this.outbound.request({ method: "eth_chainId" }, options); if (typeof result !== "string") throw new AbstractionKitError("BAD_DATA", "eth_chainId returned ill formed data", { context: JSON.stringify(result) }); return result; } catch (err) { throw translateNodeError(err, "eth_chainId"); } } /** * `eth_blockNumber`. Returns the latest block number as a bigint. */ async blockNumber(options) { try { const result = await this.outbound.request({ method: "eth_blockNumber" }, options); if (typeof result !== "string") throw new AbstractionKitError("BAD_DATA", "eth_blockNumber returned ill formed data", { context: JSON.stringify(result) }); return BigInt(result); } catch (err) { throw translateNodeError(err, "eth_blockNumber"); } } /** * `eth_getCode`. Returns the deployed bytecode at `address` at the given * block tag (default `"latest"`). */ async getCode(address, blockTag = "latest", options) { try { const result = await this.outbound.request({ method: "eth_getCode", params: [address, blockTag] }, options); if (typeof result !== "string") throw new AbstractionKitError("BAD_DATA", "eth_getCode returned ill formed data", { context: JSON.stringify(result) }); return result; } catch (err) { throw translateNodeError(err, "eth_getCode"); } } /** * `eth_getStorageAt`. Returns the 32-byte storage word at `slot` for * `address` at the given block tag (default `"latest"`), as a hex string. */ async getStorageAt(address, slot, blockTag = "latest", options) { try { const result = await this.outbound.request({ method: "eth_getStorageAt", params: [ address, slot, blockTag ] }, options); if (typeof result !== "string") throw new AbstractionKitError("BAD_DATA", "eth_getStorageAt returned ill formed data", { context: JSON.stringify(result) }); return result; } catch (err) { throw translateNodeError(err, "eth_getStorageAt"); } } /** * `eth_call`. Executes a read-only call against `to` and returns the raw * return data as a hex string. Supports state overrides via the optional * third parameter. */ async call(tx, blockTag = "latest", stateOverrides, options) { const params = stateOverrides == null ? [tx, blockTag] : [ tx, blockTag, stateOverrides ]; try { const result = await this.outbound.request({ method: "eth_call", params }, options); if (typeof result !== "string") throw new AbstractionKitError("BAD_DATA", "eth_call returned ill formed data", { context: JSON.stringify(result) }); return result; } catch (err) { throw translateNodeError(err, "eth_call"); } } /** * `eth_getTransactionCount`. Returns the transaction count (account nonce * at the EOA level — not the EntryPoint nonce; see * {@link JsonRpcNode.getEntryPointNonce}) as a bigint. */ async getTransactionCount(address, blockTag = "latest", options) { try { const result = await this.outbound.request({ method: "eth_getTransactionCount", params: [address, blockTag] }, options); if (typeof result !== "string") throw new AbstractionKitError("BAD_DATA", "eth_getTransactionCount returned ill formed data", { context: JSON.stringify(result) }); return BigInt(result); } catch (err) { throw translateNodeError(err, "eth_getTransactionCount"); } } /** * Fetch current gas prices and apply a level multiplier. * * Tries `eth_maxPriorityFeePerGas` + `eth_gasPrice` first (EIP-1559), * falling back to `eth_gasPrice` alone if the priority-fee method is * unsupported, and finally to a 1 gwei floor multiplied by `gasLevel`. * * @param gasLevel - {@link GasOption} multiplier (default: Medium = 1.2x) * @returns `[maxFeePerGas, maxPriorityFeePerGas]` as bigints */ async getFeeData(gasLevel = GasOption.Medium, options) { try { let gasPrice = null; let maxPriorityFeePerGas = null; try { const result = await this.outbound.request({ method: "eth_gasPrice" }, options); if (typeof result !== "string") throw new Abstraction