From 3159e781be066b3bf3a913a61230868782d9449a Mon Sep 17 00:00:00 2001 From: sosaucily Date: Sun, 27 Oct 2024 12:41:23 +0100 Subject: [PATCH] use a withXrplConnection context for functions that need a connection --- src/functions/ripple/ripple.functions.ts | 7 +- src/network-handlers/ripple-handler.ts | 851 ++++++++++++----------- 2 files changed, 442 insertions(+), 416 deletions(-) diff --git a/src/functions/ripple/ripple.functions.ts b/src/functions/ripple/ripple.functions.ts index f0ffaf0..30ce9ea 100644 --- a/src/functions/ripple/ripple.functions.ts +++ b/src/functions/ripple/ripple.functions.ts @@ -130,11 +130,12 @@ export function getRippleWallet(seedPhrase: string): Wallet { return Wallet.fromSeed(seedPhrase); } -export async function connectRippleClient(rippleClient: Client): Promise { +export async function connectRippleClient(rippleClient: Client): Promise { if (rippleClient.isConnected()) { - return; + return false; } await rippleClient.connect(); + return true; } export function formatRippleVaultUUID(vaultUUID: string): string { @@ -199,8 +200,6 @@ export async function getRippleVault( try { await connectRippleClient(rippleClient); - // 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 allVaults = await getAllRippleVaults(rippleClient, issuerAddress); diff --git a/src/network-handlers/ripple-handler.ts b/src/network-handlers/ripple-handler.ts index 497db17..fc0d472 100644 --- a/src/network-handlers/ripple-handler.ts +++ b/src/network-handlers/ripple-handler.ts @@ -76,47 +76,58 @@ export class RippleHandler { return new RippleHandler(seedPhrase, issuerAddress, websocketURL, minSigners); } - async disconnectClient(): Promise { + async withConnectionMgmt(callback: () => Promise): Promise { + console.log('Connecting to the async service...'); + const newConnection = !this.client.isConnected(); try { - await this.client.disconnect(); - } catch (error) { - throw new RippleError(`Could not disconnect client: ${error}`); + await connectRippleClient(this.client); + console.log('calling the callback service...'); + const result = await callback(); + return result; + } finally { + console.log('Disconnecting from the async service...'); + if (newConnection) { + // only disconnect if we connected in this function, otherwise leave the connection open + // This is to prevent closing a connection from an internally used function when the connection is still needed by the caller + // For example, getSigUpdateVaultForSSP calls getRawVault internally, and both need the connection, so we can't close the connection when getRawVault finishes + await this.client.disconnect(); + } } } async submit(signatures: string[]): Promise { - try { - await connectRippleClient(this.client); - - const multisig_tx = xrpl.multisign(signatures); + return await this.withConnectionMgmt(async () => { + try { + const multisig_tx = xrpl.multisign(signatures); - const tx: xrpl.TxResponse = - await this.client.submitAndWait(multisig_tx); - const meta: NFTokenMintMetadata = tx.result.meta! as NFTokenMintMetadata; + const tx: xrpl.TxResponse = + await this.client.submitAndWait(multisig_tx); + const meta: NFTokenMintMetadata = tx.result.meta! as NFTokenMintMetadata; - if (meta.TransactionResult !== 'tesSUCCESS') { - throw new RippleError(`Could not burn temporary Ripple Vault: ${meta!.TransactionResult}`); + if (meta.TransactionResult !== 'tesSUCCESS') { + throw new RippleError( + `Could not burn temporary Ripple Vault: ${meta!.TransactionResult}` + ); + } + return tx.result.hash; + } catch (error) { + throw new RippleError(`Could not submit transaction: ${error}`); } - return tx.result.hash; - } catch (error) { - throw new RippleError(`Could not submit transaction: ${error}`); - } + }); } async getNetworkInfo(): Promise { - try { - await connectRippleClient(this.client); - - return await this.client.request({ command: 'server_info' }); - } catch (error) { - throw new RippleError(`Could not fetch Network Info: ${error}`); - } + return await this.withConnectionMgmt(async () => { + try { + return await this.client.request({ command: 'server_info' }); + } catch (error) { + throw new RippleError(`Could not fetch Network Info: ${error}`); + } + }); } async getAddress(): Promise { try { - await connectRippleClient(this.client); - return this.wallet.classicAddress; } catch (error) { throw new RippleError(`Could not fetch Address Info: ${error}`); @@ -124,13 +135,13 @@ export class RippleHandler { } async getRawVault(uuid: string): Promise { - try { - await connectRippleClient(this.client); - - return await getRippleVault(this.client, this.issuerAddress, uuid); - } catch (error) { - throw new RippleError(`Could not fetch Vault: ${error}`); - } + return await this.withConnectionMgmt(async () => { + try { + return await getRippleVault(this.client, this.issuerAddress, uuid); + } catch (error) { + throw new RippleError(`Could not fetch Vault: ${error}`); + } + }); } async setupVault( @@ -140,92 +151,90 @@ export class RippleHandler { btcMintFeeBasisPoints: number, btcRedeemFeeBasisPoints: number ): Promise { - try { - await connectRippleClient(this.client); - - const newVault = buildDefaultNftVault(); - newVault.uuid = uuid; - newVault.creator = userAddress; - newVault.timestamp = BigNumber.from(timeStamp); - newVault.btcMintFeeBasisPoints = BigNumber.from(btcMintFeeBasisPoints); - newVault.btcRedeemFeeBasisPoints = BigNumber.from(btcRedeemFeeBasisPoints); - return await this.mintNFT(newVault); - } catch (error) { - throw new RippleError(`Could not setup Ripple Vault: ${error}`); - } + return await this.withConnectionMgmt(async () => { + try { + const newVault = buildDefaultNftVault(); + newVault.uuid = uuid; + newVault.creator = userAddress; + newVault.timestamp = BigNumber.from(timeStamp); + newVault.btcMintFeeBasisPoints = BigNumber.from(btcMintFeeBasisPoints); + newVault.btcRedeemFeeBasisPoints = BigNumber.from(btcRedeemFeeBasisPoints); + return await this.mintNFT(newVault); + } catch (error) { + throw new RippleError(`Could not setup Ripple Vault: ${error}`); + } + }); } async withdraw(uuid: string, withdrawAmount: bigint): Promise { - // 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 - 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(); - const thisVault = await this.getRawVault(nftUUID); - const burnSig = await this.burnNFT(nftUUID, 1); - - thisVault.valueMinted = thisVault.valueMinted.sub(BigNumber.from(withdrawAmount)); - const mintSig = await this.mintNFT(thisVault, 2); - return [burnSig, mintSig]; - } catch (error) { - throw new RippleError(`Unable to perform Withdraw for User: ${error}`); - } + return await this.withConnectionMgmt(async () => { + try { + console.log(`Performing Withdraw for User: ${uuid}`); + let nftUUID = uuid.substring(0, 2) === '0x' ? uuid.slice(2) : uuid; + nftUUID = nftUUID.toUpperCase(); + const thisVault = await this.getRawVault(nftUUID); + const burnSig = await this.burnNFT(nftUUID, 1); + + thisVault.valueMinted = thisVault.valueMinted.sub(BigNumber.from(withdrawAmount)); + const mintSig = await this.mintNFT(thisVault, 2); + return [burnSig, mintSig]; + } catch (error) { + throw new RippleError(`Unable to perform Withdraw for User: ${error}`); + } + }); } async setVaultStatusFunded( burnNFTSignedTxBlobs: string[], - mintTokensSignedTxBlobs: string[], // this can be a set of empty string is no tokens are being minted + mintTokensSignedTxBlobs: string[], mintNFTSignedTxBlobs: string[] ): Promise { - try { - await connectRippleClient(this.client); - - console.log('Doing the burn for SSF'); - const burn_multisig_tx = xrpl.multisign(burnNFTSignedTxBlobs); - const burnTx: xrpl.TxResponse = - await this.client.submitAndWait(burn_multisig_tx); - const burnMeta: NFTokenMintMetadata = burnTx.result.meta! as NFTokenMintMetadata; - if (burnMeta!.TransactionResult !== 'tesSUCCESS') { - throw new RippleError( - `Could not burn temporary Ripple Vault: ${burnMeta!.TransactionResult}` - ); - } + return await this.withConnectionMgmt(async () => { + try { + console.log('Doing the burn for SSF'); + const burn_multisig_tx = xrpl.multisign(burnNFTSignedTxBlobs); + const burnTx: xrpl.TxResponse = + await this.client.submitAndWait(burn_multisig_tx); + const burnMeta: NFTokenMintMetadata = burnTx.result.meta! as NFTokenMintMetadata; + if (burnMeta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError( + `Could not burn temporary Ripple Vault: ${burnMeta!.TransactionResult}` + ); + } - // multisig mint - if (mintTokensSignedTxBlobs.every(sig => sig !== '')) { - console.log('Success! Now minting the actual tokens!! How fun $$'); + // multisig mint + if (mintTokensSignedTxBlobs.every(sig => sig !== '')) { + console.log('Success! Now minting the actual tokens!! How fun $$'); + + const mint_token_multisig_tx = xrpl.multisign(mintTokensSignedTxBlobs); + const mintTokenTx: xrpl.TxResponse = + await this.client.submitAndWait(mint_token_multisig_tx); + const mintTokenMeta: NFTokenMintMetadata = mintTokenTx.result + .meta! as NFTokenMintMetadata; + if (mintTokenMeta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError( + `Could not mint tokens to user: ${mintTokenMeta!.TransactionResult}` + ); + } + } else { + console.log('No need to mint tokens, because this was a withdraw flow SSF'); + } - const mint_token_multisig_tx = xrpl.multisign(mintTokensSignedTxBlobs); - const mintTokenTx: xrpl.TxResponse = - await this.client.submitAndWait(mint_token_multisig_tx); - const mintTokenMeta: NFTokenMintMetadata = mintTokenTx.result.meta! as NFTokenMintMetadata; - if (mintTokenMeta!.TransactionResult !== 'tesSUCCESS') { + console.log('Success! Now Doing the mint for SSF'); + // multisig mint + const mint_multisig_tx = xrpl.multisign(mintNFTSignedTxBlobs); + const mintTx: xrpl.TxResponse = + await this.client.submitAndWait(mint_multisig_tx); + const mintMeta: NFTokenMintMetadata = mintTx.result.meta! as NFTokenMintMetadata; + if (mintMeta!.TransactionResult !== 'tesSUCCESS') { throw new RippleError( - `Could not mint tokens to user: ${mintTokenMeta!.TransactionResult}` + `Could not mint temporary Ripple Vault: ${mintMeta!.TransactionResult}` ); } - } else { - console.log('No need to mint tokens, because this was a withdraw flow SSF'); + } catch (error) { + throw new RippleError(`Unable to set Vault status to FUNDED: ${error}`); } - - console.log('Success! Now Doing the mint for SSF'); - // multisig mint - const mint_multisig_tx = xrpl.multisign(mintNFTSignedTxBlobs); - const mintTx: xrpl.TxResponse = - await this.client.submitAndWait(mint_multisig_tx); - const mintMeta: NFTokenMintMetadata = mintTx.result.meta! as NFTokenMintMetadata; - if (mintMeta!.TransactionResult !== 'tesSUCCESS') { - throw new RippleError( - `Could not mint temporary Ripple Vault: ${mintMeta!.TransactionResult}` - ); - } - } catch (error) { - throw new RippleError(`Unable to set Vault status to FUNDED: ${error}`); - } + }); } async performCheckCashAndNftUpdate( @@ -233,207 +242,215 @@ export class RippleHandler { burnNFTSignedTxBlobs: string[], mintNFTSignedTxBlobs: string[] ): Promise { - try { - await connectRippleClient(this.client); + return await this.withConnectionMgmt(async () => { + try { + console.log('Doing the check cashing'); + // multisig burn + const cash_check_tx = xrpl.multisign(cashCheckSignedTxBlobs); + const cashCheckTx: xrpl.TxResponse = + await this.client.submitAndWait(cash_check_tx); // add timeouts + const cashCheckMeta: NFTokenMintMetadata = cashCheckTx.result.meta! as NFTokenMintMetadata; + if (cashCheckMeta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError(`Could not cash check: ${cashCheckMeta!.TransactionResult}`); + } - console.log('Doing the check cashing'); - // multisig burn - const cash_check_tx = xrpl.multisign(cashCheckSignedTxBlobs); - const cashCheckTx: xrpl.TxResponse = - await this.client.submitAndWait(cash_check_tx); // add timeouts - const cashCheckMeta: NFTokenMintMetadata = cashCheckTx.result.meta! as NFTokenMintMetadata; - if (cashCheckMeta!.TransactionResult !== 'tesSUCCESS') { - throw new RippleError(`Could not cash check: ${cashCheckMeta!.TransactionResult}`); - } + console.log('Doing the burn for SSP'); + // multisig burn + const burn_multisig_tx = xrpl.multisign(burnNFTSignedTxBlobs); + const burnTx: xrpl.TxResponse = + await this.client.submitAndWait(burn_multisig_tx); // add timeouts + const burnMeta: NFTokenMintMetadata = burnTx.result.meta! as NFTokenMintMetadata; + if (burnMeta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError( + `Could not burn temporary Ripple Vault: ${burnMeta!.TransactionResult}` + ); + } - console.log('Doing the burn for SSP'); - // multisig burn - const burn_multisig_tx = xrpl.multisign(burnNFTSignedTxBlobs); - const burnTx: xrpl.TxResponse = - await this.client.submitAndWait(burn_multisig_tx); // add timeouts - const burnMeta: NFTokenMintMetadata = burnTx.result.meta! as NFTokenMintMetadata; - if (burnMeta!.TransactionResult !== 'tesSUCCESS') { - throw new RippleError( - `Could not burn temporary Ripple Vault: ${burnMeta!.TransactionResult}` - ); - } + console.log('Success! Now Doing the mint for SSP'); - console.log('Success! Now Doing the mint for SSP'); + // multisig mint + const mint_multisig_tx = xrpl.multisign(mintNFTSignedTxBlobs); + const mintTx: xrpl.TxResponse = + await this.client.submitAndWait(mint_multisig_tx); // add timeouts + const mintMeta: NFTokenMintMetadata = mintTx.result.meta! as NFTokenMintMetadata; + if (mintMeta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError( + `Could not mint temporary Ripple Vault: ${mintMeta!.TransactionResult}` + ); + } - // multisig mint - const mint_multisig_tx = xrpl.multisign(mintNFTSignedTxBlobs); - const mintTx: xrpl.TxResponse = - await this.client.submitAndWait(mint_multisig_tx); // add timeouts - const mintMeta: NFTokenMintMetadata = mintTx.result.meta! as NFTokenMintMetadata; - if (mintMeta!.TransactionResult !== 'tesSUCCESS') { - throw new RippleError( - `Could not mint temporary Ripple Vault: ${mintMeta!.TransactionResult}` - ); + console.log('Success! Done with the mint for SSP'); + } catch (error) { + throw new RippleError(`Unable to set Vault status to PENDING: ${error}`); } - - console.log('Success! Done with the mint for SSP'); - } catch (error) { - throw new RippleError(`Unable to set Vault status to PENDING: ${error}`); - } + }); } async setVaultStatusPending( burnNFTSignedTxBlobs: string[], mintNFTSignedTxBlobs: string[] ): Promise { - try { - await connectRippleClient(this.client); + return await this.withConnectionMgmt(async () => { + try { + console.log('Doing the burn for SSP'); + // multisig burn + const burn_multisig_tx = xrpl.multisign(burnNFTSignedTxBlobs); + const burnTx: xrpl.TxResponse = + await this.client.submitAndWait(burn_multisig_tx); + const burnMeta: NFTokenMintMetadata = burnTx.result.meta! as NFTokenMintMetadata; + if (burnMeta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError( + `Could not burn temporary Ripple Vault: ${burnMeta!.TransactionResult}` + ); + } - console.log('Doing the burn for SSP'); - // multisig burn - const burn_multisig_tx = xrpl.multisign(burnNFTSignedTxBlobs); - const burnTx: xrpl.TxResponse = - await this.client.submitAndWait(burn_multisig_tx); - const burnMeta: NFTokenMintMetadata = burnTx.result.meta! as NFTokenMintMetadata; - if (burnMeta!.TransactionResult !== 'tesSUCCESS') { - throw new RippleError( - `Could not burn temporary Ripple Vault: ${burnMeta!.TransactionResult}` - ); - } + console.log('Success! Now Doing the mint for SSP'); - console.log('Success! Now Doing the mint for SSP'); + // multisig mint + const mint_multisig_tx = xrpl.multisign(mintNFTSignedTxBlobs); + const mintTx: xrpl.TxResponse = + await this.client.submitAndWait(mint_multisig_tx); + const mintMeta: NFTokenMintMetadata = mintTx.result.meta! as NFTokenMintMetadata; + if (mintMeta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError( + `Could not mint temporary Ripple Vault: ${mintMeta!.TransactionResult}` + ); + } - // multisig mint - const mint_multisig_tx = xrpl.multisign(mintNFTSignedTxBlobs); - const mintTx: xrpl.TxResponse = - await this.client.submitAndWait(mint_multisig_tx); - const mintMeta: NFTokenMintMetadata = mintTx.result.meta! as NFTokenMintMetadata; - if (mintMeta!.TransactionResult !== 'tesSUCCESS') { - throw new RippleError( - `Could not mint temporary Ripple Vault: ${mintMeta!.TransactionResult}` - ); + console.log('Success! Done with the mint for SSP'); + } catch (error) { + throw new RippleError(`Unable to set Vault status to PENDING: ${error}`); } - - console.log('Success! Done with the mint for SSP'); - } catch (error) { - throw new RippleError(`Unable to set Vault status to PENDING: ${error}`); - } + }); } async getContractVaults(): Promise { - try { - await connectRippleClient(this.client); - - return await getAllRippleVaults(this.client, this.issuerAddress); - } catch (error) { - throw new RippleError(`Could not fetch All Vaults: ${error}`); - } + return await this.withConnectionMgmt(async () => { + try { + return await getAllRippleVaults(this.client, this.issuerAddress); + } catch (error) { + throw new RippleError(`Could not fetch All Vaults: ${error}`); + } + }); } async getNFTokenIdForVault(uuid: string): Promise { - console.log(`Getting NFTokenId for vault: ${uuid}`); - try { - await connectRippleClient(this.client); - - const getNFTsTransaction: AccountNFTsRequest = { - command: 'account_nfts', - account: this.issuerAddress, - limit: 400, - }; - - const nfts: xrpl.AccountNFTsResponse = await this.client.request(getNFTsTransaction); - const matchingNFT = nfts.result.account_nfts.find( - nft => decodeURI(nft.URI!).uuid.slice(2) === uuid - ); + return await this.withConnectionMgmt(async () => { + console.log(`Getting NFTokenId for vault: ${uuid}`); + try { + const getNFTsTransaction: AccountNFTsRequest = { + command: 'account_nfts', + account: this.issuerAddress, + limit: 400, + }; + + const nfts: xrpl.AccountNFTsResponse = await this.client.request(getNFTsTransaction); + const matchingNFT = nfts.result.account_nfts.find( + nft => decodeURI(nft.URI!).uuid.slice(2) === uuid + ); - if (!matchingNFT) { - throw new RippleError(`Vault for uuid: ${uuid} not found`); + if (!matchingNFT) { + throw new RippleError(`Vault for uuid: ${uuid} not found`); + } + return matchingNFT.NFTokenID; + } catch (error) { + throw new RippleError(`Could not find NFTokenId for vault Vault: ${error}`); } - return matchingNFT.NFTokenID; - } catch (error) { - throw new RippleError(`Could not find NFTokenId for vault Vault: ${error}`); - } + }); } async burnNFT(nftUUID: string, incrementBy: number = 0): Promise { - try { - await connectRippleClient(this.client); - - console.log(`Getting sig for Burning Ripple Vault, vault: ${nftUUID}`); - const nftTokenId = await this.getNFTokenIdForVault(nftUUID); - const burnTransactionJson: SubmittableTransaction = { - TransactionType: 'NFTokenBurn', - Account: this.issuerAddress, - NFTokenID: nftTokenId, - }; - const preparedBurnTx = await this.client.autofill(burnTransactionJson, this.minSigners); // this hardcoded number should match the number of active signers - - // 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 - preparedBurnTx.LastLedgerSequence = - Math.ceil(preparedBurnTx.LastLedgerSequence! / 10000 + 1) * 10000; // Better way?!? - - if (incrementBy > 0) { - preparedBurnTx.Sequence = preparedBurnTx.Sequence! + incrementBy; - } + return await this.withConnectionMgmt(async () => { + try { + console.log(`Getting sig for Burning Ripple Vault, vault: ${nftUUID}`); + const nftTokenId = await this.getNFTokenIdForVault(nftUUID); + const burnTransactionJson: SubmittableTransaction = { + TransactionType: 'NFTokenBurn', + Account: this.issuerAddress, + NFTokenID: nftTokenId, + }; + const preparedBurnTx = await this.client.autofill(burnTransactionJson, this.minSigners); + + // set the LastLedgerSequence to be rounded up to the nearest 10000. + // 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 + // The request has a timeout, so this shouldn't end up being a hanging request + // Using the ticket system would likely be a better way: + // https://xrpl.org/docs/concepts/accounts/tickets + preparedBurnTx.LastLedgerSequence = + Math.ceil(preparedBurnTx.LastLedgerSequence! / 10000 + 1) * 10000; + + if (incrementBy > 0) { + preparedBurnTx.Sequence = preparedBurnTx.Sequence! + incrementBy; + } - console.log('preparedBurnTx ', preparedBurnTx); + console.log('preparedBurnTx ', preparedBurnTx); - const sig = this.wallet.sign(preparedBurnTx, true); - // console.log('tx_one_sig: ', sig); - return sig.tx_blob; - } catch (error) { - throw new RippleError(`Could not burn Vault: ${error}`); - } + const sig = this.wallet.sign(preparedBurnTx, true); + // console.log('tx_one_sig: ', sig); + return sig.tx_blob; + } catch (error) { + throw new RippleError(`Could not burn Vault: ${error}`); + } + }); } async mintNFT(vault: RawVault, incrementBy: number = 0): Promise { - try { - 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); - const mintTransactionJson: SubmittableTransaction = { - TransactionType: 'NFTokenMint', - Account: this.issuerAddress, - URI: newURI, - NFTokenTaxon: 0, - }; - const preparedMintTx = await this.client.autofill(mintTransactionJson, 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 - preparedMintTx.LastLedgerSequence = - Math.ceil(preparedMintTx.LastLedgerSequence! / 10000 + 1) * 10000; - if (incrementBy > 0) { - preparedMintTx.Sequence = preparedMintTx.Sequence! + incrementBy; - } + return await this.withConnectionMgmt(async () => { + try { + console.log( + `Getting sig for Minting Ripple Vault, vault: ${JSON.stringify(vault, null, 2)}` + ); + const newURI = encodeURI(vault); + console.log('newURI: ', newURI); + const mintTransactionJson: SubmittableTransaction = { + TransactionType: 'NFTokenMint', + Account: this.issuerAddress, + URI: newURI, + NFTokenTaxon: 0, + }; + const preparedMintTx = await this.client.autofill(mintTransactionJson, this.minSigners); + + // set the LastLedgerSequence to be rounded up to the nearest 10000. + // 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 + // The request has a timeout, so this shouldn't end up being a hanging request + // Using the ticket system would likely be a better way: + // https://xrpl.org/docs/concepts/accounts/tickets + preparedMintTx.LastLedgerSequence = + Math.ceil(preparedMintTx.LastLedgerSequence! / 10000 + 1) * 10000; + if (incrementBy > 0) { + preparedMintTx.Sequence = preparedMintTx.Sequence! + incrementBy; + } - console.log('preparedMintTx ', preparedMintTx); + console.log('preparedMintTx ', preparedMintTx); - const sig = this.wallet.sign(preparedMintTx, true); - console.log('tx_one_sig: ', sig); - return sig.tx_blob; - } catch (error) { - throw new RippleError(`Could not mint Vault: ${error}`); - } + const sig = this.wallet.sign(preparedMintTx, true); + console.log('tx_one_sig: ', sig); + return sig.tx_blob; + } catch (error) { + throw new RippleError(`Could not mint Vault: ${error}`); + } + }); } async getSigUpdateVaultForSSP(uuid: string, updates: SSPVaultUpdate): Promise { - try { - await connectRippleClient(this.client); - - console.log(`Getting sig for getSigUpdateVaultForSSP, vault uuid: ${uuid}`); - const nftUUID = uuid; - const thisVault = await this.getRawVault(nftUUID); - console.log(`the vault, vault: `, thisVault); - const updatedVault = { - ...thisVault, - status: updates.status, - wdTxId: updates.wdTxId, - taprootPubKey: updates.taprootPubKey, - }; - console.log(`the updated vault, vault: `, updatedVault); - return await this.mintNFT(updatedVault, 1); - } catch (error) { - throw new RippleError(`Could not update Vault: ${error}`); - } + return await this.withConnectionMgmt(async () => { + try { + console.log(`Getting sig for getSigUpdateVaultForSSP, vault uuid: ${uuid}`); + const nftUUID = uuid; + const thisVault = await this.getRawVault(nftUUID); + console.log(`the vault, vault: `, thisVault); + const updatedVault = { + ...thisVault, + status: updates.status, + wdTxId: updates.wdTxId, + taprootPubKey: updates.taprootPubKey, + }; + console.log(`the updated vault, vault: `, updatedVault); + return await this.mintNFT(updatedVault, 1); + } catch (error) { + throw new RippleError(`Could not update Vault: ${error}`); + } + }); } async getSigUpdateVaultForSSF( @@ -441,118 +458,123 @@ export class RippleHandler { updates: SSFVaultUpdate, updateSequenceBy: number ): Promise { - try { - await connectRippleClient(this.client); - - const nftUUID = uuid; - const thisVault = await this.getRawVault(nftUUID); - const updatedVault = { - ...thisVault, - status: updates.status, - fundingTxId: updates.fundingTxId, - wdTxId: updates.wdTxId, - valueMinted: BigNumber.from(updates.valueMinted), - valueLocked: BigNumber.from(updates.valueLocked), - }; - return await this.mintNFT(updatedVault, updateSequenceBy); - } catch (error) { - throw new RippleError(`Could not update Vault: ${error}`); - } + return await this.withConnectionMgmt(async () => { + try { + const nftUUID = uuid; + const thisVault = await this.getRawVault(nftUUID); + const updatedVault = { + ...thisVault, + status: updates.status, + fundingTxId: updates.fundingTxId, + wdTxId: updates.wdTxId, + valueMinted: BigNumber.from(updates.valueMinted), + valueLocked: BigNumber.from(updates.valueLocked), + }; + return await this.mintNFT(updatedVault, updateSequenceBy); + } catch (error) { + throw new RippleError(`Could not update Vault: ${error}`); + } + }); } async getAllChecks(): Promise { - try { - await connectRippleClient(this.client); - - const getAccountObjectsRequestJSON: Request = { - command: 'account_objects', - account: this.issuerAddress, - ledger_index: 'validated', - type: 'check', - }; - - const getAccountObjectsResponse: AccountObjectsResponse = await this.client.request( - getAccountObjectsRequestJSON - ); + return await this.withConnectionMgmt(async () => { + try { + const getAccountObjectsRequestJSON: Request = { + command: 'account_objects', + account: this.issuerAddress, + ledger_index: 'validated', + type: 'check', + }; + + const getAccountObjectsResponse: AccountObjectsResponse = await this.client.request( + getAccountObjectsRequestJSON + ); - return getAccountObjectsResponse.result.account_objects; - } catch (error) { - throw new RippleError(`Could not fetch Checks: ${error}`); - } + 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; + return await this.withConnectionMgmt(async () => { + try { + const check = await getCheckByTXHash(this.client, this.issuerAddress, txHash); + const invoiceID = check.InvoiceID; - if (!invoiceID) { - throw new RippleError(`Could not find Invoice ID for Check with TX Hash: ${txHash}`); - } + if (!invoiceID) { + throw new RippleError(`Could not find Invoice ID for Check with TX Hash: ${txHash}`); + } - const vault = await this.getRawVault(`0x${invoiceID}`.toLowerCase()); + const vault = await this.getRawVault(`0x${invoiceID}`.toLowerCase()); - if (!vault) { - throw new RippleError(`Could not find Vault for Check with Invoice ID: ${check.InvoiceID}`); - } + if (!vault) { + throw new RippleError( + `Could not find Vault for Check with Invoice ID: ${check.InvoiceID}` + ); + } - const checkSendMax = check.SendMax as IssuedCurrencyAmount; + const checkSendMax = check.SendMax as IssuedCurrencyAmount; - const checkCashSignatures = await this.cashCheck(check.index, checkSendMax.value); + const checkCashSignatures = await this.cashCheck(check.index, checkSendMax.value); - const mintAndBurnSignatures = await this.withdraw( - vault.uuid, - BigInt(shiftValue(Number(checkSendMax.value))) - ); - return [checkCashSignatures, ...mintAndBurnSignatures]; - } catch (error) { - throw new RippleError(`Could not get Cash Check and Withdraw Signatures: ${error}`); - } + const mintAndBurnSignatures = await this.withdraw( + vault.uuid, + BigInt(shiftValue(Number(checkSendMax.value))) + ); + return [checkCashSignatures, ...mintAndBurnSignatures]; + } catch (error) { + throw new RippleError(`Could not get Cash Check and Withdraw Signatures: ${error}`); + } + }); } async cashCheck(checkID: string, dlcBTCAmount: string): Promise { - try { - await connectRippleClient(this.client); + return await this.withConnectionMgmt(async () => { + try { + 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 + ); - 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}`); - } + // set the LastLedgerSequence to be rounded up to the nearest 10000. + // 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 + // The request has a timeout, so this shouldn't end up being a hanging request + // Using the ticket system would likely be a better way: + // https://xrpl.org/docs/concepts/accounts/tickets + 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( @@ -561,57 +583,62 @@ export class RippleHandler { valueMinted: number, incrementBy: number = 0 ): Promise { - try { - await connectRippleClient(this.client); + return await this.withConnectionMgmt(async () => { + try { + 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 + ); - 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; - } + // set the LastLedgerSequence to be rounded up to the nearest 10000. + // 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 + // The request has a timeout, so this shouldn't end up being a hanging request + // Using the ticket system would likely be a better way: + // https://xrpl.org/docs/concepts/accounts/tickets + 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 - ); + console.log( + 'Issuer is about to sign the following mintTokens tx: ', + updatedSendTokenTransactionJSON + ); - const signSendTokenTransactionResponse: SignResponse = this.wallet.sign( - updatedSendTokenTransactionJSON, - true - ); + const signSendTokenTransactionResponse: SignResponse = this.wallet.sign( + updatedSendTokenTransactionJSON, + true + ); - return signSendTokenTransactionResponse.tx_blob; - } catch (error) { - throw new RippleError(`Could not mint tokens: ${error}`); - } + return signSendTokenTransactionResponse.tx_blob; + } catch (error) { + throw new RippleError(`Could not mint tokens: ${error}`); + } + }); } }