From 089d95a1d3e41bdfecb86241d5b87fb2c9e61ea2 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Thu, 31 Oct 2024 14:51:11 +0100 Subject: [PATCH] feat: modify xrpl signature return values --- src/models/ripple.model.ts | 14 ++ src/network-handlers/ripple-handler.ts | 274 +++++++++++++++++-------- tests/unit/utility.test.ts | 16 ++ 3 files changed, 215 insertions(+), 89 deletions(-) diff --git a/src/models/ripple.model.ts b/src/models/ripple.model.ts index 63d3303..4d3b59d 100644 --- a/src/models/ripple.model.ts +++ b/src/models/ripple.model.ts @@ -2,3 +2,17 @@ export interface SignResponse { tx_blob: string; hash: string; } + +export interface AutoFillValues { + signatureType: SignatureType; + LastLedgerSequence: number; + Sequence: number; + Fee: string; +} + +export type SignatureType = 'cashCheck' | 'burnNFT' | 'mintNFT' | 'mintToken'; + +export interface MultisignatureTransactionResponse { + tx_blob: string; + autoFillValues: AutoFillValues; +} diff --git a/src/network-handlers/ripple-handler.ts b/src/network-handlers/ripple-handler.ts index fc0d472..17ddeef 100644 --- a/src/network-handlers/ripple-handler.ts +++ b/src/network-handlers/ripple-handler.ts @@ -1,5 +1,10 @@ import { Decimal } from 'decimal.js'; import { BigNumber } from 'ethers'; +import { + AutoFillValues, + MultisignatureTransactionResponse, + SignResponse, +} from 'src/models/ripple.model.js'; import xrpl, { AccountNFTsRequest, AccountObject, @@ -25,11 +30,6 @@ import { RippleError } from '../models/errors.js'; import { RawVault, SSFVaultUpdate, SSPVaultUpdate } from '../models/ethereum-models.js'; import { shiftValue, unshiftValue } from '../utilities/index.js'; -interface SignResponse { - tx_blob: string; - hash: string; -} - function buildDefaultNftVault(): RawVault { return { uuid: `0x${'0'.repeat(64)}`, @@ -84,6 +84,8 @@ export class RippleHandler { console.log('calling the callback service...'); const result = await callback(); return result; + } catch (error) { + throw new RippleError(`Error while executing XRPL function: ${error}`); } finally { console.log('Disconnecting from the async service...'); if (newConnection) { @@ -149,8 +151,9 @@ export class RippleHandler { userAddress: string, timeStamp: number, btcMintFeeBasisPoints: number, - btcRedeemFeeBasisPoints: number - ): Promise { + btcRedeemFeeBasisPoints: number, + autoFillValues?: AutoFillValues + ): Promise { return await this.withConnectionMgmt(async () => { try { const newVault = buildDefaultNftVault(); @@ -159,24 +162,37 @@ export class RippleHandler { newVault.timestamp = BigNumber.from(timeStamp); newVault.btcMintFeeBasisPoints = BigNumber.from(btcMintFeeBasisPoints); newVault.btcRedeemFeeBasisPoints = BigNumber.from(btcRedeemFeeBasisPoints); - return await this.mintNFT(newVault); + return await this.mintNFT(newVault, undefined, autoFillValues); } catch (error) { throw new RippleError(`Could not setup Ripple Vault: ${error}`); } }); } - async withdraw(uuid: string, withdrawAmount: bigint): Promise { + async withdraw( + uuid: string, + withdrawAmount: bigint, + autoFillValues: AutoFillValues[] + ): Promise { return await this.withConnectionMgmt(async () => { try { - console.log(`Performing Withdraw for User: ${uuid}`); + console.log(`Performing Withdraw from Vault: ${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); + const burnSig = await this.burnNFT( + nftUUID, + 1, + autoFillValues.find(sig => sig.signatureType === 'burnNFT') + ); thisVault.valueMinted = thisVault.valueMinted.sub(BigNumber.from(withdrawAmount)); - const mintSig = await this.mintNFT(thisVault, 2); + const mintSig = await this.mintNFT( + thisVault, + 2, + autoFillValues.find(sig => sig.signatureType === 'mintNFT') + ); return [burnSig, mintSig]; } catch (error) { throw new RippleError(`Unable to perform Withdraw for User: ${error}`); @@ -186,7 +202,7 @@ export class RippleHandler { async setVaultStatusFunded( burnNFTSignedTxBlobs: string[], - mintTokensSignedTxBlobs: string[], + mintTokensSignedTxBlobs: string[] | null, mintNFTSignedTxBlobs: string[] ): Promise { return await this.withConnectionMgmt(async () => { @@ -202,8 +218,10 @@ export class RippleHandler { ); } + console.log('mintTokensSignedTxBlobs: ', mintTokensSignedTxBlobs); + // multisig mint - if (mintTokensSignedTxBlobs.every(sig => sig !== '')) { + if (mintTokensSignedTxBlobs && mintTokensSignedTxBlobs.every(sig => sig !== '')) { console.log('Success! Now minting the actual tokens!! How fun $$'); const mint_token_multisig_tx = xrpl.multisign(mintTokensSignedTxBlobs); @@ -290,6 +308,8 @@ export class RippleHandler { burnNFTSignedTxBlobs: string[], mintNFTSignedTxBlobs: string[] ): Promise { + console.log('burnNFTSignedTxBlobs: ', burnNFTSignedTxBlobs); + console.log('mintNFTSignedTxBlobs: ', mintNFTSignedTxBlobs); return await this.withConnectionMgmt(async () => { try { console.log('Doing the burn for SSP'); @@ -359,7 +379,11 @@ export class RippleHandler { }); } - async burnNFT(nftUUID: string, incrementBy: number = 0): Promise { + async burnNFT( + nftUUID: string, + incrementBy: number = 0, + autoFillValues?: AutoFillValues + ): Promise { return await this.withConnectionMgmt(async () => { try { console.log(`Getting sig for Burning Ripple Vault, vault: ${nftUUID}`); @@ -369,33 +393,53 @@ export class RippleHandler { 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; + const preparedBurnTx = await this.client.autofill(burnTransactionJson, this.minSigners); + if (autoFillValues) { + preparedBurnTx.Fee = autoFillValues.Fee; + preparedBurnTx.LastLedgerSequence = autoFillValues.LastLedgerSequence; + preparedBurnTx.Sequence = autoFillValues.Sequence; + } else { + // 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); - const sig = this.wallet.sign(preparedBurnTx, true); - // console.log('tx_one_sig: ', sig); - return sig.tx_blob; + const burnTransactionSignature = this.wallet.sign(preparedBurnTx, true).tx_blob; + console.log('burnTransactionSignature: ', burnTransactionSignature); + + return { + tx_blob: burnTransactionSignature, + autoFillValues: { + signatureType: 'burnNFT', + LastLedgerSequence: preparedBurnTx.LastLedgerSequence!, + Sequence: preparedBurnTx.Sequence!, + Fee: preparedBurnTx.Fee!, + }, + }; } catch (error) { throw new RippleError(`Could not burn Vault: ${error}`); } }); } - async mintNFT(vault: RawVault, incrementBy: number = 0): Promise { + async mintNFT( + vault: RawVault, + incrementBy: number = 0, + autoFillValues?: AutoFillValues + ): Promise { return await this.withConnectionMgmt(async () => { + console.log('AUTO FILL VALUES: ', autoFillValues); try { console.log( `Getting sig for Minting Ripple Vault, vault: ${JSON.stringify(vault, null, 2)}` @@ -408,37 +452,56 @@ export class RippleHandler { 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; + if (autoFillValues) { + preparedMintTx.Fee = autoFillValues.Fee; + preparedMintTx.LastLedgerSequence = autoFillValues.LastLedgerSequence; + preparedMintTx.Sequence = autoFillValues.Sequence; + } else { + // 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); - const sig = this.wallet.sign(preparedMintTx, true); - console.log('tx_one_sig: ', sig); - return sig.tx_blob; + const mintTransactionSignature = this.wallet.sign(preparedMintTx, true).tx_blob; + console.log('mintTransactionSignature: ', mintTransactionSignature); + return { + tx_blob: mintTransactionSignature, + autoFillValues: { + signatureType: 'mintNFT', + LastLedgerSequence: preparedMintTx.LastLedgerSequence!, + Sequence: preparedMintTx.Sequence!, + Fee: preparedMintTx.Fee!, + }, + }; } catch (error) { throw new RippleError(`Could not mint Vault: ${error}`); } }); } - async getSigUpdateVaultForSSP(uuid: string, updates: SSPVaultUpdate): Promise { + async getSigUpdateVaultForSSP( + uuid: string, + updates: SSPVaultUpdate, + autoFillValues?: AutoFillValues + ): Promise { 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, @@ -446,7 +509,7 @@ export class RippleHandler { taprootPubKey: updates.taprootPubKey, }; console.log(`the updated vault, vault: `, updatedVault); - return await this.mintNFT(updatedVault, 1); + return await this.mintNFT(updatedVault, 1, autoFillValues); } catch (error) { throw new RippleError(`Could not update Vault: ${error}`); } @@ -456,8 +519,9 @@ export class RippleHandler { async getSigUpdateVaultForSSF( uuid: string, updates: SSFVaultUpdate, - updateSequenceBy: number - ): Promise { + updateSequenceBy: number, + autoFillValues?: AutoFillValues + ): Promise { return await this.withConnectionMgmt(async () => { try { const nftUUID = uuid; @@ -470,7 +534,7 @@ export class RippleHandler { valueMinted: BigNumber.from(updates.valueMinted), valueLocked: BigNumber.from(updates.valueLocked), }; - return await this.mintNFT(updatedVault, updateSequenceBy); + return await this.mintNFT(updatedVault, updateSequenceBy, autoFillValues); } catch (error) { throw new RippleError(`Could not update Vault: ${error}`); } @@ -498,7 +562,10 @@ export class RippleHandler { }); } - async getCashCheckAndWithdrawSignatures(txHash: string): Promise { + async getCashCheckAndWithdrawSignatures( + txHash: string, + autoFillValues?: AutoFillValues[] + ): Promise { return await this.withConnectionMgmt(async () => { try { const check = await getCheckByTXHash(this.client, this.issuerAddress, txHash); @@ -518,11 +585,16 @@ export class RippleHandler { const checkSendMax = check.SendMax as IssuedCurrencyAmount; - const checkCashSignatures = await this.cashCheck(check.index, checkSendMax.value); + const checkCashSignatures = await this.cashCheck( + check.index, + checkSendMax.value, + autoFillValues?.find(sig => sig.signatureType === 'cashCheck') + ); const mintAndBurnSignatures = await this.withdraw( vault.uuid, - BigInt(shiftValue(Number(checkSendMax.value))) + BigInt(shiftValue(Number(checkSendMax.value))), + autoFillValues ?? [] ); return [checkCashSignatures, ...mintAndBurnSignatures]; } catch (error) { @@ -531,7 +603,11 @@ export class RippleHandler { }); } - async cashCheck(checkID: string, dlcBTCAmount: string): Promise { + async cashCheck( + checkID: string, + dlcBTCAmount: string, + autoFillValues?: AutoFillValues + ): Promise { return await this.withConnectionMgmt(async () => { try { console.log(`Cashing Check of Check ID ${checkID} for an amount of ${dlcBTCAmount}`); @@ -547,30 +623,40 @@ export class RippleHandler { }, }; - const updatedCashCheckTransactionJSON: CheckCash = await this.client.autofill( + const preparedCashCheckTx = await this.client.autofill( cashCheckTransactionJSON, this.minSigners ); + if (autoFillValues) { + preparedCashCheckTx.Fee = autoFillValues.Fee; + preparedCashCheckTx.LastLedgerSequence = autoFillValues.LastLedgerSequence; + preparedCashCheckTx.Sequence = autoFillValues.Sequence; + } else { + // 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 + preparedCashCheckTx.LastLedgerSequence = + Math.ceil(preparedCashCheckTx.LastLedgerSequence! / 10000 + 1) * 10000; + } - // 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 - ); + console.log('Issuer is about to sign the following cashCheck tx: ', preparedCashCheckTx); const signCashCheckTransactionSig: SignResponse = this.wallet.sign( - updatedCashCheckTransactionJSON, + preparedCashCheckTx, true ); - return signCashCheckTransactionSig.tx_blob; + return { + tx_blob: signCashCheckTransactionSig.tx_blob, + autoFillValues: { + signatureType: 'cashCheck', + LastLedgerSequence: preparedCashCheckTx.LastLedgerSequence!, + Sequence: preparedCashCheckTx.Sequence!, + Fee: preparedCashCheckTx.Fee!, + }, + }; } catch (error) { throw new RippleError(`Could not cash Check: ${error}`); } @@ -581,13 +667,14 @@ export class RippleHandler { updatedValueMinted: number, destinationAddress: string, valueMinted: number, - incrementBy: number = 0 - ): Promise { + incrementBy: number = 0, + autoFillValues?: AutoFillValues + ): Promise { 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 ''; + return; } const mintValue = unshiftValue( new Decimal(updatedValueMinted).minus(valueMinted).toNumber() @@ -607,35 +694,44 @@ export class RippleHandler { }, }; - const updatedSendTokenTransactionJSON: Payment = await this.client.autofill( + const preparedSendTokenTx = await this.client.autofill( sendTokenTransactionJSON, 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 - updatedSendTokenTransactionJSON.LastLedgerSequence = - Math.ceil(updatedSendTokenTransactionJSON.LastLedgerSequence! / 10000 + 1) * 10000; - - if (incrementBy > 0) { - updatedSendTokenTransactionJSON.Sequence = - updatedSendTokenTransactionJSON.Sequence! + incrementBy; + if (autoFillValues) { + preparedSendTokenTx.Fee = autoFillValues.Fee; + preparedSendTokenTx.LastLedgerSequence = autoFillValues.LastLedgerSequence; + preparedSendTokenTx.Sequence = autoFillValues.Sequence; + } else { + // 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 + preparedSendTokenTx.LastLedgerSequence = + Math.ceil(preparedSendTokenTx.LastLedgerSequence! / 10000 + 1) * 10000; + + if (incrementBy > 0) { + preparedSendTokenTx.Sequence = preparedSendTokenTx.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: ', preparedSendTokenTx); const signSendTokenTransactionResponse: SignResponse = this.wallet.sign( - updatedSendTokenTransactionJSON, + preparedSendTokenTx, true ); - return signSendTokenTransactionResponse.tx_blob; + return { + tx_blob: signSendTokenTransactionResponse.tx_blob, + autoFillValues: { + signatureType: 'mintToken', + LastLedgerSequence: preparedSendTokenTx.LastLedgerSequence!, + Sequence: preparedSendTokenTx.Sequence!, + Fee: preparedSendTokenTx.Fee!, + }, + }; } catch (error) { throw new RippleError(`Could not mint tokens: ${error}`); } diff --git a/tests/unit/utility.test.ts b/tests/unit/utility.test.ts index 9677c9b..d7b7da2 100644 --- a/tests/unit/utility.test.ts +++ b/tests/unit/utility.test.ts @@ -1,3 +1,5 @@ +import { decode } from 'ripple-binary-codec'; + import { compareUint8Arrays, createRangeFromLength, @@ -13,6 +15,20 @@ import { } from '../../src/utilities'; describe('Utility Functions', () => { + describe('template', () => { + it('should do something', () => { + console.log( + decode( + '12001922000000002400181963201B001DC130202A00000000684000000000000030730075C1260100DBC7CBB16D7BAE3E686A78651470BE7243384AA2F35C0928E4E2CDBF4E3715D200000000000000000000000000000000000000000000672369547266767462725853784C73785657446B74523473647A6A4A677638456E4D4B464B47000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0000000C03C9FC819E3C26EC4A58639ADD07F6372E810513F5D3D7374C25C65FDF1AEFE4C500000000000000000000000000000000000000000000000000000000000000008114C9C3EE81E01CB4BE6B7B905872D46BE280759158F3E0107321ED62D2459606EB785F16D3D1C840A143804D7580DF44625E560B1DC9E1800B66F674402FCEC3377F927B3AEBCE1548A89DB11A24206F2520504C08C99E3A0706722283E34828757CFABC640262B0EF06D7A077ADD7E9367DD5BB10462781A3FD4394028114AF233B92B03460E6C2599CC9C773A29D50277EAAE1F1' + ) + ); + console.log( + decode( + '1200192400181963201B001DC130202A00000000684000000000000030730075C1260100DBC7CBB16D7BAE3E686A78651470BE7243384AA2F35C0928E4E2CDBF4E3715D200000000000000000000000000000000000000000000672369547266767462725853784C73785657446B74523473647A6A4A677638456E4D4B464B47000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C0000000C03C9FC819E3C26EC4A58639ADD07F6372E810513F5D3D7374C25C65FDF1AEFE4C500000000000000000000000000000000000000000000000000000000000000008114C9C3EE81E01CB4BE6B7B905872D46BE280759158F3E0107321ED2C44668E26E34B01FD499DA91FFE04BFEFA51DCC05779EC2B69AC89F0CE3818D7440E4EBEA045B5429871C61576AA81E618918466B4FC77EA0FBD7C39A57BCB942455B71CA0164604A306470B02136AEA1420F415C254C6E0C245131B1ECE2D63A00811437B01400075D2DA23E15EFBEBD323B4C8CB2515BE1F1' + ) + ); + }); + }); describe('shiftValue', () => { it('correctly shifts the value by a power of 10^8', () => { const value = 1;