diff --git a/packages/jellyfish-api-core/__tests__/category/icxorderbook/listHTLCs.test.ts b/packages/jellyfish-api-core/__tests__/category/icxorderbook/listHTLCs.test.ts new file mode 100644 index 0000000000..fd5b3e0e4e --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/icxorderbook/listHTLCs.test.ts @@ -0,0 +1,409 @@ +import { ContainerAdapterClient } from '../../container_adapter_client' +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { + ExtHTLC, HTLC, ICXClaimDFCHTLCInfo, ICXDFCHTLCInfo, ICXEXTHTLCInfo, ICXGenericResult, ICXListHTLCOptions, + ICXOfferInfo, ICXOrderInfo, ICXOffer, ICXOrder, ICXHTLCType, ICXHTLCStatus +} from '../../../src/category/icxorderbook' +import BigNumber from 'bignumber.js' +import { setup, accountDFI, idDFI, checkDFISellOrderDetails, accountBTC, checkDFIBuyOfferDetails, checkDFCHTLCDetails, checkEXTHTLCDetails } from './common.test' + +describe('Should test ICXOrderBook.listHTLCs', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + await container.waitForReady() + await container.waitForWalletCoinbaseMaturity() + await setup(container) + }) + + afterAll(async () => { + await container.stop() + }) + + afterEach(async () => { + // cleanup code here + }) + + // common code for some tests until ICX offer + const setupUntilDFIBuyOffer = async (): Promise => { + // create order - maker + const order: ICXOrder = { + tokenFrom: idDFI, + chainTo: 'BTC', + ownerAddress: accountDFI, + receivePubkey: '037f9563f30c609b19fd435a19b8bde7d6db703012ba1aba72e9f42a87366d1941', + amountFrom: new BigNumber(15), + orderPrice: new BigNumber(0.01) + } + + let result: ICXGenericResult = await client.icxorderbook.createOrder(order, []) + const createOrderTxId = result.txid + + await container.generate(1) + + // list ICX orders + let orders: Record = await client.icxorderbook.listOrders() + await checkDFISellOrderDetails(container, order, createOrderTxId, orders as Record) + + // make Offer to partial amout 10 DFI - taker + const offer: ICXOffer = { + orderTx: createOrderTxId, + amount: new BigNumber(0.10), // 0.10 BTC = 10 DFI + ownerAddress: accountBTC + } + const accountBTCBeforeOffer = await container.call('getaccount', [accountBTC, {}, true]) + + result = await client.icxorderbook.makeOffer(offer, []) + const makeOfferTxId = result.txid + await container.generate(1) + + const accountBTCAfterOffer = await container.call('getaccount', [accountBTC, {}, true]) + + // check fee of 0.01 DFI has been reduced from the accountBTCBeforeOffer[idDFI] + // Fee = takerFeePerBTC(inBTC) * amount(inBTC) * DEX DFI per BTC rate + // NOTE(surangap): why sometimes garbage values are in expected, floting point representation problems? + expect(Number(accountBTCAfterOffer[idDFI]).toPrecision(8)).toStrictEqual((Number(accountBTCBeforeOffer[idDFI]) - Number(0.01)).toPrecision(8)) + + // List the ICX offers for orderTx = createOrderTxId and check + orders = await client.icxorderbook.listOrders({ orderTx: createOrderTxId }) + expect(Object.keys(orders).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + await checkDFIBuyOfferDetails(container, offer, makeOfferTxId, orders as Record) + + return makeOfferTxId + } + + it('Should list htlcs for particular offer', async () => { + // create order - maker + const order: ICXOrder = { + tokenFrom: idDFI, + chainTo: 'BTC', + ownerAddress: accountDFI, + receivePubkey: '037f9563f30c609b19fd435a19b8bde7d6db703012ba1aba72e9f42a87366d1941', + amountFrom: new BigNumber(15), + orderPrice: new BigNumber(0.01) + } + let result: ICXGenericResult = await client.icxorderbook.createOrder(order, []) + const createOrderTxId = result.txid + await container.generate(1) + + // list ICX orders anc check + let orders: Record = await client.icxorderbook.listOrders() + await checkDFISellOrderDetails(container, order, createOrderTxId, orders as Record) + + // make offer to partial amout 10 DFI - taker + const offer: ICXOffer = { + orderTx: createOrderTxId, + amount: new BigNumber(0.10), // 0.10 BTC = 10 DFI + ownerAddress: accountBTC + } + const accountBTCBeforeOffer = await container.call('getaccount', [accountBTC, {}, true]) + result = await client.icxorderbook.makeOffer(offer, []) + const makeOfferTxId = result.txid + await container.generate(1) + + const accountBTCAfterOffer = await container.call('getaccount', [accountBTC, {}, true]) + + // check fee of 0.01 DFI has been reduced from the accountBTCBeforeOffer[idDFI] + // Fee = takerFeePerBTC(inBTC) * amount(inBTC) * DEX DFI per BTC rate + expect(Number(accountBTCAfterOffer[idDFI])).toStrictEqual(Number(accountBTCBeforeOffer[idDFI]) - Number(0.01)) + + // List the ICX offers for orderTx = createOrderTxId and check + orders = await client.icxorderbook.listOrders({ orderTx: createOrderTxId }) + expect(Object.keys(orders).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + await checkDFIBuyOfferDetails(container, offer, makeOfferTxId, orders as Record) + + const accountDFIBeforeDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + + // create DFCHTLC - maker + const DFCHTLC: HTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(10), // in DFC + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + timeout: 500 + } + const DFCHTLCTxId = (await client.icxorderbook.submitDFCHTLC(DFCHTLC)).txid + await container.generate(1) + + const accountDFIAfterDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + expect(Number(accountDFIAfterDFCHTLC[idDFI])).toStrictEqual(Number(accountDFIBeforeDFCHTLC[idDFI]) - Number(0.01)) + + // List htlc anc check + let listHTLCOptions: ICXListHTLCOptions = { + offerTx: makeOfferTxId + } + let HTLCs: Record = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + await checkDFCHTLCDetails(DFCHTLC, DFCHTLCTxId, HTLCs) + + // submit EXT HTLC - taker + const ExtHTLC: ExtHTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(0.10), + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + htlcScriptAddress: '13sJQ9wBWh8ssihHUgAaCmNWJbBAG5Hr9N', + ownerPubkey: '036494e7c9467c8c7ff3bf29e841907fb0fa24241866569944ea422479ec0e6252', + timeout: 15 + } + const ExtHTLCTxId = (await client.icxorderbook.submitExtHTLC(ExtHTLC)).txid + await container.generate(1) + + // List htlc and check + listHTLCOptions = { + offerTx: makeOfferTxId + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(3) // extra entry for the warning text returned by the RPC atm. + await checkEXTHTLCDetails(ExtHTLC, ExtHTLCTxId, HTLCs) + await checkDFCHTLCDetails(DFCHTLC, DFCHTLCTxId, HTLCs) + }) + + // NOTE(surangap):check why this is failing + /* + it('Should test ICXListHTLCOptions.limit parameter functionality', async () => { + const makeOfferTxId = await setupUntilDFIBuyOffer() + const accountDFIBeforeDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + + // create DFCHTLC - maker + const DFCHTLC: HTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(10), // in DFC + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + timeout: 500 + } + const DFCHTLCTxId = (await client.icxorderbook.submitDFCHTLC(DFCHTLC)).txid + await container.generate(1) + + const accountDFIAfterDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + expect(Number(accountDFIAfterDFCHTLC[idDFI])).toStrictEqual(Number(accountDFIBeforeDFCHTLC[idDFI]) - Number(0.01)) + + // List htlc anc check + let listHTLCOptions: ICXListHTLCOptions = { + offerTx: makeOfferTxId + } + let HTLCs: Record = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + await checkDFCHTLCDetails(DFCHTLC, DFCHTLCTxId, HTLCs) + + // submit EXT HTLC - taker + const ExtHTLC: ExtHTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(0.10), + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + htlcScriptAddress: '13sJQ9wBWh8ssihHUgAaCmNWJbBAG5Hr9N', + ownerPubkey: '036494e7c9467c8c7ff3bf29e841907fb0fa24241866569944ea422479ec0e6252', + timeout: 15 + } + const ExtHTLCTxId = (await client.icxorderbook.submitExtHTLC(ExtHTLC)).txid + await container.generate(1) + + // List htlc without limit param and check + listHTLCOptions = { + offerTx: makeOfferTxId + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(3) // extra entry for the warning text returned by the RPC atm. + await checkEXTHTLCDetails(ExtHTLC, ExtHTLCTxId, HTLCs) + await checkDFCHTLCDetails(DFCHTLC, DFCHTLCTxId, HTLCs) + + // List htlc with limit of 1 and check + listHTLCOptions = { + offerTx: makeOfferTxId, + limit: 1 + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + }) + */ + + // NOTE(surangap): check why this is failing + /* + it('Should test ICXListHTLCOptions.refunded parameter functionality', async () => { + const makeOfferTxId = await setupUntilDFIBuyOffer() + const accountDFIBeforeDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + + // create DFCHTLC - maker + const DFCHTLC: HTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(10), // in DFC + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + timeout: 500 + } + const DFCHTLCTxId = (await client.icxorderbook.submitDFCHTLC(DFCHTLC)).txid + await container.generate(1) + + const accountDFIAfterDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + expect(Number(accountDFIAfterDFCHTLC[idDFI])).toStrictEqual(Number(accountDFIBeforeDFCHTLC[idDFI]) - Number(0.01)) + + // submit EXT HTLC - taker + const ExtHTLC: ExtHTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(0.10), + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + htlcScriptAddress: '13sJQ9wBWh8ssihHUgAaCmNWJbBAG5Hr9N', + ownerPubkey: '036494e7c9467c8c7ff3bf29e841907fb0fa24241866569944ea422479ec0e6252', + timeout: 15 + } + const ExtHTLCTxId = (await client.icxorderbook.submitExtHTLC(ExtHTLC)).txid + await container.generate(1) + + // List htlc without limit param and check + let listHTLCOptions: ICXListHTLCOptions = { + offerTx: makeOfferTxId + } + let HTLCs: Record = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(3) // extra entry for the warning text returned by the RPC atm. + await checkEXTHTLCDetails(ExtHTLC, ExtHTLCTxId, HTLCs) + await checkDFCHTLCDetails(DFCHTLC, DFCHTLCTxId, HTLCs) + + // expire HTLCs + await container.generate(550) + + // List htlc anc check + listHTLCOptions = { + offerTx: makeOfferTxId + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + console.log(HTLCs) + expect(Object.keys(HTLCs).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + + // List refended htlcs and check + listHTLCOptions = { + offerTx: makeOfferTxId, + refunded: true + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + console.log(HTLCs) + expect(Object.keys(HTLCs).length).toBe(3) // extra entry for the warning text returned by the RPC atm. + }) + */ + + it('Should test ICXListHTLCOptions.closed parameter functionality', async () => { + const makeOfferTxId = await setupUntilDFIBuyOffer() + const accountDFIBeforeDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + + // create DFCHTLC - maker + const DFCHTLC: HTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(10), // in DFC + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + timeout: 500 + } + const DFCHTLCTxId = (await client.icxorderbook.submitDFCHTLC(DFCHTLC)).txid + await container.generate(1) + + const accountDFIAfterDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + expect(Number(accountDFIAfterDFCHTLC[idDFI])).toStrictEqual(Number(accountDFIBeforeDFCHTLC[idDFI]) - Number(0.01)) + + // List htlc anc check + let listHTLCOptions: ICXListHTLCOptions = { + offerTx: makeOfferTxId + } + let HTLCs: Record = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + await checkDFCHTLCDetails(DFCHTLC, DFCHTLCTxId, HTLCs) + + // submit EXT HTLC - taker + const ExtHTLC: ExtHTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(0.10), + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + htlcScriptAddress: '13sJQ9wBWh8ssihHUgAaCmNWJbBAG5Hr9N', + ownerPubkey: '036494e7c9467c8c7ff3bf29e841907fb0fa24241866569944ea422479ec0e6252', + timeout: 15 + } + const ExtHTLCTxId = (await client.icxorderbook.submitExtHTLC(ExtHTLC)).txid + await container.generate(1) + + // List htlc without limit param and check + listHTLCOptions = { + offerTx: makeOfferTxId + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(3) // extra entry for the warning text returned by the RPC atm. + await checkEXTHTLCDetails(ExtHTLC, ExtHTLCTxId, HTLCs) + await checkDFCHTLCDetails(DFCHTLC, DFCHTLCTxId, HTLCs) + + // claim - taker + const claimTxId = (await client.icxorderbook.claimDFCHTLC(DFCHTLCTxId, 'f75a61ad8f7a6e0ab701d5be1f5d4523a9b534571e4e92e0c4610c6a6784ccef')).txid + await container.generate(1) + + // List HTLCs for offer + listHTLCOptions = { + offerTx: makeOfferTxId + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + // we have a common field "type", use that to narrow down the record + if (HTLCs[claimTxId].type === ICXHTLCType.CLAIM_DFC) { + // ICXClaimDFCHTLCInfo cast + const ClaimHTLCInfo: ICXClaimDFCHTLCInfo = HTLCs[claimTxId] as ICXClaimDFCHTLCInfo + expect(ClaimHTLCInfo.dfchtlcTx).toStrictEqual(DFCHTLCTxId) + expect(ClaimHTLCInfo.seed).toStrictEqual('f75a61ad8f7a6e0ab701d5be1f5d4523a9b534571e4e92e0c4610c6a6784ccef') + } + + // List htlc with closed=true and check + listHTLCOptions = { + offerTx: makeOfferTxId, + closed: true + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(4) // extra entry for the warning text returned by the RPC atm. + // we have a common field "type", use that to narrow down the record + if (HTLCs[claimTxId].type === ICXHTLCType.CLAIM_DFC) { + // ICXClaimDFCHTLCInfo cast + const ClaimHTLCInfo: ICXClaimDFCHTLCInfo = HTLCs[claimTxId] as ICXClaimDFCHTLCInfo + expect(ClaimHTLCInfo.dfchtlcTx).toStrictEqual(DFCHTLCTxId) + expect(ClaimHTLCInfo.seed).toStrictEqual('f75a61ad8f7a6e0ab701d5be1f5d4523a9b534571e4e92e0c4610c6a6784ccef') + } + + if (HTLCs[DFCHTLCTxId].type === ICXHTLCType.DFC) { + // ICXDFCHTLCInfo cast + const DFCHTLCInfo: ICXDFCHTLCInfo = HTLCs[DFCHTLCTxId] as ICXDFCHTLCInfo + expect(DFCHTLCInfo.offerTx).toStrictEqual(makeOfferTxId) + expect(DFCHTLCInfo.status).toStrictEqual(ICXHTLCStatus.CLAIMED) + } + + if (HTLCs[ExtHTLCTxId].type === ICXHTLCType.EXTERNAL) { + // ICXEXTHTLCInfo cast + const ExtHTLCInfo: ICXEXTHTLCInfo = HTLCs[ExtHTLCTxId] as ICXEXTHTLCInfo + expect(ExtHTLCInfo.offerTx).toStrictEqual(makeOfferTxId) + // expect(ExtHTLCInfo.status).toStrictEqual(ICXHTLCStatus.CLAIMED) //NOTE(surangap): check why is it returned as CLOSED ? + } + }) + + it('Should return an empty result set when invalid ICXListHTLCOptions.offerTx is passed', async () => { + const makeOfferTxId = await setupUntilDFIBuyOffer() + const accountDFIBeforeDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + + // create DFCHTLC - maker + const DFCHTLC: HTLC = { + offerTx: makeOfferTxId, + amount: new BigNumber(10), // in DFC + hash: '957fc0fd643f605b2938e0631a61529fd70bd35b2162a21d978c41e5241a5220', + timeout: 500 + } + const DFCHTLCTxId = (await client.icxorderbook.submitDFCHTLC(DFCHTLC)).txid + await container.generate(1) + + const accountDFIAfterDFCHTLC = await container.call('getaccount', [accountDFI, {}, true]) + expect(Number(accountDFIAfterDFCHTLC[idDFI])).toStrictEqual(Number(accountDFIBeforeDFCHTLC[idDFI]) - Number(0.01)) + + // List htlc and check + let listHTLCOptions: ICXListHTLCOptions = { + offerTx: makeOfferTxId + } + let HTLCs: Record = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(2) // extra entry for the warning text returned by the RPC atm. + await checkDFCHTLCDetails(DFCHTLC, DFCHTLCTxId, HTLCs) + + // List htlcs with invalid offer tx "123" and check + // List htlc without limit param and check + listHTLCOptions = { + offerTx: '123' + } + HTLCs = await client.icxorderbook.listHTLCs(listHTLCOptions) + expect(Object.keys(HTLCs).length).toBe(1) // extra entry for the warning text returned by the RPC atm. + }) +}) diff --git a/packages/jellyfish-api-core/src/category/icxorderbook.ts b/packages/jellyfish-api-core/src/category/icxorderbook.ts index 6defcd045f..e73c48f5cc 100644 --- a/packages/jellyfish-api-core/src/category/icxorderbook.ts +++ b/packages/jellyfish-api-core/src/category/icxorderbook.ts @@ -189,6 +189,26 @@ export class ICXOrderBook { 'bignumber' ) } + + /** + * Returns information about HTLCs based on ICXListHTLCOptions passed + * + * @param {ICXListHTLCOptions} options + * @param {string} [options.offerTx] Offer txid for which to list all HTLCS + * @param {number} [options.limit] Maximum number of orders to return (default: 20) + * @param {boolean} [options.refunded] Display refunded HTLC (default: false) + * @param {boolean} [options.closed] Display claimed HTLCs (default: false) + * @return {Promise>} Object indluding details of the HTLCS. + */ + async listHTLCs (options: ICXListHTLCOptions = {}): Promise> { + return await this.client.call( + 'icx_listhtlcs', + [ + options + ], + 'bignumber' + ) + } } /** ICX order */ export interface ICXOrder { diff --git a/website/docs/jellyfish/api/icxorderbook.md b/website/docs/jellyfish/api/icxorderbook.md index 667865f2bb..a332e740f7 100644 --- a/website/docs/jellyfish/api/icxorderbook.md +++ b/website/docs/jellyfish/api/icxorderbook.md @@ -256,3 +256,55 @@ interface ICXOfferInfo { expireHeight: number } ``` + +## listHTLCs + +Returns information about HTLCs based on ICXListHTLCOptions passed + +```ts title="client.icxorderbook.listHTLCs()" +interface icxorderbook { + listHTLCs (options: ICXListHTLCOptions = {}): Promise> +} + +interface ICXListHTLCOptions { + offerTx?: string + limit?: number + refunded?: boolean + closed?: boolean +} + +interface ICXClaimDFCHTLCInfo { + type: ICXHTLCType + dfchtlcTx: string + seed: string + height: number +} + +interface ICXDFCHTLCInfo { + type: ICXHTLCType + status: ICXHTLCStatus + offerTx: string + amount: BigNumber + amountInEXTAsset: BigNumber + hash: string + timeout: number + height: number + refundHeight: number +} + +interface ICXEXTHTLCInfo { + type: ICXHTLCType + status: ICXHTLCStatus + offerTx: string + amount: BigNumber + amountInDFCAsset: BigNumber + hash: string + htlcScriptAddress: string + ownerPubkey: string + timeout: number + height: number +} +``` + + +