Skip to content

Commit

Permalink
feat: custom paymaster handler on sendTransaction (#221)
Browse files Browse the repository at this point in the history
* feat: add custom paymaster handler to send transaction and write contract methods

* changeset

* update custom paymaster handling

* fix typing

* fix tests

* changeset

* custom paymaster handler on sendTransaction (#218)

* feat: add optional query client to agwProvider (#203)

* add optional query client to agwProvider

* add optional query client to abstractPrivyProvider

* changeset

* make field required but default value

---------

Co-authored-by: Coffee☕️ <[email protected]>

* Version Packages (#215)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix merge

* update tests

---------

Co-authored-by: Utkir S. <[email protected]>
Co-authored-by: Hung Doan <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
5 people authored Jan 30, 2025
1 parent 6ed7630 commit 0178623
Show file tree
Hide file tree
Showing 18 changed files with 141 additions and 34 deletions.
7 changes: 7 additions & 0 deletions .changeset/rotten-rivers-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@abstract-foundation/agw-client': minor
'@abstract-foundation/agw-react': minor
'@abstract-foundation/web3-react-agw': minor
---

Update sendTransaction flows to use custom paymaster handler if passed in
5 changes: 5 additions & 0 deletions .changeset/weak-maps-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@abstract-foundation/agw-client': minor
---

Add paymaster handler functions for sending transactions
34 changes: 33 additions & 1 deletion packages/agw-client/src/abstractClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { toAccount } from 'viem/accounts';
import { type ChainEIP712 } from 'viem/zksync';

import type { CustomPaymasterHandler } from './types/customPaymaster.js';
import { getSmartAccountAddressFromInitialSigner } from './utils.js';
import {
type AbstractWalletActions,
Expand Down Expand Up @@ -41,9 +42,34 @@ interface CreateAbstractClientParameters {
* @optional
*/
transport?: Transport;

/**
* The address of the smart account.
* @type {Address}
* @optional
*/
address?: Address;

/**
* Whether the client is a Privy cross-app client.
* @type {boolean}
* @optional
*/
isPrivyCrossApp?: boolean;

/**
* The transport layer for the underlying public client.
* @type {Transport}
* @optional
*/
publicTransport?: Transport;

/**
* The custom paymaster handler.
* @type {CustomPaymasterHandler}
* @optional
*/
customPaymasterHandler?: CustomPaymasterHandler;
}

type AbstractClientActions = AbstractWalletActions<ChainEIP712, Account>;
Expand All @@ -58,6 +84,7 @@ export async function createAbstractClient({
address,
isPrivyCrossApp = false,
publicTransport = http(),
customPaymasterHandler,
}: CreateAbstractClientParameters): Promise<AbstractClient> {
if (!transport) {
throw new Error('Transport is required');
Expand Down Expand Up @@ -89,7 +116,12 @@ export async function createAbstractClient({
});

const abstractClient = baseClient.extend(
globalWalletActions(signerWalletClient, publicClient, isPrivyCrossApp),
globalWalletActions(
signerWalletClient,
publicClient,
isPrivyCrossApp,
customPaymasterHandler,
),
);
return abstractClient as AbstractClient;
}
35 changes: 34 additions & 1 deletion packages/agw-client/src/actions/sendTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import {
SMART_ACCOUNT_FACTORY_ADDRESS,
} from '../constants.js';
import { type Call } from '../types/call.js';
import type {
CustomPaymasterHandler,
PaymasterArgs,
} from '../types/customPaymaster.js';
import type { SendTransactionBatchParameters } from '../types/sendTransactionBatch.js';
import { getInitializerCalldata, isSmartAccountDeployed } from '../utils.js';
import { sendPrivyTransaction } from './sendPrivyTransaction.js';
Expand All @@ -40,6 +44,7 @@ export async function sendTransactionBatch<
publicClient: PublicClient<Transport, ChainEIP712>,
parameters: SendTransactionBatchParameters<request>,
isPrivyCrossApp = false,
customPaymasterHandler: CustomPaymasterHandler | undefined = undefined,
): Promise<SendTransactionReturnType> {
const { calls, paymaster, paymasterInput, ...rest } = parameters;
if (calls.length === 0) {
Expand Down Expand Up @@ -145,6 +150,8 @@ export async function sendTransactionBatch<
},
EOA_VALIDATOR_ADDRESS,
!isDeployed,
{},
customPaymasterHandler,
);
}

Expand All @@ -167,8 +174,32 @@ export async function sendTransaction<
request
>,
isPrivyCrossApp = false,
customPaymasterHandler: CustomPaymasterHandler | undefined = undefined,
): Promise<SendEip712TransactionReturnType> {
if (isPrivyCrossApp) return await sendPrivyTransaction(client, parameters);
if (isPrivyCrossApp) {
let paymasterData: Partial<PaymasterArgs> = {};
// SendEip712TransactionParameters doesn't actually have paymaster or paymasterInput fields
// defined, so we just have to cast to any here to access them
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const requestAsAny = parameters as any;
if (
customPaymasterHandler &&
!requestAsAny.paymaster &&
!requestAsAny.paymasterInput
) {
paymasterData = await customPaymasterHandler({
...(parameters as any),
from: client.account.address,
chainId: parameters.chain?.id ?? client.chain.id,
});
}

const updatedParameters = {
...parameters,
...(paymasterData as any),
};
return await sendPrivyTransaction(client, updatedParameters);
}

const isDeployed = await isSmartAccountDeployed(
publicClient,
Expand Down Expand Up @@ -208,5 +239,7 @@ export async function sendTransaction<
parameters,
EOA_VALIDATOR_ADDRESS,
!isDeployed,
{},
customPaymasterHandler,
);
}
3 changes: 3 additions & 0 deletions packages/agw-client/src/actions/sendTransactionForSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getPeriodIdsForTransaction,
type SessionConfig,
} from '../sessions.js';
import type { CustomPaymasterHandler } from '../types/customPaymaster.js';
import { isSmartAccountDeployed } from '../utils.js';
import { sendTransactionInternal } from './sendTransactionInternal.js';

Expand Down Expand Up @@ -60,6 +61,7 @@ export async function sendTransactionForSession<
request
>,
session: SessionConfig,
customPaymasterHandler: CustomPaymasterHandler | undefined = undefined,
): Promise<SendEip712TransactionReturnType> {
const isDeployed = await isSmartAccountDeployed(
publicClient,
Expand Down Expand Up @@ -94,5 +96,6 @@ export async function sendTransactionForSession<
}),
),
},
customPaymasterHandler,
);
}
3 changes: 3 additions & 0 deletions packages/agw-client/src/actions/sendTransactionInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { INSUFFICIENT_BALANCE_SELECTOR } from '../constants.js';
import { AccountNotFoundError } from '../errors/account.js';
import { InsufficientBalanceError } from '../errors/insufficientBalance.js';
import type { CustomPaymasterHandler } from '../types/customPaymaster.js';
import { prepareTransactionRequest } from './prepareTransaction.js';
import { signTransaction } from './signTransaction.js';

Expand All @@ -48,6 +49,7 @@ export async function sendTransactionInternal<
validator: Address,
isInitialTransaction: boolean,
validationHookData: Record<string, Hex> = {},
customPaymasterHandler: CustomPaymasterHandler | undefined = undefined,
): Promise<SendEip712TransactionReturnType> {
const { chain = client.chain } = parameters;

Expand Down Expand Up @@ -91,6 +93,7 @@ export async function sendTransactionInternal<
validator,
isInitialTransaction,
validationHookData,
customPaymasterHandler,
);
return await getAction(
client,
Expand Down
22 changes: 1 addition & 21 deletions packages/agw-client/src/actions/signTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,11 @@ import {
type AssertEip712RequestParameters,
} from '../eip712.js';
import { AccountNotFoundError } from '../errors/account.js';
import type { CustomPaymasterHandler } from '../types/customPaymaster.js';
import { VALID_CHAINS } from '../utils.js';
import { transformHexValues } from '../utils.js';
import { signPrivyTransaction } from './sendPrivyTransaction.js';

export interface CustomPaymasterParameters {
nonce: number;
from: Address;
to: Address;
gas: bigint;
gasPrice: bigint;
gasPerPubdata: bigint;
value: bigint;
data: Hex | undefined;
maxFeePerGas: bigint;
maxPriorityFeePerGas: bigint;
chainId: number;
}

export type CustomPaymasterHandler = (
args: CustomPaymasterParameters,
) => Promise<{
paymaster: Address;
paymasterInput: Hex;
}>;

export async function signTransaction<
chain extends ChainEIP712 | undefined = ChainEIP712 | undefined,
account extends Account | undefined = Account | undefined,
Expand Down
6 changes: 2 additions & 4 deletions packages/agw-client/src/actions/signTransactionForSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ import {
getPeriodIdsForTransaction,
type SessionConfig,
} from '../sessions.js';
import type { CustomPaymasterHandler } from '../types/customPaymaster.js';
import { isSmartAccountDeployed } from '../utils.js';
import {
type CustomPaymasterHandler,
signTransaction,
} from './signTransaction.js';
import { signTransaction } from './signTransaction.js';

export interface SendTransactionForSessionParameters<
chain extends ChainEIP712 | undefined = ChainEIP712 | undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/agw-client/src/actions/signTypedData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import {
getPeriodIdsForTransaction,
type SessionConfig,
} from '../sessions.js';
import type { CustomPaymasterHandler } from '../types/customPaymaster.js';
import { isEip712TypedData, transformEip712TypedData } from '../utils.js';
import { sendPrivySignTypedData } from './sendPrivyTransaction.js';
import {
type CustomPaymasterHandler,
signEip712TransactionInternal,
signTransaction,
} from './signTransaction.js';
Expand Down
3 changes: 3 additions & 0 deletions packages/agw-client/src/actions/writeContractForSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { type ChainEIP712 } from 'viem/zksync';

import { AccountNotFoundError } from '../errors/account.js';
import type { SessionConfig } from '../sessions.js';
import type { CustomPaymasterHandler } from '../types/customPaymaster.js';
import { sendTransactionForSession } from './sendTransactionForSession.js';

export async function writeContractForSession<
Expand All @@ -44,6 +45,7 @@ export async function writeContractForSession<
chainOverride
>,
session: SessionConfig,
customPaymasterHandler: CustomPaymasterHandler | undefined = undefined,
): Promise<WriteContractReturnType> {
const {
abi,
Expand Down Expand Up @@ -79,6 +81,7 @@ export async function writeContractForSession<
...request,
},
session,
customPaymasterHandler,
);
} catch (error) {
throw getContractError(error as BaseError, {
Expand Down
2 changes: 1 addition & 1 deletion packages/agw-client/src/sessionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { toAccount } from 'viem/accounts';
import type { ChainEIP712 } from 'viem/chains';

import type { AbstractClient } from './abstractClient.js';
import type { CustomPaymasterHandler } from './actions/signTransaction.js';
import type { SessionConfig } from './sessions.js';
import type { CustomPaymasterHandler } from './types/customPaymaster.js';
import {
type SessionClientActions,
sessionWalletActions,
Expand Down
24 changes: 24 additions & 0 deletions packages/agw-client/src/types/customPaymaster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { type Address, type Hex } from 'viem';

export interface CustomPaymasterParameters {
from: Address;
chainId: number;
nonce?: number | undefined;
to?: Address | undefined;
gas?: bigint | undefined;
gasPrice?: bigint | undefined;
gasPerPubdata?: bigint | undefined;
value?: bigint | undefined;
data?: Hex | undefined;
maxFeePerGas?: bigint | undefined;
maxPriorityFeePerGas?: bigint | undefined;
}

export interface PaymasterArgs {
paymaster: Address;
paymasterInput: Hex;
}

export type CustomPaymasterHandler = (
args: CustomPaymasterParameters,
) => Promise<PaymasterArgs>;
15 changes: 10 additions & 5 deletions packages/agw-client/src/walletActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@ import {
} from './actions/sendTransaction.js';
import { sendTransactionForSession } from './actions/sendTransactionForSession.js';
import { signMessage } from './actions/signMessage.js';
import {
type CustomPaymasterHandler,
signTransaction,
} from './actions/signTransaction.js';
import { signTransaction } from './actions/signTransaction.js';
import { signTransactionForSession } from './actions/signTransactionForSession.js';
import {
signTypedData,
Expand All @@ -82,6 +79,7 @@ import { writeContractForSession } from './actions/writeContractForSession.js';
import { EOA_VALIDATOR_ADDRESS } from './constants.js';
import { type SessionClient, toSessionClient } from './sessionClient.js';
import type { SessionConfig } from './sessions.js';
import type { CustomPaymasterHandler } from './types/customPaymaster.js';
import type { SendTransactionBatchParameters } from './types/sendTransactionBatch.js';

export type AbstractWalletActions<
Expand Down Expand Up @@ -188,6 +186,7 @@ export function sessionWalletActions(
publicClient,
args,
session,
paymasterHandler,
),
writeContract: (args) =>
writeContractForSession(
Expand All @@ -196,6 +195,7 @@ export function sessionWalletActions(
publicClient,
args,
session,
paymasterHandler,
),
signTransaction: (args) =>
signTransactionForSession(
Expand Down Expand Up @@ -224,6 +224,7 @@ export function globalWalletActions<
signerClient: WalletClient<Transport, ChainEIP712, Account>,
publicClient: PublicClient<Transport, ChainEIP712>,
isPrivyCrossApp = false,
customPaymasterHandler?: CustomPaymasterHandler,
) {
return (
client: Client<Transport, ChainEIP712, Account>,
Expand All @@ -250,6 +251,7 @@ export function globalWalletActions<
publicClient,
args as any,
isPrivyCrossApp,
customPaymasterHandler,
),
sendTransactionBatch: (args) =>
sendTransactionBatch(
Expand All @@ -258,6 +260,7 @@ export function globalWalletActions<
publicClient,
args,
isPrivyCrossApp,
customPaymasterHandler,
),
signMessage: (args: Omit<SignMessageParameters, 'account'>) =>
signMessage(client, signerClient, args, isPrivyCrossApp),
Expand All @@ -269,7 +272,7 @@ export function globalWalletActions<
EOA_VALIDATOR_ADDRESS,
false,
{},
undefined,
customPaymasterHandler,
isPrivyCrossApp,
),
signTypedData: (
Expand All @@ -289,6 +292,7 @@ export function globalWalletActions<
publicClient,
args,
isPrivyCrossApp,
customPaymasterHandler,
),
}),
signerClient,
Expand All @@ -307,6 +311,7 @@ export function globalWalletActions<
client: client as AbstractClient,
signer,
session: session,
paymasterHandler: customPaymasterHandler,
}),
});
}
Expand Down
Loading

0 comments on commit 0178623

Please sign in to comment.