zksync-sso
Version:
ZKsync Smart Sign On SDK
109 lines (100 loc) • 3.53 kB
text/typescript
import type { Address, Hex } from "viem";
import type { CustomPaymasterHandler, CustomPaymasterHandlerResponse, CustomPaymasterParameters } from "../index.js";
const ZYFI_ENDPOINT = "https://api.zyfi.org/api/";
const ERC20_PAYMASTER = `${ZYFI_ENDPOINT}erc20_paymaster/v1`;
const SPONSORED_PAYMASTER = `${ZYFI_ENDPOINT}erc20_sponsored_paymaster/v1`;
export interface ZyfiPaymasterParams {
/** Your API key. Get it from Dashboard on https://www.zyfi.org/ */
apiKey: string;
/** The address of the token to be used for fee payment */
feeTokenAddress?: Address;
/** Whether to check for NFT ownership for fee payment */
checkNFT?: boolean;
/** Whether the transaction is on a testnet */
isTestnet?: boolean;
/** The ratio of fees to be sponsored by the paymaster (0-100) */
sponsorshipRatio?: number;
/** determines the user nonces interval for which the response will be valid (current nonce + replayLimit). */
replayLimit?: number;
}
export function createZyfiPaymaster(params: ZyfiPaymasterParams): CustomPaymasterHandler {
if (!params.apiKey) throw new Error("ZyFi: Provide API KEY");
if (
params.sponsorshipRatio !== undefined
&& (params.sponsorshipRatio > 100
|| params.sponsorshipRatio < 0)
) throw new Error("ZyFi: Sponsorship ratio must be between 0-100");
return async function zyfiPaymaster(args: CustomPaymasterParameters): CustomPaymasterHandlerResponse {
const url = params.sponsorshipRatio ? SPONSORED_PAYMASTER : ERC20_PAYMASTER;
const payload = {
replayLimit: params.replayLimit,
sponsorshipRatio: params.sponsorshipRatio,
chainId: args.chainId,
checkNFT: params.checkNFT,
feeTokenAddress: params.feeTokenAddress,
isTestnet: params.isTestnet,
gasLimit: args.gas || undefined,
txData: {
from: args.from,
to: args.to,
data: args.data ?? "0x",
value: args.value,
},
};
const response: ApiResponse = await fetch(
url,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": params.apiKey,
},
body: stringify(payload),
},
).then((response) => {
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
});
const { paymasterParams, gasPerPubdata } = response.txData.customData;
return {
paymaster: paymasterParams.paymaster,
paymasterInput: paymasterParams.paymasterInput,
maxFeePerGas: BigInt(response.txData.maxFeePerGas),
gas: BigInt(response.txData.gasLimit),
maxPriorityFeePerGas: BigInt(response.txData.maxPriorityFeePerGas ?? 0),
gasPerPubdata: gasPerPubdata ? BigInt(gasPerPubdata) : undefined,
};
};
}
type ApiResponse = {
txData: {
customData: {
paymasterParams: {
paymaster: Address;
paymasterInput: Hex;
};
gasPerPubdata?: number;
};
maxFeePerGas: string;
maxPriorityFeePerGas?: string;
gasLimit: number;
};
gasLimit: string;
gasPrice: string;
tokenAddress: Address;
tokenPrice: string;
feeTokenAmount: string;
feeTokendecimals: string;
feeUSD: string;
expirationTime: string;
expiresIn: string;
paymasterAddress: Address;
messageHash: string;
maxNonce?: string;
protocolAddress?: Address;
sponsorshipRatio?: string;
warnings?: string[];
};
function stringify(payload: any) {
return JSON.stringify(payload, (_, v) => typeof v === "bigint" ? v.toString() : v);
}