Skip to content

Commit 794cef2

Browse files
prathmeshkhandelwal10xHarbs
authored andcommitted
feat: zksync structure
1 parent 4be09aa commit 794cef2

3 files changed

Lines changed: 213 additions & 0 deletions

File tree

packages/adapters/rebalance/src/adapters/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { StargateBridgeAdapter } from './stargate';
1414
import { TacInnerBridgeAdapter, TacNetwork } from './tac';
1515
import { PendleBridgeAdapter } from './pendle';
1616
import { CCIPBridgeAdapter } from './ccip';
17+
import { ZKSyncNativeBridgeAdapter } from './zksync/zksync';
1718

1819
export class RebalanceAdapter {
1920
constructor(
@@ -94,6 +95,8 @@ export class RebalanceAdapter {
9495
return new PendleBridgeAdapter(this.config.chains, this.logger);
9596
case SupportedBridge.CCIP:
9697
return new CCIPBridgeAdapter(this.config.chains, this.logger);
98+
case SupportedBridge.Zksync:
99+
return new ZKSyncNativeBridgeAdapter(this.config.chains, this.logger);
97100
default:
98101
throw new Error(`Unsupported adapter type: ${type}`);
99102
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import {
2+
TransactionReceipt,
3+
createPublicClient,
4+
encodeFunctionData,
5+
http,
6+
erc20Abi,
7+
PublicClient,
8+
fallback,
9+
parseEventLogs,
10+
parseAbi,
11+
} from 'viem';
12+
import { BridgeAdapter, MemoizedTransactionRequest, RebalanceTransactionMemo } from '../../types';
13+
import { SupportedBridge, ChainConfiguration, ILogger } from '@mark/core';
14+
import { jsonifyError } from '@mark/logger';
15+
import type { RebalanceRoute } from '@mark/core';
16+
17+
const ZKSYNC_L1_BRIDGE = '0x57891966931eb4bb6fb81430e6ce0a03aabde063';
18+
const ZKSYNC_L2_BRIDGE = '0x11f943b2c77b743AB90f4A0Ae7d5A4e7FCA3E102';
19+
const ETH_TOKEN_L2 = '0x000000000000000000000000000000000000800A';
20+
const WITHDRAWAL_DELAY_HOURS = 24;
21+
22+
const zkSyncL1BridgeAbi = parseAbi([
23+
'function deposit(address _l2Receiver, address _l1Token, uint256 _amount, uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte, address _refundRecipient) payable',
24+
'function finalizeWithdrawal(uint256 _l2BatchNumber, uint256 _l2MessageIndex, uint16 _l2TxNumberInBatch, bytes calldata _message, bytes32[] calldata _merkleProof)',
25+
'event DepositInitiated(bytes32 indexed l2DepositTxHash, address indexed from, address indexed to, address l1Token, uint256 amount)',
26+
]);
27+
28+
const zkSyncL2BridgeAbi = parseAbi([
29+
'function withdraw(address _l1Receiver, address _l2Token, uint256 _amount)',
30+
'event WithdrawalInitiated(address indexed l2Sender, address indexed l1Receiver, address indexed l2Token, uint256 amount)',
31+
]);
32+
33+
34+
export class ZKSyncNativeBridgeAdapter implements BridgeAdapter {
35+
constructor(
36+
protected readonly chains: Record<string, ChainConfiguration>,
37+
protected readonly logger: ILogger,
38+
) {}
39+
40+
type(): SupportedBridge {
41+
return SupportedBridge.Zksync;
42+
}
43+
44+
// https://docs.zksync.io/zk-stack/concepts/fee-mechanism
45+
async getReceivedAmount(amount: string, route: RebalanceRoute): Promise<string> {
46+
try {
47+
return amount;
48+
} catch (error) {
49+
this.handleError(error, 'calculate received amount', { amount, route });
50+
}
51+
}
52+
53+
async send(
54+
sender: string,
55+
recipient: string,
56+
amount: string,
57+
route: RebalanceRoute,
58+
): Promise<MemoizedTransactionRequest[]> {
59+
try {
60+
const isL1ToL2 = route.origin === 1 && route.destination === 324;
61+
const isETH = route.asset.toLowerCase() === '0x0000000000000000000000000000000000000000';
62+
const transactions: MemoizedTransactionRequest[] = [];
63+
64+
if (isL1ToL2) {
65+
if (!isETH) {
66+
const client = await this.getClient(route.origin);
67+
const allowance = await client.readContract({
68+
address: route.asset as `0x${string}`,
69+
abi: erc20Abi,
70+
functionName: 'allowance',
71+
args: [sender as `0x${string}`, ZKSYNC_L1_BRIDGE as `0x${string}`],
72+
});
73+
74+
if (allowance < BigInt(amount)) {
75+
transactions.push({
76+
memo: RebalanceTransactionMemo.Approval,
77+
transaction: {
78+
to: route.asset as `0x${string}`,
79+
data: encodeFunctionData({
80+
abi: erc20Abi,
81+
functionName: 'approve',
82+
args: [ZKSYNC_L1_BRIDGE as `0x${string}`, BigInt(amount)],
83+
}),
84+
value: BigInt(0),
85+
},
86+
});
87+
}
88+
}
89+
90+
transactions.push({
91+
memo: RebalanceTransactionMemo.Rebalance,
92+
transaction: {
93+
to: ZKSYNC_L1_BRIDGE as `0x${string}`,
94+
data: encodeFunctionData({
95+
abi: zkSyncL1BridgeAbi,
96+
functionName: 'deposit',
97+
args: [
98+
recipient as `0x${string}`,
99+
route.asset as `0x${string}`,
100+
BigInt(amount),
101+
BigInt(200000),
102+
BigInt(800),
103+
sender as `0x${string}`,
104+
],
105+
}),
106+
value: isETH ? BigInt(amount) : BigInt(0),
107+
},
108+
});
109+
} else {
110+
transactions.push({
111+
memo: RebalanceTransactionMemo.Rebalance,
112+
transaction: {
113+
to: ZKSYNC_L2_BRIDGE as `0x${string}`,
114+
data: encodeFunctionData({
115+
abi: zkSyncL2BridgeAbi,
116+
functionName: 'withdraw',
117+
args: [
118+
recipient as `0x${string}`,
119+
route.asset === '0x0000000000000000000000000000000000000000'
120+
? (ETH_TOKEN_L2 as `0x${string}`)
121+
: (route.asset as `0x${string}`),
122+
BigInt(amount),
123+
],
124+
}),
125+
value: BigInt(0),
126+
},
127+
});
128+
}
129+
130+
return transactions;
131+
} catch (error) {
132+
this.handleError(error, 'prepare bridge transactions', { sender, recipient, amount, route });
133+
}
134+
}
135+
136+
async readyOnDestination(
137+
amount: string,
138+
route: RebalanceRoute,
139+
originTransaction: TransactionReceipt,
140+
): Promise<boolean> {
141+
try {
142+
const isL1ToL2 = route.origin === 1 && route.destination === 324;
143+
144+
if (isL1ToL2) {
145+
return true;
146+
} else {
147+
this.logger.info('zkSync withdrawal delay check - 24-hour delay required', {
148+
txBlock: Number(originTransaction.blockNumber),
149+
txHash: originTransaction.transactionHash,
150+
requiredDelayHours: WITHDRAWAL_DELAY_HOURS,
151+
});
152+
153+
return true;
154+
}
155+
} catch (error) {
156+
this.handleError(error, 'check destination readiness', { amount, route, originTransaction });
157+
}
158+
}
159+
160+
async destinationCallback(
161+
route: RebalanceRoute,
162+
originTransaction: TransactionReceipt,
163+
): Promise<MemoizedTransactionRequest | void> {
164+
try {
165+
const isL2ToL1 = route.origin === 324 && route.destination === 1;
166+
167+
if (isL2ToL1) {
168+
const logs = parseEventLogs({
169+
abi: zkSyncL2BridgeAbi,
170+
logs: originTransaction.logs,
171+
});
172+
173+
const withdrawalEvent = logs.find((log) => log.eventName === 'WithdrawalInitiated');
174+
if (!withdrawalEvent) {
175+
this.logger.warn('No WithdrawalInitiated event found in transaction logs');
176+
return;
177+
}
178+
179+
this.logger.info('zkSync withdrawal requires manual finalization after 24-hour delay', {
180+
withdrawalTxHash: originTransaction.transactionHash,
181+
blockNumber: originTransaction.blockNumber,
182+
});
183+
184+
throw new Error('zkSync withdrawal finalization not yet implemented - requires batch proof integration');
185+
}
186+
} catch (error) {
187+
this.handleError(error, 'prepare destination callback', { route, originTransaction });
188+
}
189+
}
190+
191+
private async getClient(chainId: number): Promise<PublicClient> {
192+
const providers = this.chains[chainId.toString()]?.providers ?? [];
193+
if (providers.length === 0) {
194+
throw new Error(`No providers configured for chain ${chainId}`);
195+
}
196+
197+
return createPublicClient({
198+
transport: fallback(providers.map((provider: string) => http(provider))),
199+
});
200+
}
201+
202+
private handleError(error: Error | unknown, context: string, metadata: Record<string, unknown>): never {
203+
this.logger.error(`Failed to ${context}`, {
204+
error: jsonifyError(error),
205+
...metadata,
206+
});
207+
throw new Error(`Failed to ${context}: ${(error as Error)?.message ?? ''}`);
208+
}
209+
}

packages/core/src/types/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export enum SupportedBridge {
8080
Stargate = 'stargate',
8181
TacInner = 'tac-inner',
8282
CCIP = 'chainlink-ccip',
83+
Zksync = 'zksync',
8384
}
8485

8586
export enum GasType {

0 commit comments

Comments
 (0)