Skip to content

Commit

Permalink
feat: chain adapter errors (#8155)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaladinlight authored Nov 21, 2024
1 parent b8d37c6 commit a785032
Show file tree
Hide file tree
Showing 26 changed files with 761 additions and 552 deletions.
70 changes: 38 additions & 32 deletions packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const CHAIN_ID_TO_BECH32_VAL_PREFIX = {

export const assertIsValidatorAddress = (validator: string, chainId: CosmosSdkChainId) => {
if (CHAIN_ID_TO_BECH32_VAL_PREFIX[chainId] !== bech32.decode(validator).prefix) {
throw new Error(`CosmosSdkBaseAdapter: invalid validator address ${validator}`)
throw new Error(`invalid validator address: ${validator}`)
}
}

Expand Down Expand Up @@ -147,9 +147,7 @@ export abstract class CosmosSdkBaseAdapter<T extends CosmosSdkChainId> implement
}

getBIP44Params({ accountNumber }: GetBIP44ParamsInput): BIP44Params {
if (accountNumber < 0) {
throw new Error('accountNumber must be >= 0')
}
if (accountNumber < 0) throw new Error('accountNumber must be >= 0')
return { ...this.defaultBIP44Params, accountNumber }
}

Expand Down Expand Up @@ -224,13 +222,17 @@ export abstract class CosmosSdkBaseAdapter<T extends CosmosSdkChainId> implement
pubkey: account.pubkey,
} as Account<T>
} catch (err) {
return ErrorHandler(err)
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getAccount',
options: { pubkey },
})
}
}

async getTxHistory(input: TxHistoryInput): Promise<TxHistoryResponse> {
const requestQueue = input.requestQueue ?? new PQueue()
try {
const requestQueue = input.requestQueue ?? new PQueue()

const data = await requestQueue.add(() =>
this.providers.http.getTxHistory({
pubkey: input.pubkey,
Expand Down Expand Up @@ -283,9 +285,7 @@ export abstract class CosmosSdkBaseAdapter<T extends CosmosSdkChainId> implement
})().minus(fee)

if (!availableBalance.isFinite() || availableBalance.lte(0)) {
throw new Error(
`CosmosSdkBaseAdapter: not enough balance to send: ${availableBalance.toString()}`,
)
throw new Error(`not enough balance to send: ${availableBalance.toString()}`)
}

return availableBalance.toString()
Expand Down Expand Up @@ -322,26 +322,41 @@ export abstract class CosmosSdkBaseAdapter<T extends CosmosSdkChainId> implement
receiverAddress,
hex,
}: BroadcastTransactionInput): Promise<string> {
await Promise.all([
assertAddressNotSanctioned(senderAddress),
receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress),
])

try {
return this.providers.http.sendTx({ body: { rawTx: hex } })
await Promise.all([
assertAddressNotSanctioned(senderAddress),
receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress),
])

const txHash = await this.providers.http.sendTx({ body: { rawTx: hex } })

return txHash
} catch (err) {
return ErrorHandler(err)
if ((err as Error).name === 'ResponseError') {
const response = await ((err as any).response as Response).json()

const match = response.message.match(/description:\s*([^,]+)$/)

return ErrorHandler(JSON.stringify(response), {
translation: 'chainAdapters.errors.broadcastTransactionWithMessage',
options: { message: match && match[1] ? match[1].trim() : response.message },
})
}

return ErrorHandler(err, {
translation: 'chainAdapters.errors.broadcastTransaction',
})
}
}

// eslint-disable-next-line require-await
async validateAddress(address: string): Promise<ValidAddressResult> {
const chain = this.getType()
try {
const chain = this.getType()
const { prefix } = bech32.decode(address)

if (CHAIN_ID_TO_BECH32_ADDR_PREFIX[chain] !== prefix) {
throw new Error(`Invalid address ${address} for ChainId: ${chain}`)
throw new Error(`invalid address ${address} for ${this.getDisplayName()}`)
}

return {
Expand Down Expand Up @@ -386,25 +401,16 @@ export abstract class CosmosSdkBaseAdapter<T extends CosmosSdkChainId> implement
this.providers.ws.close('txs')
}

async getValidators(): Promise<Validator[]> {
if (this.providers.http instanceof unchained.thorchain.V1Api) return []

try {
const data = await this.providers.http.getValidators()
return data.validators.map<Validator>(validator => transformValidator(validator))
} catch (err) {
return ErrorHandler(err)
}
}

async getValidator(address: string): Promise<Validator | undefined> {
if (this.providers.http instanceof unchained.thorchain.V1Api) return

try {
if (this.providers.http instanceof unchained.thorchain.V1Api) return

const validator = await this.providers.http.getValidator({ pubkey: address })
return transformValidator(validator)
} catch (err) {
return ErrorHandler(err)
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getValidator',
})
}
}

Expand Down
160 changes: 97 additions & 63 deletions packages/chain-adapters/src/cosmossdk/cosmos/CosmosChainAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { AssetId } from '@shapeshiftoss/caip'
import { ASSET_REFERENCE, cosmosAssetId } from '@shapeshiftoss/caip'
import type { CosmosSignTx } from '@shapeshiftoss/hdwallet-core'
import type { CosmosSignTx, CosmosWallet, HDWallet } from '@shapeshiftoss/hdwallet-core'
import { supportsCosmos } from '@shapeshiftoss/hdwallet-core'
import type { BIP44Params } from '@shapeshiftoss/types'
import { KnownChainIds } from '@shapeshiftoss/types'
import * as unchained from '@shapeshiftoss/unchained-client'

import { ErrorHandler } from '../../error/ErrorHandler'
import { ChainAdapterError, ErrorHandler } from '../../error/ErrorHandler'
import type {
BuildClaimRewardsTxInput,
BuildDelegateTxInput,
Expand Down Expand Up @@ -71,6 +71,15 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn
this.api = args.providers.http
}

private assertSupportsChain(wallet: HDWallet): asserts wallet is CosmosWallet {
if (!supportsCosmos(wallet)) {
throw new ChainAdapterError(`wallet does not support: ${this.getDisplayName()}`, {
translation: 'chainAdapters.errors.unsupportedChain',
options: { chain: this.getDisplayName() },
})
}
}

getDisplayName() {
return ChainAdapterDisplayName.Cosmos
}
Expand All @@ -89,30 +98,27 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn
}

async getAddress(input: GetAddressInput): Promise<string> {
const { accountNumber, pubKey, wallet, showOnDevice = false } = input
try {
const { accountNumber, pubKey, wallet, showOnDevice = false } = input

if (pubKey) return pubKey
if (pubKey) return pubKey

try {
if (supportsCosmos(wallet)) {
await verifyLedgerAppOpen(this.chainId, wallet)

const bip44Params = this.getBIP44Params({ accountNumber })
const cosmosAddress = await wallet.cosmosGetAddress({
addressNList: toAddressNList(bip44Params),
showDisplay: showOnDevice,
})

if (!cosmosAddress) {
throw new Error('Unable to generate Cosmos address.')
}

return cosmosAddress
} else {
throw new Error('Wallet does not support Cosmos.')
}
} catch (error) {
return ErrorHandler(error)
this.assertSupportsChain(wallet)
await verifyLedgerAppOpen(this.chainId, wallet)

const bip44Params = this.getBIP44Params({ accountNumber })
const address = await wallet.cosmosGetAddress({
addressNList: toAddressNList(bip44Params),
showDisplay: showOnDevice,
})

if (!address) throw new Error('error getting address from wallet')

return address
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getAddress',
})
}
}

Expand All @@ -139,16 +145,27 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn

return this.buildTransaction({ ...input, account, msg })
} catch (err) {
return ErrorHandler(err)
return ErrorHandler(err, {
translation: 'chainAdapters.errors.buildTransaction',
})
}
}

async buildSendTransaction(
input: BuildSendTxInput<KnownChainIds.CosmosMainnet>,
): Promise<{ txToSign: CosmosSignTx }> {
const { accountNumber, wallet } = input
const from = await this.getAddress({ accountNumber, wallet })
return this.buildSendApiTransaction({ ...input, from })
try {
const { accountNumber, wallet } = input

const from = await this.getAddress({ accountNumber, wallet })
const tx = await this.buildSendApiTransaction({ ...input, from })

return tx
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.buildTransaction',
})
}
}

async buildDelegateTransaction(
Expand Down Expand Up @@ -178,7 +195,9 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn

return this.buildTransaction({ ...tx, account, msg })
} catch (err) {
return ErrorHandler(err)
return ErrorHandler(err, {
translation: 'chainAdapters.errors.buildTransaction',
})
}
}

Expand Down Expand Up @@ -209,7 +228,9 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn

return this.buildTransaction({ ...tx, account, msg })
} catch (err) {
return ErrorHandler(err)
return ErrorHandler(err, {
translation: 'chainAdapters.errors.buildTransaction',
})
}
}

Expand Down Expand Up @@ -243,7 +264,9 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn

return this.buildTransaction({ ...tx, account, msg })
} catch (err) {
return ErrorHandler(err)
return ErrorHandler(err, {
translation: 'chainAdapters.errors.buildTransaction',
})
}
}

Expand All @@ -268,40 +291,48 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn

return this.buildTransaction({ ...tx, account, msg })
} catch (err) {
return ErrorHandler(err)
return ErrorHandler(err, {
translation: 'chainAdapters.errors.buildTransaction',
})
}
}

async signTransaction(signTxInput: SignTxInput<CosmosSignTx>): Promise<string> {
try {
const { txToSign, wallet } = signTxInput
if (supportsCosmos(wallet)) {
await verifyLedgerAppOpen(this.chainId, wallet)

const signedTx = await wallet.cosmosSignTx(txToSign)
this.assertSupportsChain(wallet)
await verifyLedgerAppOpen(this.chainId, wallet)

if (!signedTx?.serialized) throw new Error('Error signing tx')
const signedTx = await wallet.cosmosSignTx(txToSign)

return signedTx.serialized
} else {
throw new Error('Wallet does not support Cosmos.')
}
if (!signedTx?.serialized) throw new Error('error signing tx')

return signedTx.serialized
} catch (err) {
return ErrorHandler(err)
return ErrorHandler(err, {
translation: 'chainAdapters.errors.signTransaction',
})
}
}

async getFeeData(
_: Partial<GetFeeDataInput<KnownChainIds.CosmosMainnet>>,
): Promise<FeeDataEstimate<KnownChainIds.CosmosMainnet>> {
const gasLimit = '2000000'
const fees = await this.api.fees()
const txFee = bnOrZero(fees[Denoms.uatom]).times(gasLimit).toFixed(0)

return {
fast: { txFee, chainSpecific: { gasLimit } },
average: { txFee, chainSpecific: { gasLimit } },
slow: { txFee, chainSpecific: { gasLimit } },
try {
const gasLimit = '2000000'
const fees = await this.api.fees()
const txFee = bnOrZero(fees[Denoms.uatom]).times(gasLimit).toFixed(0)

return {
fast: { txFee, chainSpecific: { gasLimit } },
average: { txFee, chainSpecific: { gasLimit } },
slow: { txFee, chainSpecific: { gasLimit } },
}
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.getFeeData',
})
}
}

Expand All @@ -310,21 +341,24 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn
receiverAddress,
signTxInput,
}: SignAndBroadcastTransactionInput<KnownChainIds.CosmosMainnet>): Promise<string> {
await Promise.all([
assertAddressNotSanctioned(senderAddress),
receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress),
])

const { wallet } = signTxInput
try {
if (supportsCosmos(wallet)) {
const signedTx = await this.signTransaction(signTxInput)
return this.providers.http.sendTx({ body: { rawTx: signedTx } })
} else {
throw new Error('Wallet does not support Cosmos.')
}
} catch (error) {
return ErrorHandler(error)
const { wallet } = signTxInput

await Promise.all([
assertAddressNotSanctioned(senderAddress),
receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress),
])

this.assertSupportsChain(wallet)

const hex = await this.signTransaction(signTxInput)
const txHash = await this.broadcastTransaction({ senderAddress, receiverAddress, hex })

return txHash
} catch (err) {
return ErrorHandler(err, {
translation: 'chainAdapters.errors.signAndBroadcastTransaction',
})
}
}
}
Loading

0 comments on commit a785032

Please sign in to comment.