diff --git a/package-lock.json b/package-lock.json index 18fabdb..e7c990c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1207,8 +1207,8 @@ "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" }, "@everett-protocol/cosmosjs": { - "version": "git+https://github.com/everett-protocol/cosmosjs-dist.git#5b212c71b0a7983f794f068f714a6430f36bdf0b", - "from": "git+https://github.com/everett-protocol/cosmosjs-dist.git#5b212c7", + "version": "git+https://github.com/everett-protocol/cosmosjs-dist.git#f93187cecdd7b22b1ef289bce8cfc592e83c85b9", + "from": "git+https://github.com/everett-protocol/cosmosjs-dist.git#f93187c", "requires": { "@node-a-team/ts-amino": "0.0.1-alpha.2", "axios": "^0.19.0", @@ -12375,6 +12375,11 @@ "warning": "^3.0.0" } }, + "react-hook-form": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-5.7.2.tgz", + "integrity": "sha512-bJvY348vayIvEUmSK7Fvea/NgqbT2racA2IbnJz/aPlQ3GBtaTeDITH6rtCa6y++obZzG6E3Q8VuoXPir7QYUg==" + }, "react-is": { "version": "16.13.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", diff --git a/package.json b/package.json index 8aa83e6..7fa1c28 100644 --- a/package.json +++ b/package.json @@ -49,18 +49,19 @@ "write-file-webpack-plugin": "^4.5.1" }, "dependencies": { - "@everett-protocol/cosmosjs": "git+https://github.com/everett-protocol/cosmosjs-dist.git#5b212c7", - "big-integer": "^1.6.43", - "@node-a-team/ts-amino": "0.0.1-alpha.2", - "webextension-polyfill": "^0.6.0", + "@everett-protocol/cosmosjs": "git+https://github.com/everett-protocol/cosmosjs-dist.git#f93187c", "@fortawesome/fontawesome-free": "^5.13.0", + "@node-a-team/ts-amino": "0.0.1-alpha.2", "argon-dashboard-react": "^1.1.0", "axios": "^0.19.2", + "big-integer": "^1.6.43", "classnames": "^2.2.6", "react": "^16.13.0", "react-color": "^2.18.1", "react-dom": "^16.13.0", + "react-hook-form": "^5.7.2", "react-responsive-pinch-zoom-pan": "^0.3.0", - "reactstrap": "^8.4.1" + "reactstrap": "^8.4.1", + "webextension-polyfill": "^0.6.0" } } diff --git a/src/common/dec-utils/index.ts b/src/common/dec-utils/index.ts new file mode 100644 index 0000000..fbb0713 --- /dev/null +++ b/src/common/dec-utils/index.ts @@ -0,0 +1,40 @@ +import { Dec } from "@everett-protocol/cosmosjs/common/decimal"; + +export class DecUtils { + static decToStrWithoutTrailingZeros(dec: Dec): string { + return DecUtils.removeTrailingZerosFromDecStr(dec.toString()); + } + + static removeTrailingZerosFromDecStr(decStr: string): string { + for (let i = decStr.length - 1; i >= 0; i--) { + if (decStr[i] === "0") { + decStr = decStr.slice(0, i); + } else { + break; + } + } + + if (decStr.length > 0) { + if (decStr[decStr.length - 1] === ".") { + decStr = decStr.slice(0, decStr.length - 1); + } + } + + return decStr; + } + + private static precisions: { [precision: string]: Dec } = {}; + + static getPrecisionDec(precision: number): Dec { + if (DecUtils.precisions[precision.toString()]) { + return DecUtils.precisions[precision.toString()]; + } + + let dec = new Dec(1); + for (let i = 0; i < precision; i++) { + dec = dec.mul(new Dec(10)); + } + DecUtils.precisions[precision.toString()] = dec; + return dec; + } +} diff --git a/src/hooks/use-cosmosjs.ts b/src/hooks/use-cosmosjs.ts index a8d7b8f..83922e5 100644 --- a/src/hooks/use-cosmosjs.ts +++ b/src/hooks/use-cosmosjs.ts @@ -25,6 +25,7 @@ import { BACKGROUND_PORT } from "../common/message/constant"; import { AsyncWaitGroup } from "../common/async-wait-group"; import * as Canvas from "../x/canvas"; +import * as InterStaking from "../x/inter-staking"; import { BIP44 } from "@everett-protocol/cosmosjs/core/bip44"; const Buffer = require("buffer/").Buffer; @@ -88,6 +89,7 @@ export const useCosmosJS = ( Slashing.registerCodec(codec); Gov.registerCodec(codec); Canvas.registerCodec(codec); + InterStaking.registerCodec(codec); }), [opts?.registerCodec] ); diff --git a/src/hooks/use-inter-staking.ts b/src/hooks/use-inter-staking.ts new file mode 100644 index 0000000..d316630 --- /dev/null +++ b/src/hooks/use-inter-staking.ts @@ -0,0 +1,49 @@ +import { useState, useEffect } from "react"; +import { useFetch } from "./use-fetch"; +import { AccAddress } from "@everett-protocol/cosmosjs/common/address"; + +const Buffer = require("buffer/").Buffer; + +interface Result { + height: string; + result: string; +} + +/** + * @param baseUrl Url of rest endpoint + */ +export const useInterstaking = ( + baseUrl: string, + sourcePort?: string, + sourceChannel?: string, + address: string +) => { + const [url, setUrl] = useState(""); + + const [registered, setRegistered] = useState(Buffer.from([])); + + const fetch = useFetch(url, "get"); + + useEffect(() => { + setRegistered(Buffer.from([])); + + if (sourcePort && sourceChannel && address) { + setUrl(baseUrl + `/registered/${sourcePort}/${sourceChannel}/${address}`); + } + }, [baseUrl, sourcePort, sourceChannel, address]); + + useEffect(() => { + if (fetch.data && fetch.data.result) { + const result = fetch.data.result; + + setRegistered(Buffer.from(result, "base64")); + } + }, [fetch.data]); + + return { + url: fetch.url, + registered, + refresh: fetch.refresh, + fetching: fetch.fetching + }; +}; diff --git a/src/hooks/use-validator.ts b/src/hooks/use-validator.ts new file mode 100644 index 0000000..38d1d65 --- /dev/null +++ b/src/hooks/use-validator.ts @@ -0,0 +1,180 @@ +import { useState, useEffect } from "react"; +import { useFetch } from "./use-fetch"; +import { Dec } from "@everett-protocol/cosmosjs/common/decimal"; +import Axios from "axios"; + +export interface Validator { + operator_address: string; + consensus_pubkey: string; + jailed: boolean; + status: number; + tokens: string; + delegator_shares: string; + description: { + moniker: string; + identity: string; + website: string; + details: string; + }; + unbonding_height: string; + unbonding_time: string; + commission: { + commission_rates: { + rate: string; + max_rate: string; + max_change_rate: string; + }; + update_time: string; + }; + min_self_delegation: string; + /** + * The url of thumbnail image. + * This is not the native field from validator but just helper field. + * This may be the image from keybase with matched description.identity as keybase ID suffix. + * This will be fetched gradually. + * This field will be `undefined` if not yet fetched. + * This field will be empty string "" if fetched but error ocurrs or not have keybase profile image. + */ + thumbnail?: string; +} + +export interface ValidatorHook { + validators: Validator[]; + refresh: (() => Promise | undefined) | undefined; + fetching: boolean; +} + +/** + * @param baseUrl Url of rest endpoint + */ +export const useValidator = (baseUrl: string): ValidatorHook => { + const [validators, setValidators] = useState([]); + + const fetch = useFetch<{ result: Validator[] }>( + baseUrl + "/staking/validators", + "get" + ); + + useEffect(() => { + if (fetch.data && fetch.data.result) { + let validators = fetch.data.result; + validators = validators.sort((v1, v2) => { + return new Dec(v1.delegator_shares).gt(new Dec(v2.delegator_shares)) + ? -1 + : 1; + }); + + for (const val of validators) { + const thunbnail = localStorage.getItem( + `thumbnail-${val.operator_address}` + ); + val.thumbnail = thunbnail ? thunbnail : undefined; + } + + setValidators(validators); + } + }, [fetch.data]); + + useEffect(() => { + /** + * TODO: Name of variable is "isMounted". But this naming doesn't fully convey its meaning. + * This means that it will be true until this props haven't be changed. + * There will be a better naming. + */ + let isMounted = true; + + // Fetch thumbnail. + // Return whether there is a change + const fetchThumbnail = async ( + reminder: number, + len: number + ): Promise => { + let validator: Validator | undefined; + for (let i = 0; i < validators.length; i++) { + if (i % len === reminder) { + if (validators[i].thumbnail === undefined) { + validator = validators[i]; + break; + } + } + } + + if (validator) { + validator.thumbnail = ""; + try { + const result = await Axios.get<{ + status: { + code: number; + name: string; + }; + them: [ + { + id: string; + pictures: { + primary: { + url: string; + }; + }; + } + ]; + }>( + `https://keybase.io/_/api/1.0/user/lookup.json?fields=pictures&key_suffix=${validator.description.identity}` + ); + + if (result.status === 200 && result.data?.status?.code === 0) { + const them = result.data?.them; + if (them && them.length > 0) { + const pictureUrl = them[0].pictures?.primary?.url; + if (pictureUrl) { + validator.thumbnail = pictureUrl; + localStorage.setItem( + `thumbnail-${validator.operator_address}`, + pictureUrl + ); + } + } + } + } catch (e) { + console.log( + `Error occurs during fetching thumbnail for validator: ${e.toString()}` + ); + } + return true; + } + + return false; + }; + + (async () => { + if (validators && validators.length > 0) { + const fetches: Promise[] = []; + + // Fetch 10 items at once. + const len = 10; + for (let i = 0; i < len; i++) { + fetches.push(fetchThumbnail(i, len)); + } + + const result = await Promise.all(fetches); + + for (const r of result) { + if (r && isMounted) { + // If their is change, re-render the validators. + setValidators(validators.slice()); + return; + } + } + } + })(); + + return () => { + isMounted = false; + }; + }, [validators]); + + return { + validators, + refresh: fetch.refresh, + fetching: fetch.fetching + }; +}; diff --git a/src/pages/home/canvas.tsx b/src/pages/home/canvas.tsx index 19a9190..34d8e81 100644 --- a/src/pages/home/canvas.tsx +++ b/src/pages/home/canvas.tsx @@ -9,7 +9,7 @@ import React, { import { GithubPicker } from "react-color"; import { CanvasUtils } from "./utils"; -import { Color, Point, DenomToColor, AstroHubInfo } from "./constants"; +import { Color, Point, DenomToColor, AstroZoneInfo } from "./constants"; import { Button } from "reactstrap"; import { useCosmosJS } from "../../hooks/use-cosmosjs"; import { useWalletProvider } from "../../hooks/use-wallet-provider"; @@ -72,12 +72,9 @@ export const Canvas: FunctionComponent<{ const [pointsToFill, setPointsToFill] = useState([]); - const [colorToFill, setColorToFill] = useState({ - denom: "uastro", - color: DenomToColor["uastro"] - }); + const [colorToFill, setColorToFill] = useState(undefined); - const cosmosJS = useCosmosJS(AstroHubInfo, useWalletProvider(), { + const cosmosJS = useCosmosJS(AstroZoneInfo, useWalletProvider(), { useBackgroundTx: true }); @@ -131,7 +128,7 @@ export const Canvas: FunctionComponent<{ ); } - if (mousePosition.x >= 0 && mousePosition.y >= 0) { + if (colorToFill && mousePosition.x >= 0 && mousePosition.y >= 0) { CanvasUtils.drawOutlinedRect( ctx, mousePosition.x, @@ -165,7 +162,7 @@ export const Canvas: FunctionComponent<{ ); const onClick = useCallback(() => { - if (mousePosition.x >= 0 && mousePosition.y >= 0) { + if (colorToFill && mousePosition.x >= 0 && mousePosition.y >= 0) { const _pointsToFill = pointsToFill.slice(); _pointsToFill.push({ x: mousePosition.x, @@ -217,7 +214,7 @@ export const Canvas: FunctionComponent<{ "genesis", point.x, point.y, - new Coin("uastro", 1000000), + new Coin(point.color, 1000000), AccAddress.fromBech32(cosmosJS.addresses[0]) ); @@ -225,7 +222,7 @@ export const Canvas: FunctionComponent<{ } if (msgs.length > 0) { - const gas = 50000 + msgs.length * 25000; + const gas = 50000 + msgs.length * 30000; cosmosJS.sendMsgs(msgs, { gas: gas, memo: "", diff --git a/src/pages/home/constants.ts b/src/pages/home/constants.ts index e838ecf..a714e0c 100644 --- a/src/pages/home/constants.ts +++ b/src/pages/home/constants.ts @@ -1,46 +1,100 @@ import { ChainInfo } from "../../chain-info"; import { defaultBech32Config } from "@everett-protocol/cosmosjs/core/bech32Config"; -export type Point = { - x: number; - y: number; - color: string; -}; - -export type Color = { - denom: string; - color: string; -}; - -export const DenomToColor: { - [denom: string]: string; -} = { - uastro: "#ABCDEF", - test: "#BFABDE" -}; - export const AstroHubInfo: ChainInfo = { - rpc: "http://127.0.0.1:80/rpc", - rest: "http://127.0.0.1:80/rest", - chainId: "test", + rpc: "http://34.94.36.216:80/rpc", + rest: "http://34.94.36.216:80/rest", + chainId: "astrohub-1", chainName: "Astro Hub", nativeCurrency: { - coinDenom: "ASTRO", - coinMinimalDenom: "uastro", + coinDenom: "HUB", + coinMinimalDenom: "uhub", coinDecimals: 6 }, - bech32Config: defaultBech32Config("cosmos") + bech32Config: defaultBech32Config("hub") }; export const AstroZoneInfo: ChainInfo = { - rpc: "http://127.0.0.1:81/rpc", - rest: "http://127.0.0.1:81/rest", - chainId: "test2", + rpc: "http://35.235.73.59:80/rpc", + rest: "http://35.235.73.59:80/rest", + chainId: "astrocanvas-1", chainName: "Astro Zone", nativeCurrency: { coinDenom: "ASTRO", coinMinimalDenom: "uastro", coinDecimals: 6 }, - bech32Config: defaultBech32Config("cosmos") + bech32Config: defaultBech32Config("astro") +}; + +export const HubToZone = { + transfer: { + channelId: "amqggnvske", + portId: "transfer" + }, + interchainAccount: { + channelId: "amqggnvska", + portId: "interchainaccount" + } +}; + +export const ZoneToHub = { + transfer: { + channelId: "cljcoxvqrm", + portId: "transfer" + }, + interchainAccount: { + channelId: "cljcoxvqra", + portId: "interchainaccount" + } +}; + +export const CanvasId = "genesis"; + +export type Point = { + x: number; + y: number; + color: string; +}; + +export type Color = { + denom: string; + color: string; +}; + +export const DenomToColor: { + [denom: string]: string; +} = { + // validator1 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/ccadac5f/stake`]: "#E23EFF", + // validator2 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/b39a40ab/stake`]: "#92E233", + // validator3 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/87578850/stake`]: "#A16A3F", + // validator4 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/729a464b/stake`]: "#820281", + // validator5 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/d68b66dc/stake`]: "#888789", + // validator6 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/502d096c/stake`]: "#E4E4E4", + // validator7 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/1ed1caa4/stake`]: "#FFA6D1", + // validator8 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/061aaaac/stake`]: "#01E5F2", + // validator9 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/6bdad30e/stake`]: "#0082CA", + // validator10 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/820fa0b7/stake`]: "#00C000", + // validator11 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/aec5285b/stake`]: "#E79700", + // validator12 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/7e286333/stake`]: "#E6DB00", + // validator13 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/ca750c28/stake`]: "#0600EE", + // validator14 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/8fae1290/stake`]: "#E80000", + // validator15 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/3f802ab6/stake`]: "#FFFFFF", + // validator16 + [`${ZoneToHub.interchainAccount.portId}/${ZoneToHub.interchainAccount.channelId}/0052d56e/stake`]: "#000000" }; diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 76a8432..428846a 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -3,7 +3,8 @@ import React, { FunctionComponent } from "react"; import { Container, Row, Col } from "reactstrap"; import { Canvas } from "./canvas"; -import { AstroHubInfo } from "./constants"; +import { InfoView } from "./info"; +import { AstroZoneInfo, CanvasId } from "./constants"; export const PageHome: FunctionComponent = () => { return ( @@ -13,15 +14,17 @@ export const PageHome: FunctionComponent = () => {
- Col2 + + + ); diff --git a/src/pages/home/info.module.scss b/src/pages/home/info.module.scss new file mode 100644 index 0000000..5820f8b --- /dev/null +++ b/src/pages/home/info.module.scss @@ -0,0 +1,15 @@ +.color-balance { + display: flex; + align-content: center; + + p { + margin-left: 0.5rem; + margin-bottom: 0; + } +} + +.color-rect { + width: 20px; + height: 20px; + border-radius: 10px; +} diff --git a/src/pages/home/info.tsx b/src/pages/home/info.tsx new file mode 100644 index 0000000..14c41ae --- /dev/null +++ b/src/pages/home/info.tsx @@ -0,0 +1,430 @@ +import React, { + FunctionComponent, + MouseEvent, + useCallback, + useEffect, + useState +} from "react"; +import { useCosmosJS } from "../../hooks/use-cosmosjs"; +import { + AstroHubInfo, + AstroZoneInfo, + DenomToColor, + ZoneToHub +} from "./constants"; +import { useWalletProvider } from "../../hooks/use-wallet-provider"; +import { Coin } from "@everett-protocol/cosmosjs/common/coin"; +import { useValidator, Validator } from "../../hooks/use-validator"; +import { + Button, + Form, + FormGroup, + Input, + InputGroupAddon, + Label, + Modal, + InputGroupText, + Container, + Row, + Col +} from "reactstrap"; +import { MsgDelegate, MsgRegister } from "../../x/inter-staking"; +import { + AccAddress, + ValAddress +} from "@everett-protocol/cosmosjs/common/address"; +import { useInterstaking } from "../../hooks/use-inter-staking"; +import { Dec } from "@everett-protocol/cosmosjs/common/decimal"; +import { DecUtils } from "../../common/dec-utils"; +import { useForm } from "react-hook-form"; +import InputGroup from "reactstrap/lib/InputGroup"; + +import style from "./info.module.scss"; + +const Buffer = require("buffer/").Buffer; + +export const DelegateModal: FunctionComponent<{ + validator: string; + balance?: Coin; + closeModal: () => void; +}> = ({ validator, balance, closeModal }) => { + const cosmosJS = useCosmosJS(AstroZoneInfo, useWalletProvider(), { + useBackgroundTx: true + }); + + const form = useForm<{ + amount: string; + }>({ + defaultValues: { + amount: "" + } + }); + + return ( +
+
{ + if (cosmosJS.sendMsgs && cosmosJS.addresses.length > 0) { + const valAddress = ValAddress.fromBech32(validator); + // @ts-ignore + valAddress.bech32Prefix = + AstroZoneInfo.bech32Config.bech32PrefixValAddr; + + const msg = new MsgDelegate( + ZoneToHub.transfer.portId, + ZoneToHub.transfer.channelId, + ZoneToHub.interchainAccount.portId, + ZoneToHub.interchainAccount.channelId, + AstroHubInfo.bech32Config.bech32PrefixAccAddr, + AccAddress.fromBech32(cosmosJS.addresses[0]), + valAddress, + new Coin( + `${ZoneToHub.transfer.portId}/${ZoneToHub.transfer.channelId}/${AstroHubInfo.nativeCurrency.coinMinimalDenom}`, + new Dec(data.amount) + .mul( + DecUtils.getPrecisionDec( + AstroHubInfo.nativeCurrency.coinDecimals + ) + ) + .truncate() + ) + ); + + cosmosJS.sendMsgs( + [msg], + { + gas: 400000, + memo: "", + fee: new Coin(AstroZoneInfo.nativeCurrency.coinMinimalDenom, 1) + }, + closeModal + ); + } + })} + > + + + + + + + {AstroHubInfo.nativeCurrency.coinDenom} + + + {form.errors?.amount ? ( +
+ {form.errors.amount.message} +
+ ) : null} +
+
+ +
+
+ ); +}; + +export const InfoView: FunctionComponent = () => { + const zoneCosmosJS = useCosmosJS(AstroZoneInfo, useWalletProvider(), { + useBackgroundTx: true + }); + const zoneInterstaking = useInterstaking( + AstroZoneInfo.rest, + ZoneToHub.interchainAccount.portId, + ZoneToHub.interchainAccount.channelId, + zoneCosmosJS.addresses[0] + ); + const hubValidators = useValidator(AstroHubInfo.rest); + + const [zoneBalances, setZoneBalances] = useState([]); + const [availableBalanceToDelegate, setAvailableBalanceToDelegate] = useState< + Coin | undefined + >(undefined); + + useEffect(() => { + const expectedDenom = `${ZoneToHub.transfer.portId}/${ZoneToHub.transfer.channelId}/${AstroHubInfo.nativeCurrency.coinMinimalDenom}`; + for (const bal of zoneBalances) { + if (bal.denom === expectedDenom) { + setAvailableBalanceToDelegate(bal); + return; + } + } + setAvailableBalanceToDelegate(new Coin(expectedDenom, 0)); + }, [zoneBalances]); + + useEffect(() => { + if (zoneCosmosJS.addresses.length > 0 && zoneCosmosJS.queryAccount) { + zoneCosmosJS.queryAccount(zoneCosmosJS.addresses[0]).then(account => { + setZoneBalances(account.getCoins()); + }); + } + }, [zoneCosmosJS.addresses, zoneCosmosJS.queryAccount]); + + const register = useCallback(() => { + if (zoneCosmosJS.addresses.length > 0 && zoneCosmosJS.sendMsgs) { + const msg = new MsgRegister( + ZoneToHub.interchainAccount.portId, + ZoneToHub.interchainAccount.channelId, + AccAddress.fromBech32(zoneCosmosJS.addresses[0]) + ); + + zoneCosmosJS.sendMsgs([msg], { + gas: 200000, + memo: "", + fee: [new Coin(AstroZoneInfo.nativeCurrency.coinMinimalDenom, 1)] + }); + } + }, [zoneCosmosJS.addresses, zoneCosmosJS.sendMsgs]); + + const [delegateModal, setDelegateModal] = useState(); + const openDelegateModal = useCallback((val: string) => { + setDelegateModal(val); + }, []); + const closeDelegateModal = useCallback(() => { + setDelegateModal(undefined); + }, []); + + return ( +
+ + + +

+ {`Address: ${ + zoneCosmosJS.addresses.length > 0 + ? zoneCosmosJS.addresses[0] + : "unknown" + }`} +

+

Your Colors

+ + {zoneInterstaking.registered.length === 0 ? ( + + ) : null} +

Delegate to get Colors

+

{`Available balance to delegate: ${ + availableBalanceToDelegate + ? DecUtils.decToStrWithoutTrailingZeros( + new Dec(availableBalanceToDelegate.amount).quoTruncate( + DecUtils.getPrecisionDec( + AstroHubInfo.nativeCurrency.coinDecimals + ) + ) + ) + : "?" + } ${AstroHubInfo.nativeCurrency.coinDenom}`}

+ +
+ ); +}; + +export const ColorBalance: FunctionComponent<{ + balances: Coin[]; +}> = ({ balances }) => { + const colorDenoms = Object.keys(DenomToColor); + + const getBalanceByDenom = (denom: string) => { + return balances.find(b => b.denom === denom); + }; + + return ( + + + + {colorDenoms + .slice(0, Math.floor(colorDenoms.length / 2)) + .map(denom => { + const bal = getBalanceByDenom(denom); + + return ( +
+ +

+ {bal + ? new Dec(bal.amount) + .quoTruncate( + DecUtils.getPrecisionDec( + AstroZoneInfo.nativeCurrency.coinDecimals + ) + ) + .truncate() + .toString() + : "0"} +

+
+ ); + })} + + + {colorDenoms.slice(Math.floor(colorDenoms.length / 2)).map(denom => { + const bal = getBalanceByDenom(denom); + + return ( +
+ +

+ {bal + ? new Dec(bal.amount) + .quoTruncate( + DecUtils.getPrecisionDec( + AstroZoneInfo.nativeCurrency.coinDecimals + ) + ) + .truncate() + .toString() + : "0"} +

+
+ ); + })} + +
+
+ ); +}; + +export const ColorRect: FunctionComponent<{ + fill: string; +}> = ({ fill }) => { + return ( +
+ ); +}; + +export const ColorValidators: FunctionComponent<{ + validators: Validator[]; + delegateFn: (val: string) => void; +}> = props => { + const getValidatorDenom = (validator: Validator) => { + const valAddress = ValAddress.fromBech32(validator.operator_address); + const buf = Buffer.from(valAddress.toBytes()); + return `${ZoneToHub.interchainAccount.portId}/${ + ZoneToHub.interchainAccount.channelId + }/${buf.slice(0, 4).toString("hex")}/stake`; + }; + + const validators = props.validators + .filter(val => { + return Object.keys(DenomToColor).includes(getValidatorDenom(val)); + }) + .sort((v1, v2) => + v1.description.moniker.localeCompare(v2.description.moniker) + ); + + const delegate = useCallback( + (e: MouseEvent) => { + const validator = e.currentTarget.getAttribute("data-validator"); + if (validator) { + props.delegateFn(validator); + } + }, + [props.delegateFn] + ); + + return ( + + + + {validators.slice(0, Math.floor(validators.length / 2)).map(val => { + const denom = getValidatorDenom(val); + + return ( +
+ +

{val.description.moniker}

+

+ {new Dec(val.tokens) + .quoTruncate( + DecUtils.getPrecisionDec( + AstroHubInfo.nativeCurrency.coinDecimals + ) + ) + .truncate() + .toString()} +

+ +
+ ); + })} + + + {validators.slice(Math.floor(validators.length / 2)).map(val => { + const denom = getValidatorDenom(val); + + return ( +
+ +

{val.description.moniker}

+

+ {new Dec(val.tokens) + .quoTruncate( + DecUtils.getPrecisionDec( + AstroHubInfo.nativeCurrency.coinDecimals + ) + ) + .truncate() + .toString()} +

+ +
+ ); + })} + +
+
+ ); +}; diff --git a/src/x/inter-staking/codec.ts b/src/x/inter-staking/codec.ts new file mode 100644 index 0000000..10327b4 --- /dev/null +++ b/src/x/inter-staking/codec.ts @@ -0,0 +1,7 @@ +import { Codec } from "@node-a-team/ts-amino"; +import { MsgRegister, MsgDelegate } from "./msgs"; + +export function registerCodec(codec: Codec) { + codec.registerConcrete("interstaking/MsgRegister", MsgRegister.prototype); + codec.registerConcrete("interstaking/MsgDelegate", MsgDelegate.prototype); +} diff --git a/src/x/inter-staking/index.ts b/src/x/inter-staking/index.ts new file mode 100644 index 0000000..3173262 --- /dev/null +++ b/src/x/inter-staking/index.ts @@ -0,0 +1,2 @@ +export * from "./msgs"; +export * from "./codec"; diff --git a/src/x/inter-staking/msgs.ts b/src/x/inter-staking/msgs.ts new file mode 100644 index 0000000..2ddc320 --- /dev/null +++ b/src/x/inter-staking/msgs.ts @@ -0,0 +1,140 @@ +import { Amino } from "@node-a-team/ts-amino"; +const { Field, DefineStruct } = Amino; +import { Msg } from "@everett-protocol/cosmosjs/core/tx"; +import { Coin } from "@everett-protocol/cosmosjs/common/coin"; +import { + AccAddress, + ValAddress +} from "@everett-protocol/cosmosjs/common/address"; + +/* +type MsgRegister struct { + // the port on which the packet will be sent + SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty" yaml:"source_port"` + // the channel by which the packet will be sent + SourceChannel string `protobuf:"bytes,2,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty" yaml:"source_channel"` + Sender github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,3,opt,name=sender,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"sender,omitempty"` +} + */ + +@DefineStruct() +export class MsgRegister extends Msg { + @Field.String(0, { + jsonName: "source_port" + }) + public sourcePort: string; + + @Field.String(1, { + jsonName: "source_channel" + }) + public sourceChannel: string; + + @Field.Defined(2, { + jsonName: "sender" + }) + public sender: AccAddress; + + constructor(sourcePort: string, sourceChannel: string, sender: AccAddress) { + super(); + + this.sourcePort = sourcePort; + this.sourceChannel = sourceChannel; + this.sender = sender; + } + + public getSigners(): AccAddress[] { + return [this.sender]; + } + + public validateBasic(): void { + // noop + } +} + +/* +type MsgDelegate struct { + // the port on which the packet will be sent + TransferSourcePort string `protobuf:"bytes,1,opt,name=transfer_source_port,json=transferSourcePort,proto3" json:"transfer_source_port,omitempty" yaml:"transfer_source_port"` + // the channel by which the packet will be sent + TransferSourceChannel string `protobuf:"bytes,2,opt,name=transfer_source_channel,json=transferSourceChannel,proto3" json:"transfer_source_channel,omitempty" yaml:"transfer_source_channel"` + InterchainAccountSourcePort string `protobuf:"bytes,3,opt,name=interchain_account_source_port,json=interchainAccountSourcePort,proto3" json:"interchain_account_source_port,omitempty" yaml:"interchain_account_source_port"` + InterchainAccountSourceChannel string `protobuf:"bytes,4,opt,name=interchain_account_source_channel,json=interchainAccountSourceChannel,proto3" json:"interchain_account_source_channel,omitempty" yaml:"interchain_account_source_channel"` + CounterpartyBech32Addr string `protobuf:"bytes,5,opt,name=counterparty_bech32_addr,json=counterpartyBech32Addr,proto3" json:"counterparty_bech32_addr,omitempty" yaml:"counterparty_bech32_addr"` + DelegatorAddress github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,6,opt,name=delegator_address,json=delegatorAddress,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"delegator_address,omitempty" yaml:"delegator_address"` + ValidatorAddress github_com_cosmos_cosmos_sdk_types.ValAddress `protobuf:"bytes,7,opt,name=validator_address,json=validatorAddress,proto3,casttype=github.com/cosmos/cosmos-sdk/types.ValAddress" json:"validator_address,omitempty" yaml:"validator_address"` + Amount types.Coin `protobuf:"bytes,8,opt,name=amount,proto3" json:"amount"` +} + */ + +@DefineStruct() +export class MsgDelegate extends Msg { + @Field.String(0, { + jsonName: "transfer_source_port" + }) + public transferSourcePort: string; + + @Field.String(1, { + jsonName: "transfer_source_channel" + }) + public transferSourceChannel: string; + + @Field.String(2, { + jsonName: "interchain_account_source_port" + }) + public interchainAccountSourcePort: string; + + @Field.String(3, { + jsonName: "interchain_account_source_channel" + }) + public interchainAccountSourceChannel: string; + + @Field.String(4, { + jsonName: "counterparty_bech32_addr" + }) + public counterpartyBech32Addr: string; + + @Field.Defined(5, { + jsonName: "delegator_address" + }) + public delegatorAddress: AccAddress; + + @Field.Defined(6, { + jsonName: "validator_address" + }) + public validatorAddress: ValAddress; + + @Field.Defined(7, { + jsonName: "amount" + }) + public amount: Coin; + + constructor( + transferSourcePort: string, + transferSourceChannel: string, + interchainAccountSourcePort: string, + interchainAccountSourceChannel: string, + counterpartyBech32Addr: string, + delegatorAddress: AccAddress, + validatorAddress: ValAddress, + amount: Coin + ) { + super(); + + this.transferSourcePort = transferSourcePort; + this.transferSourceChannel = transferSourceChannel; + this.interchainAccountSourcePort = interchainAccountSourcePort; + this.interchainAccountSourceChannel = interchainAccountSourceChannel; + this.counterpartyBech32Addr = counterpartyBech32Addr; + this.delegatorAddress = delegatorAddress; + this.validatorAddress = validatorAddress; + this.amount = amount; + } + + public getSigners(): AccAddress[] { + return [this.delegatorAddress]; + } + + public validateBasic(): void { + // noop + } +}