@infy-protocol/sdk
Version:
Lend and rent any ERC721s and ERC1155s on supported mainnet and testnet.
279 lines (250 loc) • 8.81 kB
text/typescript
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();
};