import { assertUnreachable , COIN_DENOMS , ICosmosTransaction , ICosmosTransactionEvent , ILogMessage , IMsgBeginRedelegate , IMsgDelegate , IMsgModifyWithdrawAddress , IMsgSend , IMsgSubmitProposal , IMsgVote , IMsgWithdrawDelegationReward , IMsgWithdrawValidatorCommission , } from "@anthem/utils" ; import * as Sentry from "@sentry/browser" ; import { formatAddressString } from "./client-utils" ; /** ===========================================================================
 * Types & Config
 * ============================================================================
 */ export enum COSMOS_TRANSACTION_TYPES { SEND = "cosmos-sdk/MsgSend" , RECEIVE = "custom-receive-transaction-type" , VOTE = "cosmos-sdk/MsgVote" , DELEGATE = "cosmos-sdk/MsgDelegate" , UNDELEGATE = "cosmos-sdk/MsgUndelegate" , SUBMIT_PROPOSAL = "cosmos-sdk/MsgSubmitProposal" , BEGIN_REDELEGATE = "cosmos-sdk/MsgBeginRedelegate" , CLAIM_REWARDS = "cosmos-sdk/MsgWithdrawDelegationReward" , CLAIM_COMMISSION = "cosmos-sdk/MsgWithdrawValidatorCommission" , CREATE_VALIDATOR = "cosmos-sdk/MsgCreateValidator" , EDIT_VALIDATOR = "cosmos-sdk/MsgEditValidator" , MODIFY_WITHDRAW_ADDRESS = "cosmos-sdk/MsgModifyWithdrawAddress" } export enum TERRA_TRANSACTION_TYPES { SEND = "bank/MsgSend" , MULTI_SEND = "bank/MsgMultiSend" , RECEIVE = "custom-receive-transaction-type" , DELEGATE = "staking/MsgDelegate" , UNDELEGATE = "staking/MsgUndelegate" , REDELEGATE = "staking/MsgBeginRedelegate" , GOVERNANCE_DEPOSIT = "gov/MsgDeposit" , SUBMIT_PROPOSAL = "gov/MsgSubmitProposal" , DELEGATE_FEED_CONSENT = "oracle/MsgDelegateFeedConsent" , WITHDRAW_REWARD = "distribution/MsgWithdrawDelegationReward" , WITHDRAW_COMMISSION = "distribution/MsgWithdrawValidatorCommission" , EXCHANGE_RATE_VOTE = "oracle/MsgExchangeRateVote" , EXCHANGE_RATE_PRE_VOTE = "oracle/MsgExchangeRatePrevote" , MODIFY_WITHDRAW_ADDRESS = "distribution/MsgModifyWithdrawAddress" } /**
 * Determine if a Cosmos SDK transaction is a rewards or commissions claim
 * transaction.
 */ export const isClaimTransaction = ( type ) : boolean => { return ( type === COSMOS_TRANSACTION_TYPES . CLAIM_REWARDS || type === COSMOS_TRANSACTION_TYPES . CLAIM_COMMISSION || type === TERRA_TRANSACTION_TYPES . WITHDRAW_REWARD || type === TERRA_TRANSACTION_TYPES . WITHDRAW_COMMISSION ) ; } ; export enum TRANSACTION_STAGES { "SETUP" = "SETUP" , "SIGN" = "SIGN" , "CONFIRM" = "CONFIRM" , "PENDING" = "PENDING" , "SUCCESS" = "SUCCESS" , "SIGN_ON_LEDGER" = "SIGN_ON_LEDGER" } export interface CosmosBalance { amount : string ; denom : string ; } export type CosmosTransactionFee = Nullable < CosmosBalance > ; export interface RewardsCommissionsData { amount : string ; denom : string ; } export interface TransactionItemData { type : COSMOS_TRANSACTION_TYPES ; timestamp : string ; amount : Nullable < CosmosBalance > ; fee : CosmosTransactionFee ; toAddress : string ; fromAddress : string ; rewardsCommissionsData ? : RewardsCommissionsData [ ] ; } export interface GovernanceVoteMessageData { fee : CosmosTransactionFee ; option : string ; proposal_id : string ; timestamp : string ; type : COSMOS_TRANSACTION_TYPES . VOTE ; } export interface GovernanceSubmitProposalMessageData { fee : CosmosTransactionFee ; title : string ; deposit : Nullable < CosmosBalance > ; proposer : string ; timestamp : string ; description : string ; type : COSMOS_TRANSACTION_TYPES . SUBMIT_PROPOSAL ; } export interface ValidatorCreateOrEditMessageData { fee : CosmosTransactionFee ; type : COSMOS_TRANSACTION_TYPES ; timestamp : string ; delegatorAddress : string ; validatorAddress : string ; } export interface ValidatorModifyWithdrawAddressMessageData { fee : CosmosTransactionFee ; type : COSMOS_TRANSACTION_TYPES | TERRA_TRANSACTION_TYPES ; timestamp : string ; withdrawAddress : string ; validatorAddress : string | null ; } export type CosmosTransactionItemData = TransactionItemData | GovernanceVoteMessageData | GovernanceSubmitProposalMessageData | ValidatorCreateOrEditMessageData | ValidatorModifyWithdrawAddressMessageData ; /** ===========================================================================
 * Utils
 * ============================================================================
 */ const getFailedLogs = ( logs ) => { return logs . filter ( log => log && ! log . success ) ; } ; export const getTransactionFailedLogMessage = ( transaction ) : Nullable < string > => { try { const { log } = transaction ; if ( Array . isArray ( log ) ) { const failedLogs = getFailedLogs ( log ) ; const firstLog = failedLogs [ 0 ] ; if ( firstLog ) { const maybeLogMessage = JSON . parse ( firstLog . message || "" ) ; const { message } = maybeLogMessage ; return message || null ; } else { return "" ; } } } catch ( err ) { } return null ; } ; const getTxAmount = ( transaction , msgIndex ) : Nullable < CosmosBalance > => { const txMsg = transaction . msgs [ msgIndex ] . value as IMsgSend ; if ( txMsg && txMsg . amounts ) { return txMsg . amounts [ 0 ] ; } else if ( txMsg && txMsg . amount ) { txMsg . amount . amount . replace ( "," , "" ) ; return txMsg . amount ; } else { return null ; } } ; export const getTxFee = ( transaction ) : CosmosTransactionFee => { const { fees } = transaction ; const { amount } = fees ; if ( amount ) { return amount [ 0 ] ; } else { return null ; } } ; const getTransactionSendMessage = ( transaction , address , msgIndex ) : TransactionItemData => { const { from_address , to_address } = transaction . msgs [ msgIndex ] . value as IMsgSend ; const fee = getTxFee ( transaction ) ; const amount = getTxAmount ( transaction , msgIndex ) ; const toAddress = to_address || "" ; const fromAddress = from_address || "" ; const IS_SEND = fromAddress === address ; return { fee , amount , toAddress , fromAddress , type : IS_SEND ? ( transaction . msgs [ 0 ] . type as COSMOS_TRANSACTION_TYPES ) : COSMOS_TRANSACTION_TYPES . RECEIVE , timestamp : transaction . timestamp , } ; } ; const getDelegationTransactionMessage = ( transaction , msgIndex ) : TransactionItemData => { const msg = transaction . msgs [ msgIndex ] ; const fee = getTxFee ( transaction ) ; const vote = msg . value as IMsgDelegate ; const { amount , delegator_address , validator_address } = vote ; const delegationAmount = amount ? amount : null ; const toAddress = validator_address || "" ; const fromAddress = delegator_address || "" ; return { fee , toAddress , fromAddress , amount : delegationAmount , timestamp : transaction . timestamp , type : COSMOS_TRANSACTION_TYPES . DELEGATE , } ; } ; /**
 * Search through the transaction events to extract the rewards and
 * commissions withdraw amounts.
 *
 * NOTE: This only applies to transactions which have been updated
 * to store events data (hipparchus upgrade) - this does not apply to
 * Cosmos transactions (at the time of this writing).
 */ const getClaimDataFromEvents = ( events , type ) : RewardsCommissionsData [ ] => { let values = [ ] ; const results = [ ] ; if ( type === "rewards" ) { const rewardsEvent = events . find ( x => x . type === "withdraw_rewards" ) ; if ( rewardsEvent ) { const rewards = rewardsEvent . attributes . find ( x => x . key === "amount" ) ; if ( rewards && rewards . value ) { values = rewards . value . split ( "," ) ; } } } else { const commissionsEvent = events . find ( x => x . type === "withdraw_commission" ) ; if ( commissionsEvent ) { const commissions = commissionsEvent . attributes . find ( x => x . key === "amount" ) ; if ( commissions && commissions . value ) { values = commissions . value . split ( "," ) ; } } } for ( const value of values ) { const match = value . match ( /\d+/g ) ; if ( match ) { const numbers = match [ 0 ] ; const denomIndex = numbers . length ; const denom = value . slice ( denomIndex ) ; results . push ( { amount : numbers , denom , } ) ; } } return results ; } ; const getClaimRewardsMessageData = ( transaction , msgIndex , denom ) : TransactionItemData => { const { tags , events } = transaction ; const msg = transaction . msgs [ msgIndex ] ; const { type , value } = msg ; const { validator_address , delegator_address , } = value as IMsgWithdrawDelegationReward ; let rewards = null ; if ( tags ) { const rewardTags = tags . filter ( tag => { return tag . key === "rewards" ; } ) ; const rewardTag = rewardTags [ msgIndex ] ; if ( rewardTag && rewardTag . value ) { rewards = rewardTag . value . replace ( denom , "" ) ; rewards = rewards . replace ( /[^\d]/g , "" ) ; } } const fee = getTxFee ( transaction ) ; const validatorAddress = validator_address || "" ; const delegatorAddress = delegator_address || "" ; let claimData = [ ] ; if ( events ) { claimData = getClaimDataFromEvents ( events , "rewards" ) ; } return { fee , rewardsCommissionsData : claimData , amount : { amount : rewards || "0" , denom } , toAddress : delegatorAddress , fromAddress : validatorAddress , timestamp : transaction . timestamp , type : type as COSMOS_TRANSACTION_TYPES , } ; } ; const getValidatorClaimRewardsMessageData = ( transaction , msgIndex , denom ) : TransactionItemData => { const { tags , events } = transaction ; const msg = transaction . msgs [ msgIndex ] ; const { type , value } = msg ; const { validator_address } = value as IMsgWithdrawValidatorCommission ; let commissions = null ; if ( tags ) { const commissionsTag = tags . filter ( tag => tag . key === "commission" && Boolean ( tag . value ) ) ; if ( commissionsTag . length && commissionsTag [ 0 ] . value ) { commissions = commissionsTag [ 0 ] . value . replace ( denom , "" ) ; commissions = commissions . replace ( /[^\d]/g , "" ) ; } } const fee = getTxFee ( transaction ) ; const validatorAddress = validator_address || "" ; let claimData = [ ] ; if ( events ) { claimData = getClaimDataFromEvents ( events , "commissions" ) ; } return { fee , toAddress : "" , rewardsCommissionsData : claimData , amount : { amount : commissions || "0" , denom } , fromAddress : validatorAddress , timestamp : transaction . timestamp , type : type as COSMOS_TRANSACTION_TYPES , } ; } ; const getUndelegateMessage = ( transaction , msgIndex ) : TransactionItemData => { const msg = transaction . msgs [ msgIndex ] ; let undelegateAmount = null ; let delegatorAddress = "" ; let validatorAddress = "" ; if ( msg ) { const value = msg . value as IMsgDelegate ; const { amount } = value as IMsgDelegate ; undelegateAmount = amount ; delegatorAddress = value . delegator_address || "" ; validatorAddress = value . validator_address || "" ; } const fee = getTxFee ( transaction ) ; return { fee , amount : undelegateAmount , toAddress : formatAddressString ( delegatorAddress , true ) , fromAddress : formatAddressString ( validatorAddress , true ) , timestamp : transaction . timestamp , type : COSMOS_TRANSACTION_TYPES . UNDELEGATE , } ; } ; const getRedelegateMessageData = ( transaction , msgIndex ) : TransactionItemData => { const msg = transaction . msgs [ msgIndex ] ; const fee = getTxFee ( transaction ) ; const { value } = msg ; const { amount , validator_src_address , validator_dst_address , } = value as IMsgBeginRedelegate ; let redelegateAmount = null ; if ( amount ) { redelegateAmount = amount ; } return { fee , amount : redelegateAmount , timestamp : transaction . timestamp , fromAddress : validator_src_address , toAddress : validator_dst_address , type : COSMOS_TRANSACTION_TYPES . BEGIN_REDELEGATE , } ; } ; const getGovernanceVoteMessage = ( transaction , msgIndex ) : GovernanceVoteMessageData => { const { timestamp } = transaction ; const msg = transaction . msgs [ msgIndex ] ; const fee = getTxFee ( transaction ) ; const vote = msg . value as IMsgVote ; const { option , proposal_id } = vote ; return { option , fee , timestamp , proposal_id , type : COSMOS_TRANSACTION_TYPES . VOTE , } ; } ; const getGovernanceSubmitProposalMessage = ( transaction , msgIndex ) : GovernanceSubmitProposalMessageData => { const { timestamp } = transaction ; const msg = transaction . msgs [ msgIndex ] ; let title ; let description ; let proposer ; let deposit ; const fee = getTxFee ( transaction ) ; const data = msg . value ; if ( data . content ) { title = data . content . value . title ; description = data . content . value . description ; proposer = data . proposer ; deposit = data . initial_deposit ? data . initial_deposit [ 0 ] : null ; } else { const proposal = data as IMsgSubmitProposal ; title = proposal . title ; description = proposal . description ; proposer = proposal . proposer ; deposit = proposal . initial_deposit ? proposal . initial_deposit [ 0 ] : null ; } return { fee , title , deposit , proposer , timestamp , description , type : COSMOS_TRANSACTION_TYPES . SUBMIT_PROPOSAL , } ; } ; const getValidatorCreateOrEditMessage = ( transaction , msgIndex ) : ValidatorCreateOrEditMessageData => { const msg = transaction . msgs [ msgIndex ] ; const fee = getTxFee ( transaction ) ; const value = msg . value as IMsgWithdrawDelegationReward ; const { delegator_address , validator_address } = value ; const delegatorAddress = delegator_address || "" ; const validatorAddress = validator_address || "" ; return { fee , delegatorAddress , validatorAddress , timestamp : transaction . timestamp , type : COSMOS_TRANSACTION_TYPES . CREATE_VALIDATOR , } ; } ; const getChangeWithdrawAddressMessage = ( transaction , msgIndex ) : ValidatorModifyWithdrawAddressMessageData => { const msg = transaction . msgs [ msgIndex ] ; const fee = getTxFee ( transaction ) ; const type = msg . type ; const value = msg . value as IMsgModifyWithdrawAddress ; const { withdraw_address , validator_address } = value ; console . log ( transaction ) ; const withdrawAddress = withdraw_address || "" ; const validatorAddress = validator_address || "" ; return { fee , type , withdrawAddress , validatorAddress , timestamp : transaction . timestamp , } ; } ; /**
 * Primary method to handle converting a transaction to the relevant data
 * and text to render.
 */ export const transformCosmosTransactionToRenderElements = ( { address , transaction , msgIndex , denom , } ) : CosmosTransactionItemData | null => { const IS_COSMOS = transaction . chain . includes ( "cosmos" ) ; if ( IS_COSMOS ) { const TX_TYPE = transaction . msgs [ msgIndex ] . type as COSMOS_TRANSACTION_TYPES ; switch ( TX_TYPE ) { case COSMOS_TRANSACTION_TYPES . RECEIVE : case COSMOS_TRANSACTION_TYPES . SEND : { return getTransactionSendMessage ( transaction , address , msgIndex ) ; } case COSMOS_TRANSACTION_TYPES . DELEGATE : { return getDelegationTransactionMessage ( transaction , msgIndex ) ; } case COSMOS_TRANSACTION_TYPES . VOTE : { return getGovernanceVoteMessage ( transaction , msgIndex ) ; } case COSMOS_TRANSACTION_TYPES . UNDELEGATE : { return getUndelegateMessage ( transaction , msgIndex ) ; } case COSMOS_TRANSACTION_TYPES . SUBMIT_PROPOSAL : { return getGovernanceSubmitProposalMessage ( transaction , msgIndex ) ; } case COSMOS_TRANSACTION_TYPES . BEGIN_REDELEGATE : { return getRedelegateMessageData ( transaction , msgIndex ) ; } case COSMOS_TRANSACTION_TYPES . CLAIM_REWARDS : { return getClaimRewardsMessageData ( transaction , msgIndex , denom ) ; } case COSMOS_TRANSACTION_TYPES . CLAIM_COMMISSION : { return getValidatorClaimRewardsMessageData ( transaction , msgIndex , denom ) ; } case COSMOS_TRANSACTION_TYPES . CREATE_VALIDATOR : { return getValidatorCreateOrEditMessage ( transaction , msgIndex ) ; } case COSMOS_TRANSACTION_TYPES . EDIT_VALIDATOR : { return getValidatorCreateOrEditMessage ( transaction , msgIndex ) ; } case COSMOS_TRANSACTION_TYPES . MODIFY_WITHDRAW_ADDRESS : { return getChangeWithdrawAddressMessage ( transaction , msgIndex ) ; } default : return assertUnreachable ( TX_TYPE ) ; } } else { const TX_TYPE = transaction . msgs [ msgIndex ] . type as TERRA_TRANSACTION_TYPES ; switch ( TX_TYPE ) { case TERRA_TRANSACTION_TYPES . SEND : case TERRA_TRANSACTION_TYPES . MULTI_SEND : case TERRA_TRANSACTION_TYPES . RECEIVE : { return getTransactionSendMessage ( transaction , address , msgIndex ) ; } case TERRA_TRANSACTION_TYPES . DELEGATE : { return getDelegationTransactionMessage ( transaction , msgIndex ) ; } case TERRA_TRANSACTION_TYPES . UNDELEGATE : { return getUndelegateMessage ( transaction , msgIndex ) ; } case TERRA_TRANSACTION_TYPES . REDELEGATE : { return getRedelegateMessageData ( transaction , msgIndex ) ; } case TERRA_TRANSACTION_TYPES . WITHDRAW_REWARD : { return getClaimRewardsMessageData ( transaction , msgIndex , denom ) ; } case TERRA_TRANSACTION_TYPES . WITHDRAW_COMMISSION : { return getValidatorClaimRewardsMessageData ( transaction , msgIndex , denom ) ; } case TERRA_TRANSACTION_TYPES . MODIFY_WITHDRAW_ADDRESS : { return getChangeWithdrawAddressMessage ( transaction , msgIndex ) ; } case TERRA_TRANSACTION_TYPES . SUBMIT_PROPOSAL : { return getGovernanceSubmitProposalMessage ( transaction , msgIndex ) ; } case TERRA_TRANSACTION_TYPES . GOVERNANCE_DEPOSIT : case TERRA_TRANSACTION_TYPES . DELEGATE_FEED_CONSENT : case TERRA_TRANSACTION_TYPES . EXCHANGE_RATE_VOTE : case TERRA_TRANSACTION_TYPES . EXCHANGE_RATE_PRE_VOTE : default : console . log ( transaction ) ; Sentry . captureException ( `Unhandled Terra transaction type ${ TX_TYPE } for address: ${ address }` ) ; return null ; } } } ;