diff --git a/package.json b/package.json index b38b37a8..7f771dd6 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@metamask/approval-controller": "^5.1.1", "@metamask/network-controller": "^17.1.0", "@metamask/transaction-controller": "^19.0.1", + "@types/bn.js": "^5.1.5", "@types/jest": "^26.0.22", "@types/node": "^20.10.4", "@typescript-eslint/eslint-plugin": "^5.42.1", @@ -77,7 +78,6 @@ "@metamask/utils": "^8.3.0", "abort-controller": "^3.0.0", "async-mutex": "^0.4.1", - "bignumber.js": "^9.0.1", "bn.js": "^5.2.1", "human-standard-token-abi": "^2.0.0", "web3": "^4.2.2" diff --git a/src/SwapsController.ts b/src/SwapsController.ts index fdfc4295..dd026d47 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -19,7 +19,7 @@ import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller'; import type { TransactionParams } from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; -import { BigNumber } from 'bignumber.js'; +import BN from 'bn.js'; import abiERC20 from 'human-standard-token-abi'; import * as web3 from 'web3'; import type { Web3 as Web3Type } from 'web3'; @@ -335,27 +335,25 @@ export default class SwapsController extends BaseControllerV1< gasLimit, ); - let totalGasInWei: BigNumber; - let maxTotalGasInWei: BigNumber; + let totalGasInWei: BN; + let maxTotalGasInWei: BN; if (isEthGasPriceEstimate(gasFeeEstimates)) { const gasPrice = isCustomEthGasPriceEstimate(customGasFee) ? customGasFee.gasPrice : gasFeeEstimates.gasPrice; - totalGasInWei = tradeGasLimit.times( - gweiDecToWEIBN(gasPrice).toString(16), - 16, - ); + totalGasInWei = tradeGasLimit.mul(gweiDecToWEIBN(gasPrice)); - maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( - gweiDecToWEIBN(gasPrice).toString(16), - 16, - ); + maxTotalGasInWei = new BN(tradeMaxGasLimit).mul(gweiDecToWEIBN(gasPrice)); if (multiLayerL1TradeFeeTotal) { - totalGasInWei = totalGasInWei.plus(multiLayerL1TradeFeeTotal, 16); - maxTotalGasInWei = maxTotalGasInWei.plus(multiLayerL1TradeFeeTotal, 16); + totalGasInWei = totalGasInWei.add( + new BN(multiLayerL1TradeFeeTotal, 16), + ); + maxTotalGasInWei = maxTotalGasInWei.add( + new BN(multiLayerL1TradeFeeTotal, 16), + ); } } else { const estimatedBaseFee = @@ -369,16 +367,14 @@ export default class SwapsController extends BaseControllerV1< gasFeeEstimates.high.suggestedMaxPriorityFeePerGas, ]; - totalGasInWei = tradeGasLimit.times( - gweiDecToWEIBN(estimatedBaseFee) - .add(gweiDecToWEIBN(maxPriorityFeePerGas)) - .toString(16), - 16, + totalGasInWei = tradeGasLimit.mul( + gweiDecToWEIBN(estimatedBaseFee).add( + gweiDecToWEIBN(maxPriorityFeePerGas), + ), ); - maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( - gweiDecToWEIBN(maxFeePerGas).toString(16), - 16, + maxTotalGasInWei = new BN(tradeMaxGasLimit).mul( + gweiDecToWEIBN(maxFeePerGas), ); } @@ -387,17 +383,17 @@ export default class SwapsController extends BaseControllerV1< // It always includes any external fees charged by the quote source. In // addition, if the source asset is NATIVE, trade.value includes the amount // of swapped NATIVE. - const totalInWei = totalGasInWei.plus(trade.value, 16); - const maxTotalInWei = maxTotalGasInWei.plus(trade.value, 16); + const totalInWei = totalGasInWei.add(new BN(trade.value, 16)); + const maxTotalInWei = maxTotalGasInWei.add(new BN(trade.value, 16)); // if value in trade, NATIVE fee will be the gas, if not it will be the total wei const weiFee = sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS - ? totalInWei.minus(sourceAmount, 10) + ? totalInWei.sub(new BN(sourceAmount, 10)) : totalInWei; // sourceAmount is in wei : totalInWei; const maxWeiFee = sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS - ? maxTotalInWei.minus(sourceAmount, 10) + ? maxTotalInWei.sub(new BN(sourceAmount, 10)) : maxTotalInWei; // sourceAmount is in wei : totalInWei; const ethFee = calcTokenAmount(weiFee, 18); const maxEthFee = calcTokenAmount(maxWeiFee, 18); @@ -408,34 +404,34 @@ export default class SwapsController extends BaseControllerV1< ); // fees - const tokenPercentageOfPreFeeDestAmount = new BigNumber(100, 10) - .minus(metaMaskFee, 10) - .div(100); + const tokenPercentageOfPreFeeDestAmount = new BN(100, 10) + .subn(metaMaskFee) + .divn(100); const destinationAmountBeforeMetaMaskFee = decimalAdjustedDestinationAmount.div(tokenPercentageOfPreFeeDestAmount); - const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee.minus( + const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee.sub( decimalAdjustedDestinationAmount, ); const conversionRate = destinationTokenRate ?? 1; - const ethValueOfTokens = decimalAdjustedDestinationAmount.times( - conversionRate, - 10, - ); + const ethValueOfTokens = + decimalAdjustedDestinationAmount.muln(conversionRate); // the more tokens the better - const overallValueOfQuote = ethValueOfTokens.minus(ethFee, 10); + const overallValueOfQuote = ethValueOfTokens.sub(ethFee); const quoteValues: QuoteValues = { aggregator, tradeGasLimit: tradeGasLimit.toString(10), tradeMaxGasLimit: tradeMaxGasLimit.toString(10), - ethFee: ethFee.toFixed(18), - maxEthFee: maxEthFee.toFixed(18), - ethValueOfTokens: ethValueOfTokens.toFixed(18), - overallValueOfQuote: overallValueOfQuote.toFixed(18), - metaMaskFeeInEth: metaMaskFeeInTokens.times(conversionRate).toFixed(18), + ethFee: ethFee.toString(10, 18), + maxEthFee: maxEthFee.toString(10, 18), + ethValueOfTokens: ethValueOfTokens.toString(10, 18), + overallValueOfQuote: overallValueOfQuote.toString(10, 18), + metaMaskFeeInEth: metaMaskFeeInTokens + .muln(conversionRate) + .toString(10, 18), }; return quoteValues; @@ -482,16 +478,17 @@ export default class SwapsController extends BaseControllerV1< gasPrice = gasFee.high.suggestedMaxFeePerGas; } - const maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( - gweiDecToWEIBN(gasPrice).toString(16), - 16, + const maxTotalGasInWei = new BN(tradeMaxGasLimit).mul( + gweiDecToWEIBN(gasPrice), + ); + const maxTotalInWei = maxTotalGasInWei.add( + new BN(trade.value ?? '0x0', 16), ); - const maxTotalInWei = maxTotalGasInWei.plus(trade.value ?? '0x0', 16); const maxWeiFee = sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS - ? maxTotalInWei.minus(sourceAmount, 10) + ? maxTotalInWei.sub(new BN(sourceAmount, 10)) : maxTotalInWei; - const maxEthFee = calcTokenAmount(maxWeiFee, 18).toFixed(18); + const maxEthFee = calcTokenAmount(maxWeiFee, 18).toString(10, 18); return maxEthFee; } @@ -507,7 +504,7 @@ export default class SwapsController extends BaseControllerV1< customGasFee?: CustomEthGasPriceEstimate | CustomGasFee, ): { topAggId: string; quoteValues: { [key: string]: QuoteValues } } { let topAggId = ''; - let overallValueOfBestQuoteForSorting: BigNumber | null = null; + let overallValueOfBestQuoteForSorting: BN | null = null; const quoteValues: { [key: string]: QuoteValues } = {}; @@ -520,9 +517,7 @@ export default class SwapsController extends BaseControllerV1< ); quoteValues[quoteValue.aggregator] = quoteValue; - const bnOverallValueOfQuote = new BigNumber( - quoteValue.overallValueOfQuote, - ); + const bnOverallValueOfQuote = new BN(quoteValue.overallValueOfQuote); if ( !overallValueOfBestQuoteForSorting || bnOverallValueOfQuote.gt(overallValueOfBestQuoteForSorting) diff --git a/src/swapsInterfaces.ts b/src/swapsInterfaces.ts index 03f77313..543148b9 100644 --- a/src/swapsInterfaces.ts +++ b/src/swapsInterfaces.ts @@ -1,5 +1,5 @@ import type { TransactionParams } from '@metamask/transaction-controller'; -import type { BigNumber } from 'bignumber.js'; +import type BN from 'bn.js'; export enum APIType { TRADES = 'TRADES', @@ -123,10 +123,10 @@ type QuoteTransaction = { * @interface QuoteSavings */ export type QuoteSavings = { - total: BigNumber; - performance: BigNumber; - fee: BigNumber; - medianMetaMaskFee: BigNumber; + total: BN; + performance: BN; + fee: BN; + medianMetaMaskFee: BN; }; /** diff --git a/src/swapsUtil.test.ts b/src/swapsUtil.test.ts index 20bfbcdf..9902849c 100644 --- a/src/swapsUtil.test.ts +++ b/src/swapsUtil.test.ts @@ -1,4 +1,5 @@ -import { BigNumber } from 'bignumber.js'; +import BN from 'bn.js'; +import { remove0x } from '@metamask/utils'; import type { SwapsToken } from './swapsInterfaces'; import { APIType } from './swapsInterfaces'; @@ -727,24 +728,24 @@ describe('SwapsUtil', () => { }); describe('getMedian', () => { - const numbers = [...Array(9).keys()].map((i) => new BigNumber(i + 1)); - const largeNumbers = numbers.map((i) => i.multipliedBy(100)); + const numbers = [...Array(9).keys()].map((i) => new BN(i + 1)); + const largeNumbers = numbers.map((i) => i.muln(100)); it.each([ [numbers, '5'], [largeNumbers, '500'], ])('returns the middle value', (values, result) => { const middleValue = swapsUtil.getMedian(values); - expect(middleValue).toBeInstanceOf(BigNumber); + expect(middleValue).toBeInstanceOf(BN); expect(middleValue.toString(10)).toBe(result); }); it.each([ - [[...numbers, new BigNumber(10)], '5.5'], - [[...largeNumbers, new BigNumber(1000)], '550'], + [[...numbers, new BN(10)], '5.5'], + [[...largeNumbers, new BN(1000)], '550'], ])('returns the median value', (values, result) => { const medianValue = swapsUtil.getMedian(values); - expect(medianValue).toBeInstanceOf(BigNumber); + expect(medianValue).toBeInstanceOf(BN); expect(medianValue.toString(10)).toBe(result); }); @@ -803,10 +804,10 @@ describe('SwapsUtil', () => { gasMultiplier, null, ); - const limit: BigNumber = new BigNumber(gasEstimateWithRefund); + const limit: BN = new BN(gasEstimateWithRefund); expect(tradeGasLimit.toString(16)).toStrictEqual(limit.toString(16)); expect(tradeMaxGasLimit.toString(16)).toStrictEqual( - new BigNumber(gasEstimate).times(gasMultiplier).toString(16), + new BN(gasEstimate).muln(gasMultiplier).toString(16), ); }); @@ -822,7 +823,7 @@ describe('SwapsUtil', () => { ); expect(tradeGasLimit.toString()).toStrictEqual(averageGas.toString()); expect(tradeMaxGasLimit.toString(16)).toStrictEqual( - new BigNumber(customGasLimit).toString(16), + new BN(customGasLimit).toString(16), ); }); @@ -836,10 +837,10 @@ describe('SwapsUtil', () => { gasMultiplier, customGasLimit, ); - const limit: BigNumber = new BigNumber(gasEstimateWithRefund); + const limit: BN = new BN(remove0x(gasEstimateWithRefund)); expect(tradeGasLimit.toString(16)).toStrictEqual(limit.toString(16)); expect(tradeMaxGasLimit.toString(16)).toStrictEqual( - new BigNumber(customGasLimit).toString(16), + new BN(remove0x(customGasLimit)).toString(16), ); }); }); diff --git a/src/swapsUtil.ts b/src/swapsUtil.ts index 45ff28dc..b8ee5746 100644 --- a/src/swapsUtil.ts +++ b/src/swapsUtil.ts @@ -7,9 +7,8 @@ import { } from '@metamask/controller-utils'; import type { TransactionParams } from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; -import { add0x } from '@metamask/utils'; -import { BigNumber } from 'bignumber.js'; -import { BN } from 'bn.js'; +import { add0x, remove0x } from '@metamask/utils'; +import BN from 'bn.js'; import { ALLOWED_CONTRACT_ADDRESSES, @@ -442,12 +441,12 @@ export function calculateGasEstimateWithRefund( maxGas: number | null, estimatedRefund: number | null, estimatedGas: string | null, -): BigNumber { - const estimated = estimatedGas ? add0x(estimatedGas) : '0x0'; - const maxGasMinusRefund = new BigNumber(maxGas ?? MAX_GAS_LIMIT, 10).minus( - estimatedRefund ?? 0, +): BN { + const estimated = estimatedGas ? remove0x(estimatedGas) : '0'; + const maxGasMinusRefund = new BN(maxGas ?? MAX_GAS_LIMIT, 10).sub( + new BN(estimatedRefund ?? 0), ); - const estimatedGasBN = new BigNumber(estimated); + const estimatedGasBN = new BN(estimated, 16); const gasEstimateWithRefund = maxGasMinusRefund.lt(estimatedGasBN) ? maxGasMinusRefund : estimatedGasBN; @@ -475,20 +474,18 @@ export function getSwapsTokensReceived( postBalance: string, ): string | undefined { if (destinationToken.address === NATIVE_SWAPS_TOKEN_ADDRESS) { - const approvalTransactionGasCost = new BigNumber( + const approvalTransactionGasCost = new BN( approvalTransaction?.gasPrice ?? '0x0', - ).times(approvalReceipt?.gasUsed ?? '0x0'); - const transactionGas = new BigNumber(transaction?.gasPrice ?? '0x0').times( - receipt?.gasUsed ?? '0x0', + ).mul(new BN(approvalReceipt?.gasUsed ?? '0x0')); + const transactionGas = new BN(transaction?.gasPrice ?? '0x0').mul( + new BN(receipt?.gasUsed ?? '0x0'), ); - const totalGasCost = transactionGas.plus(approvalTransactionGasCost); + const totalGasCost = transactionGas.add(approvalTransactionGasCost); - const previousBalanceMinusGas = new BigNumber(previousBalance).minus( - totalGasCost, - ); - const postBalanceMinusGas = new BigNumber(postBalance); + const previousBalanceMinusGas = new BN(previousBalance).mul(totalGasCost); + const postBalanceMinusGas = new BN(postBalance); - return postBalanceMinusGas.minus(previousBalanceMinusGas).toString(16); + return postBalanceMinusGas.sub(previousBalanceMinusGas).toString(16); } if (!receipt?.logs || receipt.status === '0x0') { @@ -518,15 +515,15 @@ export function getSwapsTokensReceived( } /** - * Calculates the median of a sample of BigNumber values. - * @param values - A sample of BigNumber values. + * Calculates the median of a sample of BN values. + * @param values - A sample of BN values. * @returns The median of the sample. */ -export function getMedian(values: BigNumber[]) { +export function getMedian(values: BN[]) { if (!Array.isArray(values) || values.length === 0) { throw new Error('Expected non-empty array param.'); } - const sorted = [...values].sort((a, b) => a.comparedTo(b)); + const sorted = [...values].sort((a, b) => a.cmp(b)); if (sorted.length % 2 === 1) { // return middle value @@ -534,7 +531,7 @@ export function getMedian(values: BigNumber[]) { } // return mean of middle two values const upperIndex = sorted.length / 2; - return sorted[upperIndex].plus(sorted[upperIndex - 1]).div(2); + return sorted[upperIndex].add(sorted[upperIndex - 1]).div(new BN(2)); } /** @@ -548,9 +545,9 @@ export function getMedianEthValueQuote(quotes: QuoteValues[]) { } quotes.sort((quoteA, quoteB) => { - const overallValueOfQuoteA = new BigNumber(quoteA.overallValueOfQuote, 10); - const overallValueOfQuoteB = new BigNumber(quoteB.overallValueOfQuote, 10); - return overallValueOfQuoteA.comparedTo(overallValueOfQuoteB); + const overallValueOfQuoteA = new BN(quoteA.overallValueOfQuote, 10); + const overallValueOfQuoteB = new BN(quoteB.overallValueOfQuote, 10); + return overallValueOfQuoteA.cmp(overallValueOfQuoteB); }); if (quotes.length % 2 === 1) { @@ -585,23 +582,17 @@ export function getMedianEthValueQuote(quotes: QuoteValues[]) { ); return { - ethFee: new BigNumber(feesAndValueAtUpperIndex.ethFee, 10) - .plus(feesAndValueAtLowerIndex.ethFee, 10) - .dividedBy(2) + ethFee: new BN(feesAndValueAtUpperIndex.ethFee, 10) + .add(new BN(feesAndValueAtLowerIndex.ethFee, 10)) + .div(new BN(2)) .toString(10), - metaMaskFeeInEth: new BigNumber( - feesAndValueAtUpperIndex.metaMaskFeeInEth, - 10, - ) - .plus(feesAndValueAtLowerIndex.metaMaskFeeInEth, 10) - .dividedBy(2) + metaMaskFeeInEth: new BN(feesAndValueAtUpperIndex.metaMaskFeeInEth, 10) + .add(new BN(feesAndValueAtLowerIndex.metaMaskFeeInEth, 10)) + .div(new BN(2)) .toString(10), - ethValueOfTokens: new BigNumber( - feesAndValueAtUpperIndex.ethValueOfTokens, - 10, - ) - .plus(feesAndValueAtLowerIndex.ethValueOfTokens, 10) - .dividedBy(2) + ethValueOfTokens: new BN(feesAndValueAtUpperIndex.ethValueOfTokens, 10) + .add(new BN(feesAndValueAtLowerIndex.ethValueOfTokens, 10)) + .div(new BN(2)) .toString(10), }; } @@ -615,34 +606,32 @@ export function getMedianEthValueQuote(quotes: QuoteValues[]) { * the passed quote objects. */ function meansOfQuotesFeesAndValue(quotes: QuoteValues[]) { - const feeAndValueSumsAsBigNumbers = quotes.reduce( + const feeAndValueSumsAsBNs = quotes.reduce( (feeAndValueSums, quote) => ({ - ethFee: feeAndValueSums.ethFee.plus(quote.ethFee, 10), - metaMaskFeeInEth: feeAndValueSums.metaMaskFeeInEth.plus( - quote.metaMaskFeeInEth, - 10, + ethFee: feeAndValueSums.ethFee.add(new BN(quote.ethFee, 10)), + metaMaskFeeInEth: feeAndValueSums.metaMaskFeeInEth.add( + new BN(quote.metaMaskFeeInEth, 10), ), - ethValueOfTokens: feeAndValueSums.ethValueOfTokens.plus( - quote.ethValueOfTokens, - 10, + ethValueOfTokens: feeAndValueSums.ethValueOfTokens.add( + new BN(quote.ethValueOfTokens, 10), ), }), { - ethFee: new BigNumber(0, 10), - metaMaskFeeInEth: new BigNumber(0, 10), - ethValueOfTokens: new BigNumber(0, 10), + ethFee: new BN(0, 10), + metaMaskFeeInEth: new BN(0, 10), + ethValueOfTokens: new BN(0, 10), }, ); return { - ethFee: feeAndValueSumsAsBigNumbers.ethFee - .div(quotes.length, 10) + ethFee: feeAndValueSumsAsBNs.ethFee + .div(new BN(quotes.length, 10)) .toString(10), - metaMaskFeeInEth: feeAndValueSumsAsBigNumbers.metaMaskFeeInEth - .div(quotes.length, 10) + metaMaskFeeInEth: feeAndValueSumsAsBNs.metaMaskFeeInEth + .div(new BN(quotes.length, 10)) .toString(10), - ethValueOfTokens: feeAndValueSumsAsBigNumbers.ethValueOfTokens - .div(quotes.length, 10) + ethValueOfTokens: feeAndValueSumsAsBNs.ethValueOfTokens + .div(new BN(quotes.length, 10)) .toString(10), }; } @@ -668,21 +657,19 @@ export function calculateGasLimits( gasLimit: string | null, ) { let tradeGasLimit, tradeMaxGasLimit; - const customGasLimit = gasLimit && new BigNumber(gasLimit, 16); + const customGasLimit = gasLimit && new BN(remove0x(gasLimit), 16); if ( !approvalNeeded && gasEstimate && gasEstimateWithRefund && gasEstimateWithRefund !== '0' ) { - tradeGasLimit = new BigNumber(gasEstimateWithRefund, 16); + tradeGasLimit = new BN(remove0x(gasEstimateWithRefund), 16); tradeMaxGasLimit = - customGasLimit ?? - new BigNumber(gasEstimate).times(gasMultiplier).integerValue(); + customGasLimit ?? new BN(gasEstimate).muln(gasMultiplier); } else { - tradeGasLimit = new BigNumber(averageGas || MAX_GAS_LIMIT, 10); - tradeMaxGasLimit = - customGasLimit ?? new BigNumber(maxGas || MAX_GAS_LIMIT, 10); + tradeGasLimit = new BN(averageGas || MAX_GAS_LIMIT, 10); + tradeMaxGasLimit = customGasLimit ?? new BN(maxGas || MAX_GAS_LIMIT, 10); } return { tradeGasLimit, tradeMaxGasLimit }; } @@ -693,9 +680,9 @@ export function calculateGasLimits( * @param decimals - The decimals. * @returns The token amount. */ -export function calcTokenAmount(value: number | BigNumber, decimals: number) { +export function calcTokenAmount(value: number | BN, decimals: number) { const multiplier = Math.pow(10, Number(decimals || 0)); - return new BigNumber(value).div(multiplier); + return new BN(value).divn(multiplier); } /** diff --git a/yarn.lock b/yarn.lock index 21323790..3d24e97c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1502,7 +1502,7 @@ dependencies: "@babel/types" "^7.20.7" -"@types/bn.js@^5.1.0": +"@types/bn.js@^5.1.0", "@types/bn.js@^5.1.5": version "5.1.5" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== @@ -2050,11 +2050,6 @@ bech32@1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -bignumber.js@^9.0.1: - version "9.1.2" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" - integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== - bin-links@4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-4.0.3.tgz#9e4a3c5900830aee3d7f52178b65e01dcdde64a5"