Skip to content

Commit

Permalink
feat: display paymaster info when transaction is paid by paymaster (#63)
Browse files Browse the repository at this point in the history
# What ❔

Display paymaster info on tx page when transaction is paid by paymaster


## Checklist

- [ +] PR title corresponds to the body of PR (we generate changelog
entries from PRs).
- [ +] Tests for the changes have been added / updated.
  • Loading branch information
Romsters authored Oct 20, 2023
1 parent 0c0b64e commit c86360f
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 101 deletions.
1 change: 1 addition & 0 deletions packages/app/mock/transactions/Execute.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"fee": "0x2279f530c00",
"feeData": {
"amountPaid": "0x2279f530c00",
"isPaidByPaymaster": false,
"refunds": [],
"amountRefunded": "0x00"
},
Expand Down
43 changes: 34 additions & 9 deletions packages/app/src/components/FeeData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<div>
<div class="fee-info-container">
<TokenAmountPrice :token="token" :amount="feeData?.amountPaid" />
<span class="payed-by-paymaster-label" v-if="feeData?.isPaidByPaymaster">
{{ t("transactions.table.paidByPaymaster") }}
</span>
<button v-if="showDetails" class="toggle-button" @click="collapsed = !collapsed" type="button">
{{ buttonTitle }}
</button>
Expand All @@ -18,15 +21,30 @@
<div class="fee-transfers-container">
<div class="details-title">{{ t("transactions.table.feeDetails.refunds") }}</div>
<div v-for="(transfer, index) in feeData?.refunds" :key="index">
<TransferTableCell :transfer="transfer" />
<TransferTableCell :transfer="transfer" :paymaster-address="feeData?.paymasterAddress" />
</div>
</div>
<a
class="refunded-link"
href="https://era.zksync.io/docs/dev/developer-guides/transactions/fee-model.html#refunds"
target="_blank"
>{{ t("transactions.table.feeDetails.whyRefunded") }}</a
>
<div>
<a
class="refunded-link"
href="https://era.zksync.io/docs/dev/developer-guides/transactions/fee-model.html#refunds"
target="_blank"
>{{
t(
feeData?.isPaidByPaymaster
? "transactions.table.feeDetails.whyPaymasterRefunded"
: "transactions.table.feeDetails.whyRefunded"
)
}}</a
>
<a
v-if="feeData?.isPaidByPaymaster"
class="paymaster-link"
href="https://era.zksync.io/docs/reference/concepts/account-abstraction.html#paymasters"
target="_blank"
>{{ t("transactions.table.feeDetails.whatIsPaymaster") }}</a
>
</div>
</div>
</div>
</template>
Expand Down Expand Up @@ -77,7 +95,10 @@ const token = computed<Token>(() => {
</script>
<style lang="scss" scoped>
.fee-info-container {
@apply flex items-center gap-x-5;
@apply flex items-center gap-x-2;
}
.payed-by-paymaster-label {
@apply text-gray-400;
}
.toggle-button {
@apply text-primary-600 underline hover:text-[#7379E5];
Expand All @@ -93,8 +114,12 @@ const token = computed<Token>(() => {
.fee-transfers-container {
@apply flex flex-col gap-y-1;
}
.refunded-link {
.refunded-link,
.paymaster-link {
@apply w-max;
}
.paymaster-link {
@apply ml-2;
}
}
</style>
16 changes: 16 additions & 0 deletions packages/app/src/components/transactions/PaymasterLabel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div class="paymaster-label">
{{ t("transactions.table.feeDetails.paymaster") }}
</div>
</template>

<script lang="ts" setup>
import { useI18n } from "vue-i18n";
const { t } = useI18n();
</script>

<style scoped lang="scss">
.paymaster-label {
@apply h-[20px] px-1 rounded bg-neutral-200 text-center text-sm text-gray-600;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div class="transfer-info-container">
<span>{{ label }}</span>
<PaymasterLabel v-if="isPaymaster" />
<TransactionNetworkSquareBlock :network="network" />
<AddressLink v-if="network !== 'L1'" :address="address" class="address">
<span>{{ shortenFitText(address, "left") }}</span>
Expand All @@ -24,6 +25,7 @@
import AddressLink from "@/components/AddressLink.vue";
import CopyButton from "@/components/common/CopyButton.vue";
import { shortenFitText } from "@/components/common/HashLabel.vue";
import PaymasterLabel from "@/components/transactions/PaymasterLabel.vue";
import TransactionNetworkSquareBlock from "@/components/transactions/TransactionNetworkSquareBlock.vue";
import useContext from "@/composables/useContext";
Expand All @@ -44,6 +46,9 @@ defineProps({
required: true,
default: "L1",
},
isPaymaster: {
type: Boolean,
},
});
const { currentNetwork } = useContext();
</script>
Expand All @@ -54,6 +59,13 @@ const { currentNetwork } = useContext();
.transactions-data-link-network {
@apply ml-2 mr-1;
}
.paymaster-label {
@apply ml-2;
& + .transactions-data-link-network {
@apply ml-1;
}
}
.copy-btn {
@apply -top-px -mr-1.5 inline-block align-top;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
<template>
<div class="transfer-container">
<TransferInfo :label="t('transactions.table.from')" :network="transfer.fromNetwork" :address="transfer.from" />
<TransferInfo :label="t('transactions.table.transferTo')" :network="transfer.toNetwork" :address="transfer.to" />
<TransferInfo
:label="t('transactions.table.from')"
:network="transfer.fromNetwork"
:address="transfer.from"
:is-paymaster="transfer.from === paymasterAddress"
/>
<TransferInfo
:label="t('transactions.table.transferTo')"
:network="transfer.toNetwork"
:address="transfer.to"
:is-paymaster="transfer.to === paymasterAddress"
/>
<div class="transfer-amount-container">
<span>{{ t("transactions.table.for") }}</span>
<span class="transfer-amount-value">{{ transferAmount }}</span>
Expand All @@ -25,6 +35,7 @@ import TokenIconLabel from "@/components/TokenIconLabel.vue";
import TransferInfo from "@/components/transactions/infoTable/TransferInfo.vue";
import type { TokenTransfer } from "@/composables/useTransaction";
import type { Hash } from "@/types";
import { formatBigNumberish } from "@/utils/formatters";
Expand All @@ -35,6 +46,9 @@ const props = defineProps({
type: Object as PropType<TokenTransfer>,
required: true,
},
paymasterAddress: {
type: String as PropType<Hash>,
},
});
const transferAmount = computed(() =>
Expand Down
24 changes: 19 additions & 5 deletions packages/app/src/composables/useTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type TokenTransfer = {

export type FeeData = {
amountPaid: Hash;
isPaidByPaymaster: boolean;
paymasterAddress?: Hash;
refunds: TokenTransfer[];
amountRefunded: Hash;
};
Expand Down Expand Up @@ -109,6 +111,7 @@ export default (context = useContext()) => {
fee: transactionDetails.fee.toString(),
feeData: {
amountPaid: transactionDetails.fee.toString(),
isPaidByPaymaster: false,
refunds: [],
amountRefunded: BigNumber.from(0).toHexString(),
},
Expand Down Expand Up @@ -178,6 +181,11 @@ export function mapTransaction(
transfers: Api.Response.Transfer[],
logs: Api.Response.Log[]
): TransactionItem {
const fees = mapTransfers(filterFees(transfers));
const refunds = mapTransfers(filterRefunds(transfers));
const paymasterFee = fees.find((fee) => fee.from !== transaction.from);
const paymasterAddress = paymasterFee?.from;
const isPaidByPaymaster = !transaction.isL1Originated && !!paymasterFee;
return {
hash: transaction.hash,
blockHash: transaction.blockHash,
Expand All @@ -197,7 +205,9 @@ export function mapTransaction(
fee: transaction.fee,
feeData: {
amountPaid: transaction.fee!,
refunds: mapTransfers(filterRefunds(transfers)),
isPaidByPaymaster,
paymasterAddress,
refunds,
amountRefunded: sumAmounts(mapTransfers(filterRefunds(transfers))),
},
indexInBlock: transaction.transactionIndex,
Expand Down Expand Up @@ -248,12 +258,16 @@ function sumAmounts(balanceChanges: TokenTransfer[]) {
return total.toHexString() as Hash;
}

export function filterRefunds(balanceChanges: Api.Response.Transfer[]) {
return balanceChanges.filter((item) => item.type === "refund");
export function filterRefunds(transfers: Api.Response.Transfer[]) {
return transfers.filter((item) => item.type === "refund");
}

export function filterTransfers(balanceChanges: Api.Response.Transfer[]) {
return balanceChanges.filter((item) => item.type !== "fee" && item.type !== "refund");
export function filterFees(transfers: Api.Response.Transfer[]) {
return transfers.filter((item) => item.type === "fee");
}

export function filterTransfers(transfers: Api.Response.Transfer[]) {
return transfers.filter((item) => item.type !== "fee" && item.type !== "refund");
}

async function all<T>(url: URL): Promise<T[]> {
Expand Down
6 changes: 5 additions & 1 deletion packages/app/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,17 @@
"value": "Value",
"valueTooltip": "Amount of Ether being transferred from one address to another within a transaction.",
"fee": "Fee",
"paidByPaymaster": "Paid by paymaster",
"feeDetails": {
"moreDetails": "More Details",
"closeDetails": "Close Details",
"initial": "Initial:",
"refunded": "Refunded:",
"refunds": "Refunds:",
"whyRefunded": "Why I'm being refunded?"
"whyRefunded": "Why I'm being refunded?",
"whyPaymasterRefunded": "Why paymaster is being refunded?",
"whatIsPaymaster": "What is Paymaster?",
"paymaster": "Paymaster"
},
"feeTooltip": "Fee which sender paid for this transaction, amount in chosen asset & price in USD at the current time",
"direction": "Direction",
Expand Down
Loading

0 comments on commit c86360f

Please sign in to comment.