permissionless
Version:
A utility library for working with ERC-4337
215 lines • 8.93 kB
JavaScript
import { concat, concatHex, domainSeparator, encodeAbiParameters, encodeFunctionData, encodePacked, getTypesForEIP712Domain, hashMessage, hashTypedData, keccak256, stringToHex, toHex, validateTypedData } from "viem";
import { entryPoint07Abi, entryPoint07Address, getUserOperationHash, toSmartAccount } from "viem/account-abstraction";
import { getChainId, readContract } from "viem/actions";
import { getAction } from "viem/utils";
import { getAccountNonce } from "../../actions/public/getAccountNonce.js";
import { decode7579Calls } from "../../utils/decode7579Calls.js";
import { encode7579Calls } from "../../utils/encode7579Calls.js";
import { toOwner } from "../../utils/toOwner.js";
const wrapMessageHash = (messageHash, { accountAddress, version, chainId }) => {
const _domainSeparator = domainSeparator({
domain: {
name: "Nexus",
version: version,
chainId,
verifyingContract: accountAddress
}
});
const parentStructHash = keccak256(encodeAbiParameters([{ type: "bytes32" }, { type: "bytes32" }], [
keccak256(stringToHex("PersonalSign(bytes prefixed)")),
messageHash
]));
return keccak256(concatHex(["0x1901", _domainSeparator, parentStructHash]));
};
/**
* The account creation ABI for Biconomy Smart Account (from the biconomy SmartAccountFactory)
*/
/**
* Default addresses for Biconomy Smart Account
*/
const BICONOMY_ADDRESSES = {
K1_VALIDATOR_FACTORY_ADDRESS: "0x00000bb19a3579F4D779215dEf97AFbd0e30DB55",
K1_VALIDATOR_ADDRESS: "0x00000004171351c442B202678c48D8AB5B321E8f"
};
export async function toNexusSmartAccount(parameters) {
const { owners, client, index = 0n, address, version, factoryAddress = BICONOMY_ADDRESSES.K1_VALIDATOR_FACTORY_ADDRESS, validatorAddress = BICONOMY_ADDRESSES.K1_VALIDATOR_ADDRESS, attesters = [], threshold = 0 } = parameters;
const localOwner = await toOwner({ owner: owners[0] });
const entryPoint = {
address: parameters.entryPoint?.address ?? entryPoint07Address,
abi: entryPoint07Abi,
version: parameters.entryPoint?.version ?? "0.7"
};
let accountAddress = address;
const getFactoryArgs = async () => {
return {
factory: factoryAddress,
factoryData: encodeFunctionData({
abi: [
{
name: "createAccount",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ type: "address", name: "eoaOwner" },
{ type: "uint256", name: "index" },
{ type: "address[]", name: "attesters" },
{ type: "uint8", name: "threshold" }
],
outputs: [{ type: "address" }]
}
],
functionName: "createAccount",
args: [
localOwner.address,
index,
attesters.sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase())),
threshold
]
})
};
};
let chainId;
const getMemoizedChainId = async () => {
if (chainId)
return chainId;
chainId = client.chain
? client.chain.id
: await getAction(client, getChainId, "getChainId")({});
return chainId;
};
return toSmartAccount({
client,
entryPoint,
getFactoryArgs,
async getAddress() {
if (accountAddress)
return accountAddress;
accountAddress = await readContract(client, {
address: factoryAddress,
abi: [
{
name: "computeAccountAddress",
type: "function",
stateMutability: "view",
inputs: [
{ type: "address", name: "eoaOwner" },
{ type: "uint256", name: "index" },
{ type: "address[]", name: "attesters" },
{ type: "uint8", name: "threshold" }
],
outputs: [{ type: "address" }]
}
],
functionName: "computeAccountAddress",
args: [
localOwner.address,
index,
attesters.sort((left, right) => left.toLowerCase().localeCompare(right.toLowerCase())),
threshold
]
});
return accountAddress;
},
async getNonce(args) {
const TIMESTAMP_ADJUSTMENT = 16777215n; // max value for size 3
const defaultedKey = (args?.key ?? 0n) % TIMESTAMP_ADJUSTMENT;
const defaultedValidationMode = "0x00";
const key = concat([
toHex(defaultedKey, { size: 3 }),
defaultedValidationMode,
validatorAddress
]);
const address = await this.getAddress();
return getAccountNonce(client, {
address,
entryPointAddress: entryPoint.address,
key: BigInt(key)
});
},
encodeCalls: async (calls) => {
return encode7579Calls({
mode: {
type: calls.length > 1 ? "batchcall" : "call",
revertOnError: false,
selector: "0x",
context: "0x"
},
callData: calls
});
},
async decodeCalls(callData) {
return decode7579Calls(callData).callData;
},
async getStubSignature() {
const dynamicPart = validatorAddress.substring(2).padEnd(40, "0");
return `0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000${dynamicPart}000000000000000000000000000000000000000000000000000000000000004181d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b00000000000000000000000000000000000000000000000000000000000000`;
},
async sign({ hash }) {
return this.signMessage({ message: hash });
},
async signMessage({ message }) {
const wrappedMessageHash = wrapMessageHash(hashMessage(message), {
version,
accountAddress: await this.getAddress(),
chainId: await getMemoizedChainId()
});
const signature = await localOwner.signMessage({
message: {
raw: wrappedMessageHash
}
});
return encodePacked(["address", "bytes"], [validatorAddress, signature]);
},
async signTypedData(typedData) {
const { message, primaryType, types: _types, domain } = typedData;
const types = {
EIP712Domain: getTypesForEIP712Domain({
domain: domain
}),
..._types
};
validateTypedData({
domain,
message,
primaryType,
types
});
const typedHash = hashTypedData({
message,
primaryType,
types,
domain
});
const wrappedMessageHash = wrapMessageHash(typedHash, {
version,
accountAddress: await this.getAddress(),
chainId: await getMemoizedChainId()
});
const signature = await localOwner.signMessage({
message: {
raw: wrappedMessageHash
}
});
return encodePacked(["address", "bytes"], [validatorAddress, signature]);
},
async signUserOperation(parameters) {
const { chainId = await getMemoizedChainId(), ...userOperation } = parameters;
if (!chainId)
throw new Error("Chain id not found");
const hash = getUserOperationHash({
userOperation: {
...userOperation,
sender: userOperation.sender ?? (await this.getAddress()),
signature: "0x"
},
entryPointAddress: entryPoint.address,
entryPointVersion: entryPoint.version,
chainId: chainId
});
return await localOwner.signMessage({
message: { raw: hash }
});
}
});
}
//# sourceMappingURL=toNexusSmartAccount.js.map