UNPKG

@infy-protocol/sdk

Version:

Lend and rent any ERC721s and ERC1155s on supported mainnet and testnet.

279 lines (250 loc) 8.81 kB
import { BigNumber } from 'ethers'; import BigNumberJS from 'bignumber.js'; import { NFTStandard } from './types'; import { WEI_DECIMAL } from './consts'; const sameLength = <T>(a: T[], b: T[]) => a.length === b.length; const validateSameLength = (...args: any[]) => { let prev: any = args[0]; for (const curr of args) { if (!curr) continue; if (!sameLength(prev, curr)) throw new Error('args length variable'); prev = curr; } return true; }; type IObjectKeysValues = | string[] | BigNumber[] | boolean[] | number[] | string[][] | string[][][] | number[][] | [string[], number[]][]; interface IObjectKeys { [key: string]: IObjectKeysValues | undefined; } interface PrepareBatch extends IObjectKeys { nftStandards: NFTStandard[]; nftAddresses: string[]; tokenIds: BigNumber[]; lendAmounts?: BigNumber[]; rentAmounts?: BigNumber[]; maxRentDurations?: number[]; minRentDurations?: number[]; dailyRentPrices?: BigNumber[]; collateralPrices?: BigNumber[]; paymentOptions?: number[]; rentDurations?: number[]; lendingIds?: BigNumber[]; rentingIds?: BigNumber[]; allowedRenters?: string[][][]; } interface PrepareRevenueShareBatch extends IObjectKeys { nftStandards?: NFTStandard[]; nftAddresses: string[]; tokenIds: BigNumber[]; lendAmounts?: BigNumber[]; rentAmounts?: BigNumber[]; maxRentDurations?: number[]; paymentOptions?: number[]; rentDurations?: number[]; lendingIds?: BigNumber[]; rentingIds?: BigNumber[]; upfrontFees?: BigNumber[]; revenueShareInfos?: [string[], number[]][]; allowedRenters?: string[][][]; revenueAmounts?: BigNumber[]; renters?: string[]; revenueTokenAddress?: string[]; } /** * To spend as little gas as possible, arguments must follow a particular format * when passed to the contract. This function prepares whatever inputs you want * to send, and returns the inputs in an optimal format. * * This algorithm's time complexity is pretty awful. But, it will never run on * large arrays, so it doesn't really matter. * @param args */ export const prepareBatch = (args: PrepareBatch) => { if (args.nftAddresses.length === 1) return args; validateSameLength(Object.values(args)); let nfts: Map<string, PrepareBatch> = new Map(); const pb: PrepareBatch = { nftAddresses: [], tokenIds: [], nftStandards: [] }; // O(N), maybe higher because of [...o[k]!, v[i]] const updateNfts = (nftAddresses: string, i: number) => { const o = nfts.get(nftAddresses); for (const [k, v] of Object.entries(args)) { if (!o) throw new Error(`could not find ${nftAddresses}`); if (v) o[k] = [...(o[k] ?? []), v[i]] as IObjectKeysValues; } return nfts; }; const createNft = (nftAddresses: string, i: number) => { nfts.set(nftAddresses, { nftStandards: [args.nftStandards[i]], nftAddresses: [nftAddresses], tokenIds: [args.tokenIds[i]], lendAmounts: args.lendAmounts ? [args.lendAmounts[i]] : undefined, rentAmounts: args.rentAmounts ? [args.rentAmounts[i]] : undefined, maxRentDurations: args.maxRentDurations ? [args.maxRentDurations[i]] : undefined, minRentDurations: args.minRentDurations ? [args.minRentDurations[i]] : undefined, dailyRentPrices: args.dailyRentPrices ? [args.dailyRentPrices[i]] : undefined, collateralPrices: args.collateralPrices ? [args.collateralPrices[i]] : undefined, paymentOptions: args.paymentOptions ? [args.paymentOptions[i]] : undefined, rentDurations: args.rentDurations ? [args.rentDurations[i]] : undefined, lendingIds: args.lendingIds ? [args.lendingIds[i]] : undefined, rentingIds: args.rentingIds ? [args.rentingIds[i]] : undefined, allowedRenters: args.allowedRenters ? [args.allowedRenters[i]] : undefined, }); return nfts; }; // O(2 * N), yikes to 2 const worstArgsort = (tokenIds: BigNumber[]) => { var indices = new Array(tokenIds.length); for (var i = 0; i < tokenIds.length; ++i) indices[i] = i; indices.sort((a, b) => tokenIds[a].lt(tokenIds[b]) ? -1 : tokenIds[a].gt(tokenIds[b]) ? 1 : 0 ); return { sortedTokenID: sortPerIndices(indices, tokenIds), argsort: indices, }; }; const sortPerIndices = (argsort: number[], arr: any[]) => argsort.map(i => arr[i]); // O(N ** M). for each nft loop through all args. M - number of args Object.values(args.nftAddresses).forEach((nft, i) => { if (nfts.has(nft)) nfts = updateNfts(nft, i); else nfts = createNft(nft, i); }); const iterator = nfts.keys(); // O(N * N) while (iterator) { const g = iterator.next().value; if (!g) break; // end of loop const nft = nfts.get(g) as PrepareBatch; const tokenIds = nft.tokenIds as BigNumber[]; const { argsort } = worstArgsort(tokenIds); for (const k of Object.keys(nft)) { if (!nft[k]) continue; const sorted = sortPerIndices(argsort, nft[k] ?? []) as IObjectKeysValues; pb[k] = [...(pb[k] ?? []), ...sorted] as IObjectKeysValues; } } return pb; }; export const prepareRevenueShareBatch = (args: PrepareRevenueShareBatch) => { if (args.nftAddresses.length === 1) return args; validateSameLength(Object.values(args)); let nfts: Map<string, PrepareRevenueShareBatch> = new Map(); const pb: PrepareRevenueShareBatch = { nftAddresses: [], tokenIds: [], nftStandards: [], }; // O(N), maybe higher because of [...o[k]!, v[i]] const updateNfts = (nftAddresses: string, i: number) => { const o = nfts.get(nftAddresses); for (const [k, v] of Object.entries(args)) { if (!o) throw new Error(`could not find ${nftAddresses}`); if (v) o[k] = [...(o[k] ?? []), v[i]] as IObjectKeysValues; } return nfts; }; const createNft = (nftAddresses: string, i: number) => { nfts.set(nftAddresses, { nftStandards: args.nftStandards ? [args.nftStandards[i]] : undefined, nftAddresses: [nftAddresses], tokenIds: [args.tokenIds[i]], lendAmounts: args.lendAmounts ? [args.lendAmounts[i]] : undefined, rentAmounts: args.rentAmounts ? [args.rentAmounts[i]] : undefined, maxRentDurations: args.maxRentDurations ? [args.maxRentDurations[i]] : undefined, paymentOptions: args.paymentOptions ? [args.paymentOptions[i]] : undefined, rentDurations: args.rentDurations ? [args.rentDurations[i]] : undefined, lendingIds: args.lendingIds ? [args.lendingIds[i]] : undefined, rentingIds: args.rentingIds ? [args.rentingIds[i]] : undefined, upfrontFees: args.upfrontFees ? [args.upfrontFees[i]] : undefined, revenueShareInfos: args.revenueShareInfos ? [args.revenueShareInfos[i]] : undefined, allowedRenters: args.allowedRenters ? [args.allowedRenters[i]] : undefined, }); return nfts; }; // O(2 * N), yikes to 2 const worstArgsort = (tokenIds: BigNumber[]) => { var indices = new Array(tokenIds.length); for (var i = 0; i < tokenIds.length; ++i) indices[i] = i; indices.sort((a, b) => tokenIds[a].lt(tokenIds[b]) ? -1 : tokenIds[a].gt(tokenIds[b]) ? 1 : 0 ); return { sortedTokenID: sortPerIndices(indices, tokenIds), argsort: indices, }; }; const sortPerIndices = (argsort: number[], arr: any[]) => argsort.map(i => arr[i]); // O(N ** M). for each nft loop through all args. M - number of args Object.values(args.nftAddresses).forEach((nft, i) => { if (nfts.has(nft)) nfts = updateNfts(nft, i); else nfts = createNft(nft, i); }); const iterator = nfts.keys(); // O(N * N) while (iterator) { const g = iterator.next().value; if (!g) break; // end of loop const nft = nfts.get(g) as PrepareBatch; const tokenIds = nft.tokenIds as BigNumber[]; const { argsort } = worstArgsort(tokenIds); for (const k of Object.keys(nft)) { if (!nft[k]) continue; const sorted = sortPerIndices(argsort, nft[k] ?? []) as IObjectKeysValues; pb[k] = [...(pb[k] ?? []), ...sorted] as IObjectKeysValues; } } return pb; }; export const bigNumberToWei = ( amount: string | number, decimal: string | number = WEI_DECIMAL ): string => { if (isNaN(Number(amount))) { return BigNumberJS(0).toString(); } return new BigNumberJS(amount) .multipliedBy(new BigNumberJS(10).pow(Number(decimal))) .toString(); }; export const bigNumberToEther = ( amount: string | number, decimal: string | number = WEI_DECIMAL ): string => { if (isNaN(Number(amount))) { return BigNumberJS(0).toString(); } return new BigNumberJS(amount) .div(new BigNumberJS(10).pow(Number(decimal))) .toString(); };