diff --git a/.env b/.env index 340fd2a12d9..7a14fd46601 100644 --- a/.env +++ b/.env @@ -156,10 +156,8 @@ VITE_ARBITRUM_NOVA_NODE_URL=https://api.arbitrum-nova.shapeshift.com/api/v1/json VITE_BASE_NODE_URL=https://api.base.shapeshift.com/api/v1/jsonrpc VITE_MONAD_NODE_URL=https://rpc.monad.xyz VITE_PLASMA_NODE_URL=https://rpc.plasma.to -# VITE_THORCHAIN_NODE_URL=https://api.thorchain.shapeshift.com/lcd -VITE_THORCHAIN_NODE_URL=https://thornode.ninerealms.com -# VITE_MAYACHAIN_NODE_URL=https://api.mayachain.shapeshift.com/lcd -VITE_MAYACHAIN_NODE_URL=https://mayanode.mayachain.info +VITE_THORCHAIN_NODE_URL=https://api.thorchain.shapeshift.com/lcd +VITE_MAYACHAIN_NODE_URL=https://api.mayachain.shapeshift.com/lcd VITE_SOLANA_NODE_URL=https://api.solana.shapeshift.com/api/v1/jsonrpc VITE_STARKNET_NODE_URL=https://rpc.starknet.lava.build VITE_TRON_NODE_URL=https://api.trongrid.io @@ -170,10 +168,8 @@ VITE_FASTNEAR_API_URL=https://api.fastnear.com VITE_ALCHEMY_POLYGON_URL=https://polygon-mainnet.g.alchemy.com/v2/anoTMcIc2hbPUxri37h4DeuUwg2p5_xZ # midgard -# VITE_THORCHAIN_MIDGARD_URL=https://api.thorchain.shapeshift.com/midgard/v2 -VITE_THORCHAIN_MIDGARD_URL=https://midgard.ninerealms.com/v2 -# VITE_MAYACHAIN_MIDGARD_URL=https://api.mayachain.shapeshift.com/midgard/v2 -VITE_MAYACHAIN_MIDGARD_URL=https://midgard.mayachain.info/v2 +VITE_THORCHAIN_MIDGARD_URL=https://api.thorchain.shapeshift.com/midgard/v2 +VITE_MAYACHAIN_MIDGARD_URL=https://api.mayachain.shapeshift.com/midgard/v2 # foxy apr VITE_TOKEMAK_STATS_URL=https://stats.tokemaklabs.com/ diff --git a/.env.development b/.env.development index 58de8c96161..0d89af1880c 100644 --- a/.env.development +++ b/.env.development @@ -64,20 +64,17 @@ VITE_BASE_NODE_URL=https://dev-api.base.shapeshift.com/api/v1/jsonrpc VITE_MONAD_NODE_URL=https://rpc.monad.xyz VITE_PLASMA_NODE_URL=https://rpc.plasma.to VITE_HYPEREVM_NODE_URL=https://rpc.hyperliquid.xyz/evm -# Swap me back to SS as-needed -# VITE_THORCHAIN_NODE_URL=https://dev-api.thorchain.shapeshift.com/lcd -VITE_THORCHAIN_NODE_URL=https://thornode.ninerealms.com -# VITE_MAYACHAIN_NODE_URL=https://dev-api.mayachain.shapeshift.com/lcd -VITE_MAYACHAIN_NODE_URL=https://mayanode.mayachain.info +# Swap me back to 9R as-needed +# VITE_THORCHAIN_NODE_URL=https://thornode.ninerealms.com +VITE_THORCHAIN_NODE_URL=https://dev-api.thorchain.shapeshift.com/lcd +VITE_MAYACHAIN_NODE_URL=https://dev-api.mayachain.shapeshift.com/lcd VITE_SOLANA_NODE_URL=https://dev-api.solana.shapeshift.com/api/v1/jsonrpc VITE_STARKNET_NODE_URL=https://rpc.starknet.lava.build VITE_TRON_NODE_URL=https://api.trongrid.io # midgard -# VITE_THORCHAIN_MIDGARD_URL=https://dev-api.thorchain.shapeshift.com/midgard/v2 -VITE_THORCHAIN_MIDGARD_URL=https://midgard.ninerealms.com/v2 -# VITE_MAYACHAIN_MIDGARD_URL=https://dev-api.mayachain.shapeshift.com/midgard/v2 -VITE_MAYACHAIN_MIDGARD_URL=https://midgard.mayachain.info/v2 +VITE_THORCHAIN_MIDGARD_URL=https://dev-api.thorchain.shapeshift.com/midgard/v2 +VITE_MAYACHAIN_MIDGARD_URL=https://dev-api.mayachain.shapeshift.com/midgard/v2 NODE_ENV=development diff --git a/packages/chain-adapters/src/cosmossdk/mayachain/MayachainChainAdapter.ts b/packages/chain-adapters/src/cosmossdk/mayachain/MayachainChainAdapter.ts index 45fa97dd5b4..c6b59c4e930 100644 --- a/packages/chain-adapters/src/cosmossdk/mayachain/MayachainChainAdapter.ts +++ b/packages/chain-adapters/src/cosmossdk/mayachain/MayachainChainAdapter.ts @@ -1,9 +1,5 @@ import type { AssetId } from '@shapeshiftoss/caip' -import { - ASSET_REFERENCE, - generateAssetIdFromCosmosSdkDenom, - mayachainAssetId, -} from '@shapeshiftoss/caip' +import { ASSET_REFERENCE, mayachainAssetId } from '@shapeshiftoss/caip' import type { HDWallet, MayachainSignTx, MayachainWallet } from '@shapeshiftoss/hdwallet-core' import { supportsMayachain } from '@shapeshiftoss/hdwallet-core' import type { RootBip44Params } from '@shapeshiftoss/types' @@ -12,8 +8,6 @@ import * as unchained from '@shapeshiftoss/unchained-client' import { ChainAdapterError, ErrorHandler } from '../../error/ErrorHandler' import type { - Account, - BroadcastTransactionInput, BuildDepositTxInput, BuildSendApiTxInput, BuildSendTxInput, @@ -40,7 +34,6 @@ const DEFAULT_CHAIN_ID = KnownChainIds.MayachainMainnet export interface ChainAdapterArgs extends BaseChainAdapterArgs { midgardUrl: string - nodeUrl: string } export class ChainAdapter extends CosmosSdkBaseAdapter { @@ -50,8 +43,6 @@ export class ChainAdapter extends CosmosSdkBaseAdapter> { - try { - const [authRes, balanceRes] = await Promise.all([ - fetch(`${this.nodeUrl}/cosmos/auth/v1beta1/accounts/${pubkey}`), - fetch(`${this.nodeUrl}/cosmos/bank/v1beta1/balances/${pubkey}`), - ]) - - if (!authRes.ok) { - throw new Error(`Failed to fetch account: ${authRes.status} ${authRes.statusText}`) - } - if (!balanceRes.ok) { - throw new Error(`Failed to fetch balances: ${balanceRes.status} ${balanceRes.statusText}`) - } - - const authData = (await authRes.json()) as { - account: { account_number: string; sequence: string; pub_key?: { key: string } } - } - const balanceData = (await balanceRes.json()) as { - balances: { denom: string; amount: string }[] - } - - const assets = balanceData.balances.reduce<{ amount: string; assetId: AssetId }[]>( - (acc, b) => { - if (b.denom === this.denom) return acc - try { - acc.push({ - amount: b.amount, - assetId: generateAssetIdFromCosmosSdkDenom(b.denom), - }) - } catch {} - return acc - }, - [], - ) - - const nativeBalance = balanceData.balances.find(b => b.denom === this.denom)?.amount ?? '0' - - return { - balance: nativeBalance, - pubkey, - chainId: this.chainId, - assetId: this.assetId, - chain: this.getType(), - chainSpecific: { - accountNumber: authData.account.account_number, - sequence: authData.account.sequence, - assets, - delegations: [], - redelegations: [], - undelegations: [], - rewards: [], - }, - } as Account - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.getAccount', - options: { pubkey }, - }) - } - } - - async broadcastTransaction({ - senderAddress, - receiverAddress, - hex, - }: BroadcastTransactionInput): Promise { - try { - await Promise.all([ - assertAddressNotSanctioned(senderAddress), - receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress), - ]) - - const response = await fetch(`${this.nodeUrl}/cosmos/tx/v1beta1/txs`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - tx_bytes: hex, - mode: 'BROADCAST_MODE_SYNC', - }), - }) - - const data = (await response.json()) as { - tx_response?: { code: number; txhash: string; raw_log?: string } - } - - if (data.tx_response?.code !== 0) { - throw new Error(data.tx_response?.raw_log || 'Broadcast failed') - } - - return data.tx_response.txhash - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.broadcastTransaction', - }) - } - } } diff --git a/packages/chain-adapters/src/cosmossdk/mayachain/SecondClassMayachainAdapter.ts b/packages/chain-adapters/src/cosmossdk/mayachain/SecondClassMayachainAdapter.ts deleted file mode 100644 index 6245cc811f5..00000000000 --- a/packages/chain-adapters/src/cosmossdk/mayachain/SecondClassMayachainAdapter.ts +++ /dev/null @@ -1,637 +0,0 @@ -import type { AssetId } from '@shapeshiftoss/caip' -import { - ASSET_REFERENCE, - fromChainId, - generateAssetIdFromCosmosSdkDenom, - mayachainAssetId, -} from '@shapeshiftoss/caip' -import type { HDWallet, MayachainSignTx, MayachainWallet } from '@shapeshiftoss/hdwallet-core' -import { supportsMayachain } from '@shapeshiftoss/hdwallet-core' -import type { Bip44Params, RootBip44Params } from '@shapeshiftoss/types' -import { KnownChainIds } from '@shapeshiftoss/types' -import { TransferType, TxStatus } from '@shapeshiftoss/unchained-client' -import { bech32 } from 'bech32' - -import type { ChainAdapter as IChainAdapter } from '../../api' -import { ChainAdapterError, ErrorHandler } from '../../error/ErrorHandler' -import type { - Account, - BroadcastTransactionInput, - BuildDepositTxInput, - BuildSendApiTxInput, - BuildSendTxInput, - FeeDataEstimate, - GetAddressInput, - GetBip44ParamsInput, - GetFeeDataInput, - SignAndBroadcastTransactionInput, - SignTxInput, - SubscribeError, - SubscribeTxsInput, - Transaction, - TxHistoryInput, - TxHistoryResponse, - ValidAddressResult, -} from '../../types' -import { ChainAdapterDisplayName, CONTRACT_INTERACTION, ValidAddressResultType } from '../../types' -import { toAddressNList, verifyLedgerAppOpen } from '../../utils' -import { bnOrZero } from '../../utils/bignumber' -import { assertAddressNotSanctioned } from '../../utils/validateAddress' -import type { - BuildTransactionInput, - CosmosSDKToken, - MayachainMsgDeposit, - MayachainMsgSend, -} from '../types' -import { MayachainMessageType } from '../types' - -const NATIVE_FEE = '2000000000' - -export type SecondClassMayachainAdapterArgs = { - nodeUrl: string - midgardUrl: string -} - -export class SecondClassMayachainAdapter implements IChainAdapter { - public static readonly rootBip44Params: RootBip44Params = { - purpose: 44, - coinType: Number(ASSET_REFERENCE.Mayachain), - accountNumber: 0, - } - - protected readonly nodeUrl: string - protected readonly midgardUrl: string - protected readonly assetId: AssetId = mayachainAssetId - protected readonly chainId = KnownChainIds.MayachainMainnet - protected readonly denom = 'cacao' - - constructor(args: SecondClassMayachainAdapterArgs) { - this.nodeUrl = args.nodeUrl - this.midgardUrl = args.midgardUrl - } - - private assertSupportsChain(wallet: HDWallet): asserts wallet is MayachainWallet { - if (!supportsMayachain(wallet)) { - throw new ChainAdapterError(`wallet does not support: ${this.getDisplayName()}`, { - translation: 'chainAdapters.errors.unsupportedChain', - options: { chain: this.getDisplayName() }, - }) - } - } - - getDisplayName() { - return ChainAdapterDisplayName.Mayachain - } - - getName() { - const enumIndex = Object.values(ChainAdapterDisplayName).indexOf( - ChainAdapterDisplayName.Mayachain, - ) - return Object.keys(ChainAdapterDisplayName)[enumIndex] - } - - getType(): KnownChainIds.MayachainMainnet { - return KnownChainIds.MayachainMainnet - } - - getChainId() { - return this.chainId - } - - getFeeAssetId(): AssetId { - return this.assetId - } - - getBip44Params({ accountNumber }: GetBip44ParamsInput): Bip44Params { - if (accountNumber < 0) throw new Error('accountNumber must be >= 0') - return { - ...SecondClassMayachainAdapter.rootBip44Params, - accountNumber, - isChange: false, - addressIndex: 0, - } - } - - async getAddress(input: GetAddressInput): Promise { - try { - const { wallet, accountNumber, pubKey, showOnDevice = false } = input - - if (pubKey) return pubKey - - if (!wallet) throw new Error('wallet is required') - this.assertSupportsChain(wallet) - await verifyLedgerAppOpen(this.chainId, wallet) - - const bip44Params = this.getBip44Params({ accountNumber }) - const address = await wallet.mayachainGetAddress({ - 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', - }) - } - } - - // eslint-disable-next-line require-await - async validateAddress(address: string): Promise { - const MAYACHAIN_PREFIX = 'maya' - - try { - const decoded = bech32.decode(address) - if (decoded.prefix !== MAYACHAIN_PREFIX) { - return { valid: false, result: ValidAddressResultType.Invalid } - } - - return { valid: true, result: ValidAddressResultType.Valid } - } catch { - return { valid: false, result: ValidAddressResultType.Invalid } - } - } - - async getAccount(pubkey: string): Promise> { - try { - const [authRes, balanceRes] = await Promise.all([ - fetch(`${this.nodeUrl}/cosmos/auth/v1beta1/accounts/${pubkey}`), - fetch(`${this.nodeUrl}/cosmos/bank/v1beta1/balances/${pubkey}`), - ]) - - if (!authRes.ok) { - throw new Error(`Failed to fetch account: ${authRes.status} ${authRes.statusText}`) - } - if (!balanceRes.ok) { - throw new Error(`Failed to fetch balances: ${balanceRes.status} ${balanceRes.statusText}`) - } - - const authData = (await authRes.json()) as { - account: { account_number: string; sequence: string; pub_key?: { key: string } } - } - const balanceData = (await balanceRes.json()) as { - balances: { denom: string; amount: string }[] - } - - const assets = balanceData.balances.reduce((acc, b) => { - if (b.denom === this.denom) return acc - try { - acc.push({ - amount: b.amount, - assetId: generateAssetIdFromCosmosSdkDenom(b.denom), - }) - } catch {} - return acc - }, []) - - const nativeBalance = balanceData.balances.find(b => b.denom === this.denom)?.amount ?? '0' - - return { - balance: nativeBalance, - pubkey, - chainId: this.chainId, - assetId: this.assetId, - chain: this.getType(), - chainSpecific: { - accountNumber: authData.account.account_number, - sequence: authData.account.sequence, - assets, - delegations: [], - redelegations: [], - undelegations: [], - rewards: [], - }, - } as Account - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.getAccount', - options: { pubkey }, - }) - } - } - - async broadcastTransaction({ - senderAddress, - receiverAddress, - hex, - }: BroadcastTransactionInput): Promise { - try { - await Promise.all([ - assertAddressNotSanctioned(senderAddress), - receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress), - ]) - - const response = await fetch(`${this.nodeUrl}/cosmos/tx/v1beta1/txs`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - tx_bytes: hex, - mode: 'BROADCAST_MODE_SYNC', - }), - }) - - const data = (await response.json()) as { - tx_response?: { code: number; txhash: string; raw_log?: string } - } - - if (data.tx_response?.code !== 0) { - throw new Error(data.tx_response?.raw_log || 'Broadcast failed') - } - - return data.tx_response.txhash - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.broadcastTransaction', - }) - } - } - - async signTransaction(signTxInput: SignTxInput): Promise { - try { - const { txToSign, wallet } = signTxInput - - if (!wallet) throw new Error('wallet is required') - this.assertSupportsChain(wallet) - await verifyLedgerAppOpen(this.chainId, wallet) - - const signedTx = await wallet.mayachainSignTx(txToSign) - - if (!signedTx?.serialized) throw new Error('error signing tx') - - return signedTx.serialized - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.signTransaction', - }) - } - } - - async signAndBroadcastTransaction({ - senderAddress, - receiverAddress, - signTxInput, - }: SignAndBroadcastTransactionInput): Promise { - try { - const { wallet } = signTxInput - - await Promise.all([ - assertAddressNotSanctioned(senderAddress), - receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress), - ]) - - if (!wallet) throw new Error('wallet is required') - 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', - }) - } - } - - protected getAmount({ - account, - value, - fee, - sendMax, - }: { - account: Account - value: string - fee: string - sendMax?: boolean - }) { - if (!sendMax) return value - - const availableBalance = bnOrZero(account.balance).minus(fee) - - if (!availableBalance.isFinite() || availableBalance.lte(0)) { - throw new Error(`not enough balance to send: ${availableBalance.toString()}`) - } - - return availableBalance.toString() - } - - protected buildTransaction(input: BuildTransactionInput): { - txToSign: MayachainSignTx - } { - const { account, accountNumber, msg, memo = '', chainSpecific } = input - const { gas, fee } = chainSpecific - - const bip44Params = this.getBip44Params({ accountNumber }) - - const unsignedTx = { - fee: { amount: [{ amount: bnOrZero(fee).toString(), denom: this.denom }], gas }, - msg: [msg], - signatures: [], - memo, - } - - const txToSign = { - addressNList: toAddressNList(bip44Params), - tx: unsignedTx, - chain_id: fromChainId(this.getType()).chainReference, - account_number: account.chainSpecific.accountNumber, - sequence: account.chainSpecific.sequence, - } as unknown as MayachainSignTx - - return { txToSign } - } - - async buildSendApiTransaction( - input: BuildSendApiTxInput, - ): Promise<{ txToSign: MayachainSignTx }> { - try { - const { sendMax, to, value, from } = input - - const account = await this.getAccount(from) - const amount = this.getAmount({ account, value, fee: NATIVE_FEE, sendMax }) - - const msg: MayachainMsgSend = { - type: MayachainMessageType.MsgSend, - value: { - amount: [{ amount, denom: this.denom }], - from_address: from, - to_address: to, - }, - } - - const tx = Object.assign(input, { - account, - msg, - chainSpecific: { ...input.chainSpecific, fee: '0' }, - }) - - return this.buildTransaction(tx) - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.buildTransaction', - }) - } - } - - async buildSendTransaction( - input: BuildSendTxInput, - ): Promise<{ txToSign: MayachainSignTx }> { - 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 buildDepositTransaction( - input: BuildDepositTxInput, - ): Promise<{ txToSign: MayachainSignTx }> { - try { - const { from, value, memo } = input - - const account = await this.getAccount(from) - - const msg: MayachainMsgDeposit = { - type: MayachainMessageType.MsgDeposit, - value: { - coins: [{ asset: 'MAYA.CACAO', amount: bnOrZero(value).toString() }], - memo, - signer: from, - }, - } - - const tx = Object.assign(input, { - account, - msg, - chainSpecific: { ...input.chainSpecific, fee: '0' }, - }) - - return this.buildTransaction(tx) - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.buildTransaction', - }) - } - } - - // eslint-disable-next-line require-await - async getFeeData( - _: Partial>, - ): Promise> { - return { - fast: { txFee: NATIVE_FEE, chainSpecific: { gasLimit: '500000' } }, - average: { txFee: NATIVE_FEE, chainSpecific: { gasLimit: '500000' } }, - slow: { txFee: NATIVE_FEE, chainSpecific: { gasLimit: '500000' } }, - } - } - - getTxHistory(_input: TxHistoryInput): Promise { - return Promise.resolve({ - cursor: '', - pubkey: _input.pubkey, - transactions: [], - txIds: [], - }) - } - - subscribeTxs( - _input: SubscribeTxsInput, - _onMessage: (msg: Transaction) => void, - _onError: (err: SubscribeError) => void, - ): Promise { - return Promise.resolve() - } - - unsubscribeTxs(_input?: SubscribeTxsInput): void { - return - } - - closeTxs(): void { - return - } - - async parseTx(txHash: unknown, pubkey: string): Promise { - const hash = txHash as string - - const getAssetIdFromDenom = (denom: string): AssetId => { - const normalizedDenom = denom.toLowerCase().replace('maya.', '') - if (normalizedDenom === this.denom) return this.assetId - try { - return generateAssetIdFromCosmosSdkDenom(normalizedDenom) - } catch { - return this.assetId - } - } - - const response = await fetch(`${this.nodeUrl}/cosmos/tx/v1beta1/txs/${hash}`) - - if (response.ok) { - const data = (await response.json()) as { - tx_response: { - txhash: string - height: string - code: number - timestamp: string - tx: { - body: { - messages: { - '@type': string - from_address?: string - to_address?: string - amount?: { denom: string; amount: string }[] - coins?: { asset: string; amount: string }[] - memo?: string - signer?: string - }[] - memo?: string - } - } - } - } - - const txResponse = data.tx_response - const messages = txResponse.tx.body.messages - const status = txResponse.code === 0 ? TxStatus.Confirmed : TxStatus.Failed - const blockHeight = parseInt(txResponse.height, 10) - const blockTime = Math.floor(new Date(txResponse.timestamp).getTime() / 1000) - - const transfers: Transaction['transfers'] = [] - - for (const msg of messages) { - if (msg['@type'] === '/types.MsgSend' && msg.from_address && msg.to_address && msg.amount) { - for (const coin of msg.amount) { - const isSend = msg.from_address.toLowerCase() === pubkey.toLowerCase() - const isReceive = msg.to_address.toLowerCase() === pubkey.toLowerCase() - - if (isSend || isReceive) { - transfers.push({ - assetId: getAssetIdFromDenom(coin.denom), - from: [msg.from_address], - to: [msg.to_address], - type: isSend ? TransferType.Send : TransferType.Receive, - value: coin.amount, - }) - } - } - } else if (msg['@type'] === '/types.MsgDeposit' && msg.signer && msg.coins) { - for (const coin of msg.coins) { - const isSigner = msg.signer.toLowerCase() === pubkey.toLowerCase() - if (isSigner) { - transfers.push({ - assetId: getAssetIdFromDenom(coin.asset), - from: [msg.signer], - to: [msg.signer], - type: TransferType.Send, - value: coin.amount, - }) - } - } - } - } - - return { - txid: txResponse.txhash, - blockHash: '', - blockHeight, - blockTime, - confirmations: 1, - status, - chainId: this.chainId, - pubkey, - transfers, - data: txResponse.tx.body.memo - ? { parser: 'mayachain' as const, memo: txResponse.tx.body.memo } - : undefined, - } - } - - // Cosmos endpoint doesn't work for some txs, fall back to midgard - const midgardResponse = await fetch(`${this.midgardUrl}/actions?txid=${hash}`) - - if (!midgardResponse.ok) { - throw new Error( - `Failed to fetch transaction: ${midgardResponse.status} ${midgardResponse.statusText}`, - ) - } - - const midgardData = (await midgardResponse.json()) as { - actions: { - date: string - height: string - status: string - type: string - in: { address: string; coins: { amount: string; asset: string }[]; txID: string }[] - out: { address: string; coins: { amount: string; asset: string }[]; txID: string }[] - metadata?: { send?: { memo?: string }; swap?: { memo?: string } } - }[] - } - - if (!midgardData.actions?.length) { - throw new Error(`Transaction not found: ${hash}`) - } - - const action = midgardData.actions[0] - const status = action.status === 'success' ? TxStatus.Confirmed : TxStatus.Failed - const blockHeight = parseInt(action.height, 10) - const blockTime = Math.floor(parseInt(action.date, 10) / 1_000_000_000) - - const transfers: Transaction['transfers'] = [] - - for (const input of action.in) { - for (const coin of input.coins) { - const isSend = input.address.toLowerCase() === pubkey.toLowerCase() - if (isSend) { - transfers.push({ - assetId: getAssetIdFromDenom(coin.asset), - from: [input.address], - to: [action.out[0]?.address ?? input.address], - type: TransferType.Send, - value: coin.amount, - }) - } - } - } - - for (const output of action.out) { - for (const coin of output.coins) { - const isReceive = output.address.toLowerCase() === pubkey.toLowerCase() - const alreadyAdded = transfers.some( - t => t.type === TransferType.Send && t.to.includes(output.address), - ) - if (isReceive && !alreadyAdded) { - transfers.push({ - assetId: getAssetIdFromDenom(coin.asset), - from: [action.in[0]?.address ?? output.address], - to: [output.address], - type: TransferType.Receive, - value: coin.amount, - }) - } - } - } - - const memo = action.metadata?.send?.memo ?? action.metadata?.swap?.memo - - return { - txid: hash, - blockHash: '', - blockHeight, - blockTime, - confirmations: 1, - status, - chainId: this.chainId, - pubkey, - transfers, - data: memo ? { parser: 'mayachain' as const, memo } : undefined, - } - } -} diff --git a/packages/chain-adapters/src/cosmossdk/mayachain/index.ts b/packages/chain-adapters/src/cosmossdk/mayachain/index.ts index f51d4f64712..222f0c85f09 100644 --- a/packages/chain-adapters/src/cosmossdk/mayachain/index.ts +++ b/packages/chain-adapters/src/cosmossdk/mayachain/index.ts @@ -1,2 +1 @@ export * from './MayachainChainAdapter' -export * from './SecondClassMayachainAdapter' diff --git a/packages/chain-adapters/src/cosmossdk/thorchain/SecondClassThorchainAdapter.ts b/packages/chain-adapters/src/cosmossdk/thorchain/SecondClassThorchainAdapter.ts deleted file mode 100644 index c6921bd600b..00000000000 --- a/packages/chain-adapters/src/cosmossdk/thorchain/SecondClassThorchainAdapter.ts +++ /dev/null @@ -1,664 +0,0 @@ -import type { AssetId } from '@shapeshiftoss/caip' -import { - ASSET_REFERENCE, - fromChainId, - generateAssetIdFromCosmosSdkDenom, - thorchainAssetId, -} from '@shapeshiftoss/caip' -import type { HDWallet, ThorchainSignTx, ThorchainWallet } from '@shapeshiftoss/hdwallet-core' -import { supportsThorchain } from '@shapeshiftoss/hdwallet-core' -import type { Bip44Params, RootBip44Params } from '@shapeshiftoss/types' -import { KnownChainIds } from '@shapeshiftoss/types' -import { TransferType, TxStatus } from '@shapeshiftoss/unchained-client' -import { bech32 } from 'bech32' - -import type { ChainAdapter as IChainAdapter } from '../../api' -import { ChainAdapterError, ErrorHandler } from '../../error/ErrorHandler' -import type { - Account, - BroadcastTransactionInput, - BuildDepositTxInput, - BuildSendApiTxInput, - BuildSendTxInput, - FeeDataEstimate, - GetAddressInput, - GetBip44ParamsInput, - GetFeeDataInput, - SignAndBroadcastTransactionInput, - SignTxInput, - SubscribeError, - SubscribeTxsInput, - Transaction, - TxHistoryInput, - TxHistoryResponse, - ValidAddressResult, -} from '../../types' -import { ChainAdapterDisplayName, CONTRACT_INTERACTION, ValidAddressResultType } from '../../types' -import { toAddressNList, verifyLedgerAppOpen } from '../../utils' -import { bnOrZero } from '../../utils/bignumber' -import { assertAddressNotSanctioned } from '../../utils/validateAddress' -import type { - BuildTransactionInput, - CosmosSDKToken, - ThorchainMsgDeposit, - ThorchainMsgSend, - ThorSupportedCoin, -} from '../types' -import { ThorchainMessageType } from '../types' - -const NATIVE_FEE = '2000000' - -const calculateFee = (fee: string): string => { - const feeMinusAutomaticOutboundFee = bnOrZero(fee).minus(NATIVE_FEE) - return feeMinusAutomaticOutboundFee.gt(0) ? feeMinusAutomaticOutboundFee.toString() : '0' -} - -export type SecondClassThorchainAdapterArgs = { - nodeUrl: string - thorMidgardUrl: string - mayaMidgardUrl: string -} - -export class SecondClassThorchainAdapter implements IChainAdapter { - public static readonly rootBip44Params: RootBip44Params = { - purpose: 44, - coinType: Number(ASSET_REFERENCE.Thorchain), - accountNumber: 0, - } - - protected readonly nodeUrl: string - protected readonly thorMidgardUrl: string - protected readonly assetId: AssetId = thorchainAssetId - protected readonly chainId = KnownChainIds.ThorchainMainnet - protected readonly denom = 'rune' - - constructor(args: SecondClassThorchainAdapterArgs) { - this.nodeUrl = args.nodeUrl - this.thorMidgardUrl = args.thorMidgardUrl - } - - private assertSupportsChain(wallet: HDWallet): asserts wallet is ThorchainWallet { - if (!supportsThorchain(wallet)) { - throw new ChainAdapterError(`wallet does not support: ${this.getDisplayName()}`, { - translation: 'chainAdapters.errors.unsupportedChain', - options: { chain: this.getDisplayName() }, - }) - } - } - - getDisplayName() { - return ChainAdapterDisplayName.Thorchain - } - - getName() { - const enumIndex = Object.values(ChainAdapterDisplayName).indexOf( - ChainAdapterDisplayName.Thorchain, - ) - return Object.keys(ChainAdapterDisplayName)[enumIndex] - } - - getType(): KnownChainIds.ThorchainMainnet { - return KnownChainIds.ThorchainMainnet - } - - getChainId() { - return this.chainId - } - - getFeeAssetId(): AssetId { - return this.assetId - } - - getBip44Params({ accountNumber }: GetBip44ParamsInput): Bip44Params { - if (accountNumber < 0) throw new Error('accountNumber must be >= 0') - return { - ...SecondClassThorchainAdapter.rootBip44Params, - accountNumber, - isChange: false, - addressIndex: 0, - } - } - - async getAddress(input: GetAddressInput): Promise { - try { - const { wallet, accountNumber, pubKey, showOnDevice = false } = input - - if (pubKey) return pubKey - - if (!wallet) throw new Error('wallet is required') - this.assertSupportsChain(wallet) - await verifyLedgerAppOpen(this.chainId, wallet) - - const bip44Params = this.getBip44Params({ accountNumber }) - const address = await wallet.thorchainGetAddress({ - 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', - }) - } - } - - // eslint-disable-next-line require-await - async validateAddress(address: string): Promise { - const THORCHAIN_PREFIX = 'thor' - - try { - const decoded = bech32.decode(address) - if (decoded.prefix !== THORCHAIN_PREFIX) { - return { valid: false, result: ValidAddressResultType.Invalid } - } - - const wordsLength = decoded.words.length - if (wordsLength !== 32) { - return { valid: false, result: ValidAddressResultType.Invalid } - } - return { valid: true, result: ValidAddressResultType.Valid } - } catch { - return { valid: false, result: ValidAddressResultType.Invalid } - } - } - - async getAccount(pubkey: string): Promise> { - try { - const [authRes, balanceRes] = await Promise.all([ - fetch(`${this.nodeUrl}/cosmos/auth/v1beta1/accounts/${pubkey}`), - fetch(`${this.nodeUrl}/cosmos/bank/v1beta1/balances/${pubkey}`), - ]) - - if (!authRes.ok) { - throw new Error(`Failed to fetch account: ${authRes.status} ${authRes.statusText}`) - } - if (!balanceRes.ok) { - throw new Error(`Failed to fetch balances: ${balanceRes.status} ${balanceRes.statusText}`) - } - - const authData = (await authRes.json()) as { - account: { account_number: string; sequence: string; pub_key?: { key: string } } - } - const balanceData = (await balanceRes.json()) as { - balances: { denom: string; amount: string }[] - } - - const assets = balanceData.balances.reduce((acc, b) => { - if (b.denom === this.denom) return acc - try { - acc.push({ - amount: b.amount, - assetId: generateAssetIdFromCosmosSdkDenom(b.denom), - }) - } catch {} - return acc - }, []) - - const nativeBalance = balanceData.balances.find(b => b.denom === this.denom)?.amount ?? '0' - - return { - balance: nativeBalance, - pubkey, - chainId: this.chainId, - assetId: this.assetId, - chain: this.getType(), - chainSpecific: { - accountNumber: authData.account.account_number, - sequence: authData.account.sequence, - assets, - delegations: [], - redelegations: [], - undelegations: [], - rewards: [], - }, - } as Account - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.getAccount', - options: { pubkey }, - }) - } - } - - async broadcastTransaction({ - senderAddress, - receiverAddress, - hex, - }: BroadcastTransactionInput): Promise { - try { - await Promise.all([ - assertAddressNotSanctioned(senderAddress), - receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress), - ]) - - const response = await fetch(`${this.nodeUrl}/cosmos/tx/v1beta1/txs`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - tx_bytes: hex, - mode: 'BROADCAST_MODE_SYNC', - }), - }) - - const data = (await response.json()) as { - tx_response?: { code: number; txhash: string; raw_log?: string } - } - - if (data.tx_response?.code !== 0) { - throw new Error(data.tx_response?.raw_log || 'Broadcast failed') - } - - return data.tx_response.txhash - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.broadcastTransaction', - }) - } - } - - async signTransaction(signTxInput: SignTxInput): Promise { - try { - const { txToSign, wallet } = signTxInput - - this.assertSupportsChain(wallet) - await verifyLedgerAppOpen(this.chainId, wallet) - - const signedTx = await wallet.thorchainSignTx(txToSign) - - if (!signedTx?.serialized) throw new Error('error signing tx') - - return signedTx.serialized - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.signTransaction', - }) - } - } - - async signAndBroadcastTransaction({ - senderAddress, - receiverAddress, - signTxInput, - }: SignAndBroadcastTransactionInput): Promise { - try { - 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', - }) - } - } - - protected getAmount({ - account, - value, - fee, - sendMax, - }: { - account: Account - value: string - fee: string - sendMax?: boolean - }) { - if (!sendMax) return value - - const availableBalance = bnOrZero(account.balance).minus(fee) - - if (!availableBalance.isFinite() || availableBalance.lte(0)) { - throw new Error(`not enough balance to send: ${availableBalance.toString()}`) - } - - return availableBalance.toString() - } - - protected buildTransaction(input: BuildTransactionInput): { - txToSign: ThorchainSignTx - } { - const { account, accountNumber, msg, memo = '', chainSpecific } = input - const { gas, fee } = chainSpecific - - const bip44Params = this.getBip44Params({ accountNumber }) - - const unsignedTx = { - fee: { amount: [{ amount: bnOrZero(fee).toString(), denom: this.denom }], gas }, - msg: [msg], - signatures: [], - memo, - } - - const txToSign = { - addressNList: toAddressNList(bip44Params), - tx: unsignedTx, - chain_id: fromChainId(this.getType()).chainReference, - account_number: account.chainSpecific.accountNumber, - sequence: account.chainSpecific.sequence, - } as unknown as ThorchainSignTx - - return { txToSign } - } - - async buildSendApiTransaction( - input: BuildSendApiTxInput, - ): Promise<{ txToSign: ThorchainSignTx }> { - try { - const { sendMax, to, value, from, chainSpecific } = input - const { coin = 'THOR.RUNE', fee } = chainSpecific - - if (coin !== 'THOR.RUNE' && coin !== 'THOR.TCY' && coin !== 'THOR.RUJI') - throw new Error('unsupported coin type') - - if (!fee) throw new Error('fee is required') - - const account = await this.getAccount(from) - const amount = coin === 'THOR.RUNE' ? this.getAmount({ account, value, fee, sendMax }) : value - - const denom = (() => { - if (coin === 'THOR.TCY') return 'tcy' - if (coin === 'THOR.RUJI') return 'x/ruji' - return this.denom - })() - - const msg: ThorchainMsgSend = { - type: ThorchainMessageType.MsgSend, - value: { - amount: [{ amount, denom }], - from_address: from, - to_address: to, - }, - } - - const tx = Object.assign(input, { - account, - msg, - chainSpecific: { ...input.chainSpecific, fee: calculateFee(fee) }, - }) - - return this.buildTransaction(tx) - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.buildTransaction', - }) - } - } - - async buildSendTransaction( - input: BuildSendTxInput, - ): Promise<{ txToSign: ThorchainSignTx }> { - 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 buildDepositTransaction( - input: BuildDepositTxInput, - ): Promise<{ txToSign: ThorchainSignTx }> { - try { - const { from, value, memo, chainSpecific } = input - const { fee, coin = 'THOR.RUNE' } = chainSpecific - - if (!['THOR.TCY', 'THOR.RUJI', 'THOR.RUNE'].includes(coin)) - throw new Error('unsupported coin type') - - if (!fee) throw new Error('fee is required') - - const account = await this.getAccount(from) - - const msg: ThorchainMsgDeposit = { - type: ThorchainMessageType.MsgDeposit, - value: { - coins: [{ asset: coin as ThorSupportedCoin, amount: bnOrZero(value).toString() }], - memo, - signer: from, - }, - } - - const tx = Object.assign(input, { - account, - msg, - chainSpecific: { ...input.chainSpecific, fee: calculateFee(fee) }, - }) - - return this.buildTransaction(tx) - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.buildTransaction', - }) - } - } - - // eslint-disable-next-line require-await - async getFeeData( - _: Partial>, - ): Promise> { - return { - fast: { txFee: NATIVE_FEE, chainSpecific: { gasLimit: '500000000' } }, - average: { txFee: NATIVE_FEE, chainSpecific: { gasLimit: '500000000' } }, - slow: { txFee: NATIVE_FEE, chainSpecific: { gasLimit: '500000000' } }, - } - } - - getTxHistory(_input: TxHistoryInput): Promise { - return Promise.resolve({ - cursor: '', - pubkey: _input.pubkey, - transactions: [], - txIds: [], - }) - } - - subscribeTxs( - _input: SubscribeTxsInput, - _onMessage: (msg: Transaction) => void, - _onError: (err: SubscribeError) => void, - ): Promise { - return Promise.resolve() - } - - unsubscribeTxs(_input?: SubscribeTxsInput): void { - return - } - - closeTxs(): void { - return - } - - async parseTx(txHash: unknown, pubkey: string): Promise { - const hash = txHash as string - - const getAssetIdFromDenom = (denom: string): AssetId => { - const normalizedDenom = denom.toLowerCase().replace('thor.', '') - if (normalizedDenom === this.denom) return this.assetId - try { - return generateAssetIdFromCosmosSdkDenom(normalizedDenom) - } catch { - return this.assetId - } - } - - const response = await fetch(`${this.nodeUrl}/cosmos/tx/v1beta1/txs/${hash}`) - - if (response.ok) { - const data = (await response.json()) as { - tx_response: { - txhash: string - height: string - code: number - timestamp: string - tx: { - body: { - messages: { - '@type': string - from_address?: string - to_address?: string - amount?: { denom: string; amount: string }[] - coins?: { asset: string; amount: string }[] - memo?: string - signer?: string - }[] - memo?: string - } - } - } - } - - const txResponse = data.tx_response - const messages = txResponse.tx.body.messages - const status = txResponse.code === 0 ? TxStatus.Confirmed : TxStatus.Failed - const blockHeight = parseInt(txResponse.height, 10) - const blockTime = Math.floor(new Date(txResponse.timestamp).getTime() / 1000) - - const transfers: Transaction['transfers'] = [] - - for (const msg of messages) { - if (msg['@type'] === '/types.MsgSend' && msg.from_address && msg.to_address && msg.amount) { - for (const coin of msg.amount) { - const isSend = msg.from_address.toLowerCase() === pubkey.toLowerCase() - const isReceive = msg.to_address.toLowerCase() === pubkey.toLowerCase() - - if (isSend || isReceive) { - transfers.push({ - assetId: getAssetIdFromDenom(coin.denom), - from: [msg.from_address], - to: [msg.to_address], - type: isSend ? TransferType.Send : TransferType.Receive, - value: coin.amount, - }) - } - } - } else if (msg['@type'] === '/types.MsgDeposit' && msg.signer && msg.coins) { - for (const coin of msg.coins) { - const isSigner = msg.signer.toLowerCase() === pubkey.toLowerCase() - if (isSigner) { - transfers.push({ - assetId: getAssetIdFromDenom(coin.asset), - from: [msg.signer], - to: [msg.signer], - type: TransferType.Send, - value: coin.amount, - }) - } - } - } - } - - return { - txid: txResponse.txhash, - blockHash: '', - blockHeight, - blockTime, - confirmations: 1, - status, - chainId: this.chainId, - pubkey, - transfers, - data: txResponse.tx.body.memo - ? { parser: 'thorchain' as const, memo: txResponse.tx.body.memo } - : undefined, - } - } - - // Cosmos endpoint doesn't work for some txs (e.g. TCY), fall back to midgard - const midgardResponse = await fetch(`${this.thorMidgardUrl}/actions?txid=${hash}`) - - if (!midgardResponse.ok) { - throw new Error( - `Failed to fetch transaction: ${midgardResponse.status} ${midgardResponse.statusText}`, - ) - } - - const midgardData = (await midgardResponse.json()) as { - actions: { - date: string - height: string - status: string - type: string - in: { address: string; coins: { amount: string; asset: string }[]; txID: string }[] - out: { address: string; coins: { amount: string; asset: string }[]; txID: string }[] - metadata?: { send?: { memo?: string }; swap?: { memo?: string } } - }[] - } - - if (!midgardData.actions?.length) { - throw new Error(`Transaction not found: ${hash}`) - } - - const action = midgardData.actions[0] - const status = action.status === 'success' ? TxStatus.Confirmed : TxStatus.Failed - const blockHeight = parseInt(action.height, 10) - const blockTime = Math.floor(parseInt(action.date, 10) / 1_000_000_000) - - const transfers: Transaction['transfers'] = [] - - for (const input of action.in) { - for (const coin of input.coins) { - const isSend = input.address.toLowerCase() === pubkey.toLowerCase() - if (isSend) { - transfers.push({ - assetId: getAssetIdFromDenom(coin.asset), - from: [input.address], - to: [action.out[0]?.address ?? input.address], - type: TransferType.Send, - value: coin.amount, - }) - } - } - } - - for (const output of action.out) { - for (const coin of output.coins) { - const isReceive = output.address.toLowerCase() === pubkey.toLowerCase() - const alreadyAdded = transfers.some( - t => t.type === TransferType.Send && t.to.includes(output.address), - ) - if (isReceive && !alreadyAdded) { - transfers.push({ - assetId: getAssetIdFromDenom(coin.asset), - from: [action.in[0]?.address ?? output.address], - to: [output.address], - type: TransferType.Receive, - value: coin.amount, - }) - } - } - } - - const memo = action.metadata?.send?.memo ?? action.metadata?.swap?.memo - - return { - txid: hash, - blockHash: '', - blockHeight, - blockTime, - confirmations: 1, - status, - chainId: this.chainId, - pubkey, - transfers, - data: memo ? { parser: 'thorchain' as const, memo } : undefined, - } - } -} diff --git a/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts b/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts index 427c67ab84e..11b547958f3 100644 --- a/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts +++ b/packages/chain-adapters/src/cosmossdk/thorchain/ThorchainChainAdapter.ts @@ -1,9 +1,5 @@ import type { AssetId } from '@shapeshiftoss/caip' -import { - ASSET_REFERENCE, - generateAssetIdFromCosmosSdkDenom, - thorchainAssetId, -} from '@shapeshiftoss/caip' +import { ASSET_REFERENCE, thorchainAssetId } from '@shapeshiftoss/caip' import type { HDWallet, ThorchainSignTx, ThorchainWallet } from '@shapeshiftoss/hdwallet-core' import { supportsThorchain } from '@shapeshiftoss/hdwallet-core' import type { RootBip44Params } from '@shapeshiftoss/types' @@ -14,8 +10,6 @@ import PQueue from 'p-queue' import { ChainAdapterError, ErrorHandler } from '../../error/ErrorHandler' import type { - Account, - BroadcastTransactionInput, BuildDepositTxInput, BuildSendApiTxInput, BuildSendTxInput, @@ -56,7 +50,6 @@ export interface ChainAdapterArgs extends BaseChainAdapterArgs { @@ -67,7 +60,6 @@ export class ChainAdapter extends CosmosSdkBaseAdapter> { - try { - const [authRes, balanceRes] = await Promise.all([ - fetch(`${this.nodeUrl}/cosmos/auth/v1beta1/accounts/${pubkey}`), - fetch(`${this.nodeUrl}/cosmos/bank/v1beta1/balances/${pubkey}`), - ]) - - if (!authRes.ok) { - throw new Error(`Failed to fetch account: ${authRes.status} ${authRes.statusText}`) - } - if (!balanceRes.ok) { - throw new Error(`Failed to fetch balances: ${balanceRes.status} ${balanceRes.statusText}`) - } - - const authData = (await authRes.json()) as { - account: { account_number: string; sequence: string; pub_key?: { key: string } } - } - const balanceData = (await balanceRes.json()) as { - balances: { denom: string; amount: string }[] - } - - const assets = balanceData.balances.reduce<{ amount: string; assetId: AssetId }[]>( - (acc, b) => { - if (b.denom === this.denom) return acc - try { - acc.push({ - amount: b.amount, - assetId: generateAssetIdFromCosmosSdkDenom(b.denom), - }) - } catch {} - return acc - }, - [], - ) - - const nativeBalance = balanceData.balances.find(b => b.denom === this.denom)?.amount ?? '0' - - return { - balance: nativeBalance, - pubkey, - chainId: this.chainId, - assetId: this.assetId, - chain: this.getType(), - chainSpecific: { - accountNumber: authData.account.account_number, - sequence: authData.account.sequence, - assets, - delegations: [], - redelegations: [], - undelegations: [], - rewards: [], - }, - } as Account - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.getAccount', - options: { pubkey }, - }) - } - } - - async broadcastTransaction({ - senderAddress, - receiverAddress, - hex, - }: BroadcastTransactionInput): Promise { - try { - await Promise.all([ - assertAddressNotSanctioned(senderAddress), - receiverAddress !== CONTRACT_INTERACTION && assertAddressNotSanctioned(receiverAddress), - ]) - - const response = await fetch(`${this.nodeUrl}/cosmos/tx/v1beta1/txs`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - tx_bytes: hex, - mode: 'BROADCAST_MODE_SYNC', - }), - }) - - const data = (await response.json()) as { - tx_response?: { code: number; txhash: string; raw_log?: string } - } - - if (data.tx_response?.code !== 0) { - throw new Error(data.tx_response?.raw_log || 'Broadcast failed') - } - - return data.tx_response.txhash - } catch (err) { - return ErrorHandler(err, { - translation: 'chainAdapters.errors.broadcastTransaction', - }) - } - } } diff --git a/packages/chain-adapters/src/cosmossdk/thorchain/index.ts b/packages/chain-adapters/src/cosmossdk/thorchain/index.ts index 05a9883f32e..6c16c2c70ce 100644 --- a/packages/chain-adapters/src/cosmossdk/thorchain/index.ts +++ b/packages/chain-adapters/src/cosmossdk/thorchain/index.ts @@ -1,2 +1 @@ export * from './ThorchainChainAdapter' -export * from './SecondClassThorchainAdapter' diff --git a/packages/swapper/src/swappers/MayachainSwapper/endpoints.ts b/packages/swapper/src/swappers/MayachainSwapper/endpoints.ts index 070d4590903..96408f8653c 100644 --- a/packages/swapper/src/swappers/MayachainSwapper/endpoints.ts +++ b/packages/swapper/src/swappers/MayachainSwapper/endpoints.ts @@ -90,7 +90,8 @@ export const mayachainApi: SwapperApi = { const { config } = input const nodeUrl = `${config.VITE_MAYACHAIN_NODE_URL}/mayachain` + const apiUrl = `${config.VITE_UNCHAINED_MAYACHAIN_HTTP_URL}/api/v1` - return checkTradeStatus({ ...input, nodeUrl, nativeChain: 'MAYA' }) + return checkTradeStatus({ ...input, nodeUrl, apiUrl, nativeChain: 'MAYA' }) }, } diff --git a/packages/swapper/src/swappers/ThorchainSwapper/endpoints.ts b/packages/swapper/src/swappers/ThorchainSwapper/endpoints.ts index d3f2026512a..ad20a1b0d4f 100644 --- a/packages/swapper/src/swappers/ThorchainSwapper/endpoints.ts +++ b/packages/swapper/src/swappers/ThorchainSwapper/endpoints.ts @@ -102,7 +102,8 @@ export const thorchainApi: SwapperApi = { const { config } = input const nodeUrl = `${config.VITE_THORCHAIN_NODE_URL}/thorchain` + const apiUrl = `${config.VITE_UNCHAINED_THORCHAIN_HTTP_URL}/api/v1` - return checkTradeStatus({ ...input, nodeUrl, nativeChain: 'THOR' }) + return checkTradeStatus({ ...input, nodeUrl, apiUrl, nativeChain: 'THOR' }) }, } diff --git a/packages/swapper/src/thorchain-utils/checkTradeStatus.ts b/packages/swapper/src/thorchain-utils/checkTradeStatus.ts index d88941f5667..27258f801f9 100644 --- a/packages/swapper/src/thorchain-utils/checkTradeStatus.ts +++ b/packages/swapper/src/thorchain-utils/checkTradeStatus.ts @@ -1,4 +1,5 @@ import { mayachainChainId, thorchainChainId } from '@shapeshiftoss/caip' +import type { cosmossdk } from '@shapeshiftoss/unchained-client' import { TxStatus } from '@shapeshiftoss/unchained-client' import axios from 'axios' @@ -10,6 +11,7 @@ import type { ThornodeStatusResponse, ThornodeTxResponse } from './types' type CheckTradeStatusInputExtended = CheckTradeStatusInput & { nodeUrl: string + apiUrl: string nativeChain: 'THOR' | 'MAYA' } @@ -20,6 +22,7 @@ export const checkTradeStatus = async ({ fetchIsSmartContractAddressQuery, assertGetEvmChainAdapter, nodeUrl, + apiUrl, nativeChain, }: CheckTradeStatusInputExtended): Promise => { try { @@ -45,21 +48,14 @@ export const checkTradeStatus = async ({ (chainId === thorchainChainId && nativeChain === 'THOR') || (chainId === mayachainChainId && nativeChain === 'MAYA') ) { - try { - const cosmosNodeUrl = nodeUrl.replace('/thorchain', '').replace('/mayachain', '') - const { data: txResponse } = await axios.get<{ - tx_response?: { code: number; raw_log?: string } - }>(`${cosmosNodeUrl}/cosmos/tx/v1beta1/txs/${txHash}`) - - if (txResponse.tx_response && txResponse.tx_response.code !== 0) { - return { - status: TxStatus.Failed, - buyTxHash: undefined, - message: txResponse.tx_response.raw_log, - } + const { data: tx } = await axios.get(`${apiUrl}/tx/${txHash}`) + + if (tx.events['0']?.error) { + return { + status: TxStatus.Failed, + buyTxHash: undefined, + message: tx.events[0].error.message, } - } catch { - // Cosmos endpoint may 404 for some txs (e.g. MsgDeposit), continue with thorchain status check } } diff --git a/src/constants/chains.ts b/src/constants/chains.ts index 763faab7439..9b95cc4c94e 100644 --- a/src/constants/chains.ts +++ b/src/constants/chains.ts @@ -13,8 +13,6 @@ export const SECOND_CLASS_CHAINS: readonly KnownChainIds[] = [ KnownChainIds.PlasmaMainnet, KnownChainIds.NearMainnet, KnownChainIds.StarknetMainnet, - KnownChainIds.ThorchainMainnet, - KnownChainIds.MayachainMainnet, ] // returns known ChainIds as an array, excluding the ones that are currently flagged off diff --git a/src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx b/src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx index 880aae5147a..c89e493bdf6 100644 --- a/src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx +++ b/src/hooks/useActionCenterSubscribers/useSendActionSubscriber.tsx @@ -12,13 +12,11 @@ import { getConfig } from '@/config' import { SECOND_CLASS_CHAINS } from '@/constants/chains' import { getChainAdapterManager } from '@/context/PluginProvider/chainAdapterSingleton' import { getHyperEvmTransactionStatus } from '@/lib/utils/hyperevm' -import { getMayachainTransactionStatus } from '@/lib/utils/mayachain' import { getMonadTransactionStatus } from '@/lib/utils/monad' import { getNearTransactionStatus } from '@/lib/utils/near' import { getPlasmaTransactionStatus } from '@/lib/utils/plasma' import { getStarknetTransactionStatus, isStarknetChainAdapter } from '@/lib/utils/starknet' import { getSuiTransactionStatus } from '@/lib/utils/sui' -import { getThorchainSendTransactionStatus } from '@/lib/utils/thorchain' import { getTronTransactionStatus } from '@/lib/utils/tron' import { actionSlice } from '@/state/slices/actionSlice/actionSlice' import { selectPendingWalletSendActions } from '@/state/slices/actionSlice/selectors' @@ -244,18 +242,6 @@ export const useSendActionSubscriber = () => { } break } - case KnownChainIds.ThorchainMainnet: { - const thorTxStatus = await getThorchainSendTransactionStatus(txHash) - isConfirmed = - thorTxStatus === TxStatus.Confirmed || thorTxStatus === TxStatus.Failed - break - } - case KnownChainIds.MayachainMainnet: { - const mayaTxStatus = await getMayachainTransactionStatus(txHash) - isConfirmed = - mayaTxStatus === TxStatus.Confirmed || mayaTxStatus === TxStatus.Failed - break - } default: console.error(`Unsupported second-class chain: ${chainId}`) return diff --git a/src/lib/utils/mayachain.ts b/src/lib/utils/mayachain.ts deleted file mode 100644 index 85d81d8684f..00000000000 --- a/src/lib/utils/mayachain.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { ChainId } from '@shapeshiftoss/caip' -import { mayachainChainId } from '@shapeshiftoss/caip' -import type { mayachain } from '@shapeshiftoss/chain-adapters' -import type { KnownChainIds } from '@shapeshiftoss/types' -import { TxStatus } from '@shapeshiftoss/unchained-client' - -import { getConfig } from '@/config' -import { getChainAdapterManager } from '@/context/PluginProvider/chainAdapterSingleton' - -export const isMayachainChainAdapter = ( - chainAdapter: unknown, -): chainAdapter is mayachain.SecondClassMayachainAdapter => { - if (!chainAdapter || typeof chainAdapter !== 'object') return false - const adapter = chainAdapter as mayachain.SecondClassMayachainAdapter - if (typeof adapter.getChainId !== 'function') return false - return adapter.getChainId() === mayachainChainId -} - -export const assertGetMayachainChainAdapter = ( - chainId: ChainId | KnownChainIds, -): mayachain.SecondClassMayachainAdapter => { - const chainAdapterManager = getChainAdapterManager() - const adapter = chainAdapterManager.get(chainId) - - if (!isMayachainChainAdapter(adapter)) { - throw Error('invalid chain adapter') - } - - return adapter -} - -export const getMayachainTransactionStatus = async (txHash: string): Promise => { - try { - const nodeUrl = getConfig().VITE_MAYACHAIN_NODE_URL - const response = await fetch(`${nodeUrl}/cosmos/tx/v1beta1/txs/${txHash}`) - - if (!response.ok) { - if (response.status === 404) return TxStatus.Pending - return TxStatus.Unknown - } - - const data = (await response.json()) as { - tx_response?: { code: number; txhash: string } - } - - if (!data.tx_response) return TxStatus.Unknown - - if (data.tx_response.code === 0) return TxStatus.Confirmed - return TxStatus.Failed - } catch (error) { - console.error('Error getting MAYAChain transaction status:', error) - return TxStatus.Unknown - } -} diff --git a/src/lib/utils/thorchain/index.ts b/src/lib/utils/thorchain/index.ts index c54ca4ce1fa..4becff58d45 100644 --- a/src/lib/utils/thorchain/index.ts +++ b/src/lib/utils/thorchain/index.ts @@ -38,40 +38,6 @@ import { poll } from '@/lib/poll/poll' import type { getThorchainLpPosition } from '@/pages/ThorChainLP/queries/queries' import { getThorchainSaversPosition } from '@/state/slices/opportunitiesSlice/resolvers/thorchainsavers/utils' -export const getThorchainSendTransactionStatus = async (txHash: string): Promise => { - try { - const nodeUrl = getConfig().VITE_THORCHAIN_NODE_URL - const response = await axios.get(`${nodeUrl}/cosmos/tx/v1beta1/txs/${txHash}`, { - validateStatus: () => true, - }) - - if (response.data?.tx_response) { - if (response.data.tx_response.code === 0) return TxStatus.Confirmed - return TxStatus.Failed - } - - // Cosmos endpoint doesn't work for MsgDeposit txs (RUNE/TCY/RUJI internal sends) - // Fall back to midgard - const midgardUrl = getConfig().VITE_THORCHAIN_MIDGARD_URL - const midgardResponse = await axios.get( - `${midgardUrl}/actions?txid=${txHash}`, - { validateStatus: () => true }, - ) - - if (midgardResponse.data?.actions?.length) { - const action = midgardResponse.data.actions[0] - if (action.status === 'success') return TxStatus.Confirmed - if (action.status === 'pending') return TxStatus.Pending - return TxStatus.Failed - } - - return TxStatus.Pending - } catch (error) { - console.error('Error getting THORChain send transaction status:', error) - return TxStatus.Unknown - } -} - export const getThorchainTransactionStatus = async ({ txHash, skipOutbound, diff --git a/src/plugins/mayachain/index.tsx b/src/plugins/mayachain/index.tsx index 265b05e0c1f..d8c22ca91b9 100644 --- a/src/plugins/mayachain/index.tsx +++ b/src/plugins/mayachain/index.tsx @@ -1,5 +1,6 @@ import { mayachain } from '@shapeshiftoss/chain-adapters' import { KnownChainIds } from '@shapeshiftoss/types' +import * as unchained from '@shapeshiftoss/unchained-client' import { getConfig } from '@/config' import type { Plugins } from '@/plugins/types' @@ -16,8 +17,19 @@ export default function register(): Plugins { [ KnownChainIds.MayachainMainnet, () => { - return new mayachain.SecondClassMayachainAdapter({ - nodeUrl: getConfig().VITE_MAYACHAIN_NODE_URL, + const http = new unchained.mayachain.V1Api( + new unchained.mayachain.Configuration({ + basePath: getConfig().VITE_UNCHAINED_MAYACHAIN_HTTP_URL, + }), + ) + + const ws = new unchained.ws.Client( + getConfig().VITE_UNCHAINED_MAYACHAIN_WS_URL, + ) + + return new mayachain.ChainAdapter({ + providers: { http, ws }, + coinName: 'Mayachain', midgardUrl: getConfig().VITE_MAYACHAIN_MIDGARD_URL, }) }, diff --git a/src/plugins/thorchain/index.tsx b/src/plugins/thorchain/index.tsx index 9a5285ea9c7..43d233b59cc 100644 --- a/src/plugins/thorchain/index.tsx +++ b/src/plugins/thorchain/index.tsx @@ -1,5 +1,6 @@ import { thorchain } from '@shapeshiftoss/chain-adapters' import { KnownChainIds } from '@shapeshiftoss/types' +import * as unchained from '@shapeshiftoss/unchained-client' import { getConfig } from '@/config' import type { Plugins } from '@/plugins/types' @@ -16,10 +17,28 @@ export default function register(): Plugins { [ KnownChainIds.ThorchainMainnet, () => { - return new thorchain.SecondClassThorchainAdapter({ - nodeUrl: getConfig().VITE_THORCHAIN_NODE_URL, + const http = new unchained.thorchain.V1Api( + new unchained.thorchain.Configuration({ + basePath: getConfig().VITE_UNCHAINED_THORCHAIN_HTTP_URL, + }), + ) + + const httpV1 = new unchained.thorchainV1.V1Api( + new unchained.thorchainV1.Configuration({ + basePath: getConfig().VITE_UNCHAINED_THORCHAIN_V1_HTTP_URL, + }), + ) + + const ws = new unchained.ws.Client( + getConfig().VITE_UNCHAINED_THORCHAIN_WS_URL, + ) + + return new thorchain.ChainAdapter({ + providers: { http, ws }, + coinName: 'Thorchain', thorMidgardUrl: getConfig().VITE_THORCHAIN_MIDGARD_URL, mayaMidgardUrl: getConfig().VITE_MAYACHAIN_MIDGARD_URL, + httpV1, }) }, ],