Skip to content

Commit

Permalink
Merge pull request #409 from wigwamapp/advanced-tx-actions
Browse files Browse the repository at this point in the history
Speedup/cancel transactions
  • Loading branch information
serg-plusplus authored Mar 11, 2024
2 parents fcacd87 + 92d3599 commit 9e68702
Show file tree
Hide file tree
Showing 17 changed files with 697 additions and 129 deletions.
571 changes: 493 additions & 78 deletions src/app/components/blocks/activity/ActivityAsset.tsx

Large diffs are not rendered by default.

29 changes: 17 additions & 12 deletions src/app/components/blocks/activity/ActivityContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { isPopup } from "lib/ext/view";
import { rejectAllApprovals } from "core/client";

import { approvalStatusAtom } from "app/atoms";
import { ToastOverflowProvider, ToastProvider } from "app/hooks/toast";
import { ReactComponent as LinkIcon } from "app/icons/external-link.svg";

import Button from "../../elements/Button";
Expand All @@ -24,18 +25,22 @@ const ActivityContent = memo(() => {
}, [isPopupMode]);

return (
<div
className={classNames(
isPopupMode
? "w-full"
: classNames("w-[54rem] h-full mx-auto", "px-4 pt-16"),
"flex flex-col",
!delayFinished ? "hidden" : "animate-bootfadeinfast",
)}
>
<Approve />
<ActivitiesList />
</div>
<ToastProvider>
<ToastOverflowProvider>
<div
className={classNames(
isPopupMode
? "w-full"
: classNames("w-[54rem] h-full mx-auto", "px-4 pt-16"),
"flex flex-col",
!delayFinished ? "hidden" : "animate-bootfadeinfast",
)}
>
<Approve />
<ActivitiesList />
</div>
</ToastOverflowProvider>
</ToastProvider>
);
});

Expand Down
62 changes: 57 additions & 5 deletions src/app/components/blocks/approvals/DetailsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Dot from "app/components/elements/Dot";
import TokenAmount from "app/components/blocks/TokenAmount";
import { ReactComponent as WalletExplorerIcon } from "app/icons/external-link.svg";
import { ReactComponent as ChevronRightIcon } from "app/icons/chevron-right.svg";
import { ReactComponent as GasIcon } from "app/icons/gas.svg";

type DetailsTabProps = Omit<FeeButton, "onClick"> & {
accountAddress: string;
Expand Down Expand Up @@ -113,10 +114,31 @@ const DetailsTab: FC<DetailsTabProps> = ({
[action],
);

const currentNetwork = useLazyNetwork();
const explorerLink = useExplorerLink(currentNetwork);

const cancelTx =
source.replaceTx?.type === "cancel" ||
source.replaceTx?.prevReplaceTxType === "cancel";

return (
<>
<TabHeader className={withDescription ? "!mb-1" : ""}>
{tabHeader}
<TabHeader
className={classNames(
"flex items-center w-full",
withDescription ? "!mb-1" : "",
)}
>
{cancelTx ? "Cancel transaction" : tabHeader}
{source.replaceTx?.type === "speedup" && (
<>
<span className="flex-1" />
<span className="text-xs text-brand-inactivedark font-medium inline-flex items-center">
<GasIcon className="w-3 h-3 mr-1" />
Speed up
</span>
</>
)}
</TabHeader>
{withDescription && (
<p className="text-sm text-[#BCC3C4] mb-3">
Expand Down Expand Up @@ -149,9 +171,39 @@ const DetailsTab: FC<DetailsTabProps> = ({
feeMode={feeMode}
onClick={onFeeButtonClick}
/>
<Recipient action={action} />
<Tokens accountAddress={accountAddress} action={action} />
<ActivitySwap source={source} />
{cancelTx ? (
<>
<InfoRaw label="Transaction">
<div className="flex flex-col items-end">
<div className="flex items-center">
<TippySingletonProvider>
<HashPreview
hash={source.replaceTx!.prevTxHash}
className="text-sm"
startLength={8}
endLength={6}
/>
{explorerLink && (
<IconedButton
href={explorerLink.tx(source.replaceTx!.prevTxHash)}
aria-label="View in Explorer"
Icon={WalletExplorerIcon}
className="!w-6 !h-6 ml-2"
iconClassName="!w-[1.125rem]"
/>
)}
</TippySingletonProvider>
</div>
</div>
</InfoRaw>
</>
) : (
<>
<Recipient action={action} />
<Tokens accountAddress={accountAddress} action={action} />
<ActivitySwap source={source} />
</>
)}
</>
);
};
Expand Down
2 changes: 2 additions & 0 deletions src/app/components/blocks/overview/TokenActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ const TokenActivityCard = forwardRef<HTMLDivElement, TokenActivityCardProps>(
const tokenDecimals =
token.tokenType === TokenType.Asset ? token.decimals : 0;

if (amoutnBN.isZero()) return null;

return (
<div
ref={ref}
Expand Down
28 changes: 3 additions & 25 deletions src/app/components/elements/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
ForwardedRef,
forwardRef,
ReactNode,
memo,
useRef,
useMemo,
useState,
Expand All @@ -13,6 +12,8 @@ import classNames from "clsx";
import { CSSTransition } from "react-transition-group";
import Link, { LinkProps } from "lib/navigation/Link";

import CircleSpinner from "./CircleSpinner";

export type ButtonTheme =
| "primary"
| "secondary"
Expand Down Expand Up @@ -111,7 +112,7 @@ const Button = forwardRef<HTMLElement, ButtonProps>(
"transition duration-300",
)}
>
<Spinner />
<CircleSpinner />
</span>
</CSSTransition>
</>
Expand Down Expand Up @@ -212,26 +213,3 @@ const Button = forwardRef<HTMLElement, ButtonProps>(
);

export default Button;

const Spinner = memo<{ className?: string }>(({ className }) => (
<svg
className={classNames(
"animate-spin h-5 w-5 text-brand-darkaccent",
className,
)}
viewBox="0 0 20 20"
fill="none"
>
<path
d="M18.5 10C18.5 14.6944 14.6944 18.5 10 18.5C5.30558 18.5 1.5 14.6944 1.5 10C1.5 5.30558 5.30558 1.5 10 1.5C14.6944 1.5 18.5 5.30558 18.5 10Z"
stroke="currentColor"
className="opacity-25"
strokeWidth="3"
/>
<path
d="M11.8121 1.6954C11.9887 0.886016 11.4745 0.0746618 10.6478 0.0209989C8.98586 -0.0868834 7.31477 0.221661 5.78979 0.929505C3.80889 1.84897 2.18345 3.39163 1.16178 5.32181C0.140108 7.25198 -0.221611 9.46355 0.131881 11.6186C0.404014 13.2777 1.0885 14.8331 2.11221 16.1468C2.62142 16.8002 3.58147 16.7692 4.15148 16.168V16.168C4.72149 15.5669 4.67972 14.624 4.21354 13.9391C3.6433 13.1015 3.25827 12.1448 3.09232 11.1331C2.84488 9.62449 3.09808 8.07639 3.81325 6.72527C4.52842 5.37414 5.66623 4.29428 7.05286 3.65066C7.98283 3.219 8.99042 2.99947 10.0038 3C10.8322 3.00044 11.6354 2.50478 11.8121 1.6954V1.6954Z"
fill="currentColor"
className="opacity-75"
/>
</svg>
));
27 changes: 27 additions & 0 deletions src/app/components/elements/CircleSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { memo } from "react";
import classNames from "clsx";

const CircleSpinner = memo<{ className?: string }>(({ className }) => (
<svg
className={classNames(
"animate-spin h-5 w-5 text-brand-darkaccent",
className,
)}
viewBox="0 0 20 20"
fill="none"
>
<path
d="M18.5 10C18.5 14.6944 14.6944 18.5 10 18.5C5.30558 18.5 1.5 14.6944 1.5 10C1.5 5.30558 5.30558 1.5 10 1.5C14.6944 1.5 18.5 5.30558 18.5 10Z"
stroke="currentColor"
className="opacity-25"
strokeWidth="3"
/>
<path
d="M11.8121 1.6954C11.9887 0.886016 11.4745 0.0746618 10.6478 0.0209989C8.98586 -0.0868834 7.31477 0.221661 5.78979 0.929505C3.80889 1.84897 2.18345 3.39163 1.16178 5.32181C0.140108 7.25198 -0.221611 9.46355 0.131881 11.6186C0.404014 13.2777 1.0885 14.8331 2.11221 16.1468C2.62142 16.8002 3.58147 16.7692 4.15148 16.168V16.168C4.72149 15.5669 4.67972 14.624 4.21354 13.9391C3.6433 13.1015 3.25827 12.1448 3.09232 11.1331C2.84488 9.62449 3.09808 8.07639 3.81325 6.72527C4.52842 5.37414 5.66623 4.29428 7.05286 3.65066C7.98283 3.219 8.99042 2.99947 10.0038 3C10.8322 3.00044 11.6354 2.50478 11.8121 1.6954V1.6954Z"
fill="currentColor"
className="opacity-75"
/>
</svg>
));

export default CircleSpinner;
6 changes: 4 additions & 2 deletions src/app/components/screens/approvals/Transaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ const ApproveTransaction: FC<ApproveTransactionProps> = ({ approval }) => {
if (!prepared) return null;

const tx: Transaction = prepared.tx.clone();
tx.nonce = getNextNonce(tx, localNonce);
tx.nonce = approval.source?.replaceTx
? tx.nonce
: getNextNonce(tx, localNonce);

const feeSug = prepared.fees?.modes[feeMode];
if (feeSug) {
Expand All @@ -138,7 +140,7 @@ const ApproveTransaction: FC<ApproveTransactionProps> = ({ approval }) => {
}

return tx;
}, [prepared, feeMode, localNonce]);
}, [approval.source, prepared, feeMode, localNonce]);

const finalTx = useMemo<typeof originTx>(() => {
if (!originTx) return originTx;
Expand Down
7 changes: 5 additions & 2 deletions src/app/icons/alert-triangle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/app/icons/ban.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 src/app/icons/options-horizontal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/core/back/approve/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export async function processApprove(
rawTx: rawTx!,
txAction: txAction ?? undefined,
txHash,
timeAt,
timeAt: source.replaceTx?.prevTimeAt ?? timeAt,
pending: 1,
}),
saveTokenActivity(
Expand Down
14 changes: 14 additions & 0 deletions src/core/back/state/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ export const $approvals = createStore<Approval[]>([])
return [newApproval, ...approvals];
}

if (
newApproval.type === ActivityType.Transaction &&
newApproval.source.replaceTx
) {
const prevId = newApproval.source.replaceTx.prevActivityId;
const restApprovals = approvals.filter(
(a) => a.source.replaceTx?.prevActivityId !== prevId,
);

if (restApprovals.length !== approvals.length) {
return [newApproval, ...restApprovals];
}
}

return [...approvals, newApproval];
})
.on(approvalResolved, (current, id) => current.filter((a) => a.id !== id))
Expand Down
10 changes: 8 additions & 2 deletions src/core/client/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import memoizeOne from "memoize-one";
import memoize from "mem";
import { assert } from "lib/system/assert";

import { MessageType, RpcResponse } from "core/types";
import { MessageType, RpcResponse, ActivitySource } from "core/types";

import { porter } from "./base";

Expand All @@ -15,6 +15,8 @@ export class ClientProvider extends ethers.JsonRpcApiProvider {
constructor(public chainId: number) {
super(chainId);
}
source?: ActivitySource;

getNetwork = memoizeOne(super.getNetwork.bind(this));
getCode = memoize(super.getCode.bind(this));

Expand All @@ -25,6 +27,10 @@ export class ClientProvider extends ethers.JsonRpcApiProvider {
(address: string) => new ethers.VoidSigner(address, this),
);

setActivitySource = (source?: ActivitySource) => {
this.source = source;
};

async send(method: string, params: Array<any>): Promise<any> {
const res = await this.sendRpc(method, params);

Expand Down Expand Up @@ -57,7 +63,7 @@ export class ClientProvider extends ethers.JsonRpcApiProvider {
const chainId = this.chainId;

const res = await porter.request(
{ type, chainId, method, params },
{ type, chainId, method, params, source: this.source },
{ timeout: 0 },
);
assert(res?.type === type);
Expand Down
16 changes: 15 additions & 1 deletion src/core/common/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,21 @@ import { createTokenActivityKey } from "core/common/tokens";
export async function saveActivity(activity: Activity | Activity[]) {
const activities = Array.isArray(activity) ? activity : [activity];

// TODO: Add specific logic for speed-up or cancel tx
// Replace TX first
for (const activity of activities) {
if (
activity.type === ActivityType.Transaction &&
activity.source.replaceTx
) {
const { prevActivityId, prevTxHash } = activity.source.replaceTx;

await repo.activities.delete(prevActivityId);
await repo.tokenActivities
.where({ txHash: prevTxHash, pending: 1 })
.delete();
}
}

await repo.activities.bulkPut(activities).catch(console.error);

for (const activity of activities) {
Expand Down
3 changes: 2 additions & 1 deletion src/core/common/nonce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ export function saveNonce(
chainId: number,
accountAddress: string,
nonce: ethers.BigNumberish,
override = false,
) {
return enqueueSaveNonce(async () => {
try {
const key = nonceStorageKey(chainId, accountAddress);
const current = await storage.fetchForce<string>(key);

if (!current || BigInt(current) < BigInt(nonce)) {
if (!current || override || BigInt(current) < BigInt(nonce)) {
await storage.put(key, nonce.toString());
}
} catch (err) {
Expand Down
Loading

0 comments on commit 9e68702

Please sign in to comment.