diff --git a/packages/subscription-controller/CHANGELOG.md b/packages/subscription-controller/CHANGELOG.md index f4b7e6ac739..257d52c7bea 100644 --- a/packages/subscription-controller/CHANGELOG.md +++ b/packages/subscription-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- fix: `getTokenApproveAmount` precision by using bignumber.js ([#7070](https://github.com/MetaMask/core/pull/7070)) + ## [3.2.0] ### Added diff --git a/packages/subscription-controller/package.json b/packages/subscription-controller/package.json index cb832a418a3..987d726b918 100644 --- a/packages/subscription-controller/package.json +++ b/packages/subscription-controller/package.json @@ -53,7 +53,8 @@ "@metamask/messenger": "^0.3.0", "@metamask/polling-controller": "^15.0.0", "@metamask/transaction-controller": "^61.1.0", - "@metamask/utils": "^11.8.1" + "@metamask/utils": "^11.8.1", + "bignumber.js": "^9.1.2" }, "devDependencies": { "@metamask/auto-changelog": "^3.4.4", diff --git a/packages/subscription-controller/src/SubscriptionController.ts b/packages/subscription-controller/src/SubscriptionController.ts index 40ca1d7a6fd..12d56ad3e9d 100644 --- a/packages/subscription-controller/src/SubscriptionController.ts +++ b/packages/subscription-controller/src/SubscriptionController.ts @@ -9,6 +9,7 @@ import type { AuthenticationController } from '@metamask/profile-sync-controller import type { TransactionMeta } from '@metamask/transaction-controller'; import { TransactionType } from '@metamask/transaction-controller'; import { type Hex } from '@metamask/utils'; +import { BigNumber } from 'bignumber.js'; import { ACTIVE_SUBSCRIPTION_STATUSES, @@ -710,8 +711,10 @@ export class SubscriptionController extends StaticIntervalPollingController()< */ #getSubscriptionPriceAmount(price: ProductPrice) { // no need to use BigInt since max unitDecimals are always 2 for price - const amount = - (price.unitAmount / 10 ** price.unitDecimals) * price.minBillingCycles; + const amount = new BigNumber(price.unitAmount) + .div(10 ** price.unitDecimals) + .multipliedBy(price.minBillingCycles) + .toString(); return amount; } @@ -733,25 +736,14 @@ export class SubscriptionController extends StaticIntervalPollingController()< if (!conversionRate) { throw new Error('Conversion rate not found'); } - // conversion rate is a float string e.g: "1.0" - // We need to handle float conversion rates with integer math for BigInt. - // We'll scale the conversion rate to an integer by multiplying by 10^4. - // conversionRate is in usd decimal. In most currencies, we only care about 2 decimals (cents) - // So, scale must be max of 10 ** 4 (most exchanges trade with max 4 decimals of usd) - // This allows us to avoid floating point math and keep precision. - const SCALE = 10n ** 4n; - const conversionRateScaled = - BigInt(Math.round(Number(conversionRate) * Number(SCALE))) / SCALE; // price of the product - const priceAmount = this.#getSubscriptionPriceAmount(price); - const priceAmountScaled = - BigInt(Math.round(priceAmount * Number(SCALE))) / SCALE; + const priceAmount = new BigNumber(this.#getSubscriptionPriceAmount(price)); - const tokenDecimal = BigInt(10) ** BigInt(tokenPaymentInfo.decimals); - - const tokenAmount = - (priceAmountScaled * tokenDecimal) / conversionRateScaled; - return tokenAmount.toString(); + const tokenDecimal = new BigNumber(10).pow(tokenPaymentInfo.decimals); + const tokenAmount = priceAmount + .multipliedBy(tokenDecimal) + .div(conversionRate); + return tokenAmount.toFixed(0); } /** diff --git a/yarn.lock b/yarn.lock index 37784add1dd..07d8b1fa11c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5090,6 +5090,7 @@ __metadata: "@metamask/utils": "npm:^11.8.1" "@ts-bridge/cli": "npm:^0.6.4" "@types/jest": "npm:^27.4.1" + bignumber.js: "npm:^9.1.2" deepmerge: "npm:^4.2.2" jest: "npm:^27.5.1" sinon: "npm:^9.2.4"