Skip to content

Commit

Permalink
feat: cosmos wallet strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
bangjelkoski committed Sep 25, 2022
1 parent e1ef0a8 commit 78d65e0
Show file tree
Hide file tree
Showing 7 changed files with 573 additions and 0 deletions.
90 changes: 90 additions & 0 deletions packages/wallet-ts/src/Cosmos/CosmosWalletStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { AccountAddress } from '@injectivelabs/ts-types'
import { DirectSignResponse } from '@cosmjs/proto-signing'
import { Msgs } from '@injectivelabs/sdk-ts'
import { GeneralException } from '@injectivelabs/exceptions'
import { Wallet } from '../wallet-strategy/types'
import Keplr from './strategies/Keplr'
import Leap from './strategies/Leap'
import Cosmostation from './strategies/Cosmostation'
import {
ConcreteCosmosWalletStrategy,
CosmosWalletStrategyArguments,
} from './types/strategy'

const createWallet = ({
wallet,
args,
}: {
wallet: Wallet
args: CosmosWalletStrategyArguments
}): ConcreteCosmosWalletStrategy | undefined => {
switch (wallet) {
case Wallet.Keplr:
return new Keplr({ ...args })
case Wallet.Leap:
return new Leap({ ...args })
case Wallet.Cosmostation:
return new Cosmostation({ ...args })
default:
throw new GeneralException(
new Error(`The ${wallet} concrete wallet strategy is not supported`),
)
}
}

const createWallets = (
args: CosmosWalletStrategyArguments,
): Record<Wallet, ConcreteCosmosWalletStrategy | undefined> =>
Object.values(Wallet).reduce(
(strategies, wallet) => ({
...strategies,
[wallet]: createWallet({ wallet, args }),
}),
{} as Record<Wallet, ConcreteCosmosWalletStrategy | undefined>,
)

export default class CosmosWalletStrategy {
public strategies: Record<Wallet, ConcreteCosmosWalletStrategy | undefined>

public wallet: Wallet

constructor(args: CosmosWalletStrategyArguments) {
this.strategies = createWallets(args)
this.wallet = args.wallet || Wallet.Keplr
}

public getWallet(): Wallet {
return this.wallet
}

public setWallet(wallet: Wallet) {
this.wallet = wallet
}

public getStrategy(): ConcreteCosmosWalletStrategy {
if (!this.strategies[this.wallet]) {
throw new GeneralException(
new Error(`Wallet ${this.wallet} is not enabled/available!`),
)
}

return this.strategies[this.wallet] as ConcreteCosmosWalletStrategy
}

public getAddresses(): Promise<AccountAddress[]> {
return this.getStrategy().getAddresses()
}

public async sendTransaction(tx: DirectSignResponse): Promise<string> {
return this.getStrategy().sendTransaction(tx)
}

public async signTransaction(data: {
address: string
memo: string
gas: string
message: Msgs | Msgs[]
}): Promise<string | DirectSignResponse> {
return this.getStrategy().signTransaction(data)
}
}
1 change: 1 addition & 0 deletions packages/wallet-ts/src/Cosmos/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './endpoints'
export { default as CosmosWalletStrategy } from './CosmosWalletStrategy'
190 changes: 190 additions & 0 deletions packages/wallet-ts/src/Cosmos/strategies/Cosmostation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/* eslint-disable class-methods-use-this */
import { CosmosChainId } from '@injectivelabs/ts-types'
import {
UnspecifiedErrorCode,
CosmosWalletException,
TransactionException,
ErrorType,
} from '@injectivelabs/exceptions'
import { DEFAULT_STD_FEE } from '@injectivelabs/utils'
import {
createTxRawFromSigResponse,
createTransactionAndCosmosSignDocForAddressAndMsg,
} from '@injectivelabs/sdk-ts'
import type { Msgs } from '@injectivelabs/sdk-ts'
import { cosmos, InstallError, Cosmos } from '@cosmostation/extension-client'
import { DirectSignResponse, makeSignDoc } from '@cosmjs/proto-signing'
import { SEND_TRANSACTION_MODE } from '@cosmostation/extension-client/cosmos'
import { ConcreteCosmosWalletStrategy } from '../types/strategy'
import { WalletAction } from '../../wallet-strategy/types/enums'
import { getEndpointsFromChainId } from '../endpoints'

const INJECTIVE_CHAIN_NAME = 'injective'

const getChainNameFromChainId = (chainId: CosmosChainId) => {
const [chainName] = chainId.split('-')

return chainName
}

export default class Cosmostation implements ConcreteCosmosWalletStrategy {
public chainName: string

public provider?: Cosmos

public chainId: CosmosChainId

constructor(args: { chainId: CosmosChainId }) {
this.chainId = args.chainId
this.chainName = getChainNameFromChainId(args.chainId)
}

async isChainIdSupported(chainId?: CosmosChainId): Promise<boolean> {
const actualChainId = chainId || this.chainId
const provider = await this.getProvider()

const supportedChainIds = await provider.getSupportedChainIds()

return !!supportedChainIds.official.find(
(chainId) => chainId === actualChainId,
)
}

async getAddresses(): Promise<string[]> {
const { chainName } = this
const provider = await this.getProvider()

try {
const accounts = await provider.requestAccount(chainName)

return [accounts.address]
} catch (e: unknown) {
if ((e as any).code === 4001) {
throw new CosmosWalletException(
new Error('The user rejected the request'),
{
code: UnspecifiedErrorCode,
type: ErrorType.WalletError,
contextModule: WalletAction.GetAccounts,
},
)
}

throw new CosmosWalletException(new Error((e as any).message), {
code: UnspecifiedErrorCode,
type: ErrorType.WalletError,
contextModule: WalletAction.GetAccounts,
})
}
}

async sendTransaction(signResponse: DirectSignResponse): Promise<string> {
const { chainName } = this
const provider = await this.getProvider()
const txRaw = createTxRawFromSigResponse(signResponse)

try {
const response = await provider.sendTransaction(
chainName,
txRaw.serializeBinary(),
SEND_TRANSACTION_MODE.ASYNC,
)

return response.tx_response.txhash
} catch (e: unknown) {
throw new TransactionException(new Error((e as any).message), {
code: UnspecifiedErrorCode,
type: ErrorType.ChainError,
contextModule: WalletAction.SendTransaction,
})
}
}

async signTransaction(transaction: {
memo: string
address: string
gas: string
message: Msgs | Msgs[]
}) {
const { chainName, chainId } = this
const provider = await this.getProvider()
const signer = await provider.getAccount(INJECTIVE_CHAIN_NAME)
const endpoints = getEndpointsFromChainId(chainId)

try {
/** Prepare the Transaction * */
const { bodyBytes, authInfoBytes, accountNumber } =
await createTransactionAndCosmosSignDocForAddressAndMsg({
chainId,
address: transaction.address,
memo: transaction.memo,
message: transaction.message,
pubKey: Buffer.from(signer.publicKey).toString('base64'),
endpoint: endpoints.rest,
fee: {
...DEFAULT_STD_FEE,
gas: transaction.gas || DEFAULT_STD_FEE.gas,
},
})

/* Sign the transaction */
const signDirectResponse = await provider.signDirect(
chainName,
{
chain_id: chainId,
body_bytes: bodyBytes,
auth_info_bytes: authInfoBytes,
account_number: accountNumber.toString(),
},
{ fee: false, memo: true },
)

return {
signed: makeSignDoc(
signDirectResponse.signed_doc.body_bytes,
signDirectResponse.signed_doc.auth_info_bytes,
signDirectResponse.signed_doc.chain_id,
parseInt(signDirectResponse.signed_doc.account_number, 10),
),
signature: {
signature: signDirectResponse.signature,
},
} as DirectSignResponse
} catch (e: unknown) {
throw new CosmosWalletException(new Error((e as any).message), {
code: UnspecifiedErrorCode,
type: ErrorType.WalletError,
contextModule: WalletAction.SendTransaction,
})
}
}

private async getProvider(): Promise<Cosmos> {
if (this.provider) {
return this.provider
}

try {
const provider = await cosmos()

this.provider = provider

return provider
} catch (e) {
if (e instanceof InstallError) {
throw new CosmosWalletException(
new Error('Please install the Cosmostation extension'),
{
code: UnspecifiedErrorCode,
type: ErrorType.WalletNotInstalledError,
},
)
}

throw new CosmosWalletException(new Error((e as any).message), {
code: UnspecifiedErrorCode,
type: ErrorType.WalletError,
})
}
}
}
Loading

0 comments on commit 78d65e0

Please sign in to comment.