UNPKG

@citizenwallet/sdk

Version:

An sdk to easily work with citizen wallet.

361 lines 16.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BundlerService = void 0; const ethers_1 = require("ethers"); const TokenEntryPoint_abi_json_1 = __importDefault(require("../abi/TokenEntryPoint.abi.json")); const AccountFactory_abi_json_1 = __importDefault(require("../abi/AccountFactory.abi.json")); const SafeAccountFactory_abi_json_1 = __importDefault(require("../abi/SafeAccountFactory.abi.json")); const Account_abi_json_1 = __importDefault(require("../abi/Account.abi.json")); const Safe_abi_json_1 = __importDefault(require("../abi/Safe.abi.json")); const ERC20_abi_json_1 = __importDefault(require("../abi/ERC20.abi.json")); const Profile_abi_json_1 = __importDefault(require("../abi/Profile.abi.json")); const profiles_1 = require("../profiles"); const crypto_1 = require("../utils/crypto"); const calldata_1 = require("../calldata"); const accountFactoryInterface = new ethers_1.ethers.Interface(AccountFactory_abi_json_1.default); const safeAccountFactoryInterface = new ethers_1.ethers.Interface(SafeAccountFactory_abi_json_1.default); const accountInterface = new ethers_1.ethers.Interface(Account_abi_json_1.default); const safeInterface = new ethers_1.ethers.Interface(Safe_abi_json_1.default); const erc20Token = new ethers_1.ethers.Interface(ERC20_abi_json_1.default); const profileInterface = new ethers_1.ethers.Interface(Profile_abi_json_1.default); const executeCallData = (contractAddress, value, calldata) => ethers_1.ethers.getBytes(accountInterface.encodeFunctionData("execute", [ contractAddress, value, calldata, ])); const executeSafeCallData = (contractAddress, value, calldata) => ethers_1.ethers.getBytes(safeInterface.encodeFunctionData("execTransactionFromModule", [ contractAddress, value, calldata, BigInt(0), ])); const transferCallData = (tokenAddress, value, receiver, amount) => ethers_1.ethers.getBytes(accountInterface.encodeFunctionData("execute", [ tokenAddress, value, erc20Token.encodeFunctionData("transfer", [receiver, amount]), ])); const safeTransferCallData = (tokenAddress, value, receiver, amount) => ethers_1.ethers.getBytes(safeInterface.encodeFunctionData("execTransactionFromModule", [ tokenAddress, value, erc20Token.encodeFunctionData("transfer", [receiver, amount]), BigInt(0), ])); const mintCallData = (tokenAddress, value, receiver, amount) => ethers_1.ethers.getBytes(accountInterface.encodeFunctionData("execute", [ tokenAddress, value, erc20Token.encodeFunctionData("mint", [receiver, amount]), ])); const safeMintCallData = (tokenAddress, value, receiver, amount) => ethers_1.ethers.getBytes(safeInterface.encodeFunctionData("execTransactionFromModule", [ tokenAddress, value, erc20Token.encodeFunctionData("mint", [receiver, amount]), BigInt(0), ])); const burnFromCallData = (tokenAddress, value, receiver, amount) => ethers_1.ethers.getBytes(accountInterface.encodeFunctionData("execute", [ tokenAddress, value, erc20Token.encodeFunctionData("burnFrom", [receiver, amount]), ])); const safeBurnFromCallData = (tokenAddress, value, receiver, amount) => ethers_1.ethers.getBytes(safeInterface.encodeFunctionData("execTransactionFromModule", [ tokenAddress, value, erc20Token.encodeFunctionData("burnFrom", [receiver, amount]), BigInt(0), ])); const profileCallData = (profileContractAddress, profileAccountAddress, username, ipfsHash) => { return ethers_1.ethers.getBytes(accountInterface.encodeFunctionData("execute", [ profileContractAddress, BigInt(0), profileInterface.encodeFunctionData("set", [ profileAccountAddress, (0, profiles_1.formatUsernameToBytes32)(username), ipfsHash, ]), ])); }; const approveCallData = (tokenAddress, issuer, amount) => ethers_1.ethers.getBytes(accountInterface.encodeFunctionData("execute", [ tokenAddress, BigInt(0), erc20Token.encodeFunctionData("approve", [issuer, amount]), ])); const getEmptyUserOp = (sender) => ({ sender, nonce: BigInt(0), initCode: ethers_1.ethers.getBytes("0x"), callData: ethers_1.ethers.getBytes("0x"), callGasLimit: BigInt(0), verificationGasLimit: BigInt(0), preVerificationGas: BigInt(0), maxFeePerGas: BigInt(0), maxPriorityFeePerGas: BigInt(0), paymasterAndData: ethers_1.ethers.getBytes("0x"), signature: ethers_1.ethers.getBytes("0x"), }); const userOpToJson = (userop) => { const newUserop = { sender: userop.sender, nonce: ethers_1.ethers.toBeHex(userop.nonce.toString()).replace("0x0", "0x"), initCode: ethers_1.ethers.hexlify(userop.initCode), callData: ethers_1.ethers.hexlify(userop.callData), callGasLimit: ethers_1.ethers .toBeHex(userop.callGasLimit.toString()) .replace("0x0", "0x"), verificationGasLimit: ethers_1.ethers .toBeHex(userop.verificationGasLimit.toString()) .replace("0x0", "0x"), preVerificationGas: ethers_1.ethers .toBeHex(userop.preVerificationGas.toString()) .replace("0x0", "0x"), maxFeePerGas: ethers_1.ethers .toBeHex(userop.maxFeePerGas.toString()) .replace("0x0", "0x"), maxPriorityFeePerGas: ethers_1.ethers .toBeHex(userop.maxPriorityFeePerGas.toString()) .replace("0x0", "0x"), paymasterAndData: ethers_1.ethers.hexlify(userop.paymasterAndData), signature: ethers_1.ethers.hexlify(userop.signature), }; return newUserop; }; const userOpFromJson = (userop) => { const newUserop = { sender: userop.sender, nonce: BigInt(userop.nonce), initCode: ethers_1.ethers.getBytes(userop.initCode), callData: ethers_1.ethers.getBytes(userop.callData), callGasLimit: BigInt(userop.callGasLimit), verificationGasLimit: BigInt(userop.verificationGasLimit), preVerificationGas: BigInt(userop.preVerificationGas), maxFeePerGas: BigInt(userop.maxFeePerGas), maxPriorityFeePerGas: BigInt(userop.maxPriorityFeePerGas), paymasterAndData: ethers_1.ethers.getBytes(userop.paymasterAndData), signature: ethers_1.ethers.getBytes(userop.signature), }; return newUserop; }; class BundlerService { constructor(config, options) { this.config = config; this.options = {}; this.config = config; const rpcUrl = this.config.primaryRPCUrl; this.provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl); this.accountType = this.config.primaryAccountConfig.paymaster_type; if (options) { this.options = { ...this.options, ...options }; } } async senderAccountExists(sender) { const url = `${this.config.primaryNetwork.node.url}/v1/accounts/${sender}/exists`; const resp = await fetch(url); return resp.status === 200; } generateUserOp(signerAddress, sender, senderAccountExists = false, accountFactoryAddress, callData) { const userop = getEmptyUserOp(sender); // initCode if (!senderAccountExists) { const accountCreationCode = this.accountType === "cw-safe" ? safeAccountFactoryInterface.encodeFunctionData("createAccount", [ signerAddress, BigInt(0), ]) : accountFactoryInterface.encodeFunctionData("createAccount", [ signerAddress, BigInt(0), ]); userop.initCode = ethers_1.ethers.getBytes(ethers_1.ethers.concat([accountFactoryAddress, accountCreationCode])); } // callData userop.callData = callData; return userop; } async prepareUserOp(owner, sender, callData) { const accountsConfig = this.config.primaryAccountConfig; const accountFactoryAddress = accountsConfig.account_factory_address; // check that the sender's account exists const exists = await this.senderAccountExists(sender); // generate a userop const userop = this.generateUserOp(owner, sender, exists, accountFactoryAddress, callData); return userop; } async paymasterSignUserOp(userop) { const method = "pm_ooSponsorUserOperation"; const accountsConfig = this.config.primaryAccountConfig; const params = [ userOpToJson(userop), accountsConfig.entrypoint_address, { type: accountsConfig.paymaster_type }, 1, ]; const response = await this.provider.send(method, params); if (!response?.length) { throw new Error("Invalid response"); } return userOpFromJson(response[0]); } async signUserOp(signer, userop) { const accountsConfig = this.config.primaryAccountConfig; const tokenEntryPointContract = new ethers_1.ethers.Contract(accountsConfig.entrypoint_address, TokenEntryPoint_abi_json_1.default, this.provider); const userOpHash = ethers_1.ethers.getBytes(await tokenEntryPointContract.getUserOpHash(userop)); const signature = ethers_1.ethers.getBytes(await signer.signMessage(userOpHash)); return signature; } async submitUserOp(userop, data, extraData) { const method = "eth_sendUserOperation"; const primaryAccountConfig = this.config.primaryAccountConfig; const params = [ userOpToJson(userop), primaryAccountConfig.entrypoint_address, ]; if (data) { params.push(data); } if (extraData) { params.push(extraData); } const response = await this.provider.send(method, params); if (!response?.length) { throw new Error("Invalid response"); } return response; } async call(signer, contractAddress, sender, data, value, userOpData, extraData) { const owner = await signer.getAddress(); const calldata = this.accountType === "cw-safe" ? executeSafeCallData(contractAddress, value ?? BigInt(0), data) : executeCallData(contractAddress, value ?? BigInt(0), data); let userop = await this.prepareUserOp(owner, sender, calldata); // get the paymaster to sign the userop userop = await this.paymasterSignUserOp(userop); // sign the userop const signature = await this.signUserOp(signer, userop); userop.signature = signature; // submit the user op const hash = await this.submitUserOp(userop, userOpData, extraData); return hash; } async sendERC20Token(signer, tokenAddress, from, to, amount, description) { const token = this.config.primaryToken; const formattedAmount = ethers_1.ethers.parseUnits(amount, token.decimals); const calldata = this.accountType === "cw-safe" ? safeTransferCallData(tokenAddress, BigInt(0), to, formattedAmount) : transferCallData(tokenAddress, BigInt(0), to, formattedAmount); const owner = await signer.getAddress(); let userop = await this.prepareUserOp(owner, from, calldata); // get the paymaster to sign the userop userop = await this.paymasterSignUserOp(userop); // sign the userop const signature = await this.signUserOp(signer, userop); userop.signature = signature; const data = { topic: calldata_1.tokenTransferEventTopic, from, to, value: formattedAmount.toString(), }; // submit the user op const hash = await this.submitUserOp(userop, data, description !== undefined ? { description } : undefined); return hash; } async mintERC20Token(signer, tokenAddress, from, to, amount, description) { const token = this.config.primaryToken; const formattedAmount = ethers_1.ethers.parseUnits(amount, token.decimals); const calldata = this.accountType === "cw-safe" ? safeMintCallData(tokenAddress, BigInt(0), to, formattedAmount) : mintCallData(tokenAddress, BigInt(0), to, formattedAmount); const owner = await signer.getAddress(); let userop = await this.prepareUserOp(owner, from, calldata); try { // get the paymaster to sign the userop userop = await this.paymasterSignUserOp(userop); // sign the userop const signature = await this.signUserOp(signer, userop); userop.signature = signature; } catch (e) { throw new Error(`Error preparing user op: ${e}`); } try { const data = { topic: calldata_1.tokenTransferEventTopic, from: ethers_1.ethers.ZeroAddress, to, value: formattedAmount.toString(), }; // submit the user op const hash = await this.submitUserOp(userop, data, description !== undefined ? { description } : undefined); return hash; } catch (e) { if (!(await (0, crypto_1.hasRole)(tokenAddress, crypto_1.MINTER_ROLE, from, this.provider))) { throw new Error(`Signer (${from}) does not have the MINTER_ROLE on token contract ${tokenAddress}`); } throw new Error(`Error submitting user op: ${e}`); } } async burnFromERC20Token(signer, tokenAddress, sender, from, amount, description) { const token = this.config.primaryToken; const formattedAmount = ethers_1.ethers.parseUnits(amount, token.decimals); const calldata = this.accountType === "cw-safe" ? safeBurnFromCallData(tokenAddress, BigInt(0), from, formattedAmount) : burnFromCallData(tokenAddress, BigInt(0), from, formattedAmount); const owner = await signer.getAddress(); let userop = await this.prepareUserOp(owner, sender, calldata); try { // get the paymaster to sign the userop userop = await this.paymasterSignUserOp(userop); // sign the userop const signature = await this.signUserOp(signer, userop); userop.signature = signature; } catch (e) { throw new Error(`Error preparing user op: ${e}`); } try { const data = { topic: calldata_1.tokenTransferEventTopic, from, to: ethers_1.ethers.ZeroAddress, value: formattedAmount.toString(), }; // submit the user op const hash = await this.submitUserOp(userop, data, description !== undefined ? { description } : undefined); return hash; } catch (e) { if (!(await (0, crypto_1.hasRole)(tokenAddress, crypto_1.MINTER_ROLE, from, this.provider))) { throw new Error(`Signer (${from}) does not have the MINTER_ROLE on token contract ${tokenAddress}`); } throw new Error(`Error submitting user op: ${e}`); } } async setProfile(signer, signerAccountAddress, profileAccountAddress, username, ipfsHash) { const profile = this.config.community.profile; const calldata = profileCallData(profile.address, profileAccountAddress, username, ipfsHash); const owner = await signer.getAddress(); let userop = await this.prepareUserOp(owner, signerAccountAddress, calldata); // get the paymaster to sign the userop userop = await this.paymasterSignUserOp(userop); // sign the userop const signature = await this.signUserOp(signer, userop); userop.signature = signature; // submit the user op const hash = await this.submitUserOp(userop); return hash; } async awaitSuccess(txHash, timeout = 12000) { const receipt = await this.provider.waitForTransaction(txHash, 1, timeout); if (!receipt) { throw new Error("Transaction failed"); } if (receipt.status !== 1) { throw new Error("Transaction failed"); } return receipt; } } exports.BundlerService = BundlerService; //# sourceMappingURL=index.js.map