Skip to content

Commit 6c3e343

Browse files
author
Alex Sedighi
authored
feat(clk-gateway): add paymaster option (#72)
2 parents c180677 + 1ae4b75 commit 6c3e343

10 files changed

Lines changed: 657 additions & 280 deletions

File tree

.cspell.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
"CCIP",
5151
"nameservice",
5252
"firebaseapp",
53-
"permissioned"
53+
"permissioned",
54+
"Tokendecimals",
55+
"zyfi"
5456
]
5557
}

clk-gateway/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"firebase-admin": "^13.0.1",
1515
"typescript": "^5.7.2",
1616
"validator": "^13.12.0",
17-
"zksync-ethers": "^6.15.1"
17+
"zksync-ethers": "6.10.0"
1818
},
1919
"devDependencies": {
2020
"@types/cors": "^2.8.17",

clk-gateway/src/helpers.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { toUtf8Bytes, ErrorDescription } from "ethers";
2-
import { CommitBatchInfo, StoredBatchInfo as BatchInfo } from "./types";
2+
import {
3+
CommitBatchInfo,
4+
StoredBatchInfo as BatchInfo,
5+
ZyfiSponsoredRequest,
6+
ZyfiSponsoredResponse,
7+
} from "./types";
38
import {
49
diamondAddress,
510
diamondContract,
611
l1Provider,
712
l2Provider,
813
} from "./setup";
914
import { ZKSYNC_DIAMOND_INTERFACE } from "./interfaces";
15+
import { zyfiSponsoredUrl } from "./setup";
1016

1117
export function toLengthPrefixedBytes(
1218
sub: string,
@@ -162,3 +168,24 @@ export async function getBatchInfo(batchNumber: number): Promise<BatchInfo> {
162168
};
163169
return storedBatchInfo;
164170
}
171+
172+
export async function fetchZyfiSponsored(
173+
request: ZyfiSponsoredRequest,
174+
): Promise<ZyfiSponsoredResponse> {
175+
console.log(`zyfiSponsoredUrl: ${zyfiSponsoredUrl}`);
176+
const response = await fetch(zyfiSponsoredUrl!, {
177+
method: "POST",
178+
headers: {
179+
"Content-Type": "application/json",
180+
"X-API-KEY": process.env.ZYFI_API_KEY!,
181+
},
182+
body: JSON.stringify(request),
183+
});
184+
185+
if (!response.ok) {
186+
throw new Error(`Failed to fetch zyfi sponsored`);
187+
}
188+
const sponsoredResponse = (await response.json()) as ZyfiSponsoredResponse;
189+
190+
return sponsoredResponse;
191+
}

clk-gateway/src/index.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import {
1111
ZeroAddress,
1212
isAddress,
1313
} from "ethers";
14-
import { StorageProof } from "./types";
14+
import { StorageProof, ZyfiSponsoredRequest } from "./types";
1515
import {
1616
CLICK_RESOLVER_INTERFACE,
1717
STORAGE_PROOF_TYPE,
1818
CLICK_RESOLVER_ADDRESS_SELECTOR,
19+
CLICK_NAME_SERVICE_INTERFACE,
1920
} from "./interfaces";
2021
import {
2122
toLengthPrefixedBytes,
@@ -26,14 +27,17 @@ import admin from "firebase-admin";
2627
import {
2728
port,
2829
l2Provider,
30+
l2Wallet,
2931
clickResolverContract,
3032
clickNameServiceAddress,
3133
clickNameServiceContract,
3234
batchQueryOffset,
3335
cnsDomain,
3436
cnsTld,
37+
zyfiRequestTemplate,
38+
zyfiSponsoredUrl,
3539
} from "./setup";
36-
import { getBatchInfo } from "./helpers";
40+
import { getBatchInfo, fetchZyfiSponsored } from "./helpers";
3741
import reservedHashes from "./reservedHashes";
3842

3943
const app = express();
@@ -367,9 +371,31 @@ app.post(
367371
}
368372
const owner = getAddress(data.owner);
369373

370-
await admin.auth().revokeRefreshTokens(decodedToken.uid);
374+
let response;
375+
if (zyfiSponsoredUrl) {
376+
const encodedRegister = CLICK_NAME_SERVICE_INTERFACE.encodeFunctionData(
377+
"register",
378+
[owner, sub],
379+
);
380+
const zyfiRequest: ZyfiSponsoredRequest = {
381+
...zyfiRequestTemplate,
382+
txData: {
383+
...zyfiRequestTemplate.txData,
384+
data: encodedRegister,
385+
},
386+
};
387+
const zyfiResponse = await fetchZyfiSponsored(zyfiRequest);
388+
console.log(`ZyFi response: ${JSON.stringify(zyfiResponse)}`);
389+
390+
await admin.auth().revokeRefreshTokens(decodedToken.uid);
391+
392+
response = await l2Wallet.sendTransaction(zyfiResponse.txData);
393+
} else {
394+
await admin.auth().revokeRefreshTokens(decodedToken.uid);
395+
396+
response = await clickNameServiceContract.register(owner, sub);
397+
}
371398

372-
const response = await clickNameServiceContract.register(owner, sub);
373399
const receipt = await response.wait();
374400
if (receipt.status !== 1) {
375401
throw new Error("Transaction failed");

clk-gateway/src/setup.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Provider as L2Provider, Wallet } from "zksync-ethers";
2-
import { Contract, JsonRpcProvider as L1Provider } from "ethers";
2+
import { Contract, JsonRpcProvider as L1Provider, parseEther } from "ethers";
33
import {
44
ZKSYNC_DIAMOND_INTERFACE,
55
CLICK_NAME_SERVICE_INTERFACE,
66
CLICK_RESOLVER_INTERFACE,
77
} from "./interfaces";
8+
import { ZyfiSponsoredRequest } from "./types";
89
import admin from "firebase-admin";
910
import { initializeApp } from "firebase-admin/app";
1011
import dotenv from "dotenv";
@@ -42,11 +43,31 @@ const firebaseApp = initializeApp({
4243
});
4344
const cnsDomain = process.env.CNS_DOMAIN!;
4445
const cnsTld = process.env.CNS_TLD!;
46+
const zyfiSponsoredUrl = process.env.ZYFI_BASE_URL
47+
? new URL(process.env.ZYFI_SPONSORED!, process.env.ZYFI_BASE_URL)
48+
: null;
49+
50+
const zyfiRequestTemplate: ZyfiSponsoredRequest = {
51+
chainId: Number(process.env.L2_CHAIN_ID!),
52+
feeTokenAddress: process.env.FEE_TOKEN_ADDR!,
53+
gasLimit: process.env.GAS_LIMIT!,
54+
isTestnet: process.env.L2_CHAIN_ID === "300",
55+
checkNft: false,
56+
txData: {
57+
from: l2Wallet.address,
58+
to: clickNameServiceAddress,
59+
data: "0x0",
60+
value: "0",
61+
},
62+
sponsorshipRatio: 100,
63+
replayLimit: 5,
64+
};
4565

4666
export {
4767
port,
4868
l1Provider,
4969
l2Provider,
70+
l2Wallet,
5071
diamondAddress,
5172
diamondContract,
5273
clickResolverContract,
@@ -55,4 +76,6 @@ export {
5576
batchQueryOffset,
5677
cnsDomain,
5778
cnsTld,
79+
zyfiSponsoredUrl,
80+
zyfiRequestTemplate,
5881
};

clk-gateway/src/testPaymaster.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Env example: to be set in the .env file
2+
/*
3+
REGISTRAR_PRIVATE_KEY=your-eth-private-key
4+
L2_RPC_URL=https://sepolia.era.zksync.dev
5+
PAYMASTER_TEST_ADDR=0x76f03aD8AA376385e45221d45371DeB51D597c43
6+
L2_CHAIN_ID=300
7+
ZYFI_BASE_URL=https://api.zyfi.org/api/
8+
ZYFI_SPONSORED=erc20_sponsored_paymaster/v1
9+
ZYFI_API_KEY=your-zyfi-api-key
10+
FEE_TOKEN_ADDR=0xb4B74C2BfeA877672B938E408Bae8894918fE41C
11+
GAS_LIMIT=1000000
12+
*/
13+
// Usage example: source .env && npx ts-node src/testPaymaster.ts 0x2E7F3926Ae74FDCDcAde2c2AB50990C5daFD42bD alex
14+
15+
import { Interface, getAddress, Contract } from "ethers";
16+
import { Provider as L2Provider, Wallet } from "zksync-ethers";
17+
18+
// L2 Contract
19+
export const PAYMASTER_TEST_INTERFACE = new Interface([
20+
"function register(address to, string memory name)",
21+
]);
22+
23+
import dotenv from "dotenv";
24+
dotenv.config();
25+
26+
const to: string = getAddress(process.argv[2]);
27+
const name: string = process.argv[3];
28+
const privateKey = process.env.REGISTRAR_PRIVATE_KEY!;
29+
const l2Provider = new L2Provider(process.env.L2_RPC_URL!);
30+
const l2Wallet = new Wallet(privateKey, l2Provider);
31+
const paymasterTestAddress = getAddress(process.env.PAYMASTER_TEST_ADDR!);
32+
33+
const paymasterTestContract = new Contract(
34+
paymasterTestAddress,
35+
PAYMASTER_TEST_INTERFACE,
36+
l2Wallet,
37+
);
38+
const zyfiSponsoredUrl = process.env.ZYFI_BASE_URL
39+
? new URL(process.env.ZYFI_SPONSORED!, process.env.ZYFI_BASE_URL)
40+
: null;
41+
42+
type ZyfiSponsoredRequest = {
43+
chainId: number;
44+
feeTokenAddress: string;
45+
gasLimit: string;
46+
isTestnet: boolean;
47+
checkNft: boolean;
48+
txData: {
49+
from: string;
50+
to: string;
51+
value: string;
52+
data: string;
53+
};
54+
sponsorshipRatio: number;
55+
replayLimit: number;
56+
};
57+
58+
interface ZyfiSponsoredResponse {
59+
txData: {
60+
chainId: number;
61+
from: string;
62+
to: string;
63+
value: string;
64+
data: string;
65+
customData: {
66+
paymasterParams: {
67+
paymaster: string;
68+
paymasterInput: string;
69+
};
70+
gasPerPubdata: number;
71+
};
72+
maxFeePerGas: string;
73+
gasLimit: number;
74+
};
75+
gasLimit: string;
76+
gasPrice: string;
77+
tokenAddress: string;
78+
tokenPrice: string;
79+
feeTokenAmount: string;
80+
feeTokenDecimals: string;
81+
feeUSD: string;
82+
markup: string;
83+
expirationTime: string;
84+
expiresIn: string;
85+
maxNonce: string;
86+
protocolAddress: string;
87+
sponsorshipRatio: string;
88+
warnings: string[];
89+
}
90+
91+
async function fetchZyfiSponsored(
92+
request: ZyfiSponsoredRequest,
93+
): Promise<ZyfiSponsoredResponse> {
94+
console.log(`zyfiSponsoredUrl: ${zyfiSponsoredUrl}`);
95+
const response = await fetch(zyfiSponsoredUrl!, {
96+
method: "POST",
97+
headers: {
98+
"Content-Type": "application/json",
99+
"X-API-KEY": process.env.ZYFI_API_KEY!,
100+
},
101+
body: JSON.stringify(request),
102+
});
103+
104+
if (!response.ok) {
105+
throw new Error(`Failed to fetch zyfi sponsored`);
106+
}
107+
const sponsoredResponse = (await response.json()) as ZyfiSponsoredResponse;
108+
109+
return sponsoredResponse;
110+
}
111+
112+
const zyfiRequestTemplate: ZyfiSponsoredRequest = {
113+
chainId: Number(process.env.L2_CHAIN_ID!),
114+
feeTokenAddress: process.env.FEE_TOKEN_ADDR!,
115+
gasLimit: process.env.GAS_LIMIT!,
116+
isTestnet: process.env.L2_CHAIN_ID === "300",
117+
checkNft: false,
118+
txData: {
119+
from: l2Wallet.address,
120+
to: paymasterTestAddress,
121+
data: "0x0",
122+
value: "0",
123+
},
124+
sponsorshipRatio: 100,
125+
replayLimit: 5,
126+
};
127+
128+
async function main(to: string, name: string): Promise<void> {
129+
let response;
130+
if (zyfiSponsoredUrl) {
131+
const encodedRegister = PAYMASTER_TEST_INTERFACE.encodeFunctionData(
132+
"register",
133+
[to, name],
134+
);
135+
const zyfiRequest: ZyfiSponsoredRequest = {
136+
...zyfiRequestTemplate,
137+
txData: {
138+
...zyfiRequestTemplate.txData,
139+
data: encodedRegister,
140+
},
141+
};
142+
const zyfiResponse = await fetchZyfiSponsored(zyfiRequest);
143+
console.log(`ZyFi response: ${JSON.stringify(zyfiResponse)}`);
144+
145+
response = await l2Wallet.sendTransaction(zyfiResponse.txData);
146+
} else {
147+
response = await paymasterTestContract.register(to, name);
148+
}
149+
150+
const receipt = await response.wait();
151+
if (receipt.status !== 1) {
152+
throw new Error("Transaction failed");
153+
}
154+
console.log(`Transaction hash: ${receipt.hash}`);
155+
}
156+
157+
main(to, name);

clk-gateway/src/types.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,52 @@ export type StorageProofBatch = {
4444
export type StorageProof = RpcProof & {
4545
metadata: BatchMetadata;
4646
};
47+
48+
export type ZyfiSponsoredRequest = {
49+
chainId: number;
50+
feeTokenAddress: string;
51+
gasLimit: string;
52+
isTestnet: boolean;
53+
checkNft: boolean;
54+
txData: {
55+
from: string;
56+
to: string;
57+
value: string;
58+
data: string;
59+
};
60+
sponsorshipRatio: number;
61+
replayLimit: number;
62+
};
63+
64+
export interface ZyfiSponsoredResponse {
65+
txData: {
66+
chainId: number;
67+
from: string;
68+
to: string;
69+
value: string;
70+
data: string;
71+
customData: {
72+
paymasterParams: {
73+
paymaster: string;
74+
paymasterInput: string;
75+
};
76+
gasPerPubdata: number;
77+
};
78+
maxFeePerGas: string;
79+
gasLimit: number;
80+
};
81+
gasLimit: string;
82+
gasPrice: string;
83+
tokenAddress: string;
84+
tokenPrice: string;
85+
feeTokenAmount: string;
86+
feeTokendecimals: string;
87+
feeUSD: string;
88+
markup: string;
89+
expirationTime: string;
90+
expiresIn: string;
91+
maxNonce: string;
92+
protocolAddress: string;
93+
sponsorshipRatio: string;
94+
warnings: string[];
95+
}

0 commit comments

Comments
 (0)