@oydual31/more-vaults-sdk 0.4.2 → 0.6.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/README.md +94 -0
- package/dist/{spokeRoutes-B8Lnk-t4.d.cts → curatorBridge-CNs59kT9.d.cts} +222 -1
- package/dist/{spokeRoutes-B8Lnk-t4.d.ts → curatorBridge-CNs59kT9.d.ts} +222 -1
- package/dist/ethers/index.cjs +328 -3
- package/dist/ethers/index.cjs.map +1 -1
- package/dist/ethers/index.d.cts +279 -1
- package/dist/ethers/index.d.ts +279 -1
- package/dist/ethers/index.js +318 -5
- package/dist/ethers/index.js.map +1 -1
- package/dist/react/index.cjs +375 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +266 -2
- package/dist/react/index.d.ts +266 -2
- package/dist/react/index.js +372 -2
- package/dist/react/index.js.map +1 -1
- package/dist/viem/index.cjs +377 -0
- package/dist/viem/index.cjs.map +1 -1
- package/dist/viem/index.d.cts +261 -3
- package/dist/viem/index.d.ts +261 -3
- package/dist/viem/index.js +367 -2
- package/dist/viem/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ethers/abis.ts +24 -0
- package/src/ethers/curatorBridge.ts +235 -0
- package/src/ethers/curatorSubVaults.ts +443 -0
- package/src/ethers/index.ts +26 -0
- package/src/ethers/types.ts +99 -0
- package/src/react/index.ts +14 -0
- package/src/react/useCuratorBridgeQuote.ts +43 -0
- package/src/react/useERC7540RequestStatus.ts +43 -0
- package/src/react/useExecuteBridge.ts +50 -0
- package/src/react/useSubVaultPositions.ts +35 -0
- package/src/react/useVaultPortfolio.ts +35 -0
- package/src/viem/abis.ts +24 -0
- package/src/viem/curatorBridge.ts +288 -0
- package/src/viem/curatorSubVaults.ts +514 -0
- package/src/viem/index.ts +23 -0
- package/src/viem/types.ts +100 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query'
|
|
2
|
+
import { usePublicClient } from 'wagmi'
|
|
3
|
+
import { asSdkClient, getERC7540RequestStatus } from '../viem/index.js'
|
|
4
|
+
import type { ERC7540RequestStatus } from '../viem/index.js'
|
|
5
|
+
|
|
6
|
+
export type { ERC7540RequestStatus }
|
|
7
|
+
|
|
8
|
+
interface UseERC7540RequestStatusOptions {
|
|
9
|
+
/** Refetch interval in ms. Default: 15_000 (15s) — shorter since pending→claimable transitions matter */
|
|
10
|
+
refetchInterval?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read ERC7540 async request status for a specific sub-vault.
|
|
15
|
+
*
|
|
16
|
+
* Queries pending and claimable deposit/redeem amounts for the vault
|
|
17
|
+
* acting as controller in the given ERC7540 sub-vault (requestId = 0).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const { data: status } = useERC7540RequestStatus(
|
|
21
|
+
* '0xVAULT',
|
|
22
|
+
* 8453,
|
|
23
|
+
* '0xSUB_VAULT'
|
|
24
|
+
* )
|
|
25
|
+
* if (status?.canFinalizeDeposit) {
|
|
26
|
+
* // curator can call erc7540Deposit to claim shares
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
export function useERC7540RequestStatus(
|
|
30
|
+
vault: `0x${string}` | undefined,
|
|
31
|
+
chainId: number,
|
|
32
|
+
subVault: `0x${string}` | undefined,
|
|
33
|
+
options?: UseERC7540RequestStatusOptions,
|
|
34
|
+
) {
|
|
35
|
+
const publicClient = usePublicClient({ chainId })
|
|
36
|
+
return useQuery({
|
|
37
|
+
queryKey: ['erc7540RequestStatus', vault, chainId, subVault],
|
|
38
|
+
queryFn: () => getERC7540RequestStatus(asSdkClient(publicClient), vault!, subVault!),
|
|
39
|
+
enabled: !!vault && !!publicClient && !!subVault,
|
|
40
|
+
refetchInterval: options?.refetchInterval ?? 15_000,
|
|
41
|
+
staleTime: 10_000,
|
|
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,35 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query'
|
|
2
|
+
import { usePublicClient } from 'wagmi'
|
|
3
|
+
import { asSdkClient, getSubVaultPositions } from '../viem/index.js'
|
|
4
|
+
import type { SubVaultPosition } from '../viem/index.js'
|
|
5
|
+
|
|
6
|
+
export type { SubVaultPosition }
|
|
7
|
+
|
|
8
|
+
interface UseSubVaultPositionsOptions {
|
|
9
|
+
/** Refetch interval in ms. Default: 30_000 (30s) */
|
|
10
|
+
refetchInterval?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read active sub-vault positions held by a curator vault.
|
|
15
|
+
*
|
|
16
|
+
* Returns ERC4626 and ERC7540 positions with share balances and underlying values.
|
|
17
|
+
* Positions with zero share balance are excluded.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const { data: positions, isLoading } = useSubVaultPositions('0xVAULT', 8453)
|
|
21
|
+
*/
|
|
22
|
+
export function useSubVaultPositions(
|
|
23
|
+
vault: `0x${string}` | undefined,
|
|
24
|
+
chainId: number,
|
|
25
|
+
options?: UseSubVaultPositionsOptions,
|
|
26
|
+
) {
|
|
27
|
+
const publicClient = usePublicClient({ chainId })
|
|
28
|
+
return useQuery({
|
|
29
|
+
queryKey: ['subVaultPositions', vault, chainId],
|
|
30
|
+
queryFn: () => getSubVaultPositions(asSdkClient(publicClient), vault!),
|
|
31
|
+
enabled: !!vault && !!publicClient,
|
|
32
|
+
refetchInterval: options?.refetchInterval ?? 30_000,
|
|
33
|
+
staleTime: 15_000,
|
|
34
|
+
})
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query'
|
|
2
|
+
import { usePublicClient } from 'wagmi'
|
|
3
|
+
import { asSdkClient, getVaultPortfolio } from '../viem/index.js'
|
|
4
|
+
import type { VaultPortfolio } from '../viem/index.js'
|
|
5
|
+
|
|
6
|
+
export type { VaultPortfolio }
|
|
7
|
+
|
|
8
|
+
interface UseVaultPortfolioOptions {
|
|
9
|
+
/** Refetch interval in ms. Default: 30_000 (30s) */
|
|
10
|
+
refetchInterval?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read the full portfolio view for a curator vault.
|
|
15
|
+
*
|
|
16
|
+
* Combines liquid asset balances with ERC4626/ERC7540 sub-vault positions
|
|
17
|
+
* and locked ERC7540 pending assets into a single portfolio snapshot.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const { data: portfolio, isLoading } = useVaultPortfolio('0xVAULT', 8453)
|
|
21
|
+
*/
|
|
22
|
+
export function useVaultPortfolio(
|
|
23
|
+
vault: `0x${string}` | undefined,
|
|
24
|
+
chainId: number,
|
|
25
|
+
options?: UseVaultPortfolioOptions,
|
|
26
|
+
) {
|
|
27
|
+
const publicClient = usePublicClient({ chainId })
|
|
28
|
+
return useQuery({
|
|
29
|
+
queryKey: ['vaultPortfolio', vault, chainId],
|
|
30
|
+
queryFn: () => getVaultPortfolio(asSdkClient(publicClient), vault!),
|
|
31
|
+
enabled: !!vault && !!publicClient,
|
|
32
|
+
refetchInterval: options?.refetchInterval ?? 30_000,
|
|
33
|
+
staleTime: 15_000,
|
|
34
|
+
})
|
|
35
|
+
}
|
package/src/viem/abis.ts
CHANGED
|
@@ -780,6 +780,30 @@ export const REGISTRY_ABI = [
|
|
|
780
780
|
{ type: 'function', name: 'getAllowedFacets', inputs: [], outputs: [{ type: 'address[]' }], stateMutability: 'view' },
|
|
781
781
|
] as const
|
|
782
782
|
|
|
783
|
+
/**
|
|
784
|
+
* Sub-vault ABI — reads for ERC4626/ERC7540 sub-vaults and ConfigurationFacet extensions.
|
|
785
|
+
* Used by curator sub-vault portfolio helpers (Phase 5).
|
|
786
|
+
*/
|
|
787
|
+
export const SUB_VAULT_ABI = [
|
|
788
|
+
// ConfigurationFacet reads — called on the MoreVaults diamond proxy
|
|
789
|
+
{ type: 'function', name: 'tokensHeld', inputs: [{ name: 'id', type: 'bytes32' }], outputs: [{ name: '', type: 'address[]' }], stateMutability: 'view' },
|
|
790
|
+
{ type: 'function', name: 'lockedTokensAmountOfAsset', inputs: [{ name: 'asset', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
791
|
+
|
|
792
|
+
// ERC4626 standard reads — called on the sub-vault contract
|
|
793
|
+
{ type: 'function', name: 'convertToAssets', inputs: [{ name: 'shares', type: 'uint256' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
794
|
+
{ type: 'function', name: 'convertToShares', inputs: [{ name: 'assets', type: 'uint256' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
795
|
+
{ type: 'function', name: 'previewDeposit', inputs: [{ name: 'assets', type: 'uint256' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
796
|
+
{ type: 'function', name: 'previewRedeem', inputs: [{ name: 'shares', type: 'uint256' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
797
|
+
{ type: 'function', name: 'maxDeposit', inputs: [{ name: 'receiver', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
798
|
+
{ type: 'function', name: 'maxRedeem', inputs: [{ name: 'owner', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
799
|
+
|
|
800
|
+
// ERC7540 async reads — called on the sub-vault contract
|
|
801
|
+
{ type: 'function', name: 'pendingDepositRequest', inputs: [{ name: 'requestId', type: 'uint256' }, { name: 'controller', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
802
|
+
{ type: 'function', name: 'claimableDepositRequest', inputs: [{ name: 'requestId', type: 'uint256' }, { name: 'controller', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
803
|
+
{ type: 'function', name: 'pendingRedeemRequest', inputs: [{ name: 'requestId', type: 'uint256' }, { name: 'controller', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
804
|
+
{ type: 'function', name: 'claimableRedeemRequest', inputs: [{ name: 'requestId', type: 'uint256' }, { name: 'controller', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view' },
|
|
805
|
+
] as const
|
|
806
|
+
|
|
783
807
|
/**
|
|
784
808
|
* Minimal LZ Endpoint V2 ABI for compose queue management.
|
|
785
809
|
* Used by the Stargate 2-TX flow to check compose status and execute pending composes.
|
|
@@ -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
|
+
}
|