diff --git a/components/dataViews/TransactionInfo.tsx b/components/dataViews/TransactionInfo.tsx index ac01b44..c321e5c 100644 --- a/components/dataViews/TransactionInfo.tsx +++ b/components/dataViews/TransactionInfo.tsx @@ -1,11 +1,9 @@ -import React from "react"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; - -import { DbTransaction } from "../../types"; import { useAppContext } from "../../context/AppContext"; -import HashView from "./HashView"; +import { printableCoin, printableCoins } from "../../lib/displayHelpers"; +import { DbTransaction } from "../../types"; import StackableContainer from "../layout/StackableContainer"; -import { printableCoins, printableCoin } from "../../lib/displayHelpers"; +import HashView from "./HashView"; interface Props { tx: DbTransaction; @@ -32,16 +30,16 @@ const TransactionInfo = (props: Props) => { ) : msg.typeUrl === "/cosmos.staking.v1beta1.MsgDelegate" || - msg.typeUrl === "/cosmos.staking.v1beta1.MsgUnDelegate" ? ( + msg.typeUrl === "/cosmos.staking.v1beta1.MsgUndelegate" ? ( <>
  • -
    {printableCoin(props.tx.msgs[0].value.amount, state.chain)}
    +
    {printableCoin(msg.value.amount, state.chain)}
  • -
    - +
    +
  • @@ -49,21 +47,28 @@ const TransactionInfo = (props: Props) => { <>
  • -
    {printableCoin(props.tx.msgs[0].value.amount, state.chain)}
    +
    {printableCoin(msg.value.amount, state.chain)}
  • -
    - +
    +
  • -
    - +
    +
  • + ) : msg.typeUrl === "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" ? ( +
  • + +
    + +
    +
  • ) : null, )} {props.tx.fee && ( diff --git a/components/forms/DelegationForm.tsx b/components/forms/DelegationForm.tsx index 3a025bb..e8cfddd 100644 --- a/components/forms/DelegationForm.tsx +++ b/components/forms/DelegationForm.tsx @@ -11,7 +11,7 @@ import Input from "../inputs/Input"; import StackableContainer from "../layout/StackableContainer"; interface Props { - address: string | null; + delegatorAddress: string; accountOnChain: Account | null; router: NextRouter; closeForm: () => void; @@ -35,7 +35,7 @@ const DelegationForm = (props: Props) => { Number(state.chain.displayDenomExponent), ).atomics; const msgDelegate = { - delegatorAddress: props.address, + delegatorAddress: props.delegatorAddress, validatorAddress: txValidatorAddress, amount: { amount: amountInAtomics, @@ -77,7 +77,7 @@ const DelegationForm = (props: Props) => { const res = await axios.post("/api/transaction", { dataJSON }); console.log(dataJSON, "tx dataJSON", res); const { transactionID } = res.data; - props.router.push(`${props.address}/transaction/${transactionID}`); + props.router.push(`${props.delegatorAddress}/transaction/${transactionID}`); }; assert(state.chain.addressPrefix, "addressPrefix missing"); diff --git a/components/forms/ReDelegationForm.tsx b/components/forms/ReDelegationForm.tsx new file mode 100644 index 0000000..9ffb758 --- /dev/null +++ b/components/forms/ReDelegationForm.tsx @@ -0,0 +1,185 @@ +import { Decimal } from "@cosmjs/math"; +import { Account, calculateFee } from "@cosmjs/stargate"; +import { assert } from "@cosmjs/utils"; +import axios from "axios"; +import { NextRouter, withRouter } from "next/router"; +import { useState } from "react"; +import { useAppContext } from "../../context/AppContext"; +import { checkAddress, exampleValidatorAddress } from "../../lib/displayHelpers"; +import Button from "../inputs/Button"; +import Input from "../inputs/Input"; +import StackableContainer from "../layout/StackableContainer"; + +interface Props { + delegatorAddress: string; + accountOnChain: Account | null; + router: NextRouter; + closeForm: () => void; +} + +const ReDelegationForm = (props: Props) => { + const { state } = useAppContext(); + const [validatorSrcAddress, setValidatorSrcAddress] = useState(""); + const [validatorDstAddress, setValidatorDstAddress] = useState(""); + const [amount, setAmount] = useState("0"); + const [memo, setMemo] = useState(""); + const [gas, setGas] = useState(300000); + const [gasPrice, _setGasPrice] = useState(state.chain.gasPrice); + const [_processing, setProcessing] = useState(false); + const [addressErrors, setAddressErrors] = useState({ src: "", dst: "" }); + + const createTransaction = ( + txValidatorSrcAddress: string, + txValidatorDstAddress: string, + txAmount: string, + gasLimit: number, + ) => { + assert(Number.isSafeInteger(gasLimit) && gasLimit > 0, "gas limit must be a positive integer"); + + const amountInAtomics = Decimal.fromUserInput( + txAmount, + Number(state.chain.displayDenomExponent), + ).atomics; + const msgRedelegate = { + delegatorAddress: props.delegatorAddress, + validatorSrcAddress: txValidatorSrcAddress, + validatorDstAddress: txValidatorDstAddress, + amount: { + amount: amountInAtomics, + denom: state.chain.denom, + }, + }; + const msg = { + typeUrl: "/cosmos.staking.v1beta1.MsgBeginRedelegate", + value: msgRedelegate, + }; + assert(gasPrice, "gasPrice missing"); + const fee = calculateFee(gasLimit, gasPrice); + const { accountOnChain } = props; + assert(accountOnChain, "accountOnChain missing"); + return { + accountNumber: accountOnChain.accountNumber, + sequence: accountOnChain.sequence, + chainId: state.chain.chainId, + msgs: [msg], + fee: fee, + memo: memo, + }; + }; + + const handleCreate = async () => { + assert(state.chain.addressPrefix, "addressPrefix missing"); + const validatorSrcAddressError = checkAddress(validatorSrcAddress, state.chain.addressPrefix); + const validatorDstAddressError = checkAddress(validatorDstAddress, state.chain.addressPrefix); + + setAddressErrors({ + src: validatorSrcAddressError + ? `Invalid address for network ${state.chain.chainId}: ${validatorSrcAddressError}` + : "", + dst: validatorDstAddressError + ? `Invalid address for network ${state.chain.chainId}: ${validatorDstAddressError}` + : "", + }); + if (validatorSrcAddressError || validatorDstAddressError) { + return; + } + + setProcessing(true); + const tx = createTransaction(validatorSrcAddress, validatorDstAddress, amount, gas); + console.log(tx, "tx data"); + const dataJSON = JSON.stringify(tx); + const res = await axios.post("/api/transaction", { dataJSON }); + console.log(dataJSON, "tx dataJSON", res); + const { transactionID } = res.data; + props.router.push(`${props.delegatorAddress}/transaction/${transactionID}`); + }; + + assert(state.chain.addressPrefix, "addressPrefix missing"); + + return ( + + +

    Create ReDelegation

    +
    + ) => + setValidatorSrcAddress(e.target.value) + } + error={addressErrors.src} + placeholder={`E.g. ${exampleValidatorAddress(0, state.chain.addressPrefix)}`} + /> +
    +
    + ) => + setValidatorDstAddress(e.target.value) + } + error={addressErrors.dst} + placeholder={`E.g. ${exampleValidatorAddress(1, state.chain.addressPrefix)}`} + /> +
    +
    + ) => setAmount(e.target.value)} + /> +
    +
    + ) => + setGas(parseInt(e.target.value, 10)) + } + /> +
    +
    + +
    +
    + ) => setMemo(e.target.value)} + /> +
    + +

    Claim Rewards

    +
    + ) => setValidatorAddress(e.target.value)} + error={addressError} + placeholder={`E.g. ${exampleValidatorAddress(0, state.chain.addressPrefix)})}`} + /> +
    +
    + ) => + setGas(parseInt(e.target.value, 10)) + } + /> +
    +
    + +
    +
    + ) => setMemo(e.target.value)} + /> +
    + +

    Create UnDelegation

    +
    + ) => setValidatorAddress(e.target.value)} + error={addressError} + placeholder={`E.g. ${exampleValidatorAddress(0, state.chain.addressPrefix)})}`} + /> +
    +
    + ) => setAmount(e.target.value)} + /> +
    +
    + ) => + setGas(parseInt(e.target.value, 10)) + } + /> +
    +
    + +
    +
    + ) => setMemo(e.target.value)} + /> +
    +