Prefix current create form with Old

This commit is contained in:
abefernan
2024-07-22 10:57:43 +02:00
parent ee602eec51
commit f4c53edb76
17 changed files with 348 additions and 0 deletions
@@ -0,0 +1,179 @@
import SelectValidator from "@/components/SelectValidator";
import { MsgBeginRedelegateEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
interface MsgBeginRedelegateFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgBeginRedelegateForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgBeginRedelegateFormProps) => {
const { chain } = useChains();
const [validatorSrcAddress, setValidatorSrcAddress] = useState("");
const [validatorDstAddress, setValidatorDstAddress] = useState("");
const [amount, setAmount] = useState("0");
const [validatorSrcAddressError, setValidatorSrcAddressError] = useState("");
const [validatorDstAddressError, setValidatorDstAddressError] = useState("");
const [amountError, setAmountError] = useState("");
const trimmedInputs = trimStringsObj({ validatorSrcAddress, validatorDstAddress, amount });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { validatorSrcAddress, validatorDstAddress, amount } = trimmedInputs;
const isMsgValid = (): boolean => {
setValidatorSrcAddressError("");
setValidatorDstAddressError("");
setAmountError("");
const srcAddressErrorMsg = checkAddress(validatorSrcAddress, chain.addressPrefix);
if (srcAddressErrorMsg) {
setValidatorSrcAddressError(
`Invalid address for network ${chain.chainId}: ${srcAddressErrorMsg}`,
);
return false;
}
const dstAddressErrorMsg = checkAddress(validatorDstAddress, chain.addressPrefix);
if (dstAddressErrorMsg) {
setValidatorDstAddressError(
`Invalid address for network ${chain.chainId}: ${dstAddressErrorMsg}`,
);
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
try {
displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const microCoin = (() => {
try {
return displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch {
return { denom: chain.displayDenom, amount: "0" };
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.BeginRedelegate].fromPartial({
delegatorAddress: senderAddress,
validatorSrcAddress,
validatorDstAddress,
amount: microCoin,
});
const msg: MsgBeginRedelegateEncodeObject = {
typeUrl: MsgTypeUrls.BeginRedelegate,
value: msgValue,
};
setMsgGetter({ isMsgValid, msg });
}, [
chain.addressPrefix,
chain.assets,
chain.chainId,
chain.displayDenom,
senderAddress,
setMsgGetter,
trimmedInputs,
]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgBeginRedelegate</h2>
<div className="form-item">
<SelectValidator
validatorAddress={validatorSrcAddress}
setValidatorAddress={setValidatorSrcAddress}
/>
<Input
label="Source Validator Address"
name="src-validator-address"
value={validatorSrcAddress}
onChange={({ target }) => {
setValidatorSrcAddress(target.value);
setValidatorSrcAddressError("");
}}
error={validatorSrcAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<SelectValidator
validatorAddress={validatorDstAddress}
setValidatorAddress={setValidatorDstAddress}
/>
<Input
label="Destination Validator Address"
name="dst-validator-address"
value={validatorDstAddress}
onChange={({ target }) => {
setValidatorDstAddress(target.value);
setValidatorDstAddressError("");
}}
error={validatorDstAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<Input
type="number"
label={`Amount (${chain.displayDenom})`}
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<style jsx>{`
.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 MsgBeginRedelegateForm;
@@ -0,0 +1,186 @@
import { EncodeObject } from "@cosmjs/proto-signing";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import {
datetimeLocalFromTimestamp,
timestampFromDatetimeLocal,
} from "../../../../lib/dateHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
interface MsgCreateVestingAccountFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgCreateVestingAccountForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgCreateVestingAccountFormProps) => {
const { chain } = useChains();
const [toAddress, setToAddress] = useState("");
const [amount, setAmount] = useState("0");
const [endTime, setEndTime] = useState(
datetimeLocalFromTimestamp(Date.now() + 30 * 24 * 60 * 60 * 1000), // Default is one month from now
);
const [delayed, setDelayed] = useState(true);
const [toAddressError, setToAddressError] = useState("");
const [amountError, setAmountError] = useState("");
const [endTimeError, setEndTimeError] = useState("");
const trimmedInputs = trimStringsObj({ toAddress, amount, endTime });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { toAddress, amount, endTime } = trimmedInputs;
const isMsgValid = (): boolean => {
setToAddressError("");
setAmountError("");
setEndTimeError("");
const addressErrorMsg = checkAddress(toAddress, chain.addressPrefix);
if (addressErrorMsg) {
setToAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
try {
displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
const timeoutDate = new Date(Number(timestampFromDatetimeLocal(endTime, "ms")));
if (timeoutDate <= new Date()) {
setEndTimeError("End time must be a date in the future");
return false;
}
return true;
};
const microCoin = (() => {
try {
if (!amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch {
return null;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.CreateVestingAccount].fromPartial({
fromAddress: senderAddress,
toAddress,
amount: microCoin ? [microCoin] : [],
endTime: timestampFromDatetimeLocal(endTime, "s"),
delayed,
});
const msg: EncodeObject = { typeUrl: MsgTypeUrls.CreateVestingAccount, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [
chain.addressPrefix,
chain.assets,
chain.chainId,
chain.displayDenom,
delayed,
senderAddress,
setMsgGetter,
trimmedInputs,
]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgCreateVestingAccount</h2>
<div className="form-item">
<Input
label="Recipient Address"
name="recipient-address"
value={toAddress}
onChange={({ target }) => {
setToAddress(target.value);
setToAddressError("");
}}
error={toAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<Input
type="number"
label={`Amount (${chain.displayDenom})`}
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<div className="form-item">
<Input
type="datetime-local"
label="End time"
name="end-time"
value={endTime}
onChange={({ target }) => {
setEndTime(target.value);
setEndTimeError("");
}}
error={endTimeError}
/>
</div>
<div className="form-item">
<Input
type="checkbox"
label="Delayed"
name="delayed"
checked={delayed}
value={String(delayed)}
onChange={({ target }) => setDelayed(target.checked)}
/>
</div>
<style jsx>{`
.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 MsgCreateVestingAccountForm;
@@ -0,0 +1,143 @@
import SelectValidator from "@/components/SelectValidator";
import { MsgDelegateEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
interface MsgDelegateFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgDelegateForm = ({ senderAddress, setMsgGetter, deleteMsg }: MsgDelegateFormProps) => {
const { chain } = useChains();
const [validatorAddress, setValidatorAddress] = useState("");
const [amount, setAmount] = useState("0");
const [validatorAddressError, setValidatorAddressError] = useState("");
const [amountError, setAmountError] = useState("");
const trimmedInputs = trimStringsObj({ validatorAddress, amount });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { validatorAddress, amount } = trimmedInputs;
const isMsgValid = (): boolean => {
setValidatorAddressError("");
setAmountError("");
const addressErrorMsg = checkAddress(validatorAddress, chain.addressPrefix);
if (addressErrorMsg) {
setValidatorAddressError(
`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`,
);
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
try {
displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const microCoin = (() => {
try {
return displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch {
return { denom: chain.displayDenom, amount: "0" };
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.Delegate].fromPartial({
delegatorAddress: senderAddress,
validatorAddress,
amount: microCoin,
});
const msg: MsgDelegateEncodeObject = { typeUrl: MsgTypeUrls.Delegate, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [
chain.addressPrefix,
chain.assets,
chain.chainId,
chain.displayDenom,
senderAddress,
setMsgGetter,
trimmedInputs,
]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgDelegate</h2>
<div className="form-item">
<SelectValidator
validatorAddress={validatorAddress}
setValidatorAddress={setValidatorAddress}
/>
<Input
label="Validator Address"
name="validator-address"
value={validatorAddress}
onChange={({ target }) => {
setValidatorAddress(target.value);
setValidatorAddressError("");
}}
error={validatorAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<Input
type="number"
label={`Amount (${chain.displayDenom})`}
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<style jsx>{`
.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 MsgDelegateForm;
@@ -0,0 +1,260 @@
import { MsgExecuteContractEncodeObject } from "@cosmjs/cosmwasm-stargate";
import { toUtf8 } from "@cosmjs/encoding";
import dynamic from "next/dynamic";
import { useEffect, useRef, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { ChainInfo } from "../../../../context/ChainsContext/types";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import Select from "../../../inputs/Select";
import StackableContainer from "../../../layout/StackableContainer";
const JsonEditor = dynamic(() => import("../../../inputs/JsonEditor"), { ssr: false });
const customDenomOption = { label: "Custom (enter denom below)", value: "custom" } as const;
const getDenomOptions = (assets: ChainInfo["assets"]) => {
if (!assets?.length) {
return [customDenomOption];
}
return [...assets.map((asset) => ({ label: asset.symbol, value: asset })), customDenomOption];
};
interface MsgExecuteContractFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgExecuteContractForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgExecuteContractFormProps) => {
const { chain } = useChains();
const denomOptions = getDenomOptions(chain.assets);
const [contractAddress, setContractAddress] = useState("");
const [msgContent, setMsgContent] = useState("{}");
const [selectedDenom, setSelectedDenom] = useState(denomOptions[0]);
const [customDenom, setCustomDenom] = useState("");
const [amount, setAmount] = useState("0");
const jsonError = useRef(false);
const [contractAddressError, setContractAddressError] = useState("");
const [customDenomError, setCustomDenomError] = useState("");
const [amountError, setAmountError] = useState("");
const trimmedInputs = trimStringsObj({ contractAddress, customDenom, amount });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { contractAddress, customDenom, amount } = trimmedInputs;
const denom =
selectedDenom.value === customDenomOption.value ? customDenom : selectedDenom.value.symbol;
const isMsgValid = (): boolean => {
setContractAddressError("");
setCustomDenomError("");
setAmountError("");
if (jsonError.current) {
return false;
}
const addressErrorMsg = checkAddress(contractAddress, chain.addressPrefix);
if (addressErrorMsg) {
setContractAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
return false;
}
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
if (amount && Number(amount) < 0) {
setAmountError("Amount must be empty or a positive number");
return false;
}
if (selectedDenom.value === customDenomOption.value && !Number.isInteger(Number(amount))) {
setAmountError("Amount cannot be decimal for custom denom");
return false;
}
if (denom && amount) {
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
}
return true;
};
const microCoin = (() => {
try {
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return null;
}
})();
const msgContentUtf8Array = (() => {
try {
// The JsonEditor does not escape \n or remove whitespaces, so we need to parse + stringify
return toUtf8(JSON.stringify(JSON.parse(msgContent)));
} catch {
return undefined;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.ExecuteContract].fromPartial({
sender: senderAddress,
contract: contractAddress,
msg: msgContentUtf8Array,
funds: microCoin ? [microCoin] : [],
});
const msg: MsgExecuteContractEncodeObject = {
typeUrl: MsgTypeUrls.ExecuteContract,
value: msgValue,
};
setMsgGetter({ isMsgValid, msg });
}, [
chain.addressPrefix,
chain.assets,
chain.chainId,
msgContent,
selectedDenom.value,
senderAddress,
setMsgGetter,
trimmedInputs,
]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgExecuteContract</h2>
<div className="form-item">
<Input
label="Contract Address"
name="contract-address"
value={contractAddress}
onChange={({ target }) => {
setContractAddress(target.value);
setContractAddressError("");
}}
error={contractAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<JsonEditor
label="Msg JSON"
content={{ text: msgContent }}
onChange={(newMsgContent, _, { contentErrors }) => {
setMsgContent("text" in newMsgContent ? newMsgContent.text ?? "{}" : "{}");
jsonError.current = !!contentErrors;
}}
/>
</div>
<div className="form-item form-select">
<label>Choose a denom:</label>
<Select
label="Select denom"
name="denom-select"
options={denomOptions}
value={selectedDenom}
onChange={(option: (typeof denomOptions)[number]) => {
setSelectedDenom(option);
if (option.value !== customDenomOption.value) {
setCustomDenom("");
}
setCustomDenomError("");
}}
/>
</div>
{selectedDenom.value === customDenomOption.value ? (
<div className="form-item">
<Input
label="Custom denom"
name="custom-denom"
value={customDenom}
onChange={({ target }) => {
setCustomDenom(target.value);
setCustomDenomError("");
}}
placeholder={
selectedDenom.value === customDenomOption.value
? "Enter custom denom"
: "Select Custom denom above"
}
disabled={selectedDenom.value !== customDenomOption.value}
error={customDenomError}
/>
</div>
) : null}
<div className="form-item">
<Input
type="number"
label="Amount"
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
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 MsgExecuteContractForm;
@@ -0,0 +1,194 @@
import { EncodeObject } from "@cosmjs/proto-signing";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { trimStringsObj } from "../../../../lib/displayHelpers";
import { RegistryAsset } from "../../../../types/chainRegistry";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import Select from "../../../inputs/Select";
import StackableContainer from "../../../layout/StackableContainer";
const customDenomOption = { label: "Custom (enter denom below)", value: "custom" } as const;
const getDenomOptions = (assets: readonly RegistryAsset[]) => {
if (!assets?.length) {
return [customDenomOption];
}
return [...assets.map((asset) => ({ label: asset.symbol, value: asset })), customDenomOption];
};
interface MsgFundCommunityPoolFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgFundCommunityPoolForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgFundCommunityPoolFormProps) => {
const { chain } = useChains();
const denomOptions = getDenomOptions(chain.assets);
const [selectedDenom, setSelectedDenom] = useState(denomOptions[0]);
const [customDenom, setCustomDenom] = useState("");
const [amount, setAmount] = useState("0");
const [customDenomError, setCustomDenomError] = useState("");
const [amountError, setAmountError] = useState("");
const trimmedInputs = trimStringsObj({ customDenom, amount });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { customDenom, amount } = trimmedInputs;
const isMsgValid = (): boolean => {
setCustomDenomError("");
setAmountError("");
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
if (selectedDenom.value === customDenomOption.value && !Number.isInteger(Number(amount))) {
setAmountError("Amount cannot be decimal for custom denom");
return false;
}
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const denom =
selectedDenom.value === customDenomOption.value ? customDenom : selectedDenom.value.symbol;
const microCoin = (() => {
try {
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return null;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.FundCommunityPool].fromPartial({
depositor: senderAddress,
amount: microCoin ? [microCoin] : [],
});
const msg: EncodeObject = { typeUrl: MsgTypeUrls.FundCommunityPool, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [chain.assets, selectedDenom.value, senderAddress, setMsgGetter, trimmedInputs]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgFundCommunityPool</h2>
<div className="form-item form-select">
<label>Choose a denom:</label>
<Select
label="Select denom"
name="denom-select"
options={denomOptions}
value={selectedDenom}
onChange={(option: (typeof denomOptions)[number]) => {
setSelectedDenom(option);
if (option.value !== customDenomOption.value) {
setCustomDenom("");
}
setCustomDenomError("");
}}
/>
</div>
{selectedDenom.value === customDenomOption.value ? (
<div className="form-item">
<Input
label="Custom denom"
name="custom-denom"
value={customDenom}
onChange={({ target }) => {
setCustomDenom(target.value);
setCustomDenomError("");
}}
placeholder={
selectedDenom.value === customDenomOption.value
? "Enter custom denom"
: "Select Custom denom above"
}
disabled={selectedDenom.value !== customDenomOption.value}
error={customDenomError}
/>
</div>
) : null}
<div className="form-item">
<Input
type="number"
label="Amount"
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
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 MsgFundCommunityPoolForm;
@@ -0,0 +1,338 @@
import { MsgInstantiateContract2EncodeObject } from "@cosmjs/cosmwasm-stargate";
import { fromHex, toUtf8 } from "@cosmjs/encoding";
import dynamic from "next/dynamic";
import { useEffect, useRef, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { ChainInfo } from "../../../../context/ChainsContext/types";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import Select from "../../../inputs/Select";
import StackableContainer from "../../../layout/StackableContainer";
const JsonEditor = dynamic(() => import("../../../inputs/JsonEditor"), { ssr: false });
const customDenomOption = { label: "Custom (enter denom below)", value: "custom" } as const;
const getDenomOptions = (assets: ChainInfo["assets"]) => {
if (!assets?.length) {
return [customDenomOption];
}
return [...assets.map((asset) => ({ label: asset.symbol, value: asset })), customDenomOption];
};
interface MsgInstantiateContract2FormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgInstantiateContract2Form = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgInstantiateContract2FormProps) => {
const { chain } = useChains();
const denomOptions = getDenomOptions(chain.assets);
const [codeId, setCodeId] = useState("");
const [label, setLabel] = useState("");
const [adminAddress, setAdminAddress] = useState("");
const [salt, setSalt] = useState("");
const [msgContent, setMsgContent] = useState("{}");
const [selectedDenom, setSelectedDenom] = useState(denomOptions[0]);
const [customDenom, setCustomDenom] = useState("");
const [amount, setAmount] = useState("0");
const jsonError = useRef(false);
const [codeIdError, setCodeIdError] = useState("");
const [labelError, setLabelError] = useState("");
const [adminAddressError, setAdminAddressError] = useState("");
const [saltError, setSaltError] = useState("");
const [customDenomError, setCustomDenomError] = useState("");
const [amountError, setAmountError] = useState("");
const trimmedInputs = trimStringsObj({ codeId, label, adminAddress, salt, customDenom, amount });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { codeId, label, adminAddress, salt, customDenom, amount } = trimmedInputs;
const isMsgValid = (): boolean => {
setCodeIdError("");
setLabelError("");
setAdminAddressError("");
setSaltError("");
setCustomDenomError("");
setAmountError("");
if (jsonError.current) {
return false;
}
if (!codeId || !Number.isSafeInteger(Number(codeId)) || Number(codeId) <= 0) {
setCodeIdError("Code ID must be a positive integer");
return false;
}
if (!label) {
setLabelError("Label is required");
return false;
}
const addressErrorMsg = checkAddress(adminAddress, chain.addressPrefix);
if (adminAddress && addressErrorMsg) {
setAdminAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
return false;
}
try {
if (!salt) {
throw new Error("Salt is required");
}
fromHex(salt);
} catch (e) {
setSaltError(e instanceof Error ? e.message : "Salt needs to be an hexadecimal string");
return false;
}
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
if (selectedDenom.value === customDenomOption.value && !Number.isInteger(Number(amount))) {
setAmountError("Amount cannot be decimal for custom denom");
return false;
}
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const denom =
selectedDenom.value === customDenomOption.value ? customDenom : selectedDenom.value.symbol;
const microCoin = (() => {
try {
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return null;
}
})();
const hexSalt = (() => {
try {
return fromHex(salt);
} catch {
return undefined;
}
})();
const msgContentUtf8Array = (() => {
try {
// The JsonEditor does not escape \n or remove whitespaces, so we need to parse + stringify
return toUtf8(JSON.stringify(JSON.parse(msgContent)));
} catch {
return undefined;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.InstantiateContract2].fromPartial({
sender: senderAddress,
codeId: BigInt(codeId),
label,
admin: adminAddress,
fixMsg: false,
salt: hexSalt,
msg: msgContentUtf8Array,
funds: microCoin ? [microCoin] : [],
});
const msg: MsgInstantiateContract2EncodeObject = {
typeUrl: MsgTypeUrls.InstantiateContract2,
value: msgValue,
};
setMsgGetter({ isMsgValid, msg });
}, [
chain.addressPrefix,
chain.assets,
chain.chainId,
msgContent,
selectedDenom.value,
senderAddress,
setMsgGetter,
trimmedInputs,
]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgInstantiateContract2</h2>
<div className="form-item">
<Input
label="Code ID"
name="code-id"
value={codeId}
onChange={({ target }) => {
setCodeId(target.value);
setCodeIdError("");
}}
error={codeIdError}
/>
</div>
<div className="form-item">
<Input
label="Label"
name="label"
value={label}
onChange={({ target }) => {
setLabel(target.value);
setLabelError("");
}}
error={labelError}
/>
</div>
<div className="form-item">
<Input
label="Admin Address"
name="admin-address"
value={adminAddress}
onChange={({ target }) => {
setAdminAddress(target.value);
setAdminAddressError("");
}}
error={adminAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<Input
label="Salt (hex encoded)"
name="salt"
placeholder="E.g. 1bac68"
value={salt}
onChange={({ target }) => {
setSalt(target.value);
setSaltError("");
}}
error={saltError}
/>
</div>
<div className="form-item">
<JsonEditor
label="Msg JSON"
content={{ text: msgContent }}
onChange={(newMsgContent, _, { contentErrors }) => {
setMsgContent("text" in newMsgContent ? newMsgContent.text ?? "{}" : "{}");
jsonError.current = !!contentErrors;
}}
/>
</div>
<div className="form-item form-select">
<label>Choose a denom:</label>
<Select
label="Select denom"
name="denom-select"
options={denomOptions}
value={selectedDenom}
onChange={(option: (typeof denomOptions)[number]) => {
setSelectedDenom(option);
if (option.value !== customDenomOption.value) {
setCustomDenom("");
}
setCustomDenomError("");
}}
/>
</div>
{selectedDenom.value === customDenomOption.value ? (
<div className="form-item">
<Input
label="Custom denom"
name="custom-denom"
value={customDenom}
onChange={({ target }) => {
setCustomDenom(target.value);
setCustomDenomError("");
}}
placeholder={
selectedDenom.value === customDenomOption.value
? "Enter custom denom"
: "Select Custom denom above"
}
disabled={selectedDenom.value !== customDenomOption.value}
error={customDenomError}
/>
</div>
) : null}
<div className="form-item">
<Input
type="number"
label="Amount"
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
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 MsgInstantiateContract2Form;
@@ -0,0 +1,301 @@
import { MsgInstantiateContractEncodeObject } from "@cosmjs/cosmwasm-stargate";
import { toUtf8 } from "@cosmjs/encoding";
import dynamic from "next/dynamic";
import { useEffect, useRef, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { ChainInfo } from "../../../../context/ChainsContext/types";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import Select from "../../../inputs/Select";
import StackableContainer from "../../../layout/StackableContainer";
const JsonEditor = dynamic(() => import("../../../inputs/JsonEditor"), { ssr: false });
const customDenomOption = { label: "Custom (enter denom below)", value: "custom" } as const;
const getDenomOptions = (assets: ChainInfo["assets"]) => {
if (!assets?.length) {
return [customDenomOption];
}
return [...assets.map((asset) => ({ label: asset.symbol, value: asset })), customDenomOption];
};
interface MsgInstantiateContractFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgInstantiateContractForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgInstantiateContractFormProps) => {
const { chain } = useChains();
const denomOptions = getDenomOptions(chain.assets);
const [codeId, setCodeId] = useState("");
const [label, setLabel] = useState("");
const [adminAddress, setAdminAddress] = useState("");
const [msgContent, setMsgContent] = useState("{}");
const [selectedDenom, setSelectedDenom] = useState(denomOptions[0]);
const [customDenom, setCustomDenom] = useState("");
const [amount, setAmount] = useState("0");
const jsonError = useRef(false);
const [codeIdError, setCodeIdError] = useState("");
const [labelError, setLabelError] = useState("");
const [adminAddressError, setAdminAddressError] = useState("");
const [customDenomError, setCustomDenomError] = useState("");
const [amountError, setAmountError] = useState("");
const trimmedInputs = trimStringsObj({ codeId, label, adminAddress, customDenom, amount });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { codeId, label, adminAddress, customDenom, amount } = trimmedInputs;
const isMsgValid = (): boolean => {
setCodeIdError("");
setLabelError("");
setAdminAddressError("");
setCustomDenomError("");
setAmountError("");
if (jsonError.current) {
return false;
}
if (!codeId || !Number.isSafeInteger(Number(codeId)) || Number(codeId) <= 0) {
setCodeIdError("Code ID must be a positive integer");
return false;
}
if (!label) {
setLabelError("Label is required");
return false;
}
const addressErrorMsg = checkAddress(adminAddress, chain.addressPrefix);
if (adminAddress && addressErrorMsg) {
setAdminAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
return false;
}
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
if (selectedDenom.value === customDenomOption.value && !Number.isInteger(Number(amount))) {
setAmountError("Amount cannot be decimal for custom denom");
return false;
}
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const denom =
selectedDenom.value === customDenomOption.value ? customDenom : selectedDenom.value.symbol;
const microCoin = (() => {
try {
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return null;
}
})();
const msgContentUtf8Array = (() => {
try {
// The JsonEditor does not escape \n or remove whitespaces, so we need to parse + stringify
return toUtf8(JSON.stringify(JSON.parse(msgContent)));
} catch {
return undefined;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.InstantiateContract].fromPartial({
sender: senderAddress,
codeId: BigInt(codeId),
label,
admin: adminAddress,
msg: msgContentUtf8Array,
funds: microCoin ? [microCoin] : [],
});
const msg: MsgInstantiateContractEncodeObject = {
typeUrl: MsgTypeUrls.InstantiateContract,
value: msgValue,
};
setMsgGetter({ isMsgValid, msg });
}, [
chain.addressPrefix,
chain.assets,
chain.chainId,
msgContent,
selectedDenom.value,
senderAddress,
setMsgGetter,
trimmedInputs,
]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgInstantiateContract</h2>
<div className="form-item">
<Input
label="Code ID"
name="code-id"
value={codeId}
onChange={({ target }) => {
setCodeId(target.value);
setCodeIdError("");
}}
error={codeIdError}
/>
</div>
<div className="form-item">
<Input
label="Label"
name="label"
value={label}
onChange={({ target }) => {
setLabel(target.value);
setLabelError("");
}}
error={labelError}
/>
</div>
<div className="form-item">
<Input
label="Admin Address"
name="admin-address"
value={adminAddress}
onChange={({ target }) => {
setAdminAddress(target.value);
setAdminAddressError("");
}}
error={adminAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<JsonEditor
label="Msg JSON"
content={{ text: msgContent }}
onChange={(newMsgContent, _, { contentErrors }) => {
setMsgContent("text" in newMsgContent ? newMsgContent.text ?? "{}" : "{}");
jsonError.current = !!contentErrors;
}}
/>
</div>
<div className="form-item form-select">
<label>Choose a denom:</label>
<Select
label="Select denom"
name="denom-select"
options={denomOptions}
value={selectedDenom}
onChange={(option: (typeof denomOptions)[number]) => {
setSelectedDenom(option);
if (option.value !== customDenomOption.value) {
setCustomDenom("");
}
setCustomDenomError("");
}}
/>
</div>
{selectedDenom.value === customDenomOption.value ? (
<div className="form-item">
<Input
label="Custom denom"
name="custom-denom"
value={customDenom}
onChange={({ target }) => {
setCustomDenom(target.value);
setCustomDenomError("");
}}
placeholder={
selectedDenom.value === customDenomOption.value
? "Enter custom denom"
: "Select Custom denom above"
}
disabled={selectedDenom.value !== customDenomOption.value}
error={customDenomError}
/>
</div>
) : null}
<div className="form-item">
<Input
type="number"
label="Amount"
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
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 MsgInstantiateContractForm;
@@ -0,0 +1,157 @@
import { MsgMigrateContractEncodeObject } from "@cosmjs/cosmwasm-stargate";
import { toUtf8 } from "@cosmjs/encoding";
import dynamic from "next/dynamic";
import { useEffect, useRef, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
const JsonEditor = dynamic(() => import("../../../inputs/JsonEditor"), { ssr: false });
interface MsgMigrateContractFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgMigrateContractForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgMigrateContractFormProps) => {
const { chain } = useChains();
const [contractAddress, setContractAddress] = useState("");
const [codeId, setCodeId] = useState("");
const [msgContent, setMsgContent] = useState("{}");
const jsonError = useRef(false);
const [contractAddressError, setContractAddressError] = useState("");
const [codeIdError, setCodeIdError] = useState("");
const trimmedInputs = trimStringsObj({ contractAddress, codeId });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { contractAddress, codeId } = trimmedInputs;
const isMsgValid = (): boolean => {
setContractAddressError("");
setCodeIdError("");
if (jsonError.current) {
return false;
}
const addressErrorMsg = checkAddress(contractAddress, chain.addressPrefix);
if (addressErrorMsg) {
setContractAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
return false;
}
if (!codeId || !Number.isSafeInteger(Number(codeId)) || Number(codeId) <= 0) {
setCodeIdError("Code ID must be a positive integer");
return false;
}
return true;
};
const msgContentUtf8Array = (() => {
try {
// The JsonEditor does not escape \n or remove whitespaces, so we need to parse + stringify
return toUtf8(JSON.stringify(JSON.parse(msgContent)));
} catch {
return undefined;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.MigrateContract].fromPartial({
sender: senderAddress,
contract: contractAddress,
codeId: BigInt(codeId),
msg: msgContentUtf8Array,
});
const msg: MsgMigrateContractEncodeObject = {
typeUrl: MsgTypeUrls.MigrateContract,
value: msgValue,
};
setMsgGetter({ isMsgValid, msg });
}, [chain.addressPrefix, chain.chainId, msgContent, senderAddress, setMsgGetter, trimmedInputs]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgMigrateContract</h2>
<div className="form-item">
<Input
label="Contract Address"
name="contract-address"
value={contractAddress}
onChange={({ target }) => {
setContractAddress(target.value);
setContractAddressError("");
}}
error={contractAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<Input
label="Code ID"
name="code-id"
value={codeId}
onChange={({ target }) => {
setCodeId(target.value);
setCodeIdError("");
}}
error={codeIdError}
/>
</div>
<div className="form-item">
<JsonEditor
label="Msg JSON"
content={{ text: msgContent }}
onChange={(newMsgContent, _, { contentErrors }) => {
setMsgContent("text" in newMsgContent ? newMsgContent.text ?? "{}" : "{}");
jsonError.current = !!contentErrors;
}}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
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 MsgMigrateContractForm;
@@ -0,0 +1,221 @@
import { MsgSendEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { RegistryAsset } from "../../../../types/chainRegistry";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import Select from "../../../inputs/Select";
import StackableContainer from "../../../layout/StackableContainer";
const customDenomOption = { label: "Custom (enter denom below)", value: "custom" } as const;
const getDenomOptions = (assets: readonly RegistryAsset[]) => {
if (!assets?.length) {
return [customDenomOption];
}
return [...assets.map((asset) => ({ label: asset.symbol, value: asset })), customDenomOption];
};
interface MsgSendFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgSendForm = ({ senderAddress, setMsgGetter, deleteMsg }: MsgSendFormProps) => {
const { chain } = useChains();
const denomOptions = getDenomOptions(chain.assets);
const [toAddress, setToAddress] = useState("");
const [selectedDenom, setSelectedDenom] = useState(denomOptions[0]);
const [customDenom, setCustomDenom] = useState("");
const [amount, setAmount] = useState("0");
const [toAddressError, setToAddressError] = useState("");
const [customDenomError, setCustomDenomError] = useState("");
const [amountError, setAmountError] = useState("");
const trimmedInputs = trimStringsObj({ toAddress, customDenom, amount });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { toAddress, customDenom, amount } = trimmedInputs;
const isMsgValid = (): boolean => {
setToAddressError("");
setCustomDenomError("");
setAmountError("");
const addressErrorMsg = checkAddress(toAddress, chain.addressPrefix);
if (addressErrorMsg) {
setToAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
return false;
}
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
if (selectedDenom.value === customDenomOption.value && !Number.isInteger(Number(amount))) {
setAmountError("Amount cannot be decimal for custom denom");
return false;
}
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const denom =
selectedDenom.value === customDenomOption.value ? customDenom : selectedDenom.value.symbol;
const microCoin = (() => {
try {
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return null;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.Send].fromPartial({
fromAddress: senderAddress,
toAddress,
amount: microCoin ? [microCoin] : [],
});
const msg: MsgSendEncodeObject = { typeUrl: MsgTypeUrls.Send, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [
chain.addressPrefix,
chain.assets,
chain.chainId,
selectedDenom.value,
senderAddress,
setMsgGetter,
trimmedInputs,
]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgSend</h2>
<div className="form-item">
<Input
label="Recipient Address"
name="recipient-address"
value={toAddress}
onChange={({ target }) => {
setToAddress(target.value);
setToAddressError("");
}}
error={toAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item form-select">
<label>Choose a denom:</label>
<Select
label="Select denom"
name="denom-select"
options={denomOptions}
value={selectedDenom}
onChange={(option: (typeof denomOptions)[number]) => {
setSelectedDenom(option);
if (option.value !== customDenomOption.value) {
setCustomDenom("");
}
setCustomDenomError("");
}}
/>
</div>
{selectedDenom.value === customDenomOption.value ? (
<div className="form-item">
<Input
label="Custom denom"
name="custom-denom"
value={customDenom}
onChange={({ target }) => {
setCustomDenom(target.value);
setCustomDenomError("");
}}
placeholder={
selectedDenom.value === customDenomOption.value
? "Enter custom denom"
: "Select Custom denom above"
}
disabled={selectedDenom.value !== customDenomOption.value}
error={customDenomError}
/>
</div>
) : null}
<div className="form-item">
<Input
type="number"
label="Amount"
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
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 MsgSendForm;
@@ -0,0 +1,92 @@
import { EncodeObject } from "@cosmjs/proto-signing";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
interface MsgSetWithdrawAddressFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgSetWithdrawAddressForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgSetWithdrawAddressFormProps) => {
const { chain } = useChains();
const [withdrawAddress, setWithdrawAddress] = useState("");
const [withdrawAddressError, setWithdrawAddressError] = useState("");
const trimmedInputs = trimStringsObj({ withdrawAddress });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { withdrawAddress } = trimmedInputs;
const isMsgValid = (): boolean => {
setWithdrawAddressError("");
const addressErrorMsg = checkAddress(withdrawAddress, chain.addressPrefix);
if (addressErrorMsg) {
setWithdrawAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
return false;
}
return true;
};
const msgValue = MsgCodecs[MsgTypeUrls.SetWithdrawAddress].fromPartial({
delegatorAddress: senderAddress,
withdrawAddress,
});
const msg: EncodeObject = { typeUrl: MsgTypeUrls.SetWithdrawAddress, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [chain.addressPrefix, chain.chainId, senderAddress, setMsgGetter, trimmedInputs]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgSetWithdrawAddress</h2>
<div className="form-item">
<Input
label="Withdraw Address"
name="withdraw-address"
value={withdrawAddress}
onChange={({ target }) => {
setWithdrawAddress(target.value);
setWithdrawAddressError("");
}}
error={withdrawAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<style jsx>{`
.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 MsgSetWithdrawAddressForm;
@@ -0,0 +1,260 @@
import { MsgTransferEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import {
datetimeLocalFromTimestamp,
timestampFromDatetimeLocal,
} from "../../../../lib/dateHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
const humanTimestampOptions = [
{ label: "12 hours from now", value: 12 * 60 * 60 * 1000 },
{ label: "1 day from now", value: 24 * 60 * 60 * 1000 },
{ label: "2 days from now", value: 2 * 24 * 60 * 60 * 1000 },
{ label: "3 days from now", value: 3 * 24 * 60 * 60 * 1000 },
{ label: "7 days from now", value: 7 * 24 * 60 * 60 * 1000 },
{ label: "10 days from now", value: 10 * 24 * 60 * 60 * 1000 },
{ label: "2 weeks from now", value: 2 * 7 * 24 * 60 * 60 * 1000 },
{ label: "3 weeks from now", value: 3 * 7 * 24 * 60 * 60 * 1000 },
{ label: "1 month from now", value: 30 * 24 * 60 * 60 * 1000 },
];
interface MsgTransferFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgTransferForm = ({ senderAddress, setMsgGetter, deleteMsg }: MsgTransferFormProps) => {
const { chain } = useChains();
const [toAddress, setToAddress] = useState("");
const [denom, setDenom] = useState("");
const [amount, setAmount] = useState("0");
const [sourcePort, setSourcePort] = useState("transfer");
const [sourceChannel, setSourceChannel] = useState("");
const [timeout, setTimeout] = useState(
datetimeLocalFromTimestamp(Date.now() + humanTimestampOptions[0].value),
);
const [memo, setMemo] = useState("");
const [toAddressError, setToAddressError] = useState("");
const [denomError, setDenomError] = useState("");
const [amountError, setAmountError] = useState("");
const [sourcePortError, setSourcePortError] = useState("");
const [sourceChannelError, setSourceChannelError] = useState("");
const [timeoutError, setTimeoutError] = useState("");
const trimmedInputs = trimStringsObj({
toAddress,
denom,
amount,
sourcePort,
sourceChannel,
timeout,
memo,
});
useEffect(() => {
// eslint-disable-next-line no-shadow
const { toAddress, denom, amount, sourcePort, sourceChannel, timeout, memo } = trimmedInputs;
const isMsgValid = (): boolean => {
setToAddressError("");
setDenomError("");
setAmountError("");
setSourcePortError("");
setSourceChannelError("");
setTimeoutError("");
const addressErrorMsg = checkAddress(toAddress, null); // Allow address from any chain
if (addressErrorMsg) {
setToAddressError(`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`);
return false;
}
if (!denom) {
setDenomError("Denom is required");
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
if (!sourcePort) {
setSourcePortError("Source port is required");
return false;
}
if (!sourceChannel) {
setSourceChannelError("Source channel is required");
return false;
}
const timeoutDate = new Date(Number(timestampFromDatetimeLocal(timeout, "ms")));
if (timeoutDate <= new Date()) {
setTimeoutError("Timeout must be a date in the future");
return false;
}
return true;
};
const msgValue = MsgCodecs[MsgTypeUrls.Transfer].fromPartial({
sender: senderAddress,
receiver: toAddress,
token: { denom, amount },
sourcePort,
sourceChannel,
timeoutTimestamp: timestampFromDatetimeLocal(timeout, "ns"),
memo,
});
const msg: MsgTransferEncodeObject = { typeUrl: MsgTypeUrls.Transfer, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [chain.chainId, senderAddress, setMsgGetter, trimmedInputs]);
useEffect(() => {
if (!denom || !denom.startsWith("ibc/")) {
return;
}
const foundDenom = chain.assets.find((asset) => asset.base === denom);
if (!foundDenom) {
return;
}
const trace = foundDenom.traces?.[0];
if (!trace) {
return;
}
setSourcePort(trace.chain?.path?.split("/")?.[0] || "transfer");
setSourceChannel(trace.chain?.channel_id || "");
}, [chain.assets, denom]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgTransfer</h2>
<div className="form-item">
<Input
label="Recipient Address"
name="recipient-address"
value={toAddress}
onChange={({ target }) => {
setToAddress(target.value);
setToAddressError("");
}}
error={toAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<Input
label="Denom"
name="denom"
value={denom}
onChange={({ target }) => {
setDenom(target.value);
setDenomError("");
}}
error={denomError}
/>
</div>
<div className="form-item">
<Input
type="number"
label="Amount"
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<div className="form-item">
<Input
label="Source Port"
name="source-port"
value={sourcePort}
onChange={({ target }) => {
setSourcePort(target.value);
setSourcePortError("");
}}
error={sourcePortError}
/>
</div>
<div className="form-item">
<Input
label="Source Channel"
name="source-channel"
value={sourceChannel}
onChange={({ target }) => {
setSourceChannel(target.value);
setSourceChannelError("");
}}
error={sourceChannelError}
/>
</div>
<div className="form-item">
<Input
type="datetime-local"
list="timestamp-options"
label="Timeout"
name="timeout"
value={timeout}
onChange={({ target }) => {
setTimeout(target.value);
setTimeoutError("");
}}
error={timeoutError}
/>
<datalist id="timestamp-options">
{humanTimestampOptions.map(({ label, value }) => (
<option key={label} value={datetimeLocalFromTimestamp(Date.now() + value)}>
{label}
</option>
))}
</datalist>
</div>
<div className="form-item">
<Input
label="Memo"
name="memo"
value={memo}
onChange={({ target }) => setMemo(target.value)}
/>
</div>
<style jsx>{`
.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 MsgTransferForm;
@@ -0,0 +1,143 @@
import SelectValidator from "@/components/SelectValidator";
import { MsgUndelegateEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
interface MsgUndelegateFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgUndelegateForm = ({ senderAddress, setMsgGetter, deleteMsg }: MsgUndelegateFormProps) => {
const { chain } = useChains();
const [validatorAddress, setValidatorAddress] = useState("");
const [amount, setAmount] = useState("0");
const [validatorAddressError, setValidatorAddressError] = useState("");
const [amountError, setAmountError] = useState("");
const trimmedInputs = trimStringsObj({ validatorAddress, amount });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { validatorAddress, amount } = trimmedInputs;
const isMsgValid = (): boolean => {
setValidatorAddressError("");
setAmountError("");
const addressErrorMsg = checkAddress(validatorAddress, chain.addressPrefix);
if (addressErrorMsg) {
setValidatorAddressError(
`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`,
);
return false;
}
if (!amount || Number(amount) <= 0) {
setAmountError("Amount must be greater than 0");
return false;
}
try {
displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const microCoin = (() => {
try {
return displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch {
return { denom: chain.displayDenom, amount: "0" };
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.Undelegate].fromPartial({
delegatorAddress: senderAddress,
validatorAddress,
amount: microCoin,
});
const msg: MsgUndelegateEncodeObject = { typeUrl: MsgTypeUrls.Undelegate, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [
chain.addressPrefix,
chain.assets,
chain.chainId,
chain.displayDenom,
senderAddress,
setMsgGetter,
trimmedInputs,
]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgUndelegate</h2>
<div className="form-item">
<SelectValidator
validatorAddress={validatorAddress}
setValidatorAddress={setValidatorAddress}
/>
<Input
label="Validator Address"
name="validator-address"
value={validatorAddress}
onChange={({ target }) => {
setValidatorAddress(target.value);
setValidatorAddressError("");
}}
error={validatorAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<Input
type="number"
label={`Amount (${chain.displayDenom})`}
name="amount"
value={amount}
onChange={({ target }) => {
setAmount(target.value);
setAmountError("");
}}
error={amountError}
/>
</div>
<style jsx>{`
.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 MsgUndelegateForm;
@@ -0,0 +1,133 @@
import { MsgUpdateAdminEncodeObject } from "@cosmjs/cosmwasm-stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
interface MsgUpdateAdminFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgUpdateAdminForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgUpdateAdminFormProps) => {
const { chain } = useChains();
const [contractAddress, setContractAddress] = useState("");
const [newAdminAddress, setNewAdminAddress] = useState("");
const [contractAddressError, setContractAddressError] = useState("");
const [newAdminAddressError, setNewAdminAddressError] = useState("");
const trimmedInputs = trimStringsObj({ contractAddress, newAdminAddress });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { contractAddress, newAdminAddress } = trimmedInputs;
const isMsgValid = (): boolean => {
setContractAddressError("");
setNewAdminAddressError("");
const contractAddressErrorMsg = checkAddress(contractAddress, chain.addressPrefix);
if (contractAddressErrorMsg) {
setContractAddressError(
`Invalid address for network ${chain.chainId}: ${contractAddressErrorMsg}`,
);
return false;
}
const newAdminAddressErrorMsg = checkAddress(newAdminAddress, chain.addressPrefix);
if (newAdminAddressErrorMsg) {
setNewAdminAddressError(
`Invalid address for network ${chain.chainId}: ${newAdminAddressErrorMsg}`,
);
return false;
}
return true;
};
const msgValue = MsgCodecs[MsgTypeUrls.UpdateAdmin].fromPartial({
sender: senderAddress,
contract: contractAddress,
newAdmin: newAdminAddress,
});
const msg: MsgUpdateAdminEncodeObject = { typeUrl: MsgTypeUrls.UpdateAdmin, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [chain.addressPrefix, chain.chainId, senderAddress, setMsgGetter, trimmedInputs]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgUpdateAdmin</h2>
<div className="form-item">
<Input
label="Contract Address"
name="contract-address"
value={contractAddress}
onChange={({ target }) => {
setContractAddress(target.value);
setContractAddressError("");
}}
error={contractAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<div className="form-item">
<Input
label="New Admin"
name="new-admin"
value={newAdminAddress}
onChange={({ target }) => {
setNewAdminAddress(target.value);
setNewAdminAddressError("");
}}
error={newAdminAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
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 MsgUpdateAdminForm;
@@ -0,0 +1,137 @@
import { printVoteOption, voteOptions } from "@/lib/gov";
import { MsgVoteEncodeObject } from "@cosmjs/stargate";
import { longify } from "@cosmjs/stargate/build/queryclient";
import { voteOptionFromJSON } from "cosmjs-types/cosmos/gov/v1beta1/gov";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import Select from "../../../inputs/Select";
import StackableContainer from "../../../layout/StackableContainer";
const selectVoteOptions = voteOptions.map((opt) => {
const voteOptionObj = voteOptionFromJSON(opt);
return {
label: printVoteOption(voteOptionObj),
value: voteOptionObj,
};
});
interface MsgVoteFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgVoteForm = ({ senderAddress, setMsgGetter, deleteMsg }: MsgVoteFormProps) => {
const [proposalId, setProposalId] = useState("0");
const [selectedVote, setSelectedVote] = useState(selectVoteOptions[0]);
const [proposalIdError, setProposalIdError] = useState("");
const trimmedInputs = trimStringsObj({ proposalId });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { proposalId } = trimmedInputs;
const isMsgValid = (): boolean => {
setProposalIdError("");
if (!proposalId || Number(proposalId) <= 0 || !Number.isSafeInteger(Number(proposalId))) {
setProposalIdError("Proposal ID must be an integer greater than 0");
return false;
}
try {
longify(proposalId);
} catch (e: unknown) {
setProposalIdError(e instanceof Error ? e.message : "Proposal ID is not a valid Big Int");
return false;
}
return true;
};
const proposalIdBigInt = (() => {
try {
return longify(proposalId);
} catch {
return 0n;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.Vote].fromPartial({
voter: senderAddress,
proposalId: proposalIdBigInt,
option: selectedVote.value,
});
const msg: MsgVoteEncodeObject = { typeUrl: MsgTypeUrls.Vote, value: msgValue };
setMsgGetter({ isMsgValid, msg });
}, [selectedVote.value, senderAddress, setMsgGetter, trimmedInputs]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgVote</h2>
<div className="form-item">
<Input
type="number"
label="Proposal ID"
name="proposal-id"
value={proposalId}
onChange={({ target }) => {
setProposalId(target.value);
setProposalIdError("");
}}
error={proposalIdError}
/>
</div>
<div className="form-item form-select">
<label>Choose a vote:</label>
<Select
label="Select vote"
name="vote-select"
options={selectVoteOptions}
value={selectedVote}
onChange={(option: (typeof selectVoteOptions)[number]) => {
setSelectedVote(option);
}}
/>
</div>
<style jsx>{`
.form-item {
margin-top: 1.5em;
}
.form-item label {
font-style: italic;
font-size: 12px;
}
.form-select {
display: flex;
flex-direction: column;
gap: 0.8em;
}
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 MsgVoteForm;
@@ -0,0 +1,103 @@
import SelectValidator from "@/components/SelectValidator";
import { MsgWithdrawDelegatorRewardEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
import StackableContainer from "../../../layout/StackableContainer";
interface MsgWithdrawDelegatorRewardFormProps {
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgWithdrawDelegatorRewardForm = ({
senderAddress,
setMsgGetter,
deleteMsg,
}: MsgWithdrawDelegatorRewardFormProps) => {
const { chain } = useChains();
const [validatorAddress, setValidatorAddress] = useState("");
const [validatorAddressError, setValidatorAddressError] = useState("");
const trimmedInputs = trimStringsObj({ validatorAddress });
useEffect(() => {
// eslint-disable-next-line no-shadow
const { validatorAddress } = trimmedInputs;
const isMsgValid = (): boolean => {
setValidatorAddressError("");
const addressErrorMsg = checkAddress(validatorAddress, chain.addressPrefix);
if (addressErrorMsg) {
setValidatorAddressError(
`Invalid address for network ${chain.chainId}: ${addressErrorMsg}`,
);
return false;
}
return true;
};
const msgValue = MsgCodecs[MsgTypeUrls.WithdrawDelegatorReward].fromPartial({
delegatorAddress: senderAddress,
validatorAddress,
});
const msg: MsgWithdrawDelegatorRewardEncodeObject = {
typeUrl: MsgTypeUrls.WithdrawDelegatorReward,
value: msgValue,
};
setMsgGetter({ isMsgValid, msg });
}, [chain.addressPrefix, chain.chainId, senderAddress, setMsgGetter, trimmedInputs]);
return (
<StackableContainer lessPadding lessMargin>
<button className="remove" onClick={() => deleteMsg()}>
</button>
<h2>MsgWithdrawDelegatorReward</h2>
<div className="form-item">
<SelectValidator
validatorAddress={validatorAddress}
setValidatorAddress={setValidatorAddress}
/>
<Input
label="Validator Address"
name="validator-address"
value={validatorAddress}
onChange={({ target }) => {
setValidatorAddress(target.value);
setValidatorAddressError("");
}}
error={validatorAddressError}
placeholder={`E.g. ${exampleAddress(0, chain.addressPrefix)}`}
/>
</div>
<style jsx>{`
.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 MsgWithdrawDelegatorRewardForm;
@@ -0,0 +1,70 @@
import { MsgGetter } from "..";
import { MsgTypeUrl, MsgTypeUrls } from "../../../../types/txMsg";
import MsgBeginRedelegateForm from "./MsgBeginRedelegateForm";
import MsgCreateVestingAccountForm from "./MsgCreateVestingAccountForm";
import MsgDelegateForm from "./MsgDelegateForm";
import MsgExecuteContractForm from "./MsgExecuteContractForm";
import MsgFundCommunityPoolForm from "./MsgFundCommunityPoolForm";
import MsgInstantiateContract2Form from "./MsgInstantiateContract2Form";
import MsgInstantiateContractForm from "./MsgInstantiateContractForm";
import MsgMigrateContractForm from "./MsgMigrateContractForm";
import MsgSendForm from "./MsgSendForm";
import MsgSetWithdrawAddressForm from "./MsgSetWithdrawAddressForm";
import MsgTransferForm from "./MsgTransferForm";
import MsgUndelegateForm from "./MsgUndelegateForm";
import MsgUpdateAdminForm from "./MsgUpdateAdminForm";
import MsgVoteForm from "./MsgVoteForm";
import MsgWithdrawDelegatorRewardForm from "./MsgWithdrawDelegatorRewardForm";
interface MsgFormProps {
readonly msgType: MsgTypeUrl;
readonly senderAddress: string;
readonly setMsgGetter: (msgGetter: MsgGetter) => void;
readonly deleteMsg: () => void;
}
const MsgForm = ({ msgType, ...restProps }: MsgFormProps) => {
switch (msgType) {
// Bank
case MsgTypeUrls.Send:
return <MsgSendForm {...restProps} />;
// Staking
case MsgTypeUrls.Delegate:
return <MsgDelegateForm {...restProps} />;
case MsgTypeUrls.Undelegate:
return <MsgUndelegateForm {...restProps} />;
case MsgTypeUrls.BeginRedelegate:
return <MsgBeginRedelegateForm {...restProps} />;
// Distribution
case MsgTypeUrls.FundCommunityPool:
return <MsgFundCommunityPoolForm {...restProps} />;
case MsgTypeUrls.SetWithdrawAddress:
return <MsgSetWithdrawAddressForm {...restProps} />;
case MsgTypeUrls.WithdrawDelegatorReward:
return <MsgWithdrawDelegatorRewardForm {...restProps} />;
// Vesting
case MsgTypeUrls.CreateVestingAccount:
return <MsgCreateVestingAccountForm {...restProps} />;
// Governance
case MsgTypeUrls.Vote:
return <MsgVoteForm {...restProps} />;
// IBC
case MsgTypeUrls.Transfer:
return <MsgTransferForm {...restProps} />;
// CosmWasm
case MsgTypeUrls.InstantiateContract:
return <MsgInstantiateContractForm {...restProps} />;
case MsgTypeUrls.InstantiateContract2:
return <MsgInstantiateContract2Form {...restProps} />;
case MsgTypeUrls.UpdateAdmin:
return <MsgUpdateAdminForm {...restProps} />;
case MsgTypeUrls.ExecuteContract:
return <MsgExecuteContractForm {...restProps} />;
case MsgTypeUrls.MigrateContract:
return <MsgMigrateContractForm {...restProps} />;
default:
return null;
}
};
export default MsgForm;