diff --git a/examples/nextjs/components/provider.tsx b/examples/nextjs/components/provider.tsx index 9b77ce2d..477d8b13 100644 --- a/examples/nextjs/components/provider.tsx +++ b/examples/nextjs/components/provider.tsx @@ -1,14 +1,10 @@ 'use client'; -import { - bitsong, - bitsongAssetList, - osmosis, - osmosisAssetList, -} from '@nabla-studio/chain-registry'; +import { osmosis, osmosisAssetList } from '@nabla-studio/chain-registry'; import { QuirksConfig, QuirksNextProvider } from '@quirks/react'; import { type Config, ssrPersistOptions } from '@quirks/store'; import { + xdefiExtension, keplrExtension, leapExtension, cosmostationExtension, @@ -16,9 +12,14 @@ import { import { PropsWithChildren } from 'react'; const config: Config = { - wallets: [keplrExtension, leapExtension, cosmostationExtension], - chains: [osmosis, bitsong], - assetsLists: [osmosisAssetList, bitsongAssetList], + wallets: [ + keplrExtension, + leapExtension, + cosmostationExtension, + xdefiExtension, + ], + chains: [osmosis /* bitsong */], + assetsLists: [osmosisAssetList /* bitsongAssetList */], persistOptions: ssrPersistOptions, }; diff --git a/examples/nextjs/components/test.tsx b/examples/nextjs/components/test.tsx index 58372bd6..ec229f7f 100644 --- a/examples/nextjs/components/test.tsx +++ b/examples/nextjs/components/test.tsx @@ -1,11 +1,10 @@ 'use client'; +import { sign, getAddress, broadcast } from '@quirks/store'; import { useChains, useConnect } from '@quirks/react'; const send = async () => { const cosmos = (await import('osmojs')).cosmos; - const sign = (await import('@quirks/store')).sign; - const getAddress = (await import('@quirks/store')).getAddress; const { send } = cosmos.bank.v1beta1.MessageComposer.withTypeUrl; const address = getAddress('osmosis'); @@ -25,8 +24,6 @@ const send = async () => { const txRaw = await sign('osmosis', [msg]); - const broadcast = (await import('@quirks/store')).broadcast; - const res = await broadcast('osmosis', txRaw); console.log(res); diff --git a/packages/core/src/types/wallet.ts b/packages/core/src/types/wallet.ts index eded5c3b..8a464431 100644 --- a/packages/core/src/types/wallet.ts +++ b/packages/core/src/types/wallet.ts @@ -42,7 +42,7 @@ export interface WalletOptions { /** * Window extension key */ - windowKey?: string; + windowKey?: string | string[]; /** * Logo url */ diff --git a/packages/core/src/utils/extension.ts b/packages/core/src/utils/extension.ts index 57719553..f6c341af 100644 --- a/packages/core/src/utils/extension.ts +++ b/packages/core/src/utils/extension.ts @@ -1,19 +1,38 @@ import { createClientNotExistError } from './errors'; +const objectTraverse = ( + obj: T, + keys: string[], +) => { + let cursor = obj; + + for (const key of keys) { + cursor = cursor[key]; + } + + return cursor; +}; + export const getClientFromExtension = async ( - key: string, + key: string | string[], ): Promise => { if (typeof window === 'undefined') { return undefined; } - const wallet = (window as never)[key] as T; + const keys = Array.isArray(key) ? key : key.split('.'); + const wallet = objectTraverse(window as never, keys) as T; + const latestKey = [...keys].pop(); + + if (!latestKey) { + throw Error(`Invalid key: ${JSON.stringify(key)}`); + } if (wallet) { return wallet; } - const clientNotExistError = createClientNotExistError(key); + const clientNotExistError = createClientNotExistError(latestKey); if (document.readyState === 'complete') { if (wallet) { @@ -29,7 +48,7 @@ export const getClientFromExtension = async ( event.target && (event.target as Document).readyState === 'complete' ) { - const wallet = (window as never)[key] as T; + const wallet = objectTraverse(window as never, keys) as T; if (wallet) { resolve(wallet); diff --git a/packages/store/src/slices/connect.ts b/packages/store/src/slices/connect.ts index 47203eaf..cc132041 100644 --- a/packages/store/src/slices/connect.ts +++ b/packages/store/src/slices/connect.ts @@ -86,8 +86,6 @@ export const createConnectSlice: StateCreator< ), })); - console.log(chains); - return suggestChains(wallet.options.name, chains); } }, diff --git a/packages/wallets/src/index.ts b/packages/wallets/src/index.ts index b727364e..07f1f1bf 100644 --- a/packages/wallets/src/index.ts +++ b/packages/wallets/src/index.ts @@ -1,3 +1,4 @@ export * from './keplr'; export * from './leap'; export * from './cosmostation'; +export * from './xdefi'; diff --git a/packages/wallets/src/xdefi/extension.ts b/packages/wallets/src/xdefi/extension.ts new file mode 100644 index 00000000..92587dcc --- /dev/null +++ b/packages/wallets/src/xdefi/extension.ts @@ -0,0 +1,141 @@ +import type { + OfflineAminoSigner, + StdSignDoc, + AminoSignResponse, + StdSignature, +} from '@cosmjs/amino'; +import type { + OfflineDirectSigner, + DirectSignResponse, +} from '@cosmjs/proto-signing'; +import type { Key, SignOptions, WalletOptions } from '@quirks/core'; +import { ExtensionWallet, assertIsDefined } from '@quirks/core'; +import type { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; +import Long from 'long'; +import type { XDEFI } from './types'; + +export class XDEFIWalletExtension extends ExtensionWallet { + constructor(options: WalletOptions) { + super(options); + } + + override enable(chainIds: string[]): Promise { + assertIsDefined(this.client); + + return this.client.enable(chainIds); + } + + override disable(chainIds: string[]): Promise { + assertIsDefined(this.client); + + return this.client.disable(chainIds); + } + + override async getAccount(chainId: string) { + assertIsDefined(this.client); + + return await this.client.getKey(chainId); + } + + override async getAccounts(chainIds: string[]) { + assertIsDefined(this.client); + + const keys = await this.client.getKeysSettled(chainIds); + + return keys + .map((key) => { + if (key.status === 'fulfilled') { + return key.value; + } + + return undefined; + }) + .filter((key) => key !== undefined) as Key[]; + } + + override async getOfflineSigner( + chainId: string, + options?: SignOptions | undefined, + ): Promise { + assertIsDefined(this.client); + + return await this.client.getOfflineSigner(chainId, options); + } + + override async getOfflineSignerOnlyAmino( + chainId: string, + options?: SignOptions | undefined, + ): Promise { + assertIsDefined(this.client); + + return await this.client.getOfflineSignerOnlyAmino(chainId, options); + } + + override getOfflineSignerAuto( + chainId: string, + options?: SignOptions | undefined, + ): Promise { + assertIsDefined(this.client); + + return this.client.getOfflineSignerAuto(chainId, options); + } + + override signAmino( + chainId: string, + signer: string, + signDoc: StdSignDoc, + signOptions?: SignOptions | undefined, + ): Promise { + assertIsDefined(this.client); + + return this.client.signAmino(chainId, signer, signDoc, signOptions); + } + + override signDirect( + chainId: string, + signer: string, + signDoc: SignDoc, + signOptions?: SignOptions | undefined, + ): Promise { + assertIsDefined(this.client); + + return this.client.signDirect( + chainId, + signer, + { + ...signDoc, + accountNumber: Long.fromString(signDoc.accountNumber.toString(10)), + }, + signOptions, + ); + } + + override signArbitrary( + chainId: string, + signer: string, + data: string | Uint8Array, + ): Promise { + assertIsDefined(this.client); + + return this.client.signArbitrary(chainId, signer, data); + } + + override verifyArbitrary( + chainId: string, + signer: string, + data: string | Uint8Array, + signature: StdSignature, + ): Promise { + assertIsDefined(this.client); + + return this.client.verifyArbitrary(chainId, signer, data, signature); + } + + override async suggestTokens(): Promise { + console.warn("xDefi doesn't support suggestTokens"); + } + + override async suggestChains(): Promise { + console.warn("xDefi doesn't support suggestChains"); + } +} diff --git a/packages/wallets/src/xdefi/index.ts b/packages/wallets/src/xdefi/index.ts new file mode 100644 index 00000000..938d3757 --- /dev/null +++ b/packages/wallets/src/xdefi/index.ts @@ -0,0 +1,6 @@ +import { XDEFIWalletExtension } from './extension'; +import { xdefiExtensionOptions } from './registry'; + +const xdefiExtension = new XDEFIWalletExtension(xdefiExtensionOptions); + +export { xdefiExtension }; diff --git a/packages/wallets/src/xdefi/registry.ts b/packages/wallets/src/xdefi/registry.ts new file mode 100644 index 00000000..24f80932 --- /dev/null +++ b/packages/wallets/src/xdefi/registry.ts @@ -0,0 +1,24 @@ +import { WalletConnectionTypes, type WalletOptions } from '@quirks/core'; + +export const xdefiExtensionOptions: WalletOptions = { + name: 'xdefi-extension', + prettyName: 'XDEFI', + connectionType: WalletConnectionTypes.EXTENSION, + windowKey: 'xfi.keplr', + downloads: [ + { + link: 'https://chrome.google.com/webstore/detail/xdefi-wallet/hmeobnfnfcmdkdcmlblgagmfpfboieaf', + }, + ], + logoUrls: { + light: { + jpg: 'https://lh3.googleusercontent.com/6TkuRn_tZ2v5Bw4MZ2nTwJLEWU-76bAQFJhXunA7cbroI0izn7Mwi46Wvu3q5WfNUbQiPucQTCSTrb0FD_BCXuo3=w128-h128-e365-rj-sc0x00ffffff', + }, + dark: { + jpg: 'https://lh3.googleusercontent.com/6TkuRn_tZ2v5Bw4MZ2nTwJLEWU-76bAQFJhXunA7cbroI0izn7Mwi46Wvu3q5WfNUbQiPucQTCSTrb0FD_BCXuo3=w128-h128-e365-rj-sc0x00ffffff', + }, + }, + events: { + keystorechange: 'keplr_keystorechange', + }, +}; diff --git a/packages/wallets/src/xdefi/types.ts b/packages/wallets/src/xdefi/types.ts new file mode 100644 index 00000000..ad5e15aa --- /dev/null +++ b/packages/wallets/src/xdefi/types.ts @@ -0,0 +1,6 @@ +import type { Keplr } from '@keplr-wallet/types'; + +export interface XDEFI extends Keplr { + isXDEFI: boolean; + disconnect: () => Promise; +}