-
Notifications
You must be signed in to change notification settings - Fork 108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add undelegate, redelegate and claim rewards #95
Changes from 5 commits
ab6235a
5515c65
4daa6ea
f00ac89
e2a0d51
3c6c1da
312d2af
3d3fef2
38d62d5
9e3b7ff
79d2fb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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,7 +30,7 @@ const TransactionInfo = (props: Props) => { | |||||
</li> | ||||||
</> | ||||||
) : msg.typeUrl === "/cosmos.staking.v1beta1.MsgDelegate" || | ||||||
msg.typeUrl === "/cosmos.staking.v1beta1.MsgUnDelegate" ? ( | ||||||
msg.typeUrl === "/cosmos.staking.v1beta1.MsgUndelegate" ? ( | ||||||
<> | ||||||
<li> | ||||||
<label>Amount:</label> | ||||||
|
@@ -64,6 +62,19 @@ const TransactionInfo = (props: Props) => { | |||||
</div> | ||||||
</li> | ||||||
</> | ||||||
) : msg.typeUrl === "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward" ? ( | ||||||
<> | ||||||
<li> | ||||||
<label>Amount:</label> | ||||||
<div>{printableCoin(props.tx.msgs[0].value.amount, state.chain)}</div> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the same issue for the whole file, but we can do
Suggested change
since we loop over messages already. This makes the code future proof for multi message transactions (which we soon need to withdraw rewards from multiple validators). |
||||||
</li> | ||||||
<li> | ||||||
<label>Validator Address:</label> | ||||||
<div title={props.tx.msgs[0].value.validatorAddress}> | ||||||
<HashView hash={props.tx.msgs[0].value.validatorAddress} /> | ||||||
</div> | ||||||
</li> | ||||||
</> | ||||||
) : null, | ||||||
)} | ||||||
{props.tx.fee && ( | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { | ||
address: string | null; | ||
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 [addressError, setAddressError] = useState(""); | ||
|
||
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.address, | ||
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); | ||
if (validatorSrcAddressError) { | ||
setAddressError( | ||
`Invalid address for network ${state.chain.chainId}: ${validatorSrcAddressError}`, | ||
); | ||
return; | ||
} | ||
if (validatorDstAddressError) { | ||
setAddressError( | ||
`Invalid address for network ${state.chain.chainId}: ${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.address}/transaction/${transactionID}`); | ||
}; | ||
|
||
assert(state.chain.addressPrefix, "addressPrefix missing"); | ||
|
||
return ( | ||
<StackableContainer lessPadding> | ||
<button className="remove" onClick={() => props.closeForm()}> | ||
✕ | ||
</button> | ||
<h2>Create ReDelegation</h2> | ||
<div className="form-item"> | ||
<Input | ||
label="Validator Source Address" | ||
name="validatorSourceAddress" | ||
value={validatorSrcAddress} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => | ||
setValidatorSrcAddress(e.target.value) | ||
} | ||
error={addressError} | ||
placeholder={`E.g. ${exampleValidatorAddress(0, state.chain.addressPrefix)}`} | ||
/> | ||
</div> | ||
<div className="form-item"> | ||
<Input | ||
label="Validator Destination Address" | ||
name="validatorDestinationAddress" | ||
value={validatorDstAddress} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => | ||
setValidatorDstAddress(e.target.value) | ||
} | ||
error={addressError} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we need separate error props, one for each field? |
||
placeholder={`E.g. ${exampleValidatorAddress(1, state.chain.addressPrefix)}`} | ||
/> | ||
</div> | ||
<div className="form-item"> | ||
<Input | ||
label={`Amount (${state.chain.displayDenom})`} | ||
name="amount" | ||
type="number" | ||
value={amount} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setAmount(e.target.value)} | ||
/> | ||
</div> | ||
<div className="form-item"> | ||
<Input | ||
label="Gas Limit" | ||
name="gas" | ||
type="number" | ||
value={gas} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => | ||
setGas(parseInt(e.target.value, 10)) | ||
} | ||
/> | ||
</div> | ||
<div className="form-item"> | ||
<Input label="Gas Price" name="gas_price" type="string" value={gasPrice} disabled={true} /> | ||
</div> | ||
<div className="form-item"> | ||
<Input | ||
label="Memo" | ||
name="memo" | ||
type="text" | ||
value={memo} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setMemo(e.target.value)} | ||
/> | ||
</div> | ||
<Button label="ReDelegate" onClick={handleCreate} /> | ||
<style jsx>{` | ||
p { | ||
margin-top: 15px; | ||
} | ||
.form-item { | ||
margin-top: 1.5em; | ||
} | ||
button.remove { | ||
background: rgba(255, 255, 255, 0.2); | ||
width: 30px; | ||
height: 30px; | ||
border-radius: 50%; | ||
border: none; | ||
color: white; | ||
position: absolute; | ||
right: 10px; | ||
top: 10px; | ||
} | ||
`}</style> | ||
</StackableContainer> | ||
); | ||
}; | ||
|
||
export default withRouter(ReDelegationForm); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
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 { | ||
address: string | null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good to make explicit which address this is. For this component we can say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also this prop should not be |
||
accountOnChain: Account | null; | ||
router: NextRouter; | ||
closeForm: () => void; | ||
} | ||
|
||
const RewardsForm = (props: Props) => { | ||
const { state } = useAppContext(); | ||
const [validatorAddress, setValidatorAddress] = useState(""); | ||
const [memo, setMemo] = useState(""); | ||
const [gas, setGas] = useState(200000); | ||
const [gasPrice, _setGasPrice] = useState(state.chain.gasPrice); | ||
const [_processing, setProcessing] = useState(false); | ||
const [addressError, setAddressError] = useState(""); | ||
|
||
const createTransaction = (txValidatorAddress: string, gasLimit: number) => { | ||
assert(Number.isSafeInteger(gasLimit) && gasLimit > 0, "gas limit must be a positive integer"); | ||
|
||
const msgDelegatorReward = { | ||
delegatorAddress: props.address, | ||
validatorAddress: txValidatorAddress, | ||
}; | ||
const msg = { | ||
typeUrl: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", | ||
value: msgDelegatorReward, | ||
}; | ||
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 validatorAddressError = checkAddress(validatorAddress, state.chain.addressPrefix); | ||
if (validatorAddressError) { | ||
setAddressError( | ||
`Invalid address for network ${state.chain.chainId}: ${validatorAddressError}`, | ||
); | ||
return; | ||
} | ||
|
||
setProcessing(true); | ||
const tx = createTransaction(validatorAddress, 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.address}/transaction/${transactionID}`); | ||
}; | ||
|
||
assert(state.chain.addressPrefix, "addressPrefix missing"); | ||
|
||
return ( | ||
<StackableContainer lessPadding> | ||
<button className="remove" onClick={() => props.closeForm()}> | ||
✕ | ||
</button> | ||
<h2>Claim Rewards</h2> | ||
<div className="form-item"> | ||
<Input | ||
label="Validator Address" | ||
name="validatorAddress" | ||
value={validatorAddress} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValidatorAddress(e.target.value)} | ||
error={addressError} | ||
placeholder={`E.g. ${exampleValidatorAddress(0, state.chain.addressPrefix)})}`} | ||
/> | ||
</div> | ||
<div className="form-item"> | ||
<Input | ||
label="Gas Limit" | ||
name="gas" | ||
type="number" | ||
value={gas} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => | ||
setGas(parseInt(e.target.value, 10)) | ||
} | ||
/> | ||
</div> | ||
<div className="form-item"> | ||
<Input label="Gas Price" name="gas_price" type="string" value={gasPrice} disabled={true} /> | ||
</div> | ||
<div className="form-item"> | ||
<Input | ||
label="Memo" | ||
name="memo" | ||
type="text" | ||
value={memo} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setMemo(e.target.value)} | ||
/> | ||
</div> | ||
<Button label="Claim Rewards" onClick={handleCreate} /> | ||
<style jsx>{` | ||
p { | ||
margin-top: 15px; | ||
} | ||
.form-item { | ||
margin-top: 1.5em; | ||
} | ||
button.remove { | ||
background: rgba(255, 255, 255, 0.2); | ||
width: 30px; | ||
height: 30px; | ||
border-radius: 50%; | ||
border: none; | ||
color: white; | ||
position: absolute; | ||
right: 10px; | ||
top: 10px; | ||
} | ||
`}</style> | ||
</StackableContainer> | ||
); | ||
}; | ||
|
||
export default withRouter(RewardsForm); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message has no amount, just those two fields: https://github.com/cosmos/cosmos-sdk/blob/v0.47.0/proto/cosmos/distribution/v1beta1/tx.proto#L66-L77