@abstract-foundation/agw-client
Version:
Abstract Global Wallet Client SDK
155 lines (138 loc) • 4.59 kB
text/typescript
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;
}