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

feat: network switch within action response #447

Merged
merged 26 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
79b68ac
feat: arbitrum deposit eth
Sabnock01 Jul 26, 2023
7628bb6
feat: add withdraw eth integration
Sabnock01 Jul 26, 2023
f6cc848
Merge branch 'dev' into sabnock/arbitrum-eth
marcomariscal Jul 27, 2023
4631f3b
feat: arbi deposit/withdraw eth
marcomariscal Jul 27, 2023
7c2cc16
chore: arbi fork data
marcomariscal Jul 27, 2023
8ad336f
feat: arbi fork data
marcomariscal Jul 27, 2023
5da146c
feat(wip): try to handle depositing erc20
marcomariscal Jul 27, 2023
04b661c
package: arbi sdk
marcomariscal Jul 27, 2023
8361c27
chore: remove unused
marcomariscal Jul 27, 2023
5f2f203
Merge branch 'dev' into integration/arbitrum-bridge
marcomariscal Aug 9, 2023
27fa37b
Merge branch 'dev' into integration/arbitrum-bridge
marcomariscal Sep 1, 2023
a40306c
fix: imports
marcomariscal Sep 1, 2023
3d1f82a
fix: tokenSymbol to upper
marcomariscal Sep 1, 2023
877f821
chore: remove unused
marcomariscal Sep 1, 2023
44564c3
feat: handle erc20 (only works from non-forks)
marcomariscal Sep 1, 2023
8d9a712
fix: sendParam to ethBal check
marcomariscal Sep 1, 2023
e65e81e
fix: check
marcomariscal Sep 1, 2023
e298316
feat: withdraw erc20 (wip)
marcomariscal Sep 1, 2023
9d31a6a
Merge branch 'dev' into integration/arbitrum-bridge
marcomariscal Sep 8, 2023
7de7ca0
Merge branch 'dev' into integration/arbitrum-bridge
marcomariscal Sep 12, 2023
3c6ecb7
feat: prelim implementation of handling network switchin within Actio…
marcomariscal Sep 13, 2023
3516ec7
Merge branch 'integration/arbitrum-bridge' into feat/network-switch-a…
marcomariscal Sep 13, 2023
2ad8c79
feat: parse out chain handling logic from tx/approval logic
marcomariscal Sep 13, 2023
28cf1ac
Merge branch 'dev' into feat/network-switch-action-response
marcomariscal Sep 18, 2023
288ed02
feat: get the from chain id and pass that to ActionResponse
marcomariscal Sep 18, 2023
107180a
Merge branch 'dev' into feat/network-switch-action-response
brucedonovan Sep 26, 2023
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
1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const nextConfig = {
reactStrictMode: true,
env: {
NEXT_PUBLIC_ENV_TAG: process.env.NEXT_PUBLIC_ENV_TAG,
ARBITRUM_FORK_URL: process.env.ARBITRUM_FORK_URL,
NEXT_PUBLIC_TENDERLY_FORK_ID: process.env.NEXT_PUBLIC_TENDERLY_FORK_ID,
},
images: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"test-storybook": "test-storybook"
},
"dependencies": {
"@arbitrum/sdk": "^3.1.6",
"@center-inc/react": "^1.1.13",
"@headlessui/react": "^1.7.15",
"@heroicons/react": "^2.0.14",
Expand Down
36 changes: 30 additions & 6 deletions src/components/cactiComponents/ActionResponse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { AddressZero } from '@ethersproject/constants';
import { CheckCircleIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { BigNumber, UnsignedTransaction } from 'ethers';
import { formatUnits } from 'ethers/lib/utils.js';
import { formatEther, formatUnits } from 'ethers/lib/utils.js';
import tw from 'tailwind-styled-components';
import { useAccount } from 'wagmi';
import { useAccount, useNetwork, useSwitchNetwork } from 'wagmi';
import useToken from '@/hooks/useToken';
import { cleanValue } from '@/utils';
import { ActionStepper } from './ActionStepper';
Expand Down Expand Up @@ -60,6 +60,7 @@ export type ActionResponseProps = {
stepper?: boolean;
onSuccess?: (receipt?: TransactionReceipt) => void;
onError?: (receipt?: TransactionReceipt) => void;
chainId?: number;
};

/**
Expand All @@ -77,8 +78,13 @@ export const ActionResponse = ({
skipBalanceCheck,
onSuccess,
onError,
chainId = 1,
}: ActionResponseProps) => {
const { address } = useAccount();
const { chain } = useNetwork();
const { chains, switchNetworkAsync } = useSwitchNetwork();
const wrongChain = !!chainId && chain?.id !== chainId;

const _approvalParams = useMemo<ApprovalBasicParams>(
() =>
approvalParams || {
Expand Down Expand Up @@ -138,17 +144,17 @@ export const ActionResponse = ({

// explicitly showing approvalParams === undefined for clarity - as oppposed to !approvalParams
if (_approvalParams === undefined) return setHasEnoughBalance(true);
if (sendParams?.value! >= ethBal!) {
if (BigNumber.from(sendParams?.value || '0').gt(BigNumber.from(ethBal || '0')))
return setHasEnoughBalance(false);
}

// check approval token balance
if (balance && _approvalParams?.approvalAmount)
if (balance && _approvalParams?.approvalAmount) {
setHasEnoughBalance(balance.gte(_approvalParams.approvalAmount));
}
}, [_approvalParams, balance, ethBal, sendParams?.value, skipBalanceCheck]);

/**
* BUTTON FLOW:
* BUTTON FLOW for approvals and transactions:
* Update all the local states on tx/approval status changes.
**/
useEffect(() => {
Expand Down Expand Up @@ -251,8 +257,26 @@ export const ActionResponse = ({
submitTx,
token?.symbol,
disabled,
wrongChain,
chains,
switchNetworkAsync,
chainId,
]);

// handle chain changes if on the wrong chain
useEffect(() => {
if (!wrongChain) return;

const correctChainName = chains.find((c) => c.id === chainId)?.name;
setButtonLabel(
correctChainName && switchNetworkAsync ? `Switch to ${correctChainName}` : `Unsupported Chain`
);
return (
switchNetworkAsync &&
setAction({ name: 'switch', fn: async () => await switchNetworkAsync(chainId) })
);
}, [chainId, chains, state, switchNetworkAsync, wrongChain]);

/* Set the styling based on the state (Note: always diasbled if 'disabled' from props) */
const extraStyle = stylingByState[disabled ? ActionResponseState.DISABLED : state];

Expand Down
17 changes: 12 additions & 5 deletions src/components/current/MessageTranslator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { parseMessage } from '@/utils/parse-message';
import { ErrorResponse, TextResponse } from '../cactiComponents';
import { ImageVariant } from '../cactiComponents/ImageResponse';
import { TableResponse } from '../cactiComponents/TableResponse';
import ArbitrumDeposit from '../experimental_/widgets/arbitrum/ArbitrumDeposit';
import ArbitrumWithdraw from '../experimental_/widgets/arbitrum/ArbitrumWithdraw';
import { Widgetize } from '../legacy/legacyComponents/MessageTranslator';
import Avatar from '../shared/Avatar';
import { FeedbackButton } from './FeedbackButton';
Expand Down Expand Up @@ -230,7 +232,6 @@ export const Widget = (props: WidgetProps) => {
assetsToShow={6}
/>
);

widgets.set(
'yield-protocol-lend',
<YieldProtocolLend
Expand Down Expand Up @@ -283,23 +284,20 @@ export const Widget = (props: WidgetProps) => {
'zksync-deposit',
<ZKSyncDeposit tokenSymbol={parsedArgs[0]} userAmount={parsedArgs[1]} />
);

widgets.set(
'zksync-withdraw',
<ZKSyncWithdraw tokenSymbol={parsedArgs[0]} userAmount={parsedArgs[1]} />
);
widgets.set('stake-sfrxeth', <StakeSfrxEth receiver={parsedArgs[0]} value={parsedArgs[1]} />);
widgets.set(
'liquity-borrow',
<LiquityBorrow borrowAmount={parsedArgs[0]} collateralAmount={parsedArgs[1]} />
);
widgets.set('liquity-close', <LiquityClose />);

widgets.set('deposit-eth-lido', <LidoDeposit inputString={parsedArgs} />);
widgets.set('withdraw-eth-lido', <LidoWithdraw inputString={parsedArgs} />);

widgets.set('deposit-eth-reth', <RethDeposit inputString={parsedArgs} />);
widgets.set('withdraw-eth-reth', <RethWithdraw inputString={parsedArgs} />);
widgets.set('stake-sfrxeth', <StakeSfrxEth receiver={parsedArgs[0]} value={parsedArgs[1]} />);

widgets.set('savings-dai-deposit', <DepositDSR depositAmount={parsedArgs[0]} />);

Expand All @@ -325,6 +323,15 @@ export const Widget = (props: WidgetProps) => {
/>
);

widgets.set(
'arbitrum-deposit',
<ArbitrumDeposit tokenSymbol={parsedArgs[0]} amtString={parsedArgs[1]} />
);
widgets.set(
'arbitrum-withdraw',
<ArbitrumWithdraw tokenSymbol={parsedArgs[0]} amtString={parsedArgs[1]} />
);

/* If available, return the widget in the widgets map */
if (widgets.has(fnName)) {
return widgets.get(fnName)!;
Expand Down
6 changes: 6 additions & 0 deletions src/components/current/widgets/hop/HopBridge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react';
import { Hop } from '@hop-protocol/sdk';
import { config } from '@hop-protocol/sdk/dist/src/config';
import { CanonicalToken, ChainSlug } from '@hop-protocol/sdk/dist/src/constants';
import { Interface, UnsignedTransaction } from 'ethers/lib/utils';
import { erc20ABI } from 'wagmi';
Expand Down Expand Up @@ -100,6 +101,10 @@ const HopBridge = ({ inputString, tokenSymbol, toChain, fromChain }: HopBridgePr
})();
}, [_fromChain, input?.value, _toChain, tokenIn?.address, tokenSymbol]); // TODO signer is causing infinite loop

// find the chain id of the from chain using the hop sdk config
// using the `fromSlug`(returns the Chain) property of Chain doesn't return the chain id, so using the below methodology instead
const fromChainId = config.chains.mainnet[_fromChain].chainId as number | undefined;

return (
<>
<HeaderResponse
Expand All @@ -115,6 +120,7 @@ const HopBridge = ({ inputString, tokenSymbol, toChain, fromChain }: HopBridgePr
approvalParams={approvalParams}
txParams={undefined}
sendParams={sendParams}
chainId={fromChainId}
/>
</>
);
Expand Down
119 changes: 119 additions & 0 deletions src/components/experimental_/widgets/arbitrum/ArbitrumDeposit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { useEffect, useMemo, useState } from 'react';
import { Erc20Bridger, getL2Network } from '@arbitrum/sdk';
import { UnsignedTransaction } from 'ethers';
import { Interface } from 'ethers/lib/utils.js';
import { erc20ABI, useAccount, usePrepareContractWrite, useProvider } from 'wagmi';
import { ActionResponse, HeaderResponse } from '@/components/cactiComponents';
import { ApprovalBasicParams } from '@/components/cactiComponents/hooks/useApproval';
import useInput from '@/hooks/useInput';
import useToken from '@/hooks/useToken';
import Inbox from './abi/Inbox';

interface ArbitrumDepositETHProps {
tokenSymbol: string;
amtString: string;
}

// the proxy contract address, not the actual implementation address
const INBOX_CONTRACT_ADDRESS = '0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f';
export const L2_CHAIN_ID = 42161;

const ArbitrumDeposit = ({ tokenSymbol, amtString }: ArbitrumDepositETHProps) => {
const l1Provider = useProvider({ chainId: 1 });
const l2Provider = useProvider({ chainId: L2_CHAIN_ID });
const { data: token, isETH } = useToken(tokenSymbol.toUpperCase());
const { address: account } = useAccount();
const amount = useInput(amtString, token?.symbol!);

const [erc20ApprovalParams, setErc20ApprovalParams] = useState<ApprovalBasicParams>();
const [erc20SendParams, setErc20SendParams] = useState<UnsignedTransaction>();
const [error, setError] = useState('');

const { data: depositEth } = usePrepareContractWrite({
address: INBOX_CONTRACT_ADDRESS,
abi: Inbox,
functionName: 'depositEth',
overrides: { value: amount?.value },
enabled: isETH,
});

// handle erc20 (wip)
useEffect(() => {
(async () => {
if (isETH) return;
if (!l1Provider) return setError('No L1 provider detected');
if (!l2Provider) return setError('No L2 provider detected');
if (!token?.address) return setError('No token address detected');
if (!amount?.value) return setError('No amount detected');
if (!account) return setError('No account detected');

const arbitrumOne = await getL2Network(
L2_CHAIN_ID /** <-- chain id of target Arbitrum chain */
);

const erc20Bridger = new Erc20Bridger(arbitrumOne);

// get approval params
try {
const { data } = await erc20Bridger.getApproveTokenRequest({
l1Provider,
erc20L1Address: token.address,
amount: amount.value,
});

// get the spender address
const iface = new Interface(erc20ABI);
const parsed = iface.parseTransaction({ data: data.toString() });
const spender = parsed.args[0];

setErc20ApprovalParams({
approvalAmount: amount.value,
tokenAddress: token.address,
spender,
skipApproval: false,
});
} catch (error) {
console.error(error);
setError('There was an error initiating the transaction');
}

try {
// get the send params
const req = await erc20Bridger.getDepositRequest({
l1Provider,
l2Provider,
erc20L1Address: token.address,
amount: amount.value,
from: account,
});

setErc20SendParams(req.txRequest);
} catch (error) {
console.error(error);
setError('There was an error initiating the transaction');
}
})();
}, [account, amount?.value, isETH, l1Provider, l2Provider, token?.address]);

const sendParams = useMemo(
() => (isETH ? depositEth?.request : erc20SendParams),
[depositEth?.request, erc20SendParams, isETH]
);

return (
<>
<HeaderResponse
text={`Deposit ${amount?.formatted} ${tokenSymbol} to Arbitrum`}
projectName="arbitrum"
/>
<ActionResponse
label={`Deposit ${amount?.formatted || ''} ${tokenSymbol} to Arbitrum`}
txParams={undefined}
approvalParams={erc20ApprovalParams}
sendParams={sendParams}
/>
</>
);
};

export default ArbitrumDeposit;
88 changes: 88 additions & 0 deletions src/components/experimental_/widgets/arbitrum/ArbitrumWithdraw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { useEffect, useMemo, useState } from 'react';
import { Erc20Bridger, getL1Network, getL2Network } from '@arbitrum/sdk';
import { UnsignedTransaction } from 'ethers';
import { useAccount, usePrepareContractWrite, useProvider } from 'wagmi';
import { ActionResponse, HeaderResponse } from '@/components/cactiComponents';
import { ApprovalBasicParams } from '@/components/cactiComponents/hooks/useApproval';
import useInput from '@/hooks/useInput';
import useToken from '@/hooks/useToken';
import { L2_CHAIN_ID } from './ArbitrumDeposit';
import ArbSys from './abi/ArbSys';

interface ArbitrumWithdrawProps {
tokenSymbol: string;
amtString: string;
}

const ARBSYS_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000000064';

const ArbitrumWithdraw = ({ tokenSymbol, amtString }: ArbitrumWithdrawProps) => {
const l1Provider = useProvider({ chainId: 1 });
const l2Provider = useProvider({ chainId: L2_CHAIN_ID });
const { data: token, isETH } = useToken(tokenSymbol);
const amount = useInput(amtString, token?.symbol!);
const { address: account } = useAccount();
const [erc20ApprovalParams, setErc20ApprovalParams] = useState<ApprovalBasicParams>();
const [erc20SendParams, setErc20SendParams] = useState<UnsignedTransaction>();

const { data: withdrawEth } = usePrepareContractWrite({
address: ARBSYS_CONTRACT_ADDRESS,
abi: ArbSys,
functionName: 'withdrawEth',
args: [account!],
overrides: { value: amount?.value },
});

// handle erc20 withdraw
useEffect(() => {
(async () => {
if (isETH) return;
if (!token?.address) return;
if (!amount?.value) return;
if (!account) return;

const arbitrumOne = await getL2Network(L2_CHAIN_ID /** <-- chain id of Arbitrum chain */);

const erc20Bridger = new Erc20Bridger(arbitrumOne);

try {
const req = await erc20Bridger.getWithdrawalRequest({
erc20l1Address: token.address,
amount: amount.value,
destinationAddress: account,
from: account,
});

setErc20SendParams(req.txRequest);
} catch (e) {
console.error(e);
console.error('Error initiating transaction');
}
})();
}, [account, amount?.value, isETH, token?.address]);

if (!token) <>token not supported</>;
if (!amount?.value) <>amount specified is invalid</>;

const sendParams = useMemo(
() => (isETH ? withdrawEth?.request : erc20SendParams),
[erc20SendParams, isETH, withdrawEth?.request]
);

return (
<>
<HeaderResponse
text={`Withdraw ${amount?.formatted} ${tokenSymbol} from Arbitrum`}
projectName="arbitrum"
/>
<ActionResponse
label={`Withdraw ${amount?.formatted} ${tokenSymbol} from Arbitrum`}
txParams={undefined}
approvalParams={undefined}
sendParams={sendParams}
/>
</>
);
};

export default ArbitrumWithdraw;
Loading