Skip to content

Commit

Permalink
feat: era transfer, withdraw, improve lite deposit
Browse files Browse the repository at this point in the history
  • Loading branch information
JackHamer09 committed May 18, 2023
1 parent 2605cdb commit 53bb402
Show file tree
Hide file tree
Showing 24 changed files with 1,361 additions and 48 deletions.
298 changes: 298 additions & 0 deletions components/transaction/zksync/era/ConfirmTransactionModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
<template>
<CommonModal
v-if="status !== 'done'"
v-bind="$attrs"
:close-on-background-click="status === 'not-started'"
class="confirm-transaction-modal"
title="Confirm transaction"
>
<div class="flex h-full flex-col overflow-auto">
<template v-if="transaction">
<CommonCardWithLineButtons>
<AddressCardParsed
:address="account.address!"
:destination="destinations.era"
:tooltip="`${transactionActionName} from ${destinations.era.label} (L2)`"
/>
<div class="-mx-1 border-b border-dashed"></div>
<TokenBalance v-bind="transaction.token" as="div" :amount="transaction.amount" :show-send-button="false" />
</CommonCardWithLineButtons>
<TransactionItemIcon :icon="ArrowDownIcon" />
<CommonCardWithLineButtons>
<AddressCardParsed
:address="transaction.to"
:destination="destination"
:tooltip="`${transactionActionName} to ${destination.label}`"
/>
</CommonCardWithLineButtons>
</template>

<TransactionFeeDetails class="my-2" label="Fee:" :fee-token="feeToken" :fee-amount="lastFee" />

<div class="sticky bottom-0 z-[1] mt-auto w-full bg-gray bg-opacity-60 backdrop-blur-sm">
<slot name="alerts" />
<div class="mx-4 mb-3 border-t border-dashed border-gray-300"></div>
<TransactionFeeDetails
v-for="(item, index) in totalOfEachToken"
class="-my-0.5"
:key="item.token.address"
:label="index === 0 ? 'Total:' : ''"
:fee-token="item.token"
:fee-amount="item.amount"
/>
<transition v-bind="TransitionAlertScaleInOutTransition">
<div v-if="!buttonDisabled && newFeeAlert" class="mx-4 mt-3">
<CommonAlert variant="warning" :icon="ExclamationCircleIcon">
<p>Fee has changed since you started the transaction. Please confirm the updated fee to proceed.</p>
<CommonButton @click="newFeeAlert = false">Confirm</CommonButton>
</CommonAlert>
</div>
</transition>
<div v-if="error" class="mx-4">
<CommonErrorBlock :retry-button="false" class="mt-3">
{{ error.message }}
</CommonErrorBlock>
</div>
<a
v-if="destination.key === 'ethereum'"
class="link mx-auto mt-2 -mb-1 flex items-center justify-center text-center text-sm"
href="https://era.zksync.io/docs/dev/troubleshooting/withdrawal-delay.html#withdrawal-delay"
target="_blank"
>
Arriving in ~24 hours
<ArrowUpRightIcon class="ml-1 mt-0.5 h-3.5 w-3.5" />
</a>
<CommonButton
:disabled="buttonDisabled || newFeeAlert || status !== 'not-started'"
class="mx-auto mt-3"
variant="primary-solid"
@click="makeTransaction"
>
<transition v-bind="TransitionPrimaryButtonText" mode="out-in">
<span v-if="status === 'processing'">Processing...</span>
<span v-else-if="status === 'waiting-for-signature'">Waiting for confirmation</span>
<span v-else>Send to {{ destination.label }}</span>
</transition>
</CommonButton>
<transition v-bind="TransitionHeight()">
<div v-if="status === 'waiting-for-signature'" class="h-6 text-center text-sm font-medium text-gray-500">
<div class="pt-1"></div>
Confirm this transaction in your {{ walletName }} wallet
</div>
</transition>
</div>
</div>
</CommonModal>

<EraTransferSuccessfulModal
v-else-if="transaction?.type === 'transfer'"
v-bind="$attrs"
:transaction="transactionLineItem"
:in-progress="!transactionCommitted"
/>
<EraWithdrawalSuccessfulModal
v-else-if="transaction?.type === 'withdrawal'"
v-bind="$attrs"
:transaction="transactionLineItem"
:in-progress="!transactionCommitted"
/>
</template>

<script lang="ts" setup>
import { computed, ref, watch } from "vue";
import { ArrowDownIcon, ArrowUpRightIcon, ExclamationCircleIcon } from "@heroicons/vue/24/outline";
import { BigNumber } from "ethers";
import { storeToRefs } from "pinia";
import EraTransferSuccessfulModal from "@/components/transaction/zksync/era/EraTransferSuccessfulModal.vue";
import EraWithdrawalSuccessfulModal from "@/components/transaction/zksync/era/EraWithdrawalSuccessfulModal.vue";
import useTransaction from "@/composables/zksync/era/useTransaction";
import type { FeeEstimationParams } from "@/composables/zksync/era/useFee";
import type { TransactionDestination } from "@/store/destinations";
import type { Token } from "@/types";
import type { BigNumberish } from "ethers";
import type { PropType } from "vue";
import type { EraTransaction } from "~~/utils/zksync/era/mappers";
import { useDestinationsStore } from "@/store/destinations";
import { useOnboardStore } from "@/store/onboard";
import { usePreferencesStore } from "@/store/preferences";
import { useEraProviderStore } from "@/store/zksync/era/provider";
import { useEraTransactionsHistoryStore } from "@/store/zksync/era/transactionsHistory";
import { useEraWalletStore } from "@/store/zksync/era/wallet";
import { calculateFee } from "@/utils/helpers";
import { TransitionHeight, TransitionPrimaryButtonText } from "@/utils/transitions";
export type ConfirmationModalTransaction = {
type: FeeEstimationParams["type"];
to: string;
token: Token;
amount: BigNumberish;
};
const props = defineProps({
transaction: {
type: Object as PropType<ConfirmationModalTransaction>,
},
feeToken: {
type: Object as PropType<Token>,
},
fee: {
type: Object as PropType<{
gasLimit: BigNumberish;
gasPrice: BigNumberish;
}>,
},
destination: {
type: Object as PropType<TransactionDestination>,
required: true,
},
buttonDisabled: {
type: Boolean,
default: false,
},
estimate: {
type: Function as PropType<() => Promise<void>>,
required: true,
},
});
const eraTransactionsHistoryStore = useEraTransactionsHistoryStore();
const walletEraStore = useEraWalletStore();
const eraProviderStore = useEraProviderStore();
const { account, walletName } = storeToRefs(useOnboardStore());
const { destinations } = storeToRefs(useDestinationsStore());
const { previousTransactionAddress } = storeToRefs(usePreferencesStore());
const { status, error, transactionHash, commitTransaction } = useTransaction(
walletEraStore.getSigner,
eraProviderStore.requestProvider
);
const feeAmount = computed(() => {
if (!props.fee) return undefined;
return calculateFee(props.fee.gasLimit, props.fee.gasPrice).toString();
});
const lastFee = ref(feeAmount.value);
watch(feeAmount, (newFee) => {
if (newFee) {
lastFee.value = newFee;
}
});
const newFeeAlert = ref(false);
const totalOfEachToken = computed<{ token: Token; amount: BigNumberish }[]>(() => {
const tokenByAddress: { [tokenAddress: string]: Token } = {};
if (props.transaction) {
tokenByAddress[props.transaction.token.address] = props.transaction.token;
}
if (props.feeToken) {
tokenByAddress[props.feeToken.address] = props.feeToken;
}
const totalByAddress: { [tokenAddress: string]: BigNumberish } = {};
const addToTotal = (tokenAddress: string, amount: BigNumberish) => {
if (totalByAddress[tokenAddress]) {
totalByAddress[tokenAddress] = BigNumber.from(totalByAddress[tokenAddress]).add(amount);
} else {
totalByAddress[tokenAddress] = amount;
}
};
if (props.transaction) {
addToTotal(props.transaction.token.address, props.transaction.amount);
}
if (props.feeToken && lastFee.value) {
addToTotal(props.feeToken.address, lastFee.value);
}
return Object.entries(totalByAddress).map(([tokenAddress, amount]) => ({
token: tokenByAddress[tokenAddress],
amount: amount.toString(),
}));
});
const transactionCommitted = ref(false);
const transactionActionName = computed(() => {
switch (props.transaction?.type) {
case "transfer":
return "Sending";
case "withdrawal":
return "Withdrawing";
default:
return "";
}
});
const makeTransaction = async () => {
if (!props.feeToken || !feeAmount.value) return;
const fee = BigNumber.from(lastFee.value);
await props.estimate();
if (fee.lt(feeAmount.value)) {
newFeeAlert.value = true;
}
if (newFeeAlert.value || props.buttonDisabled) return;
const tx = await commitTransaction(
{
type: props.transaction!.type,
to: props.transaction!.to,
tokenAddress: props.transaction!.token.address,
amount: props.transaction!.amount,
},
props.fee!
);
if (status.value === "done") {
previousTransactionAddress.value = props.transaction!.to;
}
if (tx) {
tx.wait()
.then(async () => {
transactionCommitted.value = true;
eraTransactionsHistoryStore.reloadRecentTransactions();
walletEraStore.requestBalance({ force: true });
})
.catch((err) => {
transactionCommitted.value = false;
error.value = err;
status.value = "not-started";
});
}
};
const transactionLineItem = computed(() => {
const transaction: EraTransaction = {
transactionHash: transactionHash.value!,
status: "included",
blockNumber: 0,
type: props.transaction!.type,
from: account.value.address!,
fromNetwork: "L2",
to: props.transaction!.to,
toNetwork: "L2",
token: props.transaction!.token,
amount: BigNumber.from("0").sub(props.transaction!.amount).toString(),
feeToken: props.feeToken!,
feeAmount: "1",
receivedAt: new Date().toISOString(),
};
return transaction;
});
</script>

<style lang="scss">
.confirm-transaction-modal .modal-card {
@apply grid h-full grid-rows-[max-content_1fr];
}
</style>
81 changes: 81 additions & 0 deletions components/transaction/zksync/era/EraTransactionFooter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<template>
<div class="transaction-footer">
<!-- Change network -->
<transition v-bind="TransitionAlertScaleInOutTransition">
<CommonErrorBlock
v-if="buttonStep === 'network' && switchingNetworkError"
class="mb-2"
@try-again="eraWalletStore.setCorrectNetwork"
>
Network change error: {{ switchingNetworkError.message }}
</CommonErrorBlock>
</transition>

<div v-if="buttonStep === 'network'" class="transaction-footer-row">
<div class="mb-2 text-sm text-gray-secondary">Incorrect network selected in your {{ walletName }} wallet</div>
<CommonButton
:disabled="switchingNetworkInProgress"
variant="primary-solid"
@click="eraWalletStore.setCorrectNetwork"
>
Change wallet network to {{ eraNetwork.name }}
</CommonButton>
</div>
<div v-else-if="buttonStep === 'continue'" class="transaction-footer-row">
<slot name="after-checks" />
</div>

<transition v-bind="TransitionHeight()">
<div
v-if="continueInWalletTipDisplayed"
class="h-6 whitespace-nowrap text-center text-sm font-medium text-gray-500"
>
<div class="pt-1"></div>
Continue in your {{ walletName }} wallet
</div>
</transition>
</div>
</template>

<script lang="ts" setup>
import { computed } from "vue";
import { storeToRefs } from "pinia";
import { useOnboardStore } from "@/store/onboard";
import { useEraProviderStore } from "@/store/zksync/era/provider";
import { useEraWalletStore } from "@/store/zksync/era/wallet";
import { TransitionAlertScaleInOutTransition } from "@/utils/transitions";
const onboardStore = useOnboardStore();
const eraWalletStore = useEraWalletStore();
const { walletName } = storeToRefs(onboardStore);
const { isCorrectNetworkSet, switchingNetworkInProgress, switchingNetworkError } = storeToRefs(eraWalletStore);
const { eraNetwork } = storeToRefs(useEraProviderStore());
const buttonStep = computed(() => {
if (!isCorrectNetworkSet.value) {
return "network";
} else {
return "continue";
}
});
const continueInWalletTipDisplayed = computed(() => {
if (buttonStep.value === "network" && switchingNetworkInProgress.value) {
return true;
}
return false;
});
</script>

<style lang="scss" scoped>
.transaction-footer {
@apply sticky bottom-0 z-[2] mt-auto flex flex-col items-center bg-gray bg-opacity-60 pb-2 pt-4 backdrop-blur-sm;
.transaction-footer-row {
@apply flex w-full flex-col items-center;
}
}
</style>
Loading

0 comments on commit 53bb402

Please sign in to comment.