From 857107e2c191f81ab18eabe27589199ed6fe1227 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Mon, 21 Oct 2024 16:08:28 +0200 Subject: [PATCH] feat: add pagination to getAllRippleVaults --- package.json | 2 +- src/functions/ripple/ripple.functions.ts | 54 ++-- src/network-handlers/ripple-handler.ts | 326 ++++++++++------------- 3 files changed, 178 insertions(+), 204 deletions(-) diff --git a/package.json b/package.json index 3f4c08c..63cbfc5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "2.4.5", + "version": "2.4.6", "description": "This library provides a comprehensive set of interfaces and functions for minting dlcBTC tokens on supported blockchains.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/functions/ripple/ripple.functions.ts b/src/functions/ripple/ripple.functions.ts index ae545b6..f0ffaf0 100644 --- a/src/functions/ripple/ripple.functions.ts +++ b/src/functions/ripple/ripple.functions.ts @@ -199,30 +199,25 @@ export async function getRippleVault( try { await connectRippleClient(rippleClient); - let formattedUUID = vaultUUID.substring(0, 2) === '0x' ? vaultUUID.slice(2) : vaultUUID; - formattedUUID = formattedUUID.toUpperCase(); - const getAccountNFTsRequest: AccountNFTsRequest = { - command: 'account_nfts', - account: issuerAddress, - }; - - const { - result: { account_nfts: rippleNFTs }, - } = await rippleClient.request(getAccountNFTsRequest); + // let formattedUUID = vaultUUID.substring(0, 2) === '0x' ? vaultUUID.slice(2) : vaultUUID; + // formattedUUID = formattedUUID.toUpperCase(); + const formattedUUID = vaultUUID.substring(0, 2) === '0x' ? vaultUUID : `0x${vaultUUID}`; - const nftID = findNFTByUUID(rippleNFTs, formattedUUID).NFTokenID; + const allVaults = await getAllRippleVaults(rippleClient, issuerAddress); - const matchingNFTs = rippleNFTs.filter(nft => nft.NFTokenID === nftID); + const matchingVaults = allVaults.filter( + vault => vault.uuid.toLowerCase() === formattedUUID.toLowerCase() + ); - if (matchingNFTs.length === 0) { + if (matchingVaults.length === 0) { throw new RippleError(`Vault with UUID: ${formattedUUID} not found`); } - if (matchingNFTs.length > 1) { + if (matchingVaults.length > 1) { throw new RippleError(`Multiple Vaults found with UUID: ${formattedUUID}`); } - return hexFieldsToLowercase(decodeURI(matchingNFTs[0].URI!)); + return matchingVaults[0]; } catch (error) { throw new RippleError(`Error getting Vault ${vaultUUID}: ${error}`); } @@ -236,17 +231,28 @@ export async function getAllRippleVaults( try { await connectRippleClient(rippleClient); - const getAccountNFTsRequest: AccountNFTsRequest = { - command: 'account_nfts', - account: issuerAddress, - limit: 400, - }; + let marker: any = undefined; + const limit = 100; + let allRippleNFTs: any[] = []; - const { - result: { account_nfts: rippleNFTs }, - } = await rippleClient.request(getAccountNFTsRequest); + do { + const getAccountNFTsRequest: AccountNFTsRequest = { + command: 'account_nfts', + account: issuerAddress, + limit, + marker, + }; + + const { + result: { account_nfts: rippleNFTs, marker: newMarker }, + } = await rippleClient.request(getAccountNFTsRequest); + + allRippleNFTs = allRippleNFTs.concat(rippleNFTs); + + marker = newMarker; + } while (marker); - const rippleVaults = rippleNFTs.map(nft => hexFieldsToLowercase(decodeURI(nft.URI!))); + const rippleVaults = allRippleNFTs.map(nft => hexFieldsToLowercase(decodeURI(nft.URI!))); if (ownerAddress) { return rippleVaults.filter(vault => vault.creator === ownerAddress); diff --git a/src/network-handlers/ripple-handler.ts b/src/network-handlers/ripple-handler.ts index 4d2e252..e5f549a 100644 --- a/src/network-handlers/ripple-handler.ts +++ b/src/network-handlers/ripple-handler.ts @@ -17,7 +17,9 @@ import { connectRippleClient, decodeURI, encodeURI, + getAllRippleVaults, getCheckByTXHash, + getRippleVault, } from '../functions/ripple/ripple.functions.js'; import { RippleError } from '../models/errors.js'; import { RawVault, SSFVaultUpdate, SSPVaultUpdate } from '../models/ethereum-models.js'; @@ -28,17 +30,6 @@ interface SignResponse { hash: string; } -function lowercaseHexFields(vault: RawVault): RawVault { - return { - ...vault, - uuid: vault.uuid.toLowerCase(), - fundingTxId: vault.fundingTxId.toLowerCase(), - wdTxId: vault.wdTxId.toLowerCase(), - btcFeeRecipient: vault.btcFeeRecipient.toLowerCase(), - taprootPubKey: vault.taprootPubKey.toLowerCase(), - }; -} - function buildDefaultNftVault(): RawVault { return { uuid: `0x${'0'.repeat(64)}`, @@ -70,7 +61,7 @@ export class RippleHandler { websocketURL: string, minSigners: number ) { - this.client = new xrpl.Client(websocketURL); + this.client = new xrpl.Client(websocketURL, { timeout: 10000 }); this.wallet = xrpl.Wallet.fromSeed(seedPhrase); this.issuerAddress = issuerAddress; this.minSigners = minSigners; @@ -85,11 +76,18 @@ export class RippleHandler { return new RippleHandler(seedPhrase, issuerAddress, websocketURL, minSigners); } - async submit(signatures: string[]): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); + async disconnectClient(): Promise { + try { + await this.client.disconnect(); + } catch (error) { + throw new RippleError(`Could not disconnect client: ${error}`); } + } + + async submit(signatures: string[]): Promise { try { + await connectRippleClient(this.client); + const multisig_tx = xrpl.multisign(signatures); const tx: xrpl.TxResponse = @@ -106,10 +104,9 @@ export class RippleHandler { } async getNetworkInfo(): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + return await this.client.request({ command: 'server_info' }); } catch (error) { throw new RippleError(`Could not fetch Network Info: ${error}`); @@ -117,10 +114,9 @@ export class RippleHandler { } async getAddress(): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + return this.wallet.classicAddress; } catch (error) { throw new RippleError(`Could not fetch Address Info: ${error}`); @@ -128,37 +124,19 @@ export class RippleHandler { } async getRawVault(uuid: string): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { - const getNFTsTransaction: AccountNFTsRequest = { - command: 'account_nfts', - account: this.issuerAddress, - limit: 400, - }; - let nftUUID = uuid.substring(0, 2) === '0x' ? uuid.slice(2) : uuid; - nftUUID = nftUUID.toUpperCase(); - const nfts: xrpl.AccountNFTsResponse = await this.client.request(getNFTsTransaction); - const nftTokenId = await this.getNFTokenIdForVault(nftUUID); - const matchingNFT = nfts.result.account_nfts.filter(nft => nft.NFTokenID === nftTokenId); - if (matchingNFT.length === 0) { - throw new RippleError(`Vault with UUID: ${nftUUID} not found`); - } else if (matchingNFT.length > 1) { - throw new RippleError(`Multiple Vaults with UUID: ${nftUUID} found`); - } - const matchingVault: RawVault = decodeURI(matchingNFT[0].URI!); - return lowercaseHexFields(matchingVault); + await connectRippleClient(this.client); + + return await getRippleVault(this.client, this.issuerAddress, uuid); } catch (error) { throw new RippleError(`Could not fetch Vault: ${error}`); } } async setupVault(uuid: string, userAddress: string, timeStamp: number): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + const newVault = buildDefaultNftVault(); newVault.uuid = uuid; newVault.creator = userAddress; @@ -173,10 +151,9 @@ export class RippleHandler { // Things like withdraw and deposit should get the existing NFT vault // then burn the NFT, and mint a new one with the updated value // putting the UUID into the URI - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + console.log(`Performing Withdraw for User: ${uuid}`); let nftUUID = uuid.substring(0, 2) === '0x' ? uuid.slice(2) : uuid; nftUUID = nftUUID.toUpperCase(); @@ -196,10 +173,9 @@ export class RippleHandler { mintTokensSignedTxBlobs: string[], // this can be a set of empty string is no tokens are being minted mintNFTSignedTxBlobs: string[] ): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + console.log('Doing the burn for SSF'); const burn_multisig_tx = xrpl.multisign(burnNFTSignedTxBlobs); const burnTx: xrpl.TxResponse = @@ -249,10 +225,9 @@ export class RippleHandler { burnNFTSignedTxBlobs: string[], mintNFTSignedTxBlobs: string[] ): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + console.log('Doing the check cashing'); // multisig burn const cash_check_tx = xrpl.multisign(cashCheckSignedTxBlobs); @@ -298,10 +273,9 @@ export class RippleHandler { burnNFTSignedTxBlobs: string[], mintNFTSignedTxBlobs: string[] ): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + console.log('Doing the burn for SSP'); // multisig burn const burn_multisig_tx = xrpl.multisign(burnNFTSignedTxBlobs); @@ -334,32 +308,20 @@ export class RippleHandler { } async getContractVaults(): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { - const getNFTsTransaction: AccountNFTsRequest = { - command: 'account_nfts', - account: this.issuerAddress, - limit: 400, - }; - - const nfts: xrpl.AccountNFTsResponse = await this.client.request(getNFTsTransaction); - const allNFTs = nfts.result.account_nfts; - const allVaults: RawVault[] = allNFTs.map(nft => lowercaseHexFields(decodeURI(nft.URI!))); + await connectRippleClient(this.client); - return allVaults; + return await getAllRippleVaults(this.client, this.issuerAddress); } catch (error) { throw new RippleError(`Could not fetch All Vaults: ${error}`); } } async getNFTokenIdForVault(uuid: string): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } console.log(`Getting NFTokenId for vault: ${uuid}`); try { + await connectRippleClient(this.client); + const getNFTsTransaction: AccountNFTsRequest = { command: 'account_nfts', account: this.issuerAddress, @@ -382,9 +344,8 @@ export class RippleHandler { async burnNFT(nftUUID: string, incrementBy: number = 0): Promise { try { - if (!this.client.isConnected()) { - await this.client.connect(); - } + await connectRippleClient(this.client); + console.log(`Getting sig for Burning Ripple Vault, vault: ${nftUUID}`); const nftTokenId = await this.getNFTokenIdForVault(nftUUID); const burnTransactionJson: SubmittableTransaction = { @@ -415,9 +376,8 @@ export class RippleHandler { async mintNFT(vault: RawVault, incrementBy: number = 0): Promise { try { - if (!this.client.isConnected()) { - await this.client.connect(); - } + await connectRippleClient(this.client); + console.log(`Getting sig for Minting Ripple Vault, vault: ${JSON.stringify(vault, null, 2)}`); const newURI = encodeURI(vault); console.log('newURI: ', newURI); @@ -448,10 +408,9 @@ export class RippleHandler { } async getSigUpdateVaultForSSP(uuid: string, updates: SSPVaultUpdate): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + console.log(`Getting sig for getSigUpdateVaultForSSP, vault uuid: ${uuid}`); const nftUUID = uuid; const thisVault = await this.getRawVault(nftUUID); @@ -474,10 +433,9 @@ export class RippleHandler { updates: SSFVaultUpdate, updateSequenceBy: number ): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } try { + await connectRippleClient(this.client); + const nftUUID = uuid; const thisVault = await this.getRawVault(nftUUID); const updatedVault = { @@ -495,26 +453,30 @@ export class RippleHandler { } async getAllChecks(): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } + try { + await connectRippleClient(this.client); - const getAccountObjectsRequestJSON: Request = { - command: 'account_objects', - account: this.issuerAddress, - ledger_index: 'validated', - type: 'check', - }; + const getAccountObjectsRequestJSON: Request = { + command: 'account_objects', + account: this.issuerAddress, + ledger_index: 'validated', + type: 'check', + }; - const getAccountObjectsResponse: AccountObjectsResponse = await this.client.request( - getAccountObjectsRequestJSON - ); + const getAccountObjectsResponse: AccountObjectsResponse = await this.client.request( + getAccountObjectsRequestJSON + ); - return getAccountObjectsResponse.result.account_objects; + return getAccountObjectsResponse.result.account_objects; + } catch (error) { + throw new RippleError(`Could not fetch Checks: ${error}`); + } } async getCashCheckAndWithdrawSignatures(txHash: string): Promise { try { + await connectRippleClient(this.client); + const check = await getCheckByTXHash(this.client, this.issuerAddress, txHash); const invoiceID = check.InvoiceID; @@ -543,42 +505,46 @@ export class RippleHandler { } async cashCheck(checkID: string, dlcBTCAmount: string): Promise { - await connectRippleClient(this.client); - - console.log(`Cashing Check of Check ID ${checkID} for an amount of ${dlcBTCAmount}`); - - const cashCheckTransactionJSON: CheckCash = { - TransactionType: 'CheckCash', - Account: this.issuerAddress, - CheckID: checkID, - Amount: { - currency: XRPL_DLCBTC_CURRENCY_HEX, - value: dlcBTCAmount, - issuer: this.issuerAddress, - }, - }; - - const updatedCashCheckTransactionJSON: CheckCash = await this.client.autofill( - cashCheckTransactionJSON, - this.minSigners - ); - - // set the LastLedgerSequence to equal LastLedgerSequence plus 5 and then rounded up to the nearest 10 - // this is to ensure that the transaction is valid for a while, and that the different attestors all use a matching LLS value to have matching sigs - updatedCashCheckTransactionJSON.LastLedgerSequence = - Math.ceil(updatedCashCheckTransactionJSON.LastLedgerSequence! / 10000 + 1) * 10000; - - console.log( - 'Issuer is about to sign the following cashCheck tx: ', - updatedCashCheckTransactionJSON - ); - - const signCashCheckTransactionSig: SignResponse = this.wallet.sign( - updatedCashCheckTransactionJSON, - true - ); - - return signCashCheckTransactionSig.tx_blob; + try { + await connectRippleClient(this.client); + + console.log(`Cashing Check of Check ID ${checkID} for an amount of ${dlcBTCAmount}`); + + const cashCheckTransactionJSON: CheckCash = { + TransactionType: 'CheckCash', + Account: this.issuerAddress, + CheckID: checkID, + Amount: { + currency: XRPL_DLCBTC_CURRENCY_HEX, + value: dlcBTCAmount, + issuer: this.issuerAddress, + }, + }; + + const updatedCashCheckTransactionJSON: CheckCash = await this.client.autofill( + cashCheckTransactionJSON, + this.minSigners + ); + + // set the LastLedgerSequence to equal LastLedgerSequence plus 5 and then rounded up to the nearest 10 + // this is to ensure that the transaction is valid for a while, and that the different attestors all use a matching LLS value to have matching sigs + updatedCashCheckTransactionJSON.LastLedgerSequence = + Math.ceil(updatedCashCheckTransactionJSON.LastLedgerSequence! / 10000 + 1) * 10000; + + console.log( + 'Issuer is about to sign the following cashCheck tx: ', + updatedCashCheckTransactionJSON + ); + + const signCashCheckTransactionSig: SignResponse = this.wallet.sign( + updatedCashCheckTransactionJSON, + true + ); + + return signCashCheckTransactionSig.tx_blob; + } catch (error) { + throw new RippleError(`Could not cash Check: ${error}`); + } } async mintTokens( @@ -587,55 +553,57 @@ export class RippleHandler { valueMinted: number, incrementBy: number = 0 ): Promise { - if (!this.client.isConnected()) { - await this.client.connect(); - } + try { + await connectRippleClient(this.client); - if (updatedValueMinted === 0 || valueMinted >= updatedValueMinted) { - console.log('No need to mint tokens, because this is a withdraw SSF'); - return ''; - } - const mintValue = unshiftValue(new Decimal(updatedValueMinted).minus(valueMinted).toNumber()); - const dlcBTCAmount = mintValue.toString(); - console.log(`Minting ${dlcBTCAmount} dlcBTC to ${destinationAddress} address`); - - const sendTokenTransactionJSON: Payment = { - TransactionType: 'Payment', - Account: this.issuerAddress, - Destination: destinationAddress, - DestinationTag: 1, - Amount: { - currency: XRPL_DLCBTC_CURRENCY_HEX, - value: dlcBTCAmount, - issuer: this.issuerAddress, - }, - }; - - const updatedSendTokenTransactionJSON: Payment = await this.client.autofill( - sendTokenTransactionJSON, - this.minSigners - ); - - // set the LastLedgerSequence to equal LastLedgerSequence plus 5 and then rounded up to the nearest 10 - // this is to ensure that the transaction is valid for a while, and that the different attestors all use a matching LLS value to have matching sigs - updatedSendTokenTransactionJSON.LastLedgerSequence = - Math.ceil(updatedSendTokenTransactionJSON.LastLedgerSequence! / 10000 + 1) * 10000; - - if (incrementBy > 0) { - updatedSendTokenTransactionJSON.Sequence = - updatedSendTokenTransactionJSON.Sequence! + incrementBy; - } + if (updatedValueMinted === 0 || valueMinted >= updatedValueMinted) { + console.log('No need to mint tokens, because this is a withdraw SSF'); + return ''; + } + const mintValue = unshiftValue(new Decimal(updatedValueMinted).minus(valueMinted).toNumber()); + const dlcBTCAmount = mintValue.toString(); + console.log(`Minting ${dlcBTCAmount} dlcBTC to ${destinationAddress} address`); - console.log( - 'Issuer is about to sign the following mintTokens tx: ', - updatedSendTokenTransactionJSON - ); + const sendTokenTransactionJSON: Payment = { + TransactionType: 'Payment', + Account: this.issuerAddress, + Destination: destinationAddress, + DestinationTag: 1, + Amount: { + currency: XRPL_DLCBTC_CURRENCY_HEX, + value: dlcBTCAmount, + issuer: this.issuerAddress, + }, + }; - const signSendTokenTransactionResponse: SignResponse = this.wallet.sign( - updatedSendTokenTransactionJSON, - true - ); + const updatedSendTokenTransactionJSON: Payment = await this.client.autofill( + sendTokenTransactionJSON, + this.minSigners + ); - return signSendTokenTransactionResponse.tx_blob; + // set the LastLedgerSequence to equal LastLedgerSequence plus 5 and then rounded up to the nearest 10 + // this is to ensure that the transaction is valid for a while, and that the different attestors all use a matching LLS value to have matching sigs + updatedSendTokenTransactionJSON.LastLedgerSequence = + Math.ceil(updatedSendTokenTransactionJSON.LastLedgerSequence! / 10000 + 1) * 10000; + + if (incrementBy > 0) { + updatedSendTokenTransactionJSON.Sequence = + updatedSendTokenTransactionJSON.Sequence! + incrementBy; + } + + console.log( + 'Issuer is about to sign the following mintTokens tx: ', + updatedSendTokenTransactionJSON + ); + + const signSendTokenTransactionResponse: SignResponse = this.wallet.sign( + updatedSendTokenTransactionJSON, + true + ); + + return signSendTokenTransactionResponse.tx_blob; + } catch (error) { + throw new RippleError(`Could not mint tokens: ${error}`); + } } }