diff --git a/src/api.ts b/src/api.ts index a3cc95e..77d459f 100644 --- a/src/api.ts +++ b/src/api.ts @@ -113,7 +113,7 @@ export class CyanAPI { return await this.fetchData(`/v2/plans?wallet=${address}`); } - public async getCollectionTopBid(body: IGetCollectionTopBid['request']): Promise { + public async getCollectionTopBids(body: IGetCollectionTopBid['request']): Promise { const { collectionAddress, tokenId, chain } = body; const queryParams = new URLSearchParams({ chain: chain as string, diff --git a/src/sdk.ts b/src/sdk.ts index b5d9197..2758719 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -427,110 +427,24 @@ export class CyanSDK { return await factoryContract.getOrDeployWallet(await this.signer.getAddress()); } - public async getOrCreateCyanWallet(): Promise { - try { - return await this.getCyanWallet(); - } catch (e) { - if (e instanceof NoWalletError) { - const tx = await this.createCyanWallet(); - await tx.wait(); - return await this.getCyanWallet(); - } - throw e; - } - } - - public _setConfigs(configs: IConfigs): void { - this.configs = configs; - } - - private async _pay(plan: IPlan, isEarlyRepayment = false): Promise { - const contract = PaymentPlanV2Factory.connect(plan.paymentPlanContractAddress, this.signer); - const currencyAddress = await contract.getCurrencyAddressByPlanId(plan.planId); - const isNativeCurrency = currencyAddress === ethConsts.AddressZero; - const { currentPayment } = await this.getPaymentInfo(plan, isEarlyRepayment); - - if (!isNativeCurrency) { - await this.checkAndAllowCurrencyForPlan(currencyAddress, BigNumber.from(currentPayment)); - } - return await contract.pay(plan.planId, isEarlyRepayment, { - value: isNativeCurrency ? currentPayment : 0, - }); - } - - private async _buildStep2Request(args: ISdkPricerStep2['params']): Promise { - const { wallet, items, option, currencyAddress, autoRepayStatus } = args; - const chain = await this._getChain(); - return { - chain, - items: items.map(item => ({ - address: item.address, - tokenId: item.tokenId, - itemType: item.itemType, - amount: item.amount, - })), - currencyAddress, - option: [option.term, option.totalNumberOfPayments, option.loanRate], - autoRepayStatus, - wallet, - }; - } - - private _buildStep2Result(args: ISdkPricerStep2['params'] & IPricerStep2['response']): ISdkPricerStep2['result'] { - const { items, option, autoRepayStatus, blockNumber, plans } = args; - - return plans.map((plan, i) => { - if (!isNonErrored(plan)) { - return plan; - } - - return { - item: { - amount: items[i].amount, - tokenId: items[i].tokenId, - contractAddress: items[i].address, - cyanVaultAddress: plan.vaultAddress, - itemType: items[i].itemType, - }, - plan: { - amount: plan.price.mul(option.downpaymentRate + option.loanRate).div(100_00), - downPaymentPercent: option.downpaymentRate, - interestRate: plan.interestRate, - serviceFeeRate: option.serviceFeeRate, - term: option.term, - totalNumberOfPayments: option.totalNumberOfPayments, - counterPaidPayments: option.counterPaidPayments, - autoRepayStatus: autoRepayStatus, - }, - planId: plan.planId, - blockNum: blockNumber, - signature: plan.signature, - isChanged: items[i].price.eq(plan.price), - marketName: plan.marketName, - }; - }); - } - - private async _getChain(): Promise { - const chainId = await this.signer.getChainId(); - const CHAINS: Record = { - 1: 'mainnet', - 5: 'goerli', - 11155111: 'sepolia', - 137: 'polygon', - 80001: 'mumbai', - 42161: 'arbitrum', - 56: 'bsc', - 10: 'optimism', - }; - return CHAINS[chainId]; - } - public async getTopBid(collectionAddress: string, tokenId?: string): Promise { + /** + * Retrieves top bids of the specific collection or token + * @param {string} collectionAddress - The collection address + * @param {string} tokenId - The token id + * @returns {Promise} The top bid result + */ + public async getTopBids(collectionAddress: string, tokenId?: string): Promise { const chain = await this._getChain(); - const topBids = await this.api.getCollectionTopBid({ chain, collectionAddress, tokenId }); + const topBids = await this.api.getCollectionTopBids({ chain, collectionAddress, tokenId }); return topBids; } + /** + * Fulfill bid offer of the plan + * @param {IPlan} plan - Plan object to sell + * @param {IOffer} offer - Offer objects to accept + * @returns {Promise} + */ public async fulfillOffer(plan: IPlan, offer: IOffer): Promise { const chain = await this._getChain(); const contract = PaymentPlanV2Factory.connect(plan.paymentPlanContractAddress, this.signer); @@ -544,7 +458,18 @@ export class CyanSDK { return await contract.earlyUnwind(plan.planId, offer.price.netAmount.raw, offerFulfillment); } - public async payBulk(plans: IPlan[], isEarlyPayment: boolean, releaseWallet: string): Promise { + /** + * Pays the payment for payment plans + * @param {Array} plans - The payment plan objects + * @param {boolean} isEarlyPayment - The flag to indicate if this is early payment or not + * @param {string} releaseWallet - The wallet address to release the token + * @returns {Promise} A Promise that resolves to a ContractTransaction object. + */ + public async payBulk( + plans: IPlan[], + isEarlyPayment: boolean, + releaseWallet?: string + ): Promise { const plansWithNextPayment = await Promise.all( plans.map(async plan => { const nextPayment = await this.getPaymentInfo(plan, isEarlyPayment); @@ -613,6 +538,7 @@ export class CyanSDK { const totalNumOfPaymentsLeft = plan.totalNumOfPayments - plan.currentNumOfPayments; if ( (totalNumOfPaymentsLeft === 1 || isEarlyPayment) && + releaseWallet && cyanWallet.address.toLowerCase() !== releaseWallet.toLowerCase() ) { if (plan.tokenType === INftType.ERC1155) { @@ -646,6 +572,105 @@ export class CyanSDK { } return await cyanWallet.executeBatch(bulkTransactions); } + + public async getOrCreateCyanWallet(): Promise { + try { + return await this.getCyanWallet(); + } catch (e) { + if (e instanceof NoWalletError) { + const tx = await this.createCyanWallet(); + await tx.wait(); + return await this.getCyanWallet(); + } + throw e; + } + } + + public _setConfigs(configs: IConfigs): void { + this.configs = configs; + } + + private async _pay(plan: IPlan, isEarlyRepayment = false): Promise { + const contract = PaymentPlanV2Factory.connect(plan.paymentPlanContractAddress, this.signer); + const currencyAddress = await contract.getCurrencyAddressByPlanId(plan.planId); + const isNativeCurrency = currencyAddress === ethConsts.AddressZero; + const { currentPayment } = await this.getPaymentInfo(plan, isEarlyRepayment); + + if (!isNativeCurrency) { + await this.checkAndAllowCurrencyForPlan(currencyAddress, BigNumber.from(currentPayment)); + } + return await contract.pay(plan.planId, isEarlyRepayment, { + value: isNativeCurrency ? currentPayment : 0, + }); + } + + private async _buildStep2Request(args: ISdkPricerStep2['params']): Promise { + const { wallet, items, option, currencyAddress, autoRepayStatus } = args; + const chain = await this._getChain(); + return { + chain, + items: items.map(item => ({ + address: item.address, + tokenId: item.tokenId, + itemType: item.itemType, + amount: item.amount, + })), + currencyAddress, + option: [option.term, option.totalNumberOfPayments, option.loanRate], + autoRepayStatus, + wallet, + }; + } + + private _buildStep2Result(args: ISdkPricerStep2['params'] & IPricerStep2['response']): ISdkPricerStep2['result'] { + const { items, option, autoRepayStatus, blockNumber, plans } = args; + + return plans.map((plan, i) => { + if (!isNonErrored(plan)) { + return plan; + } + + return { + item: { + amount: items[i].amount, + tokenId: items[i].tokenId, + contractAddress: items[i].address, + cyanVaultAddress: plan.vaultAddress, + itemType: items[i].itemType, + }, + plan: { + amount: plan.price.mul(option.downpaymentRate + option.loanRate).div(100_00), + downPaymentPercent: option.downpaymentRate, + interestRate: plan.interestRate, + serviceFeeRate: option.serviceFeeRate, + term: option.term, + totalNumberOfPayments: option.totalNumberOfPayments, + counterPaidPayments: option.counterPaidPayments, + autoRepayStatus: autoRepayStatus, + }, + planId: plan.planId, + blockNum: blockNumber, + signature: plan.signature, + isChanged: items[i].price.eq(plan.price), + marketName: plan.marketName, + }; + }); + } + + private async _getChain(): Promise { + const chainId = await this.signer.getChainId(); + const CHAINS: Record = { + 1: 'mainnet', + 5: 'goerli', + 11155111: 'sepolia', + 137: 'polygon', + 80001: 'mumbai', + 42161: 'arbitrum', + 56: 'bsc', + 10: 'optimism', + }; + return CHAINS[chainId]; + } } export default CyanSDK;