Skip to content

Commit

Permalink
Merge pull request #88 from alleslabs/feat/code-snippet
Browse files Browse the repository at this point in the history
Feat/code snippet
  • Loading branch information
evilpeach committed Jan 20, 2023
2 parents 452490f + 776093a commit af7fd81
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- [#88](https://github.com/alleslabs/celatone-frontend/pull/88) Add code snippet for query and execute
- [#107](https://github.com/alleslabs/celatone-frontend/pull/107) Remove osmosis mainnet from chain list
- [#99](https://github.com/alleslabs/celatone-frontend/pull/99) Validate label and codeId field in instantiate page
- [#103](https://github.com/alleslabs/celatone-frontend/pull/103) Add check mark to selected network
Expand Down
18 changes: 10 additions & 8 deletions src/lib/components/CustomTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { TabProps } from "@chakra-ui/react";
import { Button, useTab, Badge, useMultiStyleConfig } from "@chakra-ui/react";

interface CustomTabProps extends TabProps {
count: number;
count?: number;
}

export const CustomTab = ({ count, ...restProps }: CustomTabProps) => {
Expand Down Expand Up @@ -33,13 +33,15 @@ export const CustomTab = ({ count, ...restProps }: CustomTabProps) => {
>
{tabProps.children}

<Badge
variant={isSelected ? "primary" : "gray"}
ml="6px"
color="text.main"
>
{count}
</Badge>
{count && (
<Badge
variant={isSelected ? "primary" : "gray"}
ml="6px"
color="text.main"
>
{count}
</Badge>
)}
</Button>
);
};
256 changes: 256 additions & 0 deletions src/lib/components/modal/CodeSnippet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
Button,
useDisclosure,
ModalCloseButton,
TabList,
Tabs,
TabPanels,
TabPanel,
Heading,
Icon,
Box,
} from "@chakra-ui/react";
import { useWallet } from "@cosmos-kit/react";
import AceEditor from "react-ace";
import { MdCode } from "react-icons/md";

import { CopyButton } from "../CopyButton";
import { CustomTab } from "lib/components/CustomTab";
import { useEndpoint } from "lib/hooks";
import type { ContractAddr, HumanAddr, Option } from "lib/types";

import "ace-builds/src-noconflict/ace";
import "ace-builds/src-noconflict/mode-sh";
import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/theme-monokai";

interface CodeSnippetProps {
contractAddress: HumanAddr | ContractAddr;
message: string;
type: "query" | "execute";
}

/**
*
* @todo: This is a temporary solution to get the full RPC URL for Osmosis.
*/
const getFullRpcUrl = (rpcUrl: Option<string>, chainId: Option<string>) => {
const baseUrl = rpcUrl?.slice(0, rpcUrl.length - 1);
switch (chainId) {
case "osmosis-1":
case "osmo-test-4":
return `${baseUrl}:443`;
default:
return `${baseUrl}:26657`;
}
};

const CodeSnippet = ({
contractAddress,
message,
type = "query",
}: CodeSnippetProps) => {
const { isOpen, onClose, onOpen } = useDisclosure();
const { currentChainRecord, currentChainName } = useWallet();
const isDisabled = !contractAddress || !message.length;

const endpoint = useEndpoint();
const client = currentChainRecord?.chain.daemon_name;
const rpcUrl = currentChainRecord?.preferredEndpoints?.rpc?.[0];
const chainId = currentChainRecord?.chain.chain_id;
const denom = currentChainRecord?.assetList.assets[0].base;
const codeSnippets: Record<
string,
{ name: string; mode: string; snippet: string }[]
> = {
query: [
{
name: "CLI",
mode: "sh",
snippet: `export CHAIN_ID='${chainId}'\n
export CONTRACT_ADDRESS='${contractAddress}'\n
export QUERY_MSG='${message}'\n
export RPC_URL='${getFullRpcUrl(rpcUrl, chainId)}'\n
${client} query wasm contract-state smart $CONTRACT_ADDRESS $QUERY_MSG \\
--chain-id $CHAIN_ID \\
--node $RPC_URL`,
},
{
name: "Python",
mode: "python",
snippet: `import base64
import requests\n
CONTRACT_ADDRESS = "${contractAddress}"
LCD_URL = "${endpoint}"
QUERY_MSG = b'''${message}'''\n
query_b64encoded = base64.b64encode(QUERY_MSG).decode("ascii")
res = requests.get(
f"{LCD_URL}/cosmwasm/wasm/v1/contract/{CONTRACT_ADDRESS}/smart/{query_b64encoded}"
).json()\n
print(res)`,
},
{
name: "CosmJS",
mode: "javascript",
snippet: `const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm-stargate");
const rpcURL = "${rpcUrl}";
const contractAddress =
"${contractAddress}";
const queryMsg = \`${message}\`;\n
const queryContract = async (rpcURL, contractAddress, queryMsg) => {
const client = await SigningCosmWasmClient.connect(rpcURL);
const queryResult = await client.queryContractSmart(
contractAddress,
JSON.parse(queryMsg)
);
console.log(queryResult);
};\n
queryContract(rpcURL, contractAddress, queryMsg);`,
},
{
name: "Axios",
mode: "javascript",
snippet: `const axios = require('axios');\n
const lcdURL = '${endpoint}';
const contractAddress =
"${contractAddress}";
const queryMsg = \`${message}\`;\n
const queryContract = async () => {
const queryB64Encoded = Buffer.from(JSON.stringify(queryMsg)).toString('base64');
console.log(res.data);
};\n
queryContract();`,
},
],
execute: [
{
name: "CLI",
mode: "sh",
snippet: `${client} keys add --recover celatone\n
export CHAIN_ID='${chainId}'\n
export RPC_URL='${getFullRpcUrl(rpcUrl, chainId)}'\n
export CONTRACT_ADDRESS='${contractAddress}'\n
export EXECUTE_MSG='${message}'\n
${client} tx wasm execute $CONTRACT_ADDRESS $EXECUTE_MSG \\
--from celatone \\
--chain-id $CHAIN_ID \\
--node $RPC_URL`,
},
{
name: "CosmJs",
mode: "javascript",
snippet: `const { getOfflineSignerAmino, cosmwasm } = require('osmojs');
const { SigningCosmWasmClient } = require('@cosmjs/cosmwasm-stargate');
const { Dec, IntPretty } = require('@keplr-wallet/unit');
const { coins } = require('@cosmjs/amino');
const { toUtf8 } = require('@cosmjs/encoding');
const { chains } = require('chain-registry');
const { executeContract } = cosmwasm.wasm.v1.MessageComposer.withTypeUrl;\n
const chain = chains.find(({ chain_name }) => chain_name === '${currentChainName}');
const mnemonic = '<Mnemonic>';
const contractAddress = '${contractAddress}'\n
const execute = async () => {
const signer = await getOfflineSignerAmino({ mnemonic, chain });
const rpcEndpoint = '${rpcUrl}';
const client = await SigningCosmWasmClient.connectWithSigner(rpcEndpoint, signer);
const [sender] = await signer.getAccounts();\n
const msg = executeContract({
sender: sender.address,
contract: contractAddress,
msg: toUtf8(JSON.stringify(JSON.parse(\`${message}\`))),
funds: [],
});\n
const gasEstimated = await client.simulate(sender.address, [msg]);
const fee = {
amount: coins(0, '${denom}'),
gas: new IntPretty(new Dec(gasEstimated).mul(new Dec(1.3)))
.maxDecimals(0)
.locale(false)
.toString(),
};\n
const tx = await client.signAndBroadcast(sender.address, [msg], fee);
console.log(tx);
};\n
execute();`,
},
],
};

return (
<>
<Button
isDisabled={isDisabled}
variant="outline-info"
size="sm"
ml="auto"
onClick={onOpen}
>
<Icon as={MdCode} boxSize={5} mr={1} />
Code Snippet
</Button>

<Modal isOpen={isOpen} onClose={onClose} isCentered size="4xl">
<ModalOverlay />
<ModalContent w="840px">
<ModalHeader>
<Icon as={MdCode} color="text.dark" fontSize="24px" />
<Heading as="h5" variant="h5">
Code Snippet
</Heading>
</ModalHeader>
<ModalCloseButton color="text.dark" />
<ModalBody px={4} maxH="640px" overflow="scroll">
<Tabs>
<TabList borderBottom="1px solid" borderColor="divider.main">
{codeSnippets[type].map((item) => (
<CustomTab key={`menu-${item.name}`}>{item.name}</CustomTab>
))}
</TabList>
<TabPanels>
{codeSnippets[type].map((item) => (
<TabPanel key={item.name} px={2} py={4}>
<Box
bgColor="gray.900"
p={4}
borderRadius={4}
position="relative"
>
<AceEditor
readOnly
mode={item.mode}
theme="monokai"
fontSize="14px"
style={{
width: "100%",
background: "transparent",
}}
value={item.snippet}
setOptions={{
showGutter: false,
useWorker: false,
printMargin: false,
wrap: true,
}}
/>
<Box position="absolute" top={4} right={4}>
<CopyButton value={item.snippet} />
</Box>
</Box>
</TabPanel>
))}
</TabPanels>
</Tabs>
</ModalBody>
</ModalContent>
</Modal>
</>
);
};

export default CodeSnippet;
16 changes: 14 additions & 2 deletions src/lib/pages/execute/components/ExecuteArea.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Box, Flex, Button, ButtonGroup, Icon, Text } from "@chakra-ui/react";
import type { StdFee } from "@cosmjs/stargate";
import { useWallet } from "@cosmos-kit/react";
import dynamic from "next/dynamic";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useFieldArray, useFormState, useWatch } from "react-hook-form";
import type { Control, UseFormSetValue } from "react-hook-form";
Expand All @@ -23,6 +24,10 @@ import type { ComposedMsg, ContractAddr, HumanAddr, Token } from "lib/types";
import { MsgType } from "lib/types";
import { composeMsg, jsonPrettify, jsonValidate, microfy } from "lib/utils";

const CodeSnippet = dynamic(() => import("lib/components/modal/CodeSnippet"), {
ssr: false,
});

interface ExecuteAreaProps {
control: Control<ExecutePageState>;
setValue: UseFormSetValue<ExecutePageState>;
Expand Down Expand Up @@ -246,8 +251,15 @@ export const ExecuteArea = ({ control, setValue, cmds }: ExecuteAreaProps) => {
</Button>
</Box>
</Flex>
<Flex alignItems="center" justify="space-between" mt={{ md: 8, xl: 0 }}>
<CopyButton isDisable={msg.length === 0} value={msg} />
<Flex alignItems="center" justify="space-between">
<Flex gap={2}>
<CopyButton isDisable={!msg.length} value={msg} />
<CodeSnippet
type="execute"
contractAddress={contractAddress}
message={msg}
/>
</Flex>
<Flex direction="row" align="center" gap={2}>
<Flex fontSize="14px" color="text.dark" alignItems="center">
Transaction Fee:{" "}
Expand Down
14 changes: 13 additions & 1 deletion src/lib/pages/query/components/QueryArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Box, Flex, Spacer, Button, ButtonGroup, Text } from "@chakra-ui/react";
import { useWallet } from "@cosmos-kit/react";
import { useQuery } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";

import { ContractCmdButton } from "lib/components/ContractCmdButton";
Expand All @@ -15,6 +16,10 @@ import { queryData } from "lib/services/contract";
import type { ContractAddr, RpcQueryError } from "lib/types";
import { encode, jsonPrettify, jsonValidate } from "lib/utils";

const CodeSnippet = dynamic(() => import("lib/components/modal/CodeSnippet"), {
ssr: false,
});

interface QueryAreaProps {
contractAddress: ContractAddr;
initialMsg: string;
Expand Down Expand Up @@ -119,7 +124,14 @@ export const QueryArea = ({
height="240px"
/>
<Flex align="center" justify="space-between">
<CopyButton isDisable={msg.length === 0} value={msg} />
<Flex gap={2}>
<CopyButton isDisable={!msg.length} value={msg} />
<CodeSnippet
type="query"
contractAddress={contractAddress}
message={msg}
/>
</Flex>
<Button
variant="primary"
fontSize="14px"
Expand Down

1 comment on commit af7fd81

@vercel
Copy link

@vercel vercel bot commented on af7fd81 Jan 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.