@citizenwallet/sdk
Version:
An sdk to easily work with citizen wallet.
361 lines • 16.5 kB
JavaScript
;
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