Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #4

Open
wants to merge 111 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
ebeb6a7
Initial setup
saajand Jul 1, 2024
de1dfcb
Add Cosmos wallet kit and handle Wallet connections
saajand Jul 4, 2024
6c33044
Add Rainbowkit and Wagmi packages for EVM wallet connections
saajand Jul 10, 2024
ac82b25
Add Injective contract call and Update Cosmos configs
saajand Jul 11, 2024
7e8fe1d
Update function name
saajand Jul 11, 2024
19d0ae4
Add Archway contract call and Update Cosmos configs
saajand Jul 14, 2024
a7356ec
Evm integration for multiple chains
smrkarkii Jul 17, 2024
5fab1ca
Proposals shown in table and can be approved
smrkarkii Jul 17, 2024
30b5f9b
Add a custom hook that can be used to make transactions for archway a…
saajand Jul 17, 2024
c0385ff
Add App Context file; Manage acitve cosmos chain details; Handle Cosm…
saajand Jul 17, 2024
32e50ab
Type updates
saajand Jul 17, 2024
2347878
Inital State updates
saajand Jul 17, 2024
4d2f562
Handle different function call for different chain
saajand Jul 17, 2024
01165e1
Update txn hash on Approval success
saajand Jul 17, 2024
c4d5abe
Add UI Stylings
saajand Jul 17, 2024
edca7b9
Add DaisyUI for UI pre-defined components
saajand Jul 18, 2024
4804df6
Add General Modal component
saajand Jul 18, 2024
c3af01f
Add custom hook to fetch Cosmos Contract details
saajand Jul 18, 2024
2f4ff27
Add Table to display cosmos Proposals list
saajand Jul 18, 2024
167695d
Add Proposal Execute function
saajand Jul 18, 2024
e3453d0
Update Typo
saajand Jul 18, 2024
723af1b
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
saajand Jul 18, 2024
85d19f8
Merge pull request #1 from icon-project/dev_cosmos-wallet-kit
saajand Jul 18, 2024
29ce663
fetching data from firebase
smrkarkii Jul 18, 2024
af28108
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
saajand Jul 18, 2024
9bed50a
final corrected version and adding and displaying the proposals
smrkarkii Jul 19, 2024
700edc6
Merge branch 'evm-integration' of github.com:icon-project/Multisig-Fr…
saajand Jul 19, 2024
e3dcc91
Frontend of Ethereum proposals table
smrkarkii Jul 19, 2024
6da164f
all errors in git build resolved
smrkarkii Jul 19, 2024
cc82ade
Update Cosmos transactions error returns
saajand Jul 21, 2024
8d982b0
Add Toast component as custom hook
saajand Jul 21, 2024
bf9cdd6
Use Toast in Comos page
saajand Jul 21, 2024
aed3e58
Fix Toast color issue
saajand Jul 21, 2024
6025e93
Move Action buttons to Cosmos Proposals table
saajand Jul 21, 2024
eabeb51
Config file updated
smrkarkii Jul 22, 2024
6f11db5
Update contract data Fetching function to use random available rpc ur…
saajand Jul 22, 2024
23d9ea8
Update Cosmos chains config
saajand Jul 22, 2024
c886373
Update Contract calls to use rpc urls from config
saajand Jul 22, 2024
085af31
Use Archway functions for archway contract calls
saajand Jul 22, 2024
dc78fcf
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
saajand Jul 22, 2024
2dd0ec2
Merge pull request #2 from icon-project/merge/evm-integration
saajand Jul 22, 2024
255a25b
Update layouts and UIs
saajand Jul 22, 2024
af7a09a
Update Cosmos Proposals table and data fetching flow
saajand Jul 22, 2024
f0f359f
Add Cosmos Create proposal page
saajand Jul 22, 2024
e109861
Merge pull request #3 from icon-project/develop_new-layout
saajand Jul 22, 2024
75238c3
Member add and remove completed
smrkarkii Jul 23, 2024
9334ce2
resolve merge issues
smrkarkii Jul 23, 2024
58fd769
Updated EVM Approve, Execute,Create page and all functionalites are w…
smrkarkii Jul 23, 2024
4d36abf
Add Member Update and Contract Upgrade contract calls for Archway net…
saajand Jul 23, 2024
47c50ce
Merge pull request #5 from icon-project/develop_new-layout
saajand Jul 23, 2024
6590804
Update Cosmos Proposal table to handle paginated data
saajand Jul 24, 2024
c5ee51e
Add injective functions to handle Propoals creation
saajand Jul 24, 2024
9d43e53
Merge pull request #7 from icon-project/develop_cosmos-proposals-updates
saajand Jul 24, 2024
32e56df
UI for details page and displayng information in details page
smrkarkii Jul 24, 2024
48a69c1
details page done
smrkarkii Jul 24, 2024
eddfe1e
updated for build
smrkarkii Jul 24, 2024
a3b63be
Add Multiple pages for listing cosmos proposals based on status
saajand Jul 24, 2024
da4a206
Merge pull request #8 from icon-project/develop_cosmos-proposals-updates
saajand Jul 24, 2024
42646cf
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
saajand Jul 24, 2024
70ab21d
UI updates for input field
smrkarkii Jul 25, 2024
2e2145d
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
smrkarkii Jul 25, 2024
90df3b5
error handling done and use of toast
smrkarkii Jul 25, 2024
085e283
upto testing
smrkarkii Jul 25, 2024
f8fe6f2
New page for details done
smrkarkii Jul 26, 2024
059486a
Showing current threshold and owners in create page
smrkarkii Jul 26, 2024
0196d00
prpoposals filtered by chain
smrkarkii Jul 29, 2024
c5376c5
Updates in Details page of proposals
smrkarkii Jul 29, 2024
59b3320
Merge branch 'merged-dev-evm' of github.com:icon-project/Multisig-Fro…
saajand Jul 29, 2024
0370093
Update comos contract data fetching function to handle cases when wal…
saajand Jul 29, 2024
57c4ede
Merge pull request #6 from icon-project/merged-dev-evm
saajand Jul 29, 2024
620129a
Working on UI optimization
smrkarkii Jul 30, 2024
c42bff8
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
smrkarkii Jul 30, 2024
c1f6d70
Modal used to show current threshold and owners
smrkarkii Jul 30, 2024
4fa6ab6
Cosmos details page
smrkarkii Jul 30, 2024
baac5be
TablePage created and fixed issues
smrkarkii Aug 2, 2024
631ee15
Update Cosmos Proposals Table columns and page layouts
saajand Aug 6, 2024
30a6da6
Update Cosmos and EVM proposals details page layouts and stylings
saajand Aug 6, 2024
9766d75
Handle different types of Cosmos Proposal messages in Cosmos details …
saajand Aug 6, 2024
588a199
Update Cosmos Proposal details getter function
saajand Aug 6, 2024
a7a17a4
Update yarn.lock file
saajand Aug 6, 2024
55641a5
Improvements to Sidenav
saajand Aug 6, 2024
40b12a3
Merge pull request #9 from icon-project/merged-dev-evm
saajand Aug 6, 2024
d877d44
Update env variables example
saajand Aug 13, 2024
124e14c
added support for running app with docker
FidelVe Aug 14, 2024
e8ebaf5
fix: code id visible
Itshyphen Aug 15, 2024
1d58ecb
fix: message type added in details
Itshyphen Aug 15, 2024
171a632
fixed .env file not being properly read by docker
FidelVe Aug 16, 2024
266adfc
fixed issue: having CORS error when deploying from optimized build in…
FidelVe Aug 16, 2024
e2666d8
Update Neutron approval function to use string in proposal_id
saajand Aug 16, 2024
77cbd4a
Add fetching status to Cosmos Proposals lists
saajand Aug 20, 2024
c60c0cf
Add warning to add missing chains in keplr
saajand Aug 21, 2024
497e6ed
Update Cosmos proposal details page routes adding chain name; Add UI …
saajand Aug 22, 2024
fcbc735
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
saajand Aug 22, 2024
ebda9f9
Unset Cosmos proposal details when switching chain
saajand Aug 22, 2024
d1efce7
Add fetching status to EVM Proposals lists
saajand Aug 26, 2024
925875d
Merge pull request #11 from icon-project/develop_missing-chains-handler
saajand Aug 26, 2024
00aad5b
Merge pull request #12 from icon-project/develop_neutron-approval-fix
saajand Aug 26, 2024
af12159
Merge branch 'develop' into develop_ui-improvements
saajand Aug 26, 2024
a393368
Merge pull request #13 from icon-project/develop_ui-improvements
saajand Aug 26, 2024
7980b12
Update condtions for EVM proposal Action buttons
saajand Aug 26, 2024
1b0f5fa
Improvements to EVM proposals data fetching
saajand Aug 26, 2024
6189521
feature: add support for SUI network - SUI proposals management pages…
saajand Oct 24, 2024
c0214ca
Add input box to SUI proposal approval box
saajand Oct 24, 2024
1ab3739
Add Blockchain name to Navbar for supported chains
saajand Oct 24, 2024
0884fe8
Text Updates
saajand Nov 8, 2024
bd3f40a
Fix package versions to handle conflicts
saajand Nov 8, 2024
a8e6421
Update dockerfile
saajand Nov 8, 2024
ef1ff1c
Merge pull request #14 from icon-project/develop_SUI-integration
saajand Nov 8, 2024
6706f97
Fix package versions to handle conflicts
saajand Nov 8, 2024
d0aec13
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
saajand Nov 8, 2024
de116f2
Merge pull request #16 from icon-project/improvements/docker-updates
gcranju Nov 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge branch 'develop' of github.com:icon-project/Multisig-Frontend- …
…into evm-integration
saajand committed Jul 18, 2024
commit af28108a8e585d9fd5c0013c095fbe01adce6cb5
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
VITE_APP_ENV=
VITE_APP_ARCHWAY_CONTRACT_ADDRESS=
VITE_APP_INJECTIVE_CONTRACT_ADDRESS=
VITE_APP_NEUTRON_CONTRACT_ADDRESS=
VITE_APP_WALLET_CONNECT_PROJECT_ID=
VITE_BASE_SEPOLIA_SAFE=
VITE_OPTIMISM_SEPOLIA_SAFE=
Empty file.
28 changes: 0 additions & 28 deletions .github/ISSUE_TEMPLATE/.github/PULL_REQUEST_TEMPLATE.md

This file was deleted.

11 changes: 0 additions & 11 deletions .github/ISSUE_TEMPLATE/.github/labeler.yml

This file was deleted.

25 changes: 0 additions & 25 deletions .github/ISSUE_TEMPLATE/.github/release.yml

This file was deleted.

32 changes: 0 additions & 32 deletions .github/ISSUE_TEMPLATE/.github/workflows/auto-pr-labeler.yml

This file was deleted.

25 changes: 0 additions & 25 deletions .github/ISSUE_TEMPLATE/.github/workflows/check-pr-label.yml

This file was deleted.

18 changes: 0 additions & 18 deletions .github/ISSUE_TEMPLATE/.github/workflows/lint-pr.yml

This file was deleted.

24 changes: 0 additions & 24 deletions .github/ISSUE_TEMPLATE/bug_report.md

This file was deleted.

20 changes: 0 additions & 20 deletions .github/ISSUE_TEMPLATE/feature_request.md

This file was deleted.

27 changes: 0 additions & 27 deletions .github/ISSUE_TEMPLATE/task-template.md

This file was deleted.

12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -11,11 +11,22 @@
"preview": "vite preview"
},
"dependencies": {
"@archwayhq/arch3.js": "^0.7.1",
"@cosmjs/proto-signing": "^0.32.4",
"@cosmjs/stargate": "^0.32.4",
"@cosmos-kit/keplr": "^2.12.2",
"@cosmos-kit/react": "^2.17.2",
"@injectivelabs/exceptions": "^1.14.13",
"@injectivelabs/sdk-ts": "^1.14.13",
"@injectivelabs/ts-types": "^1.14.13",
"@injectivelabs/utils": "^1.14.13",
"@interchain-ui/react": "^1.23.24",
"@keplr-wallet/types": "^0.12.108",
"@rainbow-me/rainbowkit": "2",
"@tanstack/react-query": "^5.50.1",
"bech32": "^2.0.0",
"buffer": "^6.0.3",
"interchain-query": "^1.10.7",
"chain-registry": "^1.63.15",
"ethers": "5.7.2",
"react": "^18.3.1",
@@ -30,6 +41,7 @@
"@typescript-eslint/parser": "^7.15.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19",
"daisyui": "^4.12.10",
"eslint": "^9.6.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
21 changes: 7 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import CosmosWalletWidget from './components/CosmosWalletWidget';
import { CosmosChains } from './constants/chains';
import CosmosApprovalPage from './pages/CosmosApprovalPage';
import '@rainbow-me/rainbowkit/styles.css';
import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { sepolia, baseSepolia } from 'wagmi/chains';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
import { config } from './config';
import { Landing } from './components/Landing';

import EVMManagerPage from './pages/EVMManagerPage';

const WALLET_CONNECT_PROJECT_ID = import.meta.env.VITE_APP_WALLET_CONNECT_PROJECT_ID;
// const WALLET_CONNECT_PROJECT_ID = import.meta.env.VITE_APP_WALLET_CONNECT_PROJECT_ID;

function App() {
const queryClient = new QueryClient();
@@ -27,16 +24,12 @@ function App() {
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<div className="app pt-5 pl-32">
<h3 className="text-lg font-bold">IBC</h3>
<div className="app min-h-[100vh] bg-gradient-to-b from-blue-400 to-blue-200">
<h3 className="text-4xl text-center font-bold py-6">IBC</h3>

<div>
<div className="w-[400px]">
<CosmosWalletWidget chain={CosmosChains.archway} />
</div>
<CosmosApprovalPage />

<EVMManagerPage />
</div>
<EVMManagerPage />
</div>
</RainbowKitProvider>
</QueryClientProvider>
146 changes: 146 additions & 0 deletions src/components/CosmosProposalsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { ReactNode, useEffect, useState } from 'react';
import { useAppContext } from '../context/AppContext';
import { useContractData } from '../hooks/useContractData';
import { useChain } from '@cosmos-kit/react';
import { convertTimestampToDateTime } from '../utils/dateTimeUtils';
import GeneralModal from './GeneralModal';

export type Proposal = {
id: number;
title: string;
description: string;
msgs: Array<{
wasm: {
execute: {
contract_addr: string;
msg: string;
funds: Array<any>;
};
};
}>;
status: string;
expires: {
at_time: string;
};
threshold: {
absolute_count: {
weight: number;
total_weight: number;
};
};
proposer: string;
deposit: any | null;
};

interface ModalDetails {
show: boolean;
title: string;
description: string | ReactNode;
}

const CosmosProposalsPage = () => {
const { state } = useAppContext();
const chainName = state.activeCosmosChain.chainName;
const { address } = useChain(chainName);
const { getContractData } = useContractData(chainName);
const [proposalList, setProposalList] = useState<Proposal[]>([]);
const [modalDetails, setModalDetails] = useState<ModalDetails>({
show: false,
title: '',
description: '',
});

const handleModalClose = () => {
setModalDetails({
show: false,
title: '',
description: '',
});
};

const getProposals = async () => {
const txMsg = {
list_proposals: {},
};
const { proposals } = await getContractData(txMsg);
setProposalList(proposals);
};

useEffect(() => {
if (address) {
getProposals();
}
}, [address]);

return (
<>
<div className="overflow-x-auto">
<table className="d-table rounded bg-[rgba(255,255,255,0.1)]">
<thead>
<tr>
<th>Proposal Id</th>
<th>Title</th>
<th>Description</th>
<th>Deposil</th>
<th>Proposer</th>
<th>Msgs</th>
<th>Threshold</th>
<th>Status</th>
<th>Expires At</th>
</tr>
</thead>
<tbody>
{proposalList.map((proposal, index) => (
<tr key={index}>
<th>{proposal.id}</th>
<td>{proposal.title}</td>
<td>{proposal.description}</td>
<td>{proposal.deposit}</td>
<td>{proposal.proposer}</td>
<td>
<button
className="d-btn"
onClick={() => {
setModalDetails({
show: true,
title: 'Msgs',
description: <pre>{JSON.stringify(proposal.msgs, null, 2)}</pre>,
});
}}
>
Show Msgs
</button>
</td>
<td>
<button
className="d-btn"
onClick={() => {
setModalDetails({
show: true,
title: 'Threshold',
description: <pre>{JSON.stringify(proposal.threshold, null, 2)}</pre>,
});
}}
>
Show Threshold
</button>
</td>
<td>{proposal.status}</td>
<td>{convertTimestampToDateTime(proposal.expires.at_time)}</td>
</tr>
))}
</tbody>
</table>
</div>

<GeneralModal
show={modalDetails.show}
closeHandler={handleModalClose}
title={modalDetails.title}
description={modalDetails.description}
/>
</>
);
};

export default CosmosProposalsPage;
39 changes: 22 additions & 17 deletions src/components/CosmosWalletWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
import { useChain } from '@cosmos-kit/react';
import { CosmosChains } from '../../constants/chains';
import { CosmosChains, getCosmosChain } from '../../constants/chains';
import { ChangeEvent, useEffect, useState } from 'react';
import { useAppContext } from '../../context/AppContext';
import { ActionTypes } from '../../context/ActionTypes';

interface CosmosWalletWidgetProps {
chain: string;
}
const ENV = import.meta.env.VITE_APP_ENV;
const NETWORK_TYPE = ENV === 'prod' ? 'mainnet' : 'testnet';

const CosmosWalletWidget = ({ chain }: CosmosWalletWidgetProps) => {
const [activeChain, setActiveChain] = useState(chain);
const { connect, openView, address } = useChain(activeChain);
const CosmosWalletWidget = () => {
const { state, dispatch } = useAppContext();
const chainName = state.activeCosmosChain.chainName;
const [activeChain, setActiveChain] = useState('');
const { connect, openView, address } = useChain(chainName);

const AvailableChainOptions = Object.values(CosmosChains).filter((chain) => chain.networkType === NETWORK_TYPE);

const handleActiveChainChange = (event: ChangeEvent<HTMLSelectElement>) => {
setActiveChain(event.target.value);
localStorage.setItem('cosmos-active-chain', event.target.value);
const selectedValue = event.target.value;
const cosmosChain = getCosmosChain(selectedValue as keyof typeof CosmosChains);
if (cosmosChain) {
dispatch({ type: ActionTypes.SET_ACTIVE_COSMOS_CHAIN, payload: cosmosChain });
}
};

useEffect(() => {
const activeCosmosChain = localStorage.getItem('cosmos-active-chain');
if (activeCosmosChain) {
setActiveChain(activeCosmosChain);
}
}, []);
setActiveChain(state.activeCosmosChain.chainName);
}, [state.activeCosmosChain.chainName]);

return (
<div className="cosmos-wallet-widget">
@@ -32,9 +37,9 @@ const CosmosWalletWidget = ({ chain }: CosmosWalletWidgetProps) => {
value={activeChain}
onChange={handleActiveChainChange}
>
{Object.keys(CosmosChains).map((chain: string, index) => (
<option value={chain} key={index}>
{chain}
{AvailableChainOptions.map((chain: any, index) => (
<option value={chain.chainName} key={index}>
{chain.name}
</option>
))}
</select>
31 changes: 31 additions & 0 deletions src/components/GeneralModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { ReactNode } from 'react';

interface GeneralModalProps {
show: boolean;
closeHandler: () => void;
title: string;
description: string | ReactNode;
}

const GeneralModal: React.FC<GeneralModalProps> = ({ show, closeHandler, title, description }) => {
if (!show) return null;

return (
<dialog className="d-modal d-modal-bottom sm:d-modal-middle d-modal-open">
<div className="d-modal-box">
<h3 className="font-bold text-lg">{title}</h3>
<div className="d-modal-action flex-col">
<div className="overflow-auto">{description}</div>

<form method="dialog" className="flex justify-center mt-4">
<button className="d-btn" onClick={closeHandler}>
Close
</button>
</form>
</div>
</div>
</dialog>
);
};

export default GeneralModal;
22 changes: 18 additions & 4 deletions src/constants/chains.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
export type cosmosChainDetails = {
chainId: string;
chainName: string;
name: string;
networkType: string;
};

export const CosmosChains = {
archway: 'archway',
injective: 'injective',
neutron: 'neutron',
}
archway: { chainId: 'archway-1', chainName: 'archway', name: 'archway', networkType: 'mainnet' },
injective: { chainId: 'injective-1', chainName: 'injective', name: 'injective', networkType: 'mainnet' },
neutron: { chainId: 'neutron-1', chainName: 'neturon', name: 'neturon', networkType: 'mainnet' },
archwaytestnet: { chainId: 'constantine-3', chainName: 'archwaytestnet', name: 'archway', networkType: 'testnet' },
injectivetestnet: { chainId: 'injective-888', chainName: 'injectivetestnet', name: 'injective', networkType: 'testnet' },
neutrontestnet: { chainId: 'pion-1', chainName: 'neutrontestnet', name: 'neturon', networkType: 'testnet' },
};

export const getCosmosChain = (chain: keyof typeof CosmosChains): cosmosChainDetails | undefined => {
return CosmosChains[chain];
};
29 changes: 29 additions & 0 deletions src/constants/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
const ARCHWAY_CONTRACT_ADDRESS = import.meta.env.VITE_APP_ARCHWAY_CONTRACT_ADDRESS;
const INJECTIVE_CONTRACT_ADDRESS = import.meta.env.VITE_APP_INJECTIVE_CONTRACT_ADDRESS;
const NEUTRON_CONTRACT_ADDRESS = import.meta.env.VITE_APP_NEUTRON_CONTRACT_ADDRESS;

type CosmosContracts = {
archway: string;
injective: string;
neutron: string;
// Testnets
archwaytestnet: string;
injectivetestnet: string;
neutrontestnet: string;
};

export const CosmosContracts: CosmosContracts = {
archway: ARCHWAY_CONTRACT_ADDRESS,
injective: INJECTIVE_CONTRACT_ADDRESS,
neutron: NEUTRON_CONTRACT_ADDRESS,
archwaytestnet: ARCHWAY_CONTRACT_ADDRESS,
injectivetestnet: INJECTIVE_CONTRACT_ADDRESS,
neutrontestnet: NEUTRON_CONTRACT_ADDRESS,
};

export const getCosmosContractByChain = (chain: string): string | undefined => {
if (chain in CosmosContracts) {
return CosmosContracts[chain as keyof CosmosContracts];
}
};

const BASE_SEPOLIA_SAFE = import.meta.env.VITE_BASE_SEPOLIA_SAFE;
const SEPOLIA_SAFE = import.meta.env.VITE_SEPOLIA_SAFE;
const FUJI_SAFE = import.meta.env.VITE_FUJI_SAFE;
3 changes: 3 additions & 0 deletions src/context/ActionTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum ActionTypes {
SET_ACTIVE_COSMOS_CHAIN = 'SET_ACTIVE_COSMOS_CHAIN',
}
51 changes: 51 additions & 0 deletions src/context/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { createContext, useContext, useReducer, Dispatch, ReactNode } from 'react';
import { ActionTypes } from './ActionTypes';
import { CosmosChains, cosmosChainDetails } from '../constants/chains';

const ENV = import.meta.env.VITE_APP_ENV;
const NETWORK_TYPE = ENV === 'prod' ? 'mainnet' : 'testnet';

interface State {
activeCosmosChain: cosmosChainDetails;
}

type Action = { type: ActionTypes.SET_ACTIVE_COSMOS_CHAIN; payload: cosmosChainDetails };

const defaultChain = Object.values(CosmosChains).find((chain) => chain.networkType === NETWORK_TYPE);
const initialState: State = {
activeCosmosChain: {
chainId: defaultChain!.chainId,
chainName: defaultChain!.chainName,
name: defaultChain!.name,
networkType: defaultChain!.networkType,
},
};

interface ContextType {
state: State;
dispatch: Dispatch<Action>;
}
const AppContext = createContext<ContextType | undefined>(undefined);

const reducer = (state: State, action: Action): State => {
switch (action.type) {
case ActionTypes.SET_ACTIVE_COSMOS_CHAIN:
return { ...state, activeCosmosChain: action.payload };
default:
throw new Error('Unhandled action type');
}
};

export const AppProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);

return <AppContext.Provider value={{ state, dispatch }}>{children}</AppContext.Provider>;
};

export const useAppContext = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error('useAppContext must be used within an AppProvider');
}
return context;
};
36 changes: 36 additions & 0 deletions src/hooks/useContractData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useChain } from '@cosmos-kit/react';
import { getCosmosContractByChain } from '../constants/contracts';
import { getKeplr } from '../utils/walletUtils';
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { prependHttpsIfNeeded } from '../utils/chainUtils';

export const useContractData = (chainName: string) => {
const { address, chain } = useChain(chainName);

const getContractData = async (msgs: { [key: string]: Record<string, never> }) => {
if (!address) {
console.log('Wallet not connected. Please connect the wallet');
return;
}

try {
const rpc_url = chain.apis?.rpc && chain.apis?.rpc[0].address;
if (!rpc_url) throw new Error('No chain rpcUrl found.');
const rpcUrl = prependHttpsIfNeeded(rpc_url);

const contractAddress = getCosmosContractByChain(chain.chain_name);
if (!contractAddress) throw new Error('No contract address details found.');
const chainId = chain.chain_id;
const { offlineSigner } = await getKeplr(chainId);

const client = await SigningCosmWasmClient.connectWithSigner(rpcUrl, offlineSigner);
const result = await client.queryContractSmart(contractAddress, msgs);
return result;
} catch (e: any) {
console.error(e);
return;
}
};

return { getContractData };
};
74 changes: 74 additions & 0 deletions src/hooks/useTx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useChain } from '@cosmos-kit/react';
import { StdFee } from '@cosmjs/stargate';
import { coins } from '@cosmjs/proto-signing';
import { Dec, IntPretty } from '@keplr-wallet/unit';
import { getCosmosContractByChain } from '../constants/contracts';
import { getKeplr } from '../utils/walletUtils';
import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { prependHttpsIfNeeded } from '../utils/chainUtils';

interface Msg {
typeUrl: string;
value: any;
}

interface TxOptions {
fee?: StdFee | null;
onSuccess?: () => void;
}

export enum TxStatus {
Failed = 'Transaction Failed',
Successful = 'Transaction Successful',
Broadcasting = 'Transaction Broadcasting',
}

export const useTx = (chainName: string) => {
const { address, chain } = useChain(chainName);

const tx = async (msgs: Msg[], options: TxOptions) => {
if (!address) {
console.log('Wallet not connected. Please connect the wallet');
return;
}

try {
let fee: StdFee;
const rpc_url = chain.apis?.rpc && chain.apis?.rpc[0].address;
if (!rpc_url) throw new Error('No chain rpcUrl found.');
const rpcUrl = prependHttpsIfNeeded(rpc_url);

const contractAddress = getCosmosContractByChain(chain.chain_name);
if (!contractAddress) throw new Error('No contract address details found.');
const chainId = chain.chain_id;
const { offlineSigner } = await getKeplr(chainId);

const client = await SigningCosmWasmClient.connectWithSigner(rpcUrl, offlineSigner);

if (options?.fee) {
fee = options.fee;
} else {
const gasEstimated = await client.simulate(address, msgs, '');

const chainFeeDetails = chain?.fees?.fee_tokens[0];
fee = {
amount: coins(chainFeeDetails?.average_gas_price || 0, chainFeeDetails?.denom || ''),
gas: new IntPretty(new Dec(gasEstimated).mul(new Dec(1.3))).maxDecimals(0).locale(false).toString(),
};
}

const result = await client.execute(address, contractAddress, msgs, fee, '');
console.log('Transaction result:', result);

if (result.transactionHash && options.onSuccess) {
options.onSuccess();
}
return result.transactionHash;
} catch (e: any) {
console.error(e);
return;
}
};

return { tx };
};
12 changes: 10 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -4,16 +4,24 @@ import App from './App.tsx';
import './index.css';
import { ChainProvider } from '@cosmos-kit/react';
import { chains, assets } from 'chain-registry';
import { chains as testnetChains, assets as testnetAssets } from 'chain-registry/testnet';
import { wallets } from '@cosmos-kit/keplr';
import { ThemeProvider } from '@interchain-ui/react';
import { AppProvider } from './context/AppContext.tsx';

import '@interchain-ui/react/styles';

const ENV = import.meta.env.VITE_APP_ENV;
const chainsInUse = ENV === 'prod' ? chains : testnetChains;
const assetsInUse = ENV === 'prod' ? assets : testnetAssets;

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ThemeProvider>
<ChainProvider chains={chains} assetLists={assets} wallets={wallets}>
<App />
<ChainProvider chains={chainsInUse} assetLists={assetsInUse} wallets={wallets}>
<AppProvider>
<App />
</AppProvider>
</ChainProvider>
</ThemeProvider>
</React.StrictMode>,
167 changes: 167 additions & 0 deletions src/pages/CosmosApprovalPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { useState } from 'react';
import { getCosmosContractByChain } from '../constants/contracts';
import { executeInjectiveContractCall } from '../utils/injectiveUtils';
import { useChain } from '@cosmos-kit/react';
import { useTx } from '../hooks/useTx';
import CosmosWalletWidget from '../components/CosmosWalletWidget';
import { useAppContext } from '../context/AppContext';
import CosmosProposalsTable from '../components/CosmosProposalsTable';

const CosmosApprovalPage = () => {
const { state } = useAppContext();
const chainName = state.activeCosmosChain.chainName;
const contractAddress = getCosmosContractByChain(chainName);
const { address, isWalletConnected, connect } = useChain(chainName);
const [proposalInput, setProposalInput] = useState('');
const [txnHash, setTxnHash] = useState('');
const { tx } = useTx(chainName);

const handleInjectiveApprove = async () => {
const chainId = state.activeCosmosChain.chainId;
if (!contractAddress) {
console.log('No contract address found.');
return;
}
const voteValue = 'yes';
const txMsg = {
vote: {
proposal_id: Number(proposalInput),
vote: voteValue,
},
};
const res = await executeInjectiveContractCall(chainId, contractAddress, txMsg);
if (res) {
console.log('Transaction Success. TxHash', res);
setTxnHash(res);
}
};

const handleApprove = async () => {
const voteValue = 'yes';
const txMsg = {
vote: {
proposal_id: Number(proposalInput),
vote: voteValue,
},
};

const encodedMsg = {
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
value: {
sender: address,
contract: contractAddress,
msg: Buffer.from(JSON.stringify(txMsg)),
},
};

const res = await tx([encodedMsg], {
onSuccess: () => {
console.log('Transaction Success!');
},
});
if (res) {
console.log('Transaction Success. TxHash', res);
setTxnHash(res);
}
};

const handleApproveClick = () => {
if (!isWalletConnected) {
connect();
return;
}
const chain_name = state.activeCosmosChain.name;
if (chain_name === 'injective') {
handleInjectiveApprove();
} else {
handleApprove();
}
};

const handleInjectiveExecute = async () => {
const chainId = state.activeCosmosChain.chainId;
if (!contractAddress) {
console.log('No contract address found.');
return;
}
const txMsg = {
execute: {
proposal_id: Number(proposalInput),
},
};
const res = await executeInjectiveContractCall(chainId, contractAddress, txMsg);
if (res) {
console.log('Transaction Success. TxHash', res);
setTxnHash(res);
}
};

const handleExecute = async () => {
const txMsg = {
execute: {
proposal_id: Number(proposalInput),
},
};

const encodedMsg = {
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
value: {
sender: address,
contract: contractAddress,
msg: Buffer.from(JSON.stringify(txMsg)),
},
};

const res = await tx([encodedMsg], {
onSuccess: () => {
console.log('Transaction Success!');
},
});
if (res) {
console.log('Transaction Success. TxHash', res);
setTxnHash(res);
}
};

const handleExecuteClick = () => {
if (!isWalletConnected) {
connect();
return;
}
const chain_name = state.activeCosmosChain.name;
if (chain_name === 'injective') {
handleInjectiveExecute();
} else {
handleExecute();
}
};

return (
<div className="cosmos-approval-page w-full m-auto bg-[rgba(255,255,255,0.5)] p-4 rounded flex flex-col items-center">
<CosmosWalletWidget />

<h3 className="font-bold text-lg mt-4 mb-3">Cosmos Approval</h3>
<div className="approval-submit-section flex gap-2">
<input
value={proposalInput}
onChange={(e) => setProposalInput(e.target.value)}
placeholder="Proposal ID"
className="bg-gray-200 p-2 rounded hover:outline-none focus:outline-none"
/>
<button onClick={handleApproveClick} className="bg-blue-600 text-white p-2 rounded font-bold">
Approve Proposal
</button>
<button onClick={handleExecuteClick} className="bg-blue-600 text-white p-2 rounded font-bold">
Execute Proposal
</button>
</div>
{txnHash && <div>Transaction hash: {txnHash}</div>}

<div className="mt-4 w-full max-w-[1920px]">
<CosmosProposalsTable />
</div>
</div>
);
};

export default CosmosApprovalPage;
26 changes: 26 additions & 0 deletions src/utils/archwayUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getKeplr } from './walletUtils';
import { SigningArchwayClient } from '@archwayhq/arch3.js';

const RPC_URL = 'https://rpc.constantine.archway.io:443'; // Update

export const executeArchwayContractCall = async (
chainId: string,
contractAddress: string,
txMsg: any,
): Promise<string | undefined> => {
try {
const { key, offlineSigner } = await getKeplr(chainId);
const walletAddress = key.bech32Address;
const signingClient = await SigningArchwayClient.connectWithSigner(RPC_URL, offlineSigner);

const txResponse = await signingClient.execute(walletAddress, contractAddress, txMsg, 'auto');
console.log(txResponse);
if (!txResponse.transactionHash) {
throw new Error('No transaction hash!');
}

return txResponse.transactionHash;
} catch (error) {
console.log('ERROR:', error);
}
};
7 changes: 7 additions & 0 deletions src/utils/chainUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function prependHttpsIfNeeded(url: string): string {
if (url.startsWith('https://')) {
return url;
} else {
return 'https://' + url;
}
}
7 changes: 7 additions & 0 deletions src/utils/dateTimeUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function convertTimestampToDateTime(timestamp: string): string {
const ts = Number(timestamp);
const date = new Date(ts / 1_000_000);
const formattedDate = date.toISOString().substring(0, 19);

return formattedDate;
}
88 changes: 88 additions & 0 deletions src/utils/injectiveUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { getKeplr } from './walletUtils';
import { TxRaw, CosmosTxV1Beta1Tx, BroadcastModeKeplr } from '@injectivelabs/sdk-ts';
import { TransactionException } from '@injectivelabs/exceptions';
import {
MsgExecuteContract,
BaseAccount,
ChainRestAuthApi,
createTransaction,
ChainRestTendermintApi,
getTxRawFromTxRawOrDirectSignResponse,
} from '@injectivelabs/sdk-ts';
import { getStdFee, DEFAULT_BLOCK_TIMEOUT_HEIGHT } from '@injectivelabs/utils';
// import { ChainId } from '@injectivelabs/ts-types';
import { BigNumberInBase } from '@injectivelabs/utils';
import { SignDoc } from '@keplr-wallet/types';
import { Network, getNetworkEndpoints } from '@injectivelabs/networks';

const ENV = import.meta.env.VITE_APP_ENV;
const NetworkType = ENV === 'prod' ? Network.Mainnet : Network.Testnet;

const broadcastTx = async (chainId: string, txRaw: TxRaw) => {
const { offlineSigner } = await getKeplr(chainId);
const result = await offlineSigner.keplr.sendTx(
chainId,
CosmosTxV1Beta1Tx.TxRaw.encode(txRaw).finish(),
BroadcastModeKeplr.Sync,
);

if (!result || result.length === 0) {
throw new TransactionException(new Error('Transaction failed to be broadcasted'), { contextModule: 'Keplr' });
}

return Buffer.from(result).toString('hex');
};

export const executeInjectiveContractCall = async (
chainId: string,
contractAddress: string,
txMsg: any,
): Promise<string | undefined> => {
try {
const { key, offlineSigner } = await getKeplr(chainId);
const pubKey = Buffer.from(key.pubKey).toString('base64');
const injectiveAddress = key.bech32Address;
const endpoints = getNetworkEndpoints(NetworkType);
const restEndpoint = endpoints.rest;

/** Account Details **/
const chainRestAuthApi = new ChainRestAuthApi(restEndpoint);
const accountDetailsResponse = await chainRestAuthApi.fetchAccount(injectiveAddress);
const baseAccount = BaseAccount.fromRestApi(accountDetailsResponse);

/** Block Details */
const chainRestTendermintApi = new ChainRestTendermintApi(restEndpoint);
const latestBlock = await chainRestTendermintApi.fetchLatestBlock();
const latestHeight = latestBlock.header.height;
const timeoutHeight = new BigNumberInBase(latestHeight).plus(DEFAULT_BLOCK_TIMEOUT_HEIGHT);

/** Preparing the transaction */
const msg = MsgExecuteContract.fromJSON({
sender: injectiveAddress,
contractAddress: contractAddress,
// contract: contractAddress,
msg: txMsg,
});

/** Prepare the Transaction **/
const { signDoc } = createTransaction({
message: msg,
memo: '',
fee: getStdFee({}),
pubKey: pubKey,
sequence: baseAccount.sequence,
timeoutHeight: timeoutHeight.toNumber(),
accountNumber: baseAccount.accountNumber,
chainId: chainId,
});

const directSignResponse = await offlineSigner.signDirect(injectiveAddress, signDoc as unknown as SignDoc);
const txRaw = getTxRawFromTxRawOrDirectSignResponse(directSignResponse);
const txHash = await broadcastTx(chainId, txRaw);
return txHash;
// const response = await new TxRestClient(restEndpoint).fetchTxPoll(txHash);
// console.log(response);
} catch (error) {
console.log('ERROR:', error);
}
};
10 changes: 10 additions & 0 deletions src/utils/walletUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const getKeplr = async (chainId: string) => {
const keplr = (window as any).keplr;
await keplr.enable(chainId);

const offlineSigner = keplr.getOfflineSigner(chainId);
const accounts = await offlineSigner.getAccounts();
const key = await keplr.getKey(chainId);

return { offlineSigner, accounts, key };
};
6 changes: 5 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
@@ -4,5 +4,9 @@ export default {
theme: {
extend: {},
},
plugins: [],
plugins: [require('daisyui')],
daisyui: {
prefix: "d-",
themes: ["light"]
},
};
17 changes: 16 additions & 1 deletion vite.config.ts
Original file line number Diff line number Diff line change
@@ -4,5 +4,20 @@ import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), nodePolyfills()],
plugins: [
react(),
nodePolyfills({
include: ['path', 'stream', 'util'],
exclude: ['http'],
globals: {
Buffer: true,
global: true,
process: true,
},
overrides: {
fs: 'memfs',
},
protocolImports: true,
}),
],
});
7,594 changes: 2,631 additions & 4,963 deletions yarn.lock

Large diffs are not rendered by default.