UNPKG

@abstract-foundation/agw-client

Version:
155 lines (138 loc) 4.59 kB
import { type Account, type Address, BaseError, type Chain, type Client, getAddress, InvalidAddressError, type IsUndefined, isAddress, type MaybeRequired, type Transport, } from "viem"; import { readContract } from "viem/actions"; import { getAction, parseAccount } from "viem/utils"; import type { ChainEIP712 } from "viem/zksync"; import { ExclusiveDelegateResolverAbi } from "../abis/ExclusiveDelegateResolver.js"; import { AGW_LINK_DELEGATION_RIGHTS, CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, } from "../constants.js"; import { AccountNotFoundError } from "../errors/account.js"; export interface GetLinkedAgwReturnType { agw: Address | undefined; } export type GetLinkedAgwParameters< account extends Account | undefined = Account | undefined, > = MaybeRequired<{ address?: Address | undefined }, IsUndefined<account>>; export type GetLinkedAgwAction< account extends Account | undefined = Account | undefined, > = IsUndefined<account> extends true ? ( parameters: GetLinkedAgwParameters<account>, ) => Promise<GetLinkedAgwReturnType> : ( parameters?: GetLinkedAgwParameters<account>, ) => Promise<GetLinkedAgwReturnType>; export interface IsLinkedAccountParameters { address: Address; } /** * Get the linked Abstract Global Wallet for an Ethereum Mainnet address. * * @example * ```tsx * import { linkableWalletActions } from "@abstract-foundation/agw-client"; * import { createWalletClient, custom } from "viem"; * import { sepolia } from "viem/chains"; * * export default function CheckLinkedWallet() { * async function checkLinkedWallet() { * // Initialize a Viem Wallet client and extend it with linkableWalletActions * const client = createWalletClient({ * chain: sepolia, * transport: custom(window.ethereum!), * }).extend(linkableWalletActions()); * * // Check if an address has a linked AGW * const { agw } = await client.getLinkedAgw(); * * if (agw) { * console.log("Linked AGW:", agw); * } else { * console.log("No linked AGW found"); * } * } * * return <button onClick={checkLinkedWallet}>Check Linked AGW</button>; * } * ``` * * @param parameters - Parameters for getting the linked AGW. If the client has a connected account, this can be omitted * @param parameters.address - The Ethereum Mainnet address to check for a linked AGW. If not provided, defaults to the connected account's address * @returns Object containing the address of the linked AGW, or undefined if no AGW is linked */ export async function getLinkedAgw< chain extends Chain | undefined = Chain | undefined, >( client: Client<Transport, chain, undefined>, parameters: GetLinkedAgwParameters<undefined>, ): Promise<GetLinkedAgwReturnType>; export async function getLinkedAgw< chain extends Chain | undefined = Chain | undefined, account extends Account = Account, >( client: Client<Transport, chain, account>, parameters?: GetLinkedAgwParameters<account>, ): Promise<GetLinkedAgwReturnType>; export async function getLinkedAgw< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, >( client: Client<Transport, chain, account>, parameters?: GetLinkedAgwParameters<account>, ): Promise<GetLinkedAgwReturnType> { const { address = client.account?.address } = (parameters ?? {}) as { address?: Address | undefined; }; if (address === undefined) { throw new BaseError("No address provided"); } if (!isAddress(address, { strict: false })) { throw new InvalidAddressError({ address }); } const checksummedAddress = getAddress(address); const result = await getAction( client, readContract, "readContract", )({ abi: ExclusiveDelegateResolverAbi, address: CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, functionName: "exclusiveWalletByRights", args: [checksummedAddress, AGW_LINK_DELEGATION_RIGHTS], }); if (result === checksummedAddress) { return { agw: undefined, }; } return { agw: result, }; } export async function isLinkedAccount( client: Client<Transport, ChainEIP712, Account>, parameters: IsLinkedAccountParameters, ): Promise<boolean> { const { address } = parameters; if (client.account === undefined) { throw new AccountNotFoundError({ docsPath: "/docs/contract/readContract", }); } const clientAccount = parseAccount(client.account); const { agw } = await getLinkedAgw(client, { address }); return agw === clientAccount.address; }