-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: route xcm-sdk chain connections through Talisman Wallet
- Loading branch information
Showing
10 changed files
with
213 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
apps/portal/src/components/widgets/xcm/api/utils/wrapChainApi.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { AnyChain, ChainRoutes, Parachain } from '@galacticcouncil/xcm-core' | ||
import { chainsByGenesisHashAtom } from '@talismn/balances-react' | ||
|
||
import { apiPromiseAtom } from '@/domains/common/atoms/apiPromiseAtom' | ||
import { jotaiStore } from '@/util/jotaiStore' | ||
|
||
/** | ||
* This function wraps the `chain.api` method from `@galacticcouncil/xcm-cfg` so that the chain connections | ||
* made via the `@galacticcouncil/xcm-sdk` library can share the one WsProvider with the balances library. | ||
* | ||
* Without this, two connections would need to be made to each chain rpc: one for the balances library, one for the XCM SDK. | ||
*/ | ||
export function overrideChainApis(chains: Map<string, AnyChain>): Map<string, AnyChain> { | ||
return new Map(chains.entries().map(([key, chain]) => [key, wrapChainApi(chain)])) | ||
} | ||
|
||
/** | ||
* This function wraps the `chain.api` method from `@galacticcouncil/xcm-cfg` so that the chain connections | ||
* made via the `@galacticcouncil/xcm-sdk` library can share the one WsProvider with the balances library. | ||
* | ||
* Without this, two connections would need to be made to each chain rpc: one for the balances library, one for the XCM SDK. | ||
*/ | ||
export function overrideRoutesChainApis(routes: Map<string, ChainRoutes>): Map<string, ChainRoutes> { | ||
return new Map( | ||
routes.entries().map(([key, route]) => { | ||
return [ | ||
key, | ||
// we need to use a Proxy, because `route.chain` is a getter (i.e. we can't assign to it directly) | ||
new Proxy(route, { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
get(target: any, prop) { | ||
if (prop !== 'chain') return target[prop] | ||
return wrapChainApi(target.chain) | ||
}, | ||
}), | ||
] | ||
}) | ||
) | ||
} | ||
|
||
/** | ||
* Returns the `chain` given to it, but with an override for the `chain.api` getter. | ||
* | ||
* The new `chain.api` getter will proxy all websocket requests through to the Talisman Wallet. | ||
* Also, the connection will be shared with any other Talisman Portal atoms/hooks which use `apiPromiseAtom`. | ||
*/ | ||
function wrapChainApi(chain: AnyChain): AnyChain { | ||
if (!(chain instanceof Parachain)) return chain | ||
|
||
const chainProxy = new Proxy(chain, { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
get(target: any, prop) { | ||
if (prop !== 'api') return target[prop] | ||
|
||
const getApi = async () => { | ||
const chaindataChainsByGenesisHash = await jotaiStore.get(chainsByGenesisHashAtom) | ||
const chaindataChain = chaindataChainsByGenesisHash?.[chain.genesisHash] | ||
if (!chaindataChain) { | ||
console.warn( | ||
`Unable to proxy ${chain.key} connection through Talisman Wallet shared interface [NO CHAINDATA CHAIN]` | ||
) | ||
return chain.api | ||
} | ||
|
||
const api = await jotaiStore.get(apiPromiseAtom(chaindataChain.id)) | ||
if (!api) { | ||
console.warn(`Unable to proxy ${chain.key} connection through Talisman Wallet shared interface [NO API]`) | ||
return chain.api | ||
} | ||
|
||
// we need to await api.isReady here, because the xcm-sdk library expects us to have done so before | ||
// we return the ApiPromise to it | ||
await api.isReady | ||
|
||
return api | ||
} | ||
|
||
// NOTE: Make sure to call getApi() here, | ||
// i.e. don't return the function - return the Promise which is returned by the function | ||
return getApi() | ||
}, | ||
}) | ||
|
||
return chainProxy | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { ApiPromise } from '@polkadot/api' | ||
import { chainConnectorsAtom } from '@talismn/balances-react' | ||
import * as AvailJsSdk from 'avail-js-sdk' | ||
import { atom } from 'jotai' | ||
import { atomEffect } from 'jotai-effect' | ||
import { atomFamily } from 'jotai/utils' | ||
|
||
/** | ||
* This atom can be used to get access to an `ApiPromise` for talking to a Polkadot blockchain. | ||
* | ||
* The advantage of using this atom over creating your own `ApiPromise`, is that the underlying websocket | ||
* connections will be shared between all code which uses this atom. | ||
* | ||
* Also, when the user has Talisman Wallet installed, the underlying websocket connections will be routed | ||
* through their wallet, thus further reducing the total number of active websocket connections. | ||
*/ | ||
export const apiPromiseAtom = atomFamily((chainId?: string) => | ||
atom(async get => { | ||
if (!chainId) return | ||
|
||
const subChainConnector = get(chainConnectorsAtom).substrate | ||
if (!subChainConnector) return | ||
|
||
const isAvail = ['avail', 'avail-turing-testnet'].includes(chainId) | ||
const extraProps = isAvail | ||
? { types: AvailJsSdk.spec.types, rpc: AvailJsSdk.spec.rpc, signedExtensions: AvailJsSdk.spec.signedExtensions } | ||
: {} | ||
|
||
const provider = subChainConnector.asProvider(chainId) | ||
const apiPromise = new ApiPromise({ provider, noInitWarn: true, ...extraProps }) | ||
|
||
// register effect to clean up ApiPromise when it's no longer in use | ||
get(cleanupApiPromiseEffect(chainId, apiPromise)) | ||
|
||
return apiPromise | ||
}) | ||
) | ||
|
||
const cleanupApiPromiseEffect = (chainId: string | undefined, apiPromise: ApiPromise) => | ||
atomEffect(() => { | ||
return () => { | ||
apiPromiseAtom.remove(chainId) | ||
try { | ||
apiPromise.disconnect() | ||
} catch (cause) { | ||
console.warn(`Failed to close ${chainId} apiPromise: ${cause}`) | ||
} | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { createStore, Provider } from 'jotai' | ||
import { ReactNode } from 'react' | ||
|
||
/** | ||
* Use this for access to the jotai store from outside of the react component lifecycle. | ||
* | ||
* For more information, see https://jotai.org/docs/guides/using-store-outside-react. | ||
*/ | ||
export const jotaiStore = createStore() | ||
|
||
export const JotaiProvider = ({ children }: { children?: ReactNode }) => ( | ||
<Provider store={jotaiStore}>{children}</Provider> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters