@oydual31/more-vaults-sdk 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oydual31/more-vaults-sdk",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript SDK for MoreVaults protocol — viem/wagmi and ethers.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Curator BridgeFacet helpers for the MoreVaults ethers.js v6 SDK.
3
+ *
4
+ * Provides typed helpers to quote and execute cross-chain asset bridging
5
+ * via BridgeFacet.executeBridging on any MoreVaults diamond.
6
+ *
7
+ * Key flows:
8
+ * 1. `quoteCuratorBridgeFee` — read-only fee estimation via LzAdapter
9
+ * 2. `executeCuratorBridge` — send bridging transaction (curator only)
10
+ * 3. `encodeBridgeParams` — encode the 5-field bridgeSpecificParams bytes
11
+ * 4. `findBridgeRoute` — resolve OFT route for a token on given chains
12
+ *
13
+ * @module curatorBridge
14
+ */
15
+
16
+ import { AbiCoder, Contract, getAddress } from "ethers";
17
+ import type { Provider, Signer, ContractTransactionReceipt } from "ethers";
18
+ import { BRIDGE_FACET_ABI, LZ_ADAPTER_ABI } from "./abis";
19
+ import { OFT_ROUTES } from "./chains";
20
+ import { getCuratorVaultStatus } from "./curatorStatus";
21
+
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // Types
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+
26
+ /**
27
+ * Parameters for a curator bridge operation.
28
+ */
29
+ export interface CuratorBridgeParams {
30
+ /** OFT contract address on the source chain (from OFT_ROUTES[symbol][chainId].oft) */
31
+ oftToken: string;
32
+ /** LayerZero endpoint ID of the destination chain */
33
+ dstEid: number;
34
+ /** Amount to bridge (in token's native units) */
35
+ amount: bigint;
36
+ /** Vault address on the destination chain (hub or spoke) */
37
+ dstVault: string;
38
+ /** Address where excess LayerZero gas refunds are sent (usually the curator wallet) */
39
+ refundAddress: string;
40
+ }
41
+
42
+ // ─────────────────────────────────────────────────────────────────────────────
43
+ // Encoding helpers
44
+ // ─────────────────────────────────────────────────────────────────────────────
45
+
46
+ /**
47
+ * Encode the 5-field bridgeSpecificParams for use in `executeBridging`.
48
+ *
49
+ * Encodes: (oftToken, dstEid, amount, dstVault, refundAddress)
50
+ * Types: (address, uint32, uint256, address, address)
51
+ *
52
+ * @param params Full bridge parameters including refundAddress
53
+ * @returns ABI-encoded hex string
54
+ */
55
+ export function encodeBridgeParams(params: CuratorBridgeParams): string {
56
+ const coder = AbiCoder.defaultAbiCoder();
57
+ return coder.encode(
58
+ ["address", "uint32", "uint256", "address", "address"],
59
+ [
60
+ getAddress(params.oftToken),
61
+ params.dstEid,
62
+ params.amount,
63
+ getAddress(params.dstVault),
64
+ getAddress(params.refundAddress),
65
+ ]
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Encode the 4-field bridgeSpecificParams for `quoteBridgeFee`.
71
+ * Does NOT include refundAddress — quoting only needs routing parameters.
72
+ *
73
+ * @internal
74
+ */
75
+ function encodeBridgeParamsForQuote(
76
+ params: Omit<CuratorBridgeParams, "refundAddress">
77
+ ): string {
78
+ const coder = AbiCoder.defaultAbiCoder();
79
+ return coder.encode(
80
+ ["address", "uint32", "uint256", "address"],
81
+ [
82
+ getAddress(params.oftToken),
83
+ params.dstEid,
84
+ params.amount,
85
+ getAddress(params.dstVault),
86
+ ]
87
+ );
88
+ }
89
+
90
+ // ─────────────────────────────────────────────────────────────────────────────
91
+ // Route resolver
92
+ // ─────────────────────────────────────────────────────────────────────────────
93
+
94
+ /**
95
+ * Find the OFT bridge route for a given token address on the source chain.
96
+ *
97
+ * Searches OFT_ROUTES for an asset whose `token` or `oft` field matches
98
+ * the provided address on the given source chainId.
99
+ *
100
+ * @param srcChainId EVM chain ID of the source chain
101
+ * @param dstChainId EVM chain ID of the destination chain
102
+ * @param tokenAddress ERC-20 token address on the source chain
103
+ * @returns Route info or null if no matching route exists
104
+ */
105
+ export function findBridgeRoute(
106
+ srcChainId: number,
107
+ dstChainId: number,
108
+ tokenAddress: string
109
+ ): { oftSrc: string; oftDst: string; symbol: string } | null {
110
+ const normalizedToken = getAddress(tokenAddress);
111
+
112
+ for (const [symbol, chains] of Object.entries(OFT_ROUTES)) {
113
+ const srcEntry = (
114
+ chains as Record<number, { oft: string; token: string }>
115
+ )[srcChainId];
116
+ const dstEntry = (
117
+ chains as Record<number, { oft: string; token: string }>
118
+ )[dstChainId];
119
+
120
+ if (!srcEntry || !dstEntry) continue;
121
+
122
+ const srcToken = getAddress(srcEntry.token);
123
+ const srcOft = getAddress(srcEntry.oft);
124
+
125
+ if (srcToken === normalizedToken || srcOft === normalizedToken) {
126
+ return {
127
+ oftSrc: srcOft,
128
+ oftDst: getAddress(dstEntry.oft),
129
+ symbol,
130
+ };
131
+ }
132
+ }
133
+
134
+ return null;
135
+ }
136
+
137
+ // ─────────────────────────────────────────────────────────────────────────────
138
+ // Read operations
139
+ // ─────────────────────────────────────────────────────────────────────────────
140
+
141
+ /**
142
+ * Quote the native fee required to bridge assets via the vault's LzAdapter.
143
+ *
144
+ * Calls `lzAdapter.quoteBridgeFee(bridgeSpecificParams)` using a 4-field
145
+ * encoding (no refundAddress). The returned fee must be sent as `value`
146
+ * when calling `executeBridging`.
147
+ *
148
+ * @param provider Read-only provider (must be on the vault's chain)
149
+ * @param vault Hub vault address (diamond proxy)
150
+ * @param params Bridge parameters (refundAddress is included but not encoded for quote)
151
+ * @returns Native fee in wei
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * const fee = await quoteCuratorBridgeFee(provider, VAULT, {
156
+ * oftToken: '0x27a16dc786820B16E5c9028b75B99F6f604b5d26',
157
+ * dstEid: 30101,
158
+ * amount: 1_000_000n,
159
+ * dstVault: '0xSpokeVault...',
160
+ * refundAddress: '0xCurator...',
161
+ * })
162
+ * ```
163
+ */
164
+ export async function quoteCuratorBridgeFee(
165
+ provider: Provider,
166
+ vault: string,
167
+ params: CuratorBridgeParams
168
+ ): Promise<bigint> {
169
+ const status = await getCuratorVaultStatus(provider, vault);
170
+ const lzAdapter = status.lzAdapter;
171
+
172
+ const bridgeSpecificParams = encodeBridgeParamsForQuote(params);
173
+
174
+ const adapterContract = new Contract(lzAdapter, LZ_ADAPTER_ABI, provider);
175
+ const nativeFee = (await adapterContract.quoteBridgeFee.staticCall(
176
+ bridgeSpecificParams
177
+ )) as bigint;
178
+
179
+ return nativeFee;
180
+ }
181
+
182
+ // ─────────────────────────────────────────────────────────────────────────────
183
+ // Write operations
184
+ // ─────────────────────────────────────────────────────────────────────────────
185
+
186
+ /**
187
+ * Execute a curator bridge operation via `BridgeFacet.executeBridging`.
188
+ *
189
+ * This is a direct curator call (NOT via multicall). The vault pauses during
190
+ * bridging for security. The `token` parameter is the underlying ERC-20,
191
+ * NOT the OFT address.
192
+ *
193
+ * Steps:
194
+ * 1. Get lzAdapter from `getCuratorVaultStatus`
195
+ * 2. Quote the native bridge fee
196
+ * 3. Encode 5-field bridgeSpecificParams
197
+ * 4. Call `vault.executeBridging(adapter, token, amount, bridgeSpecificParams)` with fee as value
198
+ *
199
+ * @param signer Signer with curator account attached
200
+ * @param vault Hub vault address (diamond proxy)
201
+ * @param token Underlying ERC-20 token address (NOT the OFT address)
202
+ * @param params Full bridge parameters including refundAddress
203
+ * @returns Transaction receipt
204
+ * @throws If caller is not curator, vault is paused, or bridge fails
205
+ */
206
+ export async function executeCuratorBridge(
207
+ signer: Signer,
208
+ vault: string,
209
+ token: string,
210
+ params: CuratorBridgeParams
211
+ ): Promise<ContractTransactionReceipt> {
212
+ const provider = signer.provider!;
213
+
214
+ // Step 1: Get lzAdapter address from vault status
215
+ const status = await getCuratorVaultStatus(provider, vault);
216
+ const lzAdapter = status.lzAdapter;
217
+
218
+ // Step 2: Quote the bridge fee
219
+ const fee = await quoteCuratorBridgeFee(provider, vault, params);
220
+
221
+ // Step 3: Encode full 5-field bridgeSpecificParams
222
+ const bridgeSpecificParams = encodeBridgeParams(params);
223
+
224
+ // Step 4: Execute bridging
225
+ const vaultContract = new Contract(vault, BRIDGE_FACET_ABI, signer);
226
+ const tx = await vaultContract.executeBridging(
227
+ getAddress(lzAdapter),
228
+ getAddress(token),
229
+ params.amount,
230
+ bridgeSpecificParams,
231
+ { value: fee }
232
+ );
233
+
234
+ return tx.wait() as Promise<ContractTransactionReceipt>;
235
+ }
@@ -171,6 +171,15 @@ export {
171
171
  encodeUniswapV3SwapCalldata,
172
172
  } from "./curatorSwaps";
173
173
 
174
+ // --- Curator bridge helpers ---
175
+ export {
176
+ encodeBridgeParams,
177
+ quoteCuratorBridgeFee,
178
+ executeCuratorBridge,
179
+ findBridgeRoute,
180
+ } from "./curatorBridge";
181
+ export type { CuratorBridgeParams } from "./curatorBridge";
182
+
174
183
  // --- Topology ---
175
184
  export {
176
185
  getVaultTopology,
@@ -62,3 +62,7 @@ export type { CuratorAction } from './useSubmitActions.js'
62
62
  export { useExecuteActions } from './useExecuteActions.js'
63
63
 
64
64
  export { useVetoActions } from './useVetoActions.js'
65
+
66
+ // --- Curator Bridge hooks ---
67
+ export { useCuratorBridgeQuote } from './useCuratorBridgeQuote.js'
68
+ export { useExecuteBridge } from './useExecuteBridge.js'
@@ -0,0 +1,43 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+ import { usePublicClient } from 'wagmi'
3
+ import { asSdkClient, quoteCuratorBridgeFee } from '../viem/index.js'
4
+ import type { CuratorBridgeParams } from '../viem/index.js'
5
+
6
+ export type { CuratorBridgeParams }
7
+
8
+ /**
9
+ * Quote the native fee required to bridge assets from the hub vault via LzAdapter.
10
+ *
11
+ * Refreshes every 60s — bridge fees fluctuate with LayerZero network demand.
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * const { fee, isLoading } = useCuratorBridgeQuote('0xVAULT', 8453, {
16
+ * oftToken: '0x27a16dc786820B16E5c9028b75B99F6f604b5d26',
17
+ * dstEid: 30101,
18
+ * amount: 1_000_000n,
19
+ * dstVault: '0xSpokeVault...',
20
+ * refundAddress: '0xCurator...',
21
+ * })
22
+ * ```
23
+ */
24
+ export function useCuratorBridgeQuote(
25
+ vault: `0x${string}` | undefined,
26
+ chainId: number,
27
+ params: CuratorBridgeParams | undefined,
28
+ ) {
29
+ const publicClient = usePublicClient({ chainId })
30
+
31
+ const query = useQuery({
32
+ queryKey: ['curatorBridgeQuote', vault, chainId, params],
33
+ queryFn: () => quoteCuratorBridgeFee(asSdkClient(publicClient), vault!, params!),
34
+ enabled: !!vault && !!publicClient && !!params,
35
+ refetchInterval: 60_000,
36
+ staleTime: 30_000,
37
+ })
38
+
39
+ return {
40
+ ...query,
41
+ fee: query.data,
42
+ }
43
+ }
@@ -0,0 +1,50 @@
1
+ import { useMutation } from '@tanstack/react-query'
2
+ import { usePublicClient, useWalletClient } from 'wagmi'
3
+ import { asSdkClient, executeCuratorBridge } from '../viem/index.js'
4
+ import type { CuratorBridgeParams } from '../viem/index.js'
5
+ import type { Address } from 'viem'
6
+
7
+ export type { CuratorBridgeParams }
8
+
9
+ /**
10
+ * Execute a curator bridge operation via BridgeFacet.executeBridging.
11
+ *
12
+ * This is a direct curator call (NOT via multicall). The vault pauses during
13
+ * bridging for security. Automatically quotes and includes the required
14
+ * LayerZero fee as msg.value.
15
+ *
16
+ * @param vault Hub vault address (diamond proxy)
17
+ * @param token Underlying ERC-20 token address to bridge (NOT the OFT address)
18
+ * @param chainId Chain ID of the hub vault
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * const { mutateAsync, isPending } = useExecuteBridge('0xVAULT', USDC_ADDRESS, 8453)
23
+ *
24
+ * await mutateAsync({
25
+ * oftToken: '0x27a16dc786820B16E5c9028b75B99F6f604b5d26',
26
+ * dstEid: 30101,
27
+ * amount: 1_000_000n,
28
+ * dstVault: '0xSpokeVault...',
29
+ * refundAddress: curatorAddress,
30
+ * })
31
+ * ```
32
+ */
33
+ export function useExecuteBridge(
34
+ vault: `0x${string}`,
35
+ token: Address,
36
+ chainId: number,
37
+ ) {
38
+ const publicClient = usePublicClient({ chainId })
39
+ const { data: walletClient } = useWalletClient({ chainId })
40
+
41
+ return useMutation({
42
+ mutationFn: async (params: CuratorBridgeParams) => {
43
+ if (!walletClient || !publicClient) {
44
+ throw new Error('Wallet or public client not available')
45
+ }
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ return executeCuratorBridge(walletClient as any, asSdkClient(publicClient), vault, token, params)
48
+ },
49
+ })
50
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Curator BridgeFacet helpers for the MoreVaults SDK.
3
+ *
4
+ * Provides typed helpers to quote and execute cross-chain asset bridging
5
+ * via BridgeFacet.executeBridging on any MoreVaults diamond.
6
+ *
7
+ * Key flows:
8
+ * 1. `quoteCuratorBridgeFee` — read-only fee estimation via LzAdapter
9
+ * 2. `executeCuratorBridge` — send bridging transaction (curator only)
10
+ * 3. `encodeBridgeParams` — encode the 5-field bridgeSpecificParams bytes
11
+ * 4. `findBridgeRoute` — resolve OFT route for a token on given chains
12
+ *
13
+ * Bridge call flow:
14
+ * curator → vault.executeBridging(adapter, token, amount, bridgeSpecificParams)
15
+ * bridgeSpecificParams = abi.encode(oftToken, dstEid, amount, dstVault, refundAddress)
16
+ *
17
+ * Quote call flow:
18
+ * publicClient → lzAdapter.quoteBridgeFee(encode(oftToken, dstEid, amount, dstVault))
19
+ *
20
+ * @module curatorBridge
21
+ */
22
+
23
+ import {
24
+ type Address,
25
+ type PublicClient,
26
+ type WalletClient,
27
+ type Hash,
28
+ encodeAbiParameters,
29
+ getAddress,
30
+ } from 'viem'
31
+ import { BRIDGE_FACET_ABI, LZ_ADAPTER_ABI } from './abis.js'
32
+ import { OFT_ROUTES } from './chains.js'
33
+ import { getCuratorVaultStatus } from './curatorStatus.js'
34
+
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // Types
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+
39
+ /**
40
+ * Parameters for a curator bridge operation.
41
+ * Used in both quoting (4-field) and execution (5-field with refundAddress).
42
+ */
43
+ export interface CuratorBridgeParams {
44
+ /** OFT contract address on the source chain (from OFT_ROUTES[symbol][chainId].oft) */
45
+ oftToken: Address
46
+ /** LayerZero endpoint ID of the destination chain */
47
+ dstEid: number
48
+ /** Amount to bridge (in token's native units) */
49
+ amount: bigint
50
+ /** Vault address on the destination chain (hub or spoke) */
51
+ dstVault: Address
52
+ /** Address where excess LayerZero gas refunds are sent (usually the curator wallet) */
53
+ refundAddress: Address
54
+ }
55
+
56
+ // ─────────────────────────────────────────────────────────────────────────────
57
+ // Encoding helpers
58
+ // ─────────────────────────────────────────────────────────────────────────────
59
+
60
+ /**
61
+ * Encode the 5-field bridgeSpecificParams for use in `executeBridging`.
62
+ *
63
+ * Encodes: (oftToken, dstEid, amount, dstVault, refundAddress)
64
+ * Types: (address, uint32, uint256, address, address)
65
+ *
66
+ * @param params Full bridge parameters including refundAddress
67
+ * @returns ABI-encoded bytes (`0x`-prefixed hex string)
68
+ */
69
+ export function encodeBridgeParams(params: CuratorBridgeParams): `0x${string}` {
70
+ return encodeAbiParameters(
71
+ [
72
+ { type: 'address' },
73
+ { type: 'uint32' },
74
+ { type: 'uint256' },
75
+ { type: 'address' },
76
+ { type: 'address' },
77
+ ],
78
+ [
79
+ getAddress(params.oftToken),
80
+ params.dstEid,
81
+ params.amount,
82
+ getAddress(params.dstVault),
83
+ getAddress(params.refundAddress),
84
+ ],
85
+ )
86
+ }
87
+
88
+ /**
89
+ * Encode the 4-field bridgeSpecificParams for `quoteBridgeFee`.
90
+ * Does NOT include refundAddress — quoting only needs the routing parameters.
91
+ *
92
+ * Encodes: (oftToken, dstEid, amount, dstVault)
93
+ * Types: (address, uint32, uint256, address)
94
+ *
95
+ * @internal
96
+ */
97
+ function encodeBridgeParamsForQuote(params: Omit<CuratorBridgeParams, 'refundAddress'>): `0x${string}` {
98
+ return encodeAbiParameters(
99
+ [
100
+ { type: 'address' },
101
+ { type: 'uint32' },
102
+ { type: 'uint256' },
103
+ { type: 'address' },
104
+ ],
105
+ [
106
+ getAddress(params.oftToken),
107
+ params.dstEid,
108
+ params.amount,
109
+ getAddress(params.dstVault),
110
+ ],
111
+ )
112
+ }
113
+
114
+ // ─────────────────────────────────────────────────────────────────────────────
115
+ // Route resolver
116
+ // ─────────────────────────────────────────────────────────────────────────────
117
+
118
+ /**
119
+ * Find the OFT bridge route for a given token address on the source chain.
120
+ *
121
+ * Searches OFT_ROUTES for an asset whose `token` or `oft` field matches
122
+ * the provided address on the given source chainId.
123
+ *
124
+ * @param srcChainId EVM chain ID of the source chain (where the vault holds the token)
125
+ * @param dstChainId EVM chain ID of the destination chain
126
+ * @param tokenAddress ERC-20 token address on the source chain
127
+ * @returns Route info or null if no matching route exists
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const route = findBridgeRoute(8453, 1, '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913')
132
+ * // → { symbol: 'stgUSDC', oftHub: '0x27a1...', oftSpoke: '0xc026...' }
133
+ * ```
134
+ */
135
+ export function findBridgeRoute(
136
+ srcChainId: number,
137
+ dstChainId: number,
138
+ tokenAddress: Address,
139
+ ): { oftSrc: Address; oftDst: Address; symbol: string } | null {
140
+ const normalizedToken = getAddress(tokenAddress)
141
+
142
+ for (const [symbol, chains] of Object.entries(OFT_ROUTES)) {
143
+ const srcEntry = (chains as Record<number, { oft: `0x${string}`; token: `0x${string}` }>)[srcChainId]
144
+ const dstEntry = (chains as Record<number, { oft: `0x${string}`; token: `0x${string}` }>)[dstChainId]
145
+
146
+ if (!srcEntry || !dstEntry) continue
147
+
148
+ // Match if the provided address is either the token OR the OFT on the source chain
149
+ const srcToken = getAddress(srcEntry.token)
150
+ const srcOft = getAddress(srcEntry.oft)
151
+
152
+ if (srcToken === normalizedToken || srcOft === normalizedToken) {
153
+ return {
154
+ oftSrc: srcOft,
155
+ oftDst: getAddress(dstEntry.oft),
156
+ symbol,
157
+ }
158
+ }
159
+ }
160
+
161
+ return null
162
+ }
163
+
164
+ // ─────────────────────────────────────────────────────────────────────────────
165
+ // Read operations
166
+ // ─────────────────────────────────────────────────────────────────────────────
167
+
168
+ /**
169
+ * Quote the native fee required to bridge assets via the vault's LzAdapter.
170
+ *
171
+ * Calls `lzAdapter.quoteBridgeFee(bridgeSpecificParams)` using a 4-field
172
+ * encoding (no refundAddress). The returned fee must be sent as `msg.value`
173
+ * when calling `executeBridging`.
174
+ *
175
+ * @param publicClient Viem public client (must be on the vault's chain)
176
+ * @param vault Hub vault address (diamond proxy)
177
+ * @param params Bridge parameters (refundAddress is optional here)
178
+ * @returns Native fee in wei
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * const fee = await quoteCuratorBridgeFee(publicClient, VAULT, {
183
+ * oftToken: '0x27a16dc786820B16E5c9028b75B99F6f604b5d26', // stgUSDC on Base
184
+ * dstEid: 30101, // Ethereum EID
185
+ * amount: 1_000_000n, // 1 USDC
186
+ * dstVault: '0xSpokeVault...',
187
+ * refundAddress: '0xCurator...',
188
+ * })
189
+ * console.log('Fee:', formatEther(fee), 'ETH')
190
+ * ```
191
+ */
192
+ export async function quoteCuratorBridgeFee(
193
+ publicClient: PublicClient,
194
+ vault: Address,
195
+ params: CuratorBridgeParams,
196
+ ): Promise<bigint> {
197
+ // Get lzAdapter from vault status
198
+ const status = await getCuratorVaultStatus(publicClient, vault)
199
+ const lzAdapter = status.lzAdapter
200
+
201
+ // Encode 4-field params (no refundAddress) for quoting
202
+ const bridgeSpecificParams = encodeBridgeParamsForQuote(params)
203
+
204
+ // Call quoteBridgeFee on the LzAdapter (not the vault)
205
+ const nativeFee = await publicClient.readContract({
206
+ address: lzAdapter,
207
+ abi: LZ_ADAPTER_ABI,
208
+ functionName: 'quoteBridgeFee',
209
+ args: [bridgeSpecificParams],
210
+ })
211
+
212
+ return nativeFee as bigint
213
+ }
214
+
215
+ // ─────────────────────────────────────────────────────────────────────────────
216
+ // Write operations
217
+ // ─────────────────────────────────────────────────────────────────────────────
218
+
219
+ /**
220
+ * Execute a curator bridge operation via `BridgeFacet.executeBridging`.
221
+ *
222
+ * This is a direct curator call (NOT via multicall). The vault pauses during
223
+ * bridging for security. The `token` passed to executeBridging is the underlying
224
+ * ERC-20, NOT the OFT address.
225
+ *
226
+ * Steps:
227
+ * 1. Get lzAdapter from `getCuratorVaultStatus`
228
+ * 2. Quote the native bridge fee
229
+ * 3. Encode 5-field bridgeSpecificParams
230
+ * 4. Call `vault.executeBridging(adapter, token, amount, bridgeSpecificParams)` with fee as value
231
+ *
232
+ * @param walletClient Wallet client with curator account attached
233
+ * @param publicClient Public client for reads and fee quoting
234
+ * @param vault Hub vault address (diamond proxy)
235
+ * @param token Underlying ERC-20 token address (NOT the OFT address)
236
+ * @param params Full bridge parameters including refundAddress
237
+ * @returns Transaction hash
238
+ * @throws If caller is not curator, vault is paused, or bridge fails
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const txHash = await executeCuratorBridge(walletClient, publicClient, VAULT, USDC_ADDRESS, {
243
+ * oftToken: '0x27a16dc786820B16E5c9028b75B99F6f604b5d26',
244
+ * dstEid: 30101,
245
+ * amount: 1_000_000n,
246
+ * dstVault: '0xSpokeVault...',
247
+ * refundAddress: curatorAddress,
248
+ * })
249
+ * ```
250
+ */
251
+ export async function executeCuratorBridge(
252
+ walletClient: WalletClient,
253
+ publicClient: PublicClient,
254
+ vault: Address,
255
+ token: Address,
256
+ params: CuratorBridgeParams,
257
+ ): Promise<Hash> {
258
+ const account = walletClient.account!
259
+ const v = getAddress(vault)
260
+
261
+ // Step 1: Get lzAdapter address from vault status
262
+ const status = await getCuratorVaultStatus(publicClient, vault)
263
+ const lzAdapter = status.lzAdapter
264
+
265
+ // Step 2: Quote the bridge fee
266
+ const fee = await quoteCuratorBridgeFee(publicClient, vault, params)
267
+
268
+ // Step 3: Encode full 5-field bridgeSpecificParams
269
+ const bridgeSpecificParams = encodeBridgeParams(params)
270
+
271
+ // Step 4: Execute bridging with fee as msg.value
272
+ const txHash = await walletClient.writeContract({
273
+ address: v,
274
+ abi: BRIDGE_FACET_ABI,
275
+ functionName: 'executeBridging',
276
+ args: [
277
+ lzAdapter,
278
+ getAddress(token),
279
+ params.amount,
280
+ bridgeSpecificParams,
281
+ ],
282
+ value: fee,
283
+ account,
284
+ chain: walletClient.chain,
285
+ })
286
+
287
+ return txHash
288
+ }
package/src/viem/index.ts CHANGED
@@ -178,6 +178,13 @@ export {
178
178
  buildUniswapV3Swap,
179
179
  encodeUniswapV3SwapCalldata,
180
180
  } from './curatorSwaps'
181
+ export {
182
+ encodeBridgeParams,
183
+ quoteCuratorBridgeFee,
184
+ executeCuratorBridge,
185
+ findBridgeRoute,
186
+ } from './curatorBridge'
187
+ export type { CuratorBridgeParams } from './curatorBridge'
181
188
 
182
189
  // --- wagmi compatibility ---
183
190
  // Re-export viem's PublicClient type for wagmi compatibility.