From c119561bdb6c4213e93c88ad5e74608ddc3b722d Mon Sep 17 00:00:00 2001 From: Ryan Ghods Date: Tue, 2 Jan 2024 17:35:18 -0800 Subject: [PATCH] fix basis points rounding (#1344) * fix basis points rounding * fix * use bigints * fix * improve fn names --- src/constants.ts | 7 ++++--- src/sdk.ts | 20 +++++++++----------- src/utils/utils.ts | 27 +++++++++++++++++++++++---- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index ab6055113..ec38ac974 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,12 +1,13 @@ -import { ethers } from "ethers"; +import { FixedNumber, ZeroAddress } from "ethers"; -export const INVERSE_BASIS_POINT = 10_000; // 100 basis points per 1% +export const FIXED_NUMBER_100 = FixedNumber.fromValue(100); +export const INVERSE_BASIS_POINT = 10_000n; // 100 basis points per 1% export const MAX_EXPIRATION_MONTHS = 1; export const API_BASE_MAINNET = "https://api.opensea.io"; export const API_BASE_TESTNET = "https://testnets-api.opensea.io"; -export const DEFAULT_ZONE = ethers.ZeroAddress; +export const DEFAULT_ZONE = ZeroAddress; export const ENGLISH_AUCTION_ZONE_MAINNETS = "0x110b2b128a9ed1be5ef3232d8e4e41640df5c2cd"; export const ENGLISH_AUCTION_ZONE_TESTNETS = diff --git a/src/sdk.ts b/src/sdk.ts index e80d2bc4a..7b235ae77 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -54,10 +54,11 @@ import { hasErrorCode, getAssetItemType, getAddressAfterRemappingSharedStorefrontAddressToLazyMintAdapterAddress, - feesToBasisPoints, requireValidProtocol, getWETHAddress, isTestChain, + basisPointsForFee, + totalBasisPointsForFees, } from "./utils/utils"; /** @@ -244,12 +245,9 @@ export class OpenSeaSDK { private getAmountWithBasisPointsApplied = ( amount: bigint, - basisPoints: number, + basisPoints: bigint, ): string => { - return ( - (amount * BigInt(basisPoints)) / - BigInt(INVERSE_BASIS_POINT) - ).toString(); + return ((amount * basisPoints) / INVERSE_BASIS_POINT).toString(); }; private async getFees({ @@ -266,10 +264,10 @@ export class OpenSeaSDK { endAmount?: bigint; }): Promise { const collectionFees = collection.fees; - const collectionFeesBasisPoints = feesToBasisPoints(collectionFees); + const collectionFeesBasisPoints = totalBasisPointsForFees(collectionFees); const sellerBasisPoints = INVERSE_BASIS_POINT - collectionFeesBasisPoints; - const getConsiderationItem = (basisPoints: number, recipient?: string) => { + const getConsiderationItem = (basisPoints: bigint, recipient?: string) => { return { token: paymentTokenAddress, amount: this.getAmountWithBasisPointsApplied(startAmount, basisPoints), @@ -288,7 +286,7 @@ export class OpenSeaSDK { } for (const fee of collectionFees) { considerationItems.push( - getConsiderationItem(fee.fee * 100, fee.recipient), + getConsiderationItem(basisPointsForFee(fee), fee.recipient), ); } return considerationItems; @@ -1106,10 +1104,10 @@ export class OpenSeaSDK { // Validation if (startAmount == null || startAmountWei < 0) { - throw new Error(`Starting price must be a number >= 0`); + throw new Error("Starting price must be a number >= 0"); } if (isEther && orderSide === OrderSide.BID) { - throw new Error(`Offers must use wrapped ETH or an ERC-20 token.`); + throw new Error("Offers must use wrapped ETH or an ERC-20 token."); } if (priceDiffWei < 0) { throw new Error( diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 42a5daedf..336844655 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -2,11 +2,12 @@ import { CROSS_CHAIN_SEAPORT_V1_5_ADDRESS, ItemType, } from "@opensea/seaport-js/lib/constants"; -import { ethers } from "ethers"; +import { ethers, FixedNumber } from "ethers"; import { MAX_EXPIRATION_MONTHS, SHARED_STOREFRONT_LAZY_MINT_ADAPTER_CROSS_CHAIN_ADDRESS, SHARED_STOREFRONT_ADDRESSES, + FIXED_NUMBER_100, } from "../constants"; import { Chain, @@ -224,9 +225,27 @@ export const getAddressAfterRemappingSharedStorefrontAddressToLazyMintAdapterAdd * @param fees The fees to sum up * @returns sum of basis points */ -export const feesToBasisPoints = (fees: Fee[]): number => { - const feeBasisPoints = fees.map((fee) => fee.fee * 100); - return feeBasisPoints.reduce((sum, basisPoints) => basisPoints + sum, 0); +export const totalBasisPointsForFees = (fees: Fee[]): bigint => { + const feeBasisPoints = fees.map((fee) => basisPointsForFee(fee)); + const totalBasisPoints = feeBasisPoints.reduce( + (sum, basisPoints) => basisPoints + sum, + 0n, + ); + return totalBasisPoints; +}; + +/** + * Converts a fee to its basis points representation. + * @param fee The fee to convert + * @returns the basis points + */ +export const basisPointsForFee = (fee: Fee): bigint => { + return BigInt( + FixedNumber.fromString(fee.fee.toString()) + .mul(FIXED_NUMBER_100) + .toFormat(0) // format to 0 decimal places to convert to bigint + .toString(), + ); }; /**