@0xfutbol/id
Version:
React component library with shared providers for 0xFutbol ID
1,435 lines • 87.3 kB
JavaScript
import {B as BaseError,T as toSignatureHash,U as isHex,V as toFunctionSelector,W as isAddress,X as AbiItemAmbiguityError,Y as AbiEventNotFoundError,Z as formatAbiItem,_ as keccak256,$ as toBytes,a0 as encodeAbiParameters,a1 as InvalidAddressError,a2 as AbiEventSignatureEmptyTopicsError,a3 as AbiEventSignatureNotFoundError,a4 as DecodeLogTopicsMismatch,a5 as decodeAbiParameters,a6 as AbiDecodingDataSizeTooSmallError,a7 as PositionOutOfBoundsError,a8 as DecodeLogDataMismatch,a9 as size,P as readContract,aa as prepareContractCall,ab as once,r as randomBytesHex,ac as toWei,ad as ZERO_ADDRESS,ae as isContractDeployed,af as parseAbiItem,ag as withCache,j as isHex$1,M as stringToHex,ah as concat,ai as pad,aj as toHex,i as toHex$1,ak as getDefaultBundlerUrl,al as ENTRYPOINT_ADDRESS_v0_6,g as getClientFetch,s as stringify,am as hexToBigInt,an as getEntryPointVersion,ao as encode,ap as resolvePromisedValue,aq as hexToBytes,ar as isThirdwebUrl,as as getDefaultGasOverrides,at as getContract,au as keccak256$1,av as encodeAbiParameters$1,aw as maxUint96$1,ax as DUMMY_SIGNATURE,ay as ENTRYPOINT_ADDRESS_v0_7,az as decodeErrorResult,aA as MANAGED_ACCOUNT_GAS_BUFFER,aB as isZkSyncChain,aC as getDefaultAccountFactory,aD as sendTransaction,d as getAddress,p as parseTypedData,h as getCachedChain,t as trackTransaction,aE as allowance,aF as approve,aG as toSerializableTransaction,aH as isSmartWallet}from'./index-DNoa140s.js';import {t as toBigInt,p as populateEip712Transaction,s as signEip712Transaction}from'./send-eip712-transaction-uuOZ8__5.js';class FilterTypeNotSupportedError extends BaseError {
constructor(type) {
super(`Filter type "${type}" is not supported.`, {
name: 'FilterTypeNotSupportedError',
});
}
}/**
* Returns the event selector for a given event definition.
*
* @example
* const selector = toEventSelector('Transfer(address indexed from, address indexed to, uint256 amount)')
* // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
*/
const toEventSelector = toSignatureHash;function getAbiItem(parameters) {
const { abi, args = [], name } = parameters;
const isSelector = isHex(name, { strict: false });
const abiItems = abi.filter((abiItem) => {
if (isSelector) {
if (abiItem.type === 'function')
return toFunctionSelector(abiItem) === name;
if (abiItem.type === 'event')
return toEventSelector(abiItem) === name;
return false;
}
return 'name' in abiItem && abiItem.name === name;
});
if (abiItems.length === 0)
return undefined;
if (abiItems.length === 1)
return abiItems[0];
let matchedAbiItem = undefined;
for (const abiItem of abiItems) {
if (!('inputs' in abiItem))
continue;
if (!args || args.length === 0) {
if (!abiItem.inputs || abiItem.inputs.length === 0)
return abiItem;
continue;
}
if (!abiItem.inputs)
continue;
if (abiItem.inputs.length === 0)
continue;
if (abiItem.inputs.length !== args.length)
continue;
const matched = args.every((arg, index) => {
const abiParameter = 'inputs' in abiItem && abiItem.inputs[index];
if (!abiParameter)
return false;
return isArgOfType(arg, abiParameter);
});
if (matched) {
// Check for ambiguity against already matched parameters (e.g. `address` vs `bytes20`).
if (matchedAbiItem &&
'inputs' in matchedAbiItem &&
matchedAbiItem.inputs) {
const ambiguousTypes = getAmbiguousTypes(abiItem.inputs, matchedAbiItem.inputs, args);
if (ambiguousTypes)
throw new AbiItemAmbiguityError({
abiItem,
type: ambiguousTypes[0],
}, {
abiItem: matchedAbiItem,
type: ambiguousTypes[1],
});
}
matchedAbiItem = abiItem;
}
}
if (matchedAbiItem)
return matchedAbiItem;
return abiItems[0];
}
/** @internal */
function isArgOfType(arg, abiParameter) {
const argType = typeof arg;
const abiParameterType = abiParameter.type;
switch (abiParameterType) {
case 'address':
return isAddress(arg, { strict: false });
case 'bool':
return argType === 'boolean';
case 'function':
return argType === 'string';
case 'string':
return argType === 'string';
default: {
if (abiParameterType === 'tuple' && 'components' in abiParameter)
return Object.values(abiParameter.components).every((component, index) => {
return isArgOfType(Object.values(arg)[index], component);
});
// `(u)int<M>`: (un)signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`
// https://regexr.com/6v8hp
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))
return argType === 'number' || argType === 'bigint';
// `bytes<M>`: binary type of `M` bytes, `0 < M <= 32`
// https://regexr.com/6va55
if (/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(abiParameterType))
return argType === 'string' || arg instanceof Uint8Array;
// fixed-length (`<type>[M]`) and dynamic (`<type>[]`) arrays
// https://regexr.com/6va6i
if (/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(abiParameterType)) {
return (Array.isArray(arg) &&
arg.every((x) => isArgOfType(x, {
...abiParameter,
// Pop off `[]` or `[M]` from end of type
type: abiParameterType.replace(/(\[[0-9]{0,}\])$/, ''),
})));
}
return false;
}
}
}
/** @internal */
function getAmbiguousTypes(sourceParameters, targetParameters, args) {
for (const parameterIndex in sourceParameters) {
const sourceParameter = sourceParameters[parameterIndex];
const targetParameter = targetParameters[parameterIndex];
if (sourceParameter.type === 'tuple' &&
targetParameter.type === 'tuple' &&
'components' in sourceParameter &&
'components' in targetParameter)
return getAmbiguousTypes(sourceParameter.components, targetParameter.components, args[parameterIndex]);
const types = [sourceParameter.type, targetParameter.type];
const ambiguous = (() => {
if (types.includes('address') && types.includes('bytes20'))
return true;
if (types.includes('address') && types.includes('string'))
return isAddress(args[parameterIndex], { strict: false });
if (types.includes('address') && types.includes('bytes'))
return isAddress(args[parameterIndex], { strict: false });
return false;
})();
if (ambiguous)
return types;
}
return;
}const docsPath$1 = '/docs/contract/encodeEventTopics';
function encodeEventTopics(parameters) {
const { abi, eventName, args } = parameters;
let abiItem = abi[0];
if (eventName) {
const item = getAbiItem({ abi, name: eventName });
if (!item)
throw new AbiEventNotFoundError(eventName, { docsPath: docsPath$1 });
abiItem = item;
}
if (abiItem.type !== 'event')
throw new AbiEventNotFoundError(undefined, { docsPath: docsPath$1 });
const definition = formatAbiItem(abiItem);
const signature = toEventSelector(definition);
let topics = [];
if (args && 'inputs' in abiItem) {
const indexedInputs = abiItem.inputs?.filter((param) => 'indexed' in param && param.indexed);
const args_ = Array.isArray(args)
? args
: Object.values(args).length > 0
? (indexedInputs?.map((x) => args[x.name]) ?? [])
: [];
if (args_.length > 0) {
topics =
indexedInputs?.map((param, i) => {
if (Array.isArray(args_[i]))
return args_[i].map((_, j) => encodeArg({ param, value: args_[i][j] }));
return typeof args_[i] !== 'undefined' && args_[i] !== null
? encodeArg({ param, value: args_[i] })
: null;
}) ?? [];
}
}
return [signature, ...topics];
}
function encodeArg({ param, value, }) {
if (param.type === 'string' || param.type === 'bytes')
return keccak256(toBytes(value));
if (param.type === 'tuple' || param.type.match(/^(.*)\[(\d+)?\]$/))
throw new FilterTypeNotSupportedError(param.type);
return encodeAbiParameters([param], [value]);
}function isAddressEqual(a, b) {
if (!isAddress(a, { strict: false }))
throw new InvalidAddressError({ address: a });
if (!isAddress(b, { strict: false }))
throw new InvalidAddressError({ address: b });
return a.toLowerCase() === b.toLowerCase();
}const docsPath = '/docs/contract/decodeEventLog';
function decodeEventLog(parameters) {
const { abi, data, strict: strict_, topics, } = parameters;
const strict = strict_ ?? true;
const [signature, ...argTopics] = topics;
if (!signature)
throw new AbiEventSignatureEmptyTopicsError({ docsPath });
const abiItem = (() => {
if (abi.length === 1)
return abi[0];
return abi.find((x) => x.type === 'event' &&
signature === toEventSelector(formatAbiItem(x)));
})();
if (!(abiItem && 'name' in abiItem) || abiItem.type !== 'event')
throw new AbiEventSignatureNotFoundError(signature, { docsPath });
const { name, inputs } = abiItem;
const isUnnamed = inputs?.some((x) => !('name' in x && x.name));
let args = isUnnamed ? [] : {};
// Decode topics (indexed args).
const indexedInputs = inputs.filter((x) => 'indexed' in x && x.indexed);
for (let i = 0; i < indexedInputs.length; i++) {
const param = indexedInputs[i];
const topic = argTopics[i];
if (!topic)
throw new DecodeLogTopicsMismatch({
abiItem,
param: param,
});
args[isUnnamed ? i : param.name || i] = decodeTopic({ param, value: topic });
}
// Decode data (non-indexed args).
const nonIndexedInputs = inputs.filter((x) => !('indexed' in x && x.indexed));
if (nonIndexedInputs.length > 0) {
if (data && data !== '0x') {
try {
const decodedData = decodeAbiParameters(nonIndexedInputs, data);
if (decodedData) {
if (isUnnamed)
args = [...args, ...decodedData];
else {
for (let i = 0; i < nonIndexedInputs.length; i++) {
args[nonIndexedInputs[i].name] = decodedData[i];
}
}
}
}
catch (err) {
if (strict) {
if (err instanceof AbiDecodingDataSizeTooSmallError ||
err instanceof PositionOutOfBoundsError)
throw new DecodeLogDataMismatch({
abiItem,
data: data,
params: nonIndexedInputs,
size: size(data),
});
throw err;
}
}
}
else if (strict) {
throw new DecodeLogDataMismatch({
abiItem,
data: '0x',
params: nonIndexedInputs,
size: 0,
});
}
}
return {
eventName: name,
args: Object.values(args).length > 0 ? args : undefined,
};
}
function decodeTopic({ param, value }) {
if (param.type === 'string' ||
param.type === 'bytes' ||
param.type === 'tuple' ||
param.type.match(/^(.*)\[(\d+)?\]$/))
return value;
const decodedArg = decodeAbiParameters([param], value) || [];
return decodedArg[0];
}// TODO(v3): checksum address.
/**
* Extracts & decodes logs matching the provided signature(s) (`abi` + optional `eventName`)
* from a set of opaque logs.
*
* @param parameters - {@link ParseEventLogsParameters}
* @returns The logs. {@link ParseEventLogsReturnType}
*
* @example
* import { createClient, http } from 'viem'
* import { mainnet } from 'viem/chains'
* import { parseEventLogs } from 'viem/op-stack'
*
* const client = createClient({
* chain: mainnet,
* transport: http(),
* })
*
* const receipt = await getTransactionReceipt(client, {
* hash: '0xec23b2ba4bc59ba61554507c1b1bc91649e6586eb2dd00c728e8ed0db8bb37ea',
* })
*
* const logs = parseEventLogs({ logs: receipt.logs })
* // [{ args: { ... }, eventName: 'TransactionDeposited', ... }, ...]
*/
function parseEventLogs$1(parameters) {
const { abi, args, logs, strict = true } = parameters;
const eventName = (() => {
if (!parameters.eventName)
return undefined;
if (Array.isArray(parameters.eventName))
return parameters.eventName;
return [parameters.eventName];
})();
return logs
.map((log) => {
try {
const abiItem = abi.find((abiItem) => abiItem.type === 'event' &&
log.topics[0] === toEventSelector(abiItem));
if (!abiItem)
return null;
const event = decodeEventLog({
...log,
abi: [abiItem],
strict,
});
// Check that the decoded event name matches the provided event name.
if (eventName && !eventName.includes(event.eventName))
return null;
// Check that the decoded event args match the provided args.
if (!includesArgs({
args: event.args,
inputs: abiItem.inputs,
matchArgs: args,
}))
return null;
return { ...event, ...log };
}
catch (err) {
let eventName;
let isUnnamed;
if (err instanceof AbiEventSignatureNotFoundError)
return null;
if (err instanceof DecodeLogDataMismatch ||
err instanceof DecodeLogTopicsMismatch) {
// If strict mode is on, and log data/topics do not match event definition, skip.
if (strict)
return null;
eventName = err.abiItem.name;
isUnnamed = err.abiItem.inputs?.some((x) => !('name' in x && x.name));
}
// Set args to empty if there is an error decoding (e.g. indexed/non-indexed params mismatch).
return { ...log, args: isUnnamed ? [] : {}, eventName };
}
})
.filter(Boolean);
}
function includesArgs(parameters) {
const { args, inputs, matchArgs } = parameters;
if (!matchArgs)
return true;
if (!args)
return false;
function isEqual(input, value, arg) {
try {
if (input.type === 'address')
return isAddressEqual(value, arg);
if (input.type === 'string' || input.type === 'bytes')
return keccak256(toBytes(value)) === arg;
return value === arg;
}
catch {
return false;
}
}
if (Array.isArray(args) && Array.isArray(matchArgs)) {
return matchArgs.every((value, index) => {
if (value === null || value === undefined)
return true;
const input = inputs[index];
if (!input)
return false;
const value_ = Array.isArray(value) ? value : [value];
return value_.some((value) => isEqual(input, value, args[index]));
});
}
if (typeof args === 'object' &&
!Array.isArray(args) &&
typeof matchArgs === 'object' &&
!Array.isArray(matchArgs))
return Object.entries(matchArgs).every(([key, value]) => {
if (value === null || value === undefined)
return true;
const input = inputs.find((input) => input.name === key);
if (!input)
return false;
const value_ = Array.isArray(value) ? value : [value];
return value_.some((value) => isEqual(input, value, args[key]));
});
return false;
}const FN_SELECTOR$4 = "0xf15d424e";
const FN_INPUTS$4 = [
{
type: "address",
name: "signer",
},
];
const FN_OUTPUTS$4 = [
{
type: "tuple",
name: "permissions",
components: [
{
type: "address",
name: "signer",
},
{
type: "address[]",
name: "approvedTargets",
},
{
type: "uint256",
name: "nativeTokenLimitPerTransaction",
},
{
type: "uint128",
name: "startTimestamp",
},
{
type: "uint128",
name: "endTimestamp",
},
],
},
];
/**
* Calls the "getPermissionsForSigner" function on the contract.
* @param options - The options for the getPermissionsForSigner function.
* @returns The parsed result of the function call.
* @extension ERC4337
* @example
* ```ts
* import { getPermissionsForSigner } from "thirdweb/extensions/erc4337";
*
* const result = await getPermissionsForSigner({
* contract,
* signer: ...,
* });
*
* ```
*/
async function getPermissionsForSigner(options) {
return readContract({
contract: options.contract,
method: [FN_SELECTOR$4, FN_INPUTS$4, FN_OUTPUTS$4],
params: [options.signer],
});
}const FN_SELECTOR$3 = "0x5892e236";
const FN_INPUTS$3 = [
{
type: "tuple",
name: "req",
components: [
{
type: "address",
name: "signer",
},
{
type: "uint8",
name: "isAdmin",
},
{
type: "address[]",
name: "approvedTargets",
},
{
type: "uint256",
name: "nativeTokenLimitPerTransaction",
},
{
type: "uint128",
name: "permissionStartTimestamp",
},
{
type: "uint128",
name: "permissionEndTimestamp",
},
{
type: "uint128",
name: "reqValidityStartTimestamp",
},
{
type: "uint128",
name: "reqValidityEndTimestamp",
},
{
type: "bytes32",
name: "uid",
},
],
},
{
type: "bytes",
name: "signature",
},
];
const FN_OUTPUTS$3 = [];
/**
* Prepares a transaction to call the "setPermissionsForSigner" function on the contract.
* @param options - The options for the "setPermissionsForSigner" function.
* @returns A prepared transaction object.
* @extension ERC4337
* @example
* ```ts
* import { sendTransaction } from "thirdweb";
* import { setPermissionsForSigner } from "thirdweb/extensions/erc4337";
*
* const transaction = setPermissionsForSigner({
* contract,
* req: ...,
* signature: ...,
* overrides: {
* ...
* }
* });
*
* // Send the transaction
* await sendTransaction({ transaction, account });
* ```
*/
function setPermissionsForSigner(options) {
const asyncOptions = once(async () => {
return "asyncParams" in options ? await options.asyncParams() : options;
});
return prepareContractCall({
contract: options.contract,
method: [FN_SELECTOR$3, FN_INPUTS$3, FN_OUTPUTS$3],
params: async () => {
const resolvedOptions = await asyncOptions();
return [resolvedOptions.req, resolvedOptions.signature];
},
value: async () => (await asyncOptions()).overrides?.value,
accessList: async () => (await asyncOptions()).overrides?.accessList,
gas: async () => (await asyncOptions()).overrides?.gas,
gasPrice: async () => (await asyncOptions()).overrides?.gasPrice,
maxFeePerGas: async () => (await asyncOptions()).overrides?.maxFeePerGas,
maxPriorityFeePerGas: async () => (await asyncOptions()).overrides?.maxPriorityFeePerGas,
nonce: async () => (await asyncOptions()).overrides?.nonce,
extraGas: async () => (await asyncOptions()).overrides?.extraGas,
erc20Value: async () => (await asyncOptions()).overrides?.erc20Value,
});
}/**
* @internal
*/
function tenYearsFromNow() {
return new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 10); // 10 years
}
/**
* @internal
*/
function dateToSeconds(date) {
return toBigInt(Math.floor(date.getTime() / 1000));
}const SignerPermissionRequest = [
{ name: "signer", type: "address" },
{ name: "isAdmin", type: "uint8" },
{ name: "approvedTargets", type: "address[]" },
{ name: "nativeTokenLimitPerTransaction", type: "uint256" },
{ name: "permissionStartTimestamp", type: "uint128" },
{ name: "permissionEndTimestamp", type: "uint128" },
{ name: "reqValidityStartTimestamp", type: "uint128" },
{ name: "reqValidityEndTimestamp", type: "uint128" },
{ name: "uid", type: "bytes32" },
];/**
* @internal
*/
async function signPermissionRequest(options) {
const { account, contract, req } = options;
const signature = await account.signTypedData({
domain: {
name: "Account",
version: "1",
verifyingContract: contract.address,
chainId: contract.chain.id,
},
primaryType: "SignerPermissionRequest",
types: { SignerPermissionRequest },
message: req,
});
return { req, signature };
}
/**
* @internal
*/
async function toContractPermissions(options) {
const { target, permissions } = options;
return {
approvedTargets: permissions.approvedTargets === "*"
? [ZERO_ADDRESS]
: permissions.approvedTargets,
nativeTokenLimitPerTransaction: toWei(permissions.nativeTokenLimitPerTransaction?.toString() || "0"),
permissionStartTimestamp: dateToSeconds(permissions.permissionStartTimestamp || new Date(0)),
permissionEndTimestamp: dateToSeconds(permissions.permissionEndTimestamp || tenYearsFromNow()),
reqValidityStartTimestamp: 0n,
reqValidityEndTimestamp: dateToSeconds(tenYearsFromNow()),
uid: await randomBytesHex(),
isAdmin: 0, // session key flag
signer: target,
};
}/**
* Adds session key permissions for a specified address.
* @param options - The options for the removeSessionKey function.
* @param {Contract} options.contract - The smart account contract to add the session key to
* @returns The transaction object to be sent.
* @example
* ```ts
* import { addSessionKey } from 'thirdweb/extensions/erc4337';
* import { sendTransaction } from 'thirdweb';
*
* const transaction = addSessionKey({
* contract,
* account,
* sessionKeyAddress,
* permissions: {
* approvedTargets: ['0x...'],
* nativeTokenLimitPerTransaction: 0.1, // in ETH
* permissionStartTimestamp: new Date(),
* permissionEndTimestamp: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), // 1 year from now
* }
* });
*
* await sendTransaction({ transaction, account });
* ```
* @extension ERC4337
*/
function addSessionKey(options) {
const { contract, sessionKeyAddress, account, permissions } = options;
return setPermissionsForSigner({
contract,
async asyncParams() {
const { req, signature } = await signPermissionRequest({
account,
contract,
req: await toContractPermissions({
target: sessionKeyAddress,
permissions,
}),
});
return { signature, req };
},
});
}
/**
* Checks if the session key should be updated.
* @param currentPermissions - The current permissions of the session key.
* @param newPermissions - The new permissions to set for the session key.
* @returns A boolean indicating if the session key should be updated.
* @extension ERC4337
* @example
* ```ts
* import { shouldUpdateSessionKey } from "thirdweb/extensions/erc4337";
*
* const shouldUpdate = await shouldUpdateSessionKey({ accountContract, sessionKeyAddress, newPermissions });
* ```
*/
async function shouldUpdateSessionKey(args) {
const { accountContract, sessionKeyAddress, newPermissions } = args;
// check if account is deployed
const accountDeployed = await isContractDeployed(accountContract);
if (!accountDeployed) {
return true;
}
// get current permissions
const currentPermissions = await getPermissionsForSigner({
contract: accountContract,
signer: sessionKeyAddress,
});
// check end time validity
if (currentPermissions.endTimestamp &&
currentPermissions.endTimestamp < Math.floor(new Date().getTime() / 1000)) {
return true;
}
// check targets
if (!areSessionKeyContractTargetsEqual(currentPermissions.approvedTargets, newPermissions.approvedTargets)) {
return true;
}
// check if the new native token limit is greater than the current one
if (toWei(newPermissions.nativeTokenLimitPerTransaction?.toString() ?? "0") >
currentPermissions.nativeTokenLimitPerTransaction) {
return true;
}
return false;
}
function areSessionKeyContractTargetsEqual(currentTargets, newTargets) {
// Handle the case where approvedTargets is "*"
if (newTargets === "*" &&
currentTargets.length === 1 &&
currentTargets[0] === ZERO_ADDRESS) {
return true;
}
if (newTargets !== "*") {
return newTargets
.map((target) => target.toLowerCase())
.every((target) => currentTargets.map((t) => t.toLowerCase()).includes(target));
}
return false;
}/**
* @internal
*/
const maxUint96 = 2n ** 96n - 1n;/**
* Parses logs and returns the corresponding events.
* @param options - The options for parsing logs.
* @returns The parsed events.
* @example
* ```ts
* import { parseEventLogs } from "thirdweb";
* const events = parseEventLogs({
* logs,
* events: [preparedEvent, preparedEvent2],
* });
* ```
* @contract
*/
function parseEventLogs(options) {
const { logs, events, strict } = options;
return parseEventLogs$1({
logs,
abi: events.map((e) => e.abiEvent),
strict,
});
}/**
* @internal
*/
function isAbiEvent(item) {
return !!(item &&
typeof item === "object" &&
"type" in item &&
item.type === "event");
}/**
* Prepares an event by parsing the signature, generating the event hash, and encoding the event topics.
* @param options - The options for preparing the event.
* @returns The prepared event object.
* @example
* ```ts
* import { prepareEvent } from "thirdweb";
* const myEvent = prepareEvent({
* signature: "event MyEvent(uint256 myArg)",
* });
* ```
* @contract
*/
function prepareEvent(options) {
const { signature } = options;
let resolvedSignature;
if (isAbiEvent(signature)) {
resolvedSignature = signature;
}
else {
resolvedSignature = parseAbiItem(signature);
}
return {
abiEvent: resolvedSignature,
hash: toSignatureHash(resolvedSignature),
// @ts-expect-error - TODO: investiagte why this complains, it works fine however
topics: encodeEventTopics({
abi: [resolvedSignature],
args: options.filters,
}),
};
}/**
* Creates an event object for the UserOperationRevertReason event.
* @param filters - Optional filters to apply to the event.
* @returns The prepared event object.
* @extension ERC4337
* @example
* ```ts
* import { getContractEvents } from "thirdweb";
* import { userOperationRevertReasonEvent } from "thirdweb/extensions/erc4337";
*
* const events = await getContractEvents({
* contract,
* events: [
* userOperationRevertReasonEvent({
* userOpHash: ...,
* sender: ...,
* })
* ],
* });
* ```
*/
function userOperationRevertReasonEvent(filters = {}) {
return prepareEvent({
signature: "event UserOperationRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason)",
filters,
});
}/**
* Creates an event object for the PostOpRevertReason event.
* @param filters - Optional filters to apply to the event.
* @returns The prepared event object.
* @extension ERC4337
* @example
* ```ts
* import { getContractEvents } from "thirdweb";
* import { postOpRevertReasonEvent } from "thirdweb/extensions/erc4337";
*
* const events = await getContractEvents({
* contract,
* events: [
* postOpRevertReasonEvent({
* userOpHash: ...,
* sender: ...,
* })
* ],
* });
* ```
*/
function postOpRevertReasonEvent(filters = {}) {
return prepareEvent({
signature: "event PostOpRevertReason(bytes32 indexed userOpHash, address indexed sender, uint256 nonce, bytes revertReason)",
filters,
});
}function formatUserOperationReceipt(userOpReceiptRaw) {
const { receipt: transactionReceipt } = userOpReceiptRaw;
const receipt = {
...transactionReceipt,
transactionHash: transactionReceipt.transactionHash,
blockNumber: transactionReceipt.blockNumber
? BigInt(transactionReceipt.blockNumber)
: null,
contractAddress: transactionReceipt.contractAddress
? transactionReceipt.contractAddress
: null,
cumulativeGasUsed: transactionReceipt.cumulativeGasUsed
? BigInt(transactionReceipt.cumulativeGasUsed)
: null,
effectiveGasPrice: transactionReceipt.effectiveGasPrice
? BigInt(transactionReceipt.effectiveGasPrice)
: null,
gasUsed: transactionReceipt.gasUsed
? BigInt(transactionReceipt.gasUsed)
: null,
logs: transactionReceipt.logs,
to: transactionReceipt.to ? transactionReceipt.to : null,
transactionIndex: transactionReceipt.transactionIndex,
status: transactionReceipt.status,
type: transactionReceipt.type,
};
if (transactionReceipt.blobGasPrice)
receipt.blobGasPrice = BigInt(transactionReceipt.blobGasPrice);
if (transactionReceipt.blobGasUsed)
receipt.blobGasUsed = BigInt(transactionReceipt.blobGasUsed);
const userOpReceipt = {
...userOpReceiptRaw,
receipt,
userOpHash: userOpReceiptRaw.userOpHash,
actualGasCost: BigInt(userOpReceiptRaw.actualGasCost),
actualGasUsed: BigInt(userOpReceiptRaw.actualGasUsed),
nonce: BigInt(userOpReceiptRaw.nonce),
};
return userOpReceipt;
}/**
* Predict the address of a smart account.
* @param args - The options for predicting the address of a smart account.
* @returns The predicted address of the smart account.
* @example
* ```ts
* import { predictAddress } from "thirdweb/wallets/smart";
*
* const predictedAddress = await predictAddress({
* factoryContract,
* adminAddress,
* accountSalt,
* });
* ```
* @walletUtils
* @deprecated Use `predictSmartAccountAddress` instead.
*/
async function predictAddress(args) {
const { factoryContract, predictAddressOverride: predictAddress, adminAddress, accountSalt, accountAddress, } = args;
if (predictAddress) {
return predictAddress(factoryContract, adminAddress);
}
if (accountAddress) {
return accountAddress;
}
if (!adminAddress) {
throw new Error("Account address is required to predict the smart wallet address.");
}
return withCache(async () => {
const saltHex = accountSalt && isHex$1(accountSalt)
? accountSalt
: stringToHex(accountSalt ?? "");
return readContract({
contract: factoryContract,
method: "function getAddress(address, bytes) returns (address)",
params: [adminAddress, saltHex],
});
}, {
cacheKey: `${args.factoryContract.chain.id}-${args.factoryContract.address}-${args.adminAddress}-${args.accountSalt}`,
cacheTime: 1000 * 60 * 60 * 24, // 1 day
});
}
/**
* @internal
*/
function prepareCreateAccount(args) {
const { adminAddress, factoryContract, createAccountOverride: createAccount, accountSalt, } = args;
if (createAccount) {
return createAccount(factoryContract, adminAddress);
}
const saltHex = accountSalt && isHex$1(accountSalt)
? accountSalt
: stringToHex(accountSalt ?? "");
return prepareContractCall({
contract: factoryContract,
method: "function createAccount(address, bytes) returns (address)",
params: [adminAddress, saltHex],
});
}
/**
* @internal
*/
function prepareExecute(args) {
const { accountContract, transaction, executeOverride: execute } = args;
if (execute) {
return execute(accountContract, transaction);
}
return prepareContractCall({
contract: accountContract,
method: "function execute(address, uint256, bytes)",
params: [
transaction.to || "",
transaction.value || 0n,
transaction.data || "0x",
],
// if gas is specified for the inner tx, use that and add 21k for the execute call on the account contract
// this avoids another estimateGas call when bundling the userOp
// and also allows for passing custom gas limits for the inner tx
gas: transaction.gas ? transaction.gas + 21000n : undefined,
});
}
/**
* @internal
*/
function prepareBatchExecute(args) {
const { accountContract, transactions, executeBatchOverride: executeBatch, } = args;
if (executeBatch) {
return executeBatch(accountContract, transactions);
}
return prepareContractCall({
contract: accountContract,
method: "function executeBatch(address[], uint256[], bytes[])",
params: [
transactions.map((tx) => tx.to || ""),
transactions.map((tx) => tx.value || 0n),
transactions.map((tx) => tx.data || "0x"),
],
});
}const FN_SELECTOR$2 = "0x35567e1a";
const FN_INPUTS$2 = [
{
type: "address",
name: "sender",
},
{
type: "uint192",
name: "key",
},
];
const FN_OUTPUTS$2 = [
{
type: "uint256",
name: "nonce",
},
];
/**
* Calls the "getNonce" function on the contract.
* @param options - The options for the getNonce function.
* @returns The parsed result of the function call.
* @extension ERC4337
* @example
* ```ts
* import { getNonce } from "thirdweb/extensions/erc4337";
*
* const result = await getNonce({
* contract,
* sender: ...,
* key: ...,
* });
*
* ```
*/
async function getNonce(options) {
return readContract({
contract: options.contract,
method: [FN_SELECTOR$2, FN_INPUTS$2, FN_OUTPUTS$2],
params: [options.sender, options.key],
});
}const FN_SELECTOR$1 = "0xa6193531";
const FN_INPUTS$1 = [
{
type: "tuple",
name: "userOp",
components: [
{
type: "address",
name: "sender",
},
{
type: "uint256",
name: "nonce",
},
{
type: "bytes",
name: "initCode",
},
{
type: "bytes",
name: "callData",
},
{
type: "uint256",
name: "callGasLimit",
},
{
type: "uint256",
name: "verificationGasLimit",
},
{
type: "uint256",
name: "preVerificationGas",
},
{
type: "uint256",
name: "maxFeePerGas",
},
{
type: "uint256",
name: "maxPriorityFeePerGas",
},
{
type: "bytes",
name: "paymasterAndData",
},
{
type: "bytes",
name: "signature",
},
],
},
];
const FN_OUTPUTS$1 = [
{
type: "bytes32",
},
];
/**
* Calls the "getUserOpHash" function on the contract.
* @param options - The options for the getUserOpHash function.
* @returns The parsed result of the function call.
* @extension ERC4337
* @example
* ```ts
* import { getUserOpHash } from "thirdweb/extensions/erc4337";
*
* const result = await getUserOpHash({
* contract,
* userOp: ...,
* });
*
* ```
*/
async function getUserOpHash$2(options) {
return readContract({
contract: options.contract,
method: [FN_SELECTOR$1, FN_INPUTS$1, FN_OUTPUTS$1],
params: [options.userOp],
});
}const FN_SELECTOR = "0x22cdde4c";
const FN_INPUTS = [
{
type: "tuple",
name: "userOp",
components: [
{
type: "address",
name: "sender",
},
{
type: "uint256",
name: "nonce",
},
{
type: "bytes",
name: "initCode",
},
{
type: "bytes",
name: "callData",
},
{
type: "bytes32",
name: "accountGasLimits",
},
{
type: "uint256",
name: "preVerificationGas",
},
{
type: "bytes32",
name: "gasFees",
},
{
type: "bytes",
name: "paymasterAndData",
},
{
type: "bytes",
name: "signature",
},
],
},
];
const FN_OUTPUTS = [
{
type: "bytes32",
},
];
/**
* Calls the "getUserOpHash" function on the contract.
* @param options - The options for the getUserOpHash function.
* @returns The parsed result of the function call.
* @extension ERC4337
* @example
* ```ts
* import { getUserOpHash } from "thirdweb/extensions/erc4337";
*
* const result = await getUserOpHash({
* contract,
* userOp: ...,
* });
*
* ```
*/
async function getUserOpHash$1(options) {
return readContract({
contract: options.contract,
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS],
params: [options.userOp],
});
}function getInitCode(unpackedUserOperation) {
return unpackedUserOperation.factory
? concat([
unpackedUserOperation.factory,
unpackedUserOperation.factoryData || "0x",
])
: "0x";
}
function getAccountGasLimits(unpackedUserOperation) {
return concat([
pad(toHex(BigInt(unpackedUserOperation.verificationGasLimit)), {
size: 16,
}),
pad(toHex(BigInt(unpackedUserOperation.callGasLimit)), { size: 16 }),
]);
}
function getGasLimits(unpackedUserOperation) {
return concat([
pad(toHex(BigInt(unpackedUserOperation.maxPriorityFeePerGas)), {
size: 16,
}),
pad(toHex(BigInt(unpackedUserOperation.maxFeePerGas)), { size: 16 }),
]);
}
function getPaymasterAndData$1(unpackedUserOperation) {
return unpackedUserOperation.paymaster
? concat([
unpackedUserOperation.paymaster,
pad(toHex(BigInt(unpackedUserOperation.paymasterVerificationGasLimit || 0)), {
size: 16,
}),
pad(toHex(BigInt(unpackedUserOperation.paymasterPostOpGasLimit || 0)), {
size: 16,
}),
unpackedUserOperation.paymasterData || "0x",
])
: "0x";
}
const getPackedUserOperation = (userOperation) => {
return {
sender: userOperation.sender,
nonce: BigInt(userOperation.nonce),
initCode: getInitCode(userOperation),
callData: userOperation.callData,
accountGasLimits: getAccountGasLimits(userOperation),
preVerificationGas: BigInt(userOperation.preVerificationGas),
gasFees: getGasLimits(userOperation),
paymasterAndData: getPaymasterAndData$1(userOperation),
signature: userOperation.signature,
};
};const generateRandomUint192 = () => {
const rand1 = BigInt(Math.floor(Math.random() * 0x100000000));
const rand2 = BigInt(Math.floor(Math.random() * 0x100000000));
const rand3 = BigInt(Math.floor(Math.random() * 0x100000000));
const rand4 = BigInt(Math.floor(Math.random() * 0x100000000));
const rand5 = BigInt(Math.floor(Math.random() * 0x100000000));
const rand6 = BigInt(Math.floor(Math.random() * 0x100000000));
return ((rand1 << BigInt(160)) |
(rand2 << BigInt(128)) |
(rand3 << BigInt(96)) |
(rand4 << BigInt(64)) |
(rand5 << BigInt(32)) |
rand6);
};
/**
* @internal
*/
function hexlifyUserOp(userOp) {
return Object.fromEntries(Object.entries(userOp).map(([key, val]) => [
key,
// turn any value that's not hex into hex
val === undefined || val === null || isHex$1(val) ? val : toHex$1(val),
]));
}/**
* Get paymaster and data details for a user operation.
* @param args - The userOp and options
* @returns - The paymaster and data details for the user operation.
* @example
* ```ts
* import { getPaymasterAndData } from "thirdweb/wallets/smart";
*
* const userOp = createUnsignedUserOp(...);
*
* const paymasterAndData = await getPaymasterAndData({
* userOp,
* client,
* chain,
* });
* ```
* @walletUtils
*/
async function getPaymasterAndData(args) {
const { userOp, paymasterOverride, client, chain, entrypointAddress } = args;
if (paymasterOverride) {
return paymasterOverride(userOp);
}
const headers = {
"Content-Type": "application/json",
};
const entrypoint = entrypointAddress ?? ENTRYPOINT_ADDRESS_v0_6;
const paymasterUrl = getDefaultBundlerUrl(chain);
const body = {
jsonrpc: "2.0",
id: 1,
method: "pm_sponsorUserOperation",
params: [hexlifyUserOp(userOp), entrypoint],
};
// Ask the paymaster to sign the transaction and return a valid paymasterAndData value.
const fetchWithHeaders = getClientFetch(client);
const response = await fetchWithHeaders(paymasterUrl, {
method: "POST",
headers,
body: stringify(body),
});
const res = await response.json();
if (!response.ok) {
const error = res.error || response.statusText;
const code = res.code || "UNKNOWN";
throw new Error(`Paymaster error: ${error}
Status: ${response.status}
Code: ${code}`);
}
if (res.result) {
// some paymasters return a string, some return an object with more data
if (typeof res.result === "string") {
return {
paymasterAndData: res.result,
};
}
// check for policy errors
if (res.result.policyId && res.result.reason) {
console.warn(`Paymaster policy rejected this transaction with reason: ${res.result.reason} (policyId: ${res.result.policyId})`);
}
return {
paymasterAndData: res.result.paymasterAndData,
verificationGasLimit: res.result.verificationGasLimit
? hexToBigInt(res.result.verificationGasLimit)
: undefined,
preVerificationGas: res.result.preVerificationGas
? hexToBigInt(res.result.preVerificationGas)
: undefined,
callGasLimit: res.result.callGasLimit
? hexToBigInt(res.result.callGasLimit)
: undefined,
paymaster: res.result.paymaster,
paymasterData: res.result.paymasterData,
paymasterVerificationGasLimit: res.result.paymasterVerificationGasLimit
? hexToBigInt(res.result.paymasterVerificationGasLimit)
: undefined,
paymasterPostOpGasLimit: res.result.paymasterPostOpGasLimit
? hexToBigInt(res.result.paymasterPostOpGasLimit)
: undefined,
};
}
const error = res.error?.message || res.error || response.statusText || "unknown error";
throw new Error(`Paymaster error from ${paymasterUrl}: ${error}`);
}const isDeployingSet = new Set();
const getKey = (accountContract) => {
return `${accountContract.chain.id}:${accountContract.address}`;
};
const markAccountDeploying = (accountContract) => {
isDeployingSet.add(getKey(accountContract));
};
const clearAccountDeploying = (accountContract) => {
isDeployingSet.delete(getKey(accountContract));
};
const isAccountDeploying = (accountContract) => {
return isDeployingSet.has(getKey(accountContract));
};
/**
* Wait for the user operation to be mined.
* @param args - The options and user operation hash
* @returns - The transaction receipt
*
* @example
* ```ts
* import { waitForUserOpReceipt } from "thirdweb/wallets/smart";
*
* const receipt = await waitForUserOpReceipt({
* chain,
* client,
* userOpHash,
* });
* ```
* @walletUtils
*/
async function waitForUserOpReceipt(args) {
const timeout = args.timeoutMs || 120000; // 2mins
const interval = args.intervalMs || 1000; // 1s
const endtime = Date.now() + timeout;
while (Date.now() < endtime) {
const userOpReceipt = await getUserOpReceipt(args);
if (userOpReceipt) {
return userOpReceipt;
}
await new Promise((resolve) => setTimeout(resolve, interval));
}
throw new Error(`Timeout waiting for userOp to be mined on chain ${args.chain.id} with UserOp hash: ${args.userOpHash}`);
}
/**
* Creates an unsigned user operation from a prepared transaction.
* @param args - The prepared transaction and options
* @returns - The unsigned user operation
* @example
* ```ts
* import { createUnsignedUserOp } from "thirdweb/wallets/smart";
*
* const transaction = prepareContractCall(...);
*
* const userOp = await createUnsignedUserOp({
* transaction,
* factoryContract,
* accountContract,
* adminAddress,
* sponsorGas,
* overrides,
* });
* ```
* @walletUtils
*/
async function createUnsignedUserOp(args) {
const { transaction: executeTx, accountContract, factoryContract, adminAddress, overrides, sponsorGas, waitForDeployment = true, isDeployedOverride, } = args;
const chain = executeTx.chain;
const client = executeTx.client;
const bundlerOptions = {
client,
chain,
bundlerUrl: overrides?.bundlerUrl,
entrypointAddress: overrides?.entrypointAddress,
};
const entrypointVersion = getEntryPointVersion(args.overrides?.entrypointAddress || ENTRYPOINT_ADDRESS_v0_6);
const [isDeployed, callData, callGasLimit, gasFees, nonce] = await Promise.all([
typeof isDeployedOverride === "boolean"
? isDeployedOverride
: isContractDeployed(accountContract).then((isDeployed) => isDeployed || isAccountDeploying(accountContract)),
encode(executeTx),
resolvePromisedValue(executeTx.gas),
getGasFees({
executeTx,
bundlerOptions,
chain,
client,
}),
getAccountNonce({
accountContract,
chain,
client,
entrypointAddress: overrides?.entrypointAddress,
getNonceOverride: overrides?.getAccountNonce,
}),
]);
const { maxFeePerGas, maxPriorityFeePerGas } = gasFees;
if (entrypointVersion === "v0.7") {
return populateUserOp_v0_7({
bundlerOptions,
factoryContract,
accountContract,
adminAddress,
sponsorGas,
overrides,
isDeployed,
nonce,
callData,
callGasLimit,
maxFeePerGas,
maxPriorityFeePerGas,
waitForDeployment,
});
}
// default to v0.6
return populateUserOp_v0_6({
bundlerOptions,
factoryContract,
accountContract,
adminAddress,
sponsorGas,
overrides,
isDeployed,
nonce,
callData,
callGasLimit,
maxFeePerGas,
maxPriorityFeePerGas,
waitForDeployment,
});
}
async function getGasFees(args) {
const { executeTx, bundlerOptions, chain, client } = args;
let { maxFeePerGas, maxPriorityFeePerGas } = executeTx;
const bundlerUrl = bundlerOptions?.bundlerUrl ?? getDefaultBundlerUrl(chain);
if (isThirdwebUrl(bundlerUrl)) {
// get gas prices from bundler
const bundlerGasPrice = await getUserOpGasFees({
options: bundlerOptions,
});
maxFeePerGas = bundlerGasPrice.maxFeePerGas;
maxPriorityFeePerGas = bundlerGasPrice.maxPriorityFeePerGas;
}
else {
// Check for expl