Skip to content

Commit

Permalink
feat: enable attaching funds
Browse files Browse the repository at this point in the history
  • Loading branch information
marslavish committed Jul 7, 2024
1 parent f2608e5 commit e052575
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 110 deletions.
144 changes: 104 additions & 40 deletions templates/chain-template/components/contract/AttachFundsSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,117 @@
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { Coin } from '@cosmjs/amino';
import { Box, Select, SelectOption, Text } from '@interchain-ui/react';
import { SelectAssetContent } from './SelectAssetContent';
import { Asset } from '@chain-registry/types';
import BigNumber from 'bignumber.js';

import { JsonInput } from './JsonInput';
import { SelectAssetContent } from './SelectAssetContent';
import { getExponentFromAsset, prettifyJson } from '@/utils';

const defaultAssetListJson = prettifyJson(
JSON.stringify([{ denom: '', amount: '' }])
);

export type SelectedAssetWithAmount = {
asset: Asset | undefined;
amount: string;
};

export const defaultSelectedAsset: SelectedAssetWithAmount = {
asset: undefined,
amount: '',
};

type FundsOptionKey = 'no_funds' | 'select_asset' | 'json_asset_list';
export type FundsOptionKey = 'no_funds' | 'select_asset' | 'json_asset_list';

type FundsOption = {
label: string;
key: FundsOptionKey;
label: string;
content: React.ReactNode;
};

type AttachFundsSelectProps = {};
type AttachFundsSelectProps = {
setFunds: (funds: Coin[]) => void;
setIsAssetListJsonValid: (isValid: boolean) => void;
};

export const AttachFundsSelect = ({}: AttachFundsSelectProps) => {
export const AttachFundsSelect = ({
setFunds,
setIsAssetListJsonValid,
}: AttachFundsSelectProps) => {
const [selectedOptionKey, setSelectedOptionKey] =
useState<FundsOptionKey>('no_funds');
const [jsonValue, setJsonValue] = useState('');

const fundsOptions: FundsOption[] = [
{
label: 'No funds attached',
key: 'no_funds',
content: null,
},
{
label: 'Select asset and fill amount',
key: 'select_asset',
content: <SelectAssetContent />,
},
{
label: 'Provide JSON asset list',
key: 'json_asset_list',
content: (
<JsonInput
value={jsonValue}
setValue={setJsonValue}
height="275px"
mt="10px"
/>
),
},
] as const;

const optionContent = fundsOptions.find(
({ key }) => key === selectedOptionKey
)?.content;

const defaultOption = fundsOptions[0];
const [assetListJson, setAssetListJson] = useState(defaultAssetListJson);
const [selectedAssetsWithAmount, setSelectedAssetsWithAmount] = useState<
SelectedAssetWithAmount[]
>([defaultSelectedAsset]);

const fundsOptionsMap: Record<FundsOptionKey, FundsOption> = useMemo(() => {
return {
no_funds: {
key: 'no_funds',
label: 'No funds attached',
content: null,
},
select_asset: {
key: 'select_asset',
label: 'Select asset and fill amount',
content: (
<SelectAssetContent
selectedAssetsWithAmount={selectedAssetsWithAmount}
setSelectedAssetsWithAmount={setSelectedAssetsWithAmount}
/>
),
},
json_asset_list: {
key: 'json_asset_list',
label: 'Provide JSON asset list',
content: (
<JsonInput
value={assetListJson}
setValue={setAssetListJson}
minLines={14}
height="292px"
mt="10px"
/>
),
},
};
}, [selectedAssetsWithAmount, assetListJson]);

useEffect(() => {
setIsAssetListJsonValid(true);

if (selectedOptionKey === 'no_funds') {
setFunds([]);
}

if (selectedOptionKey === 'select_asset') {
const funds = selectedAssetsWithAmount
.filter(({ asset, amount }) => asset && amount)
.map(({ asset, amount }) => ({
denom: asset!.base,
amount: BigNumber(amount)
.shiftedBy(getExponentFromAsset(asset!) ?? 6)
.toString(),
}));

setFunds(funds);
}

if (selectedOptionKey === 'json_asset_list') {
try {
const parsedJson = JSON.parse(assetListJson);
setFunds(parsedJson);
} catch (e) {
setFunds([]);
setIsAssetListJsonValid(false);
}
}
}, [selectedOptionKey, selectedAssetsWithAmount, assetListJson]);

const optionContent = fundsOptionsMap[selectedOptionKey].content;
const defaultOption = fundsOptionsMap.no_funds;

return (
<>
Expand All @@ -62,13 +126,13 @@ export const AttachFundsSelect = ({}: AttachFundsSelectProps) => {
label: defaultOption.label,
index: 0,
}}
fullWidth
onSelectItem={(item) => {
if (!item) return;
setSelectedOptionKey(item.key as FundsOptionKey);
}}
fullWidth
>
{fundsOptions.map(({ key, label }) => (
{Object.values(fundsOptionsMap).map(({ key, label }) => (
<SelectOption key={key} optionKey={key} label={label}>
{label}
</SelectOption>
Expand Down
13 changes: 10 additions & 3 deletions templates/chain-template/components/contract/ExecuteTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useChainStore } from '@/contexts';
import { useChain } from '@cosmos-kit/react';
import { CopyButton } from './CopyButton';
import { validateJson } from '@/utils';
import { Coin } from '@cosmjs/amino';

type ExecuteTabProps = {
show: boolean;
Expand All @@ -21,6 +22,8 @@ export const ExecuteTab = ({ show }: ExecuteTabProps) => {
const [contractAddress, setContractAddress] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [msg, setMsg] = useState('');
const [funds, setFunds] = useState<Coin[]>([]);
const [isAssetListJsonValid, setIsAssetListJsonValid] = useState(true);

const { selectedChain } = useChainStore();
const { address, connect } = useChain(selectedChain);
Expand All @@ -35,15 +38,16 @@ export const ExecuteTab = ({ show }: ExecuteTabProps) => {
address,
contractAddress,
fee: { amount: [], gas: '200000' },
funds: [],
funds,
onTxSucceed: () => setIsLoading(false),
onTxFailed: () => setIsLoading(false),
});
};

const isMsgValid = validateJson(msg) === null;

const isExecuteButtonDisabled = !contractAddress || !isMsgValid || !address;
const isExecuteButtonDisabled =
!contractAddress || !isMsgValid || !address || !isAssetListJsonValid;

return (
<Box display={show ? 'block' : 'none'}>
Expand Down Expand Up @@ -97,7 +101,10 @@ export const ExecuteTab = ({ show }: ExecuteTabProps) => {
</Box>

<Box flex="1">
<AttachFundsSelect />
<AttachFundsSelect
setFunds={setFunds}
setIsAssetListJsonValid={setIsAssetListJsonValid}
/>
</Box>
</Box>

Expand Down
4 changes: 3 additions & 1 deletion templates/chain-template/components/contract/JsonInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ interface JsonState {
type JsonInputProps = {
value: string;
setValue: (value: string) => void;
minLines?: number;
} & Pick<BoxProps, 'width' | 'height' | 'mt' | 'mb'>;

export const JsonInput = ({
value = '',
setValue,
minLines = MIN_LINES,
...rest
}: JsonInputProps) => {
const [jsonState, setJsonState] = useState<JsonState>({ state: 'empty' });
Expand All @@ -83,7 +85,7 @@ export const JsonInput = ({
const isValidJson = validateJson(value) === null;

const lines = useMemo(() => {
return Math.max(countJsonLines(value), MIN_LINES);
return Math.max(countJsonLines(value), minLines);
}, [value]);

return (
Expand Down
120 changes: 56 additions & 64 deletions templates/chain-template/components/contract/SelectAssetContent.tsx
Original file line number Diff line number Diff line change
@@ -1,77 +1,69 @@
import { Dispatch, SetStateAction, useMemo } from 'react';
import { assets } from 'chain-registry';

import {
Avatar,
Box,
NumberField,
Select,
SelectOption,
Stack,
Text,
} from '@interchain-ui/react';
import osmoAssets from 'chain-registry/testnet/osmosistestnet/assets';
import { useState } from 'react';
defaultSelectedAsset,
SelectedAssetWithAmount,
} from './AttachFundsSelect';
import { Button } from '@/components';
import { HiOutlineTrash } from 'react-icons/hi';
import { SelectAssetItem } from './SelectAssetItem';
import { useChainStore } from '@/contexts';

const assets = osmoAssets.assets.slice(0, 2);
type SelectAssetContentProps = {
selectedAssetsWithAmount: SelectedAssetWithAmount[];
setSelectedAssetsWithAmount: Dispatch<
SetStateAction<SelectedAssetWithAmount[]>
>;
};

type SelectAssetContentProps = {};
export const SelectAssetContent = ({
selectedAssetsWithAmount,
setSelectedAssetsWithAmount,
}: SelectAssetContentProps) => {
const { selectedChain } = useChainStore();

export const SelectAssetContent = ({}: SelectAssetContentProps) => {
const [selectedDenoms, setSelectedDenoms] = useState<string[]>([]);
const nativeAssets = useMemo(() => {
return (
assets
.find(({ chain_name }) => chain_name === selectedChain)
?.assets.filter(
({ type_asset, base }) =>
type_asset !== 'cw20' &&
type_asset !== 'ics20' &&
!base.startsWith('factory/')
) || []
);
}, [selectedChain]);

return (
<>
<Box
display="flex"
justifyContent="space-between"
alignItems="flex-end"
gap="10px"
my="20px"
>
<Box>
<Text fontSize="16px" fontWeight="400" attributes={{ mb: '10px' }}>
Asset
</Text>
<Select
width="140px"
onSelectItem={(item) => {
if (!item) return;
setSelectedDenoms((prev) => [...prev, item.key]);
}}
placeholder="Select"
>
{assets.map(({ base, symbol, logo_URIs }) => (
<SelectOption key={base} optionKey={base} label={symbol}>
<Stack space="10px" attributes={{ alignItems: 'center' }}>
<Avatar
name={symbol}
src={logo_URIs?.svg || logo_URIs?.png}
size="xs"
/>
<Text fontSize="16px" fontWeight="500">
{symbol}
</Text>
</Stack>
</SelectOption>
))}
</Select>
</Box>
const selectedSymbols = selectedAssetsWithAmount
.map(({ asset }) => asset?.symbol)
.filter(Boolean) as string[];

<Box flex="1">
<Text fontSize="16px" fontWeight="400" attributes={{ mb: '10px' }}>
Amount
</Text>
<NumberField placeholder="Enter amount" />
</Box>
const handleAddAssets = () => {
setSelectedAssetsWithAmount((prev) => [...prev, defaultSelectedAsset]);
};

<Button
px="10px"
color="$text"
leftIcon={<HiOutlineTrash size="20px" />}
return (
<>
{selectedAssetsWithAmount.map((assetWithAmount, index) => (
<SelectAssetItem
key={assetWithAmount.asset?.symbol || index}
itemIndex={index}
allAssets={nativeAssets}
selectedSymbols={selectedSymbols}
disableTrashButton={selectedAssetsWithAmount.length === 1}
selectedAssetWithAmount={assetWithAmount}
setSelectedAssetsWithAmount={setSelectedAssetsWithAmount}
/>
</Box>
))}

<Button color="$text">Add More Assets</Button>
<Button
color="$text"
onClick={handleAddAssets}
disabled={selectedAssetsWithAmount.length === nativeAssets.length}
>
Add More Assets
</Button>
</>
);
};
Loading

0 comments on commit e052575

Please sign in to comment.