diff --git a/.changeset/funny-pugs-thank.md b/.changeset/funny-pugs-thank.md new file mode 100644 index 0000000..f452cb2 --- /dev/null +++ b/.changeset/funny-pugs-thank.md @@ -0,0 +1,5 @@ +--- +"@abstract-foundation/agw-client": minor +--- + +Tighten `getLinkedAgw` typing so account-hoisted clients can call it with zero arguments, while clients without a hoisted account must pass `{ address }`. diff --git a/packages/agw-client/src/actions/getLinkedAgw.ts b/packages/agw-client/src/actions/getLinkedAgw.ts index 1107ec3..99f33ef 100644 --- a/packages/agw-client/src/actions/getLinkedAgw.ts +++ b/packages/agw-client/src/actions/getLinkedAgw.ts @@ -6,7 +6,9 @@ import { type Client, getAddress, InvalidAddressError, + type IsUndefined, isAddress, + type MaybeRequired, type Transport, } from 'viem'; import { readContract } from 'viem/actions'; @@ -24,9 +26,19 @@ export interface GetLinkedAgwReturnType { agw: Address | undefined; } -export interface GetLinkedAgwParameters { - address?: Address | undefined; -} +export type GetLinkedAgwParameters< + account extends Account | undefined = Account | undefined, +> = MaybeRequired<{ address?: Address | undefined }, IsUndefined>; + +export type GetLinkedAgwAction< + account extends Account | undefined = Account | undefined, +> = IsUndefined extends true + ? ( + parameters: GetLinkedAgwParameters, + ) => Promise + : ( + parameters?: GetLinkedAgwParameters, + ) => Promise; export interface IsLinkedAccountParameters { address: Address; @@ -63,18 +75,33 @@ export interface IsLinkedAccountParameters { * } * ``` * - * @param parameters - Parameters for getting the linked AGW + * @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, + parameters: GetLinkedAgwParameters, +): Promise; +export async function getLinkedAgw< + chain extends Chain | undefined = Chain | undefined, + account extends Account = Account, +>( + client: Client, + parameters?: GetLinkedAgwParameters, +): Promise; export async function getLinkedAgw< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, >( client: Client, - parameters: GetLinkedAgwParameters, + parameters?: GetLinkedAgwParameters, ): Promise { - const { address = client.account?.address } = parameters; + const { address = client.account?.address } = (parameters ?? {}) as { + address?: Address | undefined; + }; if (address === undefined) { throw new BaseError('No address provided'); diff --git a/packages/agw-client/src/clients/decorators/linkablePublic.ts b/packages/agw-client/src/clients/decorators/linkablePublic.ts index 895c908..48312c0 100644 --- a/packages/agw-client/src/clients/decorators/linkablePublic.ts +++ b/packages/agw-client/src/clients/decorators/linkablePublic.ts @@ -6,15 +6,15 @@ import { getLinkedAccounts, } from '../../actions/getLinkedAccounts.js'; import { + type GetLinkedAgwAction, type GetLinkedAgwParameters, - type GetLinkedAgwReturnType, getLinkedAgw, } from '../../actions/getLinkedAgw.js'; -export interface LinkablePublicActions { - getLinkedAgw: ( - args: GetLinkedAgwParameters, - ) => Promise; +export interface LinkablePublicActions< + account extends Account | undefined = Account | undefined, +> { + getLinkedAgw: GetLinkedAgwAction; getLinkedAccounts: ( args: GetLinkedAccountsParameters, ) => Promise; @@ -25,10 +25,18 @@ export function linkablePublicActions< chain extends ChainEIP712 | undefined = ChainEIP712 | undefined, account extends Account | undefined = Account | undefined, >() { - return ( - client: Client, - ): LinkablePublicActions => ({ - getLinkedAgw: (args) => getLinkedAgw(client, args), + return < + clientTransport extends transport = transport, + clientChain extends chain = chain, + clientAccount extends account = account, + >( + client: Client, + ): LinkablePublicActions => ({ + getLinkedAgw: ((parameters?: GetLinkedAgwParameters) => + getLinkedAgw( + client as Client, + (parameters ?? {}) as GetLinkedAgwParameters, + )) as GetLinkedAgwAction, getLinkedAccounts: (args) => getLinkedAccounts(client, args), }); } diff --git a/packages/agw-client/src/clients/decorators/linkableWallet.ts b/packages/agw-client/src/clients/decorators/linkableWallet.ts index 60441f3..48a37a8 100644 --- a/packages/agw-client/src/clients/decorators/linkableWallet.ts +++ b/packages/agw-client/src/clients/decorators/linkableWallet.ts @@ -7,7 +7,8 @@ import { walletActions, } from 'viem'; import { - type GetLinkedAgwReturnType, + type GetLinkedAgwAction, + type GetLinkedAgwParameters, getLinkedAgw, } from '../../actions/getLinkedAgw.js'; import { @@ -21,7 +22,7 @@ export type LinkableWalletActions< account extends Account | undefined = Account | undefined, > = WalletActions & { linkToAgw: (args: LinkToAgwParameters) => Promise; - getLinkedAgw: () => Promise; + getLinkedAgw: GetLinkedAgwAction; }; export function linkableWalletActions< @@ -29,11 +30,19 @@ export function linkableWalletActions< chain extends Chain | undefined = Chain | undefined, account extends Account | undefined = Account | undefined, >() { - return ( - client: WalletClient, - ): LinkableWalletActions => ({ + return < + clientTransport extends transport = transport, + clientChain extends chain = chain, + clientAccount extends account = account, + >( + client: WalletClient, + ): LinkableWalletActions => ({ ...walletActions(client), linkToAgw: (args) => linkToAgw(client, args), - getLinkedAgw: () => getLinkedAgw(client, {}), + getLinkedAgw: ((parameters?: GetLinkedAgwParameters) => + getLinkedAgw( + client as WalletClient, + (parameters ?? {}) as GetLinkedAgwParameters, + )) as GetLinkedAgwAction, }); } diff --git a/packages/agw-client/test/src/actions/getLinkedAgw.test.ts b/packages/agw-client/test/src/actions/getLinkedAgw.test.ts new file mode 100644 index 0000000..eeb23cd --- /dev/null +++ b/packages/agw-client/test/src/actions/getLinkedAgw.test.ts @@ -0,0 +1,51 @@ +import { createClient, getAddress } from 'viem'; +import { ChainEIP712 } from 'viem/zksync'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { getLinkedAgw } from '../../../src/actions/getLinkedAgw.js'; +import { + AGW_LINK_DELEGATION_RIGHTS, + CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, +} from '../../../src/constants.js'; +import { anvilAbstractTestnet } from '../../anvil.js'; +import { address } from '../../constants.js'; + +vi.mock('viem/actions', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...(actual as object), + readContract: vi.fn(), + }; +}); + +import { readContract } from 'viem/actions'; + +const baseClient = createClient({ + account: address.smartAccountAddress, + chain: anvilAbstractTestnet.chain as ChainEIP712, + transport: anvilAbstractTestnet.clientConfig.transport, +}); + +beforeEach(() => { + vi.resetAllMocks(); +}); + +describe('getLinkedAgw', () => { + it('uses the connected account when parameters are omitted', async () => { + const linkedAgw = '0x1234567890123456789012345678901234567890'; + vi.mocked(readContract).mockResolvedValue(linkedAgw); + + const result = await getLinkedAgw(baseClient); + + expect(readContract).toHaveBeenCalledWith(baseClient, { + abi: expect.any(Array), + address: CANONICAL_EXCLUSIVE_DELEGATE_RESOLVER_ADDRESS, + functionName: 'exclusiveWalletByRights', + args: [ + getAddress(address.smartAccountAddress), + AGW_LINK_DELEGATION_RIGHTS, + ], + }); + expect(result).toEqual({ agw: linkedAgw }); + }); +});