@citizenwallet/sdk
Version:
An sdk to easily work with citizen wallet.
166 lines (136 loc) • 4.4 kB
text/typescript
import accountFactoryAbi from "../abi/AccountFactory.abi.json";
import erc20Abi from "../abi/ERC20.abi.json";
import accountAbi from "../abi/Account.abi.json";
import safeAccountAbi from "../abi/Safe.abi.json";
import { type CommunityConfig } from "../config";
import {
JsonRpcProvider,
Contract,
verifyMessage,
hashMessage,
getBytes,
} from "ethers";
export const getENSAddress = async (
mainnetRpcUrl: string,
domain: string
): Promise<string | null> => {
try {
const provider = new JsonRpcProvider(mainnetRpcUrl);
const address = await provider.resolveName(domain);
return address;
} catch (error) {
console.error("Failed to resolve ENS name", error);
return null;
}
};
export const getAccountAddress = async (
config: CommunityConfig,
address: string,
salt: bigint = BigInt(0),
options?: { accountFactoryAddress?: string }
): Promise<string | null> => {
const { accountFactoryAddress } = options ?? {};
const rpc = new JsonRpcProvider(config.getRPCUrl(accountFactoryAddress));
const contract = new Contract(
config.getAccountConfig(accountFactoryAddress).account_factory_address,
accountFactoryAbi,
rpc
);
try {
const accountAddress = await contract.getFunction("getAddress")(
address,
salt
);
return accountAddress;
} catch (error) {
console.error("Error fetching account address:", error);
return null;
}
};
export const getAccountBalance = async (
config: CommunityConfig,
address: string,
options?: { accountFactoryAddress?: string; tokenAddress?: string }
): Promise<bigint | null> => {
const { accountFactoryAddress, tokenAddress } = options ?? {};
const rpc = new JsonRpcProvider(config.getRPCUrl(accountFactoryAddress));
const contract = new Contract(
tokenAddress ?? config.primaryToken.address,
erc20Abi,
rpc
);
try {
const balance = await contract.getFunction("balanceOf")(address);
return balance;
} catch (error) {
console.error("Error fetching account balance:", error);
return null;
}
};
export const verifyAccountOwnership = async (
config: CommunityConfig,
accountAddress: string,
message: string,
signature: string,
options?: { accountFactoryAddress?: string }
): Promise<boolean> => {
const { accountFactoryAddress } = options ?? {};
const recoveredAddress = verifyMessage(
message.startsWith("0x") ? getBytes(message) : message,
signature
);
if (recoveredAddress.toLowerCase() === accountAddress.toLowerCase()) {
return true;
}
try {
const rpc = new JsonRpcProvider(config.getRPCUrl(accountFactoryAddress));
const contract = new Contract(accountAddress, accountAbi, rpc);
// Check if isValidSignature is implemented by calling it
try {
const hash = hashMessage(message);
const magicValue = await contract.getFunction("isValidSignature")(
hash,
signature
);
if (magicValue === "0x1626ba7e") {
return true;
}
} catch (error) {
console.warn(error);
// Function is not implemented
console.warn("isValidSignature is not implemented on this contract");
}
try {
const owner = await contract.getFunction("owner")();
if (owner.toLowerCase() === recoveredAddress.toLowerCase()) {
return true;
}
} catch (error) {
console.warn("owner function not implemented or failed:", error);
// If owner function doesn't exist or fails, we continue with other checks
}
const safeContract = new Contract(accountAddress, safeAccountAbi, rpc);
const isOwner = await safeContract.getFunction("isOwner")(recoveredAddress);
return isOwner;
} catch (error) {
console.error("Error verifying account ownership:", error);
}
return false;
};
export const isSafeOwner = async (
config: CommunityConfig,
accountAddress: string,
ownerAddress: string,
options?: { accountFactoryAddress?: string }
): Promise<boolean> => {
const { accountFactoryAddress } = options ?? {};
const rpc = new JsonRpcProvider(config.getRPCUrl(accountFactoryAddress));
const contract = new Contract(accountAddress, safeAccountAbi, rpc);
try {
const isOwner = await contract.getFunction("isOwner")(ownerAddress);
return isOwner;
} catch (error) {
console.error("Error verifying safe owner:", error);
return false;
}
};