UNPKG

@ajna-finance/sdk

Version:

A typescript SDK that can be used to create Dapps in Ajna ecosystem.

249 lines 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NonfungiblePool = void 0; const tslib_1 = require("tslib"); const ethers_1 = require("ethers"); const constants_1 = require("../constants"); const erc721_1 = require("../contracts/erc721"); const erc721_pool_1 = require("../contracts/erc721-pool"); const pool_1 = require("../contracts/pool"); const types_1 = require("../types"); const numeric_1 = require("../utils/numeric"); const pricing_1 = require("../utils/pricing"); const time_1 = require("../utils/time"); const NonfungibleBucket_1 = require("./NonfungibleBucket"); const Pool_1 = require("./Pool"); class NonfungiblePool extends Pool_1.Pool { constructor(provider, poolAddress, ajnaAddress) { super(provider, poolAddress, ajnaAddress, (0, erc721_pool_1.getErc721PoolContract)(poolAddress, provider), (0, erc721_pool_1.getErc721PoolContractMulti)(poolAddress)); this.isSubset = false; } initialize() { const _super = Object.create(null, { initialize: { get: () => super.initialize } }); return tslib_1.__awaiter(this, void 0, void 0, function* () { yield _super.initialize.call(this); const collateralToken = (0, erc721_1.getNftContract)(this.collateralAddress, this.provider); this.collateralSymbol = (yield collateralToken.symbol()).replace(/"+/g, ''); this.name = this.collateralSymbol + '-' + this.quoteSymbol; this.isSubset = yield this.contract.isSubset(); }); } toString() { return `${this.name} ${this.isSubset ? 'subset' : 'collection'} pool`; } /** * Approve this pool to transfer an NFT. * @param signer pool user * @param tokenId NFT token id * @returns promise to transaction */ collateralApprove(signer, tokenId) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return (0, erc721_pool_1.approve)(signer, this.poolAddress, this.collateralAddress, tokenId); }); } /** * Approve this pool to transfer multiple NFTs. * @param signer pool user * @returns promise to transaction */ collateralApproveAll(signer) { return tslib_1.__awaiter(this, void 0, void 0, function* () { return (0, erc721_pool_1.approveAll)(signer, this.poolAddress, this.collateralAddress); }); } /** * Deposit one or more NFTs into a bucket (not for borrowers). * @param signer address to be awarded LP * @param bucketIndex identifies the price bucket * @param tokenIdsToAdd identifies NFTs to deposit * @param ttlSeconds revert if not processed in this amount of time * @returns promise to transaction */ addCollateral(signer, bucketIndex, tokenIdsToAdd, ttlSeconds) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const contractPoolWithSigner = this.contract.connect(signer); return (0, erc721_pool_1.addCollateral)(contractPoolWithSigner, tokenIdsToAdd, bucketIndex, yield (0, time_1.getExpiry)(this.provider, ttlSeconds)); }); } /** * Merge collateral accross a number of buckets, `removalIndexes` reconstitute an `NFT`. * @param signer address to merge LPB and potentially remove whole NFTs * @param removalIndexes Array of bucket indexes to remove all collateral that the caller has ownership over. * @param noOfNFTsToRemove Intergral number of `NFT`s to remove if collateral amount is met `noOfNFTsToRemove`, else merge at bucket index, `toIndex`. * @param toIndex The bucket index to which merge collateral into. * @returns promise to transaction */ mergeOrRemoveCollateral(signer, removalIndexes, noOfNFTsToRemove, toIndex) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const contractPoolWithSigner = this.contract.connect(signer); return (0, erc721_pool_1.mergeOrRemoveCollateral)(contractPoolWithSigner, removalIndexes, noOfNFTsToRemove, toIndex); }); } /** * Withdraw collateral from a bucket (not for borrowers). * @param signer address to redeem LP * @param bucketIndex identifies the price bucket * @param maxAmount optionally limits amount to remove * @returns promise to transaction */ removeCollateral(signer, bucketIndex, noOfNFTsToRemove) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const contractPoolWithSigner = this.contract.connect(signer); return (0, erc721_pool_1.removeCollateral)(contractPoolWithSigner, noOfNFTsToRemove, bucketIndex); }); } /** * Pledges collateral and draws debt. * @param signer borrower * @param amountToBorrow new debt to draw * @param tokenIdsToPledge identifies NFTs to deposit as collateral * @param limitIndex revert if loan would drop LUP below this bucket (or pass MAX_FENWICK_INDEX) * @returns promise to transaction */ drawDebt(signer, amountToBorrow, tokenIdsToPledge, limitIndex) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const contractPoolWithSigner = this.contract.connect(signer); const borrowerAddress = yield signer.getAddress(); return (0, erc721_pool_1.drawDebt)(contractPoolWithSigner, borrowerAddress, amountToBorrow, limitIndex !== null && limitIndex !== void 0 ? limitIndex : constants_1.MAX_FENWICK_INDEX, tokenIdsToPledge); }); } /** * Repays debt and pulls collateral. * @param signer borrower * @param maxQuoteTokenAmountToRepay amount for partial repayment, MaxUint256 for full repayment, 0 for no repayment * @param noOfNFTsToPull number of NFTs to withdraw after repayment * @param limitIndex revert if LUP has moved below this bucket by the time the transaction is processed * @returns promise to transaction */ repayDebt(signer, maxQuoteTokenAmountToRepay, noOfNFTsToPull, limitIndex) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const contractPoolWithSigner = this.contract.connect(signer); const sender = yield signer.getAddress(); return (0, erc721_pool_1.repayDebt)(contractPoolWithSigner, sender, maxQuoteTokenAmountToRepay, noOfNFTsToPull, sender, limitIndex !== null && limitIndex !== void 0 ? limitIndex : constants_1.MAX_FENWICK_INDEX); }); } /** * @param bucketIndex fenwick index of the desired bucket * @returns {@link NonfungibleBucket} modeling bucket at specified index */ getBucketByIndex(bucketIndex) { const bucket = new NonfungibleBucket_1.NonfungibleBucket(this.provider, this, bucketIndex); return bucket; } /** * @param price price within range supported by Ajna * @returns {@link NonfungibleBucket} modeling bucket at nearest to specified price */ getBucketByPrice(price) { const bucketIndex = (0, pricing_1.priceToIndex)(price); // priceToIndex should throw upon invalid price const bucket = new NonfungibleBucket_1.NonfungibleBucket(this.provider, this, bucketIndex); return bucket; } /** * @param minPrice lowest desired price * @param maxPrice highest desired price * @returns array of {@link NonfungibleBucket}s between specified prices */ getBucketsByPriceRange(minPrice, maxPrice) { if (minPrice.gt(maxPrice)) throw new types_1.SdkError('maxPrice must exceed minPrice'); const buckets = new Array(); for (let index = (0, pricing_1.priceToIndex)(maxPrice); index <= (0, pricing_1.priceToIndex)(minPrice); index++) { buckets.push(new NonfungibleBucket_1.NonfungibleBucket(this.provider, this, index)); } return buckets; } totalBorrowerTokens(borrower) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const contractPool = this.contract.connect(this.provider); return yield contractPool.totalBorrowerTokens(borrower); }); } totalBucketTokens(borrower) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const contractPool = this.contract.connect(this.provider); return yield contractPool.totalBucketTokens(borrower); }); } /** * Withdraw all available liquidity from the given buckets using multicall transaction (first quote token, then - collateral if LP is left). * @param signer address to redeem LP * @param bucketIndices array of bucket indices to withdraw liquidity from * @returns promise to transaction */ withdrawLiquidity(signer, bucketIndices) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const signerAddress = yield signer.getAddress(); const callData = []; // sort bucketIndices in ascending index order (descendingPrice) for use in mergeOrRemoveCollateral const sortedBucketIndexes = bucketIndices.sort((a, b) => a - b); // get buckets const bucketPromises = sortedBucketIndexes.map(bucketIndex => this.getBucketByIndex(bucketIndex)); const buckets = yield Promise.all(bucketPromises); // get bucket details const bucketStatusPromises = buckets.map(bucket => bucket.getStatus()); const bucketStatuses = yield Promise.all(bucketStatusPromises); // determine lender's LP balance const lpBalancePromises = sortedBucketIndexes.map(bucketIndex => (0, pool_1.lenderInfo)(this.contract, signerAddress, bucketIndex)); const lpBalances = yield Promise.all(lpBalancePromises); let surplusCollateral = ethers_1.BigNumber.from(0); const surplusIndexes = []; for (let i = 0; i < sortedBucketIndexes.length; ++i) { const [lpBalance] = lpBalances[i]; const bucketStatus = bucketStatuses[i]; const bucketIndex = sortedBucketIndexes[i]; // if there is any quote token in the bucket, redeem LP for deposit first if (lpBalance && bucketStatus.deposit.gt(0)) { callData.push({ methodName: 'removeQuoteToken', args: [ethers_1.constants.MaxUint256, bucketIndex], }); } const depositWithdrawnEstimate = (0, numeric_1.wmul)(lpBalance, bucketStatus.exchangeRate); // TODO: add slippage param to increase tx success likelihood? // CAUTION: This estimate may cause revert because we cannot predict exchange rate for an // arbitrary future block where the TX will be processed. const withdrawCollateral = (bucketStatus.deposit.eq(0) || depositWithdrawnEstimate.gt(bucketStatus.deposit)) && bucketStatus.collateral.gt(0); // attempt to remove collateral if there is an integer number of NFTs available for claiming if (withdrawCollateral) { // TODO: track the surplus tokens after const tokensToRemove = (0, numeric_1.wadToIntRoundingDown)(bucketStatus.collateral); if (tokensToRemove > 0) { callData.push({ methodName: 'removeCollateral', args: [tokensToRemove, bucketIndex], }); // track any surplus collateral in the bucket after removal to be used for mergeOrRemoveCollateral const postRemoveSurplus = bucketStatus.collateral.sub((0, numeric_1.toWad)(tokensToRemove)); if (postRemoveSurplus.gt(0)) { surplusCollateral = surplusCollateral.add(postRemoveSurplus); surplusIndexes.push(bucketIndex); } } else { // track any surplus collateral associated with the lender and bucket to be used for mergeOrRemoveCollateral surplusCollateral = surplusCollateral.add(bucketStatus.collateral); surplusIndexes.push(bucketIndex); } } } // if there was enough surplus collateral across the lender's buckets, call mergeOrRemoveCollateral on the surplus if (surplusCollateral.gt(1)) { const mergeOrRemoveToIndex = surplusIndexes[surplusIndexes.length - 1]; const tokensToRemove = (0, numeric_1.wadToIntRoundingDown)(surplusCollateral); callData.push({ methodName: 'mergeOrRemoveCollateral', args: [surplusIndexes, tokensToRemove, mergeOrRemoveToIndex], }); } return this.multicall(signer, callData); }); } } exports.NonfungiblePool = NonfungiblePool; //# sourceMappingURL=NonfungiblePool.js.map