diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4de2d32d..c2f134007 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/src/lib/components/CustomTab.tsx b/src/lib/components/CustomTab.tsx
index 8dc396164..570172c96 100644
--- a/src/lib/components/CustomTab.tsx
+++ b/src/lib/components/CustomTab.tsx
@@ -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) => {
@@ -33,13 +33,15 @@ export const CustomTab = ({ count, ...restProps }: CustomTabProps) => {
>
{tabProps.children}
-
- {count}
-
+ {count && (
+
+ {count}
+
+ )}
);
};
diff --git a/src/lib/components/modal/CodeSnippet.tsx b/src/lib/components/modal/CodeSnippet.tsx
new file mode 100644
index 000000000..380e331bc
--- /dev/null
+++ b/src/lib/components/modal/CodeSnippet.tsx
@@ -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, chainId: Option) => {
+ 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 = '';
+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 (
+ <>
+
+
+
+
+
+
+
+
+ Code Snippet
+
+
+
+
+
+
+ {codeSnippets[type].map((item) => (
+ {item.name}
+ ))}
+
+
+ {codeSnippets[type].map((item) => (
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+ >
+ );
+};
+
+export default CodeSnippet;
diff --git a/src/lib/pages/execute/components/ExecuteArea.tsx b/src/lib/pages/execute/components/ExecuteArea.tsx
index 87f2bf55f..f7893cb42 100644
--- a/src/lib/pages/execute/components/ExecuteArea.tsx
+++ b/src/lib/pages/execute/components/ExecuteArea.tsx
@@ -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";
@@ -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;
setValue: UseFormSetValue;
@@ -246,8 +251,15 @@ export const ExecuteArea = ({ control, setValue, cmds }: ExecuteAreaProps) => {
-
-
+
+
+
+
+
Transaction Fee:{" "}
diff --git a/src/lib/pages/query/components/QueryArea.tsx b/src/lib/pages/query/components/QueryArea.tsx
index c359537af..b2cd24705 100644
--- a/src/lib/pages/query/components/QueryArea.tsx
+++ b/src/lib/pages/query/components/QueryArea.tsx
@@ -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";
@@ -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;
@@ -119,7 +124,14 @@ export const QueryArea = ({
height="240px"
/>
-
+
+
+
+