Skip to content

Commit

Permalink
relay deposit status, add error icon and logging
Browse files Browse the repository at this point in the history
  • Loading branch information
holic committed Apr 30, 2024
1 parent 0e75745 commit 0a300ec
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 100 deletions.
2 changes: 1 addition & 1 deletion packages/account-kit/playground/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useLocalStorage } from "usehooks-ts";
export function App() {
const { openAccountModal } = useAccountModal();

const [openModal, setOpenModal] = useLocalStorage<boolean>("mud:accountKitPlayground:openModalOnMount", true);
const [openModal, setOpenModal] = useLocalStorage<boolean>("mud:accountKitPlayground:openModalOnMount", false);

useEffect(() => {
if (openModal) {
Expand Down
16 changes: 16 additions & 0 deletions packages/account-kit/src/icons/WarningIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable max-len */
import { IconSVG, Props } from "./IconSVG";

export function WarningIcon(props: Props) {
return (
<IconSVG fill="none" {...props}>
<path
d="M12 9V11M12 15H12.01M5.07183 19H18.9282C20.4678 19 21.4301 17.3333 20.6603 16L13.7321 4C12.9623 2.66667 11.0378 2.66667 10.268 4L3.33978 16C2.56998 17.3333 3.53223 19 5.07183 19Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</IconSVG>
);
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
import { useQuery } from "@tanstack/react-query";
import { BridgeTransaction } from "./common";
import { TransactionStatus } from "./TransactionStatus";
import { DepositStatus } from "./DepositStatus";
import { formatBalance } from "./formatBalance";
import { useChains } from "wagmi";
import { BridgeDeposit } from "./useDeposits";

export type Props = BridgeTransaction;
export type Props = BridgeDeposit;

export function BridgeTransactionStatus({
export function BridgeDepositStatus({
amount,
chainL1,
chainL2,
chainL1Id,
chainL2Id,
hashL1,
receiptL1: receiptL1Promise,
receiptL2: receiptL2Promise,
start,
estimatedTime,
}: Props) {
const chains = useChains();
const chainL1 = chains.find((chain) => chain.id === chainL1Id)!;
const chainL2 = chains.find((chain) => chain.id === chainL2Id)!;

const receiptL1 = useQuery({
queryKey: ["bridgeTransactionStatus", "L1", hashL1],
queryKey: ["bridgeDepositStatus", "L1", hashL1],
queryFn: () => receiptL1Promise,
});

const receiptL2 = useQuery({
queryKey: ["bridgeTransactionStatus", "L2", hashL1],
queryKey: ["bridgeDepositStatus", "L2", hashL1],
queryFn: () => receiptL2Promise,
});

return (
<TransactionStatus
<DepositStatus
status={receiptL1.status === "success" ? receiptL2.status : receiptL1.status}
progress={{
duration: estimatedTime,
Expand Down Expand Up @@ -126,6 +131,6 @@ export function BridgeTransactionStatus({
</>
);
})()}
</TransactionStatus>
</DepositStatus>
);
}
22 changes: 13 additions & 9 deletions packages/account-kit/src/steps/deposit/DepositContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { DepositMethod } from "./common";
import { useSourceChains } from "./useSourceChains";
import { useConfig } from "../../AccountKitConfigProvider";
import { DepositMethodForm } from "./DepositMethodForm";
import { useDepositTransactions } from "./useDepoitTransactions";
import { BridgeTransactionStatus } from "./BridgeTransactionStatus";
import { useDeposits } from "./useDeposits";
import { BridgeDepositStatus } from "./BridgeDepositStatus";
import { assertExhaustive } from "@latticexyz/common/utils";
import { RelayDepositStatus } from "./RelayDepositStatus";

export function DepositContent() {
const { chainId: appChainId } = useConfig();
Expand All @@ -28,7 +29,7 @@ export function DepositContent() {
? selectedDepositMethod
: sourceChain.depositMethods[0];

const { transactions } = useDepositTransactions();
const { deposits } = useDeposits();

return (
<>
Expand All @@ -50,15 +51,18 @@ export function DepositContent() {
depositMethod={depositMethod}
setDepositMethod={setSelectedDepositMethod}
/>
{/* TODO: make transactions dismissable */}
{transactions.length > 0 ? (
{/* TODO: make deposits dismissable */}
{deposits.length > 0 ? (
<div className="flex flex-col gap-1 px-5">
{transactions.map((transaction) => {
switch (transaction.type) {
{deposits.map((deposit) => {
switch (deposit.type) {
case "bridge":
return <BridgeTransactionStatus key={transaction.uid} {...transaction} />;
return <BridgeDepositStatus key={deposit.uid} {...deposit} />;
case "relay":
return <RelayDepositStatus key={deposit.uid} {...deposit} />;
default:
assertExhaustive(transaction.type);
// TODO: wtf TS y u no narrow
assertExhaustive(deposit.type);
}
})}
</div>
Expand Down
27 changes: 24 additions & 3 deletions packages/account-kit/src/steps/deposit/DepositForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DepositMethod, SourceChain } from "./common";
import { ReactNode, useEffect, useRef } from "react";
import { SubmitButton } from "./SubmitButton";
import { useIsMounted } from "usehooks-ts";
import { WarningIcon } from "../../icons/WarningIcon";

export const DEFAULT_DEPOSIT_AMOUNT = 0.005;

Expand Down Expand Up @@ -78,6 +79,18 @@ export function DepositForm({
}
}, [isComplete, queryClient, resetStep]);

useEffect(() => {
if (balance.error) {
console.error("Failed to get balance for", userAddress, "on", sourceChain.id, balance.error);
}
}, [balance.error, sourceChain.id, userAddress]);

useEffect(() => {
if (estimatedFee.error) {
console.error("Failed to estimate fee for", selectedMethod, "deposit from", sourceChain.id, estimatedFee.error);
}
}, [estimatedFee.error, selectedMethod, sourceChain.id, userAddress]);

return (
<form
className="flex flex-col px-5 gap-5"
Expand Down Expand Up @@ -146,17 +159,25 @@ export function DepositForm({
>
<dt>Available to deposit</dt>
<dd>
{balance.data ? (
{balance.isSuccess ? (
<>{formatBalance(balance.data.value)} Ξ</>
) : balance.isError ? (
<span title={String(balance.error)}>
<WarningIcon className="inline-block text-amber-500" />
</span>
) : balance.isLoading ? (
<PendingIcon className="inline-block text-xs" />
) : null}
</dd>
<dt>Estimated fee</dt>
<dd>
{estimatedFee?.fee ? (
{estimatedFee.fee ? (
<>{formatGas(estimatedFee.fee)} gwei</>
) : estimatedFee?.isLoading ? (
) : estimatedFee.error ? (
<span title={String(estimatedFee.error)}>
<WarningIcon className="inline-block text-amber-500" />
</span>
) : estimatedFee.isLoading ? (
<PendingIcon className="inline-block text-xs" />
) : null}
</dd>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PendingIcon } from "../../icons/PendingIcon";
import { ReactNode, useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
import { CheckIcon } from "../../icons/CheckIcon";
import { CloseIcon } from "../../icons/CloseIcon";
import { WarningIcon } from "../../icons/WarningIcon";

export type Props = {
status: "pending" | "success" | "error";
Expand All @@ -13,7 +13,7 @@ export type Props = {
children: ReactNode;
};

export function TransactionStatus({ status, progress, children }: Props) {
export function DepositStatus({ status, progress, children }: Props) {
const [appear, setAppear] = useState(false);
useEffect(() => {
setAppear(true);
Expand All @@ -26,7 +26,7 @@ export function TransactionStatus({ status, progress, children }: Props) {
{status === "success" ? (
<CheckIcon className="flex-shrink-0 text-green-600" />
) : status === "error" ? (
<CloseIcon className="flex-shrink-0 text-red-600" />
<WarningIcon className="flex-shrink-0 text-amber-500" />
) : (
<PendingIcon className="flex-shrink-0 text-neutral-400 dark:text-neutral-500 transition" />
)}
Expand Down
92 changes: 48 additions & 44 deletions packages/account-kit/src/steps/deposit/DepositViaBridgeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { getL2TransactionHashes, publicActionsL1, publicActionsL2, walletActions
import { SubmitButton } from "./SubmitButton";
import { Account, Chain } from "viem";
import { debug } from "../../debug";
import { BridgeTransaction, useDepositTransactions } from "./useDepoitTransactions";
import { BridgeDeposit, useDeposits } from "./useDeposits";

export function DepositViaBridgeForm(props: Props) {
const appChain = useAppChain();
const { addTransaction } = useDepositTransactions();
const { addDeposit } = useDeposits();

const { data: appAccountClient } = useAppAccountClient();
const appAccountAddress = appAccountClient?.account.address;
Expand Down Expand Up @@ -60,52 +60,56 @@ export function DepositViaBridgeForm(props: Props) {
publicClientL2?.uid,
],
mutationFn: async () => {
// TODO: take these in as args instead?
if (!walletClientL1) throw new Error("Could not deposit. L1 wallet client not ready.");
if (!publicClientL1) throw new Error("Could not deposit. L1 public client not ready.");
if (!publicClientL2) throw new Error("Could not deposit. L2 public client not ready.");
if (!prepare.data) throw new Error("Could not deposit. Transaction not prepared.");
const hashL1 = await walletClientL1.depositTransaction(prepare.data);
try {
// TODO: take these in as args instead?
if (!walletClientL1) throw new Error("Could not deposit. L1 wallet client not ready.");
if (!publicClientL1) throw new Error("Could not deposit. L1 public client not ready.");
if (!publicClientL2) throw new Error("Could not deposit. L2 public client not ready.");
if (!prepare.data) throw new Error("Could not deposit. Transaction not prepared.");
const hashL1 = await walletClientL1.depositTransaction(prepare.data);

const receiptL1 = publicClientL1.waitForTransactionReceipt({ hash: hashL1 }).then((receipt) => {
if (receipt.status === "reverted") {
throw new Error("L1 deposit transaction reverted.");
}
const hashL2 = getL2TransactionHashes(receipt).at(0);
if (!hashL2) {
console.error("Could not find L2 hash in L1 deposit transaction receipt.", receipt);
throw new Error("Could not find L2 hash in L1 deposit transaction receipt.");
}
return {
receiptL1: receipt,
hashL2,
};
});
const receiptL1 = publicClientL1.waitForTransactionReceipt({ hash: hashL1 }).then((receipt) => {
if (receipt.status === "reverted") {
throw new Error("L1 deposit transaction reverted.");
}
const hashL2 = getL2TransactionHashes(receipt).at(0);
if (!hashL2) {
console.error("Could not find L2 hash in L1 deposit transaction receipt.", receipt);
throw new Error("Could not find L2 hash in L1 deposit transaction receipt.");
}
return {
receiptL1: receipt,
hashL2,
};
});

const receiptL2 = receiptL1.then(async ({ hashL2 }) => {
const receipt = await publicClientL2.waitForTransactionReceipt({ hash: hashL2 });
if (receipt.status === "reverted") {
// I really really hope this never happens.
throw new Error("L2 bridge deposit transaction reverted.");
}
return receipt;
});
const receiptL2 = receiptL1.then(async ({ hashL2 }) => {
const receipt = await publicClientL2.waitForTransactionReceipt({ hash: hashL2 });
if (receipt.status === "reverted") {
// I really really hope this never happens.
throw new Error("L2 bridge deposit transaction reverted.");
}
return receipt;
});

const bridgeTransaction = {
type: "bridge",
uid: `${props.sourceChain.id}:${hashL1}`,
amount: prepare.data.request.mint!,
chainL1: props.sourceChain,
chainL2: appChain,
hashL1,
receiptL1,
receiptL2,
start: new Date(),
estimatedTime: 1000 * 60 * 3,
} satisfies BridgeTransaction;
const pendingDeposit = {
type: "bridge",
amount: prepare.data.request.mint!,
chainL1Id: props.sourceChain.id,
chainL2Id: appChain.id,
hashL1,
receiptL1,
receiptL2,
start: new Date(),
estimatedTime: 1000 * 60 * 3,
} satisfies BridgeDeposit;

debug("bridge transaction submitted", bridgeTransaction);
addTransaction(bridgeTransaction);
debug("bridge transaction submitted", pendingDeposit);
addDeposit(pendingDeposit);
} catch (error) {
console.error("Error while depositing via bridge", error);
throw error;
}
},
});

Expand Down
43 changes: 34 additions & 9 deletions packages/account-kit/src/steps/deposit/DepositViaRelayForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { useAppChain } from "../../useAppChain";
import { BridgeActionParameters, GetBridgeQuoteParameters } from "@reservoir0x/relay-sdk";
import { useRelay } from "./useRelay";
import { useDeposits } from "./useDeposits";

export function DepositViaRelayForm(props: Props) {
const appChain = useAppChain();
const { data: appAccountClient } = useAppAccountClient();
const { data: walletClient } = useWalletClient();
const { data: relay } = useRelay();
const relayClient = relay?.client;
const { addDeposit } = useDeposits();

if (appAccountClient?.type === "smartAccountClient") {
// TODO: wire this up differently to make a `depositTo` call on gas tank
Expand All @@ -39,7 +41,7 @@ export function DepositViaRelayForm(props: Props) {
];

const bridgeParams =
walletClient && appAccountClient && props.amount != null
walletClient && appAccountClient && props.amount != null && props.amount > 0n
? ({
wallet: walletClient,
chainId: props.sourceChain.id,
Expand Down Expand Up @@ -83,14 +85,37 @@ export function DepositViaRelayForm(props: Props) {

const bridge = useMutation({
mutationKey: ["relayBridge"],
mutationFn: (params: BridgeActionParameters) =>
relayClient.actions.bridge({
...params,
// TODO: translate this to something useful
onProgress(progress) {
console.log("onProgress", progress);
},
}),
mutationFn: async (params: BridgeActionParameters) => {
try {
// This start time isn't very accurate because the `bridge` call below doesn't resolve until everything is complete.
// Ideally `start` is initialized after the transaction is signed by the user wallet.
const start = new Date();

const pendingDeposit = relayClient.actions.bridge({
...params,
// TODO: translate this to something useful
onProgress(progress) {
console.log("onProgress", progress);
},
});

// TODO: move this into `onProgress` once we can determine that the tx has been signed and sent to mempool
addDeposit({
type: "relay",
amount: BigInt(params.amount), // ugh
chainL1Id: params.chainId,
chainL2Id: params.toChainId,
start,
estimatedTime: 1000 * 30,
depositPromise: pendingDeposit,
});

return await pendingDeposit;
} catch (error) {
console.error("Error while depositing via Relay", error);
throw error;
}
},
});

const fee = quote.data?.quote.fees?.gas;
Expand Down
Loading

0 comments on commit 0a300ec

Please sign in to comment.