Skip to content

Commit

Permalink
feat: display transfer gas cost estimation (#174)
Browse files Browse the repository at this point in the history
## Description
Display gas estimation of transactions that will be executed

## Related Issue Or Context
Closes: #157

## How Has This Been Tested? Testing details.
- [x] Mocked gas fee test 

## Types of changes
- [x] Transactions gas fee estimations for evm and substrate networks

## Checklist:
- [x] Add tests.
  • Loading branch information
saadahmsiddiqui authored Apr 22, 2024
1 parent 21ceb18 commit 7114f62
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import '../../common/buttons/button';
import '../../address-input';
import '../../resource-amount-selector';
import './transfer-button';
import './transfer-detail';
import './transfer-status';
import '../../network-selector';
import { BaseComponent } from '../../common';
Expand Down Expand Up @@ -171,6 +172,7 @@ export class FungibleTokenTransfer extends BaseComponent {
this.renderAmountOnDestination()
)}
<sygma-fungible-transfer-detail
.estimatedGasFee=${this.transferController.estimatedGas}
.selectedResource=${this.transferController.selectedResource}
.fee=${this.transferController.fee}
.sourceDomainConfig=${this.transferController.sourceDomainConfig}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { css } from 'lit';

export const styles = css`
.transferDetail {
gap: 0.5rem;
display: flex;
flex-direction: column;
}
.transferDetailContainer {
display: flex;
font-size: 0.75rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type { HTMLTemplateResult } from 'lit';
import { html } from 'lit';
import { when } from 'lit/directives/when.js';
import { customElement, property } from 'lit/decorators.js';
import { BigNumber } from 'ethers';
import type { SubstrateFee } from '@buildwithsygma/sygma-sdk-core/substrate';
import { tokenBalanceToNumber } from '../../../../utils/token';
import { BaseComponent } from '../../../common/base-component';
import { styles } from './styles';
Expand All @@ -19,15 +21,21 @@ export class FungibleTransferDetail extends BaseComponent {
static styles = styles;

@property({ type: Object })
fee?: EvmFee;
fee?: EvmFee | SubstrateFee | null;

@property({ type: Object })
selectedResource?: Resource;

@property({ type: Object })
sourceDomainConfig?: BaseConfig<Network>;

getFeeParams(type: FeeHandlerType): { decimals?: number; symbol: string } {
@property({ type: Object })
estimatedGasFee?: BigNumber;

getSygmaFeeParams(type: FeeHandlerType): {
decimals?: number;
symbol: string;
} {
let decimals = undefined;
let symbol = '';

Expand All @@ -49,28 +57,70 @@ export class FungibleTransferDetail extends BaseComponent {
}
}

getFee(): string {
getGasFeeParams(): {
decimals?: number;
symbol: string;
} {
let decimals = undefined;
let symbol = '';
if (this.sourceDomainConfig) {
decimals = Number(this.sourceDomainConfig.nativeTokenDecimals);
symbol = this.sourceDomainConfig.nativeTokenSymbol.toUpperCase();
}
return { decimals, symbol };
}

getSygmaFee(): string {
if (!this.fee) return '';
const { symbol, decimals } = this.getFeeParams(this.fee.type);
const { symbol, decimals } = this.getSygmaFeeParams(this.fee.type);
const { fee } = this.fee;
let _fee = '';

if (decimals) {
_fee = tokenBalanceToNumber(fee, decimals, 4);
// * BigNumber.from(fee.toString()) from
// * substrate gas
// * hex doesn't start with 0x :shrug:
_fee = tokenBalanceToNumber(BigNumber.from(fee.toString()), decimals, 4);
}

return `${_fee} ${symbol}`;
}

getEstimatedGasFee(): string {
if (!this.estimatedGasFee) return '';
const { symbol, decimals } = this.getGasFeeParams();

if (decimals && this.estimatedGasFee) {
const gasFee = tokenBalanceToNumber(this.estimatedGasFee, decimals, 4);
return `${gasFee} ${symbol}`;
}

return 'calculating...';
}

render(): HTMLTemplateResult {
return html`
<section class="transferDetail">
${when(
this.fee !== undefined,
this.fee !== null,
() =>
html`<div class="transferDetailContainer">
<div class="transferDetailContainerLabel">Bridge Fee</div>
<div class="transferDetailContainerValue">${this.getFee()}</div>
<div class="transferDetailContainerValue">
${this.getSygmaFee()}
</div>
</div>`
)}
${when(
this.estimatedGasFee !== undefined,
() =>
html`<div class="transferDetailContainer">
<div class="transferDetailContainerLabel" id="gasFeeLabel">
Gas Fee
</div>
<div class="transferDetailContainerValue" id="gasFeeValue">
${this.getEstimatedGasFee()}
</div>
</div>`
)}
</section>
Expand Down
2 changes: 2 additions & 0 deletions packages/widget/src/controllers/transfers/evm/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export async function buildEvmFungibleTransactions(
!address ||
providerChaiId !== this.sourceNetwork.chainId
) {
this.estimatedGas = undefined;
this.resetFee();
return;
}
Expand Down Expand Up @@ -83,5 +84,6 @@ export async function buildEvmFungibleTransactions(
transfer,
this.fee
);
await this.estimateGas();
this.host.requestUpdate();
}
1 change: 1 addition & 0 deletions packages/widget/src/controllers/transfers/evm/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export async function executeNextEvmTransaction(
this.host.requestUpdate();
await tx.wait();
this.pendingEvmApprovalTransactions.shift();
await this.estimateGas();
} catch (e) {
console.log(e);
this.errorMessage = 'Approval transaction reverted or rejected';
Expand Down
31 changes: 31 additions & 0 deletions packages/widget/src/controllers/transfers/evm/gas-estimate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Web3Provider } from '@ethersproject/providers';
import type { EIP1193Provider } from '@web3-onboard/core';
import { ethers, type BigNumber, type PopulatedTransaction } from 'ethers';

/**
* This method calculate the amount of gas
* list of transactions will cost
* @param {number} chainId blockchain ID
* @param {Eip1193Provider} eip1193Provider EIP compatible provider
* @param {string} sender address of signer connected with provider
* @param {PopulatedTransaction[]} transactions list of EVM transactions
* @returns {Promise<BigNumber>} gas cost in 18 decimals // or chain native decimals
*/
export async function estimateEvmTransactionsGasCost(
chainId: number,
eip1193Provider: EIP1193Provider,
sender: string,
transactions: PopulatedTransaction[]
): Promise<BigNumber> {
const provider = new Web3Provider(eip1193Provider, chainId);
const signer = provider.getSigner(sender);

let cost = ethers.constants.Zero;
for (const transaction of transactions) {
const _cost = await signer.estimateGas(transaction);
cost = cost.add(_cost);
}

const gasPrice = await provider.getGasPrice();
return gasPrice.mul(cost);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
getRoutes
} from '@buildwithsygma/sygma-sdk-core';
import { ContextConsumer } from '@lit/context';
import type { UnsignedTransaction, BigNumber } from 'ethers';
import { ethers } from 'ethers';
import { BigNumber, ethers } from 'ethers';
import type { UnsignedTransaction, PopulatedTransaction } from 'ethers';
import type { ReactiveController, ReactiveElement } from 'lit';
import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { ApiPromise, SubmittableResult } from '@polkadot/api';
Expand All @@ -33,6 +33,7 @@ import {
buildSubstrateFungibleTransactions,
executeNextSubstrateTransaction
} from './substrate';
import { estimateEvmTransactionsGasCost } from './evm/gas-estimate';

export type SubstrateTransaction = SubmittableExtrinsic<
'promise',
Expand Down Expand Up @@ -71,6 +72,7 @@ export class FungibleTokenTransferController implements ReactiveController {
public supportedDestinationNetworks: Domain[] = [];
public supportedResources: Resource[] = [];
public fee: EvmFee | SubstrateFee | null = null;
public estimatedGas: BigNumber | undefined;

//Evm transfer
protected buildEvmTransactions = buildEvmFungibleTransactions;
Expand Down Expand Up @@ -208,6 +210,7 @@ export class FungibleTokenTransferController implements ReactiveController {
this.waitingTxExecution = false;
this.waitingUserConfirmation = false;
this.transferTransactionId = undefined;
this.estimatedGas = undefined;
this.resetFee();
void this.init(this.env);
}
Expand Down Expand Up @@ -440,4 +443,58 @@ export class FungibleTokenTransferController implements ReactiveController {
throw new Error('Unsupported network type');
}
}

public async estimateGas(): Promise<void> {
if (!this.sourceNetwork) return;
switch (this.sourceNetwork.type) {
case Network.EVM:
await this.estimateEvmGas();
break;
case Network.SUBSTRATE:
await this.estimateSubstrateGas();
break;
}
}

private async estimateSubstrateGas(): Promise<void> {
if (!this.walletContext.value?.substrateWallet?.signerAddress) return;
const sender = this.walletContext.value?.substrateWallet?.signerAddress;

const paymentInfo = await (
this.pendingTransferTransaction as SubstrateTransaction
).paymentInfo(sender);

const { partialFee } = paymentInfo;
this.estimatedGas = BigNumber.from(partialFee.toString());
}

private async estimateEvmGas(): Promise<void> {
if (
!this.sourceNetwork?.chainId ||
!this.walletContext.value?.evmWallet?.provider ||
!this.walletContext.value.evmWallet.address
)
return;

const state = this.getTransferState();
const transactions = [];

switch (state) {
case FungibleTransferState.PENDING_APPROVALS:
transactions.push(...this.pendingEvmApprovalTransactions);
break;
case FungibleTransferState.PENDING_TRANSFER:
transactions.push(this.pendingTransferTransaction);
break;
}

const estimatedGas = await estimateEvmTransactionsGasCost(
this.sourceNetwork?.chainId,
this.walletContext.value?.evmWallet?.provider,
this.walletContext.value.evmWallet.address,
transactions as PopulatedTransaction[]
);

this.estimatedGas = estimatedGas;
}
}
3 changes: 3 additions & 0 deletions packages/widget/src/controllers/transfers/substrate/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export async function buildSubstrateFungibleTransactions(
!substrateProvider ||
!address
) {
this.estimatedGas = undefined;
this.resetFee();
return;
}

Expand All @@ -37,5 +39,6 @@ export async function buildSubstrateFungibleTransactions(
transfer,
this.fee
);
await this.estimateGas();
this.host.requestUpdate();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
Network,
ResourceType
} from '@buildwithsygma/sygma-sdk-core';
import type { BigNumber } from 'ethers';
import { constants } from 'ethers';
import { parseUnits } from 'ethers/lib/utils';
import { parseEther, parseUnits } from 'ethers/lib/utils';
import { FungibleTransferDetail } from '../../../../src/components/transfer/fungible/transfer-detail';

describe('sygma-fungible-transfer-detail', function () {
Expand Down Expand Up @@ -42,6 +43,8 @@ describe('sygma-fungible-transfer-detail', function () {
resources: []
};

const mockedEstimatedGas: BigNumber = parseEther('0.0004');

afterEach(() => {
fixtureCleanup();
});
Expand All @@ -67,7 +70,7 @@ describe('sygma-fungible-transfer-detail', function () {
assert.isNotNull(transferDetail);
});

it('shows fee correctly', async () => {
it('shows sygma fee correctly', async () => {
const value = '1.02 ETH';
mockedFee.fee = parseUnits('1.02', 18);

Expand All @@ -85,4 +88,23 @@ describe('sygma-fungible-transfer-detail', function () {

assert.include(transferDetail.innerHTML, value);
});

it('shows gas fee correctly', async () => {
const MOCKED_GAS = '0.0004 ETH';

const el = await fixture(html`
<sygma-fungible-transfer-detail
.fee=${mockedFee}
.selectedResource=${mockedResource}
.sourceDomainConfig=${mockedSourceDomainConfig}
.estimatedGasFee=${mockedEstimatedGas}
></sygma-fungible-transfer-detail>
`);

const valueElements = el.shadowRoot!.querySelector(
'#gasFeeValue'
) as HTMLElement;

assert.equal(valueElements.textContent?.trim(), MOCKED_GAS);
});
});

0 comments on commit 7114f62

Please sign in to comment.