@oydual31/more-vaults-sdk 0.2.1 → 0.2.2

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.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "TypeScript SDK for MoreVaults protocol — viem/wagmi and ethers.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -2,7 +2,7 @@ import { usePublicClient, useChainId } from 'wagmi'
2
2
  import { useQuery } from '@tanstack/react-query'
3
3
  import { asSdkClient } from '../viem/wagmiCompat.js'
4
4
  import {
5
- getVaultTopology,
5
+ discoverVaultTopology,
6
6
  isOnHubChain,
7
7
  getAllVaultChainIds,
8
8
  OMNI_FACTORY_ADDRESS,
@@ -28,14 +28,14 @@ interface UseVaultTopologyReturn {
28
28
  }
29
29
 
30
30
  /**
31
- * Resolve the cross-chain topology of a vault from the current wallet chain.
31
+ * Resolve the cross-chain topology of a vault with automatic multi-chain discovery.
32
32
  *
33
- * Returns the hub chain, all spoke chains, and whether the user needs to
34
- * switch networks to interact with the hub.
35
- *
36
- * Since MoreVaults uses CREATE3, the vault address is the same on all chains.
33
+ * Uses `discoverVaultTopology` internally: if the wallet's current chain doesn't
34
+ * know the vault, it iterates all supported chains via public RPCs until the hub
35
+ * is found. Works even without a connected wallet.
37
36
  *
38
37
  * @example
38
+ * // Works regardless of which chain the wallet is on (or if disconnected)
39
39
  * const { topology, needsNetworkSwitch, allChainIds } = useVaultTopology('0xVAULT')
40
40
  *
41
41
  * if (needsNetworkSwitch) {
@@ -50,9 +50,14 @@ export function useVaultTopology(
50
50
  const publicClient = usePublicClient()
51
51
 
52
52
  const { data: topology, isLoading } = useQuery<VaultTopology>({
53
- queryKey: ['vaultTopology', vault, currentChainId, factoryAddress],
54
- queryFn: () => getVaultTopology(asSdkClient(publicClient), vault!, factoryAddress),
55
- enabled: !!vault && !!publicClient,
53
+ // Key does NOT include currentChainId — topology is chain-independent
54
+ queryKey: ['vaultTopology', vault, factoryAddress],
55
+ queryFn: () => discoverVaultTopology(
56
+ vault!,
57
+ publicClient ? asSdkClient(publicClient) : null,
58
+ factoryAddress,
59
+ ),
60
+ enabled: !!vault,
56
61
  staleTime: 5 * 60 * 1000, // topology rarely changes — 5 min cache
57
62
  })
58
63
 
package/src/viem/index.ts CHANGED
@@ -118,6 +118,7 @@ export type {
118
118
  export {
119
119
  getVaultTopology,
120
120
  getFullVaultTopology,
121
+ discoverVaultTopology,
121
122
  isOnHubChain,
122
123
  getAllVaultChainIds,
123
124
  OMNI_FACTORY_ADDRESS,
@@ -1,5 +1,6 @@
1
1
  import { type Address, type PublicClient, getAddress } from 'viem'
2
- import { EID_TO_CHAIN_ID, CHAIN_ID_TO_EID } from './chains'
2
+ import { EID_TO_CHAIN_ID, CHAIN_ID_TO_EID, CHAIN_IDS } from './chains'
3
+ import { createChainClient } from './spokeRoutes'
3
4
 
4
5
  // MoreVaults OMNI factory — same address on every supported chain (CREATE3)
5
6
  export const OMNI_FACTORY_ADDRESS: Address = '0x7bDB8B17604b03125eFAED33cA0c55FBf856BB0C'
@@ -170,6 +171,89 @@ export async function getFullVaultTopology(
170
171
  return topo
171
172
  }
172
173
 
174
+ /** All mainnet chain IDs where the OMNI_FACTORY is deployed */
175
+ const DISCOVERY_CHAIN_IDS = Object.values(CHAIN_IDS).filter(
176
+ id => id !== 545, // exclude testnet
177
+ ) as number[]
178
+
179
+ /**
180
+ * Discover a vault's topology across all supported chains.
181
+ *
182
+ * Unlike `getVaultTopology` (which queries a single chain), this function
183
+ * automatically iterates all supported chains when the initial query returns
184
+ * `role: "local"`. This handles the case where the caller doesn't know which
185
+ * chain the vault is deployed on, or when no wallet is connected.
186
+ *
187
+ * If a `publicClient` is provided, it's tried first. If that returns "local",
188
+ * every other supported chain is probed via `createChainClient` (public RPCs).
189
+ * If no `publicClient` is provided, all chains are probed.
190
+ *
191
+ * Once a hub is found, `getFullVaultTopology` is called to get the complete
192
+ * spoke list.
193
+ *
194
+ * @param vault Vault address (same on all chains via CREATE3)
195
+ * @param publicClient Optional — client for the "preferred" chain to try first
196
+ * @param factoryAddress MoreVaults factory (defaults to OMNI_FACTORY_ADDRESS)
197
+ *
198
+ * @example
199
+ * // No wallet connected — discovers that 0x8f74... is hub on Base
200
+ * const topo = await discoverVaultTopology('0x8f740...')
201
+ * // { role: 'hub', hubChainId: 8453, spokeChainIds: [1, 42161] }
202
+ */
203
+ export async function discoverVaultTopology(
204
+ vault: Address,
205
+ publicClient?: PublicClient | null,
206
+ factoryAddress: Address = OMNI_FACTORY_ADDRESS,
207
+ ): Promise<VaultTopology> {
208
+ const v = getAddress(vault)
209
+
210
+ // 1. Try the provided client first (fast path — avoids extra RPC calls)
211
+ let triedChainId: number | undefined
212
+ if (publicClient) {
213
+ try {
214
+ const topo = await getVaultTopology(publicClient, v, factoryAddress)
215
+ if (topo.role !== 'local') {
216
+ // Found hub or spoke — if spoke, resolve full topology from hub
217
+ if (topo.role === 'spoke') {
218
+ const hubClient = createChainClient(topo.hubChainId)
219
+ if (hubClient) {
220
+ try {
221
+ return await getFullVaultTopology(hubClient, v, factoryAddress)
222
+ } catch { /* fall through to return partial */ }
223
+ }
224
+ }
225
+ return topo
226
+ }
227
+ triedChainId = publicClient.chain?.id
228
+ } catch { /* client failed — continue with discovery */ }
229
+ }
230
+
231
+ // 2. Iterate all supported chains
232
+ for (const chainId of DISCOVERY_CHAIN_IDS) {
233
+ if (chainId === triedChainId) continue
234
+ const client = createChainClient(chainId)
235
+ if (!client) continue
236
+
237
+ try {
238
+ const topo = await getVaultTopology(client, v, factoryAddress)
239
+ if (topo.role === 'hub') return topo
240
+ if (topo.role === 'spoke') {
241
+ // Found spoke — get full topology from hub
242
+ const hubClient = createChainClient(topo.hubChainId)
243
+ if (hubClient) {
244
+ try {
245
+ return await getFullVaultTopology(hubClient, v, factoryAddress)
246
+ } catch { return topo }
247
+ }
248
+ return topo
249
+ }
250
+ } catch { /* this chain doesn't have the factory or vault — skip */ }
251
+ }
252
+
253
+ // 3. Not found on any chain — return local with chainId 0
254
+ return { role: 'local', hubChainId: 0, spokeChainIds: [] }
255
+ }
256
+
173
257
  /**
174
258
  * Check if a wallet is connected to the hub chain for a given vault.
175
259
  * Useful for showing a "Switch to Base" prompt before deposit.