Skip to content

Commit

Permalink
feat: Implement CosmWasm contracts (#1222)
Browse files Browse the repository at this point in the history
* wip

* wip: cosmwasm contracts ui

* wip

* feat: add store for cosmwasm

* wip: cosmwasm contracts

* wip

* wip: add attach funds option

* wip

* wip: deploy contract

* chore: add loading and error handling

* wip

* feat: add state management

* fix: lint issues

* fix: lint issues

* chore

* refactor

* chore

* chore: review changes (#1224)

* chore: review changes

* chore: refactor code

* feat: allow user to provide address list for instantiation

* chore: review changes

* chore

* chore: add cosmwasm icon in sidebar

---------

Co-authored-by: chary <57086313+charymalloju@users.noreply.github.com>
  • Loading branch information
Hemanthghs and charymalloju authored Apr 25, 2024
1 parent 4faffd7 commit 200c0f4
Show file tree
Hide file tree
Showing 55 changed files with 4,085 additions and 5 deletions.
3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@0xsquid/sdk": "^1.14.15",
"@cosmjs/amino": "^0.31.3",
"@cosmjs/cosmwasm-stargate": "0.32.2",
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@emotion/cache": "^11.11.0",
Expand All @@ -25,6 +26,7 @@
"@reduxjs/toolkit": "^1.9.7",
"@skip-router/core": "^1.3.11",
"@types/node": "20.6.5",
"@types/node-gzip": "1.1.0",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"autoprefixer": "10.4.16",
Expand All @@ -41,6 +43,7 @@
"mathjs": "^12.0.0",
"moment": "^2.29.4",
"next": "^14.0.1",
"node-gzip": "^1.1.2",
"postcss": "8.4.30",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
Expand Down
5 changes: 5 additions & 0 deletions frontend/public/cosmwasm-icon-active.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions frontend/public/cosmwasm-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions frontend/src/app/(routes)/cosmwasm/Cosmwasm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';
import TopNav from '@/components/TopNav';
import Image from 'next/image';
import React from 'react';

const Cosmwasm = () => {
const message =
'All Networks page is not supported for Cosmwasm, Please select a network.';
return (
<div className="h-screen flex flex-col p-6 pl-10">
<div className="w-full flex justify-between items-center">
<h2 className="text-[20px] leading-normal font-normal">Cosmwasm</h2>
<TopNav message={message} />
</div>
<div className="flex-1 flex flex-col justify-center items-center gap-4">
<Image
src="/no-multisigs.png"
width={400}
height={235}
alt={'No Transactions'}
draggable={false}
/>
<p>{message}</p>
<button
className="primary-custom-btn"
onClick={() => {
document.getElementById('select-network')!.click();
}}
>
Select Network
</button>
</div>
</div>
);
};

export default Cosmwasm;
25 changes: 25 additions & 0 deletions frontend/src/app/(routes)/cosmwasm/[network]/ChainContracts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';
import { useAppSelector } from '@/custom-hooks/StateHooks';
import React from 'react';
import PageContracts from './PageContracts';

const ChainContracts = ({ network }: { network: string }) => {
const nameToChainIDs = useAppSelector((state) => state.wallet.nameToChainIDs);
const chainName = network.toLowerCase();
const validChain = chainName in nameToChainIDs;
return (
<div>
{validChain ? (
<PageContracts chainName={chainName} />
) : (
<>
<div className="flex justify-center items-center h-screen w-full text-white txt-lg">
- The {chainName} is not supported -
</div>
</>
)}
</div>
);
};

export default ChainContracts;
68 changes: 68 additions & 0 deletions frontend/src/app/(routes)/cosmwasm/[network]/PageContracts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client';
import TopNav from '@/components/TopNav';
import React, { useState } from 'react';
import { useAppSelector } from '@/custom-hooks/StateHooks';
import Contracts from '../components/Contracts';
import AllContracts from '../components/AllContracts';
import DialogTxContractStatus from '../components/DialogTxUploadCodeStatus';
import DialogTxExecuteStatus from '../components/DialogTxExecuteStatus';
import DialogTxInstantiateStatus from '../components/DialogTxInstantiateStatus';

const PageContracts = ({ chainName }: { chainName: string }) => {
const nameToChainIDs: Record<string, string> = useAppSelector(
(state) => state.wallet.nameToChainIDs
);
const chainID = nameToChainIDs[chainName];
const tabs = ['Contracts', 'All Contracts'];
const [selectedTab, setSelectedTab] = useState('Contracts');
return (
<div className="h-screen flex flex-col p-6 px-10 gap-10">
<div className="flex flex-col gap-6">
<div className="w-full flex justify-between items-center">
<h2 className="text-[20px] leading-normal font-normal">
CosmWasm Smart Contracts
</h2>
<TopNav />
</div>
<div className="flex gap-10 items-center border-b-[1px] border-[#ffffff1e] mt-6">
{tabs.map((tab) => (
<div key={tab} className="flex flex-col justify-center">
<div
className={
selectedTab.toLowerCase() === tab.toLowerCase()
? 'menu-item font-semibold'
: 'menu-item font-normal'
}
onClick={() => {
setSelectedTab(tab);
}}
>
{tab}
</div>
<div
className={
selectedTab.toLowerCase() === tab.toLowerCase()
? 'rounded-full h-[3px] primary-gradient'
: 'rounded-full h-[3px] bg-transparent'
}
></div>
</div>
))}
</div>
</div>

<div className="rounded-2xl bg-[#FFFFFF0D] p-10 h-full overflow-y-scroll">
{selectedTab === 'Contracts' ? (
<Contracts chainID={chainID} />
) : (
<AllContracts />
)}
</div>
<DialogTxContractStatus chainID={chainID} />
<DialogTxExecuteStatus chainID={chainID} />
<DialogTxInstantiateStatus chainID={chainID} />
</div>
);
};

export default PageContracts;
9 changes: 9 additions & 0 deletions frontend/src/app/(routes)/cosmwasm/[network]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import '../cosmwasm.css';
import ChainContracts from './ChainContracts';

const page = ({ params: { network } }: { params: { network: string } }) => {
return <ChainContracts network={network} />;
};

export default page;
60 changes: 60 additions & 0 deletions frontend/src/app/(routes)/cosmwasm/components/AddAddresses.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react';
import AddressInputField from './AddressInputField';

interface AddAddressesI {
addresses: string[];
setAddresses: React.Dispatch<React.SetStateAction<string[]>>;
}

const AddAddresses = (props: AddAddressesI) => {
const { addresses, setAddresses } = props;

const onAddAddress = (address: string) => {
setAddresses((prev) => [...prev, address]);
};

const onDelete = (index: number) => {
const newAddresses = addresses.filter((_, i) => i !== index);
setAddresses(newAddresses);
};

const handleAddressChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number
) => {
const input = e.target.value;
const newAddresses = addresses.map((value, key) => {
if (index === key) {
input.trim();
}
return value;
});
setAddresses(newAddresses);
};

return (
<div className="space-y-6">
{addresses.map((value, index) => (
<div key={index}>
<AddressInputField
address={value}
index={index}
onDelete={onDelete}
handleChange={handleAddressChange}
/>
</div>
))}
<div className="flex justify-end">
<button
type="button"
className="primary-gradient add-address-btn"
onClick={() => onAddAddress('')}
>
Add More
</button>
</div>
</div>
);
};

export default AddAddresses;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { InputAdornment, TextField } from '@mui/material';
import React from 'react';
import { customTextFieldStyles } from '../styles';
import Image from 'next/image';

interface AddressInputFieldI {
address: string;
handleChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number
) => void;
onDelete: (index: number) => void;
index: number;
}

const AddressInputField = (props: AddressInputFieldI) => {
const { address, handleChange, onDelete, index } = props;
return (
<TextField
name="sourceAmount"
className="rounded-lg bg-[#ffffff0D]"
fullWidth
required={false}
size="small"
autoFocus={true}
placeholder="Enter Address"
sx={customTextFieldStyles}
value={address}
InputProps={{
sx: {
input: {
color: 'white !important',
fontSize: '14px',
padding: 2,
},
},
endAdornment: (
<InputAdornment position="start">
{index === 0 ? null : (
<div
className="cursor-pointer"
onClick={() => {
onDelete(index);
}}
>
<Image src="/delete-icon.svg" width={24} height={24} alt="" />
</div>
)}
</InputAdornment>
),
}}
onChange={(e) => handleChange(e, index)}
/>
);
};

export default AddressInputField;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';

const AllContracts = () => {
return <div className="h-1/2 flex-center-center">Comming Soon...</div>;
};

export default AllContracts;
50 changes: 50 additions & 0 deletions frontend/src/app/(routes)/cosmwasm/components/AmountInputField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { InputAdornment, TextField } from '@mui/material';
import React from 'react';
import { customTextFieldStyles } from '../styles';
import Image from 'next/image';

interface AmountInputFieldI {
amount: string;
handleChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number
) => void;
onDelete: () => void;
index: number;
}

const AmountInputField = (props: AmountInputFieldI) => {
const { amount, handleChange, onDelete, index } = props;
return (
<TextField
name="sourceAmount"
className="rounded-lg bg-[#ffffff0D]"
fullWidth
required={false}
size="small"
autoFocus={true}
placeholder="Enter Amount"
sx={customTextFieldStyles}
value={amount}
InputProps={{
sx: {
input: {
color: 'white !important',
fontSize: '14px',
padding: 2,
},
},
endAdornment: (
<InputAdornment position="start">
<div className="cursor-pointer" onClick={onDelete}>
<Image src="/delete-icon.svg" width={24} height={24} alt="" />
</div>
</InputAdornment>
),
}}
onChange={(e) => handleChange(e, index)}
/>
);
};

export default AmountInputField;
Loading

0 comments on commit 200c0f4

Please sign in to comment.