diff --git a/packages/core/src/utils/extension.ts b/packages/core/src/utils/extension.ts index f6c341af..457a86a6 100644 --- a/packages/core/src/utils/extension.ts +++ b/packages/core/src/utils/extension.ts @@ -4,13 +4,17 @@ const objectTraverse = ( obj: T, keys: string[], ) => { - let cursor = obj; + try { + let cursor = obj; - for (const key of keys) { - cursor = cursor[key]; - } + for (const key of keys) { + cursor = cursor[key]; + } - return cursor; + return cursor; + } catch { + return undefined; + } }; export const getClientFromExtension = async ( @@ -22,9 +26,9 @@ export const getClientFromExtension = async ( const keys = Array.isArray(key) ? key : key.split('.'); const wallet = objectTraverse(window as never, keys) as T; - const latestKey = [...keys].pop(); + const firstKey = [...keys].shift(); - if (!latestKey) { + if (!firstKey) { throw Error(`Invalid key: ${JSON.stringify(key)}`); } @@ -32,7 +36,7 @@ export const getClientFromExtension = async ( return wallet; } - const clientNotExistError = createClientNotExistError(latestKey); + const clientNotExistError = createClientNotExistError(firstKey); if (document.readyState === 'complete') { if (wallet) { diff --git a/packages/store/src/slices/connect.ts b/packages/store/src/slices/connect.ts index cc132041..f9fcea2e 100644 --- a/packages/store/src/slices/connect.ts +++ b/packages/store/src/slices/connect.ts @@ -124,6 +124,8 @@ export const createConnectSlice: StateCreator< throw createInvalidWalletName(walletName); } + await wallet.init(); + set(() => ({ reconnectionStatus: ReconnectionStates.WAITING })); if (get().options.autoSuggestions) { diff --git a/packages/wallets/src/index.ts b/packages/wallets/src/index.ts index 07f1f1bf..3d92a4df 100644 --- a/packages/wallets/src/index.ts +++ b/packages/wallets/src/index.ts @@ -2,3 +2,4 @@ export * from './keplr'; export * from './leap'; export * from './cosmostation'; export * from './xdefi'; +export * from './station'; diff --git a/packages/wallets/src/station/extension.ts b/packages/wallets/src/station/extension.ts new file mode 100644 index 00000000..8824caf8 --- /dev/null +++ b/packages/wallets/src/station/extension.ts @@ -0,0 +1,167 @@ +import type { + OfflineAminoSigner, + StdSignDoc, + AminoSignResponse, + StdSignature, +} from '@cosmjs/amino'; +import type { + OfflineDirectSigner, + DirectSignResponse, +} from '@cosmjs/proto-signing'; +import type { + Key, + SignOptions, + SuggestChain, + SuggestToken, + WalletOptions, +} from '@quirks/core'; +import { ExtensionWallet, assertIsDefined } from '@quirks/core'; +import type { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; +import type { Keplr } from '@keplr-wallet/types'; +import Long from 'long'; +import { chainRegistryChainToKeplr } from '../keplr/utils'; + +export class StationWalletExtension 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(suggestions: SuggestToken[]): Promise { + assertIsDefined(this.client); + + for (const suggestion of suggestions) { + for (const token of suggestion.tokens) { + await this.client.suggestToken( + suggestion.chainId, + token.contractAddress, + token.viewingKey, + ); + } + } + } + + override async suggestChains(suggestions: SuggestChain[]): Promise { + assertIsDefined(this.client); + + for (const suggestion of suggestions) { + const suggestChain = chainRegistryChainToKeplr( + suggestion.chain, + suggestion.assetList ? [suggestion.assetList] : [], + ); + + await this.client.experimentalSuggestChain(suggestChain); + } + } +} diff --git a/packages/wallets/src/station/index.ts b/packages/wallets/src/station/index.ts new file mode 100644 index 00000000..66ff5a50 --- /dev/null +++ b/packages/wallets/src/station/index.ts @@ -0,0 +1,6 @@ +import { StationWalletExtension } from './extension'; +import { stationExtensionOptions } from './registry'; + +const stationExtension = new StationWalletExtension(stationExtensionOptions); + +export { stationExtension }; diff --git a/packages/wallets/src/station/registry.ts b/packages/wallets/src/station/registry.ts new file mode 100644 index 00000000..21d8e454 --- /dev/null +++ b/packages/wallets/src/station/registry.ts @@ -0,0 +1,38 @@ +import { WalletConnectionTypes, type WalletOptions } from '@quirks/core'; + +export const stationExtensionOptions: WalletOptions = { + name: 'station-extension', + prettyName: 'Station', + connectionType: WalletConnectionTypes.EXTENSION, + windowKey: 'station.keplr', + downloads: [ + { + device: 'desktop', + browser: 'chrome', + link: 'https://chrome.google.com/webstore/detail/station-wallet/aiifbnbfobpmeekipheeijimdpnlpgpp', + }, + { + device: 'desktop', + browser: 'firefox', + link: 'https://addons.mozilla.org/en-US/firefox/addon/terra-station-wallet/', + }, + { + device: 'desktop', + browser: 'edge', + link: 'https://microsoftedge.microsoft.com/addons/detail/station-wallet/ajkhoeiiokighlmdnlakpjfoobnjinie', + }, + ], + logoUrls: { + light: { + svg: 'https://assets.website-files.com/611153e7af981472d8da199c/62d8d4463816d7f427a7600b_01_Tarra_icon.svg', + png: 'https://assets.website-files.com/611153e7af981472d8da199c/62d8b6af5e23fd010628b529_01_Tarra_icon.png', + }, + dark: { + svg: 'https://assets.website-files.com/611153e7af981472d8da199c/62d8d4463816d7f427a7600b_01_Tarra_icon.svg', + png: 'https://assets.website-files.com/611153e7af981472d8da199c/62d8b6af5e23fd010628b529_01_Tarra_icon.png', + }, + }, + events: { + keystorechange: 'station_wallet_change', + }, +};