@@ -86,13 +88,12 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) =>
diff --git a/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx b/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx
index 9d25782..1b7e00e 100644
--- a/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx
+++ b/packages/nextjs/app/debug/_components/contract/ContractVariables.tsx
@@ -12,39 +12,38 @@ export const ContractVariables = ({
if (!deployedContractData) {
return null;
}
-
- const functionsToDisplay = (
- (deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[]
- )
- .filter(fn => {
- const isQueryableWithNoParams =
- (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0;
- return isQueryableWithNoParams;
- })
- .map(fn => {
- return {
- fn,
- inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name],
- };
- })
- .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1));
-
+ // const functionsToDisplay = (
+ // (deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[]
+ // )
+ // .filter(fn => {
+ // const isQueryableWithNoParams =
+ // (fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0;
+ // return isQueryableWithNoParams;
+ // })
+ // .map(fn => {
+ // return {
+ // fn,
+ // inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name],
+ // };
+ // })
+ // .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1));
+ const functionsToDisplay = [] as AbiFunction[];
if (!functionsToDisplay.length) {
return <>No contract variables>;
}
-
- return (
- <>
- {functionsToDisplay.map(({ fn, inheritedFrom }) => (
-
- ))}
- >
- );
+ return <>No contract variables>;
+ // return (
+ // <>
+ // {functionsToDisplay.map(({ fn, inheritedFrom }) => (
+ //
+ // ))}
+ // >
+ // );
};
diff --git a/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx b/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx
index ee703a6..ce1b705 100644
--- a/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx
+++ b/packages/nextjs/app/debug/_components/contract/ContractWriteMethods.tsx
@@ -1,32 +1,18 @@
-import { Abi, AbiFunction } from "abitype";
-import { WriteOnlyFunctionForm } from "~~/app/debug/_components/contract";
-import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract";
+import { FunctionForm } from "~~/app/debug/_components/contract";
+import { Contract, ContractName } from "~~/utils/scaffold-move/contract";
export const ContractWriteMethods = ({
- onChange,
deployedContractData,
}: {
- onChange: () => void;
deployedContractData: Contract
;
}) => {
- if (!deployedContractData) {
+ if (!deployedContractData || deployedContractData.abi === undefined) {
return null;
}
- const functionsToDisplay = (
- (deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[]
- )
- .filter(fn => {
- const isWriteableFunction = fn.stateMutability !== "view" && fn.stateMutability !== "pure";
- return isWriteableFunction;
- })
- .map(fn => {
- return {
- fn,
- inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name],
- };
- })
- .sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1));
+ const functionsToDisplay = deployedContractData.abi.exposed_functions.filter((fn) =>
+ fn.is_entry,
+ );
if (!functionsToDisplay.length) {
return <>No write methods>;
@@ -34,14 +20,11 @@ export const ContractWriteMethods = ({
return (
<>
- {functionsToDisplay.map(({ fn, inheritedFrom }, idx) => (
- (
+
))}
>
diff --git a/packages/nextjs/app/debug/_components/contract/FunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/FunctionForm.tsx
new file mode 100644
index 0000000..3b35af6
--- /dev/null
+++ b/packages/nextjs/app/debug/_components/contract/FunctionForm.tsx
@@ -0,0 +1,196 @@
+"use client";
+
+import {Types} from "aptos";
+import {parseTypeTag} from "@aptos-labs/ts-sdk";
+import {
+ useWallet,
+ InputTransactionData,
+} from "@aptos-labs/wallet-adapter-react";
+
+import { useState } from "react";
+import useSubmitTransaction from "~~/hooks/scaffold-move/useSubmitTransaction";
+import {SubmitHandler} from "react-hook-form";
+import {encodeInputArgsForViewRequest} from "../../../../utils/utils";
+import { view } from "~~/hooks";
+
+const zeroInputs = false;
+
+type ContractFormType = {
+ typeArgs: string[];
+ args: string[];
+ ledgerVersion?: string;
+};
+
+type FunctionFormProps = {
+ module: Types.MoveModule;
+ fn: Types.MoveFunction;
+ write: boolean;
+};
+
+function removeSignerParam(fn: Types.MoveFunction, write: boolean) {
+ if (!write) {
+ return fn.params;
+ }
+ return fn.params.filter((p) => p !== "signer" && p !== "&signer");
+}
+
+export const FunctionForm = ({
+ module,
+ fn,
+ write,
+}: FunctionFormProps) => {
+ const {submitTransaction, transactionResponse, transactionInProcess} = useSubmitTransaction();
+ const [inProcess, setInProcess] = useState(false);
+ const [result, setResult] = useState();
+ const [data, setData] = useState({ typeArgs: [], args: [] });
+
+ const fnParams = removeSignerParam(fn, write);
+
+ const convertArgument = (arg: string | null | undefined, type: string): any => {
+ if (typeof arg !== "string") {
+ arg = "";
+ }
+ arg = arg.trim();
+ const typeTag = parseTypeTag(type);
+ if (typeTag.isVector()) {
+ const innerTag = typeTag.value;
+ if (innerTag.isVector()) {
+ return JSON.parse(arg) as any[];
+ }
+ if (innerTag.isU8()) {
+ if (arg.startsWith("0x")) {
+ return arg;
+ }
+ }
+ if (arg.startsWith("[")) {
+ return JSON.parse(arg) as any[];
+ } else {
+ return arg.split(",").map((arg) => {
+ return arg.trim();
+ });
+ }
+ } else if (typeTag.isStruct()) {
+ if (typeTag.isOption()) {
+ if (arg === "") {
+ return undefined;
+ } else {
+ arg = convertArgument(arg, typeTag.value.typeArgs[0].toString());
+ return arg;
+ }
+ }
+ }
+ return arg;
+ };
+
+ const handleWrite = async () => {
+ const payload: InputTransactionData = {
+ data: {
+ function: `${module.address}::${module.name}::${fn.name}`,
+ typeArguments: data.typeArgs,
+ functionArguments: data.args.map((arg, i) => {
+ const type = fnParams[i];
+ return convertArgument(arg, type);
+ }),
+ },
+ };
+
+ try {
+ await submitTransaction(payload);
+
+ if (transactionResponse?.transactionSubmitted) {
+ console.log("function_interacted", fn.name, {
+ txn_status: transactionResponse.success ? "success" : "failed",
+ });
+ }
+ } catch (e: any) {
+ console.error("โก๏ธ ~ file: FunctionForm.tsx:handleWrite ~ error", e);
+ }
+ };
+
+ const handleView = async () => {
+ let viewRequest: Types.ViewRequest;
+ try {
+ viewRequest = {
+ function: `${module.address}::${module.name}::${fn.name}`,
+ type_arguments: data.typeArgs,
+ arguments: data.args.map((arg, i) => {
+ return encodeInputArgsForViewRequest(fn.params[i], arg);
+ }),
+ };
+ } catch (e: any) {
+ console.error("Parsing arguments failed: " + e?.message);
+ return;
+ }
+ setInProcess(true);
+ try {
+ const result = await view(viewRequest, state.network_value, data.ledgerVersion);
+ setResult(result);
+ console.log("function_interacted", fn.name, { txn_status: "success" });
+ } catch (e: any) {
+ let error = e.message ?? JSON.stringify(e);
+ const prefix = "Error:";
+ if (error.startsWith(prefix)) {
+ error = error.substring(prefix.length).trim();
+ }
+ setResult(undefined);
+ console.log("function_interacted", fn.name, { txn_status: "failed" });
+ }
+ setInProcess(false);
+ };
+
+ const isFunctionSuccess = !!(
+ transactionResponse?.transactionSubmitted && transactionResponse?.success
+ );
+
+ return (
+
+
+
+ {fn.name}
+
+ {fnParams.map((param, i) => {
+ const isOption = param.startsWith("0x1::option::Option");
+ return (
+
+
+ {`arg${i}:`}
+
+
+ {
+ const newArgs = [...data.args];
+ newArgs[i] = e.target.value;
+ setData({ ...data, args: newArgs });
+ }}
+ />
+
+
+ );
+ })}
+
+ {!zeroInputs && (
+
+ )}
+ {write && (
+
+ Send ๐ธ
+
+ )}
+ {!write && (
+
+ {transactionInProcess && }
+ Read ๐ก
+
+ )}
+
+
+ {transactionResponse ? (
+
+ {transactionResponse.message}
+
+ ) : null}
+
+ );
+};
diff --git a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx
deleted file mode 100644
index a0d097a..0000000
--- a/packages/nextjs/app/debug/_components/contract/ReadOnlyFunctionForm.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { InheritanceTooltip } from "./InheritanceTooltip";
-import { Abi, AbiFunction } from "abitype";
-import { Address } from "viem";
-import { useReadContract } from "wagmi";
-import {
- ContractInput,
- displayTxResult,
- getFunctionInputKey,
- getInitialFormState,
- getParsedContractFunctionArgs,
- transformAbiFunction,
-} from "~~/app/debug/_components/contract";
-import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
-import { getParsedError, notification } from "~~/utils/scaffold-eth";
-
-type ReadOnlyFunctionFormProps = {
- contractAddress: Address;
- abiFunction: AbiFunction;
- inheritedFrom?: string;
- abi: Abi;
-};
-
-export const ReadOnlyFunctionForm = ({
- contractAddress,
- abiFunction,
- inheritedFrom,
- abi,
-}: ReadOnlyFunctionFormProps) => {
- const [form, setForm] = useState>(() => getInitialFormState(abiFunction));
- const [result, setResult] = useState();
- const { targetNetwork } = useTargetNetwork();
-
- const { isFetching, refetch, error } = useReadContract({
- address: contractAddress,
- functionName: abiFunction.name,
- abi: abi,
- args: getParsedContractFunctionArgs(form),
- chainId: targetNetwork.id,
- query: {
- enabled: false,
- retry: false,
- },
- });
-
- useEffect(() => {
- if (error) {
- const parsedError = getParsedError(error);
- notification.error(parsedError);
- }
- }, [error]);
-
- const transformedFunction = transformAbiFunction(abiFunction);
- const inputElements = transformedFunction.inputs.map((input, inputIndex) => {
- const key = getFunctionInputKey(abiFunction.name, input, inputIndex);
- return (
- {
- setResult(undefined);
- setForm(updatedFormValue);
- }}
- form={form}
- stateObjectKey={key}
- paramType={input}
- />
- );
- });
-
- return (
-
-
- {abiFunction.name}
-
-
- {inputElements}
-
-
- {result !== null && result !== undefined && (
-
-
Result:
-
{displayTxResult(result, "sm")}
-
- )}
-
-
{
- const { data } = await refetch();
- setResult(data);
- }}
- disabled={isFetching}
- >
- {isFetching && }
- Read ๐ก
-
-
-
- );
-};
diff --git a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
deleted file mode 100644
index b8e8f84..0000000
--- a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { InheritanceTooltip } from "./InheritanceTooltip";
-import { Abi, AbiFunction } from "abitype";
-import { Address, TransactionReceipt } from "viem";
-import { useAccount, useWaitForTransactionReceipt, useWriteContract } from "wagmi";
-import {
- ContractInput,
- TxReceipt,
- getFunctionInputKey,
- getInitialFormState,
- getParsedContractFunctionArgs,
- transformAbiFunction,
-} from "~~/app/debug/_components/contract";
-import { IntegerInput } from "~~/components/scaffold-eth";
-import { useTransactor } from "~~/hooks/scaffold-eth";
-import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
-
-type WriteOnlyFunctionFormProps = {
- abi: Abi;
- abiFunction: AbiFunction;
- onChange: () => void;
- contractAddress: Address;
- inheritedFrom?: string;
-};
-
-export const WriteOnlyFunctionForm = ({
- abi,
- abiFunction,
- onChange,
- contractAddress,
- inheritedFrom,
-}: WriteOnlyFunctionFormProps) => {
- const [form, setForm] = useState>(() => getInitialFormState(abiFunction));
- const [txValue, setTxValue] = useState("");
- const { chain } = useAccount();
- const writeTxn = useTransactor();
- const { targetNetwork } = useTargetNetwork();
- const writeDisabled = !chain || chain?.id !== targetNetwork.id;
-
- const { data: result, isPending, writeContractAsync } = useWriteContract();
-
- const handleWrite = async () => {
- if (writeContractAsync) {
- try {
- const makeWriteWithParams = () =>
- writeContractAsync({
- address: contractAddress,
- functionName: abiFunction.name,
- abi: abi,
- args: getParsedContractFunctionArgs(form),
- value: BigInt(txValue),
- });
- await writeTxn(makeWriteWithParams);
- onChange();
- } catch (e: any) {
- console.error("โก๏ธ ~ file: WriteOnlyFunctionForm.tsx:handleWrite ~ error", e);
- }
- }
- };
-
- const [displayedTxResult, setDisplayedTxResult] = useState();
- const { data: txResult } = useWaitForTransactionReceipt({
- hash: result,
- });
- useEffect(() => {
- setDisplayedTxResult(txResult);
- }, [txResult]);
-
- // TODO use `useMemo` to optimize also update in ReadOnlyFunctionForm
- const transformedFunction = transformAbiFunction(abiFunction);
- const inputs = transformedFunction.inputs.map((input, inputIndex) => {
- const key = getFunctionInputKey(abiFunction.name, input, inputIndex);
- return (
- {
- setDisplayedTxResult(undefined);
- setForm(updatedFormValue);
- }}
- form={form}
- stateObjectKey={key}
- paramType={input}
- />
- );
- });
- const zeroInputs = inputs.length === 0 && abiFunction.stateMutability !== "payable";
-
- return (
-
-
-
- {abiFunction.name}
-
-
- {inputs}
- {abiFunction.stateMutability === "payable" ? (
-
-
- payable value
- wei
-
-
{
- setDisplayedTxResult(undefined);
- setTxValue(updatedTxValue);
- }}
- placeholder="value (wei)"
- />
-
- ) : null}
-
- {!zeroInputs && (
-
- {displayedTxResult ? : null}
-
- )}
-
-
- {isPending && }
- Send ๐ธ
-
-
-
-
- {zeroInputs && txResult ? (
-
-
-
- ) : null}
-
- );
-};
diff --git a/packages/nextjs/app/debug/_components/contract/index.tsx b/packages/nextjs/app/debug/_components/contract/index.tsx
index 83833d8..059863a 100644
--- a/packages/nextjs/app/debug/_components/contract/index.tsx
+++ b/packages/nextjs/app/debug/_components/contract/index.tsx
@@ -1,8 +1,7 @@
export * from "./ContractInput";
export * from "./ContractUI";
export * from "./DisplayVariable";
-export * from "./ReadOnlyFunctionForm";
export * from "./TxReceipt";
export * from "./utilsContract";
export * from "./utilsDisplay";
-export * from "./WriteOnlyFunctionForm";
+export * from "./FunctionForm"
diff --git a/packages/nextjs/app/debug/page.tsx b/packages/nextjs/app/debug/page.tsx
index e6fb89f..267b3c6 100644
--- a/packages/nextjs/app/debug/page.tsx
+++ b/packages/nextjs/app/debug/page.tsx
@@ -4,7 +4,7 @@ import { getMetadata } from "~~/utils/scaffold-eth/getMetadata";
export const metadata = getMetadata({
title: "Debug Contracts",
- description: "Debug your deployed ๐ Scaffold-ETH 2 contracts in an easy way",
+ description: "Debug your deployed ๐ Scaffold-Move contracts in an easy way",
});
const Debug: NextPage = () => {
diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx
index b91c22f..c9becca 100644
--- a/packages/nextjs/app/page.tsx
+++ b/packages/nextjs/app/page.tsx
@@ -15,7 +15,7 @@ const Home: NextPage = () => {
Welcome to
- Scaffold-ETH 2
+ Scaffold-Move
Connected Address:
@@ -30,11 +30,11 @@ const Home: NextPage = () => {
Edit your smart contract{" "}
- YourContract.sol
+ OnchainBio.move
{" "}
in{" "}
- packages/hardhat/contracts
+ packages/move/sources
diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx
index 6d4a03d..fde57e5 100644
--- a/packages/nextjs/components/Header.tsx
+++ b/packages/nextjs/components/Header.tsx
@@ -20,6 +20,10 @@ export const menuLinks: HeaderMenuLink[] = [
label: "Home",
href: "/",
},
+ {
+ label: "Bio",
+ href: "/bio",
+ },
{
label: "Debug Contracts",
href: "/debug",
@@ -94,8 +98,8 @@ export const Header = () => {
- Scaffold-ETH
- Ethereum dev stack
+ Scaffold-Move
+ Move dev stack
diff --git a/packages/nextjs/components/scaffold-move/Address.tsx b/packages/nextjs/components/scaffold-move/Address.tsx
new file mode 100644
index 0000000..24101ab
--- /dev/null
+++ b/packages/nextjs/components/scaffold-move/Address.tsx
@@ -0,0 +1,134 @@
+"use client";
+
+import { useState } from "react";
+import Link from "next/link";
+import { CopyToClipboard } from "react-copy-to-clipboard";
+import { hardhat } from "viem/chains";
+import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";
+import { BlockieAvatar } from "~~/components/scaffold-eth";
+import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
+import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth";
+
+type AddressProps = {
+ address?: string;
+ disableAddressLink?: boolean;
+ format?: "short" | "long";
+ size?: "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl";
+};
+
+const blockieSizeMap = {
+ xs: 6,
+ sm: 7,
+ base: 8,
+ lg: 9,
+ xl: 10,
+ "2xl": 12,
+ "3xl": 15,
+};
+
+/**
+ * Displays an address (or ENS) with a Blockie image and option to copy address.
+ */
+export const Address = ({ address, disableAddressLink, format, size = "base" }: AddressProps) => {
+ const [ens, setEns] = useState();
+ const [ensAvatar, setEnsAvatar] = useState();
+ const [addressCopied, setAddressCopied] = useState(false);
+
+ const { targetNetwork } = useTargetNetwork();
+
+ // const { data: fetchedEns } = useEnsName({
+ // address: address,
+ // chainId: 1,
+ // query: {
+ // enabled: isAddress(address ?? ""),
+ // },
+ // });
+ // const { data: fetchedEnsAvatar } = useEnsAvatar({
+ // name: fetchedEns ? normalize(fetchedEns) : undefined,
+ // chainId: 1,
+ // query: {
+ // enabled: Boolean(fetchedEns),
+ // gcTime: 30_000,
+ // },
+ // });
+
+ // We need to apply this pattern to avoid Hydration errors.
+ // useEffect(() => {
+ // setEns(fetchedEns);
+ // }, [fetchedEns]);
+
+ // useEffect(() => {
+ // setEnsAvatar(fetchedEnsAvatar);
+ // }, [fetchedEnsAvatar]);
+
+ // Skeleton UI
+ if (!address) {
+ return (
+
+ );
+ }
+
+
+ const blockExplorerAddressLink = getBlockExplorerAddressLink(targetNetwork, address);
+ let displayAddress = address?.slice(0, 6) + "..." + address?.slice(-4);
+
+ if (ens) {
+ displayAddress = ens;
+ } else if (format === "long") {
+ displayAddress = address;
+ }
+
+ return (
+
+
+
+
+ {disableAddressLink ? (
+
{displayAddress}
+ ) : targetNetwork.id === hardhat.id ? (
+
+ {displayAddress}
+
+ ) : (
+
+ {displayAddress}
+
+ )}
+ {addressCopied ? (
+
+ ) : (
+
{
+ setAddressCopied(true);
+ setTimeout(() => {
+ setAddressCopied(false);
+ }, 800);
+ }}
+ >
+
+
+ )}
+
+ );
+};
diff --git a/packages/nextjs/components/scaffold-move/Balance.tsx b/packages/nextjs/components/scaffold-move/Balance.tsx
new file mode 100644
index 0000000..e9a7064
--- /dev/null
+++ b/packages/nextjs/components/scaffold-move/Balance.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import { Address, formatEther } from "viem";
+import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
+import { useWatchBalance } from "~~/hooks/scaffold-eth/useWatchBalance";
+import {useGetAccountAPTBalance} from "~~/hooks/scaffold-move/useGetAccountAPTBalance";
+import {getFormattedBalanceStr} from "../../utils/scaffold-move/ContentValue/CurrencyValue"
+
+type BalanceProps = {
+ address: string;
+};
+
+/**
+ * Display APT balance of an APT address.
+ */
+export const Balance = ({ address }: BalanceProps) => {
+ const balance = useGetAccountAPTBalance(address);
+
+ if (!address || balance === null) {
+ return (
+
+ );
+ }
+
+ // if (isError) {
+ // return (
+ //
+ // );
+ // }
+ // const formattedBalance = balance ? Number(formatEther(balance.value)) : 0;
+
+ return (
+
+ <>
+ {getFormattedBalanceStr(balance)}
+ MOVE
+ >
+
+ );
+};
diff --git a/packages/nextjs/components/scaffold-move/index.tsx b/packages/nextjs/components/scaffold-move/index.tsx
new file mode 100644
index 0000000..c64f173
--- /dev/null
+++ b/packages/nextjs/components/scaffold-move/index.tsx
@@ -0,0 +1,2 @@
+export * from "./Address";
+export * from "./Balance";
\ No newline at end of file
diff --git a/packages/nextjs/constants.tsx b/packages/nextjs/constants.tsx
new file mode 100644
index 0000000..432a365
--- /dev/null
+++ b/packages/nextjs/constants.tsx
@@ -0,0 +1,87 @@
+/**
+ * Network
+ */
+export const devnetUrl = "https://aptos.devnet.m1.movementlabs.xyz";
+
+export const networks = {
+ mainnet: "https://aptos.movementlabs.xyz",
+ testnet: "https://aptos.testnet.movementlabs.xyz",
+ devnet: devnetUrl,
+ local: "http://127.0.0.1:8080/v1",
+ previewnet: "https://aptos.testnet.movementlabs.xyz",
+ randomnet: "https://aptos.testnet.movementlabs.xyz",
+};
+
+export type NetworkName = keyof typeof networks;
+
+export function isValidNetworkName(value: string): value is NetworkName {
+ return value in networks;
+}
+
+export enum Network {
+ MAINNET = "mainnet",
+ TESTNET = "testnet",
+ DEVNET = "devnet",
+ LOCAL = "local",
+ PREVIEWNET = "previewnet",
+ RANDOMNET = "randomnet",
+}
+
+// Remove trailing slashes
+for (const key of Object.keys(networks)) {
+ const networkName = key as NetworkName;
+ if (networks[networkName].endsWith("/")) {
+ networks[networkName] = networks[networkName].slice(0, -1);
+ }
+}
+
+export const defaultNetworkName: NetworkName = "devnet" as const;
+
+if (!(defaultNetworkName in networks)) {
+ throw `defaultNetworkName '${defaultNetworkName}' not in Networks!`;
+}
+
+export const defaultNetwork = networks[defaultNetworkName];
+
+/**
+ * Feature
+ */
+export const features = {
+ prod: "Production Mode",
+ dev: "Development Mode",
+ earlydev: "Early Development Mode",
+};
+
+export type FeatureName = keyof typeof features;
+export function isValidFeatureName(value: string): value is FeatureName {
+ return value in features;
+}
+
+// Remove trailing slashes
+for (const key of Object.keys(features)) {
+ const featureName = key as FeatureName;
+ if (features[featureName].endsWith("/")) {
+ features[featureName] = features[featureName].slice(0, -1);
+ }
+}
+
+export const defaultFeatureName: FeatureName = "prod" as const;
+
+if (!(defaultFeatureName in features)) {
+ throw `defaultFeatureName '${defaultFeatureName}' not in Features!`;
+}
+
+export const defaultFeature = features[defaultFeatureName];
+
+/**
+ * Delegation Service
+ */
+export const OCTA = 100000000;
+export const WHILTELISTED_TESTNET_DELEGATION_NODES = null;
+
+/**
+ * Core Address
+ */
+export const objectCoreAddress = "0x1::object::ObjectCore";
+export const tokenV2Address = "0x4::token::Token";
+export const collectionV2Address = "0x4::collection::Collection";
diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts
deleted file mode 100644
index 008d4eb..0000000
--- a/packages/nextjs/contracts/deployedContracts.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * This file is autogenerated by Scaffold-ETH.
- * You should not edit it manually or your changes might be overwritten.
- */
-import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";
-
-const deployedContracts = {} as const;
-
-export default deployedContracts satisfies GenericContractsDeclaration;
diff --git a/packages/nextjs/contracts/deployedModules.ts b/packages/nextjs/contracts/deployedModules.ts
new file mode 100644
index 0000000..6396ffa
--- /dev/null
+++ b/packages/nextjs/contracts/deployedModules.ts
@@ -0,0 +1,20 @@
+import { GenericContractsDeclaration } from "~~/utils/scaffold-move/contract";
+
+const deployedContracts = {
+ "devnet": {
+ "onchain_bio": {
+ "bytecode": "0xa11ceb0b060000000a010006020608030e0f051d1307304a087a4010ba01220adc01090ce501340d99020200000101010200030e0002040700000500010000060203000109050000010501080103060c080108010001080001060c0b6f6e636861696e5f62696f067369676e657206737472696e670342696f06537472696e67076765745f62696f087265676973746572046e616d650362696f0a616464726573735f6f664295cca96321b2807473c0df06fa0ec4b1e22e612f8577cc36406d8c0e67630c0000000000000000000000000000000000000000000000000000000000000001126170746f733a3a6d657461646174615f76310e000001076765745f62696f010100000202070801080801000100010003050b002b0010001402010104010004100a001102290004080a0011022c00010b010b0212000c030b000b032d0002000100",
+ "abi": {"address":"0x4295cca96321b2807473c0df06fa0ec4b1e22e612f8577cc36406d8c0e67630c","name":"onchain_bio","friends":[],"exposed_functions":[{"name":"get_bio","visibility":"public","is_entry":false,"is_view":true,"generic_type_params":[],"params":["address"],"return":["0x1::string::String"]},{"name":"register","visibility":"public","is_entry":true,"is_view":false,"generic_type_params":[],"params":["&signer","0x1::string::String","0x1::string::String"],"return":[]}],"structs":[{"name":"Bio","is_native":false,"abilities":["drop","store","key"],"generic_type_params":[],"fields":[{"name":"name","type":"0x1::string::String"},{"name":"bio","type":"0x1::string::String"}]}]}
+ },
+"onchain_poems": {
+ "bytecode": "0xa11ceb0b060000000b010004020408030c0a05161207284c08744006b4010a10be013a0af8010c0c8402260daa02020000010100020e000103070000040001000005020300010501080104060c080108010801000108000d6f6e636861696e5f706f656d7306737472696e670b496e736372697074696f6e06537472696e67086765745f706f656d08726567697374657204706f656d057469746c6506617574686f724295cca96321b2807473c0df06fa0ec4b1e22e612f8577cc36406d8c0e67630c000000000000000000000000000000000000000000000000000000000000000103080100000000000000126170746f733a3a6d657461646174615f7631260101000000000000000d455f414c52454144595f484153000001086765745f706f656d010100000203060801070801080801000100010003050b002b00100014020101040004090b010b020b0312000c040b000b042d0002000000",
+ "abi": {"address":"0x4295cca96321b2807473c0df06fa0ec4b1e22e612f8577cc36406d8c0e67630c","name":"onchain_poems","friends":[],"exposed_functions":[{"name":"get_poem","visibility":"public","is_entry":false,"is_view":true,"generic_type_params":[],"params":["address"],"return":["0x1::string::String"]},{"name":"register","visibility":"public","is_entry":true,"is_view":false,"generic_type_params":[],"params":["&signer","0x1::string::String","0x1::string::String","0x1::string::String"],"return":[]}],"structs":[{"name":"Inscription","is_native":false,"abilities":["drop","store","key"],"generic_type_params":[],"fields":[{"name":"poem","type":"0x1::string::String"},{"name":"title","type":"0x1::string::String"},{"name":"author","type":"0x1::string::String"}]}]}
+ },
+"onchain_poems_with_table": {
+ "bytecode": "0xa11ceb0b060000000c01000a020a14031e23044106054739078001bb0108bb024006fb020a1085033a0abf031c0cdb036f0dca0404000001010102010301040005070000060800030707000410040203010001000800010000090201000212020400041306010203020114080101060415010a02030403050407050504060c0802080208020001060c040308000708010501050203080003070b03020900090109000901010800010900010801010b030209000901186f6e636861696e5f706f656d735f776974685f7461626c65056576656e74067369676e657206737472696e67057461626c6504506f656d08506f656d4c69737406537472696e670b6372656174655f706f656d106372656174655f706f656d5f6c69737407706f656d5f6964076164647265737304706f656d057469746c6506617574686f7205706f656d73055461626c650c706f656d5f636f756e7465720a616464726573735f6f660675707365727404656d6974036e65774295cca96321b2807473c0df06fa0ec4b1e22e612f8577cc36406d8c0e67630c000000000000000000000000000000000000000000000000000000000000000103080100000000000000126170746f733a3a6d657461646174615f76312601010000000000000011455f4e4f545f494e495449414c495a4544000104506f656d010400000002050a030b050c08020d08020e08020102020f0b03020308001103000104010103250b0011020c070a072901040705090700270a072a010c060a06100014060100000000000000160c040a040b070b010b020b0312000c050a060f010a040a0538000b040b060f00150b05380102010104000908380206000000000000000012010c010b000b012d01020101010000",
+ "abi": {"address":"0x4295cca96321b2807473c0df06fa0ec4b1e22e612f8577cc36406d8c0e67630c","name":"onchain_poems_with_table","friends":[],"exposed_functions":[{"name":"create_poem","visibility":"public","is_entry":true,"is_view":false,"generic_type_params":[],"params":["&signer","0x1::string::String","0x1::string::String","0x1::string::String"],"return":[]},{"name":"create_poem_list","visibility":"public","is_entry":true,"is_view":false,"generic_type_params":[],"params":["&signer"],"return":[]}],"structs":[{"name":"Poem","is_native":false,"abilities":["copy","drop","store"],"generic_type_params":[],"fields":[{"name":"poem_id","type":"u64"},{"name":"address","type":"address"},{"name":"poem","type":"0x1::string::String"},{"name":"title","type":"0x1::string::String"},{"name":"author","type":"0x1::string::String"}]},{"name":"PoemList","is_native":false,"abilities":["key"],"generic_type_params":[],"fields":[{"name":"poems","type":"0x1::table::Table"},{"name":"poem_counter","type":"u64"}]}]}
+ }
+ }
+} as const;
+
+export default deployedContracts satisfies GenericContractsDeclaration;
\ No newline at end of file
diff --git a/packages/nextjs/contracts/externalContracts.ts b/packages/nextjs/contracts/externalContracts.ts
deleted file mode 100644
index ab6daa8..0000000
--- a/packages/nextjs/contracts/externalContracts.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";
-
-/**
- * @example
- * const externalContracts = {
- * 1: {
- * DAI: {
- * address: "0x...",
- * abi: [...],
- * },
- * },
- * } as const;
- */
-const externalContracts = {} as const;
-
-export default externalContracts satisfies GenericContractsDeclaration;
diff --git a/packages/nextjs/contracts/externalModules.ts b/packages/nextjs/contracts/externalModules.ts
new file mode 100644
index 0000000..68eed0b
--- /dev/null
+++ b/packages/nextjs/contracts/externalModules.ts
@@ -0,0 +1,9 @@
+import { GenericContractsDeclaration } from "~~/utils/scaffold-move/contract";
+
+const externalContracts = {
+ "devnet": {
+
+ }
+} as const;
+
+export default externalContracts satisfies GenericContractsDeclaration;
\ No newline at end of file
diff --git a/packages/nextjs/global-config/GlobalConfig.tsx b/packages/nextjs/global-config/GlobalConfig.tsx
new file mode 100644
index 0000000..727037d
--- /dev/null
+++ b/packages/nextjs/global-config/GlobalConfig.tsx
@@ -0,0 +1,117 @@
+import {AptosClient, IndexerClient} from "aptos";
+import React, {useMemo} from "react";
+import {
+ FeatureName,
+ NetworkName,
+ defaultNetworkName,
+ networks,
+} from "../constants";
+import {
+ getSelectedFeatureFromLocalStorage,
+ useFeatureSelector,
+} from "./feature-selection";
+import {useNetworkSelector} from "./network-selection";
+import {getGraphqlURI} from "../hooks/scaffold-move/useGraphqlClient";
+import {Aptos, AptosConfig, NetworkToNetworkName} from "@aptos-labs/ts-sdk";
+
+const HEADERS = {
+ "x-indexer-client": "aptos-explorer",
+};
+
+export type GlobalState = {
+ /** actual state */
+ readonly feature_name: FeatureName;
+ /** derived from external state ?network= query parameter - e.g. devnet */
+ readonly network_name: NetworkName;
+ /** derived from network_name - url to connect to network */
+ readonly network_value: string;
+ /** derived from network_value */
+ readonly aptos_client: AptosClient;
+ /** derived from network_value */
+ readonly indexer_client?: IndexerClient;
+ /** derived from network_value */
+ readonly sdk_v2_client?: Aptos;
+};
+
+type GlobalActions = {
+ selectFeature: ReturnType[1];
+ selectNetwork: ReturnType[1];
+};
+
+function deriveGlobalState({
+ feature_name,
+ network_name,
+}: {
+ feature_name: FeatureName;
+ network_name: NetworkName;
+}): GlobalState {
+ const indexerUri = getGraphqlURI(network_name);
+ let indexerClient = undefined;
+ if (indexerUri) {
+ indexerClient = new IndexerClient(indexerUri, {HEADERS});
+ }
+ return {
+ feature_name,
+ network_name,
+ network_value: networks[network_name],
+ aptos_client: new AptosClient(networks[network_name], {
+ HEADERS,
+ }),
+ indexer_client: indexerClient,
+ sdk_v2_client: new Aptos(
+ new AptosConfig({
+ network: NetworkToNetworkName[network_name],
+ clientConfig: {
+ HEADERS,
+ },
+ }),
+ ),
+ };
+}
+
+const initialGlobalState = deriveGlobalState({
+ feature_name: getSelectedFeatureFromLocalStorage(),
+ network_name: defaultNetworkName,
+});
+
+export const GlobalStateContext = React.createContext(initialGlobalState);
+export const GlobalActionsContext = React.createContext({} as GlobalActions);
+
+export const GlobalStateProvider = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ const [selectedFeature, selectFeature] = useFeatureSelector();
+ const [selectedNetwork, selectNetwork] = useNetworkSelector();
+ const globalState: GlobalState = useMemo(
+ () =>
+ deriveGlobalState({
+ feature_name: selectedFeature,
+ network_name: selectedNetwork,
+ }),
+ [selectedFeature, selectedNetwork],
+ );
+
+ const globalActions = useMemo(
+ () => ({
+ selectFeature,
+ selectNetwork,
+ }),
+ [selectFeature, selectNetwork],
+ );
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export const useGlobalState = () =>
+ [
+ React.useContext(GlobalStateContext),
+ React.useContext(GlobalActionsContext),
+ ] as const;
diff --git a/packages/nextjs/global-config/feature-selection.ts b/packages/nextjs/global-config/feature-selection.ts
new file mode 100644
index 0000000..9241779
--- /dev/null
+++ b/packages/nextjs/global-config/feature-selection.ts
@@ -0,0 +1,63 @@
+import {useSearchParams} from "react-router-dom";
+import {
+ FeatureName,
+ defaultFeatureName,
+ features,
+ isValidFeatureName,
+} from "../constants";
+import {useCallback, useEffect, useState} from "react";
+
+export function getSelectedFeatureFromLocalStorage(): FeatureName {
+ let selected_feature = localStorage.getItem("selected_feature");
+ if (selected_feature) {
+ selected_feature = selected_feature.toLowerCase();
+ if (selected_feature in features) {
+ return selected_feature as FeatureName;
+ }
+ }
+ return defaultFeatureName;
+}
+
+// This is a custom hook that allows us to select a feature
+// The feature is stored in local storage across sessions and also in the url as a query param during the session lifetime.
+// don't use this hook directly in components, rather use: const [useGlobalState, {selectFeature}] = useGlobalState();
+export function useFeatureSelector() {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [selectedFeature, setSelectedFeature] = useState(
+ getSelectedFeatureFromLocalStorage,
+ );
+
+ const featureQueryParam = searchParams.get("feature");
+
+ const selectFeature = useCallback(
+ (feature_name: FeatureName) => {
+ if (!isValidFeatureName(feature_name)) return;
+ localStorage.setItem("selected_feature", feature_name);
+ // only show the "feature" param in the url when it's not "prod"
+ // we don't want the users to know the existence of the "feature" param
+ if (feature_name === defaultFeatureName) {
+ setSearchParams((prev) => {
+ const newParams = new URLSearchParams(prev);
+ newParams.delete("feature");
+ return newParams;
+ });
+ } else {
+ setSearchParams((prev) => {
+ const newParams = new URLSearchParams(prev);
+ newParams.set("feature", feature_name);
+ return newParams;
+ });
+ }
+ setSelectedFeature(feature_name);
+ },
+ [setSearchParams],
+ );
+
+ useEffect(() => {
+ if (featureQueryParam) {
+ selectFeature(featureQueryParam as FeatureName);
+ }
+ }, [featureQueryParam, selectFeature]);
+
+ return [selectedFeature, selectFeature] as const;
+}
diff --git a/packages/nextjs/global-config/network-selection.ts b/packages/nextjs/global-config/network-selection.ts
new file mode 100644
index 0000000..cf1a6ec
--- /dev/null
+++ b/packages/nextjs/global-config/network-selection.ts
@@ -0,0 +1,80 @@
+import {useSearchParams} from "react-router-dom";
+import {
+ NetworkName,
+ isValidNetworkName,
+ defaultNetworkName,
+} from "../constants";
+import {useEffect} from "react";
+
+const SELECTED_NETWORK_LOCAL_STORAGE_KEY = "selected_network";
+
+function getUserSelectedNetworkFromLocalStorageWithDefault(): NetworkName {
+ const network = localStorage.getItem(SELECTED_NETWORK_LOCAL_STORAGE_KEY);
+ if (!isValidNetworkName(network ?? "")) {
+ return defaultNetworkName;
+ }
+ return network as NetworkName;
+}
+
+function writeSelectedNetworkToLocalStorage(network: NetworkName) {
+ const currentLocalStorageNetwork = localStorage.getItem(
+ SELECTED_NETWORK_LOCAL_STORAGE_KEY,
+ );
+ if (network === defaultNetworkName && currentLocalStorageNetwork != null) {
+ // if network selection is default network (i.e. mainnet) we remove the local storage entry
+ localStorage.removeItem(SELECTED_NETWORK_LOCAL_STORAGE_KEY);
+ } else if (currentLocalStorageNetwork !== network) {
+ localStorage.setItem(SELECTED_NETWORK_LOCAL_STORAGE_KEY, network);
+ }
+}
+
+// This is a custom hook that allows us to read and write the selectedNetwork.
+// Note that this hook implements essentially 3 things:
+// 1. The hook will return the currently selected network, which is essentially whatever is contained in the URL query param "network".
+// 2. If the URL query param "network" is not present, the hook will perform this on initialization:
+// 1. check localStorage for a previously selected network. If no previously selected network is found, it will default to the defaultNetworkName.
+// 2. set the URL query param "network" to the result of 1.
+// 3. Lastly, the hook provides a function to explicitly select/switch a network. This function will update the URL query param "network" and also store the selected network in local storage.
+// This is aimed to be used by the network selection dropdown in the header.
+// WARNING: don't use this hook directly in components, rather use: const [useGlobalState, {selectNetwork}] = useGlobalState();
+export function useNetworkSelector() {
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const selectedNetworkQueryParam = searchParams.get("network") ?? "";
+
+ function selectNetwork(
+ network: NetworkName,
+ {replace = false}: {replace?: boolean} = {},
+ ) {
+ if (!isValidNetworkName(network)) return;
+ setSearchParams(
+ (prev) => {
+ const newParams = new URLSearchParams(prev);
+ newParams.set("network", network);
+ return newParams;
+ },
+ {replace},
+ );
+ writeSelectedNetworkToLocalStorage(network);
+ }
+
+ // on init check for existence of network query param, if not present, check local storage for a previously selected network. Then set query param to the network defined in local storage.
+ useEffect(
+ () => {
+ const currentNetworkSearchParam = searchParams.get("network");
+ if (!isValidNetworkName(currentNetworkSearchParam ?? "")) {
+ selectNetwork(getUserSelectedNetworkFromLocalStorageWithDefault(), {
+ replace: true,
+ });
+ }
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [], // empty [] makes this effect only run once (on mount)
+ );
+
+ if (isValidNetworkName(selectedNetworkQueryParam)) {
+ return [selectedNetworkQueryParam, selectNetwork] as const;
+ } else {
+ return [defaultNetworkName, selectNetwork] as const;
+ }
+}
diff --git a/packages/nextjs/hooks/client.ts b/packages/nextjs/hooks/client.ts
new file mode 100644
index 0000000..b199628
--- /dev/null
+++ b/packages/nextjs/hooks/client.ts
@@ -0,0 +1,37 @@
+export enum ResponseErrorType {
+ NOT_FOUND = "Not found",
+ UNHANDLED = "Unhandled",
+ TOO_MANY_REQUESTS = "To Many Requests",
+}
+
+export type ResponseError =
+ | {type: ResponseErrorType.NOT_FOUND; message?: string}
+ | {type: ResponseErrorType.UNHANDLED; message: string}
+ | {type: ResponseErrorType.TOO_MANY_REQUESTS; message?: string};
+
+export async function withResponseError(promise: Promise): Promise {
+ return await promise.catch((error) => {
+ console.error("ERROR!", error, typeof error);
+ if (typeof error == "object" && "status" in error) {
+ // This is a request!
+ error = error as Response;
+ if (error.status === 404) {
+ throw {type: ResponseErrorType.NOT_FOUND};
+ }
+ }
+ if (
+ error.message
+ .toLowerCase()
+ .includes(ResponseErrorType.TOO_MANY_REQUESTS.toLowerCase())
+ ) {
+ throw {
+ type: ResponseErrorType.TOO_MANY_REQUESTS,
+ };
+ }
+
+ throw {
+ type: ResponseErrorType.UNHANDLED,
+ message: error.toString(),
+ };
+ });
+}
diff --git a/packages/nextjs/hooks/index.ts b/packages/nextjs/hooks/index.ts
new file mode 100644
index 0000000..502852e
--- /dev/null
+++ b/packages/nextjs/hooks/index.ts
@@ -0,0 +1,313 @@
+import {AptosClient, Types} from "aptos";
+import {withResponseError} from "./client";
+
+// export async function getTransactions(
+// requestParameters: {start?: number; limit?: number},
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// const {start, limit} = requestParameters;
+// let bigStart;
+// if (start !== undefined) {
+// bigStart = BigInt(start);
+// }
+// const transactions = await withResponseError(
+// client.getTransactions({start: bigStart, limit}),
+// );
+
+// // Sort in descending order
+// transactions.sort(sortTransactions);
+
+// return transactions;
+// }
+
+// export async function getAccountTransactions(
+// requestParameters: {address: string; start?: number; limit?: number},
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// const {address, start, limit} = requestParameters;
+// let bigStart;
+// if (start !== undefined) {
+// bigStart = BigInt(start);
+// }
+// const transactions = await withResponseError(
+// client.getAccountTransactions(address, {start: bigStart, limit}),
+// );
+
+// // Sort in descending order
+// transactions.sort(sortTransactions);
+
+// return transactions;
+// }
+
+// export function getTransaction(
+// requestParameters: {txnHashOrVersion: string | number},
+// nodeUrl: string,
+// ): Promise {
+// const {txnHashOrVersion} = requestParameters;
+// if (typeof txnHashOrVersion === "number" || isNumeric(txnHashOrVersion)) {
+// const version =
+// typeof txnHashOrVersion === "number"
+// ? txnHashOrVersion
+// : parseInt(txnHashOrVersion);
+// return getTransactionByVersion(version, nodeUrl);
+// } else {
+// return getTransactionByHash(txnHashOrVersion as string, nodeUrl);
+// }
+// }
+
+// function getTransactionByVersion(
+// version: number,
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// return withResponseError(client.getTransactionByVersion(BigInt(version)));
+// }
+
+// function getTransactionByHash(
+// hash: string,
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// return withResponseError(client.getTransactionByHash(hash));
+// }
+
+// export function getLedgerInfo(nodeUrl: string): Promise {
+// const client = new AptosClient(nodeUrl);
+// return withResponseError(client.getLedgerInfo());
+// }
+
+// export function getLedgerInfoWithoutResponseError(
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// return client.getLedgerInfo();
+// }
+
+// export function getAccount(
+// requestParameters: {address: string},
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// const {address} = requestParameters;
+// return withResponseError(client.getAccount(address));
+// }
+
+export function getAccountResources(
+ requestParameters: {address: string; ledgerVersion?: number},
+ nodeUrl: string,
+): Promise {
+ const client = new AptosClient(nodeUrl);
+ const {address, ledgerVersion} = requestParameters;
+ let ledgerVersionBig;
+ if (ledgerVersion !== undefined) {
+ ledgerVersionBig = BigInt(ledgerVersion);
+ }
+ return withResponseError(
+ client.getAccountResources(address, {ledgerVersion: ledgerVersionBig}),
+ );
+}
+
+// export function getAccountResource(
+// requestParameters: {
+// address: string;
+// resourceType: string;
+// ledgerVersion?: number;
+// },
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// const {address, resourceType, ledgerVersion} = requestParameters;
+// let ledgerVersionBig;
+// if (ledgerVersion !== undefined) {
+// ledgerVersionBig = BigInt(ledgerVersion);
+// }
+// return withResponseError(
+// client.getAccountResource(address, resourceType, {
+// ledgerVersion: ledgerVersionBig,
+// }),
+// );
+// }
+
+export function getAccountModules(
+ requestParameters: {address: string; ledgerVersion?: number},
+ nodeUrl: string,
+): Promise {
+ const client = new AptosClient(nodeUrl);
+ const {address, ledgerVersion} = requestParameters;
+ let ledgerVersionBig;
+ if (ledgerVersion !== undefined) {
+ ledgerVersionBig = BigInt(ledgerVersion);
+ }
+ return withResponseError(
+ client.getAccountModules(address, {ledgerVersion: ledgerVersionBig}),
+ );
+}
+
+export function getAccountModule(
+ requestParameters: {
+ address: string;
+ moduleName: string;
+ ledgerVersion?: number;
+ },
+ nodeUrl: string,
+): Promise {
+ const client = new AptosClient(nodeUrl);
+ const {address, moduleName, ledgerVersion} = requestParameters;
+ let ledgerVersionBig;
+ if (ledgerVersion !== undefined) {
+ ledgerVersionBig = BigInt(ledgerVersion);
+ }
+ return withResponseError(
+ client.getAccountModule(address, moduleName, {
+ ledgerVersion: ledgerVersionBig,
+ }),
+ );
+}
+
+export function view(
+ request: Types.ViewRequest,
+ nodeUrl: string,
+ ledgerVersion?: string,
+): Promise {
+ const client = new AptosClient(nodeUrl);
+ let parsedVersion = ledgerVersion;
+
+ // Handle non-numbers, to default to the latest ledger version
+ if (typeof ledgerVersion === "string" && isNaN(parseInt(ledgerVersion, 10))) {
+ parsedVersion = undefined;
+ }
+
+ return client.view(request, parsedVersion);
+}
+
+// export function getTableItem(
+// requestParameters: {tableHandle: string; data: Types.TableItemRequest},
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// const {tableHandle, data} = requestParameters;
+// return withResponseError(client.getTableItem(tableHandle, data));
+// }
+
+// export function getBlockByHeight(
+// requestParameters: {height: number; withTransactions: boolean},
+// nodeUrl: string,
+// ): Promise {
+// const {height, withTransactions} = requestParameters;
+// const client = new AptosClient(nodeUrl);
+// return withResponseError(client.getBlockByHeight(height, withTransactions));
+// }
+
+// export function getBlockByVersion(
+// requestParameters: {version: number; withTransactions: boolean},
+// nodeUrl: string,
+// ): Promise {
+// const {version, withTransactions} = requestParameters;
+// const client = new AptosClient(nodeUrl);
+// return withResponseError(client.getBlockByVersion(version, withTransactions));
+// }
+
+// export async function getRecentBlocks(
+// currentBlockHeight: number,
+// count: number,
+// nodeUrl: string,
+// ): Promise {
+// const client = new AptosClient(nodeUrl);
+// const blocks = [];
+// for (let i = 0; i < count; i++) {
+// const block = await client.getBlockByHeight(currentBlockHeight - i, false);
+// blocks.push(block);
+// }
+// return blocks;
+// }
+
+// export async function getStake(
+// client: AptosClient,
+// delegatorAddress: Types.Address,
+// validatorAddress: Types.Address,
+// ): Promise {
+// const payload: Types.ViewRequest = {
+// function: "0x1::delegation_pool::get_stake",
+// type_arguments: [],
+// arguments: [validatorAddress, delegatorAddress],
+// };
+// return withResponseError(client.view(payload));
+// }
+
+// export async function getValidatorCommission(
+// client: AptosClient,
+// validatorAddress: Types.Address,
+// ): Promise {
+// const payload: Types.ViewRequest = {
+// function: "0x1::delegation_pool::operator_commission_percentage",
+// type_arguments: [],
+// arguments: [validatorAddress],
+// };
+// return withResponseError(client.view(payload));
+// }
+
+// export async function getValidatorCommissionChange(
+// client: AptosClient,
+// validatorAddress: Types.Address,
+// ): Promise {
+// const payload: Types.ViewRequest = {
+// function:
+// "0x1::delegation_pool::operator_commission_percentage_next_lockup_cycle",
+// type_arguments: [],
+// arguments: [validatorAddress],
+// };
+// return withResponseError(client.view(payload));
+// }
+
+// export async function getDelegationPoolExist(
+// client: AptosClient,
+// validatorAddress: Types.Address,
+// ): Promise {
+// const payload: Types.ViewRequest = {
+// function: "0x1::delegation_pool::delegation_pool_exists",
+// type_arguments: [],
+// arguments: [validatorAddress],
+// };
+// return withResponseError(client.view(payload));
+// }
+
+// // Return whether `pending_inactive` stake can be directly withdrawn from the delegation pool,
+// // for the edge case when the validator had gone inactive before its lockup expired.
+// export async function getCanWithdrawPendingInactive(
+// client: AptosClient,
+// validatorAddress: Types.Address,
+// ): Promise {
+// const payload: Types.ViewRequest = {
+// function: "0x1::delegation_pool::can_withdraw_pending_inactive",
+// type_arguments: [],
+// arguments: [validatorAddress],
+// };
+// return withResponseError(client.view(payload));
+// }
+
+// export async function getAddStakeFee(
+// client: AptosClient,
+// validatorAddress: Types.Address,
+// amount: string,
+// ): Promise {
+// const payload: Types.ViewRequest = {
+// function: "0x1::delegation_pool::get_add_stake_fee",
+// type_arguments: [],
+// arguments: [validatorAddress, (Number(amount) * OCTA).toString()],
+// };
+// return withResponseError(client.view(payload));
+// }
+
+// export async function getValidatorState(
+// client: AptosClient,
+// validatorAddress: Types.Address,
+// ): Promise {
+// const payload: Types.ViewRequest = {
+// function: "0x1::stake::get_validator_state",
+// type_arguments: [],
+// arguments: [validatorAddress],
+// };
+// return withResponseError(client.view(payload));
+// }
diff --git a/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts b/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts
index 8f649c3..7b41c69 100644
--- a/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { useTargetNetwork } from "./useTargetNetwork";
import { useIsMounted } from "usehooks-ts";
import { usePublicClient } from "wagmi";
-import { Contract, ContractCodeStatus, ContractName, contracts } from "~~/utils/scaffold-eth/contract";
+import { Contract, ContractCodeStatus, ContractName, contracts } from "~~/utils/scaffold-move/contract";
/**
* Gets the matching contract info for the provided contract name from the contracts present in deployedContracts.ts
@@ -10,30 +10,31 @@ import { Contract, ContractCodeStatus, ContractName, contracts } from "~~/utils/
*/
export const useDeployedContractInfo = (contractName: TContractName) => {
const isMounted = useIsMounted();
- const { targetNetwork } = useTargetNetwork();
- const deployedContract = contracts?.[targetNetwork.id]?.[contractName as ContractName] as Contract;
+ const targetNetwork = "devnet";
+ const deployedContract = contracts?.[targetNetwork]?.[contractName.toString()] as Contract;
+ console.log("deployedContract", deployedContract);
const [status, setStatus] = useState(ContractCodeStatus.LOADING);
- const publicClient = usePublicClient({ chainId: targetNetwork.id });
+ // const publicClient = usePublicClient({ chainId: targetNetwork.id });
useEffect(() => {
const checkContractDeployment = async () => {
try {
- if (!isMounted() || !publicClient) return;
+ // if (!isMounted() || !publicClient) return;
if (!deployedContract) {
setStatus(ContractCodeStatus.NOT_FOUND);
return;
}
- const code = await publicClient.getBytecode({
- address: deployedContract.address,
- });
+ // const code = await publicClient.getBytecode({
+ // address: deployedContract.address,
+ // });
- // If contract code is `0x` => no contract deployed on that address
- if (code === "0x") {
- setStatus(ContractCodeStatus.NOT_FOUND);
- return;
- }
+ // // If contract code is `0x` => no contract deployed on that address
+ // if (code === "0x") {
+ // setStatus(ContractCodeStatus.NOT_FOUND);
+ // return;
+ // }
setStatus(ContractCodeStatus.DEPLOYED);
} catch (e) {
console.error(e);
@@ -42,7 +43,7 @@ export const useDeployedContractInfo = (cont
};
checkContractDeployment();
- }, [isMounted, contractName, deployedContract, publicClient]);
+ }, [isMounted, contractName, deployedContract]);
return {
data: status === ContractCodeStatus.DEPLOYED ? deployedContract : undefined,
diff --git a/packages/nextjs/hooks/scaffold-move/useGetAccountAPTBalance.ts b/packages/nextjs/hooks/scaffold-move/useGetAccountAPTBalance.ts
new file mode 100644
index 0000000..c8d65d6
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-move/useGetAccountAPTBalance.ts
@@ -0,0 +1,30 @@
+import {Types} from "aptos";
+import {useGetAccountResources} from "./useGetAccountResources";
+
+interface CoinStore {
+ coin: {
+ value: string;
+ };
+}
+
+export function useGetAccountAPTBalance(address: string) {
+ console.log("useGetAccountAPTBalance", address);
+ const {isLoading, data, error} = useGetAccountResources(address);
+
+ if (isLoading || error || !data) {
+ return null;
+ }
+
+ const coinStore = data.find(
+ (resource) =>
+ resource.type === "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>",
+ );
+
+ if (!coinStore) {
+ return null;
+ }
+
+ const coinStoreData: CoinStore = coinStore.data as CoinStore;
+
+ return coinStoreData?.coin?.value;
+}
diff --git a/packages/nextjs/hooks/scaffold-move/useGetAccountModules.ts b/packages/nextjs/hooks/scaffold-move/useGetAccountModules.ts
new file mode 100644
index 0000000..395a14d
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-move/useGetAccountModules.ts
@@ -0,0 +1,16 @@
+import {Types} from "aptos";
+import {useQuery, UseQueryResult} from "@tanstack/react-query";
+import {getAccountModules} from "..";
+import {ResponseError} from "../client";
+import {useGlobalState} from "../../global-config/GlobalConfig";
+
+export function useGetAccountModules(
+ address: string,
+): UseQueryResult {
+ const [state] = useGlobalState();
+
+ return useQuery, ResponseError>({
+ queryKey: ["accountModules", {address}, state.network_value],
+ queryFn: () => getAccountModules({address}, state.network_value),
+ });
+}
diff --git a/packages/nextjs/hooks/scaffold-move/useGetAccountResources.ts b/packages/nextjs/hooks/scaffold-move/useGetAccountResources.ts
new file mode 100644
index 0000000..b6c7b3e
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-move/useGetAccountResources.ts
@@ -0,0 +1,20 @@
+import {Types} from "aptos";
+import {useQuery, UseQueryResult} from "@tanstack/react-query";
+import {getAccountResources} from "..";
+import {ResponseError} from "../client";
+import {useGlobalState} from "../../global-config/GlobalConfig";
+
+export function useGetAccountResources(
+ address: string,
+ options?: {
+ retry?: number | boolean;
+ },
+): UseQueryResult {
+ const [state] = useGlobalState();
+ const test = useQuery, ResponseError>({
+ queryKey: ["accountResources", {address}, state.network_value],
+ queryFn: () => getAccountResources({address}, state.network_value),
+ retry: options?.retry ?? false,
+ });
+ return test;
+}
diff --git a/packages/nextjs/hooks/scaffold-move/useGraphqlClient.tsx b/packages/nextjs/hooks/scaffold-move/useGraphqlClient.tsx
new file mode 100644
index 0000000..8b196eb
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-move/useGraphqlClient.tsx
@@ -0,0 +1,81 @@
+import React from "react";
+import {
+ ApolloClient,
+ InMemoryCache,
+ ApolloProvider,
+ HttpLink,
+ NormalizedCacheObject,
+} from "@apollo/client";
+import {useEffect, useState} from "react";
+import {NetworkName} from "../../constants";
+import {useGlobalState} from "../../global-config/GlobalConfig";
+
+function getIsGraphqlClientSupportedFor(networkName: NetworkName): boolean {
+ const graphqlUri = getGraphqlURI(networkName);
+ return typeof graphqlUri === "string" && graphqlUri.length > 0;
+}
+
+export function getGraphqlURI(networkName: NetworkName): string | undefined {
+ switch (networkName) {
+ case "mainnet":
+ return "https://api.mainnet.aptoslabs.com/v1/graphql";
+ case "testnet":
+ return "https://api-staging.testnet.aptoslabs.com/v1/graphql";
+ case "devnet":
+ return "https://api-staging.devnet.aptoslabs.com/v1/graphql";
+ case "local":
+ return "http://127.0.0.1:8090/v1/graphql";
+ case "randomnet":
+ return "https://indexer.random.aptoslabs.com/v1/graphql";
+ default:
+ return undefined;
+ }
+}
+
+function getGraphqlClient(
+ networkName: NetworkName,
+): ApolloClient {
+ return new ApolloClient({
+ link: new HttpLink({
+ uri: getGraphqlURI(networkName),
+ }),
+ cache: new InMemoryCache(),
+ });
+}
+
+export function useGetGraphqlClient() {
+ const [state] = useGlobalState();
+ const [graphqlClient, setGraphqlClient] = useState<
+ ApolloClient
+ >(getGraphqlClient(state.network_name));
+
+ useEffect(() => {
+ setGraphqlClient(getGraphqlClient(state.network_name));
+ }, [state.network_name]);
+
+ return graphqlClient;
+}
+
+type GraphqlClientProviderProps = {
+ children: React.ReactNode;
+};
+
+export function GraphqlClientProvider({children}: GraphqlClientProviderProps) {
+ const graphqlClient = useGetGraphqlClient();
+
+ return {children} ;
+}
+
+export function useGetIsGraphqlClientSupported(): boolean {
+ const [state] = useGlobalState();
+ const [isGraphqlClientSupported, setIsGraphqlClientSupported] =
+ useState(getIsGraphqlClientSupportedFor(state.network_name));
+
+ useEffect(() => {
+ setIsGraphqlClientSupported(
+ getIsGraphqlClientSupportedFor(state.network_name),
+ );
+ }, [state.network_name]);
+
+ return isGraphqlClientSupported;
+}
diff --git a/packages/nextjs/hooks/scaffold-move/useSubmitTransaction.ts b/packages/nextjs/hooks/scaffold-move/useSubmitTransaction.ts
new file mode 100644
index 0000000..eab968d
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-move/useSubmitTransaction.ts
@@ -0,0 +1,116 @@
+import {FailedTransactionError} from "aptos";
+import {useEffect, useState} from "react";
+import {
+ useWallet,
+ InputTransactionData,
+} from "@aptos-labs/wallet-adapter-react";
+import {useGlobalState} from "../../global-config/GlobalConfig";
+
+export type TransactionResponse =
+ | TransactionResponseOnSubmission
+ | TransactionResponseOnError;
+
+// "submission" here means that the transaction is posted on chain and gas is paid.
+// However, the status of the transaction might not be "success".
+export type TransactionResponseOnSubmission = {
+ transactionSubmitted: true;
+ transactionHash: string;
+ success: boolean; // indicates if the transaction submitted but failed or not
+ message?: string; // error message if the transaction failed
+};
+
+export type TransactionResponseOnError = {
+ transactionSubmitted: false;
+ message: string;
+};
+
+const useSubmitTransaction = () => {
+ const [transactionResponse, setTransactionResponse] =
+ useState(null);
+ const [transactionInProcess, setTransactionInProcess] =
+ useState(false);
+ const [state] = useGlobalState();
+ const {signAndSubmitTransaction, network} = useWallet();
+
+ useEffect(() => {
+ if (transactionResponse !== null) {
+ setTransactionInProcess(false);
+ }
+ }, [transactionResponse]);
+
+ async function submitTransaction(transaction: InputTransactionData) {
+ console.log("network", network?.name, state.network_name);
+ // if (
+ // network?.name.toLocaleLowerCase() !==
+ // (state.network_name === "local" ? "localhost" : state.network_name)
+ // ) {
+ // setTransactionResponse({
+ // transactionSubmitted: false,
+ // message:
+ // "Wallet and Explorer should use the same network to submit a transaction",
+ // });
+ // return;
+ // }
+
+
+ setTransactionInProcess(true);
+ console.log("submitting transaction", transaction);
+ const signAndSubmitTransactionCall = async (
+ transaction: InputTransactionData,
+ ): Promise => {
+ const responseOnError: TransactionResponseOnError = {
+ transactionSubmitted: false,
+ message: "Unknown Error",
+ };
+
+ let response;
+ try {
+ response = await signAndSubmitTransaction(transaction);
+ console.log("response", response);
+
+ // transaction submit succeed
+ if ("hash" in response) {
+ await state.aptos_client.waitForTransaction(response["hash"], {
+ checkSuccess: true,
+ });
+ return {
+ transactionSubmitted: true,
+ transactionHash: response["hash"],
+ success: true,
+ };
+ }
+ // transaction failed
+ return {...responseOnError, message: response.message};
+ } catch (error) {
+ if (error instanceof FailedTransactionError) {
+ return {
+ transactionSubmitted: true,
+ transactionHash: response ? response.hash : "",
+ message: error.message,
+ success: false,
+ };
+ } else if (error instanceof Error) {
+ return {...responseOnError, message: error.message};
+ }
+ }
+ return responseOnError;
+ };
+
+ await signAndSubmitTransactionCall(transaction).then(
+ setTransactionResponse,
+ );
+ }
+
+ function clearTransactionResponse() {
+ setTransactionResponse(null);
+ }
+
+ return {
+ submitTransaction,
+ transactionInProcess,
+ transactionResponse,
+ clearTransactionResponse,
+ };
+};
+
+export default useSubmitTransaction;
diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index a6c02b5..ed4894c 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -14,6 +14,7 @@
"vercel:yolo": "vercel --build-env NEXT_PUBLIC_IGNORE_BUILD_ERROR=true"
},
"dependencies": {
+ "@apollo/client": "^3.10.8",
"@aptos-labs/wallet-adapter-ant-design": "^2.6.2",
"@aptos-labs/wallet-adapter-react": "^3.4.2",
"@heroicons/react": "^2.0.11",
@@ -25,6 +26,7 @@
"blo": "^1.0.1",
"burner-connector": "^0.0.8",
"daisyui": "4.5.0",
+ "graphql": "^16.9.0",
"next": "^14.0.4",
"next-themes": "^0.2.1",
"nprogress": "^0.2.0",
@@ -33,7 +35,9 @@
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
+ "react-hook-form": "^7.52.1",
"react-hot-toast": "^2.4.0",
+ "react-router-dom": "^6.24.1",
"use-debounce": "^8.0.4",
"usehooks-ts": "^2.13.0",
"viem": "2.13.6",
diff --git a/packages/nextjs/utils/scaffold-eth/contract.ts b/packages/nextjs/utils/scaffold-eth/contract.ts
index 68e494c..b8ea655 100644
--- a/packages/nextjs/utils/scaffold-eth/contract.ts
+++ b/packages/nextjs/utils/scaffold-eth/contract.ts
@@ -24,8 +24,8 @@ import {
import { Config, UseReadContractParameters, UseWatchContractEventParameters } from "wagmi";
import { WriteContractParameters, WriteContractReturnType } from "wagmi/actions";
import { WriteContractVariables } from "wagmi/query";
-import deployedContractsData from "~~/contracts/deployedContracts";
-import externalContractsData from "~~/contracts/externalContracts";
+import deployedContractsData from "~~/contracts/deployedModules";
+import externalContractsData from "~~/contracts/externalModules";
import scaffoldConfig from "~~/scaffold.config";
type AddExternalFlag = {
diff --git a/packages/nextjs/utils/scaffold-eth/decodeTxData.ts b/packages/nextjs/utils/scaffold-eth/decodeTxData.ts
index 65c20f7..5b9a3b8 100644
--- a/packages/nextjs/utils/scaffold-eth/decodeTxData.ts
+++ b/packages/nextjs/utils/scaffold-eth/decodeTxData.ts
@@ -2,7 +2,7 @@ import { TransactionWithFunction } from "./block";
import { GenericContractsDeclaration } from "./contract";
import { Abi, AbiFunction, decodeFunctionData, getAbiItem } from "viem";
import { hardhat } from "viem/chains";
-import contractData from "~~/contracts/deployedContracts";
+import contractData from "~~/contracts/deployedModules";
type ContractsInterfaces = Record;
type TransactionType = TransactionWithFunction | null;
diff --git a/packages/nextjs/utils/scaffold-move/ContentValue/CurrencyValue.tsx b/packages/nextjs/utils/scaffold-move/ContentValue/CurrencyValue.tsx
new file mode 100644
index 0000000..8c2836b
--- /dev/null
+++ b/packages/nextjs/utils/scaffold-move/ContentValue/CurrencyValue.tsx
@@ -0,0 +1,97 @@
+import React from "react";
+
+const APTOS_DECIMALS = 8;
+
+function trimRight(rightSide: string) {
+ while (rightSide.endsWith("0")) {
+ rightSide = rightSide.slice(0, -1);
+ }
+ return rightSide;
+}
+
+export function getFormattedBalanceStr(
+ balance: string,
+ decimals?: number,
+ fixedDecimalPlaces?: number,
+): string {
+ // If balance is zero or decimals is 0, just return it
+ if (balance == "0" || (decimals !== undefined && decimals === 0)) {
+ return balance;
+ }
+
+ const len = balance.length;
+ decimals = decimals || APTOS_DECIMALS;
+
+ // If length is less than decimals, pad with 0s to decimals length and return
+ if (len <= decimals) {
+ return "0." + (trimRight("0".repeat(decimals - len) + balance) || "0");
+ }
+
+ // Otherwise, insert decimal point at len - decimals
+ const leftSide = BigInt(balance.slice(0, len - decimals)).toLocaleString(
+ "en-US",
+ );
+ let rightSide = balance.slice(len - decimals);
+ if (BigInt(rightSide) == BigInt(0)) {
+ return leftSide;
+ }
+
+ // remove trailing 0s
+ rightSide = trimRight(rightSide);
+ if (
+ fixedDecimalPlaces !== undefined &&
+ rightSide.length > fixedDecimalPlaces
+ ) {
+ rightSide = rightSide.slice(0, fixedDecimalPlaces - rightSide.length);
+ }
+
+ if (rightSide.length === 0 || rightSide === "0") {
+ return leftSide;
+ }
+
+ return leftSide + "." + trimRight(rightSide);
+}
+
+type CurrencyValueProps = {
+ amount: string;
+ decimals?: number;
+ fixedDecimalPlaces?: number;
+ currencyCode?: string | React.ReactNode;
+};
+
+export default function CurrencyValue({
+ amount,
+ decimals,
+ fixedDecimalPlaces,
+ currencyCode,
+}: CurrencyValueProps) {
+ const number = getFormattedBalanceStr(amount, decimals, fixedDecimalPlaces);
+ if (currencyCode) {
+ return (
+
+ {number} {currencyCode}
+
+ );
+ } else {
+ return {number} ;
+ }
+}
+
+export function APTCurrencyValue({
+ amount: amountStr,
+ decimals,
+ fixedDecimalPlaces,
+}: CurrencyValueProps) {
+ // remove leading "-" when it's a negative number
+ let amount = amountStr;
+ if (amountStr.startsWith("-")) {
+ amount = amountStr.substring(1);
+ }
+
+ return (
+
+ );
+}
diff --git a/packages/nextjs/utils/scaffold-move/contract.ts b/packages/nextjs/utils/scaffold-move/contract.ts
new file mode 100644
index 0000000..4238042
--- /dev/null
+++ b/packages/nextjs/utils/scaffold-move/contract.ts
@@ -0,0 +1,103 @@
+import type { MergeDeepRecord } from "type-fest/source/merge-deep";
+import deployedContractsData from "~~/contracts/deployedModules";
+import externalContractsData from "~~/contracts/externalModules";
+import scaffoldConfig from "~~/scaffold.config";
+
+
+type AddExternalFlag = {
+ [ChainId in keyof T]: {
+ [ContractName in keyof T[ChainId]]: T[ChainId][ContractName] & { external?: true };
+ };
+};
+
+const deepMergeContracts = , E extends Record>(
+ local: L,
+ external: E,
+) => {
+ const result: Record = {};
+ const allKeys = Array.from(new Set([...Object.keys(external), ...Object.keys(local)]));
+ for (const key of allKeys) {
+ if (!external[key]) {
+ result[key] = local[key];
+ continue;
+ }
+ const amendedExternal = Object.fromEntries(
+ Object.entries(external[key] as Record>).map(([contractName, declaration]) => [
+ contractName,
+ { ...declaration, external: true },
+ ]),
+ );
+ result[key] = { ...local[key], ...amendedExternal };
+ }
+ return result as MergeDeepRecord, AddExternalFlag, { arrayMergeMode: "replace" }>;
+};
+
+const contractsData = deepMergeContracts(deployedContractsData, externalContractsData);
+
+type MoveFunction = {
+ name: string;
+ visibility: string;
+ is_entry: boolean;
+ is_view: boolean;
+ generic_type_params: any[];
+ params: string[];
+ return: string[];
+};
+
+type MoveStructField = {
+ name: string;
+ type: string;
+};
+
+type MoveStruct = {
+ name: string;
+ is_native: boolean;
+ abilities: string[];
+ generic_type_params: any[];
+ fields: MoveStructField[];
+};
+
+export type GenericContract = {
+ bytecode: string;
+ abi?: GenericContractAbi;
+ external?: true;
+};
+
+export type GenericContractAbi = {
+ address: string; // TODO: address type
+ name: string;
+ friends: string[]; //TODO: check which type?
+ exposed_functions: MoveFunction[];
+ structs: MoveStruct[];
+}
+
+export type GenericContractsDeclaration = {
+ [chainId: string]: {
+ [contractName: string]: GenericContract;
+ };
+};
+
+export const contracts = contractsData as GenericContractsDeclaration | null;
+
+
+type ConfiguredChainId = (typeof scaffoldConfig)["targetNetworks"][0]["id"];
+
+type IsContractDeclarationMissing = typeof contractsData extends { [key in ConfiguredChainId]: any }
+ ? TNo
+ : TYes;
+
+type ContractsDeclaration = IsContractDeclarationMissing;
+
+type Contracts = ContractsDeclaration[ConfiguredChainId];
+
+
+export type ContractName = keyof Contracts;
+export type Contract = Contracts[TContractName];
+
+
+
+export enum ContractCodeStatus {
+ "LOADING",
+ "DEPLOYED",
+ "NOT_FOUND",
+}
diff --git a/packages/nextjs/utils/scaffold-move/contractsData.ts b/packages/nextjs/utils/scaffold-move/contractsData.ts
new file mode 100644
index 0000000..5a638c4
--- /dev/null
+++ b/packages/nextjs/utils/scaffold-move/contractsData.ts
@@ -0,0 +1,7 @@
+import scaffoldConfig from "~~/scaffold.config";
+import { contracts } from "~~/utils/scaffold-move/contract";
+
+export function getAllContracts() {
+ const contractsData = contracts?.["devnet"];
+ return contractsData ? contractsData : {};
+}
diff --git a/packages/nextjs/utils/utils.ts b/packages/nextjs/utils/utils.ts
new file mode 100644
index 0000000..db9513b
--- /dev/null
+++ b/packages/nextjs/utils/utils.ts
@@ -0,0 +1,282 @@
+import {HexString, Types} from "aptos";
+// import pako from "pako";
+// import {Statsig} from "statsig-react";
+/**
+ * Helper function for exhaustiveness checks.
+ *
+ * Hint: If this function is causing a type error, check to make sure that your
+ * switch statement covers all cases!
+ */
+// export function assertNever(x: never): never {
+// throw new Error("Unexpected object: " + x);
+// }
+
+// /*
+// If the transaction doesn't have a version property,
+// that means it's a pending transaction (and thus it's expected version will be higher than any existing versions).
+// We can consider the version to be Infinity for this case.
+// */
+// export function sortTransactions(
+// a: Types.Transaction,
+// b: Types.Transaction,
+// ): number {
+// const first = "version" in a ? parseInt(a.version) : Infinity;
+// const second = "version" in b ? parseInt(b.version) : Infinity;
+// return first < second ? 1 : -1;
+// }
+
+// /*
+// Converts a utf8 string encoded as hex back to string
+// if hex starts with 0x - ignore this part and start from the 3rd char (at index 2).
+// */
+// export function hex_to_string(hex: string): string {
+// const hexString = hex.toString();
+// let str = "";
+// let n = hex.startsWith("0x") ? 2 : 0;
+// for (n; n < hexString.length; n += 2) {
+// str += String.fromCharCode(parseInt(hexString.substring(n, n + 2), 16));
+// }
+// return str;
+// }
+
+// /* set localStorage with Expiry */
+// export function setLocalStorageWithExpiry(
+// key: string,
+// value: string,
+// ttl: number,
+// ) {
+// const now = new Date();
+
+// const item = {
+// value: value,
+// expiry: now.getTime() + ttl,
+// };
+
+// localStorage.setItem(key, JSON.stringify(item));
+// }
+
+// /* get localStorage with Expiry */
+// export function getLocalStorageWithExpiry(key: string) {
+// const itemStr = localStorage.getItem(key);
+
+// if (!itemStr) {
+// return null;
+// }
+
+// const item = JSON.parse(itemStr);
+// const now = new Date();
+
+// if (now.getTime() > item.expiry) {
+// localStorage.removeItem(key);
+// return null;
+// }
+
+// return item.value;
+// }
+
+// export async function fetchJsonResponse(url: string) {
+// const response = await fetch(url);
+// return await response.json();
+// }
+
+// /**
+// * Convert a module source code in gzipped hex string to plain text
+// * @param source module source code in gzipped hex string
+// * @returns original source code in plain text
+// */
+// export function transformCode(source: string): string {
+// try {
+// return pako.ungzip(new HexString(source).toUint8Array(), {to: "string"});
+// } catch {
+// return "";
+// }
+// }
+
+// export function getBytecodeSizeInKB(bytecodeHex: string): number {
+// // Convert the hex string to a byte array
+// const textEncoder = new TextEncoder();
+// const byteArray = new Uint8Array(textEncoder.encode(bytecodeHex));
+
+// // Compute the size of the byte array in kilobytes (KB)
+// const sizeInKB = byteArray.length / 1024;
+
+// // Return the size in KB with two decimal places
+// return parseFloat(sizeInKB.toFixed(2));
+// }
+
+// /**
+// * Standardizes an address to the format "0x" followed by 64 lowercase hexadecimal digits.
+// */
+// export const standardizeAddress = (address: string): string => {
+// // Convert the address to lowercase
+// address = address.toLowerCase();
+// // If the address has more than 66 characters, it's already invalid
+// if (address.length > 66) {
+// return address;
+// }
+// // Remove the "0x" prefix if present
+// const addressWithoutPrefix = address.startsWith("0x")
+// ? address.slice(2)
+// : address;
+// // If the address has more than 64 characters after removing the prefix, it's already invalid
+// if (addressWithoutPrefix.length > 64) {
+// return address;
+// }
+// // Pad the address with leading zeros if necessary to ensure it has exactly 64 characters (excluding the "0x" prefix)
+// const addressWithPadding = addressWithoutPrefix.padStart(64, "0");
+// // Return the standardized address with the "0x" prefix
+// return "0x" + addressWithPadding;
+// };
+
+// // inspired by https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
+// function escapeRegExp(regexpString: string) {
+// return regexpString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+// }
+
+// // Get the line number of a public function in a source code.
+// // The line number is zero-based.
+// // Return 0 if the function is not found.
+// export function getPublicFunctionLineNumber(
+// sourceCode: string,
+// functionName: string,
+// ) {
+// const lines = sourceCode.split("\n");
+// const publicEntryFunRegexp = new RegExp(
+// `\\s*public\\s*(entry\\s*)?fun\\s*${escapeRegExp(
+// functionName,
+// )}\\s*(?:<|\\()`,
+// );
+
+// const lineNumber = lines.findIndex((line) =>
+// line.match(publicEntryFunRegexp),
+// );
+// if (lineNumber !== -1) {
+// return lineNumber;
+// }
+
+// return 0;
+// }
+
+export function encodeInputArgsForViewRequest(type: string, value: string) {
+ if (type.includes("vector")) {
+ // when it's a vector, we support both hex and javascript array format
+ return value.trim().startsWith("0x")
+ ? value.trim()
+ : encodeVectorForViewRequest(type, value);
+ } else if (type === "bool") {
+ if (value !== "true" && value !== "false")
+ throw new Error(`Invalid bool value: ${value}`);
+
+ return value === "true" ? true : false;
+ } else if (["u8", "u16", "u32"].includes(type)) {
+ return ensureNumber(value);
+ } else if (type.startsWith("0x1::option::Option")) {
+ return {vec: [...(value ? [value] : [])]};
+ } else return value;
+}
+
+// Deserialize "[1,2,3]" or "1,2,3" to ["1", "2", "3"]
+export function deserializeVector(vectorString: string): string[] {
+ let result = vectorString.trim();
+ if (result[0] === "[" && result[result.length - 1] === "]") {
+ result = result.slice(1, -1);
+ }
+ // There's a tradeoff here between empty string, and empty array. We're going with empty array.
+ if (result.length == 0) {
+ return [];
+ }
+ return result.split(",");
+}
+
+function encodeVectorForViewRequest(type: string, value: string) {
+ const rawVector = deserializeVector(value);
+ const regex = /vector<([^]+)>/;
+ const match = type.match(regex);
+ if (match) {
+ if (match[1] === "u8") {
+ return (
+ HexString.fromUint8Array(
+ new Uint8Array(
+ rawVector.map((v) => {
+ const result = ensureNumber(v.trim());
+ if (result < 0 || result > 255)
+ throw new Error(`Invalid u8 value: ${result}`);
+ return result;
+ }),
+ ),
+ ) as any
+ ).hexString;
+ } else if (["u16", "u32"].includes(match[1])) {
+ return rawVector.map((v) => ensureNumber(v.trim()));
+ } else if (["u64", "u128", "u256"].includes(match[1])) {
+ // For bigint, not need to convert, only validation
+ rawVector.forEach((v) => ensureBigInt(v.trim()));
+ return rawVector;
+ } else if (match[1] === "bool") {
+ return rawVector.map((v) => ensureBoolean(v.trim()));
+ } else {
+ // 1. Address type no need to convert
+ // 2. Other complex types like Struct is not support yet. We just pass what user input.
+ return rawVector;
+ }
+ } else {
+ throw new Error(`Unsupported type: ${type}`);
+ }
+}
+
+function ensureNumber(val: number | string): number {
+ assertType(val, ["number", "string"]);
+ if (typeof val === "number") {
+ return val;
+ }
+
+ const res = Number.parseInt(val, 10);
+ if (Number.isNaN(res)) {
+ throw new Error("Invalid number string.");
+ }
+
+ return res;
+}
+
+export function ensureBigInt(val: number | bigint | string): bigint {
+ assertType(val, ["number", "bigint", "string"]);
+ return BigInt(val);
+}
+
+export function ensureBoolean(val: boolean | string): boolean {
+ assertType(val, ["boolean", "string"]);
+ if (typeof val === "boolean") {
+ return val;
+ }
+
+ if (val === "true") {
+ return true;
+ }
+ if (val === "false") {
+ return false;
+ }
+
+ throw new Error("Invalid boolean string.");
+}
+
+function assertType(val: any, types: string[] | string, message?: string) {
+ if (!types?.includes(typeof val)) {
+ throw new Error(
+ message ||
+ `Invalid arg: ${val} type should be ${
+ types instanceof Array ? types.join(" or ") : types
+ }`,
+ );
+ }
+}
+
+// // We should not be using statsig for logging like this, we will transition to google analytics
+// export function getStableID(): string {
+// return Statsig.initializeCalled() ? Statsig.getStableID() : "not_initialized";
+// }
+
+// address' coming back from the node trim leading zeroes
+// for example: 0x123 => 0x000...000123 (61 0s before 123)
+export function normalizeAddress(address: string): string {
+ return "0x" + address.substring(2).padStart(64, "0");
+}
diff --git a/scripts/loadContracts.js b/scripts/loadContracts.js
new file mode 100644
index 0000000..1b03801
--- /dev/null
+++ b/scripts/loadContracts.js
@@ -0,0 +1,104 @@
+const fs = require('fs');
+const path = require('path');
+const yaml = require('js-yaml');
+const { AptosClient } = require('aptos'); // Assuming you're using the Aptos SDK for JavaScript
+
+// Paths to the relevant files
+const moveTomlPath = path.join(__dirname, '../packages/move/Move.toml');
+const configYamlPath = path.join(__dirname, '../packages/move/.aptos/config.yaml');
+const deployedModulesPath = path.join(__dirname, '../packages/nextjs/contracts/deployedModules.ts');
+const externalModulesPath = path.join(__dirname, '../packages/nextjs/contracts/externalModules.ts');
+
+// Function to parse the TOML file and extract addresses
+function parseToml(filePath) {
+ const toml = fs.readFileSync(filePath, 'utf-8');
+ const addressesSection = toml.match(/\[addresses\]([\s\S]*?)(?=\[|$)/);
+ if (addressesSection) {
+ const addresses = {};
+ const lines = addressesSection[1].trim().split('\n');
+ lines.forEach(line => {
+ const [key, value] = line.split('=').map(part => part.trim().replace(/['"]+/g, ''));
+ addresses[key] = value.replace(/^0x/, ''); // Strip 0x from the address
+ });
+ return addresses;
+ }
+ return null;
+}
+
+// Function to parse the YAML config file
+function parseYaml(filePath) {
+ const yamlContent = fs.readFileSync(filePath, 'utf-8');
+ return yaml.load(yamlContent);
+}
+
+// Function to write modules to a TypeScript file
+function writeModules(filePath, modules, network, variableName) {
+ const moduleEntries = modules.map(module => {
+ return `"${module.abi.name}": {
+ "bytecode": ${JSON.stringify(module.bytecode)},
+ "abi": ${JSON.stringify(module.abi)}
+ }`;
+ }).join(',\n');
+
+ const output = `
+import { GenericContractsDeclaration } from "~~/utils/scaffold-move/contract";
+
+const ${variableName} = {
+ "${network}": {
+ ${moduleEntries}
+ }
+} as const;
+
+export default ${variableName} satisfies GenericContractsDeclaration;
+ `;
+
+ fs.writeFileSync(filePath, output.trim(), 'utf-8');
+}
+
+// Function to fetch account modules
+async function getAccountModules(requestParameters, nodeUrl) {
+ const client = new AptosClient(nodeUrl);
+ const { address, ledgerVersion } = requestParameters;
+ let ledgerVersionBig;
+ if (ledgerVersion !== undefined) {
+ ledgerVersionBig = BigInt(ledgerVersion);
+ }
+ return client.getAccountModules(address, { ledgerVersion: ledgerVersionBig });
+}
+
+// Main function to perform the tasks
+async function main() {
+ const config = parseYaml(configYamlPath);
+ const nodeUrl = config.profiles.default.rest_url;
+ const accountAddress = config.profiles.default.account.replace(/^0x/, ''); // Strip 0x from the account address
+
+ const addresses = parseToml(moveTomlPath);
+
+ // Ensure the output directory exists
+ const outputDirectory = path.dirname(deployedModulesPath);
+ if (!fs.existsSync(outputDirectory)) {
+ fs.mkdirSync(outputDirectory, { recursive: true });
+ }
+
+ // Fetch and save account modules for the account from config.yaml
+ const deployedModules = await getAccountModules({ address: accountAddress }, nodeUrl);
+ writeModules(deployedModulesPath, deployedModules, "devnet", "deployedContracts");
+ console.log(`Data for deployed modules at address ${accountAddress} saved successfully.`);
+
+ // Fetch and save account modules for each address from Move.toml, excluding the one from config.yaml
+ if (addresses) {
+ const externalModules = [];
+ for (const [name, address] of Object.entries(addresses)) {
+ if (address.toLowerCase() !== accountAddress.toLowerCase()) {
+ const modules = await getAccountModules({ address }, nodeUrl);
+ externalModules.push(...modules);
+ console.log(`Data for address ${address} saved successfully.`);
+ }
+ }
+ writeModules(externalModulesPath, externalModules, "devnet", "externalContracts");
+ } else {
+ console.log('No addresses found in Move.toml.');
+ }
+}
+
+main().catch(console.error);
diff --git a/yarn.lock b/yarn.lock
index 87269ae..7f62f06 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -98,6 +98,43 @@ __metadata:
languageName: node
linkType: hard
+"@apollo/client@npm:^3.10.8":
+ version: 3.10.8
+ resolution: "@apollo/client@npm:3.10.8"
+ dependencies:
+ "@graphql-typed-document-node/core": ^3.1.1
+ "@wry/caches": ^1.0.0
+ "@wry/equality": ^0.5.6
+ "@wry/trie": ^0.5.0
+ graphql-tag: ^2.12.6
+ hoist-non-react-statics: ^3.3.2
+ optimism: ^0.18.0
+ prop-types: ^15.7.2
+ rehackt: ^0.1.0
+ response-iterator: ^0.2.6
+ symbol-observable: ^4.0.0
+ ts-invariant: ^0.10.3
+ tslib: ^2.3.0
+ zen-observable-ts: ^1.2.5
+ peerDependencies:
+ graphql: ^15.0.0 || ^16.0.0
+ graphql-ws: ^5.5.5
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ subscriptions-transport-ws: ^0.9.0 || ^0.11.0
+ peerDependenciesMeta:
+ graphql-ws:
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+ subscriptions-transport-ws:
+ optional: true
+ checksum: 965e95389bdbde8aa5f542f11860fd930438f635560c766c716f186d5a9d6c5f0bfb31088cf28150c4f3969191030f1bbc0254418738a5737568108e96ffd815
+ languageName: node
+ linkType: hard
+
"@aptos-connect/wallet-adapter-plugin@npm:^1.0.0":
version: 1.0.0
resolution: "@aptos-connect/wallet-adapter-plugin@npm:1.0.0"
@@ -823,6 +860,15 @@ __metadata:
languageName: node
linkType: hard
+"@graphql-typed-document-node/core@npm:^3.1.1":
+ version: 3.2.0
+ resolution: "@graphql-typed-document-node/core@npm:3.2.0"
+ peerDependencies:
+ graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
+ checksum: fa44443accd28c8cf4cb96aaaf39d144a22e8b091b13366843f4e97d19c7bfeaf609ce3c7603a4aeffe385081eaf8ea245d078633a7324c11c5ec4b2011bb76d
+ languageName: node
+ linkType: hard
+
"@heroicons/react@npm:^2.0.11":
version: 2.0.18
resolution: "@heroicons/react@npm:2.0.18"
@@ -1887,6 +1933,13 @@ __metadata:
languageName: node
linkType: hard
+"@remix-run/router@npm:1.17.1":
+ version: 1.17.1
+ resolution: "@remix-run/router@npm:1.17.1"
+ checksum: f6ab2498d0b29ea76e3ddf6c6fec78c99f7c8739b2d413217fa569fca3de2256ecfd71385c78ea9d323fd9787614b25fc13db3aaa2e5d5673c9a698d448e710a
+ languageName: node
+ linkType: hard
+
"@rollup/pluginutils@npm:^4.0.0":
version: 4.2.1
resolution: "@rollup/pluginutils@npm:4.2.1"
@@ -2009,6 +2062,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@se-2/nextjs@workspace:packages/nextjs"
dependencies:
+ "@apollo/client": ^3.10.8
"@aptos-labs/wallet-adapter-ant-design": ^2.6.2
"@aptos-labs/wallet-adapter-react": ^3.4.2
"@heroicons/react": ^2.0.11
@@ -2032,6 +2086,7 @@ __metadata:
eslint-config-next: ^14.0.4
eslint-config-prettier: ^9.1.0
eslint-plugin-prettier: ^5.1.3
+ graphql: ^16.9.0
next: ^14.0.4
next-themes: ^0.2.1
nprogress: ^0.2.0
@@ -2042,7 +2097,9 @@ __metadata:
react: ^18.2.0
react-copy-to-clipboard: ^5.1.0
react-dom: ^18.2.0
+ react-hook-form: ^7.52.1
react-hot-toast: ^2.4.0
+ react-router-dom: ^6.24.1
tailwindcss: ^3.4.3
type-fest: ^4.6.0
typescript: 5.1.6
@@ -2417,6 +2474,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/js-yaml@npm:^4":
+ version: 4.0.9
+ resolution: "@types/js-yaml@npm:4.0.9"
+ checksum: e5e5e49b5789a29fdb1f7d204f82de11cb9e8f6cb24ab064c616da5d6e1b3ccfbf95aa5d1498a9fbd3b9e745564e69b4a20b6c530b5a8bbb2d4eb830cda9bc69
+ languageName: node
+ linkType: hard
+
"@types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.9":
version: 7.0.13
resolution: "@types/json-schema@npm:7.0.13"
@@ -3433,6 +3497,51 @@ __metadata:
languageName: node
linkType: hard
+"@wry/caches@npm:^1.0.0":
+ version: 1.0.1
+ resolution: "@wry/caches@npm:1.0.1"
+ dependencies:
+ tslib: ^2.3.0
+ checksum: 9e89aa8e9e08577b2e4acbe805f406b141ae49c2ac4a2e22acf21fbee68339fa0550e0dee28cf2158799f35bb812326e80212e49e2afd169f39f02ad56ae4ef4
+ languageName: node
+ linkType: hard
+
+"@wry/context@npm:^0.7.0":
+ version: 0.7.4
+ resolution: "@wry/context@npm:0.7.4"
+ dependencies:
+ tslib: ^2.3.0
+ checksum: 9bc8c30a31f9c7d36b616e89daa9280c03d196576a4f9fef800e9bd5de9434ba70216322faeeacc7ef1ab95f59185599d702538114045df729a5ceea50aef4e2
+ languageName: node
+ linkType: hard
+
+"@wry/equality@npm:^0.5.6":
+ version: 0.5.7
+ resolution: "@wry/equality@npm:0.5.7"
+ dependencies:
+ tslib: ^2.3.0
+ checksum: 892f262fae362df80f199b12658ea6966949539d4a3a50c1acf00d94a367d673a38f8efa1abcb726ae9e5cc5e62fce50c540c70f797b7c8a2c4308b401dfd903
+ languageName: node
+ linkType: hard
+
+"@wry/trie@npm:^0.4.3":
+ version: 0.4.3
+ resolution: "@wry/trie@npm:0.4.3"
+ dependencies:
+ tslib: ^2.3.0
+ checksum: 106e021125cfafd22250a6631a0438a6a3debae7bd73f6db87fe42aa0757fe67693db0dfbe200ae1f60ba608c3e09ddb8a4e2b3527d56ed0a7e02aa0ee4c94e1
+ languageName: node
+ linkType: hard
+
+"@wry/trie@npm:^0.5.0":
+ version: 0.5.0
+ resolution: "@wry/trie@npm:0.5.0"
+ dependencies:
+ tslib: ^2.3.0
+ checksum: 92aeea34152bd8485184236fe328d3d05fc98ee3b431d82ee60cf3584dbf68155419c3d65d0ff3731b204ee79c149440a9b7672784a545afddc8d4342fbf21c9
+ languageName: node
+ linkType: hard
+
"abbrev@npm:1, abbrev@npm:^1.0.0":
version: 1.1.1
resolution: "abbrev@npm:1.1.1"
@@ -6639,6 +6748,24 @@ __metadata:
languageName: node
linkType: hard
+"graphql-tag@npm:^2.12.6":
+ version: 2.12.6
+ resolution: "graphql-tag@npm:2.12.6"
+ dependencies:
+ tslib: ^2.1.0
+ peerDependencies:
+ graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
+ checksum: b15162a3d62f17b9b79302445b9ee330e041582f1c7faca74b9dec5daa74272c906ec1c34e1c50592bb6215e5c3eba80a309103f6ba9e4c1cddc350c46f010df
+ languageName: node
+ linkType: hard
+
+"graphql@npm:^16.9.0":
+ version: 16.9.0
+ resolution: "graphql@npm:16.9.0"
+ checksum: 8cb3d54100e9227310383ce7f791ca48d12f15ed9f2021f23f8735f1121aafe4e5e611a853081dd935ce221724ea1ae4638faef5d2921fb1ad7c26b5f46611e9
+ languageName: node
+ linkType: hard
+
"h3@npm:^1.8.1, h3@npm:^1.8.2":
version: 1.10.0
resolution: "h3@npm:1.10.0"
@@ -6752,6 +6879,15 @@ __metadata:
languageName: node
linkType: hard
+"hoist-non-react-statics@npm:^3.3.2":
+ version: 3.3.2
+ resolution: "hoist-non-react-statics@npm:3.3.2"
+ dependencies:
+ react-is: ^16.7.0
+ checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8
+ languageName: node
+ linkType: hard
+
"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.1":
version: 4.1.1
resolution: "http-cache-semantics@npm:4.1.1"
@@ -8777,6 +8913,18 @@ __metadata:
languageName: node
linkType: hard
+"optimism@npm:^0.18.0":
+ version: 0.18.0
+ resolution: "optimism@npm:0.18.0"
+ dependencies:
+ "@wry/caches": ^1.0.0
+ "@wry/context": ^0.7.0
+ "@wry/trie": ^0.4.3
+ tslib: ^2.3.0
+ checksum: d6ed6a90b05ee886dadfe556c7a30227c66843f51278e51eb843977a6a9368b6c50297fcc63fa514f53d8a5a58f8ddc8049c2356bd4ffac32f8961bcb806254d
+ languageName: node
+ linkType: hard
+
"optionator@npm:^0.9.3":
version: 0.9.3
resolution: "optionator@npm:0.9.3"
@@ -9304,7 +9452,7 @@ __metadata:
languageName: node
linkType: hard
-"prop-types@npm:^15.8.1":
+"prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
version: 15.8.1
resolution: "prop-types@npm:15.8.1"
dependencies:
@@ -10004,6 +10152,15 @@ __metadata:
languageName: node
linkType: hard
+"react-hook-form@npm:^7.52.1":
+ version: 7.52.1
+ resolution: "react-hook-form@npm:7.52.1"
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+ checksum: 224fec214c5c7093b6949bc0a4fce3cf9b7a567a2f36dc3c7feeb1e721c5cccbd21f0f0ab19aa1f5f912014264f9c2224181370007609693b6c5ef6778f59ca5
+ languageName: node
+ linkType: hard
+
"react-hot-toast@npm:^2.4.0":
version: 2.4.1
resolution: "react-hot-toast@npm:2.4.1"
@@ -10016,7 +10173,7 @@ __metadata:
languageName: node
linkType: hard
-"react-is@npm:^16.13.1":
+"react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f
@@ -10078,6 +10235,30 @@ __metadata:
languageName: node
linkType: hard
+"react-router-dom@npm:^6.24.1":
+ version: 6.24.1
+ resolution: "react-router-dom@npm:6.24.1"
+ dependencies:
+ "@remix-run/router": 1.17.1
+ react-router: 6.24.1
+ peerDependencies:
+ react: ">=16.8"
+ react-dom: ">=16.8"
+ checksum: 95d9183524075aeec222b8e3181c47a6f58118a82d8d83dd85bf9f94a6cd69856c71f8f5d9788e50f442b9ea694209db7a96727a394de08c828bbc212328dc95
+ languageName: node
+ linkType: hard
+
+"react-router@npm:6.24.1":
+ version: 6.24.1
+ resolution: "react-router@npm:6.24.1"
+ dependencies:
+ "@remix-run/router": 1.17.1
+ peerDependencies:
+ react: ">=16.8"
+ checksum: 18acd84a4fc19ef63316a0ed73a549d1d83b48458ca4c90e14b9f59d259984062928f4427307b68f504452eb30dfd373edda826dd21b7f75a4bc2801b7336489
+ languageName: node
+ linkType: hard
+
"react-style-singleton@npm:^2.2.1":
version: 2.2.1
resolution: "react-style-singleton@npm:2.2.1"
@@ -10225,6 +10406,21 @@ __metadata:
languageName: node
linkType: hard
+"rehackt@npm:^0.1.0":
+ version: 0.1.0
+ resolution: "rehackt@npm:0.1.0"
+ peerDependencies:
+ "@types/react": "*"
+ react: "*"
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ react:
+ optional: true
+ checksum: 2c3bcd72524bf47672640265e79cba785e0e6837b9b385ccb0a3ea7d00f55a439d9aed3e0ae71e991d88e0d4b2b3158457c92e75fff5ebf99cd46e280068ddeb
+ languageName: node
+ linkType: hard
+
"require-directory@npm:^2.1.1":
version: 2.1.1
resolution: "require-directory@npm:2.1.1"
@@ -10333,6 +10529,13 @@ __metadata:
languageName: node
linkType: hard
+"response-iterator@npm:^0.2.6":
+ version: 0.2.6
+ resolution: "response-iterator@npm:0.2.6"
+ checksum: b0db3c0665a0d698d65512951de9623c086b9c84ce015a76076d4bd0bf733779601d0b41f0931d16ae38132fba29e1ce291c1f8e6550fc32daaa2dc3ab4f338d
+ languageName: node
+ linkType: hard
+
"responselike@npm:^2.0.0":
version: 2.0.1
resolution: "responselike@npm:2.0.1"
@@ -10485,9 +10688,10 @@ __metadata:
version: 0.0.0-use.local
resolution: "se-2@workspace:."
dependencies:
- "@aptos-labs/wallet-adapter-ant-design": ^2.6.2
+ "@types/js-yaml": ^4
aptos: ^1.21.0
husky: ^8.0.1
+ js-yaml: ^4.1.0
lint-staged: ^13.0.3
languageName: unknown
linkType: soft
@@ -11032,6 +11236,13 @@ __metadata:
languageName: node
linkType: hard
+"symbol-observable@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "symbol-observable@npm:4.0.0"
+ checksum: 212c7edce6186634d671336a88c0e0bbd626c2ab51ed57498dc90698cce541839a261b969c2a1e8dd43762133d47672e8b62e0b1ce9cf4157934ba45fd172ba8
+ languageName: node
+ linkType: hard
+
"synckit@npm:^0.8.6":
version: 0.8.8
resolution: "synckit@npm:0.8.8"
@@ -11244,6 +11455,15 @@ __metadata:
languageName: node
linkType: hard
+"ts-invariant@npm:^0.10.3":
+ version: 0.10.3
+ resolution: "ts-invariant@npm:0.10.3"
+ dependencies:
+ tslib: ^2.1.0
+ checksum: bb07d56fe4aae69d8860e0301dfdee2d375281159054bc24bf1e49e513fb0835bf7f70a11351344d213a79199c5e695f37ebbf5a447188a377ce0cd81d91ddb5
+ languageName: node
+ linkType: hard
+
"ts-morph@npm:12.0.0":
version: 12.0.0
resolution: "ts-morph@npm:12.0.0"
@@ -11325,7 +11545,7 @@ __metadata:
languageName: node
linkType: hard
-"tslib@npm:^2.6.2":
+"tslib@npm:^2.3.0, tslib@npm:^2.6.2":
version: 2.6.3
resolution: "tslib@npm:2.6.3"
checksum: 74fce0e100f1ebd95b8995fbbd0e6c91bdd8f4c35c00d4da62e285a3363aaa534de40a80db30ecfd388ed7c313c42d930ee0eaf108e8114214b180eec3dbe6f5
@@ -12357,6 +12577,22 @@ __metadata:
languageName: node
linkType: hard
+"zen-observable-ts@npm:^1.2.5":
+ version: 1.2.5
+ resolution: "zen-observable-ts@npm:1.2.5"
+ dependencies:
+ zen-observable: 0.8.15
+ checksum: 3b707b7a0239a9bc40f73ba71b27733a689a957c1f364fabb9fa9cbd7d04b7c2faf0d517bf17004e3ed3f4330ac613e84c0d32313e450ddaa046f3350af44541
+ languageName: node
+ linkType: hard
+
+"zen-observable@npm:0.8.15":
+ version: 0.8.15
+ resolution: "zen-observable@npm:0.8.15"
+ checksum: b7289084bc1fc74a559b7259faa23d3214b14b538a8843d2b001a35e27147833f4107590b1b44bf5bc7f6dfe6f488660d3a3725f268e09b3925b3476153b7821
+ languageName: node
+ linkType: hard
+
"zustand@npm:4.4.1, zustand@npm:^4.1.2":
version: 4.4.1
resolution: "zustand@npm:4.4.1"