diff --git a/apps/shell/src/app/error.tsx b/apps/shell/src/app/error.tsx
index f185176ce..f15146e8e 100644
--- a/apps/shell/src/app/error.tsx
+++ b/apps/shell/src/app/error.tsx
@@ -1,6 +1,7 @@
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
+import { Button, Container, Heading } from '@haqq/shell-ui-kit';
export default function ErrorPage({
error,
@@ -15,15 +16,18 @@ export default function ErrorPage({
}, [error]);
return (
-
-
Something went wrong!
- {
- reset();
- }}
- >
- Try again
-
-
+
+
+ Something went wrong!
+
+ {
+ reset();
+ }}
+ >
+ Try again
+
+
+
);
}
diff --git a/apps/shell/src/app/global-error.tsx b/apps/shell/src/app/global-error.tsx
index ddd0f00dd..46083527f 100644
--- a/apps/shell/src/app/global-error.tsx
+++ b/apps/shell/src/app/global-error.tsx
@@ -2,6 +2,7 @@
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
import NextError from 'next/error';
+import { Button, Heading } from '@haqq/shell-ui-kit';
export default function GlobalError({
error,
@@ -17,17 +18,21 @@ export default function GlobalError({
return (
- Something went wrong!
- {/* This is the default Next.js error component but it doesn't allow omitting the statusCode property yet. */}
- {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
-
- {
- reset();
- }}
- >
- Try again
-
+
+ Something went wrong!
+
+ {
+ reset();
+ }}
+ >
+ Try again
+
+
+ {/* This is the default Next.js error component but it doesn't allow omitting the statusCode property yet. */}
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
+
+
);
diff --git a/apps/shell/src/app/global.css b/apps/shell/src/app/global.css
index 2e0650451..7d04e0107 100644
--- a/apps/shell/src/app/global.css
+++ b/apps/shell/src/app/global.css
@@ -38,10 +38,4 @@ body {
body {
@apply bg-haqq-black font-guise text-white will-change-scroll;
-
- /* background-image: url(/sunrise.jpg);
- background-position: top center;
- background-repeat: no-repeat;
- background-size: contain;
- background-attachment: fixed; */
}
diff --git a/apps/shell/src/app/governance/proposal/new/page.tsx b/apps/shell/src/app/governance/proposal/new/page.tsx
new file mode 100644
index 000000000..385cf3c86
--- /dev/null
+++ b/apps/shell/src/app/governance/proposal/new/page.tsx
@@ -0,0 +1,37 @@
+import {
+ HydrationBoundary,
+ QueryClient,
+ dehydrate,
+} from '@tanstack/react-query';
+import { Container } from '@haqq/shell-ui-kit';
+import { CreateSoftwareUpgradeProposalFormKeplr } from '../../../../components/submit-proposal-su-form-keplr';
+import { CreateTextProposalFormKeplr } from '../../../../components/submit-proposal-text-form-keplr';
+
+export default async function CreateProposal() {
+ const queryClient = new QueryClient();
+
+ const dehydratedState = dehydrate(queryClient);
+
+ return (
+
+
+
+
+
+ Submit proposal
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/shell/src/components/submit-proposal-form.tsx b/apps/shell/src/components/submit-proposal-form.tsx
new file mode 100644
index 000000000..3472c8e30
--- /dev/null
+++ b/apps/shell/src/components/submit-proposal-form.tsx
@@ -0,0 +1,439 @@
+// 'use client';
+// import { useCallback } from 'react';
+// import { yupResolver } from '@hookform/resolvers/yup';
+// import clsx from 'clsx';
+// import Link from 'next/link';
+// import { useForm, SubmitHandler } from 'react-hook-form';
+// import { useNetwork } from 'wagmi';
+// import * as Yup from 'yup';
+// import { getChainParams } from '@haqq/data-access-cosmos';
+// import {
+// ICreateTextProposalForm,
+// getFormattedAddress,
+// useProposalActions,
+// useSupportedChains,
+// useToast,
+// } from '@haqq/shell-shared';
+// import {
+// Button,
+// Container,
+// Heading,
+// LinkIcon,
+// ToastError,
+// ToastLoading,
+// ToastSuccess,
+// } from '@haqq/shell-ui-kit';
+// import { FormInputError } from './submit-proposal-su-form-keplr';
+
+// const inputClassnames = clsx(
+// 'inline-block w-full rounded-[6px] border border-white/20 bg-transparent px-[16px] py-[12px] text-[14px] leading-[22px] text-white placeholder-white/25 outline-none',
+// 'transition-colors duration-150 ease-in will-change-[color,background]',
+// 'focus:border-white/30 focus:bg-transparent focus:text-white',
+// 'hover:border-white/10',
+// );
+
+// const submitTextProposalSchema = Yup.object({
+// title: Yup.string().required('Title is required'),
+// description: Yup.string().required('Description is required'),
+// initialDeposit: Yup.number()
+// .min(0, 'Deposit cannot be negative')
+// .required('Deposit is required'),
+// }).required();
+
+// const submitUpgradeProposalSchema = Yup.object({
+// title: Yup.string().required('Title is required'),
+// description: Yup.string().required('Description is required'),
+// initialDeposit: Yup.number()
+// .min(0, 'Initial deposit cannot be negative')
+// .required('Initial deposit is required'),
+// applyHeight: Yup.number()
+// .min(0, 'Apply height cannot be negative')
+// .required('Apply Height is required'),
+// planName: Yup.string().required('Plan name is required'),
+// plan: Yup.string()
+// .required('Plan is required')
+// .test('is-json', 'Upgrade plan must be valid JSON', (value: string) => {
+// try {
+// JSON.parse(value);
+// return true;
+// } catch (error) {
+// return false;
+// }
+// }),
+// }).required();
+
+// export function CreateTextProposalForm() {
+// const toast = useToast();
+// const {
+// register,
+// handleSubmit,
+// formState: { errors },
+// } = useForm({
+// resolver: yupResolver(submitTextProposalSchema),
+// defaultValues: {
+// title: 'Test proposal 32312323',
+// description:
+// 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Amet aliquam minima sit quaerat! Repudiandae maxime rerum provident, repellat ipsa nesciunt doloribus nam facilis iure modi unde, odio, illo officia voluptatem.',
+// initialDeposit: 1,
+// },
+// });
+// const chains = useSupportedChains();
+// const { chain = chains[0] } = useNetwork();
+// const { explorer } = getChainParams(
+// chain.unsupported !== undefined && !chain.unsupported
+// ? chain.id
+// : chains[0].id,
+// );
+// const { submitTextProposal } = useProposalActions();
+
+// const handleSubmitProposal = useCallback(
+// async (data: ICreateTextProposalForm) => {
+// try {
+// const submitProposalPromise = submitTextProposal(data);
+
+// await toast.promise(submitProposalPromise, {
+// loading: Proposal submit in progress ,
+// success: (tx) => {
+// console.log('Vote successful', { tx });
+// const txHash = tx?.txhash;
+
+// return (
+//
+//
+//
Your vote will be counted!!!
+//
+//
+//
+// {getFormattedAddress(txHash)}
+//
+//
+//
+//
+// );
+// },
+// error: (error) => {
+// console.error(error);
+// return For some reason your vote failed. ;
+// },
+// });
+// } catch (error) {
+// console.error((error as Error).message);
+// }
+// },
+// [explorer.cosmos, submitTextProposal, toast],
+// );
+
+// const onFormSubmit: SubmitHandler = (data) => {
+// handleSubmitProposal(data);
+// };
+
+// return (
+//
+//
+//
+//
+//
+// Text proposal
+//
+//
+
+//
+//
+//
+//
+// );
+// }
+
+// interface CreateUpgradeProposalForm {
+// title: string;
+// description: string;
+// initialDeposit: number;
+// applyHeight: number;
+// planName: string;
+// plan: string;
+// }
+// export function CreateUpgradeProposalForm() {
+// const toast = useToast();
+// const {
+// register,
+// handleSubmit,
+// formState: { errors },
+// } = useForm({
+// resolver: yupResolver(submitUpgradeProposalSchema),
+// });
+// const chains = useSupportedChains();
+// const { chain = chains[0] } = useNetwork();
+// const { explorer } = getChainParams(
+// chain.unsupported !== undefined && !chain.unsupported
+// ? chain.id
+// : chains[0].id,
+// );
+// const { submitSuProposal } = useProposalActions();
+
+// const handleSubmitProposal = useCallback(
+// async (data: CreateUpgradeProposalForm) => {
+// try {
+// const submitProposalPromise = submitSuProposal(data);
+
+// await toast.promise(submitProposalPromise, {
+// loading: Proposal submit in progress ,
+// success: (tx) => {
+// console.log('Vote successful', { tx });
+// const txHash = tx?.txhash;
+
+// return (
+//
+//
+//
Your vote will be counted!!!
+//
+//
+//
+// {getFormattedAddress(txHash)}
+//
+//
+//
+//
+// );
+// },
+// error: (error) => {
+// console.error(error);
+// return For some reason your vote failed. ;
+// },
+// });
+// } catch (error) {
+// console.error((error as Error).message);
+// }
+// },
+// [explorer.cosmos, submitSuProposal, toast],
+// );
+
+// const onFormSubmit: SubmitHandler = (data) => {
+// handleSubmitProposal(data);
+// };
+
+// return (
+//
+//
+//
+//
+//
+// Text proposal
+//
+//
+
+//
+//
+//
+//
+// );
+// }
diff --git a/apps/shell/src/components/submit-proposal-su-form-keplr.tsx b/apps/shell/src/components/submit-proposal-su-form-keplr.tsx
new file mode 100644
index 000000000..dae8bf73d
--- /dev/null
+++ b/apps/shell/src/components/submit-proposal-su-form-keplr.tsx
@@ -0,0 +1,548 @@
+'use client';
+import { ReactNode, useCallback, useState } from 'react';
+import { yupResolver } from '@hookform/resolvers/yup';
+import { Keplr } from '@keplr-wallet/types';
+import clsx from 'clsx';
+import Link from 'next/link';
+import { useForm, SubmitHandler } from 'react-hook-form';
+import { useNetwork } from 'wagmi';
+import * as Yup from 'yup';
+import { getChainParams } from '@haqq/data-access-cosmos';
+import {
+ ICreateUpgradeProposalForm,
+ getFormattedAddress,
+ getKeplrWallet,
+ haqqToEth,
+ useAddress,
+ useProposalActions,
+ useSupportedChains,
+ useToast,
+ useWallet,
+} from '@haqq/shell-shared';
+import {
+ Button,
+ Container,
+ Heading,
+ LinkIcon,
+ ToastError,
+ ToastLoading,
+ ToastSuccess,
+ Tooltip,
+} from '@haqq/shell-ui-kit';
+
+const inputClassnames = clsx(
+ 'inline-block w-full rounded-[6px] border border-white/20 bg-transparent px-[16px] py-[12px] text-[14px] leading-[22px] text-white placeholder-white/25 outline-none',
+ 'transition-colors duration-150 ease-in will-change-[color,background]',
+ 'focus:border-white/50 focus:bg-transparent focus:text-white',
+ 'hover:border-white/40',
+);
+
+const schema = Yup.object({
+ title: Yup.string().required('Title is required'),
+ description: Yup.string().required('Description is required'),
+ initialDeposit: Yup.number()
+ .min(0, 'Initial deposit cannot be negative')
+ .required('Initial deposit is required'),
+ applyHeight: Yup.number()
+ .min(0, 'Apply height cannot be negative')
+ .required('Apply Height is required'),
+ planName: Yup.string().required('Plan name is required'),
+ plan: Yup.string()
+ .required('Plan is required')
+ .test('is-json', 'Upgrade plan must be valid JSON', (value: string) => {
+ try {
+ JSON.parse(value);
+ return true;
+ } catch (error) {
+ return false;
+ }
+ }),
+}).required();
+
+async function enableChains(keplrWallet: Keplr) {
+ await keplrWallet.enable(['haqq_11235-1']);
+}
+
+export async function addHaqqMainnet(keplrWallet: Keplr) {
+ const basePrefix = 'haqq';
+ try {
+ await keplrWallet.experimentalSuggestChain({
+ features: ['ibc-transfer', 'ibc-go', 'eth-address-gen', 'eth-key-sign'],
+ chainId: 'haqq_11235-1',
+ chainName: 'HAQQ Mainnet',
+ rpc: 'https://rpc.tm.haqq.network',
+ rest: 'https://rest.cosmos.haqq.network',
+ bip44: {
+ coinType: 60,
+ },
+ bech32Config: {
+ bech32PrefixAccAddr: basePrefix,
+ bech32PrefixAccPub: `${basePrefix}pub`,
+ bech32PrefixValAddr: `${basePrefix}valoper`,
+ bech32PrefixValPub: `${basePrefix}valoperpub`,
+ bech32PrefixConsAddr: `${basePrefix}valcons`,
+ bech32PrefixConsPub: `${basePrefix}valconspub`,
+ },
+ currencies: [
+ {
+ // Coin denomination to be displayed to the user.
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ ],
+ feeCurrencies: [
+ {
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ ],
+ stakeCurrency: {
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ });
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+export async function addHaqqTestedge(keplrWallet: Keplr) {
+ const basePrefix = 'haqq';
+ try {
+ await keplrWallet.experimentalSuggestChain({
+ features: ['ibc-transfer', 'ibc-go', 'eth-address-gen', 'eth-key-sign'],
+ chainId: 'haqq_54211-3',
+ chainName: 'HAQQ Testedge 2',
+ rpc: 'https://rpc.tm.testedge2.haqq.network',
+ rest: 'https://rest.cosmos.testedge2.haqq.network',
+ bip44: {
+ coinType: 60,
+ },
+ bech32Config: {
+ bech32PrefixAccAddr: basePrefix,
+ bech32PrefixAccPub: `${basePrefix}pub`,
+ bech32PrefixValAddr: `${basePrefix}valoper`,
+ bech32PrefixValPub: `${basePrefix}valoperpub`,
+ bech32PrefixConsAddr: `${basePrefix}valcons`,
+ bech32PrefixConsPub: `${basePrefix}valconspub`,
+ },
+ currencies: [
+ {
+ // Coin denomination to be displayed to the user.
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ ],
+ feeCurrencies: [
+ {
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ ],
+ stakeCurrency: {
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ });
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+export function CreateSoftwareUpgradeProposalFormKeplr() {
+ const toast = useToast();
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
+ resolver: yupResolver(schema),
+ });
+ const chains = useSupportedChains();
+ const { chain = chains[0] } = useNetwork();
+ const { explorer } = getChainParams(
+ chain.unsupported !== undefined && !chain.unsupported
+ ? chain.id
+ : chains[0].id,
+ );
+ const { submitSoftwareUpgradeProposalKeplr } = useProposalActions();
+ const [isKeplrConnected, setIsKeplrConnected] = useState(false);
+ const [keplrAccount, setKeplrAccount] = useState(null);
+ const { ethAddress } = useAddress();
+ const hasMetamaskConnected = !!ethAddress;
+ const { disconnect } = useWallet();
+
+ const handleSubmitProposalKeplr = useCallback(
+ async (validatetFormData: ICreateUpgradeProposalForm) => {
+ const keplr = await getKeplrWallet();
+
+ if (keplr) {
+ try {
+ const submitProposalPromise = submitSoftwareUpgradeProposalKeplr(
+ validatetFormData,
+ keplr,
+ );
+
+ await toast.promise(submitProposalPromise, {
+ loading: Proposal submit in progress ,
+ success: (tx) => {
+ console.log('Submit proposal successful', { tx });
+ const txHash = tx?.txhash;
+
+ return (
+
+
+
Proposal submitted successfully
+
+
+
+ {getFormattedAddress(txHash)}
+
+
+
+
+ );
+ },
+ error: (error) => {
+ console.error(error);
+ return For some reason submit failed. ;
+ },
+ });
+ } catch (error) {
+ console.error((error as Error).message);
+ }
+ }
+ },
+ [explorer.cosmos, submitSoftwareUpgradeProposalKeplr, toast],
+ );
+
+ const onFormSubmitKeplr: SubmitHandler = (
+ data,
+ ) => {
+ handleSubmitProposalKeplr({
+ ...data,
+ plan: JSON.stringify(JSON.parse(data.plan)),
+ });
+ };
+
+ const handleConnectKeplrClick = useCallback(async () => {
+ const keplrWallet = await getKeplrWallet();
+
+ if (hasMetamaskConnected) {
+ disconnect();
+ }
+
+ if (keplrWallet) {
+ try {
+ await enableChains(keplrWallet);
+ } catch (e) {
+ await addHaqqMainnet(keplrWallet);
+ // await addHaqqTestedge(keplrWallet);
+ } finally {
+ await enableChains(keplrWallet);
+ }
+
+ const haqqAccount = await keplrWallet.getKey('haqq_11235-1');
+
+ setIsKeplrConnected(true);
+ setKeplrAccount(haqqAccount.bech32Address);
+ }
+ }, [disconnect, hasMetamaskConnected]);
+
+ const form = (
+
+
+
+
+
+ Proposal title
+
+
+
+
+
+
+
+
+
+
+
+
+ Description
+
+
+
+
+
+
+
+
+
+
+
+
+ Initial Deposit
+
+
+
+
+
+
+
+
+
+
+
+ Apply Height
+
+
+
+
+
+
+
+
+
+
+
+
+ Upgrade plan version
+
+
+
+
+
+
+
+
+
+
+
+ Upgrade plan info
+
+
+
+
+
+
+
+
+
+
+ Submit proposal
+
+
+
+
+ );
+
+ return (
+
+
+
+
+
+ Software upgrade
+
+
+
+
+ {keplrAccount && (
+
+
+
+ {keplrAccount}
+
+
+
+ {haqqToEth(keplrAccount)}
+
+
+ }
+ />
+
+ )}
+ {form}
+ >
+ }
+ content={
+
+
+ To submit a proposal, you need to connect your Keplr wallet.
+
+
+ If you have a Metamask wallet connected, please disconnect it
+ before connecting Keplr.
+
+
+
+ {isKeplrConnected && keplrAccount
+ ? `Connected to: ${getFormattedAddress(keplrAccount)}`
+ : 'Connect Keplr Wallet'}
+
+
+
+ }
+ />
+
+
+
+ );
+}
+
+export function FormInputError({ error }: { error?: string }) {
+ if (!error) {
+ return null;
+ }
+
+ return (
+
+ {error}
+
+ );
+}
+
+export function BlurredBlock({
+ isBlurred,
+ blurredContent,
+ content,
+}: {
+ blurredContent: ReactNode;
+ content: ReactNode;
+ isBlurred: boolean;
+}) {
+ return (
+
+
+
+ {blurredContent}
+
+
+ {isBlurred && (
+
+ )}
+
+
+ );
+}
+
+function MyAccountAmountBlock({
+ title,
+ value,
+ isGreen = false,
+ valueClassName,
+ className,
+}: {
+ title: string;
+ value: string | ReactNode;
+ isGreen?: boolean;
+ valueClassName?: string;
+ className?: string;
+}) {
+ return (
+
+
+ {title}
+
+
+ {value}
+
+
+ );
+}
diff --git a/apps/shell/src/components/submit-proposal-text-form-keplr.tsx b/apps/shell/src/components/submit-proposal-text-form-keplr.tsx
new file mode 100644
index 000000000..970e180dc
--- /dev/null
+++ b/apps/shell/src/components/submit-proposal-text-form-keplr.tsx
@@ -0,0 +1,463 @@
+'use client';
+import { ReactNode, useCallback, useState } from 'react';
+import { yupResolver } from '@hookform/resolvers/yup';
+import { Keplr } from '@keplr-wallet/types';
+import clsx from 'clsx';
+import Link from 'next/link';
+import { useForm, SubmitHandler } from 'react-hook-form';
+import { useNetwork } from 'wagmi';
+import * as Yup from 'yup';
+import { getChainParams } from '@haqq/data-access-cosmos';
+import {
+ ICreateTextProposalForm,
+ getFormattedAddress,
+ getKeplrWallet,
+ haqqToEth,
+ useAddress,
+ useProposalActions,
+ useSupportedChains,
+ useToast,
+ useWallet,
+} from '@haqq/shell-shared';
+import {
+ Button,
+ Container,
+ Heading,
+ LinkIcon,
+ ToastError,
+ ToastLoading,
+ ToastSuccess,
+ // Tooltip,
+} from '@haqq/shell-ui-kit';
+
+const inputClassnames = clsx(
+ 'inline-block w-full rounded-[6px] border border-white/20 bg-transparent px-[16px] py-[12px] text-[14px] leading-[22px] text-white placeholder-white/25 outline-none',
+ 'transition-colors duration-150 ease-in will-change-[color,background]',
+ 'focus:border-white/50 focus:bg-transparent focus:text-white',
+ 'hover:border-white/40',
+);
+
+const schema = Yup.object({
+ title: Yup.string().required('Title is required'),
+ description: Yup.string().required('Description is required'),
+ initialDeposit: Yup.number()
+ .min(0, 'Deposit cannot be negative')
+ .required('Deposit is required'),
+}).required();
+
+async function enableChains(keplrWallet: Keplr) {
+ await keplrWallet.enable(['haqq_11235-1']);
+}
+
+export async function addHaqqMainnet(keplrWallet: Keplr) {
+ const basePrefix = 'haqq';
+ try {
+ await keplrWallet.experimentalSuggestChain({
+ features: ['ibc-transfer', 'ibc-go', 'eth-address-gen', 'eth-key-sign'],
+ chainId: 'haqq_11235-1',
+ chainName: 'HAQQ Mainnet',
+ rpc: 'https://rpc.tm.haqq.network',
+ rest: 'https://rest.cosmos.haqq.network',
+ bip44: {
+ coinType: 60,
+ },
+ bech32Config: {
+ bech32PrefixAccAddr: basePrefix,
+ bech32PrefixAccPub: `${basePrefix}pub`,
+ bech32PrefixValAddr: `${basePrefix}valoper`,
+ bech32PrefixValPub: `${basePrefix}valoperpub`,
+ bech32PrefixConsAddr: `${basePrefix}valcons`,
+ bech32PrefixConsPub: `${basePrefix}valconspub`,
+ },
+ currencies: [
+ {
+ // Coin denomination to be displayed to the user.
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ ],
+ feeCurrencies: [
+ {
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ ],
+ stakeCurrency: {
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ });
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+export async function addHaqqTestedge(keplrWallet: Keplr) {
+ const basePrefix = 'haqq';
+ try {
+ await keplrWallet.experimentalSuggestChain({
+ features: ['ibc-transfer', 'ibc-go', 'eth-address-gen', 'eth-key-sign'],
+ chainId: 'haqq_54211-3',
+ chainName: 'HAQQ Testedge 2',
+ rpc: 'https://rpc.tm.testedge2.haqq.network',
+ rest: 'https://rest.cosmos.testedge2.haqq.network',
+ bip44: {
+ coinType: 60,
+ },
+ bech32Config: {
+ bech32PrefixAccAddr: basePrefix,
+ bech32PrefixAccPub: `${basePrefix}pub`,
+ bech32PrefixValAddr: `${basePrefix}valoper`,
+ bech32PrefixValPub: `${basePrefix}valoperpub`,
+ bech32PrefixConsAddr: `${basePrefix}valcons`,
+ bech32PrefixConsPub: `${basePrefix}valconspub`,
+ },
+ currencies: [
+ {
+ // Coin denomination to be displayed to the user.
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ ],
+ feeCurrencies: [
+ {
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ ],
+ stakeCurrency: {
+ coinDenom: 'ISLM',
+ coinMinimalDenom: 'aISLM',
+ coinDecimals: 18,
+ },
+ });
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+export function CreateTextProposalFormKeplr() {
+ const toast = useToast();
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({
+ resolver: yupResolver(schema),
+ });
+ const chains = useSupportedChains();
+ const { chain = chains[0] } = useNetwork();
+ const { explorer } = getChainParams(
+ chain.unsupported !== undefined && !chain.unsupported
+ ? chain.id
+ : chains[0].id,
+ );
+ const { submitTextProposalKeplr } = useProposalActions();
+ const [isKeplrConnected, setIsKeplrConnected] = useState(false);
+ const [keplrAccount, setKeplrAccount] = useState(null);
+ const { ethAddress } = useAddress();
+ const hasMetamaskConnected = !!ethAddress;
+ const { disconnect } = useWallet();
+
+ const handleSubmitProposalKeplr = useCallback(
+ async (validatetFormData: ICreateTextProposalForm) => {
+ const keplr = await getKeplrWallet();
+
+ if (keplr) {
+ try {
+ const submitProposalPromise = submitTextProposalKeplr(
+ validatetFormData,
+ keplr,
+ );
+
+ await toast.promise(submitProposalPromise, {
+ loading: Proposal submit in progress ,
+ success: (tx) => {
+ console.log('Submit proposal successful', { tx });
+ const txHash = tx?.txhash;
+
+ return (
+
+
+
Proposal submitted successfully
+
+
+
+ {getFormattedAddress(txHash)}
+
+
+
+
+ );
+ },
+ error: (error) => {
+ console.error(error);
+ return For some reason submit failed. ;
+ },
+ });
+ } catch (error) {
+ console.error((error as Error).message);
+ }
+ }
+ },
+ [explorer.cosmos, submitTextProposalKeplr, toast],
+ );
+
+ const onFormSubmitKeplr: SubmitHandler = (data) => {
+ handleSubmitProposalKeplr(data);
+ };
+
+ const handleConnectKeplrClick = useCallback(async () => {
+ const keplrWallet = await getKeplrWallet();
+
+ if (hasMetamaskConnected) {
+ disconnect();
+ }
+
+ if (keplrWallet) {
+ try {
+ await enableChains(keplrWallet);
+ } catch (e) {
+ await addHaqqMainnet(keplrWallet);
+ // await addHaqqTestedge(keplrWallet);
+ } finally {
+ await enableChains(keplrWallet);
+ }
+
+ const haqqAccount = await keplrWallet.getKey('haqq_11235-1');
+
+ setIsKeplrConnected(true);
+ setKeplrAccount(haqqAccount.bech32Address);
+ }
+ }, [disconnect, hasMetamaskConnected]);
+
+ const form = (
+
+
+
+
+
+ Proposal title
+
+
+
+
+
+
+ {errors.title &&
{errors.title.message}
}
+
+
+
+
+
+ Description
+
+
+
+
+
+ {errors.description &&
{errors.description.message}
}
+
+
+
+
+
+ Initial Deposit
+
+
+
+
+
+ {errors.initialDeposit &&
{errors.initialDeposit.message}
}
+
+
+
+
+ Submit proposal
+
+
+
+
+ );
+
+ return (
+
+
+
+
+
+ Text
+
+
+
+
+ {keplrAccount && (
+
+
+
+ {keplrAccount}
+
+
+
+ {haqqToEth(keplrAccount)}
+
+
+ }
+ />
+
+ )}
+ {form}
+ >
+ }
+ content={
+
+
+ To submit a proposal, you need to connect your Keplr wallet.
+
+
+ If you have a Metamask wallet connected, please disconnect it
+ before connecting Keplr.
+
+
+
+ {isKeplrConnected && keplrAccount
+ ? `Connected to: ${getFormattedAddress(keplrAccount)}`
+ : 'Connect Keplr Wallet'}
+
+
+
+ }
+ />
+
+
+
+ );
+}
+
+export function FormInputError({ error }: { error?: string }) {
+ if (!error) {
+ return null;
+ }
+
+ return (
+
+ {error}
+
+ );
+}
+
+export function BlurredBlock({
+ isBlurred,
+ blurredContent,
+ content,
+}: {
+ blurredContent: ReactNode;
+ content: ReactNode;
+ isBlurred: boolean;
+}) {
+ return (
+
+
+
+ {blurredContent}
+
+
+ {isBlurred && (
+
+ )}
+
+
+ );
+}
+
+function MyAccountAmountBlock({
+ title,
+ value,
+ isGreen = false,
+ valueClassName,
+ className,
+}: {
+ title: string;
+ value: string | ReactNode;
+ isGreen?: boolean;
+ valueClassName?: string;
+ className?: string;
+}) {
+ return (
+
+
+ {title}
+
+
+ {value}
+
+
+ );
+}
diff --git a/libs/data-access/cosmos/jest.config.ts b/libs/data-access/cosmos/jest.config.ts
index 9645b9e19..5714f9793 100644
--- a/libs/data-access/cosmos/jest.config.ts
+++ b/libs/data-access/cosmos/jest.config.ts
@@ -8,7 +8,7 @@ export default {
{ jsc: { transform: { react: { runtime: 'automatic' } } } },
],
},
- eFileExtensions: ['ts', 'js', 'html'],
+ moduleFileExtensions: ['ts', 'js', 'html'],
testEnvironment: 'node',
coverageDirectory: '../../../coverage/libs/data-access/cosmos',
};
diff --git a/libs/data-access/falconer/jest.config.ts b/libs/data-access/falconer/jest.config.ts
index 5b115f385..82ebf9162 100644
--- a/libs/data-access/falconer/jest.config.ts
+++ b/libs/data-access/falconer/jest.config.ts
@@ -8,7 +8,7 @@ export default {
{ jsc: { transform: { react: { runtime: 'automatic' } } } },
],
},
- eFileExtensions: ['ts', 'js', 'html'],
+ moduleFileExtensions: ['ts', 'js', 'html'],
testEnvironment: 'node',
coverageDirectory: '../../../coverage/libs/data-access/falconer',
};
diff --git a/libs/shell/shared/package.json b/libs/shell/shared/package.json
index 2629b3151..18caee6e1 100644
--- a/libs/shell/shared/package.json
+++ b/libs/shell/shared/package.json
@@ -1,5 +1,8 @@
{
"name": "@haqq/shell-shared",
"version": "0.0.1",
- "type": "module"
+ "type": "module",
+ "dependencies": {
+ "@haqq/data-access-cosmos": "^0.0.1"
+ }
}
diff --git a/libs/shell/shared/src/hooks/use-authz-actions/use-authz-actions.tsx b/libs/shell/shared/src/hooks/use-authz-actions/use-authz-actions.tsx
index 969310f57..e44394c57 100644
--- a/libs/shell/shared/src/hooks/use-authz-actions/use-authz-actions.tsx
+++ b/libs/shell/shared/src/hooks/use-authz-actions/use-authz-actions.tsx
@@ -93,7 +93,7 @@ export function useAuthzActions(): AuthzActionsHook {
memo,
grantParams,
);
-
+ console.log({ msg });
const rawTx = await signTransaction(msg, sender);
const txResponse = await trackBroadcastTx(
broadcastTransaction(rawTx),
@@ -156,7 +156,7 @@ export function useAuthzActions(): AuthzActionsHook {
memo,
revokeParams,
);
-
+ console.log({ msg });
const rawTx = await signTransaction(msg, sender);
const txResponse = await trackBroadcastTx(
broadcastTransaction(rawTx),
diff --git a/libs/shell/shared/src/hooks/use-proposal-actions/use-proposal-actions.ts b/libs/shell/shared/src/hooks/use-proposal-actions/use-proposal-actions.ts
index 496bbbcbb..709de8fc2 100644
--- a/libs/shell/shared/src/hooks/use-proposal-actions/use-proposal-actions.ts
+++ b/libs/shell/shared/src/hooks/use-proposal-actions/use-proposal-actions.ts
@@ -1,12 +1,45 @@
'use client';
import { useCallback } from 'react';
+import { Message } from '@bufbuild/protobuf';
+import {
+ generateFee,
+ generateTypes,
+ generateMessage,
+ createEIP712,
+ createMsgSubmitProposal,
+ MSG_SUBMIT_TYPES,
+} from '@evmos/eip712';
+import {
+ createAnyMessage,
+ // createMsgSubmitProposal as createMsgSubmitProposalCosmos,
+ createTxRaw,
+} from '@evmos/proto';
+import { createTransaction } from '@evmos/proto';
import { createMsgDeposit, createMsgVote } from '@evmos/proto';
+import * as coinProto from '@evmos/proto/dist/proto/cosmos/base/v1beta1/coin';
+import * as govProto from '@evmos/proto/dist/proto/cosmos/gov/v1beta1/gov';
+import * as govTxProto from '@evmos/proto/dist/proto/cosmos/gov/v1beta1/tx';
+import * as upgradeProto from '@evmos/proto/dist/proto/cosmos/upgrade/v1beta1/upgrade';
import { createTxMsgVote, createTxMsgDeposit } from '@evmos/transactions';
+import type {
+ Chain,
+ Fee,
+ MessageMsgSubmitProposal,
+ Sender,
+} from '@evmos/transactions';
import type { MessageMsgDepositParams } from '@evmos/transactions';
+import { Keplr } from '@keplr-wallet/types';
+// import { Any } from 'cosmjs-types/google/protobuf/any';
+import Long from 'long';
import { usePostHog } from 'posthog-js/react';
-import { formatUnits } from 'viem';
+import { formatUnits, toHex } from 'viem';
import { useNetwork } from 'wagmi';
-import { BroadcastTxResponse, getChainParams } from '@haqq/data-access-cosmos';
+import {
+ BroadcastTxResponse,
+ DEFAULT_FEE,
+ VESTING_DEFAULT_FEE,
+ getChainParams,
+} from '@haqq/data-access-cosmos';
import { mapToCosmosChain } from '@haqq/data-access-cosmos';
import { EstimatedFeeResponse } from '@haqq/data-access-falconer';
import { useCosmosService } from '../../providers/cosmos-provider';
@@ -16,6 +49,9 @@ import { getAmountIncludeFee } from '../../utils/get-amount-include-fee';
import { trackBroadcastTx } from '../../utils/track-broadcast-tx';
import { useAddress } from '../use-address/use-address';
+const { TextProposal } = govProto.cosmos.gov.v1beta1;
+const { Plan, SoftwareUpgradeProposal } = upgradeProto.cosmos.upgrade.v1beta1;
+
interface ProposalActionsHook {
vote: (
proposalId: number,
@@ -36,6 +72,308 @@ interface ProposalActionsHook {
proposalId: number,
amount: number,
) => Promise;
+ // submitTextProposal: (
+ // proposalParams: CreateTextProposalForm,
+ // estimatedFee?: EstimatedFeeResponse,
+ // ) => Promise;
+ // submitSuProposal: (
+ // proposalParams: CreateTextProposalForm,
+ // estimatedFee?: EstimatedFeeResponse,
+ // ) => Promise;
+ submitTextProposalKeplr: (
+ proposalParams: ICreateTextProposalForm,
+ keplr: Keplr,
+ ) => Promise;
+ submitSoftwareUpgradeProposalKeplr: (
+ proposalParams: ICreateUpgradeProposalForm,
+ keplr: Keplr,
+ ) => Promise;
+}
+
+export interface ICreateTextProposalForm {
+ title: string;
+ description: string;
+ initialDeposit: number;
+}
+
+export interface ICreateUpgradeProposalForm {
+ title: string;
+ description: string;
+ initialDeposit: number;
+ applyHeight: number;
+ planName: string;
+ plan: string;
+}
+
+export const MSG_SUBMIT_TEXT_PROPOSAL_TYPES = {
+ MsgValue: [
+ { name: 'content', type: 'TextProposal' },
+ { name: 'initial_deposit', type: 'Coin[]' },
+ { name: 'proposer', type: 'string' },
+ ],
+ TextProposal: [
+ {
+ name: 'type',
+ type: 'string',
+ },
+ {
+ name: 'value',
+ type: 'TextProposalContent',
+ },
+ ],
+ TextProposalContent: [
+ {
+ name: 'path',
+ type: 'string',
+ },
+ {
+ name: 'message',
+ type: 'string',
+ },
+ ],
+};
+
+export const MSG_SUBMIT_UPGRADE_PROPOSAL_TYPES = {
+ MsgValue: [
+ { name: 'content', type: 'UpgradeProposal' },
+ { name: 'initial_deposit', type: 'Coin[]' },
+ { name: 'proposer', type: 'string' },
+ ],
+ UpgradeProposal: [
+ {
+ name: 'type',
+ type: 'string',
+ },
+ {
+ name: 'value',
+ type: 'UpgradeProposalContent',
+ },
+ ],
+ UpgradeProposalContent: [
+ { name: '@type', type: 'string' },
+ { name: 'title', type: 'string' },
+ { name: 'description', type: 'string' },
+ { name: 'plan', type: 'Plan' },
+ ],
+ Plan: [
+ { name: 'name', type: 'string' },
+ { name: 'time', type: 'string' },
+ { name: 'height', type: 'string' },
+ { name: 'info', type: 'string' },
+ ],
+};
+
+// function createMsgSubmitProposalTxtEIP712(
+// content: any,
+// initialDepositDenom: string,
+// initialDepositAmount: string,
+// proposer: string,
+// ) {
+// console.log('createMsgSubmitProposalEIP712', {
+// content,
+// initialDepositDenom,
+// initialDepositAmount,
+// proposer,
+// });
+// return {
+// type: 'cosmos-sdk/MsgSubmitProposal',
+// value: {
+// content: {
+// type: 'cosmos-sdk/TextProposal',
+// value: {
+// '@type': 'cosmos.gov.v1beta1.TextProposal',
+// ...content.toObject(),
+// },
+// },
+// initial_deposit: [
+// {
+// amount: initialDepositAmount,
+// denom: initialDepositDenom,
+// },
+// ],
+// proposer,
+// },
+// };
+// }
+
+// function createMsgSubmitProposalSuEIP712(
+// content: any,
+// initialDepositDenom: string,
+// initialDepositAmount: string,
+// proposer: string,
+// ) {
+// console.log('createMsgSubmitProposalEIP712', {
+// content,
+// initialDepositDenom,
+// initialDepositAmount,
+// proposer,
+// });
+// return {
+// type: 'cosmos-sdk/MsgSubmitProposal',
+// value: {
+// content: {
+// type: 'cosmos-sdk/TextProposal',
+// value: {
+// '@type': 'cosmos.gov.v1beta1.TextProposal',
+// ...content.toObject(),
+// },
+// },
+// initial_deposit: [
+// {
+// amount: initialDepositAmount,
+// denom: initialDepositDenom,
+// },
+// ],
+// proposer,
+// },
+// };
+// }
+
+// function createTxMsgSubmitSuProposal(
+// chain: Chain,
+// sender: Sender,
+// fee: Fee,
+// memo: string,
+// params: MessageMsgSubmitProposal,
+// contentPath: string,
+// ) {
+// const feeObject = generateFee(
+// fee.amount,
+// fee.denom,
+// fee.gas,
+// sender.accountAddress,
+// );
+// const types = generateTypes(MSG_SUBMIT_UPGRADE_PROPOSAL_TYPES);
+// const msg = createMsgSubmitProposalSuEIP712(
+// params.content,
+// params.initialDepositDenom,
+// params.initialDepositAmount,
+// sender.accountAddress,
+// );
+// console.log({ msg });
+// const messages = generateMessage(
+// sender.accountNumber.toString(),
+// sender.sequence.toString(),
+// chain.cosmosChainId,
+// memo,
+// feeObject,
+// msg,
+// );
+// console.log({ messages });
+// const eipToSign = createEIP712(types, chain.chainId, messages);
+// console.log({ eipToSign });
+// const msgCosmos = createMsgSubmitProposalCosmos(
+// params.content,
+// contentPath,
+// params.initialDepositDenom,
+// params.initialDepositAmount,
+// sender.accountAddress,
+// );
+// console.log({ msgCosmos });
+// const tx = createTransaction(
+// msgCosmos,
+// memo,
+// fee.amount,
+// fee.denom,
+// parseInt(fee.gas, 10),
+// 'ethsecp256',
+// sender.pubkey,
+// sender.sequence,
+// sender.accountNumber,
+// chain.cosmosChainId,
+// );
+// return {
+// signDirect: tx.signDirect,
+// legacyAmino: tx.legacyAmino,
+// eipToSign,
+// };
+// }
+
+// function createTxMsgSubmitTxtProposal(
+// chain: Chain,
+// sender: Sender,
+// fee: Fee,
+// memo: string,
+// params: MessageMsgSubmitProposal,
+// contentPath: string,
+// ) {
+// const feeObject = generateFee(
+// fee.amount,
+// fee.denom,
+// fee.gas,
+// sender.accountAddress,
+// );
+// const types = generateTypes(MSG_SUBMIT_TEXT_PROPOSAL_TYPES);
+// const msg = createMsgSubmitProposalTxtEIP712(
+// params.content,
+// params.initialDepositDenom,
+// params.initialDepositAmount,
+// sender.accountAddress,
+// );
+// console.log({ msg });
+// const messages = generateMessage(
+// sender.accountNumber.toString(),
+// sender.sequence.toString(),
+// chain.cosmosChainId,
+// memo,
+// feeObject,
+// msg,
+// );
+// console.log({ messages });
+// const eipToSign = createEIP712(types, chain.chainId, messages);
+// console.log({ eipToSign });
+// const msgCosmos = createMsgSubmitProposalCosmos(
+// params.content,
+// contentPath,
+// params.initialDepositDenom,
+// params.initialDepositAmount,
+// sender.accountAddress,
+// );
+// console.log({ msgCosmos });
+// const tx = createTransaction(
+// msgCosmos,
+// memo,
+// fee.amount,
+// fee.denom,
+// parseInt(fee.gas, 10),
+// 'ethsecp256',
+// sender.pubkey,
+// sender.sequence,
+// sender.accountNumber,
+// chain.cosmosChainId,
+// );
+// return {
+// signDirect: tx.signDirect,
+// legacyAmino: tx.legacyAmino,
+// eipToSign,
+// };
+// }
+
+function createMsgSubmitProposalCosmos(
+ content: Message,
+ contentPath: string,
+ initialDepositDenom: any,
+ initialDepositAmount: any,
+ proposer: any,
+) {
+ const initialDeposit = new coinProto.cosmos.base.v1beta1.Coin({
+ denom: initialDepositDenom,
+ amount: initialDepositAmount,
+ });
+
+ const msg = new govTxProto.cosmos.gov.v1beta1.MsgSubmitProposal({
+ content: createAnyMessage({
+ path: contentPath,
+ message: content,
+ }),
+ initial_deposit: [initialDeposit],
+ proposer,
+ });
+
+ return {
+ message: msg,
+ path: 'cosmos.gov.v1beta1.MsgSubmitProposal',
+ };
}
export function useProposalActions(): ProposalActionsHook {
@@ -56,6 +394,7 @@ export function useProposalActions(): ProposalActionsHook {
: chains[0].id,
);
const haqqChain = mapToCosmosChain(chainParams);
+ const { getAccountBaseInfo } = useCosmosService();
const posthog = usePostHog();
const chainId = chain.id;
@@ -225,10 +564,368 @@ export function useProposalActions(): ProposalActionsHook {
],
);
+ // const handleSubmitTextProposal = useCallback(
+ // async (
+ // submitProposalData: CreateTextProposalForm,
+ // estimatedFee?: EstimatedFeeResponse,
+ // ) => {
+ // console.log('handleSubmitProposal', { submitProposalData, estimatedFee });
+ // const pubkey = await getPubkey(ethAddress as string);
+ // const sender = await getSender(haqqAddress as string, pubkey);
+ // const memo = `Submit proposal ${submitProposalData.title}`;
+
+ // if (sender && haqqChain) {
+ // const fee = getFee(estimatedFee);
+ // const proposalParams: MessageMsgSubmitProposal = {
+ // content: TextProposal.fromObject({
+ // title: submitProposalData.title,
+ // description: submitProposalData.description,
+ // }),
+ // proposer: sender.accountAddress,
+ // initialDepositAmount: BigInt(
+ // submitProposalData.initialDeposit * 10 ** 18,
+ // ).toString(),
+ // initialDepositDenom: 'aISLM',
+ // };
+
+ // console.log({ proposalParams });
+
+ // const msg = createTxMsgSubmitTxtProposal(
+ // haqqChain,
+ // sender,
+ // fee,
+ // memo,
+ // proposalParams,
+ // 'cosmos.gov.v1beta1.TextProposal',
+ // );
+ // console.log({ msg });
+ // const rawTx = await signTransaction(msg, sender);
+ // console.log({ rawTx });
+ // const txResponse = await broadcastTransaction(rawTx);
+
+ // if (txResponse.code !== 0) {
+ // throw new Error(txResponse.raw_log);
+ // }
+
+ // const transactionStatus = await getTransactionStatus(txResponse.txhash);
+
+ // if (transactionStatus === null) {
+ // throw new Error('Transaction not found');
+ // }
+
+ // return transactionStatus.tx_response;
+ // } else {
+ // throw new Error('No sender');
+ // }
+ // },
+ // [
+ // getPubkey,
+ // ethAddress,
+ // getSender,
+ // haqqAddress,
+ // haqqChain,
+ // getFee,
+ // signTransaction,
+ // broadcastTransaction,
+ // getTransactionStatus,
+ // ],
+ // );
+
+ // const handleSubmitSuProposal = useCallback(
+ // async (
+ // submitProposalData: CreateTextProposalForm,
+ // estimatedFee?: EstimatedFeeResponse,
+ // ) => {
+ // console.log('handleSubmitProposal', { submitProposalData, estimatedFee });
+ // const pubkey = await getPubkey(ethAddress as string);
+ // const sender = await getSender(haqqAddress as string, pubkey);
+ // const memo = `Submit proposal ${submitProposalData.title}`;
+
+ // if (sender && haqqChain) {
+ // const fee = getFee(estimatedFee);
+ // const proposalParams: MessageMsgSubmitProposal = {
+ // content: TextProposal.fromObject({
+ // title: submitProposalData.title,
+ // description: submitProposalData.description,
+ // }),
+ // proposer: sender.accountAddress,
+ // initialDepositAmount: BigInt(
+ // submitProposalData.initialDeposit * 10 ** 18,
+ // ).toString(),
+ // initialDepositDenom: 'aISLM',
+ // };
+
+ // console.log({ proposalParams });
+
+ // const msg = createTxMsgSubmitSuProposal(
+ // haqqChain,
+ // sender,
+ // fee,
+ // memo,
+ // proposalParams,
+ // 'cosmos.gov.v1beta1.SoftwareUpgradeProposal',
+ // );
+ // console.log({ msg });
+ // const rawTx = await signTransaction(msg, sender);
+ // console.log({ rawTx });
+ // const txResponse = await broadcastTransaction(rawTx);
+
+ // if (txResponse.code !== 0) {
+ // throw new Error(txResponse.raw_log);
+ // }
+
+ // const transactionStatus = await getTransactionStatus(txResponse.txhash);
+
+ // if (transactionStatus === null) {
+ // throw new Error('Transaction not found');
+ // }
+
+ // return transactionStatus.tx_response;
+ // } else {
+ // throw new Error('No sender');
+ // }
+ // },
+ // [
+ // getPubkey,
+ // ethAddress,
+ // getSender,
+ // haqqAddress,
+ // haqqChain,
+ // getFee,
+ // signTransaction,
+ // broadcastTransaction,
+ // getTransactionStatus,
+ // ],
+ // );
+
+ const handleSubmitTextProposalKeplr = useCallback(
+ async (submitProposalData: ICreateTextProposalForm, keplr: Keplr) => {
+ console.log('handleSubmitTextProposalKeplr', {
+ submitProposalData,
+ keplr,
+ });
+ const memo = `Submit proposal "${submitProposalData.title}"`;
+
+ if (haqqChain) {
+ const offlineSigner = keplr.getOfflineSigner(haqqChain.cosmosChainId);
+ const accounts = await offlineSigner.getAccounts();
+ const currentAccount = accounts[0];
+ const pubkeyFromKeplr = Buffer.from(currentAccount.pubkey).toString(
+ 'base64',
+ );
+ const fromChain = await getAccountBaseInfo(currentAccount.address);
+ const sender: Sender = {
+ accountAddress: currentAccount.address,
+ pubkey: pubkeyFromKeplr,
+ sequence: Number(fromChain?.sequence),
+ accountNumber: Number(fromChain?.account_number),
+ };
+
+ const proposalParams: MessageMsgSubmitProposal = {
+ content: TextProposal.fromObject({
+ title: submitProposalData.title,
+ description: submitProposalData.description,
+ }),
+ proposer: sender.accountAddress,
+ initialDepositAmount: BigInt(
+ submitProposalData.initialDeposit * 10 ** 18,
+ ).toString(),
+ initialDepositDenom: 'aISLM',
+ };
+ const msgCosmos = createMsgSubmitProposalCosmos(
+ proposalParams.content,
+ 'cosmos.gov.v1beta1.TextProposal',
+ proposalParams.initialDepositDenom,
+ proposalParams.initialDepositAmount,
+ sender.accountAddress,
+ );
+ console.log({ msgCosmos });
+ const tx = createTransaction(
+ msgCosmos,
+ memo,
+ VESTING_DEFAULT_FEE.amount,
+ VESTING_DEFAULT_FEE.denom,
+ parseInt(VESTING_DEFAULT_FEE.gas, 10),
+ 'ethsecp256',
+ sender.pubkey,
+ sender.sequence,
+ sender.accountNumber,
+ haqqChain.cosmosChainId,
+ );
+ console.log({ tx });
+
+ const signResponse = await offlineSigner.signDirect(
+ sender.accountAddress,
+ {
+ bodyBytes: tx.signDirect.body.serializeBinary(),
+ authInfoBytes: tx.signDirect.authInfo.serializeBinary(),
+ chainId: haqqChain.cosmosChainId,
+ accountNumber: new Long(sender.accountNumber),
+ },
+ );
+
+ if (!signResponse) {
+ // Handle signature failure here.
+ throw new Error('Transaction signature failed');
+ }
+
+ const { signed, signature } = signResponse;
+
+ const signatures = [
+ new Uint8Array(Buffer.from(signature.signature, 'base64')),
+ ];
+
+ const signedTx = createTxRaw(
+ signed.bodyBytes,
+ signed.authInfoBytes,
+ signatures,
+ );
+
+ const txResponse = await broadcastTransaction(signedTx);
+
+ if (txResponse.code !== 0) {
+ throw new Error(txResponse.raw_log);
+ }
+
+ const transactionStatus = await getTransactionStatus(txResponse.txhash);
+
+ if (transactionStatus === null) {
+ throw new Error('Transaction not found');
+ }
+
+ return transactionStatus.tx_response;
+ } else {
+ throw new Error('No sender');
+ }
+ },
+ [haqqChain, getAccountBaseInfo, broadcastTransaction, getTransactionStatus],
+ );
+
+ const handleSubmitProposalSoftwareUpgradeKeplr = useCallback(
+ async (submitProposalData: ICreateUpgradeProposalForm, keplr: Keplr) => {
+ console.log('handleSubmitProposalSoftwareUpgradeKeplr', {
+ submitProposalData,
+ keplr,
+ });
+ const memo = `Submit proposal software upgrade proposal: "${submitProposalData.title}"`;
+
+ if (haqqChain) {
+ const offlineSigner = keplr.getOfflineSigner(haqqChain.cosmosChainId);
+ const accounts = await offlineSigner.getAccounts();
+ const currentAccount = accounts[0];
+ const pubkeyFromKeplr = Buffer.from(currentAccount.pubkey).toString(
+ 'base64',
+ );
+ const fromChain = await getAccountBaseInfo(currentAccount.address);
+ const sender: Sender = {
+ accountAddress: currentAccount.address,
+ pubkey: pubkeyFromKeplr,
+ sequence: Number(fromChain?.sequence),
+ accountNumber: Number(fromChain?.account_number),
+ };
+
+ const proposalParams: MessageMsgSubmitProposal = {
+ content: SoftwareUpgradeProposal.fromObject({
+ title: submitProposalData.title,
+ description: submitProposalData.description,
+ plan: Plan.fromObject({
+ name: submitProposalData.planName,
+ height: submitProposalData.applyHeight,
+ info: submitProposalData.plan,
+ }),
+ }),
+ proposer: sender.accountAddress,
+ initialDepositAmount: BigInt(
+ submitProposalData.initialDeposit * 10 ** 18,
+ ).toString(),
+ initialDepositDenom: 'aISLM',
+ };
+ const msgCosmos = createMsgSubmitProposalCosmos(
+ proposalParams.content,
+ 'cosmos.upgrade.v1beta1.SoftwareUpgradeProposal',
+ proposalParams.initialDepositDenom,
+ proposalParams.initialDepositAmount,
+ sender.accountAddress,
+ );
+
+ const tx = createTransaction(
+ msgCosmos,
+ memo,
+ VESTING_DEFAULT_FEE.amount,
+ VESTING_DEFAULT_FEE.denom,
+ parseInt(VESTING_DEFAULT_FEE.gas, 10),
+ 'ethsecp256',
+ sender.pubkey,
+ sender.sequence,
+ sender.accountNumber,
+ haqqChain.cosmosChainId,
+ );
+
+ const signResponse = await offlineSigner.signDirect(
+ sender.accountAddress,
+ {
+ bodyBytes: tx.signDirect.body.serializeBinary(),
+ authInfoBytes: tx.signDirect.authInfo.serializeBinary(),
+ chainId: haqqChain.cosmosChainId,
+ accountNumber: new Long(sender.accountNumber),
+ },
+ );
+
+ if (!signResponse) {
+ // Handle signature failure here.
+ throw new Error('Transaction signature failed');
+ }
+
+ const { signed, signature } = signResponse;
+
+ const signatures = [
+ new Uint8Array(Buffer.from(signature.signature, 'base64')),
+ ];
+
+ const signedTx = createTxRaw(
+ signed.bodyBytes,
+ signed.authInfoBytes,
+ signatures,
+ );
+
+ const txResponse = await broadcastTransaction(signedTx);
+ // const txResponse = await keplr.sendTx(
+ // 'haqq_11235-1',
+ // signedTx.message.serializeBinary(),
+ // BroadcastMode.Sync,
+ // );
+ // console.log({
+ // txResponse,
+ // b: Buffer.from(txResponse).toString('base64'),
+ // });
+
+ if (txResponse.code !== 0) {
+ throw new Error(txResponse.raw_log);
+ }
+
+ const transactionStatus = await getTransactionStatus(txResponse.txhash);
+
+ if (transactionStatus === null) {
+ throw new Error('Transaction not found');
+ }
+
+ return transactionStatus.tx_response;
+ } else {
+ throw new Error('No sender');
+ }
+ },
+ [haqqChain, getAccountBaseInfo, broadcastTransaction, getTransactionStatus],
+ );
+
return {
vote: handleVote,
deposit: handleDeposit,
getVoteEstimatedFee: handleVoteEstimatedFee,
getDepositEstimatedFee: handleDepositEstimatedFee,
+ // submitTextProposal: handleSubmitTextProposal,
+ // submitSuProposal: handleSubmitSuProposal,
+ submitTextProposalKeplr: handleSubmitTextProposalKeplr,
+ submitSoftwareUpgradeProposalKeplr:
+ handleSubmitProposalSoftwareUpgradeKeplr,
};
}
diff --git a/libs/shell/shared/src/providers/wagmi-provider.tsx b/libs/shell/shared/src/providers/wagmi-provider.tsx
index 05a81b8a6..06995c8e7 100644
--- a/libs/shell/shared/src/providers/wagmi-provider.tsx
+++ b/libs/shell/shared/src/providers/wagmi-provider.tsx
@@ -11,6 +11,31 @@ export type Chain = WagmiChain & {
unsupported?: boolean | undefined;
};
+const haqqMainFork = {
+ id: 11235,
+ name: 'HAQQ Mainnet fork',
+ network: 'haqq-mainnet-fork',
+ nativeCurrency: {
+ decimals: 18,
+ name: 'Islamic Coin',
+ symbol: 'ISLM',
+ },
+ rpcUrls: {
+ default: {
+ http: ['http://95.179.165.238:8545'],
+ },
+ public: {
+ http: ['http://95.179.165.238:8545'],
+ },
+ },
+ blockExplorers: {
+ default: {
+ name: 'HAQQ Explorer',
+ url: 'https://explorer.testedge2.haqq.network',
+ },
+ },
+};
+
export const haqqLocalnet: Chain = {
id: 121799,
name: 'HAQQ Localnet',
@@ -33,7 +58,7 @@ export const haqqLocalnet: Chain = {
};
const SupportedChainsContext = createContext([
- haqqMainnet,
+ haqqMainFork,
haqqTestedge2,
]);
@@ -53,7 +78,7 @@ export function WagmiProvider({
children,
walletConnectProjectId,
isProduction = true,
- supportedChains = [haqqMainnet, haqqTestedge2],
+ supportedChains = [haqqMainFork, haqqTestedge2],
}: PropsWithChildren<{
walletConnectProjectId?: string;
isProduction?: boolean;
diff --git a/libs/shell/shared/src/providers/wallet-provider.tsx b/libs/shell/shared/src/providers/wallet-provider.tsx
index 3d19ab0bd..069ad4ede 100644
--- a/libs/shell/shared/src/providers/wallet-provider.tsx
+++ b/libs/shell/shared/src/providers/wallet-provider.tsx
@@ -277,18 +277,21 @@ export function WalletProvider({ children }: { children: ReactNode }) {
const ethAddress = haqqToEth(sender.accountAddress);
const signature = await walletClient.request({
method: 'eth_signTypedData_v4',
- params: [ethAddress as `0x${string}`, JSON.stringify(msg.eipToSign)],
+ params: [ethAddress as Hex, JSON.stringify(msg.eipToSign)],
});
+ console.log({ signature });
const extension = signatureToWeb3Extension(
haqqChain,
sender,
signature,
);
+ console.log({ extension });
const rawTx = createTxRawEIP712(
msg.legacyAmino.body,
msg.legacyAmino.authInfo,
extension,
);
+ console.log({ rawTx });
posthog.capture('sign tx success', {
chaidId: haqqChain.chainId,
diff --git a/libs/shell/ui-kit/src/index.ts b/libs/shell/ui-kit/src/index.ts
index 95fbcd88c..ee42fbb77 100644
--- a/libs/shell/ui-kit/src/index.ts
+++ b/libs/shell/ui-kit/src/index.ts
@@ -47,7 +47,6 @@ export * from './components/footer/footer';
export * from './components/select-chain-modal/select-chain-modal';
export * from './components/search-input/search-input';
export * from './components/sort-select/sort-select';
-export * from './components/search-input/search-input';
export * from './utils/format-date';
export * from './components/top-validators-warning-modal/top-validators-warning-modal';
export * from './utils/format-percent';