Skip to content

Commit

Permalink
feat: AgentContextProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
veeso committed Feb 25, 2024
1 parent 9b167c0 commit 1003165
Show file tree
Hide file tree
Showing 9 changed files with 953 additions and 8 deletions.
3 changes: 2 additions & 1 deletion src/ekoke_erc20_swap_frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@
"prettier:check": "prettier --config .prettierrc --check src/"
},
"dependencies": {
"@dfinity/agent": "^1.0.1",
"@dfinity/candid": "^1.0.1",
"@dfinity/principal": "^1.0.1",
"metamask-react": "^2.7.0",
"react": "^18.2",
"react-dom": "^18.2",
"react-feather": "^2.0",
"react-helmet": "^6.1",
"react-ic-wallet": "^0.1.0",
"react-ic-wallet": "^0.1.1",
"web3": "^4.5.0"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as React from 'react';
import Summary from './pages/Summary';

import SwapErc20ToIcrc from './pages/SwapErc20ToIcrc';
import SwapIcrcToErc20 from './pages/SwapIcrcToErc20';
import AgentContextProvider from '../../ic/AgentContext';

export enum Page {
Summary,
Expand All @@ -19,6 +21,13 @@ const ConnectedPage = () => {
if (page === Page.Erc20ToIcrc) {
return <SwapErc20ToIcrc onSwitchPage={setPage} />;
}
if (page === Page.IcrcToErc20) {
return (
<AgentContextProvider>
<SwapIcrcToErc20 onSwitchPage={setPage} />
</AgentContextProvider>
);
}

return <Summary onSwitchPage={setPage} />;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import * as Icon from 'react-feather';
import { useConnectedMetaMask } from 'metamask-react';
import { useConnectedIcWallet } from 'react-ic-wallet';
import { Principal } from '@dfinity/principal';

import Web3Client from '../../../web3/Web3Client';
import { ChainId } from '../../MetamaskConnect';
Expand All @@ -16,7 +17,6 @@ import EthereumWhite from '../../svg/EthereumWhite';
import Input from '../../reusable/Input';
import { e8sToEkoke, validatePrincipal } from '../../../utils';
import Alerts from '../../reusable/Alerts';
import { Principal } from '@dfinity/principal';
import Paragraph from '../../reusable/Paragraph';

const SwapErc20ToIcrc = ({ onSwitchPage }: PageProps) => {
Expand Down Expand Up @@ -153,7 +153,7 @@ const SwapErc20ToIcrc = ({ onSwitchPage }: PageProps) => {
value={amount}
placeholder="10000"
type="number"
validationMessage="Please enter a valid principal."
validationMessage="Please enter a valid amount."
validate={validateUserAmount}
onChange={onAmountChange}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import * as React from 'react';
import * as Icon from 'react-feather';
import { useConnectedMetaMask } from 'metamask-react';
import { useConnectedIcWallet } from 'react-ic-wallet';

import { Page, PageProps } from '../ConnectedPage';
import { e8sToEkoke, validateEthAddress } from '../../../utils';
import Container from '../../reusable/Container';
import Link from '../../reusable/Link';
import Ethereum from '../../svg/Ethereum';
import Heading from '../../reusable/Heading';
import InternetComputer from '../../svg/InternetComputer';
import Input from '../../reusable/Input';
import Alerts from '../../reusable/Alerts';
import Paragraph from '../../reusable/Paragraph';
import Button from '../../reusable/Button';

const SwapIcrcToErc20 = ({ onSwitchPage }: PageProps) => {
const { account } = useConnectedMetaMask();
const { principal } = useConnectedIcWallet();

const [recipientAddress, setRecipientAddress] =
React.useState<string>(account);
const [amount, setAmount] = React.useState<string>('');
const [userBalance, setUserBalance] = React.useState<BigInt>();
const [processing, setProcessing] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | null>(null);
const [swapTxHash, setSwapTxHash] = React.useState<string | false>(false);

const onRecipientAddressChanged = (
e: React.ChangeEvent<HTMLInputElement>,
) => {
setRecipientAddress(e.target.value);
};

const onAmountChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setAmount(e.target.value);
};

const validateUserAmount = (
amount: string | number | readonly string[] | undefined,
): boolean => {
if (typeof amount !== 'string') return false;

if (isNaN(parseInt(amount))) {
return false;
}

if (userBalance === undefined) return true;

const userAmount = BigInt(amount);
return userAmount <= userBalance.valueOf();
};

const onSwap = () => {
setProcessing(true);

// check if the user has enough balance
if (!validateUserAmount(amount)) {
setProcessing(false);
setError('Insufficient balance.');
return;
}
if (!validateEthAddress(recipientAddress)) {
setProcessing(false);
setError('Invalid address.');
return;
}

const numAmount = BigInt(amount);
};

const disabled = !recipientAddress || !amount || processing;

return (
<Container.FlexCols className="items-center justify-center">
<Container.Card className="px-12 sm:px-4">
<Container.FlexResponsiveRow className="items-center sm:items-start justify-between sm:justify-start gap-8">
<Container.Container className="flex-0">
{!processing && (
<Link.IconLink
className="hover:cursor-pointer"
onClick={() => onSwitchPage(Page.Summary)}
>
<Icon.ArrowLeft className="mr-2 inline" />
Back
</Link.IconLink>
)}
</Container.Container>
<Container.FlexRow className="flex-1 items-center justify-center gap-4">
<InternetComputer className="h-[32px] sm:hidden" />
<Heading.H1 className="sm:text-lg">Swap ICRC to ERC20</Heading.H1>
<Ethereum className="w-[32px] sm:hidden" />
</Container.FlexRow>
</Container.FlexResponsiveRow>
{userBalance && (
<Container.Container className="py-4 text-text">
<span>Your EKOKE ICRC balance: {e8sToEkoke(userBalance)}</span>
</Container.Container>
)}
<Container.FlexCols className="gap-4">
<Container.Container>
<Input.IconInput
className="pl-[40px] sm:pl-[8px]"
icon={<Ethereum className="h-[20px] sm:hidden" />}
label="Recipient Ethereum Address"
id="recipient-eth-address"
placeholder={account}
value={recipientAddress}
validate={validateEthAddress}
validationMessage="Please enter a valid ethereum address."
onChange={onRecipientAddressChanged}
/>
</Container.Container>
<Container.Container>
<Input.IconInput
className="pl-[40px]"
icon={<Icon.DollarSign size={20} className="inline" />}
label="Amount"
id="swap-amount"
value={amount}
placeholder="10000"
type="number"
validationMessage="Please enter a valid amount."
validate={validateUserAmount}
onChange={onAmountChange}
/>
</Container.Container>
<Container.FlexCols className="items-center justify-center gap-8 sm:gap-2">
{error && (
<Alerts.Danger>
<Paragraph.Default className="!text-left">
{error}
</Paragraph.Default>
</Alerts.Danger>
)}
{swapTxHash && (
<Alerts.Info>
<Paragraph.Default className="!text-left">
Swap successful! See your transaction{' '}
<Link.Paragraph
href={`https://etherscan.io/tx/${swapTxHash}`}
target="_blank"
>
{swapTxHash}
</Link.Paragraph>
</Paragraph.Default>
</Alerts.Info>
)}
<Button.Cta onClick={onSwap} disabled={disabled}>
{processing ? (
<Icon.Loader className="inline mr-2 animate-spin" size={20} />
) : (
<InternetComputer className="inline mr-2 h-[20px]" />
)}
<span>Swap</span>
</Button.Cta>
</Container.FlexCols>
</Container.FlexCols>
</Container.Card>
</Container.FlexCols>
);
};

export default SwapIcrcToErc20;
57 changes: 57 additions & 0 deletions src/ekoke_erc20_swap_frontend/src/js/ic/AgentContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from 'react';
import { ActorSubclass } from '@dfinity/agent';
import { useIcWallet } from 'react-ic-wallet';

import {
EkokeLedger,
idlFactory as ekokeLedgerIdlFactory,
} from './ekoke-ledger.did';
import {
EkokeErc20Swap,
idlFactory as ekokeErc20SwapIdlFactory,
} from './ekoke-erc20-swap.did';

const EKOKE_ERC20_SWAP_CANISTER_ID = '';
const EKOKE_LEDGER_CANISTER_ID = '';

interface Context {
ekokeErc20Swap?: ActorSubclass<EkokeErc20Swap>;
ekokeLedger?: ActorSubclass<EkokeLedger>;
}

export const AgentContext = React.createContext<Context>({});

const AgentContextProvider = ({ children }: { children?: React.ReactNode }) => {
const [ekokeErc20Swap, setEkokeErc20Swap] =
React.useState<ActorSubclass<EkokeErc20Swap>>();
const [ekokeLedger, setEkokeLedger] =
React.useState<ActorSubclass<EkokeLedger>>();

const { createActor, status } = useIcWallet();

React.useEffect(() => {
if (status === 'connected') {
createActor(EKOKE_ERC20_SWAP_CANISTER_ID, ekokeErc20SwapIdlFactory).then(
(actor) => {
setEkokeErc20Swap(actor as ActorSubclass<EkokeErc20Swap>);
},
);
createActor(EKOKE_LEDGER_CANISTER_ID, ekokeLedgerIdlFactory).then(
(actor) => {
setEkokeLedger(actor as ActorSubclass<EkokeLedger>);
},
);
} else {
setEkokeErc20Swap(undefined);
setEkokeLedger(undefined);
}
}, [status]);

return (
<AgentContext.Provider value={{ ekokeErc20Swap, ekokeLedger }}>
{children}
</AgentContext.Provider>
);
};

export default AgentContextProvider;
Loading

0 comments on commit 1003165

Please sign in to comment.