Skip to content

Commit d1dedc8

Browse files
authored
feat: custom paymaster handler & zyfi integration (#51)
* feat: custom paymaster handler & zyfi integration * feat: export paymaster package * fix: bigint serializer * feat: passkey paymaster handler & general paymaster handler & fix types * fix: import path * chore: add new words to cspell * chore: fix zyfi input data
1 parent ba0fcdf commit d1dedc8

File tree

14 files changed

+330
-16
lines changed

14 files changed

+330
-16
lines changed

cspell-config/cspell-misc.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ testid
1111
vueuse
1212
dockerized
1313
ethereum
14+
foundryup
1415

1516
// examples/bank-demo
1617
ctap
@@ -28,3 +29,11 @@ usdc
2829
Fren
2930
fren
3031
nfts
32+
33+
// examples/demo-app
34+
zkout
35+
cancun
36+
EVMLA
37+
38+
// paymasters
39+
zyfi

packages/sdk/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@
104104
"import": "./dist/_esm/abi/index.js",
105105
"require": "./dist/_cjs/abi/index.js"
106106
},
107+
"./paymaster": {
108+
"types": "./dist/_types/paymaster/index.d.ts",
109+
"import": "./dist/_esm/paymaster/index.js",
110+
"require": "./dist/_cjs/paymaster/index.js"
111+
},
107112
"./package.json": "./package.json"
108113
},
109114
"typesVersions": {

packages/sdk/src/client-auth-server/Signer.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { type Address, type Chain, createWalletClient, custom, type Hash, http, type RpcSchema as RpcSchemaGeneric, type SendTransactionParameters, type Transport, type WalletClient } from "viem";
2+
import type { TransactionRequestEIP712 } from "viem/chains";
23

34
import { createZksyncSessionClient, type ZksyncSsoSessionClient } from "../client/index.js";
45
import type { Communicator } from "../communicator/index.js";
6+
import { type CustomPaymasterHandler, getTransactionWithPaymasterData } from "../paymaster/index.js";
57
import { StorageItem } from "../utils/storage.js";
68
import type { AppMetadata, RequestArguments } from "./interface.js";
79
import type { AuthServerRpcSchema, ExtractParams, ExtractReturnType, Method, RPCRequestMessage, RPCResponseMessage, RpcSchema } from "./rpc.js";
@@ -38,6 +40,7 @@ type SignerConstructorParams = {
3840
chains: readonly Chain[];
3941
transports?: Record<number, Transport>;
4042
session?: () => SessionPreferences | Promise<SessionPreferences>;
43+
paymasterHandler?: CustomPaymasterHandler;
4144
};
4245

4346
type ChainsInfo = ExtractReturnType<"eth_requestAccounts", AuthServerRpcSchema>["chainsInfo"];
@@ -49,12 +52,13 @@ export class Signer implements SignerInterface {
4952
private readonly chains: readonly Chain[];
5053
private readonly transports: Record<number, Transport> = {};
5154
private readonly sessionParameters?: () => (SessionPreferences | Promise<SessionPreferences>);
55+
private readonly paymasterHandler?: CustomPaymasterHandler;
5256

5357
private _account: StorageItem<Account | null>;
5458
private _chainsInfo = new StorageItem<ChainsInfo>(StorageItem.scopedStorageKey("chainsInfo"), []);
5559
private client: { instance: ZksyncSsoSessionClient; type: "session" } | { instance: WalletClient; type: "auth-server" } | undefined;
5660

57-
constructor({ metadata, communicator, updateListener, session, chains, transports }: SignerConstructorParams) {
61+
constructor({ metadata, communicator, updateListener, session, chains, transports, paymasterHandler }: SignerConstructorParams) {
5862
if (!chains.length) throw new Error("At least one chain must be included in the config");
5963

6064
this.getMetadata = metadata;
@@ -63,6 +67,7 @@ export class Signer implements SignerInterface {
6367
this.sessionParameters = session;
6468
this.chains = chains;
6569
this.transports = transports || {};
70+
this.paymasterHandler = paymasterHandler;
6671

6772
this._account = new StorageItem<Account | null>(StorageItem.scopedStorageKey("account"), null, {
6873
onChange: (newValue) => {
@@ -136,6 +141,7 @@ export class Signer implements SignerInterface {
136141
contracts: chainInfo.contracts,
137142
chain,
138143
transport: this.transports[chain.id] || http(),
144+
paymasterHandler: this.paymasterHandler,
139145
}),
140146
};
141147
} else {
@@ -266,6 +272,23 @@ export class Signer implements SignerInterface {
266272
// Open popup immediately to make sure popup won't be blocked by Safari
267273
await this.communicator.ready();
268274

275+
if (request.method === "eth_sendTransaction") {
276+
const params = request.params![0] as TransactionRequestEIP712;
277+
if (params) {
278+
/* eslint-disable @typescript-eslint/no-unused-vars */
279+
const { chainId: _, ...transaction } = await getTransactionWithPaymasterData(
280+
this.chain.id,
281+
params.from,
282+
params,
283+
this.paymasterHandler,
284+
);
285+
request = {
286+
method: request.method,
287+
params: [transaction] as ExtractParams<TMethod, TSchema>,
288+
};
289+
}
290+
}
291+
269292
const message = this.createRequestMessage<TMethod, TSchema>({
270293
action: request,
271294
chainId: this.chain.id,

packages/sdk/src/client-auth-server/WalletProvider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { toHex } from "viem";
44

55
import { PopupCommunicator } from "../communicator/PopupCommunicator.js";
66
import { serializeError, standardErrors } from "../errors/index.js";
7+
import type { CustomPaymasterHandler } from "../paymaster/index.js";
78
import { getFavicon, getWebsiteName } from "../utils/helpers.js";
89
import type {
910
AppMetadata,
@@ -22,13 +23,14 @@ export type WalletProviderConstructorOptions = {
2223
transports?: Record<number, Transport>;
2324
session?: SessionPreferences | (() => SessionPreferences | Promise<SessionPreferences>);
2425
authServerUrl?: string;
26+
paymasterHandler?: CustomPaymasterHandler;
2527
};
2628

2729
export class WalletProvider extends EventEmitter implements ProviderInterface {
2830
readonly isZksyncSso = true;
2931
private signer: Signer;
3032

31-
constructor({ metadata, chains, transports, session, authServerUrl }: WalletProviderConstructorOptions) {
33+
constructor({ metadata, chains, transports, session, authServerUrl, paymasterHandler }: WalletProviderConstructorOptions) {
3234
super();
3335
const communicator = new PopupCommunicator(authServerUrl || DEFAULT_AUTH_SERVER_URL);
3436
this.signer = new Signer({
@@ -41,6 +43,7 @@ export class WalletProvider extends EventEmitter implements ProviderInterface {
4143
chains,
4244
transports,
4345
session: typeof session === "object" ? () => session : session,
46+
paymasterHandler,
4447
});
4548
}
4649

packages/sdk/src/client/passkey/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type Account, type Address, type Chain, type Client, createClient, getAddress, type Prettify, type PublicActions, publicActions, type PublicRpcSchema, type RpcSchema, type Transport, type WalletActions, walletActions, type WalletClientConfig, type WalletRpcSchema } from "viem";
22
import { eip712WalletActions } from "viem/zksync";
33

4+
import type { CustomPaymasterHandler } from "../../paymaster/index.js";
45
import { passkeyHashSignatureResponseFormat } from "../../utils/passkey.js";
56
import { toPasskeyAccount } from "./account.js";
67
import { requestPasskeyAuthentication } from "./actions/passkey.js";
@@ -63,6 +64,7 @@ type ZksyncSsoPasskeyData = {
6364
userName: string; // Basically unique user id (which is called `userName` in webauthn)
6465
userDisplayName: string; // Also option required for webauthn
6566
contracts: PasskeyRequiredContracts;
67+
paymasterHandler?: CustomPaymasterHandler;
6668
};
6769

6870
export type ClientWithZksyncSsoPasskeyData<

packages/sdk/src/client/passkey/decorators/wallet.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { type Account, bytesToHex, type Chain, formatTransaction, type Transport, type WalletActions } from "viem";
1+
import { type Account, bytesToHex, type Chain, type ExactPartial, formatTransaction, type RpcTransaction, type Transport, type WalletActions } from "viem";
22
import { deployContract, getAddresses, getChainId, sendRawTransaction, signMessage, signTypedData, writeContract } from "viem/actions";
3-
import { signTransaction, type ZksyncEip712Meta } from "viem/zksync";
3+
import { signTransaction, type TransactionRequestEIP712, type ZksyncEip712Meta } from "viem/zksync";
44

5+
import { getTransactionWithPaymasterData } from "../../../paymaster/index.js";
56
import { sendEip712Transaction } from "../../session/actions/sendEip712Transaction.js";
67
import type { ClientWithZksyncSsoPasskeyData } from "../client.js";
78

@@ -33,19 +34,39 @@ export function zksyncSsoPasskeyWalletActions<
3334
delete unformattedTx.eip712Meta;
3435
}
3536

37+
/* eslint-disable @typescript-eslint/no-unused-vars */
38+
const { chainId: _, ...unformattedTxWithPaymaster } = await getTransactionWithPaymasterData(
39+
client.chain.id,
40+
client.account.address,
41+
unformattedTx,
42+
client.paymasterHandler,
43+
);
44+
3645
const formatters = client.chain?.formatters;
3746
const format = formatters?.transaction?.format || formatTransaction;
3847

3948
const tx = {
40-
...format(unformattedTx),
49+
...format(unformattedTxWithPaymaster as ExactPartial<RpcTransaction>),
4150
type: "eip712",
4251
};
4352

4453
return await sendEip712Transaction(client, tx);
4554
},
4655
signMessage: (args) => signMessage(client, args),
47-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
48-
signTransaction: (args) => signTransaction(client, args as any),
56+
57+
signTransaction: async (args) => {
58+
/* eslint-disable @typescript-eslint/no-unused-vars */
59+
const { chainId: _, ...unformattedTxWithPaymaster } = await getTransactionWithPaymasterData(
60+
client.chain.id,
61+
client.account.address,
62+
args as TransactionRequestEIP712,
63+
client.paymasterHandler,
64+
);
65+
return signTransaction(client, {
66+
...args,
67+
unformattedTxWithPaymaster,
68+
} as any);
69+
},
4970
signTypedData: (args) => signTypedData(client, args),
5071
writeContract: (args) => writeContract(client, args),
5172
};

packages/sdk/src/client/session/actions/session.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { waitForTransactionReceipt } from "viem/actions";
33
import { getGeneralPaymasterInput, sendTransaction } from "viem/zksync";
44

55
import { SessionKeyModuleAbi } from "../../../abi/SessionKeyModule.js";
6+
import { type CustomPaymasterHandler, getTransactionWithPaymasterData } from "../../../paymaster/index.js";
67
import { noThrow } from "../../../utils/helpers.js";
78
import type { SessionConfig } from "../../../utils/session.js";
89

@@ -16,6 +17,7 @@ export type CreateSessionArgs = {
1617
paymasterInput?: Hex;
1718
};
1819
onTransactionSent?: (hash: Hash) => void;
20+
paymasterHandler?: CustomPaymasterHandler;
1921
};
2022
export type CreateSessionReturnType = {
2123
transactionReceipt: TransactionReceipt;
@@ -41,7 +43,14 @@ export const createSession = async <
4143
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4244
} as any;
4345

44-
const transactionHash = await sendTransaction(client, sendTransactionArgs);
46+
const transactionWithPaymasterData: any = await getTransactionWithPaymasterData(
47+
client.chain.id,
48+
client.account.address,
49+
sendTransactionArgs,
50+
args.paymasterHandler,
51+
);
52+
53+
const transactionHash = await sendTransaction(client, transactionWithPaymasterData);
4554
if (args.onTransactionSent) {
4655
noThrow(() => args.onTransactionSent?.(transactionHash));
4756
}
@@ -64,6 +73,7 @@ export type RevokeSessionArgs = {
6473
paymasterInput?: Hex;
6574
};
6675
onTransactionSent?: (hash: Hash) => void;
76+
paymasterHandler?: CustomPaymasterHandler;
6777
};
6878
export type RevokeSessionReturnType = {
6979
transactionReceipt: TransactionReceipt;
@@ -89,7 +99,15 @@ export const revokeSession = async <
8999
// eslint-disable-next-line @typescript-eslint/no-explicit-any
90100
} as any;
91101

92-
const transactionHash = await sendTransaction(client, sendTransactionArgs);
102+
const transactionWithPaymasterData: any = await getTransactionWithPaymasterData(
103+
client.chain.id,
104+
client.account.address,
105+
sendTransactionArgs,
106+
args.paymasterHandler,
107+
);
108+
109+
const transactionHash = await sendTransaction(client, transactionWithPaymasterData);
110+
93111
if (args.onTransactionSent) {
94112
noThrow(() => args.onTransactionSent?.(transactionHash));
95113
}

packages/sdk/src/client/session/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { type Account, type Address, type Chain, type Client, createClient, crea
22
import { privateKeyToAccount } from "viem/accounts";
33
import { zksyncInMemoryNode } from "viem/chains";
44

5+
import type { CustomPaymasterHandler } from "../../paymaster/index.js";
56
import { encodeSessionTx } from "../../utils/encoding.js";
67
import type { SessionConfig } from "../../utils/session.js";
78
import { toSessionAccount } from "./account.js";
@@ -88,6 +89,7 @@ export function createZksyncSessionClient<
8889
sessionKey: parameters.sessionKey,
8990
sessionConfig: parameters.sessionConfig,
9091
contracts: parameters.contracts,
92+
paymasterHandler: parameters.paymasterHandler,
9193
}))
9294
.extend(publicActions)
9395
.extend(publicActionsRewrite)
@@ -102,6 +104,7 @@ type ZksyncSsoSessionData = {
102104
sessionKey: Hash;
103105
sessionConfig: SessionConfig;
104106
contracts: SessionRequiredContracts;
107+
paymasterHandler?: CustomPaymasterHandler;
105108
};
106109

107110
export type ClientWithZksyncSsoSessionData<
@@ -139,4 +142,5 @@ export interface ZksyncSsoSessionClientConfig<
139142
contracts: SessionRequiredContracts;
140143
key?: string;
141144
name?: string;
145+
paymasterHandler?: CustomPaymasterHandler;
142146
}

packages/sdk/src/client/session/decorators/wallet.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { type Account, bytesToHex, type Chain, formatTransaction, type Transport, type WalletActions } from "viem";
2-
import { deployContract, getAddresses, getChainId, sendRawTransaction, signMessage, signTypedData, writeContract } from "viem/actions";
3-
import { signTransaction, type ZksyncEip712Meta } from "viem/zksync";
4-
1+
import {
2+
type Account, bytesToHex,
3+
type Chain, type ExactPartial, formatTransaction, type RpcTransaction,
4+
type Transport, type WalletActions } from "viem";
5+
import {
6+
deployContract, getAddresses, getChainId, sendRawTransaction,
7+
signMessage, signTypedData, writeContract,
8+
} from "viem/actions";
9+
import { signTransaction, type TransactionRequestEIP712, type ZksyncEip712Meta } from "viem/zksync";
10+
11+
import { getTransactionWithPaymasterData } from "../../../paymaster/index.js";
512
import { sendEip712Transaction } from "../actions/sendEip712Transaction.js";
613
import type { ClientWithZksyncSsoSessionData } from "../client.js";
714

@@ -33,19 +40,38 @@ export function zksyncSsoWalletActions<
3340
delete unformattedTx.eip712Meta;
3441
}
3542

43+
/* eslint-disable @typescript-eslint/no-unused-vars */
44+
const { chainId: _, ...unformattedTxWithPaymaster } = await getTransactionWithPaymasterData(
45+
client.chain.id,
46+
client.account.address,
47+
unformattedTx,
48+
client.paymasterHandler,
49+
);
50+
3651
const formatters = client.chain?.formatters;
3752
const format = formatters?.transaction?.format || formatTransaction;
3853

3954
const tx = {
40-
...format(unformattedTx),
55+
...format(unformattedTxWithPaymaster as ExactPartial<RpcTransaction>),
4156
type: "eip712",
4257
};
4358

4459
return await sendEip712Transaction(client, tx);
4560
},
4661
signMessage: (args) => signMessage(client, args),
47-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
48-
signTransaction: (args) => signTransaction(client, args as any),
62+
63+
signTransaction: async (args) => {
64+
const { chainId: _, ...unformattedTxWithPaymaster } = await getTransactionWithPaymasterData(
65+
client.chain.id,
66+
client.account.address,
67+
args as TransactionRequestEIP712,
68+
client.paymasterHandler,
69+
);
70+
return signTransaction(client, {
71+
...args,
72+
unformattedTxWithPaymaster,
73+
} as any);
74+
},
4975
signTypedData: (args) => signTypedData(client, args),
5076
writeContract: (args) => writeContract(client, args),
5177
};

packages/sdk/src/connector/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import {
1919
import type { ZksyncSsoSessionClient } from "../client/index.js";
2020
import { EthereumProviderError } from "../errors/errors.js";
2121
import { type AppMetadata, type ProviderInterface, type SessionPreferences, WalletProvider } from "../index.js";
22+
import type { CustomPaymasterHandler } from "../paymaster/index.js";
2223
export { callPolicy } from "../client-auth-server/index.js";
2324

2425
export type ZksyncSsoConnectorOptions = {
2526
metadata?: Partial<AppMetadata>;
2627
session?: SessionPreferences | (() => SessionPreferences | Promise<SessionPreferences>);
2728
authServerUrl?: string;
29+
paymasterHandler?: CustomPaymasterHandler;
2830
};
2931

3032
export const zksyncSsoConnector = (parameters: ZksyncSsoConnectorOptions) => {
@@ -140,6 +142,7 @@ export const zksyncSsoConnector = (parameters: ZksyncSsoConnectorOptions) => {
140142
session: parameters.session,
141143
transports: config.transports,
142144
chains: config.chains,
145+
paymasterHandler: parameters.paymasterHandler,
143146
});
144147
}
145148
return walletProvider;

0 commit comments

Comments
 (0)