@ajna-finance/sdk
Version:
A typescript SDK that can be used to create Dapps in Ajna ecosystem.
249 lines • 13.2 kB
JavaScript
;
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