diff --git a/package.json b/package.json index 58c6ea0..e099081 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "prompts": "^2.4.2", "ramda": "^0.30.1", "scure": "^1.6.0", - "tiny-secp256k1": "^2.2.3" + "tiny-secp256k1": "^2.2.3", + "xrpl": "^4.0.0" } } diff --git a/src/functions/bitcoin/bitcoin-request-functions.ts b/src/functions/bitcoin/bitcoin-request-functions.ts index 83f4240..33cdb09 100644 --- a/src/functions/bitcoin/bitcoin-request-functions.ts +++ b/src/functions/bitcoin/bitcoin-request-functions.ts @@ -17,7 +17,9 @@ export async function fetchBitcoinTransaction( const response = await fetch(bitcoinBlockchainAPITransactionEndpoint); if (!response.ok) - throw new Error(`Bitcoin Network Transaction Response was not OK: ${response.statusText}`); + throw new Error( + `Bitcoin Network Transaction Response was not OK: ${response.statusText} - btc tx id: ${txID}` + ); return await response.json(); } catch (error) { diff --git a/src/index.ts b/src/index.ts index e750e7c..1a25c2f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import { LedgerDLCHandler } from './dlc-handlers/ledger-dlc-handler.js'; import { PrivateKeyDLCHandler } from './dlc-handlers/private-key-dlc-handler.js'; import { SoftwareWalletDLCHandler } from './dlc-handlers/software-wallet-dlc-handler.js'; import { EthereumHandler } from './network-handlers/ethereum-handler.js'; +import { RippleHandler } from './network-handlers/ripple-handler.js'; import { ProofOfReserveHandler } from './proof-of-reserve-handlers/proof-of-reserve-handler.js'; export { @@ -10,4 +11,5 @@ export { SoftwareWalletDLCHandler, EthereumHandler, ProofOfReserveHandler, + RippleHandler, }; diff --git a/src/models/attestor.models.ts b/src/models/attestor.models.ts index 92b9bd2..bd1fe88 100644 --- a/src/models/attestor.models.ts +++ b/src/models/attestor.models.ts @@ -3,7 +3,9 @@ export type AttestorChainID = | 'evm-arbsepolia' | 'evm-localhost' | 'evm-hardhat-arb' - | 'evm-hardhat-eth'; + | 'evm-hardhat-eth' + | 'ripple-xrpl-mainnet' + | 'ripple-xrpl-testnet'; export interface FundingTXAttestorInfo { vaultUUID: string; diff --git a/src/models/errors.ts b/src/models/errors.ts index 30e65d7..c5b440a 100644 --- a/src/models/errors.ts +++ b/src/models/errors.ts @@ -32,3 +32,10 @@ export class LedgerError extends Error { this.name = 'LedgerError'; } } + +export class RippleError extends Error { + constructor(message: string) { + super(message); + this.name = 'RippleError'; + } +} diff --git a/src/network-handlers/ripple-handler.ts b/src/network-handlers/ripple-handler.ts new file mode 100644 index 0000000..7f35684 --- /dev/null +++ b/src/network-handlers/ripple-handler.ts @@ -0,0 +1,469 @@ +import { BigNumber } from 'ethers'; +import xrpl, { AccountNFTsRequest, SubmittableTransaction } from 'xrpl'; +import { NFTokenMintMetadata } from 'xrpl/dist/npm/models/transactions/NFTokenMint.js'; + +import { RippleError } from '../models/errors.js'; +import { RawVault, VaultState } from '../models/ethereum-models.js'; + +function encodeNftURI(vault: RawVault): string { + const VERSION = parseInt('1', 16).toString().padStart(2, '0'); // 1 as hex + const status = parseInt(vault.status.toString(), 16).toString().padStart(2, '0'); + console.log( + `UUID: ${vault.uuid}, valueLocked: ${vault.valueLocked}, valueMinted: ${vault.valueMinted}` + ); + let uuid = vault.uuid; + if (uuid === '') { + uuid = vault.uuid.padStart(64, '0'); + } + if (uuid.substring(0, 2) === '0x') { + uuid = uuid.slice(2); + } + // add padding so valueLocked and valueMinted are always 16 characters + const valueLockedPadded = vault.valueLocked._hex.substring(2).padStart(16, '0'); + const valueMintedPadded = vault.valueMinted._hex.substring(2).padStart(16, '0'); + const fundingTxId = vault.fundingTxId.padStart(64, '0'); + const wdTxId = vault.wdTxId.padStart(64, '0'); + const btcMintFeeBasisPoints = vault.btcMintFeeBasisPoints._hex.substring(2).padStart(2, '0'); + const btcRedeemFeeBasisPoints = vault.btcRedeemFeeBasisPoints._hex.substring(2).padStart(2, '0'); + const btcFeeRecipient = vault.btcFeeRecipient.padStart(64, '0'); + const taprootPubKey = vault.taprootPubKey.padStart(64, '0'); + console.log( + 'built URI:', + `${VERSION}${status}${uuid}${valueLockedPadded}${valueMintedPadded}${fundingTxId}${wdTxId}${btcMintFeeBasisPoints}${btcRedeemFeeBasisPoints}${btcFeeRecipient}${taprootPubKey}` + ); + return `${VERSION}${status}${uuid}${valueLockedPadded}${valueMintedPadded}${fundingTxId}${wdTxId}${btcMintFeeBasisPoints}${btcRedeemFeeBasisPoints}${btcFeeRecipient}${taprootPubKey}`; +} + +function decodeNftURI(URI: string): RawVault { + let VERSION = 0; + let uuid = ''; + let valueLocked = BigNumber.from(0); + let valueMinted = BigNumber.from(0); + let status = 0; + let fundingTxId = ''; + let wdTxId = ''; + let btcMintFeeBasisPoints = BigNumber.from(0); + let btcRedeemFeeBasisPoints = BigNumber.from(0); + let btcFeeRecipient = ''; + let taprootPubKey = ''; + try { + VERSION = parseInt(URI.slice(0, 2), 16); + status = parseInt(URI.slice(2, 4), 16); + console.log(`Decoding URI: ${URI}`); + uuid = URI.slice(4, 68); + valueLocked = BigNumber.from(`0x${URI.slice(68, 84)}`); + valueMinted = BigNumber.from(`0x${URI.slice(84, 100)}`); + fundingTxId = URI.slice(100, 164); + if (fundingTxId === '0'.repeat(64)) { + fundingTxId = ''; + } + wdTxId = URI.slice(164, 228); + if (wdTxId === '0'.repeat(64)) { + wdTxId = ''; + } + btcMintFeeBasisPoints = BigNumber.from(`0x${URI.slice(228, 230)}`); + btcRedeemFeeBasisPoints = BigNumber.from(`0x${URI.slice(230, 232)}`); + btcFeeRecipient = URI.slice(232, 298); + taprootPubKey = URI.slice(298, 362); + } catch (error) { + console.log(`Error decoding URI: ${error}`); + console.log(`URI which failed to Decode: ${URI}`); + return {} as RawVault; + } + console.log( + `Decoded URI: Version: ${VERSION}, status: ${status}, UUID: ${uuid}, valueLocked: ${valueLocked}, valueMinted: ${valueMinted}, fundingTxId: ${fundingTxId}, wdTxId: ${wdTxId}, btcMintFeeBasisPoints: ${btcMintFeeBasisPoints}, btcRedeemFeeBasisPoints: ${btcRedeemFeeBasisPoints}, btcFeeRecipient: ${btcFeeRecipient} , taprootPubKey: ${taprootPubKey}` + ); + const baseVault = buildDefaultNftVault(); + return { + ...baseVault, + uuid: `0x${uuid}`, + status: status, + valueLocked: valueLocked, + valueMinted: valueMinted, + fundingTxId: fundingTxId, + wdTxId: wdTxId, + btcMintFeeBasisPoints: btcMintFeeBasisPoints, + btcRedeemFeeBasisPoints: btcRedeemFeeBasisPoints, + btcFeeRecipient: btcFeeRecipient, + taprootPubKey: taprootPubKey, + }; +} + +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)}`, + valueLocked: BigNumber.from(0), + valueMinted: BigNumber.from(0), + protocolContract: '', + timestamp: BigNumber.from(0), + creator: '', + status: 0, + fundingTxId: '0'.repeat(64), + closingTxId: '', + wdTxId: '0'.repeat(64), + btcFeeRecipient: '03c9fc819e3c26ec4a58639add07f6372e810513f5d3d7374c25c65fdf1aefe4c5', + btcMintFeeBasisPoints: BigNumber.from(100), + btcRedeemFeeBasisPoints: BigNumber.from(100), + taprootPubKey: '0'.repeat(64), + }; +} + +export class RippleHandler { + private client: xrpl.Client; + private demo_wallet: xrpl.Wallet; + + private constructor() { + this.client = new xrpl.Client('wss://s.altnet.rippletest.net:51233'); + this.demo_wallet = xrpl.Wallet.fromSeed('sEdV6wSeVoUGwu7KHyFZ3UkrQxpvGZU'); //rNT2CxBbKtiUwy4UFwXS11PETZVW8j4k3g + } + + static fromWhatever(): RippleHandler { + return new RippleHandler(); + } + + async getNetworkInfo(): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + try { + return await this.client.request({ command: 'server_info' }); + } catch (error) { + throw new RippleError(`Could not fetch Network Info: ${error}`); + } + } + + async getAddress(): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + try { + return this.demo_wallet.classicAddress; + } catch (error) { + throw new RippleError(`Could not fetch Address Info: ${error}`); + } + } + + // static fromPrivateKey( + // ethereumDeploymentPlans: EthereumDeploymentPlan[], + // ethereumPrivateKey: string, + // rpcEndpoint: string + // ): EthereumHandler { + // const provider = getProvider(rpcEndpoint); + // const signer = new Wallet(ethereumPrivateKey, provider); + // const ethereumContracts = getEthereumContracts(ethereumDeploymentPlans, signer); + // return new EthereumHandler(ethereumContracts); + // } + + // static fromSigner( + // ethereumDeploymentPlans: EthereumDeploymentPlan[], + // signer: providers.JsonRpcSigner + // ): EthereumHandler { + // const ethereumContracts = getEthereumContracts(ethereumDeploymentPlans, signer); + // return new EthereumHandler(ethereumContracts); + // } + + // getContracts(): DLCEthereumContracts { + // return this.ethereumContracts; + // } + + // async getAllUserVaults(): Promise { + // try { + // const userAddress = await this.ethereumContracts.dlcManagerContract.signer.getAddress(); + // return await getAllAddressVaults(this.ethereumContracts.dlcManagerContract, userAddress); + // } catch (error) { + // throw new EthereumHandlerError(`Could not fetch all User Vaults: ${error}`); + // } + // } + + async getRawVault(uuid: string): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + try { + const getNFTsTransaction: AccountNFTsRequest = { + command: 'account_nfts', + account: this.demo_wallet.classicAddress, + }; + 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.find(nft => nft.NFTokenID === nftTokenId); + if (!matchingNFT) { + throw new RippleError(`Vault with UUID: ${nftUUID} not found`); + } + const matchingVault: RawVault = decodeNftURI(matchingNFT.URI!); + return lowercaseHexFields(matchingVault); + } catch (error) { + throw new RippleError(`Could not fetch Vault: ${error}`); + } + } + + async setupVault(): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + try { + const newVault = buildDefaultNftVault(); + const newVaultUUID = await this.mintNFT(newVault); + + await this.burnNFT(newVaultUUID); + + newVault.uuid = newVaultUUID; + await this.mintNFT(newVault); + + return `0x${newVaultUUID}`; + } catch (error) { + throw new RippleError(`Could not setup Ripple Vault: ${error}`); + } + } + + async withdraw(uuid: string, withdrawAmount: bigint) { + // 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 { + let nftUUID = uuid.substring(0, 2) === '0x' ? uuid.slice(2) : uuid; + nftUUID = nftUUID.toUpperCase(); + // return await withdraw(this.ethereumContracts.dlcManagerContract, vaultUUID, withdrawAmount); + const thisVault = await this.getRawVault(nftUUID); + await this.burnNFT(nftUUID); + thisVault.valueMinted = thisVault.valueMinted.sub(BigNumber.from(withdrawAmount)); + await this.mintNFT(thisVault); + } catch (error) { + throw new RippleError(`Unable to perform Withdraw for User: ${error}`); + } + } + + async setVaultStatusFunded( + uuid: string, + bitcoinTransactionID: string, + updatedValueMinted: bigint + ): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + try { + let nftUUID = uuid.substring(0, 2) === '0x' ? uuid.slice(2) : uuid; + nftUUID = nftUUID.toUpperCase(); + + console.log(`Setting Vault status to FUNDED, vault: ${nftUUID}`); + const thisVault = await this.getRawVault(nftUUID); + await this.burnNFT(nftUUID); + const newVault = { + ...thisVault, + status: VaultState.FUNDED, + fundingTxId: bitcoinTransactionID, + wdTxId: '', + valueMinted: BigNumber.from(updatedValueMinted), + valueLocked: BigNumber.from(updatedValueMinted), + }; + await this.mintNFT(newVault); + console.log(`Vault status set to FUNDED, vault: ${nftUUID}`); + } catch (error) { + throw new RippleError(`Unable to set Vault status to FUNDED: ${error}`); + } + } + + async setVaultStatusPending( + uuid: string, + bitcoinTransactionID: string, + updatedValueMinted: bigint, + userPubkey: string + ): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + try { + let nftUUID = uuid.substring(0, 2) === '0x' ? uuid.slice(2) : uuid; + nftUUID = nftUUID.toUpperCase(); + + console.log(`Setting Vault status to Pending, vault: ${nftUUID}`); + const thisVault = await this.getRawVault(nftUUID); + await this.burnNFT(nftUUID); + const newVault = { + ...thisVault, + status: VaultState.PENDING, + wdTxId: bitcoinTransactionID, + taprootPubKey: userPubkey, + }; + await this.mintNFT(newVault); + console.log(`Vault status set to Pending, vault: ${nftUUID}`); + } catch (error) { + throw new RippleError(`Unable to set Vault status to FUNDED: ${error}`); + } + } + + // async getUserDLCBTCBalance(): Promise { + // try { + // const userAddress = await this.ethereumContracts.dlcManagerContract.signer.getAddress(); + // return await getAddressDLCBTCBalance(this.ethereumContracts.dlcBTCContract, userAddress); + // } catch (error) { + // throw new EthereumHandlerError(`Could not fetch User's dlcBTC balance: ${error}`); + // } + // } + + // async getDLCBTCTotalSupply(): Promise { + // try { + // return await getDLCBTCTotalSupply(this.ethereumContracts.dlcBTCContract); + // } catch (error) { + // throw new EthereumHandlerError(`Could not fetch Total Supply of dlcBTC: ${error}`); + // } + // } + + // async getLockedBTCBalance(userVaults?: RawVault[]): Promise { + // try { + // if (!userVaults) { + // userVaults = await this.getAllUserVaults(); + // } + // return await getLockedBTCBalance(userVaults); + // } catch (error) { + // throw new EthereumHandlerError(`Could not fetch Total Supply of Locked dlcBTC: ${error}`); + // } + // } + + // async getAttestorGroupPublicKey(): Promise { + // try { + // return getAttestorGroupPublicKey(this.ethereumContracts.dlcManagerContract); + // } catch (error) { + // throw new EthereumHandlerError(`Could not fetch Attestor Public Key: ${error}`); + // } + // } + + // async isWhiteLisingEnabled(): Promise { + // try { + // return await isWhitelistingEnabled(this.ethereumContracts.dlcManagerContract); + // } catch (error) { + // throw new EthereumHandlerError(`Could not fetch Whitelisting Status: ${error}`); + // } + // } + + // async isUserWhitelisted(): Promise { + // try { + // const userAddress = await this.ethereumContracts.dlcManagerContract.signer.getAddress(); + // return await isUserWhitelisted(this.ethereumContracts.dlcManagerContract, userAddress); + // } catch (error) { + // throw new EthereumHandlerError(`Could not fetch User Whitelisting Status: ${error}`); + // } + // } + + async getContractVaults(): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + try { + const getNFTsTransaction: AccountNFTsRequest = { + command: 'account_nfts', + account: this.demo_wallet.classicAddress, + }; + + const nfts: xrpl.AccountNFTsResponse = await this.client.request(getNFTsTransaction); + const allNFTs = nfts.result.account_nfts; + const allVaults: RawVault[] = allNFTs.map(nft => lowercaseHexFields(decodeNftURI(nft.URI!))); + + // allVaults.forEach((vault, index) => { + // if (vault.uuid === '') { + // vault.uuid = allNFTs[index].NFTokenID; + // } + // }); + + return allVaults; + } 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 { + const getNFTsTransaction: AccountNFTsRequest = { + command: 'account_nfts', + account: this.demo_wallet.classicAddress, + }; + + const nfts: xrpl.AccountNFTsResponse = await this.client.request(getNFTsTransaction); + let matchingNFT = nfts.result.account_nfts.find( + nft => decodeNftURI(nft.URI!).uuid.slice(2) === uuid + ); + if (!matchingNFT) { + console.log('Could not find matching NFT by URI, trying by NFTokenID'); + // when first creating a vault, the tokenID is the UUID + matchingNFT = nfts.result.account_nfts.find(nft => nft.NFTokenID === uuid); + if (matchingNFT) { + console.log('Found matching NFT by NFTokenID'); + } + } else { + console.log('Found matching NFT by URI'); + } + 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}`); + } + } + + async burnNFT(nftUUID: string): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + console.log(`Burning Ripple Vault, vault: ${nftUUID}`); + const nftTokenId = await this.getNFTokenIdForVault(nftUUID); + const burnTransactionJson: SubmittableTransaction = { + TransactionType: 'NFTokenBurn', + Account: this.demo_wallet.classicAddress, + NFTokenID: nftTokenId, + }; + const burnTx: xrpl.TxResponse = await this.client.submitAndWait( + burnTransactionJson, + { wallet: this.demo_wallet } + ); + const burnMeta: NFTokenMintMetadata = burnTx.result.meta! as NFTokenMintMetadata; + if (burnMeta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError( + `Could not burn temporary Ripple Vault: ${burnMeta!.TransactionResult}` + ); + } + } + + async mintNFT(vault: RawVault): Promise { + if (!this.client.isConnected()) { + await this.client.connect(); + } + console.log(`Minting Ripple Vault, vault: ${JSON.stringify(vault, null, 2)}`); + const newURI = encodeNftURI(vault); + const mintTransactionJson: SubmittableTransaction = { + TransactionType: 'NFTokenMint', + Account: this.demo_wallet.classicAddress, + URI: newURI, + NFTokenTaxon: 0, + }; + const mintTx: xrpl.TxResponse = await this.client.submitAndWait( + mintTransactionJson, + { wallet: this.demo_wallet } + ); + const meta: NFTokenMintMetadata = mintTx.result.meta! as NFTokenMintMetadata; + if (meta!.TransactionResult !== 'tesSUCCESS') { + throw new RippleError(`Could not mint Ripple Vault: ${meta!.TransactionResult}`); + } + return meta!.nftoken_id!; + } +} diff --git a/yarn.lock b/yarn.lock index 8aa3761..3460762 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1105,6 +1105,13 @@ resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.12.0.tgz#ad903528bf3687a44da435d7b2479d724d374f5d" integrity sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA== +"@noble/curves@^1.0.0", "@noble/curves@~1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + "@noble/curves@~1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" @@ -1117,6 +1124,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== +"@noble/hashes@1.5.0", "@noble/hashes@^1.0.0", "@noble/hashes@~1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + "@noble/secp256k1@^1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -1153,6 +1165,28 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== +"@scure/base@^1.1.3", "@scure/base@~1.1.7", "@scure/base@~1.1.8": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== + +"@scure/bip32@^1.3.1": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6" + integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw== + dependencies: + "@noble/curves" "~1.6.0" + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.7" + +"@scure/bip39@^1.2.1": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" + integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== + dependencies: + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.8" + "@scure/btc-signer@^1.3.1": version "1.3.2" resolved "https://registry.yarnpkg.com/@scure/btc-signer/-/btc-signer-1.3.2.tgz#56cf02a2e318097b1e4f531fac8ef114bdf4ddc8" @@ -1457,6 +1491,23 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@xrplf/isomorphic@^1.0.0", "@xrplf/isomorphic@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@xrplf/isomorphic/-/isomorphic-1.0.1.tgz#d7676e0ec0e55a39f37ddc1f3cc30eeab52e0739" + integrity sha512-0bIpgx8PDjYdrLFeC3csF305QQ1L7sxaWnL5y71mCvhenZzJgku9QsA+9QCXBC1eNYtxWO/xR91zrXJy2T/ixg== + dependencies: + "@noble/hashes" "^1.0.0" + eventemitter3 "5.0.1" + ws "^8.13.0" + +"@xrplf/secret-numbers@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@xrplf/secret-numbers/-/secret-numbers-1.0.0.tgz#cc19ff84236cc2737b38f2e42a29924f2b8ffc0e" + integrity sha512-qsCLGyqe1zaq9j7PZJopK+iGTGRbk6akkg6iZXJJgxKwck0C5x5Gnwlb1HKYGOwPKyrXWpV6a2YmcpNpUFctGg== + dependencies: + "@xrplf/isomorphic" "^1.0.0" + ripple-keypairs "^2.0.0" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -1694,6 +1745,11 @@ bech32@^2.0.0: resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== +bignumber.js@^9.0.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + bindings@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -2547,6 +2603,11 @@ ethers@5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +eventemitter3@5.0.1, eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -4354,6 +4415,32 @@ ripemd160@2, ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +ripple-address-codec@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-5.0.0.tgz#97059f7bba6f9ed7a52843de8aa427723fb529f6" + integrity sha512-de7osLRH/pt5HX2xw2TRJtbdLLWHu0RXirpQaEeCnWKY5DYHykh3ETSkofvm0aX0LJiV7kwkegJxQkmbO94gWw== + dependencies: + "@scure/base" "^1.1.3" + "@xrplf/isomorphic" "^1.0.0" + +ripple-binary-codec@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-2.1.0.tgz#f1ef81f8d1f05a6cecc06fc6d9b13456569cafda" + integrity sha512-q0GAx+hj3UVcDbhXVjk7qeNfgUMehlElYJwiCuIBwqs/51GVTOwLr39Ht3eNsX5ow2xPRaC5mqHwcFDvLRm6cA== + dependencies: + "@xrplf/isomorphic" "^1.0.1" + bignumber.js "^9.0.0" + ripple-address-codec "^5.0.0" + +ripple-keypairs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-2.0.0.tgz#4a1a8142e9a58c07e61b3cc6cfe7317db718d289" + integrity sha512-b5rfL2EZiffmklqZk1W+dvSy97v3V/C7936WxCCgDynaGPp7GE6R2XO7EU9O2LlM/z95rj870IylYnOQs+1Rag== + dependencies: + "@noble/curves" "^1.0.0" + "@xrplf/isomorphic" "^1.0.0" + ripple-address-codec "^5.0.0" + run-async@^2.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -5024,6 +5111,26 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@^8.13.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +xrpl@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xrpl/-/xrpl-4.0.0.tgz#c031b848c2a3e955b69b1dd438b1156e6826a2d6" + integrity sha512-VZm1lQWHQ6PheAAFGdH+ISXKvqB2hZDQ0w4ZcdAEtmqZQXtSIVQHOKPz95rEgGANbos7+XClxJ73++joPhA8Cw== + dependencies: + "@scure/bip32" "^1.3.1" + "@scure/bip39" "^1.2.1" + "@xrplf/isomorphic" "^1.0.1" + "@xrplf/secret-numbers" "^1.0.0" + bignumber.js "^9.0.0" + eventemitter3 "^5.0.1" + ripple-address-codec "^5.0.0" + ripple-binary-codec "^2.1.0" + ripple-keypairs "^2.0.0" + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"