From 831a9ac8eb837ae97e37e1674c764d41912cf18e Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Thu, 16 Jan 2025 22:30:17 +0300 Subject: [PATCH 01/19] added v4 sign --- packages/networks/ton/package.json | 2 +- .../networks/ton/src/services/Provider.ts | 9 ++++++++ .../ton/src/services/TransactionSigner.ts | 23 +++++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index fb8843e..ce45d9c 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/ton", - "version": "0.1.0", + "version": "0.1.1", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/ton/src/services/Provider.ts b/packages/networks/ton/src/services/Provider.ts index a5cdc2b..e7fb190 100644 --- a/packages/networks/ton/src/services/Provider.ts +++ b/packages/networks/ton/src/services/Provider.ts @@ -298,6 +298,15 @@ export class Provider implements ProviderInterface { }) } + /** + * Create wallet contract for version 4 + * @param publicKey - Public key of the wallet + * @returns Wallet contract + */ + createWalletV4(publicKey: Buffer): WalletContractV4 { + return WalletContractV4.create({ workchain: this.workchain, publicKey }) + } + /** * Retry the function * @param fn - Function that will be retried diff --git a/packages/networks/ton/src/services/TransactionSigner.ts b/packages/networks/ton/src/services/TransactionSigner.ts index dd836b9..56d5731 100644 --- a/packages/networks/ton/src/services/TransactionSigner.ts +++ b/packages/networks/ton/src/services/TransactionSigner.ts @@ -1,7 +1,7 @@ import { Provider } from '../services/Provider' import { mnemonicToPrivateKey } from '@ton/crypto' -import type { OpenedContract, WalletContractV5R1 } from '@ton/ton' import { type Cell, SendMode, type MessageRelaxed } from '@ton/core' +import type { OpenedContract, WalletContractV4, WalletContractV5R1 } from '@ton/ton' import type { PrivateKey, TransactionId, TransactionSignerInterface } from '@multiplechain/types' export class TransactionSigner implements TransactionSignerInterface { @@ -23,7 +23,7 @@ export class TransactionSigner implements TransactionSignerInterface + wallet: OpenedContract /** * @param rawData - Transaction data @@ -53,6 +53,25 @@ export class TransactionSigner implements TransactionSignerInterface { + const { publicKey, secretKey } = await mnemonicToPrivateKey(privateKey.split(' ')) + const contract = this.provider.createWalletV4(publicKey) + this.wallet = this.provider.client1.open(contract) + const seqno = await this.wallet.getSeqno() + this.signedData = this.wallet.createTransfer({ + seqno, + secretKey, + messages: [this.rawData], + sendMode: SendMode.PAY_GAS_SEPARATELY + }) + return this + } + /** * Send the transaction to the blockchain network * @returns Transaction ID From c56d16fe4eee16c0d0486d916b8c3be4db969c23 Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 17 Jan 2025 08:45:32 +0300 Subject: [PATCH 02/19] update type --- packages/networks/ton/package.json | 2 +- .../ton/src/services/TransactionSigner.ts | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index ce45d9c..8fcad3d 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/ton", - "version": "0.1.1", + "version": "0.1.2", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/ton/src/services/TransactionSigner.ts b/packages/networks/ton/src/services/TransactionSigner.ts index 56d5731..08af314 100644 --- a/packages/networks/ton/src/services/TransactionSigner.ts +++ b/packages/networks/ton/src/services/TransactionSigner.ts @@ -4,11 +4,13 @@ import { type Cell, SendMode, type MessageRelaxed } from '@ton/core' import type { OpenedContract, WalletContractV4, WalletContractV5R1 } from '@ton/ton' import type { PrivateKey, TransactionId, TransactionSignerInterface } from '@multiplechain/types' -export class TransactionSigner implements TransactionSignerInterface { +type RawData = MessageRelaxed | MessageRelaxed[] + +export class TransactionSigner implements TransactionSignerInterface { /** * Transaction data from the blockchain network */ - rawData: MessageRelaxed + rawData: RawData /** * Signed transaction data @@ -29,7 +31,7 @@ export class TransactionSigner implements TransactionSignerInterface Date: Fri, 17 Jan 2025 18:27:41 +0300 Subject: [PATCH 03/19] fix --- packages/networks/ton/package.json | 2 +- packages/networks/ton/src/browser/Wallet.ts | 22 ++++++++------ .../ton/src/services/TransactionSigner.ts | 30 ++++++++++++------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index 8fcad3d..2e3fc45 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/ton", - "version": "0.1.2", + "version": "0.1.5", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/ton/src/browser/Wallet.ts b/packages/networks/ton/src/browser/Wallet.ts index 6c89817..80f88d4 100644 --- a/packages/networks/ton/src/browser/Wallet.ts +++ b/packages/networks/ton/src/browser/Wallet.ts @@ -164,20 +164,24 @@ export class Wallet implements WalletInterface { const account = this.walletProvider.account - const data = transactionSigner.getRawData() - const info = data.info as CommonMessageInfoRelaxedInternal + const messages = transactionSigner.getRawData() + + const tonConnectMessageFormat = [] + for (const message of messages) { + const info = message.info as CommonMessageInfoRelaxedInternal + tonConnectMessageFormat.push({ + address: info.dest.toString(), + amount: info.value.coins.toString(), + payload: message.body.toBoc().toString('base64') + }) + } + const result = await this.walletProvider.sendTransaction( { validUntil: Math.floor(Date.now() / 1000) + 60, from: this.getRawAddress(), network: account?.chain, - messages: [ - { - address: info.dest.toString(), - amount: info.value.coins.toString(), - payload: data.body.toBoc().toString('base64') - } - ] + messages: tonConnectMessageFormat }, modalAction ) diff --git a/packages/networks/ton/src/services/TransactionSigner.ts b/packages/networks/ton/src/services/TransactionSigner.ts index 08af314..3fa5138 100644 --- a/packages/networks/ton/src/services/TransactionSigner.ts +++ b/packages/networks/ton/src/services/TransactionSigner.ts @@ -4,13 +4,11 @@ import { type Cell, SendMode, type MessageRelaxed } from '@ton/core' import type { OpenedContract, WalletContractV4, WalletContractV5R1 } from '@ton/ton' import type { PrivateKey, TransactionId, TransactionSignerInterface } from '@multiplechain/types' -type RawData = MessageRelaxed | MessageRelaxed[] - -export class TransactionSigner implements TransactionSignerInterface { +export class TransactionSigner implements TransactionSignerInterface { /** * Transaction data from the blockchain network */ - rawData: RawData + rawData: MessageRelaxed[] /** * Signed transaction data @@ -31,8 +29,8 @@ export class TransactionSigner implements TransactionSignerInterface Date: Wed, 22 Jan 2025 11:32:58 +0300 Subject: [PATCH 04/19] fix --- packages/networks/ton/tests/assets.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/networks/ton/tests/assets.spec.ts b/packages/networks/ton/tests/assets.spec.ts index 23fe402..b8b63b0 100644 --- a/packages/networks/ton/tests/assets.spec.ts +++ b/packages/networks/ton/tests/assets.spec.ts @@ -45,7 +45,7 @@ const checkSigner = async (signer: TransactionSigner, privateKey?: string): Prom const rawData = signer.getRawData() - assert.isObject(rawData) + assert.isArray(rawData) await signer.sign(privateKey ?? senderPrivateKey) From 78f30516fa5098483f068863a04a81a3c56e030e Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Thu, 23 Jan 2025 10:54:38 +0300 Subject: [PATCH 05/19] fix --- packages/networks/ton/package.json | 2 +- packages/networks/ton/src/browser/adapters/TonConnect.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index 2e3fc45..370a2ab 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/ton", - "version": "0.1.5", + "version": "0.1.6", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/ton/src/browser/adapters/TonConnect.ts b/packages/networks/ton/src/browser/adapters/TonConnect.ts index 9a8e386..368d930 100644 --- a/packages/networks/ton/src/browser/adapters/TonConnect.ts +++ b/packages/networks/ton/src/browser/adapters/TonConnect.ts @@ -7,7 +7,7 @@ import type { ConnectConfig, WalletAdapterInterface } from '@multiplechain/types export type TonConnectConfig = ConnectConfig & { manifestUrl?: string buttonRootId?: string - themeMode?: THEME + themeMode?: string } let ui: TonConnectUI @@ -20,7 +20,7 @@ const createUI = (config?: TonConnectConfig): TonConnectUI => { ui = new TonConnectUI({ uiPreferences: { - theme: config?.themeMode ?? THEME.LIGHT + theme: config?.themeMode === 'light' ? THEME.LIGHT : THEME.DARK }, manifestUrl: config?.manifestUrl, buttonRootId: config?.buttonRootId From 1e20024451ed470fe536e0c0cb5f9fd500fa6233 Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Thu, 23 Jan 2025 11:00:36 +0300 Subject: [PATCH 06/19] fix --- packages/networks/ton/package.json | 2 +- packages/networks/ton/src/browser/adapters/TonConnect.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index 370a2ab..d2be8c7 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/ton", - "version": "0.1.6", + "version": "0.1.7", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/ton/src/browser/adapters/TonConnect.ts b/packages/networks/ton/src/browser/adapters/TonConnect.ts index 368d930..66d3ce0 100644 --- a/packages/networks/ton/src/browser/adapters/TonConnect.ts +++ b/packages/networks/ton/src/browser/adapters/TonConnect.ts @@ -11,6 +11,7 @@ export type TonConnectConfig = ConnectConfig & { } let ui: TonConnectUI +let rejectedAction: (reason?: any) => void let connectedAction: (value: TonConnectUI | PromiseLike) => void const createUI = (config?: TonConnectConfig): TonConnectUI => { @@ -33,6 +34,12 @@ const createUI = (config?: TonConnectConfig): TonConnectUI => { } }) + ui.onModalStateChange((state) => { + if (state.status === 'closed' && state.closeReason === 'action-cancelled') { + rejectedAction(ErrorTypeEnum.CLOSED_WALLETCONNECT_MODAL) + } + }) + return ui } @@ -67,6 +74,7 @@ const TonConnect: WalletAdapterInterface = { return await new Promise((resolve, reject) => { try { connectedAction = resolve + rejectedAction = reject void createUI(config).openModal() } catch (error) { reject(error) From af5cb2751f07a6ab5065ac9dedcd818696b1dc1d Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 24 Jan 2025 20:21:24 +0300 Subject: [PATCH 07/19] handle crush --- packages/networks/ton/package.json | 2 +- packages/networks/ton/src/browser/adapters/TonConnect.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index d2be8c7..d7c2cfa 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/ton", - "version": "0.1.7", + "version": "0.1.8", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/ton/src/browser/adapters/TonConnect.ts b/packages/networks/ton/src/browser/adapters/TonConnect.ts index 66d3ce0..26a1f3c 100644 --- a/packages/networks/ton/src/browser/adapters/TonConnect.ts +++ b/packages/networks/ton/src/browser/adapters/TonConnect.ts @@ -53,8 +53,12 @@ const TonConnect: WalletAdapterInterface = { return ui?.connected ?? false }, disconnect: async () => { - if (ui) { - await ui.disconnect() + try { + if (ui) { + await ui.disconnect() + } + } catch (error) { + console.error(error) } }, connect: async (provider?: Provider, _config?: ConnectConfig) => { From 697cefb1c4c6db7bf7ef6700f250b919fbfb0a5c Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 24 Jan 2025 20:33:13 +0300 Subject: [PATCH 08/19] added error map --- packages/networks/ton/package.json | 2 +- packages/networks/ton/src/browser/Wallet.ts | 95 +++++++++++++-------- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index d7c2cfa..307a505 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/ton", - "version": "0.1.8", + "version": "0.1.10", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/ton/src/browser/Wallet.ts b/packages/networks/ton/src/browser/Wallet.ts index 80f88d4..e921b54 100644 --- a/packages/networks/ton/src/browser/Wallet.ts +++ b/packages/networks/ton/src/browser/Wallet.ts @@ -2,22 +2,26 @@ import { Provider } from '../services/Provider' import { CHAIN, type TonConnectUI } from '@tonconnect/ui' import type { TransactionSigner } from '../services/TransactionSigner' import { Address, Cell, type CommonMessageInfoRelaxedInternal } from '@ton/core' -import type { - WalletInterface, - WalletAdapterInterface, - WalletPlatformEnum, - TransactionId, - SignedMessage, - WalletAddress, - ConnectConfig, - UnknownConfig +import { + type WalletInterface, + type WalletAdapterInterface, + type WalletPlatformEnum, + type TransactionId, + type SignedMessage, + type WalletAddress, + type ConnectConfig, + type UnknownConfig, + ErrorTypeEnum } from '@multiplechain/types' type WalletAdapter = WalletAdapterInterface const rejectMap = (error: any, reject: (a: any) => any): any => { console.error('MultipleChain TON Connect Error:', error) - // const errorMessage = String(error.message ?? '') + const errorMessage = String(error.message ?? '') + if (errorMessage.includes('Reject request')) { + return reject(ErrorTypeEnum.WALLET_REQUEST_REJECTED) + } return reject(error) } @@ -107,7 +111,14 @@ export class Wallet implements WalletInterface { - rejectMap(error, reject) + const customReject = (error: any): void => { + if (error.message === ErrorTypeEnum.WALLET_REQUEST_REJECTED) { + reject(new Error(ErrorTypeEnum.WALLET_CONNECT_REJECTED)) + } else { + reject(error) + } + } + rejectMap(error, customReject) }) }) } @@ -163,32 +174,42 @@ export class Wallet implements WalletInterface { - const account = this.walletProvider.account - const messages = transactionSigner.getRawData() - - const tonConnectMessageFormat = [] - for (const message of messages) { - const info = message.info as CommonMessageInfoRelaxedInternal - tonConnectMessageFormat.push({ - address: info.dest.toString(), - amount: info.value.coins.toString(), - payload: message.body.toBoc().toString('base64') - }) - } - - const result = await this.walletProvider.sendTransaction( - { - validUntil: Math.floor(Date.now() / 1000) + 60, - from: this.getRawAddress(), - network: account?.chain, - messages: tonConnectMessageFormat - }, - modalAction - ) - - const messageHash = Cell.fromBase64(result.boc).hash().toString('hex') - - return await this.networkProvider.findTxHashByMessageHash(messageHash) + return await new Promise((resolve, reject) => { + try { + const account = this.walletProvider.account + const messages = transactionSigner.getRawData() + + const tonConnectMessageFormat = [] + for (const message of messages) { + const info = message.info as CommonMessageInfoRelaxedInternal + tonConnectMessageFormat.push({ + address: info.dest.toString(), + amount: info.value.coins.toString(), + payload: message.body.toBoc().toString('base64') + }) + } + + this.walletProvider + .sendTransaction( + { + validUntil: Math.floor(Date.now() / 1000) + 60, + from: this.getRawAddress(), + network: account?.chain, + messages: tonConnectMessageFormat + }, + modalAction + ) + .then(async (result) => { + const messageHash = Cell.fromBase64(result.boc).hash().toString('hex') + resolve(await this.networkProvider.findTxHashByMessageHash(messageHash)) + }) + .catch((error) => { + rejectMap(error, reject) + }) + } catch (error) { + rejectMap(error, reject) + } + }) } /** From 738e22c14354a527ec48911c0c00661bb745767b Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Thu, 30 Jan 2025 11:09:50 +0300 Subject: [PATCH 09/19] xrpl ready to dev --- .env.example | 10 + packages/networks/xrpl/.eslintrc.json | 3 + packages/networks/xrpl/README.md | 5 + packages/networks/xrpl/esbuild.ts | 3 + packages/networks/xrpl/index.example.html | 311 +++++++ packages/networks/xrpl/package-lock.json | 772 ++++++++++++++++++ packages/networks/xrpl/package.json | 84 ++ packages/networks/xrpl/pnpm-lock.yaml | 521 ++++++++++++ packages/networks/xrpl/src/assets/Coin.ts | 77 ++ packages/networks/xrpl/src/assets/index.ts | 1 + packages/networks/xrpl/src/browser/Wallet.ts | 136 +++ .../xrpl/src/browser/adapters/index.ts | 0 packages/networks/xrpl/src/browser/index.ts | 28 + packages/networks/xrpl/src/index.ts | 7 + .../xrpl/src/models/CoinTransaction.ts | 63 ++ .../networks/xrpl/src/models/Transaction.ts | 142 ++++ packages/networks/xrpl/src/models/index.ts | 2 + .../networks/xrpl/src/services/Provider.ts | 130 +++ .../xrpl/src/services/TransactionListener.ts | 342 ++++++++ .../xrpl/src/services/TransactionSigner.ts | 60 ++ packages/networks/xrpl/src/services/index.ts | 2 + packages/networks/xrpl/tests/assets.spec.ts | 63 ++ packages/networks/xrpl/tests/models.spec.ts | 78 ++ packages/networks/xrpl/tests/services.spec.ts | 67 ++ packages/networks/xrpl/tests/setup.ts | 13 + packages/networks/xrpl/tsconfig.json | 21 + packages/networks/xrpl/vite.config.ts | 10 + packages/networks/xrpl/vite.svg | 1 + packages/networks/xrpl/vitest.config.ts | 12 + 29 files changed, 2964 insertions(+) create mode 100644 packages/networks/xrpl/.eslintrc.json create mode 100644 packages/networks/xrpl/README.md create mode 100644 packages/networks/xrpl/esbuild.ts create mode 100644 packages/networks/xrpl/index.example.html create mode 100644 packages/networks/xrpl/package-lock.json create mode 100644 packages/networks/xrpl/package.json create mode 100644 packages/networks/xrpl/pnpm-lock.yaml create mode 100644 packages/networks/xrpl/src/assets/Coin.ts create mode 100644 packages/networks/xrpl/src/assets/index.ts create mode 100644 packages/networks/xrpl/src/browser/Wallet.ts create mode 100644 packages/networks/xrpl/src/browser/adapters/index.ts create mode 100644 packages/networks/xrpl/src/browser/index.ts create mode 100644 packages/networks/xrpl/src/index.ts create mode 100644 packages/networks/xrpl/src/models/CoinTransaction.ts create mode 100644 packages/networks/xrpl/src/models/Transaction.ts create mode 100644 packages/networks/xrpl/src/models/index.ts create mode 100644 packages/networks/xrpl/src/services/Provider.ts create mode 100644 packages/networks/xrpl/src/services/TransactionListener.ts create mode 100644 packages/networks/xrpl/src/services/TransactionSigner.ts create mode 100644 packages/networks/xrpl/src/services/index.ts create mode 100644 packages/networks/xrpl/tests/assets.spec.ts create mode 100644 packages/networks/xrpl/tests/models.spec.ts create mode 100644 packages/networks/xrpl/tests/services.spec.ts create mode 100644 packages/networks/xrpl/tests/setup.ts create mode 100644 packages/networks/xrpl/tsconfig.json create mode 100644 packages/networks/xrpl/vite.config.ts create mode 100644 packages/networks/xrpl/vite.svg create mode 100644 packages/networks/xrpl/vitest.config.ts diff --git a/.env.example b/.env.example index be83464..649d3bc 100644 --- a/.env.example +++ b/.env.example @@ -94,6 +94,16 @@ BTC_RECEIVER_ADDRESS='tb1q9uxj8p043sjkm0qzlsys7677mv98j76k8cvgtg' BTC_TRANSFER_AMOUNT=0.00001 #Bitcoin +#XRPl +XRP_TRANSFER_TEST_IS_ACTIVE=false +XRP_LISTENER_TEST_IS_ACTIVE=false +XRP_BLOCKCYPHER_TOKEN='49d43a59a4f24d31a9731eb067ab971c' +XRP_SENDER_PRIVATE_KEY='cNHUtnWqwAwGajUGjyHLNbUcfHaDC3ujqjc6qcZik5Xa58Hj46vG' +XRP_SENDER_ADDRESS='tb1q8juz7c302wdcpfz83zvvyf4jxc8sfq4wyth3pr' +XRP_RECEIVER_ADDRESS='tb1q9uxj8p043sjkm0qzlsys7677mv98j76k8cvgtg' +XRP_TRANSFER_AMOUNT=0.00001 +#XRPl + #Solana # Assets SOL_COIN_TRANSFER_TEST_IS_ACTIVE=false diff --git a/packages/networks/xrpl/.eslintrc.json b/packages/networks/xrpl/.eslintrc.json new file mode 100644 index 0000000..9e99876 --- /dev/null +++ b/packages/networks/xrpl/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../../.eslintrc.json"] +} diff --git a/packages/networks/xrpl/README.md b/packages/networks/xrpl/README.md new file mode 100644 index 0000000..dfb02a3 --- /dev/null +++ b/packages/networks/xrpl/README.md @@ -0,0 +1,5 @@ +# MultipleChain XRP Ledger + +MultipleChain aims for easy access by simplifying the complex structure of many blockchains. For example, each blockchain network has a different structure for transfer initiation or transaction data. You may need to learn new things from scratch for each blockchain network. This is necessary if you want to go into detail. But if you just want to get to the basics. MultipleChain will make your work much easier. In many different programming languages. + +#### 📚 [Documentation](https://multiplechain.gitbook.io/multiplechain-docs) diff --git a/packages/networks/xrpl/esbuild.ts b/packages/networks/xrpl/esbuild.ts new file mode 100644 index 0000000..f25aad1 --- /dev/null +++ b/packages/networks/xrpl/esbuild.ts @@ -0,0 +1,3 @@ +void import('../../../esbuild').then((module) => { + module.default() +}) diff --git a/packages/networks/xrpl/index.example.html b/packages/networks/xrpl/index.example.html new file mode 100644 index 0000000..98db5bc --- /dev/null +++ b/packages/networks/xrpl/index.example.html @@ -0,0 +1,311 @@ + + + + + + + Browser Tests + + + + +
+
    +
    + +
    +
    Adapter id:
    +
    Adapter name:
    +
    + Adapter icon: + icon +
    +
    Platforms:
    +
    Download link:
    +
    Deep link:
    +
    + Connected address: +
    + +
    Result:
    + +
    Result:
    +
    + + + + diff --git a/packages/networks/xrpl/package-lock.json b/packages/networks/xrpl/package-lock.json new file mode 100644 index 0000000..312fc86 --- /dev/null +++ b/packages/networks/xrpl/package-lock.json @@ -0,0 +1,772 @@ +{ + "name": "@multiplechain/bitcoin", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@multiplechain/bitcoin", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@multiplechain/types": "^0.1.55", + "@multiplechain/utils": "^0.1.18", + "axios": "^1.6.8", + "bitcore-lib": "^10.0.28", + "ws": "^8.17.0" + }, + "devDependencies": { + "@types/bitcore-lib": "^0.15.6" + } + }, + "node_modules/@multiplechain/types": { + "version": "0.1.55", + "resolved": "https://registry.npmjs.org/@multiplechain/types/-/types-0.1.55.tgz", + "integrity": "sha512-9fYrLaDxX2pj9zfIbmvk1hPF3FH/bK+Q3uTF+k3zdsitP/HzQ1S9zImNz20Tvoqkk1ns+/8ecE0Y6GNowsDOQA==" + }, + "node_modules/@multiplechain/utils": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@multiplechain/utils/-/utils-0.1.18.tgz", + "integrity": "sha512-UCoOOBJrawp/lInepxuoEiYwId0wMPg7rX8p78FLqzGl8f/b93sAtv7sP87/VVKwhdoEuNZ/uQjckycmNeks/w==", + "dependencies": { + "@types/ws": "^8.5.10", + "bignumber.js": "^9.1.2", + "web3-utils": "^4.2.0", + "ws": "^8.16.0" + } + }, + "node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", + "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", + "dependencies": { + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", + "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", + "dependencies": { + "@noble/hashes": "~1.3.2", + "@scure/base": "~1.1.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/bitcore-lib": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/@types/bitcore-lib/-/bitcore-lib-0.15.6.tgz", + "integrity": "sha512-CtKDBgSBubPXZ0wFeCiUCSdzH+cuy6nFya3FboOqf44evi+OmkQPqEg3ASMpmPDYE8vkcxV302Iu8lZqCjYieg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.12.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", + "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/base-x": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", + "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/bigi": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", + "integrity": "sha512-ddkU+dFIuEIW8lE7ZwdIAf2UPoM90eaprg5m3YXAVVTmKlqV/9BX4A2M8BOK2yOq6/VgZFVhK6QAxJebhlbhzw==" + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, + "node_modules/bip-schnorr": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.6.4.tgz", + "integrity": "sha512-dNKw7Lea8B0wMIN4OjEmOk/Z5qUGqoPDY0P2QttLqGk1hmDPytLWW8PR5Pb6Vxy6CprcdEgfJpOjUu+ONQveyg==", + "dependencies": { + "bigi": "^1.4.2", + "ecurve": "^1.0.6", + "js-sha256": "^0.9.0", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcore-lib": { + "version": "10.0.28", + "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-10.0.28.tgz", + "integrity": "sha512-uOWHpWbUxEj411p+tp6DCb9NfZdsCAHl6Z/rs96mquQMsef29fOqWUyk4Bl7yLf+KwzU5ZKy0HIPnjGFlmpXpg==", + "dependencies": { + "bech32": "=2.0.0", + "bip-schnorr": "=0.6.4", + "bn.js": "=4.11.8", + "bs58": "^4.0.1", + "buffer-compare": "=1.1.1", + "elliptic": "^6.5.3", + "inherits": "=2.0.1", + "lodash": "^4.17.20" + } + }, + "node_modules/bitcore-lib/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==" + }, + "node_modules/bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/buffer-compare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-compare/-/buffer-compare-1.1.1.tgz", + "integrity": "sha512-O6NvNiHZMd3mlIeMDjP6t/gPG75OqGPeiRZXoMQZJ6iy9GofCls4Ijs5YkPZZwoysizLiedhticmdyx/GyHghA==" + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ecurve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", + "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", + "dependencies": { + "bigi": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/elliptic": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", + "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ethereum-cryptography": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", + "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", + "dependencies": { + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@scure/bip32": "1.3.3", + "@scure/bip39": "1.2.2" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/web3-errors": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/web3-errors/-/web3-errors-1.1.4.tgz", + "integrity": "sha512-WahtszSqILez+83AxGecVroyZsMuuRT+KmQp4Si5P4Rnqbczno1k748PCrZTS1J4UCPmXMG2/Vt+0Bz2zwXkwQ==", + "dependencies": { + "web3-types": "^1.3.1" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-types": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/web3-types/-/web3-types-1.6.0.tgz", + "integrity": "sha512-qgOtADqlD5hw+KPKBUGaXAcdNLL0oh6qTeVgXwewCfbL/lG9R+/GrgMQB1gbTJ3cit8hMwtH8KX2Em6OwO0HRw==", + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-utils": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-4.2.3.tgz", + "integrity": "sha512-m5plKTC2YtQntHITQRyIePw52UVP1IrShhmA2FACtn4zmc5ADmrXOlQWiPzxFP/18eRJsAaUAw2+CQn1u4WPxQ==", + "dependencies": { + "ethereum-cryptography": "^2.0.0", + "eventemitter3": "^5.0.1", + "web3-errors": "^1.1.4", + "web3-types": "^1.6.0", + "web3-validator": "^2.0.5" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/web3-validator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/web3-validator/-/web3-validator-2.0.5.tgz", + "integrity": "sha512-2gLOSW8XqEN5pw5jVUm20EB7A8SbQiekpAtiI0JBmCIV0a2rp97v8FgWY5E3UEqnw5WFfEqvcDVW92EyynDTyQ==", + "dependencies": { + "ethereum-cryptography": "^2.0.0", + "util": "^0.12.5", + "web3-errors": "^1.1.4", + "web3-types": "^1.5.0", + "zod": "^3.21.4" + }, + "engines": { + "node": ">=14", + "npm": ">=6.12.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/packages/networks/xrpl/package.json b/packages/networks/xrpl/package.json new file mode 100644 index 0000000..ab52dc4 --- /dev/null +++ b/packages/networks/xrpl/package.json @@ -0,0 +1,84 @@ +{ + "name": "@multiplechain/xrpl", + "version": "0.1.0", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.es.js", + "unpkg": "dist/index.umd.js", + "browser": "dist/index.umd.js", + "jsdelivr": "dist/index.umd.js", + "exports": { + ".": { + "import": { + "default": "./dist/index.es.js", + "types": "./dist/browser/index.d.ts" + }, + "require": { + "default": "./dist/index.cjs", + "types": "./dist/index.d.ts" + } + }, + "./node": { + "default": "./dist/index.cjs", + "types": "./dist/index.d.ts" + }, + "./browser": { + "default": "./dist/index.es.js", + "types": "./dist/browser/index.d.ts" + } + }, + "typesVersions": { + "*": { + "node": [ + "./dist/index.d.ts" + ], + "browser": [ + "./dist/browser/index.d.ts" + ] + } + }, + "files": [ + "dist", + "README.md", + "!tsconfig.tsbuildinfo" + ], + "scripts": { + "dev": "vite", + "clean": "rm -rf dist", + "watch": "tsc --watch", + "build:vite": "vite build", + "build:node": "tsx esbuild.ts", + "typecheck": "tsc --noEmit", + "lint": "eslint . --ext .ts", + "test": "vitest run --dir tests", + "test-ui": "vitest watch --ui", + "prepublishOnly": "pnpm run build", + "build": "pnpm run build:vite && pnpm run build:node" + }, + "keywords": [ + "web3", + "crypto", + "blockchain", + "multiple-chain" + ], + "author": "MultipleChain", + "license": "MIT", + "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/xrpl", + "repository": { + "type": "git", + "url": "git+https://github.com/MultipleChain/js.git" + }, + "bugs": { + "url": "https://github.com/MultipleChain/js/issues" + }, + "dependencies": { + "@multiplechain/types": "^0.1.70", + "@multiplechain/utils": "^0.1.21", + "axios": "^1.6.8", + "isomorphic-ws": "^5.0.0", + "ws": "^8.17.0" + }, + "devDependencies": { + "@types/ws": "^8.5.10" + } +} \ No newline at end of file diff --git a/packages/networks/xrpl/pnpm-lock.yaml b/packages/networks/xrpl/pnpm-lock.yaml new file mode 100644 index 0000000..49ed22a --- /dev/null +++ b/packages/networks/xrpl/pnpm-lock.yaml @@ -0,0 +1,521 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@multiplechain/types': + specifier: ^0.1.70 + version: 0.1.70 + '@multiplechain/utils': + specifier: ^0.1.21 + version: 0.1.23 + axios: + specifier: ^1.6.8 + version: 1.7.9 + isomorphic-ws: + specifier: ^5.0.0 + version: 5.0.0(ws@8.18.0) + ws: + specifier: ^8.17.0 + version: 8.18.0 + devDependencies: + '@types/ws': + specifier: ^8.5.10 + version: 8.5.14 + +packages: + + '@multiplechain/types@0.1.70': + resolution: {integrity: sha512-o9ovdaefDHE5gorb83avugCjeixfBXrfJkIgSEEcT549yGF8CkmG/jgZIlqXKJf8Lh0tbg4zMAAanUSxUqV1FQ==} + + '@multiplechain/utils@0.1.23': + resolution: {integrity: sha512-6mgJiXQsElObKgX/yA8DUpQYd+BS1GKhAyLj5F/2mv9yY3boZrX8sS7ZLk+g5NX45N+BACNMxbs09TG1iFiRsA==} + + '@noble/curves@1.4.2': + resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} + + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + + '@scure/base@1.1.9': + resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} + + '@scure/bip32@1.4.0': + resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} + + '@scure/bip39@1.3.0': + resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} + + '@types/node@22.12.0': + resolution: {integrity: sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==} + + '@types/ws@8.5.14': + resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} + + bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + + call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + ethereum-cryptography@2.2.1: + resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.4: + resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==} + engines: {node: '>= 0.4'} + + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.2.7: + resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + isomorphic-ws@5.0.0: + resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} + peerDependencies: + ws: '*' + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + web3-errors@1.3.1: + resolution: {integrity: sha512-w3NMJujH+ZSW4ltIZZKtdbkbyQEvBzyp3JRn59Ckli0Nz4VMsVq8aF1bLWM7A2kuQ+yVEm3ySeNU+7mSRwx7RQ==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-types@1.10.0: + resolution: {integrity: sha512-0IXoaAFtFc8Yin7cCdQfB9ZmjafrbP6BO0f0KT/khMhXKUpoJ6yShrVhiNpyRBo8QQjuOagsWzwSK2H49I7sbw==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-utils@4.3.3: + resolution: {integrity: sha512-kZUeCwaQm+RNc2Bf1V3BYbF29lQQKz28L0y+FA4G0lS8IxtJVGi5SeDTUkpwqqkdHHC7JcapPDnyyzJ1lfWlOw==} + engines: {node: '>=14', npm: '>=6.12.0'} + + web3-validator@2.0.6: + resolution: {integrity: sha512-qn9id0/l1bWmvH4XfnG/JtGKKwut2Vokl6YXP5Kfg424npysmtRLe9DgiNBM9Op7QL/aSiaA0TVXibuIuWcizg==} + engines: {node: '>=14', npm: '>=6.12.0'} + + which-typed-array@1.1.18: + resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} + engines: {node: '>= 0.4'} + + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + +snapshots: + + '@multiplechain/types@0.1.70': {} + + '@multiplechain/utils@0.1.23': + dependencies: + '@types/ws': 8.5.14 + bignumber.js: 9.1.2 + web3-utils: 4.3.3 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@noble/curves@1.4.2': + dependencies: + '@noble/hashes': 1.4.0 + + '@noble/hashes@1.4.0': {} + + '@scure/base@1.1.9': {} + + '@scure/bip32@1.4.0': + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + + '@scure/bip39@1.3.0': + dependencies: + '@noble/hashes': 1.4.0 + '@scure/base': 1.1.9 + + '@types/node@22.12.0': + dependencies: + undici-types: 6.20.0 + + '@types/ws@8.5.14': + dependencies: + '@types/node': 22.12.0 + + asynckit@0.4.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + axios@1.7.9: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + bignumber.js@9.1.2: {} + + call-bind-apply-helpers@1.0.1: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + get-intrinsic: 1.2.7 + set-function-length: 1.2.2 + + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.1 + get-intrinsic: 1.2.7 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + delayed-stream@1.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + ethereum-cryptography@2.2.1: + dependencies: + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + + eventemitter3@5.0.1: {} + + follow-redirects@1.15.9: {} + + for-each@0.3.4: + dependencies: + is-callable: 1.2.7 + + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + function-bind@1.1.2: {} + + get-intrinsic@1.2.7: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + inherits@2.0.4: {} + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.3 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.3 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.3 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.18 + + isomorphic-ws@5.0.0(ws@8.18.0): + dependencies: + ws: 8.18.0 + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + possible-typed-array-names@1.0.0: {} + + proxy-from-env@1.1.0: {} + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-regex: 1.2.1 + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.7 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + undici-types@6.20.0: {} + + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.0 + is-typed-array: 1.1.15 + which-typed-array: 1.1.18 + + web3-errors@1.3.1: + dependencies: + web3-types: 1.10.0 + + web3-types@1.10.0: {} + + web3-utils@4.3.3: + dependencies: + ethereum-cryptography: 2.2.1 + eventemitter3: 5.0.1 + web3-errors: 1.3.1 + web3-types: 1.10.0 + web3-validator: 2.0.6 + + web3-validator@2.0.6: + dependencies: + ethereum-cryptography: 2.2.1 + util: 0.12.5 + web3-errors: 1.3.1 + web3-types: 1.10.0 + zod: 3.24.1 + + which-typed-array@1.1.18: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 + for-each: 0.3.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + ws@8.18.0: {} + + zod@3.24.1: {} diff --git a/packages/networks/xrpl/src/assets/Coin.ts b/packages/networks/xrpl/src/assets/Coin.ts new file mode 100644 index 0000000..1a2855f --- /dev/null +++ b/packages/networks/xrpl/src/assets/Coin.ts @@ -0,0 +1,77 @@ +import { Provider } from '../services/Provider' +import { TransactionSigner } from '../services/TransactionSigner' +import { + ErrorTypeEnum, + type CoinInterface, + type TransferAmount, + type WalletAddress +} from '@multiplechain/types' + +export class Coin implements CoinInterface { + /** + * Blockchain network provider + */ + provider: Provider + + /** + * @param provider network provider + */ + constructor(provider?: Provider) { + this.provider = provider ?? Provider.instance + } + + /** + * @returns Coin name + */ + getName(): string { + return 'XRP' + } + + /** + * @returns Coin symbol + */ + getSymbol(): string { + return 'XRP' + } + + /** + * @returns Decimal value of the coin + */ + getDecimals(): number { + return 6 + } + + /** + * @param owner Wallet address + * @returns Wallet balance as currency of COIN + */ + async getBalance(owner: WalletAddress): Promise { + return await Promise.resolve(100) + } + + /** + * @param sender Sender wallet address + * @param receiver Receiver wallet address + * @param amount Amount of assets that will be transferred + * @returns Transaction signer + */ + async transfer( + sender: WalletAddress, + receiver: WalletAddress, + amount: TransferAmount + ): Promise { + if (amount < 0) { + throw new Error(ErrorTypeEnum.INVALID_AMOUNT) + } + + if (amount > (await this.getBalance(sender))) { + throw new Error(ErrorTypeEnum.INSUFFICIENT_BALANCE) + } + + if (sender === receiver) { + throw new Error(ErrorTypeEnum.INVALID_ADDRESS) + } + + return new TransactionSigner() + } +} diff --git a/packages/networks/xrpl/src/assets/index.ts b/packages/networks/xrpl/src/assets/index.ts new file mode 100644 index 0000000..c03964d --- /dev/null +++ b/packages/networks/xrpl/src/assets/index.ts @@ -0,0 +1 @@ +export * from './Coin' diff --git a/packages/networks/xrpl/src/browser/Wallet.ts b/packages/networks/xrpl/src/browser/Wallet.ts new file mode 100644 index 0000000..add2bee --- /dev/null +++ b/packages/networks/xrpl/src/browser/Wallet.ts @@ -0,0 +1,136 @@ +import { + type WalletInterface, + type WalletAdapterInterface, + type WalletPlatformEnum, + type UnknownConfig, + type ConnectConfig, + type WalletAddress, + type SignedMessage, + type TransactionId +} from '@multiplechain/types' +import { Provider } from '../services/Provider' +import type { TransactionSigner } from '../services/TransactionSigner' + +const rejectMap = (error: any, reject: (a: any) => any): any => { + console.error('MultipleChain XRPl Wallet Error:', error) + + return reject(error) +} + +type WalletAdapter = WalletAdapterInterface + +export class Wallet implements WalletInterface { + adapter: WalletAdapter + + walletProvider: any + + networkProvider: Provider + + /** + * @param adapter - Wallet adapter + * @param provider - Network provider + */ + constructor(adapter: WalletAdapter, provider?: Provider) { + this.adapter = adapter + this.networkProvider = provider ?? Provider.instance + } + + /** + * @returns Wallet ID + */ + getId(): string { + return this.adapter.id + } + + /** + * @returns Wallet name + */ + getName(): string { + return this.adapter.name + } + + /** + * @returns Wallet icon + */ + getIcon(): string { + return this.adapter.icon + } + + /** + * @returns Wallet platforms + */ + getPlatforms(): WalletPlatformEnum[] { + return this.adapter.platforms + } + + /** + * @returns Wallet download link + */ + getDownloadLink(): string | undefined { + return this.adapter.downloadLink + } + + /** + * @param url url for deep linking + * @param config configuration for deep linking + * @returns deep link + */ + createDeepLink(url: string, config?: UnknownConfig): string | null { + if (this.adapter.createDeepLink === undefined) { + return null + } + + return this.adapter.createDeepLink(url, config) + } + + /** + * @param config connection configuration + * @returns WalletAddress + */ + async connect(config?: ConnectConfig): Promise { + return await new Promise((resolve, reject) => {}) + } + + /** + * @returns wallet detection status + */ + async isDetected(): Promise { + return this.adapter.isDetected() + } + + /** + * @returns connection status + */ + async isConnected(): Promise { + return this.adapter.isConnected() + } + + /** + * @returns wallet address + */ + async getAddress(): Promise { + return this.walletProvider.getAddress() + } + + /** + * @param message message to sign + * @returns signed message + */ + async signMessage(message: string): Promise { + return await new Promise((resolve, reject) => {}) + } + + /** + * @param transactionSigner transaction signer + * @returns transaction id + */ + async sendTransaction(transactionSigner: TransactionSigner): Promise { + return await new Promise((resolve, reject) => {}) + } + + /** + * @param eventName event name + * @param callback event callback + */ + on(eventName: string, callback: (...args: any[]) => void): void {} +} diff --git a/packages/networks/xrpl/src/browser/adapters/index.ts b/packages/networks/xrpl/src/browser/adapters/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/networks/xrpl/src/browser/index.ts b/packages/networks/xrpl/src/browser/index.ts new file mode 100644 index 0000000..ef4b5c0 --- /dev/null +++ b/packages/networks/xrpl/src/browser/index.ts @@ -0,0 +1,28 @@ +import type { Provider } from 'sats-connect' +import { Wallet, type WalletProvider } from './Wallet' +import * as adapterList from './adapters/index' +import type { + WalletAdapterListType, + WalletAdapterInterface, + RegisterWalletAdapterType +} from '@multiplechain/types' + +const adapters: WalletAdapterListType = {} + +const registerAdapter: RegisterWalletAdapterType = ( + adapter: WalletAdapterInterface +): void => { + if (Object.values(adapters).find((a) => a.id === adapter.id) !== undefined) { + throw new Error(`Adapter with id ${adapter.id} already exists`) + } + + adapters[adapter.id] = adapter +} + +export * from '../index' + +export const browser = { + Wallet, + registerAdapter, + adapters: Object.assign(adapters, adapterList) +} diff --git a/packages/networks/xrpl/src/index.ts b/packages/networks/xrpl/src/index.ts new file mode 100644 index 0000000..19e38b2 --- /dev/null +++ b/packages/networks/xrpl/src/index.ts @@ -0,0 +1,7 @@ +export * from './services/Provider' + +export * as assets from './assets/index' +export * as models from './models/index' +export * as services from './services/index' + +export * as types from '@multiplechain/types' diff --git a/packages/networks/xrpl/src/models/CoinTransaction.ts b/packages/networks/xrpl/src/models/CoinTransaction.ts new file mode 100644 index 0000000..6ad9770 --- /dev/null +++ b/packages/networks/xrpl/src/models/CoinTransaction.ts @@ -0,0 +1,63 @@ +import { fromSatoshi } from '../utils' +import { Transaction } from './Transaction' +import { TransactionStatusEnum, AssetDirectionEnum } from '@multiplechain/types' +import type { WalletAddress, CoinTransactionInterface, TransferAmount } from '@multiplechain/types' + +export class CoinTransaction extends Transaction implements CoinTransactionInterface { + /** + * @returns Wallet address of the receiver of transaction + */ + async getReceiver(): Promise { + const data = await this.getData() + return data?.vout[0].scriptpubkey_address ?? '' + } + + /** + * @returns Wallet address of the sender of transaction + */ + async getSender(): Promise { + return await this.getSigner() + } + + /** + * @returns Amount of coin that will be transferred + */ + async getAmount(): Promise { + const data = await this.getData() + return fromSatoshi(data?.vout[0].value ?? 0) + } + + /** + * @param direction - Direction of the transaction (asset) + * @param address - Wallet address of the receiver or sender of the transaction, dependant on direction + * @param amount Amount of assets that will be transferred + * @returns Status of the transaction + */ + async verifyTransfer( + direction: AssetDirectionEnum, + address: WalletAddress, + amount: TransferAmount + ): Promise { + const status = await this.getStatus() + + if (status === TransactionStatusEnum.PENDING) { + return TransactionStatusEnum.PENDING + } + + if ((await this.getAmount()) !== amount) { + return TransactionStatusEnum.FAILED + } + + if (direction === AssetDirectionEnum.INCOMING) { + if ((await this.getReceiver()).toLowerCase() !== address.toLowerCase()) { + return TransactionStatusEnum.FAILED + } + } else { + if ((await this.getSender()).toLowerCase() !== address.toLowerCase()) { + return TransactionStatusEnum.FAILED + } + } + + return TransactionStatusEnum.CONFIRMED + } +} diff --git a/packages/networks/xrpl/src/models/Transaction.ts b/packages/networks/xrpl/src/models/Transaction.ts new file mode 100644 index 0000000..b19785f --- /dev/null +++ b/packages/networks/xrpl/src/models/Transaction.ts @@ -0,0 +1,142 @@ +import axios from 'axios' +import { Provider } from '../services/Provider' +import { ErrorTypeEnum, TransactionStatusEnum } from '@multiplechain/types' +import { + TransactionTypeEnum, + type BlockConfirmationCount, + type BlockNumber, + type BlockTimestamp, + type TransactionFee, + type TransactionId, + type TransactionInterface, + type WalletAddress +} from '@multiplechain/types' + +export class Transaction implements TransactionInterface { + /** + * Each transaction has its own unique ID defined by the user + */ + id: TransactionId + + /** + * Blockchain network provider + */ + provider: Provider + + /** + * Transaction data + */ + data: any | null = null + + /** + * @param id Transaction id + * @param provider Blockchain network provider + */ + constructor(id: TransactionId, provider?: Provider) { + this.id = id + this.provider = provider ?? Provider.instance + } + + /** + * @returns Transaction data + */ + async getData(): Promise { + if (this.data !== null) { + return this.data + } + try { + } catch (error) { + console.error('MC XRPl TX getData', error) + throw new Error(ErrorTypeEnum.RPC_REQUEST_ERROR) + } + } + + /** + * @param ms - Milliseconds to wait for the transaction to be confirmed. Default is 4000ms + * @returns Status of the transaction + */ + async wait(ms: number = 4000): Promise { + return await new Promise((resolve, reject) => { + const check = async (): Promise => { + try { + const status = await this.getStatus() + if (status === TransactionStatusEnum.CONFIRMED) { + resolve(TransactionStatusEnum.CONFIRMED) + return + } else if (status === TransactionStatusEnum.FAILED) { + reject(TransactionStatusEnum.FAILED) + return + } + setTimeout(check, ms) + } catch (error) { + console.error('MC XRPl TX wait', error) + reject(TransactionStatusEnum.FAILED) + } + } + void check() + }) + } + + /** + * @returns Transaction ID + */ + getId(): TransactionId { + return this.id + } + + /** + * @returns Transaction type + */ + async getType(): Promise { + return TransactionTypeEnum.COIN + } + + /** + * @returns Transaction URL + */ + getUrl(): string { + return '' + } + + /** + * @returns Wallet address of the sender of transaction + */ + async getSigner(): Promise { + return '' + } + + /** + * @returns Transaction fee + */ + async getFee(): Promise { + return 0 + } + + /** + * @returns Block number that transaction + */ + async getBlockNumber(): Promise { + return 0 + } + + /** + * @returns Block timestamp that transaction + */ + async getBlockTimestamp(): Promise { + return 0 + } + + /** + * @returns Confirmation count of the block + */ + async getBlockConfirmationCount(): Promise { + return 0 + } + + /** + * @returns Status of the transaction + */ + async getStatus(): Promise { + return TransactionStatusEnum.PENDING + } +} diff --git a/packages/networks/xrpl/src/models/index.ts b/packages/networks/xrpl/src/models/index.ts new file mode 100644 index 0000000..4cca7f1 --- /dev/null +++ b/packages/networks/xrpl/src/models/index.ts @@ -0,0 +1,2 @@ +export * from './Transaction' +export * from './CoinTransaction' diff --git a/packages/networks/xrpl/src/services/Provider.ts b/packages/networks/xrpl/src/services/Provider.ts new file mode 100644 index 0000000..efee7a8 --- /dev/null +++ b/packages/networks/xrpl/src/services/Provider.ts @@ -0,0 +1,130 @@ +import axios from 'axios' +import { checkWebSocket } from '@multiplechain/utils' +import { + ErrorTypeEnum, + type NetworkConfigInterface, + type ProviderInterface +} from '@multiplechain/types' + +export class Provider implements ProviderInterface { + /** + * Network configuration of the provider + */ + network: NetworkConfigInterface + + /** + * API URL + */ + api: string + + /** + * Explorer URL + */ + explorer: string + + /** + * Websocket URL + */ + wsUrl: string + + /** + * BlockCypher token + */ + blockCypherToken?: string + + /** + * Default BlockCypher token + */ + defaultBlockCypherToken = '49d43a59a4f24d31a9731eb067ab971c' + + /** + * Static instance of the provider + */ + private static _instance: Provider + + /** + * @param network - Network configuration of the provider + */ + constructor(network: NetworkConfigInterface) { + this.update(network) + } + + /** + * Get the static instance of the provider + * @returns Provider + */ + static get instance(): Provider { + if (Provider._instance === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_NOT_INITIALIZED) + } + return Provider._instance + } + + /** + * Initialize the static instance of the provider + * @param network - Network configuration of the provider + */ + static initialize(network: NetworkConfigInterface): void { + if (Provider._instance !== undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_ALREADY_INITIALIZED) + } + Provider._instance = new Provider(network) + } + + /** + * Check RPC connection + * @param url - RPC URL + * @returns Connection status + */ + async checkRpcConnection(url?: string): Promise { + try { + const response = await axios.get(url ?? this.createEndpoint('blocks/tip/height')) + + if (response.status !== 200) { + return new Error(response.statusText + ': ' + JSON.stringify(response.data)) + } + + return true + } catch (error) { + return error as Error + } + } + + /** + * Check WS connection + * @param url - Websocket URL + * @returns Connection status + */ + async checkWsConnection(url?: string): Promise { + try { + const result: any = await checkWebSocket(url ?? this.wsUrl) + + if (result instanceof Error) { + return result + } + + return true + } catch (error) { + return error as Error + } + } + + /** + * Update network configuration of the provider + * @param network - Network configuration + */ + update(network: NetworkConfigInterface): void { + this.network = network + Provider._instance = this + this.network.rpcUrl = this.api + this.network.wsUrl = this.wsUrl + } + + /** + * Get the current network configuration is testnet or not + * @returns Testnet or not + */ + isTestnet(): boolean { + return this.network?.testnet ?? false + } +} diff --git a/packages/networks/xrpl/src/services/TransactionListener.ts b/packages/networks/xrpl/src/services/TransactionListener.ts new file mode 100644 index 0000000..3f6a0ab --- /dev/null +++ b/packages/networks/xrpl/src/services/TransactionListener.ts @@ -0,0 +1,342 @@ +import type { + TransactionTypeEnum, + DynamicTransactionType, + TransactionListenerInterface, + DynamicTransactionListenerFilterType, + TransactionId +} from '@multiplechain/types' +import WebSocket from 'isomorphic-ws' +import { Provider } from './Provider' +import { fromSatoshi } from '../utils' +import { Transaction } from '../models/Transaction' +import { CoinTransaction } from '../models/CoinTransaction' +import { checkWebSocket, objectsEqual } from '@multiplechain/utils' +import { TransactionListenerProcessIndex } from '@multiplechain/types' + +interface Values { + txId: string + amount?: number + sender?: string + receiver?: string +} + +type TransactionListenerTriggerType = DynamicTransactionType< + T, + Transaction, + Transaction, + CoinTransaction, + Transaction, + Transaction +> + +type TransactionListenerCallbackType< + T extends TransactionTypeEnum, + Transaction = TransactionListenerTriggerType +> = (transaction: Transaction) => void + +export class TransactionListener< + T extends TransactionTypeEnum, + DTransaction extends TransactionListenerTriggerType, + CallBackType extends TransactionListenerCallbackType +> implements TransactionListenerInterface +{ + /** + * Transaction type + */ + type: T + + /** + * Provider + */ + provider: Provider + + /** + * Listener status + */ + status: boolean = false + + /** + * Transaction listener callback + */ + callbacks: CallBackType[] = [] + + /** + * Triggered transactions + */ + triggeredTransactions: TransactionId[] = [] + + /** + * Transaction listener filter + */ + filter?: DynamicTransactionListenerFilterType | Record + + /** + * WebSocket + */ + webSocket: WebSocket + + /** + * Dynamic stop method + */ + dynamicStop: () => void = () => {} + + /** + * @param type - Transaction type + * @param filter - Transaction listener filter + * @param provider - Provider + */ + constructor(type: T, filter?: DynamicTransactionListenerFilterType, provider?: Provider) { + this.type = type + this.filter = filter ?? {} + this.provider = provider ?? Provider.instance + } + + /** + * Close the listener + */ + stop(): void { + if (this.status) { + this.status = false + this.dynamicStop() + } + } + + /** + * Start the listener + */ + start(): void { + if (!this.status) { + this.status = true + // @ts-expect-error allow dynamic access + this[TransactionListenerProcessIndex[this.type]]() + } + } + + /** + * Get the listener status + * @returns Listener status + */ + getStatus(): boolean { + return this.status + } + + /** + * Listen to the transaction events + * @param callback - Transaction listener callback + * @returns Connection status + */ + async on(callback: CallBackType): Promise { + if (this.webSocket === undefined) { + try { + await checkWebSocket(this.provider.wsUrl) + this.webSocket = new WebSocket(this.provider.wsUrl) + } catch (error) { + throw new Error( + 'WebSocket connection is not available' + + (error instanceof Error ? ': ' + error.message : '') + ) + } + } + + this.start() + this.callbacks.push(callback) + + return true + } + + /** + * Trigger the event when a transaction is detected + * @param transaction - Transaction data + */ + trigger(transaction: TransactionListenerTriggerType): void { + if (!this.triggeredTransactions.includes(transaction.id)) { + this.triggeredTransactions.push(transaction.id) + this.callbacks.forEach((callback) => { + callback(transaction as unknown as DTransaction) + }) + } + } + + isBlockCypherProcess(): boolean { + return this.provider.isTestnet() || this.provider.blockCypherToken !== undefined + } + + /** + * Create message for the listener + * @param receiver - Receiver address + * @returns Message + */ + createMessage(receiver?: string): string { + let message + if (this.isBlockCypherProcess()) { + interface Config { + event: string + token: string + address?: string + } + + const config: Config = { + event: 'unconfirmed-tx', + token: this.provider.blockCypherToken ?? this.provider.defaultBlockCypherToken + } + + if (receiver !== undefined) { + config.address = receiver + } + + message = JSON.stringify(config) + } else { + message = JSON.stringify({ + op: 'unconfirmed_sub' + }) + } + + this.dynamicStop = () => { + if (!this.isBlockCypherProcess()) { + this.webSocket.send( + JSON.stringify({ + op: 'unconfirmed_unsub' + }) + ) + } + this.webSocket.close() + } + + return message + } + + /** + * Parse the data + * @param data - Data + * @returns Parsed data + */ + getValues(data: any): Values { + const values: Values = { + txId: '' + } + + if (this.isBlockCypherProcess()) { + values.txId = data.hash + values.sender = data.inputs[0].addresses[0] + values.receiver = data.outputs[0].addresses[0] + values.amount = fromSatoshi(data.outputs[0].value as number) + } else { + values.txId = data.x.hash + values.receiver = data.x.out[0].addr + values.sender = data.x.inputs[0].prev_out.addr + values.amount = fromSatoshi(data.x.out[0].value as number) + } + + return values + } + + /** + * General transaction process + */ + generalProcess(): void { + const message = this.createMessage() + + this.webSocket.addEventListener('open', () => { + this.webSocket.send(message) + }) + + this.webSocket.addEventListener('message', async (res: WebSocket.MessageEvent) => { + const values = this.getValues(JSON.parse(res.data as string)) + + if ( + this.filter?.signer !== undefined && + values.sender !== this.filter.signer.toLowerCase() + ) { + return + } + + this.trigger(new Transaction(values.txId)) + }) + } + + /** + * Contract transaction process + */ + contractProcess(): void { + throw new Error('This method is not implemented for CRPl.') + } + + /** + * Coin transaction process + */ + coinProcess(): void { + const filter = this.filter as DynamicTransactionListenerFilterType + + if ( + filter.signer !== undefined && + filter.sender !== undefined && + filter.signer !== filter.sender + ) { + throw new Error( + 'Sender and signer must be the same in coin transactions. Or only one of them can be defined.' + ) + } + + const sender = filter.sender ?? filter.signer + + const message = this.createMessage(filter.receiver) + + this.webSocket.addEventListener('open', () => { + this.webSocket.send(message) + }) + + this.webSocket.addEventListener('message', async (res: WebSocket.MessageEvent) => { + const data = JSON.parse(res.data as string) + + interface ParamsType { + sender?: string + receiver?: string + } + + const expectedParams: ParamsType = {} + const receivedParams: ParamsType = {} + + if (String(data.event).includes('events limit reached')) { + throw new Error('BlockCypher events limit reached.') + } + + const values = this.getValues(data) + + if (sender !== undefined) { + expectedParams.sender = sender.toLowerCase() + receivedParams.sender = values.sender?.toLowerCase() + } + + if (filter.receiver !== undefined) { + expectedParams.receiver = filter.receiver.toLowerCase() + receivedParams.receiver = values.receiver?.toLowerCase() + } + + if (!objectsEqual(expectedParams, receivedParams)) { + return + } + + const transaction = new CoinTransaction(values.txId) + + if (filter.amount !== undefined && values.amount !== filter.amount) { + return + } + + this.trigger(transaction) + }) + } + + /** + * Token transaction process + */ + tokenProcess(): void { + throw new Error('This method is not implemented for CRPl.') + } + + /** + * NFT transaction process + */ + nftProcess(): void { + throw new Error('This method is not implemented for CRPl.') + } +} diff --git a/packages/networks/xrpl/src/services/TransactionSigner.ts b/packages/networks/xrpl/src/services/TransactionSigner.ts new file mode 100644 index 0000000..ad93fcd --- /dev/null +++ b/packages/networks/xrpl/src/services/TransactionSigner.ts @@ -0,0 +1,60 @@ +import { Provider } from '../services/Provider' +import type { PrivateKey, TransactionId, TransactionSignerInterface } from '@multiplechain/types' + +export class TransactionSigner implements TransactionSignerInterface { + /** + * Transaction data from the blockchain network + */ + rawData: any + + /** + * Signed transaction data + */ + signedData?: string + + /** + * Blockchain network provider + */ + provider: Provider + + /** + * @param rawData - Transaction data + * @param provider - Blockchain network provider + */ + constructor(rawData: any, provider?: Provider) { + this.rawData = rawData + this.provider = provider ?? Provider.instance + } + + /** + * Sign the transaction + * @param privateKey - Transaction data + * @returns Signed transaction data + */ + async sign(privateKey: PrivateKey): Promise { + return this + } + + /** + * Send the transaction to the blockchain network + * @returns Transaction ID + */ + async send(): Promise { + } + + /** + * Get the raw transaction data + * @returns Transaction data + */ + getRawData(): any { + return this.rawData + } + + /** + * Get the signed transaction data + * @returns Signed transaction data + */ + getSignedData(): string { + return this.signedData ?? '' + } +} diff --git a/packages/networks/xrpl/src/services/index.ts b/packages/networks/xrpl/src/services/index.ts new file mode 100644 index 0000000..0b05d2e --- /dev/null +++ b/packages/networks/xrpl/src/services/index.ts @@ -0,0 +1,2 @@ +export * from './TransactionSigner' +export * from './TransactionListener' diff --git a/packages/networks/xrpl/tests/assets.spec.ts b/packages/networks/xrpl/tests/assets.spec.ts new file mode 100644 index 0000000..bfe87c8 --- /dev/null +++ b/packages/networks/xrpl/tests/assets.spec.ts @@ -0,0 +1,63 @@ +import { describe, it, expect, assert } from 'vitest' + +import { Coin } from '../src/assets/Coin' +import { math } from '@multiplechain/utils' +import { Transaction } from '../src/models/Transaction' +import { TransactionStatusEnum, type TransactionId } from '@multiplechain/types' +import { TransactionSigner } from '../src/services/TransactionSigner' + +const testAmount = Number(process.env.XRP_TRANSFER_AMOUNT) +const senderTestAddress = String(process.env.XRP_SENDER_ADDRESS) +const receiverTestAddress = String(process.env.XRP_RECEIVER_ADDRESS) +const senderPrivateKey = String(process.env.XRP_SENDER_PRIVATE_KEY) +const transferTestIsActive = Boolean(process.env.XRP_TRANSFER_TEST_IS_ACTIVE !== 'false') + +const checkSigner = async (signer: TransactionSigner, privateKey?: string): Promise => { + expect(signer).toBeInstanceOf(TransactionSigner) + + const rawData = signer.getRawData() + + assert.isObject(rawData) + + await signer.sign(privateKey ?? senderPrivateKey) + + assert.isString(signer.getSignedData()) +} + +const checkTx = async (transactionId: TransactionId): Promise => { + const transaction = new Transaction(transactionId) + const status = await transaction.wait(10 * 1000) + expect(status).toBe(TransactionStatusEnum.CONFIRMED) +} + +describe('Coin', () => { + const coin = new Coin() + it('Name and symbol', () => { + expect(coin.getName()).toBe('XRP') + expect(coin.getSymbol()).toBe('XRP') + }) + + it('Decimals', () => { + expect(coin.getDecimals()).toBe(6) + }) + + it('Balance', async () => { + const balance = await coin.getBalance('tb1qc240vx54n08hnhx8l4rqxjzcxf4f0ssq5asawm') + expect(balance).toBe(0.00003) + }) + + it('Transfer', async () => { + const signer = await coin.transfer(senderTestAddress, receiverTestAddress, testAmount) + + await checkSigner(signer) + + if (!transferTestIsActive) return + + const beforeBalance = await coin.getBalance(receiverTestAddress) + + await checkTx(await signer.send()) + + const afterBalance = await coin.getBalance(receiverTestAddress) + expect(afterBalance).toBe(math.add(beforeBalance, testAmount)) + }) +}) diff --git a/packages/networks/xrpl/tests/models.spec.ts b/packages/networks/xrpl/tests/models.spec.ts new file mode 100644 index 0000000..9c80487 --- /dev/null +++ b/packages/networks/xrpl/tests/models.spec.ts @@ -0,0 +1,78 @@ +import { describe, it, expect } from 'vitest' +import { Transaction } from '../src/models/Transaction' +import { CoinTransaction } from '../src/models/CoinTransaction' +import { AssetDirectionEnum, TransactionStatusEnum } from '@multiplechain/types' + +const testAmount = Number(process.env.XRP_TRANSFER_AMOUNT) +const senderTestAddress = String(process.env.XRP_SENDER_ADDRESS) +const receiverTestAddress = String(process.env.XRP_RECEIVER_ADDRESS) +const txId = '335c8a251e5f18121977c3159f46983d5943325abccc19e4718c49089553d60c' + +describe('Transaction', () => { + const tx = new Transaction(txId) + it('Id', async () => { + expect(tx.getId()).toBe(txId) + }) + + it('Data', async () => { + expect(await tx.getData()).toBeTypeOf('object') + }) + + it('Wait', async () => { + expect(await tx.wait()).toBe(TransactionStatusEnum.CONFIRMED) + }) + + it('URL', async () => { + expect(tx.getUrl()).toBe('https://blockstream.info/testnet/tx/' + txId) + }) + + it('Sender', async () => { + expect((await tx.getSigner()).toLowerCase()).toBe(senderTestAddress.toLowerCase()) + }) + + it('Fee', async () => { + expect(await tx.getFee()).toBe(0.00014) + }) + + it('Block Number', async () => { + expect(await tx.getBlockNumber()).toBe(2814543) + }) + + it('Block Timestamp', async () => { + expect(await tx.getBlockTimestamp()).toBe(1715328679) + }) + + it('Block Confirmation Count', async () => { + expect(await tx.getBlockConfirmationCount()).toBeGreaterThan(13) + }) + + it('Status', async () => { + expect(await tx.getStatus()).toBe(TransactionStatusEnum.CONFIRMED) + }) +}) + +describe('Coin Transaction', () => { + const tx = new CoinTransaction(txId) + + it('Receiver', async () => { + expect((await tx.getReceiver()).toLowerCase()).toBe(receiverTestAddress.toLowerCase()) + }) + + it('Amount', async () => { + expect(await tx.getAmount()).toBe(testAmount) + }) + + it('Verify Transfer', async () => { + expect( + await tx.verifyTransfer(AssetDirectionEnum.INCOMING, receiverTestAddress, testAmount) + ).toBe(TransactionStatusEnum.CONFIRMED) + + expect( + await tx.verifyTransfer(AssetDirectionEnum.OUTGOING, senderTestAddress, testAmount) + ).toBe(TransactionStatusEnum.CONFIRMED) + + expect( + await tx.verifyTransfer(AssetDirectionEnum.OUTGOING, receiverTestAddress, testAmount) + ).toBe(TransactionStatusEnum.FAILED) + }) +}) diff --git a/packages/networks/xrpl/tests/services.spec.ts b/packages/networks/xrpl/tests/services.spec.ts new file mode 100644 index 0000000..e0fddaf --- /dev/null +++ b/packages/networks/xrpl/tests/services.spec.ts @@ -0,0 +1,67 @@ +import { describe, it, expect } from 'vitest' + +import { provider } from './setup' +import { Provider } from '../src/services/Provider' +import { TransactionListener } from '../src/services/TransactionListener' +import { TransactionTypeEnum } from '@multiplechain/types' +import { CoinTransaction } from '../src/models/CoinTransaction' +import { Coin } from '../src/assets/Coin' +import { sleep } from '@multiplechain/utils' + +const senderTestAddress = String(process.env.XRP_SENDER_ADDRESS) +const receiverTestAddress = String(process.env.XRP_RECEIVER_ADDRESS) +const senderPrivateKey = String(process.env.XRP_SENDER_PRIVATE_KEY) +const listenerTestIsActive = Boolean(process.env.XRP_LISTENER_TEST_IS_ACTIVE !== 'false') + +describe('Provider', () => { + it('isTestnet', () => { + expect(provider.isTestnet()).toBe(true) + }) + + it('instance', () => { + expect(Provider.instance).toBe(provider) + }) + + it('checkRpcConnection', async () => { + expect(await provider.checkRpcConnection()).toBe(true) + }) + + it('checkWsConnection', async () => { + expect(await provider.checkWsConnection()).toBe(true) + }) +}) + +describe('Transaction Listener', () => { + if (!listenerTestIsActive) { + it('No test is active', () => { + expect(true).toBe(true) + }) + return + } + + it('Coin', async () => { + const listener = new TransactionListener(TransactionTypeEnum.COIN, { + signer: senderTestAddress, + receiver: receiverTestAddress + }) + + const signer = await new Coin().transfer(senderTestAddress, receiverTestAddress, 0.0001) + + const waitListenerEvent = async (): Promise => { + return await new Promise((resolve, reject) => { + void listener + .on((transaction) => { + listener.stop() + resolve(transaction) + }) + .then(async () => { + await sleep(2000) + void (await signer.sign(senderPrivateKey)).send() + }) + .catch(reject) + }) + } + + expect(await waitListenerEvent()).toBeInstanceOf(CoinTransaction) + }) +}) diff --git a/packages/networks/xrpl/tests/setup.ts b/packages/networks/xrpl/tests/setup.ts new file mode 100644 index 0000000..476b875 --- /dev/null +++ b/packages/networks/xrpl/tests/setup.ts @@ -0,0 +1,13 @@ +import { Provider } from '../src/services/Provider' + +let provider: Provider + +try { + provider = Provider.instance +} catch (e) { + provider = new Provider({ + testnet: true + }) +} + +export { provider } diff --git a/packages/networks/xrpl/tsconfig.json b/packages/networks/xrpl/tsconfig.json new file mode 100644 index 0000000..594d173 --- /dev/null +++ b/packages/networks/xrpl/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "noEmit": true, + "composite": true, + "declaration": true, + "outDir": "./dist/esm", + "declarationDir": "./dist/types" + }, + "extends": "../../../tsconfig.json", + "include": [ + "src", + ".eslintrc.json", + "tests", + "vite.config.ts", + "esbuild.ts", + "vitest.config.ts", + "../../../esbuild.ts", + "../../../vite.config.ts", + "../../../vitest.config.ts" + ] +} diff --git a/packages/networks/xrpl/vite.config.ts b/packages/networks/xrpl/vite.config.ts new file mode 100644 index 0000000..7a87a00 --- /dev/null +++ b/packages/networks/xrpl/vite.config.ts @@ -0,0 +1,10 @@ +import { mergeConfig } from 'vite' +import mainConfig from '../../../vite.config' + +export default mergeConfig(mainConfig, { + build: { + lib: { + name: 'MultipleChain.XRPl' + } + } +}) diff --git a/packages/networks/xrpl/vite.svg b/packages/networks/xrpl/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/packages/networks/xrpl/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/networks/xrpl/vitest.config.ts b/packages/networks/xrpl/vitest.config.ts new file mode 100644 index 0000000..8a33630 --- /dev/null +++ b/packages/networks/xrpl/vitest.config.ts @@ -0,0 +1,12 @@ +import { mergeConfig, defineConfig } from 'vitest/config' +import mainConfig from '../../../vite.config' + +export default mergeConfig( + mainConfig, + defineConfig({ + test: { + testTimeout: 600000, + setupFiles: ['./tests/setup.ts'] + } + }) +) From 3df80b10a07a6aba74f9ab8e2e75bdd93a1e87c5 Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Thu, 30 Jan 2025 11:10:31 +0300 Subject: [PATCH 10/19] fix --- packages/networks/bitcoin/package.json | 2 +- packages/networks/boilerplate/package.json | 2 +- packages/networks/solana/package.json | 2 +- packages/networks/ton/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/networks/bitcoin/package.json b/packages/networks/bitcoin/package.json index 269743e..f62d3b8 100644 --- a/packages/networks/bitcoin/package.json +++ b/packages/networks/bitcoin/package.json @@ -63,7 +63,7 @@ ], "author": "MultipleChain", "license": "MIT", - "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/network-name", + "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/bitcoin", "repository": { "type": "git", "url": "git+https://github.com/MultipleChain/js.git" diff --git a/packages/networks/boilerplate/package.json b/packages/networks/boilerplate/package.json index cbc5909..fa2557f 100644 --- a/packages/networks/boilerplate/package.json +++ b/packages/networks/boilerplate/package.json @@ -63,7 +63,7 @@ ], "author": "MultipleChain", "license": "MIT", - "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/network-name", + "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/boilerplate", "repository": { "type": "git", "url": "git+https://github.com/MultipleChain/js.git" diff --git a/packages/networks/solana/package.json b/packages/networks/solana/package.json index c295a90..6ff23d4 100644 --- a/packages/networks/solana/package.json +++ b/packages/networks/solana/package.json @@ -63,7 +63,7 @@ ], "author": "MultipleChain", "license": "MIT", - "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/network-name", + "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/solana", "repository": { "type": "git", "url": "git+https://github.com/MultipleChain/js.git" diff --git a/packages/networks/ton/package.json b/packages/networks/ton/package.json index 307a505..75813db 100644 --- a/packages/networks/ton/package.json +++ b/packages/networks/ton/package.json @@ -63,7 +63,7 @@ ], "author": "MultipleChain", "license": "MIT", - "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/network-name", + "homepage": "https://github.com/MultipleChain/js/tree/master/packages/networks/ton", "repository": { "type": "git", "url": "git+https://github.com/MultipleChain/js.git" From d7096bc7fb8e8d7dc302bd8b87675a57f0f20d7d Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Thu, 30 Jan 2025 15:04:23 +0300 Subject: [PATCH 11/19] fixed metadata problem --- packages/networks/solana/package.json | 2 +- packages/networks/solana/src/assets/Token.ts | 36 +++++++++++--------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/networks/solana/package.json b/packages/networks/solana/package.json index 6ff23d4..ef41398 100644 --- a/packages/networks/solana/package.json +++ b/packages/networks/solana/package.json @@ -1,6 +1,6 @@ { "name": "@multiplechain/solana", - "version": "0.4.15", + "version": "0.4.16", "type": "module", "main": "dist/index.cjs", "module": "dist/index.es.js", diff --git a/packages/networks/solana/src/assets/Token.ts b/packages/networks/solana/src/assets/Token.ts index 5cb5b83..24c5fc1 100644 --- a/packages/networks/solana/src/assets/Token.ts +++ b/packages/networks/solana/src/assets/Token.ts @@ -57,23 +57,27 @@ export class Token extends Contract implements TokenInterface 'confirmed', programId ) - if (result === null) return null - return (this.metadata = { - name: result.name, - symbol: result.symbol, - programId: programId.toBase58(), - decimals: accountInfo.value.data.parsed.info.decimals - }) - } else { - const metaplex = Metaplex.make(this.provider.web3) - const data = await metaplex.nfts().findByMint({ mintAddress: this.pubKey }) - return (this.metadata = { - name: data.name, - symbol: data.symbol, - programId: programId.toBase58(), - decimals: accountInfo.value.data.parsed.info.decimals - }) + if (result !== null) { + return (this.metadata = { + name: result.name, + symbol: result.symbol, + programId: programId.toBase58(), + decimals: accountInfo.value.data.parsed.info.decimals + }) + } } + + const metaplex = Metaplex.make(this.provider.web3) + const data = await metaplex.nfts().findByMint({ mintAddress: this.pubKey }) + + if (data === null) return null + + return (this.metadata = { + name: data.name, + symbol: data.symbol, + programId: programId.toBase58(), + decimals: accountInfo.value.data.parsed.info.decimals + }) } /** From ee1e5d1a2bd6fcf7f30b9345e7d0eebb590618e3 Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 31 Jan 2025 14:54:16 +0300 Subject: [PATCH 12/19] fix --- packages/networks/bitcoin/src/services/TransactionListener.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/networks/bitcoin/src/services/TransactionListener.ts b/packages/networks/bitcoin/src/services/TransactionListener.ts index 7c4af08..8d8ca48 100644 --- a/packages/networks/bitcoin/src/services/TransactionListener.ts +++ b/packages/networks/bitcoin/src/services/TransactionListener.ts @@ -245,7 +245,7 @@ export class TransactionListener< if ( this.filter?.signer !== undefined && - values.sender !== this.filter.signer.toLowerCase() + values.sender?.toLowerCase() !== this.filter.signer.toLowerCase() ) { return } From c34b69d8ab71f70b091327ce7ad228642c2aee4f Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 31 Jan 2025 15:35:12 +0300 Subject: [PATCH 13/19] done --- packages/networks/xrpl/package.json | 8 +- packages/networks/xrpl/pnpm-lock.yaml | 105 ++++++-- packages/networks/xrpl/src/assets/Coin.ts | 26 +- .../xrpl/src/models/CoinTransaction.ts | 6 +- .../networks/xrpl/src/models/Transaction.ts | 70 ++++-- packages/networks/xrpl/src/services/Client.ts | 87 +++++++ .../networks/xrpl/src/services/Provider.ts | 76 ++++-- .../xrpl/src/services/TransactionListener.ts | 233 ++++++++---------- .../xrpl/src/services/TransactionSigner.ts | 48 +++- packages/networks/xrpl/src/utils.ts | 11 + packages/networks/xrpl/tests/assets.spec.ts | 9 +- packages/networks/xrpl/tests/models.spec.ts | 12 +- packages/networks/xrpl/tests/setup.ts | 12 +- 13 files changed, 483 insertions(+), 220 deletions(-) create mode 100644 packages/networks/xrpl/src/services/Client.ts create mode 100644 packages/networks/xrpl/src/utils.ts diff --git a/packages/networks/xrpl/package.json b/packages/networks/xrpl/package.json index ab52dc4..7caa7e7 100644 --- a/packages/networks/xrpl/package.json +++ b/packages/networks/xrpl/package.json @@ -74,11 +74,7 @@ "dependencies": { "@multiplechain/types": "^0.1.70", "@multiplechain/utils": "^0.1.21", - "axios": "^1.6.8", - "isomorphic-ws": "^5.0.0", - "ws": "^8.17.0" - }, - "devDependencies": { - "@types/ws": "^8.5.10" + "axios": "^1.7.9", + "xrpl": "^4.1.0" } } \ No newline at end of file diff --git a/packages/networks/xrpl/pnpm-lock.yaml b/packages/networks/xrpl/pnpm-lock.yaml index 49ed22a..cc113ed 100644 --- a/packages/networks/xrpl/pnpm-lock.yaml +++ b/packages/networks/xrpl/pnpm-lock.yaml @@ -15,18 +15,11 @@ importers: specifier: ^0.1.21 version: 0.1.23 axios: - specifier: ^1.6.8 + specifier: ^1.7.9 version: 1.7.9 - isomorphic-ws: - specifier: ^5.0.0 - version: 5.0.0(ws@8.18.0) - ws: - specifier: ^8.17.0 - version: 8.18.0 - devDependencies: - '@types/ws': - specifier: ^8.5.10 - version: 8.5.14 + xrpl: + specifier: ^4.1.0 + version: 4.1.0 packages: @@ -58,6 +51,13 @@ packages: '@types/ws@8.5.14': resolution: {integrity: sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==} + '@xrplf/isomorphic@1.0.1': + resolution: {integrity: sha512-0bIpgx8PDjYdrLFeC3csF305QQ1L7sxaWnL5y71mCvhenZzJgku9QsA+9QCXBC1eNYtxWO/xR91zrXJy2T/ixg==} + engines: {node: '>=16.0.0'} + + '@xrplf/secret-numbers@1.0.0': + resolution: {integrity: sha512-qsCLGyqe1zaq9j7PZJopK+iGTGRbk6akkg6iZXJJgxKwck0C5x5Gnwlb1HKYGOwPKyrXWpV6a2YmcpNpUFctGg==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -187,11 +187,6 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} - isomorphic-ws@5.0.0: - resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} - peerDependencies: - ws: '*' - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -211,6 +206,18 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + ripple-address-codec@5.0.0: + resolution: {integrity: sha512-de7osLRH/pt5HX2xw2TRJtbdLLWHu0RXirpQaEeCnWKY5DYHykh3ETSkofvm0aX0LJiV7kwkegJxQkmbO94gWw==} + engines: {node: '>= 16'} + + ripple-binary-codec@2.2.0: + resolution: {integrity: sha512-93fvAW3oXux4NY5Xf79dUIOhud5DmyEcC5RrTYdl0BPaYOGXC/txCQl9FnwIVZGkVMtZFLcFwJfmH1zFgfRyKA==} + engines: {node: '>= 18'} + + ripple-keypairs@2.0.0: + resolution: {integrity: sha512-b5rfL2EZiffmklqZk1W+dvSy97v3V/C7936WxCCgDynaGPp7GE6R2XO7EU9O2LlM/z95rj870IylYnOQs+1Rag==} + engines: {node: '>= 16'} + safe-regex-test@1.1.0: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} @@ -257,6 +264,10 @@ packages: utf-8-validate: optional: true + xrpl@4.1.0: + resolution: {integrity: sha512-H/+BCEnFLyQOBUC6h4nMKg7I9AuxHe4kj9ZwQHX2zoL9n/ZOERc6B2U079pogI84zCbYdUWIn4DkoIYvjO/hpg==} + engines: {node: '>=18.0.0'} + zod@3.24.1: resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} @@ -301,6 +312,23 @@ snapshots: dependencies: '@types/node': 22.12.0 + '@xrplf/isomorphic@1.0.1': + dependencies: + '@noble/hashes': 1.4.0 + eventemitter3: 5.0.1 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@xrplf/secret-numbers@1.0.0': + dependencies: + '@xrplf/isomorphic': 1.0.1 + ripple-keypairs: 2.0.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + asynckit@0.4.0: {} available-typed-arrays@1.0.7: @@ -444,10 +472,6 @@ snapshots: dependencies: which-typed-array: 1.1.18 - isomorphic-ws@5.0.0(ws@8.18.0): - dependencies: - ws: 8.18.0 - math-intrinsics@1.1.0: {} mime-db@1.52.0: {} @@ -460,6 +484,32 @@ snapshots: proxy-from-env@1.1.0: {} + ripple-address-codec@5.0.0: + dependencies: + '@scure/base': 1.1.9 + '@xrplf/isomorphic': 1.0.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ripple-binary-codec@2.2.0: + dependencies: + '@xrplf/isomorphic': 1.0.1 + bignumber.js: 9.1.2 + ripple-address-codec: 5.0.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ripple-keypairs@2.0.0: + dependencies: + '@noble/curves': 1.4.2 + '@xrplf/isomorphic': 1.0.1 + ripple-address-codec: 5.0.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + safe-regex-test@1.1.0: dependencies: call-bound: 1.0.3 @@ -518,4 +568,19 @@ snapshots: ws@8.18.0: {} + xrpl@4.1.0: + dependencies: + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + '@xrplf/isomorphic': 1.0.1 + '@xrplf/secret-numbers': 1.0.0 + bignumber.js: 9.1.2 + eventemitter3: 5.0.1 + ripple-address-codec: 5.0.0 + ripple-binary-codec: 2.2.0 + ripple-keypairs: 2.0.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + zod@3.24.1: {} diff --git a/packages/networks/xrpl/src/assets/Coin.ts b/packages/networks/xrpl/src/assets/Coin.ts index 1a2855f..74c5ebb 100644 --- a/packages/networks/xrpl/src/assets/Coin.ts +++ b/packages/networks/xrpl/src/assets/Coin.ts @@ -1,3 +1,4 @@ +import { dropsToXrp, xrpToDrops } from 'xrpl' import { Provider } from '../services/Provider' import { TransactionSigner } from '../services/TransactionSigner' import { @@ -46,19 +47,21 @@ export class Coin implements CoinInterface { * @returns Wallet balance as currency of COIN */ async getBalance(owner: WalletAddress): Promise { - return await Promise.resolve(100) + return dropsToXrp(await this.provider.rpc.getBalance(owner)) } /** * @param sender Sender wallet address * @param receiver Receiver wallet address * @param amount Amount of assets that will be transferred + * @param memo Memo for the transaction * @returns Transaction signer */ async transfer( sender: WalletAddress, receiver: WalletAddress, - amount: TransferAmount + amount: TransferAmount, + memo?: string ): Promise { if (amount < 0) { throw new Error(ErrorTypeEnum.INVALID_AMOUNT) @@ -72,6 +75,23 @@ export class Coin implements CoinInterface { throw new Error(ErrorTypeEnum.INVALID_ADDRESS) } - return new TransactionSigner() + const info = await this.provider.rpc.getAccountInfo(receiver) + + if (this.provider.rpc.isError(info) && info.error === 'actNotFound') { + const minReserve = await this.provider.rpc.getMinimumReserve() + if (amount < minReserve) { + throw new Error( + `This account is not activated, so you have to send at least ${minReserve} XRP to activate it` + ) + } + } + + return new TransactionSigner({ + Account: sender, + Destination: receiver, + Amount: xrpToDrops(amount), + TransactionType: 'Payment', + Memos: memo ? [this.provider.createMemo(memo)] : [] + }) } } diff --git a/packages/networks/xrpl/src/models/CoinTransaction.ts b/packages/networks/xrpl/src/models/CoinTransaction.ts index 6ad9770..13a5fe4 100644 --- a/packages/networks/xrpl/src/models/CoinTransaction.ts +++ b/packages/networks/xrpl/src/models/CoinTransaction.ts @@ -1,4 +1,4 @@ -import { fromSatoshi } from '../utils' +import { dropsToXrp } from 'xrpl' import { Transaction } from './Transaction' import { TransactionStatusEnum, AssetDirectionEnum } from '@multiplechain/types' import type { WalletAddress, CoinTransactionInterface, TransferAmount } from '@multiplechain/types' @@ -9,7 +9,7 @@ export class CoinTransaction extends Transaction implements CoinTransactionInter */ async getReceiver(): Promise { const data = await this.getData() - return data?.vout[0].scriptpubkey_address ?? '' + return data?.Destination ?? '' } /** @@ -24,7 +24,7 @@ export class CoinTransaction extends Transaction implements CoinTransactionInter */ async getAmount(): Promise { const data = await this.getData() - return fromSatoshi(data?.vout[0].value ?? 0) + return dropsToXrp(data?.Amount ?? 0) } /** diff --git a/packages/networks/xrpl/src/models/Transaction.ts b/packages/networks/xrpl/src/models/Transaction.ts index b19785f..aa87f57 100644 --- a/packages/networks/xrpl/src/models/Transaction.ts +++ b/packages/networks/xrpl/src/models/Transaction.ts @@ -1,4 +1,3 @@ -import axios from 'axios' import { Provider } from '../services/Provider' import { ErrorTypeEnum, TransactionStatusEnum } from '@multiplechain/types' import { @@ -11,8 +10,17 @@ import { type TransactionInterface, type WalletAddress } from '@multiplechain/types' +import { dropsToXrp, type Transaction as BaseTransactionData, type TransactionMetadata } from 'xrpl' -export class Transaction implements TransactionInterface { +export type TransactionData = BaseTransactionData & { + meta?: TransactionMetadata + Destination?: string + ledger_index?: number + Amount?: number + date?: number +} + +export class Transaction implements TransactionInterface { /** * Each transaction has its own unique ID defined by the user */ @@ -26,7 +34,7 @@ export class Transaction implements TransactionInterface { /** * Transaction data */ - data: any | null = null + data: TransactionData | null = null /** * @param id Transaction id @@ -40,11 +48,12 @@ export class Transaction implements TransactionInterface { /** * @returns Transaction data */ - async getData(): Promise { - if (this.data !== null) { + async getData(): Promise { + if (this.data?.meta) { return this.data } try { + return (this.data = await this.provider.rpc.getTransaction(this.id)) } catch (error) { console.error('MC XRPl TX getData', error) throw new Error(ErrorTypeEnum.RPC_REQUEST_ERROR) @@ -60,12 +69,8 @@ export class Transaction implements TransactionInterface { const check = async (): Promise => { try { const status = await this.getStatus() - if (status === TransactionStatusEnum.CONFIRMED) { - resolve(TransactionStatusEnum.CONFIRMED) - return - } else if (status === TransactionStatusEnum.FAILED) { - reject(TransactionStatusEnum.FAILED) - return + if (status !== TransactionStatusEnum.PENDING) { + resolve(status) } setTimeout(check, ms) } catch (error) { @@ -95,48 +100,79 @@ export class Transaction implements TransactionInterface { * @returns Transaction URL */ getUrl(): string { - return '' + return this.provider.explorer + 'transactions/' + this.id + } + + async getMemos(): Promise { + const data = await this.getData() + return (data?.Memos ?? []).map(({ Memo }) => { + if (Memo.MemoData) { + Memo.MemoData = Buffer.from(Memo.MemoData, 'hex').toString('utf-8') + } + + if (Memo.MemoType) { + Memo.MemoType = Buffer.from(Memo.MemoType, 'hex').toString('utf-8') + } + + if (Memo.MemoFormat) { + Memo.MemoFormat = Buffer.from(Memo.MemoFormat, 'hex').toString('utf-8') + } + + return Memo + }) } /** * @returns Wallet address of the sender of transaction */ async getSigner(): Promise { - return '' + const data = await this.getData() + return data?.Account ?? '' } /** * @returns Transaction fee */ async getFee(): Promise { - return 0 + const data = await this.getData() + return dropsToXrp(data?.Fee ?? 0) } /** * @returns Block number that transaction */ async getBlockNumber(): Promise { - return 0 + const data = await this.getData() + return data?.ledger_index ?? 0 } /** * @returns Block timestamp that transaction */ async getBlockTimestamp(): Promise { - return 0 + const data = await this.getData() + return data?.date ?? 0 } /** * @returns Confirmation count of the block */ async getBlockConfirmationCount(): Promise { - return 0 + const blockNumber = await this.getBlockNumber() + const ledger = await this.provider.rpc.getLedger() + return ledger.result.ledger_index - blockNumber } /** * @returns Status of the transaction */ async getStatus(): Promise { + const data = await this.getData() + if (data?.meta) { + return data?.meta?.TransactionResult === 'tesSUCCESS' + ? TransactionStatusEnum.CONFIRMED + : TransactionStatusEnum.FAILED + } return TransactionStatusEnum.PENDING } } diff --git a/packages/networks/xrpl/src/services/Client.ts b/packages/networks/xrpl/src/services/Client.ts new file mode 100644 index 0000000..29905b4 --- /dev/null +++ b/packages/networks/xrpl/src/services/Client.ts @@ -0,0 +1,87 @@ +import axios from 'axios' +import type { TransactionData } from '../models' +import type { AccountInfoResponse, ErrorResponse, LedgerResponse } from 'xrpl' + +export default class Client { + private readonly rpcUrl: string + + constructor(rpcUrl: string) { + this.rpcUrl = rpcUrl + } + + async request(method: string, params: any): Promise { + try { + const response = await axios.post(this.rpcUrl, { + method, + params: [params] + }) + + if (response.status !== 200 || response.data.error) { + throw new Error(JSON.stringify(response.data)) + } + + return response.data + } catch (error) { + return error as Error + } + } + + async getMinimumReserve(): Promise { + const result = await this.request('server_info', {}) + + if (result instanceof Error) { + throw result + } + + return result.info.validated_ledger.reserve_base_xrp + } + + async getAccountInfo(address: string): Promise { + return await this.request('account_info', { + account: address, + ledger_index: 'validated' + }) + } + + isError(response: any): response is ErrorResponse { + return response && response.status === 'error' + } + + async getBalance(address: string): Promise { + const response = await this.getAccountInfo(address) + + if (this.isError(response)) { + return '0' + } + + return response.result.account_data.Balance + } + + async getLedger(): Promise { + return await this.request('ledger', { + ledger_index: 'validated' + }) + } + + async getFee(): Promise { + const response = await this.request('fee', {}) + + if (response instanceof Error) { + throw response + } + + return response.result.drops.minimum_fee + } + + async getTransaction(txId: string): Promise { + const { result } = await this.request('tx', { + transaction: txId + }) + + if (result.error) { + throw new Error(result.error_message as string) + } + + return result + } +} diff --git a/packages/networks/xrpl/src/services/Provider.ts b/packages/networks/xrpl/src/services/Provider.ts index efee7a8..01880b7 100644 --- a/packages/networks/xrpl/src/services/Provider.ts +++ b/packages/networks/xrpl/src/services/Provider.ts @@ -1,4 +1,5 @@ -import axios from 'axios' +import Client from './Client' +import { Client as WsClient, type Memo } from 'xrpl' import { checkWebSocket } from '@multiplechain/utils' import { ErrorTypeEnum, @@ -12,30 +13,19 @@ export class Provider implements ProviderInterface { */ network: NetworkConfigInterface - /** - * API URL - */ - api: string + ws: WsClient + + rpc: Client - /** - * Explorer URL - */ explorer: string - /** - * Websocket URL - */ - wsUrl: string + testnetRpc = 'https://s.altnet.rippletest.net:51234' - /** - * BlockCypher token - */ - blockCypherToken?: string + testnetWs = 'wss://s.altnet.rippletest.net:51233' - /** - * Default BlockCypher token - */ - defaultBlockCypherToken = '49d43a59a4f24d31a9731eb067ab971c' + mainnetRpc = 'https://xrplcluster.com' + + mainnetWs = 'wss://xrplcluster.com' /** * Static instance of the provider @@ -78,10 +68,19 @@ export class Provider implements ProviderInterface { */ async checkRpcConnection(url?: string): Promise { try { - const response = await axios.get(url ?? this.createEndpoint('blocks/tip/height')) + const response = await fetch(url ?? this.network.rpcUrl ?? '', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + method: 'server_info', + params: [] + }) + }) if (response.status !== 200) { - return new Error(response.statusText + ': ' + JSON.stringify(response.data)) + return new Error(response.statusText + ': ' + response.status) } return true @@ -97,7 +96,7 @@ export class Provider implements ProviderInterface { */ async checkWsConnection(url?: string): Promise { try { - const result: any = await checkWebSocket(url ?? this.wsUrl) + const result: any = await checkWebSocket(url ?? this.network.wsUrl ?? '') if (result instanceof Error) { return result @@ -116,8 +115,21 @@ export class Provider implements ProviderInterface { update(network: NetworkConfigInterface): void { this.network = network Provider._instance = this - this.network.rpcUrl = this.api - this.network.wsUrl = this.wsUrl + if (!network.wsUrl) { + throw new Error(ErrorTypeEnum.WS_URL_NOT_DEFINED) + } + if (!network.rpcUrl) { + throw new Error('RPC URL is not defined') + } + this.ws = new WsClient(network.wsUrl) + this.rpc = new Client(network.rpcUrl) + this.explorer = network.testnet ? 'https://testnet.xrpl.org/' : 'https://livenet.xrpl.org/' + if (!network.rpcUrl) { + this.network.rpcUrl = network.testnet ? this.testnetRpc : this.mainnetRpc + } + if (!network.wsUrl) { + this.network.wsUrl = network.testnet ? this.testnetWs : this.mainnetWs + } } /** @@ -127,4 +139,18 @@ export class Provider implements ProviderInterface { isTestnet(): boolean { return this.network?.testnet ?? false } + + /** + * Create memo object + * @param memo - Memo data + * @returns Memo object + */ + createMemo(memo: string): Memo { + return { + Memo: { + MemoData: Buffer.from(memo).toString('hex'), + MemoType: Buffer.from('text').toString('hex') + } + } + } } diff --git a/packages/networks/xrpl/src/services/TransactionListener.ts b/packages/networks/xrpl/src/services/TransactionListener.ts index 3f6a0ab..e9f41db 100644 --- a/packages/networks/xrpl/src/services/TransactionListener.ts +++ b/packages/networks/xrpl/src/services/TransactionListener.ts @@ -5,20 +5,21 @@ import type { DynamicTransactionListenerFilterType, TransactionId } from '@multiplechain/types' -import WebSocket from 'isomorphic-ws' import { Provider } from './Provider' -import { fromSatoshi } from '../utils' +import { objectsEqual } from '@multiplechain/utils' import { Transaction } from '../models/Transaction' import { CoinTransaction } from '../models/CoinTransaction' -import { checkWebSocket, objectsEqual } from '@multiplechain/utils' import { TransactionListenerProcessIndex } from '@multiplechain/types' +import { + type SubscribeRequest, + type TransactionStream, + type UnsubscribeRequest, + type Client as WsClient +} from 'xrpl' -interface Values { - txId: string - amount?: number - sender?: string - receiver?: string -} +type Command = Omit + +type TransactionStreamWithHash = TransactionStream & { hash: string } type TransactionListenerTriggerType = DynamicTransactionType< T, @@ -73,7 +74,7 @@ export class TransactionListener< /** * WebSocket */ - webSocket: WebSocket + webSocket: WsClient /** * Dynamic stop method @@ -128,8 +129,8 @@ export class TransactionListener< async on(callback: CallBackType): Promise { if (this.webSocket === undefined) { try { - await checkWebSocket(this.provider.wsUrl) - this.webSocket = new WebSocket(this.provider.wsUrl) + await this.provider.ws.connect() + this.webSocket = this.provider.ws } catch (error) { throw new Error( 'WebSocket connection is not available' + @@ -157,100 +158,58 @@ export class TransactionListener< } } - isBlockCypherProcess(): boolean { - return this.provider.isTestnet() || this.provider.blockCypherToken !== undefined + idByFilter(): string { + return btoa(JSON.stringify(this.filter) + this.type) } - /** - * Create message for the listener - * @param receiver - Receiver address - * @returns Message - */ - createMessage(receiver?: string): string { - let message - if (this.isBlockCypherProcess()) { - interface Config { - event: string - token: string - address?: string - } - - const config: Config = { - event: 'unconfirmed-tx', - token: this.provider.blockCypherToken ?? this.provider.defaultBlockCypherToken - } - - if (receiver !== undefined) { - config.address = receiver - } - - message = JSON.stringify(config) - } else { - message = JSON.stringify({ - op: 'unconfirmed_sub' - }) - } - - this.dynamicStop = () => { - if (!this.isBlockCypherProcess()) { - this.webSocket.send( - JSON.stringify({ - op: 'unconfirmed_unsub' - }) - ) + createCommands(args: Command): { + sub: SubscribeRequest + unSub: UnsubscribeRequest + } { + return { + sub: { + id: this.idByFilter(), + command: 'subscribe', + ...args + }, + unSub: { + id: this.idByFilter(), + command: 'unsubscribe', + ...args } - this.webSocket.close() } - - return message } /** - * Parse the data - * @param data - Data - * @returns Parsed data + * General transaction process */ - getValues(data: any): Values { - const values: Values = { - txId: '' - } + generalProcess(): void { + const args: Command = { streams: ['transactions'] } - if (this.isBlockCypherProcess()) { - values.txId = data.hash - values.sender = data.inputs[0].addresses[0] - values.receiver = data.outputs[0].addresses[0] - values.amount = fromSatoshi(data.outputs[0].value as number) - } else { - values.txId = data.x.hash - values.receiver = data.x.out[0].addr - values.sender = data.x.inputs[0].prev_out.addr - values.amount = fromSatoshi(data.x.out[0].value as number) + if (this.filter?.signer) { + args.accounts = [this.filter.signer] } - return values - } + const { sub, unSub } = this.createCommands(args) - /** - * General transaction process - */ - generalProcess(): void { - const message = this.createMessage() + void this.webSocket.request(sub).then(() => { + const callback = (tx: TransactionStreamWithHash): void => { + if ( + this.filter?.signer !== undefined && + tx.tx_json?.Account.toLowerCase() !== this.filter.signer.toLowerCase() + ) { + return + } - this.webSocket.addEventListener('open', () => { - this.webSocket.send(message) - }) + this.trigger(new Transaction(tx.hash)) + } - this.webSocket.addEventListener('message', async (res: WebSocket.MessageEvent) => { - const values = this.getValues(JSON.parse(res.data as string)) + this.webSocket.on('transaction', callback) - if ( - this.filter?.signer !== undefined && - values.sender !== this.filter.signer.toLowerCase() - ) { - return + this.dynamicStop = () => { + void this.webSocket.request(unSub) + void this.webSocket.off('transaction', callback) } - - this.trigger(new Transaction(values.txId)) }) } @@ -279,50 +238,70 @@ export class TransactionListener< const sender = filter.sender ?? filter.signer - const message = this.createMessage(filter.receiver) - - this.webSocket.addEventListener('open', () => { - this.webSocket.send(message) - }) - - this.webSocket.addEventListener('message', async (res: WebSocket.MessageEvent) => { - const data = JSON.parse(res.data as string) - - interface ParamsType { - sender?: string - receiver?: string - } - - const expectedParams: ParamsType = {} - const receivedParams: ParamsType = {} - - if (String(data.event).includes('events limit reached')) { - throw new Error('BlockCypher events limit reached.') - } - - const values = this.getValues(data) + const args: Command & { + accounts: string[] + } = { streams: ['transactions'], accounts: [] } - if (sender !== undefined) { - expectedParams.sender = sender.toLowerCase() - receivedParams.sender = values.sender?.toLowerCase() - } + if (sender) { + args.accounts.push(sender) + } - if (filter.receiver !== undefined) { - expectedParams.receiver = filter.receiver.toLowerCase() - receivedParams.receiver = values.receiver?.toLowerCase() - } + if (filter.receiver) { + args.accounts.push(filter.receiver) + } - if (!objectsEqual(expectedParams, receivedParams)) { - return + const { sub, unSub } = this.createCommands(args) + + void this.webSocket.request(sub).then(() => { + const callback = async ( + tx: TransactionStreamWithHash & { + tx_json: { + Account: string + Destination?: string + } + } + ): Promise => { + interface ParamsType { + sender?: string + receiver?: string + } + + const expectedParams: ParamsType = {} + const receivedParams: ParamsType = {} + + if (sender !== undefined) { + expectedParams.sender = sender.toLowerCase() + receivedParams.sender = tx.tx_json?.Account.toLowerCase() + } + + if (filter.receiver !== undefined) { + expectedParams.receiver = filter.receiver.toLowerCase() + receivedParams.receiver = tx.tx_json?.Destination?.toLowerCase() + } + + if (!objectsEqual(expectedParams, receivedParams)) { + return + } + + const transaction = new CoinTransaction(tx.hash) + + if (filter.amount !== undefined) { + await transaction.wait() + const amount = await transaction.getAmount() + if (amount !== filter.amount) { + return + } + } + + this.trigger(transaction) } - const transaction = new CoinTransaction(values.txId) + this.webSocket.on('transaction', callback) - if (filter.amount !== undefined && values.amount !== filter.amount) { - return + this.dynamicStop = () => { + void this.webSocket.request(unSub) + void this.webSocket.off('transaction', callback) } - - this.trigger(transaction) }) } diff --git a/packages/networks/xrpl/src/services/TransactionSigner.ts b/packages/networks/xrpl/src/services/TransactionSigner.ts index ad93fcd..f548742 100644 --- a/packages/networks/xrpl/src/services/TransactionSigner.ts +++ b/packages/networks/xrpl/src/services/TransactionSigner.ts @@ -1,16 +1,24 @@ import { Provider } from '../services/Provider' +import { type ECDSA, Wallet, type SubmittableTransaction } from 'xrpl' import type { PrivateKey, TransactionId, TransactionSignerInterface } from '@multiplechain/types' -export class TransactionSigner implements TransactionSignerInterface { +export interface SignedTransaction { + tx_blob: string + hash: string +} + +export class TransactionSigner + implements TransactionSignerInterface +{ /** * Transaction data from the blockchain network */ - rawData: any + rawData: SubmittableTransaction /** * Signed transaction data */ - signedData?: string + signedData?: SignedTransaction /** * Blockchain network provider @@ -21,7 +29,7 @@ export class TransactionSigner implements TransactionSignerInterface { + async sign(privateKey: PrivateKey, algorithm?: ECDSA): Promise { + await this.provider.ws.connect() + + const senderWallet = Wallet.fromSeed(privateKey, { + algorithm + }) + + this.rawData = await this.provider.ws.autofill(this.rawData) + + this.signedData = senderWallet.sign(this.rawData) + return this } @@ -40,13 +59,26 @@ export class TransactionSigner implements TransactionSignerInterface { + if (!this.signedData) { + throw new Error('Transaction not signed') + } + + const { result } = await this.provider.ws.submit(this.signedData.tx_blob) + + if (result.engine_result !== 'tesSUCCESS') { + throw new Error(`Transaction failed: ${result.engine_result_message}`) + } + + await this.provider.ws.disconnect() + + return this.signedData.hash } /** * Get the raw transaction data * @returns Transaction data */ - getRawData(): any { + getRawData(): SubmittableTransaction { return this.rawData } @@ -54,7 +86,7 @@ export class TransactionSigner implements TransactionSignerInterface { + return math.div(amount, 100000000, 8) +} + +export const toSatoshi = (amount: number): number => { + return math.mul(amount, 100000000, 8) +} diff --git a/packages/networks/xrpl/tests/assets.spec.ts b/packages/networks/xrpl/tests/assets.spec.ts index bfe87c8..b769acf 100644 --- a/packages/networks/xrpl/tests/assets.spec.ts +++ b/packages/networks/xrpl/tests/assets.spec.ts @@ -5,6 +5,7 @@ import { math } from '@multiplechain/utils' import { Transaction } from '../src/models/Transaction' import { TransactionStatusEnum, type TransactionId } from '@multiplechain/types' import { TransactionSigner } from '../src/services/TransactionSigner' +import { ECDSA } from 'xrpl' const testAmount = Number(process.env.XRP_TRANSFER_AMOUNT) const senderTestAddress = String(process.env.XRP_SENDER_ADDRESS) @@ -19,9 +20,9 @@ const checkSigner = async (signer: TransactionSigner, privateKey?: string): Prom assert.isObject(rawData) - await signer.sign(privateKey ?? senderPrivateKey) + await signer.sign(privateKey ?? senderPrivateKey, ECDSA.secp256k1) - assert.isString(signer.getSignedData()) + assert.isObject(signer.getSignedData()) } const checkTx = async (transactionId: TransactionId): Promise => { @@ -42,8 +43,8 @@ describe('Coin', () => { }) it('Balance', async () => { - const balance = await coin.getBalance('tb1qc240vx54n08hnhx8l4rqxjzcxf4f0ssq5asawm') - expect(balance).toBe(0.00003) + const balance = await coin.getBalance('rsDiH2LtPbcmkbTT3iLfKcPVtCJPGXxjry') + expect(balance).toBe(100) }) it('Transfer', async () => { diff --git a/packages/networks/xrpl/tests/models.spec.ts b/packages/networks/xrpl/tests/models.spec.ts index 9c80487..8927fb6 100644 --- a/packages/networks/xrpl/tests/models.spec.ts +++ b/packages/networks/xrpl/tests/models.spec.ts @@ -3,10 +3,10 @@ import { Transaction } from '../src/models/Transaction' import { CoinTransaction } from '../src/models/CoinTransaction' import { AssetDirectionEnum, TransactionStatusEnum } from '@multiplechain/types' -const testAmount = Number(process.env.XRP_TRANSFER_AMOUNT) +const testAmount = 0.001 const senderTestAddress = String(process.env.XRP_SENDER_ADDRESS) const receiverTestAddress = String(process.env.XRP_RECEIVER_ADDRESS) -const txId = '335c8a251e5f18121977c3159f46983d5943325abccc19e4718c49089553d60c' +const txId = '8F0EFC4482865B47B156F1D001B4EAB3DC0B0C7231AE99377B634B1E3FA1563B' describe('Transaction', () => { const tx = new Transaction(txId) @@ -23,7 +23,7 @@ describe('Transaction', () => { }) it('URL', async () => { - expect(tx.getUrl()).toBe('https://blockstream.info/testnet/tx/' + txId) + expect(tx.getUrl()).toBe('https://testnet.xrpl.org/transactions/' + txId) }) it('Sender', async () => { @@ -31,15 +31,15 @@ describe('Transaction', () => { }) it('Fee', async () => { - expect(await tx.getFee()).toBe(0.00014) + expect(await tx.getFee()).toBe(0.000012) }) it('Block Number', async () => { - expect(await tx.getBlockNumber()).toBe(2814543) + expect(await tx.getBlockNumber()).toBe(4436513) }) it('Block Timestamp', async () => { - expect(await tx.getBlockTimestamp()).toBe(1715328679) + expect(await tx.getBlockTimestamp()).toBe(791630821) }) it('Block Confirmation Count', async () => { diff --git a/packages/networks/xrpl/tests/setup.ts b/packages/networks/xrpl/tests/setup.ts index 476b875..545fc90 100644 --- a/packages/networks/xrpl/tests/setup.ts +++ b/packages/networks/xrpl/tests/setup.ts @@ -2,11 +2,21 @@ import { Provider } from '../src/services/Provider' let provider: Provider +// Mainnet +// wss://xrplcluster.com +// https://xrplcluster.com + +// Testnet +// wss://s.altnet.rippletest.net:51233/ +// https://s.altnet.rippletest.net:51234/ + try { provider = Provider.instance } catch (e) { provider = new Provider({ - testnet: true + testnet: true, + rpcUrl: 'https://s.altnet.rippletest.net:51234', + wsUrl: 'wss://s.altnet.rippletest.net:51233' }) } From 353f1780a0cc73fcb36b71df0742e415dd7e8fd8 Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 31 Jan 2025 15:36:24 +0300 Subject: [PATCH 14/19] deleted --- packages/networks/xrpl/src/utils.ts | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 packages/networks/xrpl/src/utils.ts diff --git a/packages/networks/xrpl/src/utils.ts b/packages/networks/xrpl/src/utils.ts deleted file mode 100644 index 14bd3ec..0000000 --- a/packages/networks/xrpl/src/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { math } from '@multiplechain/utils' - -export * from '@multiplechain/utils' - -export const fromSatoshi = (amount: number): number => { - return math.div(amount, 100000000, 8) -} - -export const toSatoshi = (amount: number): number => { - return math.mul(amount, 100000000, 8) -} From 6b136f4b8743205dfe3bb43330ac92a665263de4 Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 31 Jan 2025 15:41:28 +0300 Subject: [PATCH 15/19] added xrpl --- .env.example | 9 ++++----- vitest.config.ts | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 649d3bc..64ece15 100644 --- a/.env.example +++ b/.env.example @@ -97,11 +97,10 @@ BTC_TRANSFER_AMOUNT=0.00001 #XRPl XRP_TRANSFER_TEST_IS_ACTIVE=false XRP_LISTENER_TEST_IS_ACTIVE=false -XRP_BLOCKCYPHER_TOKEN='49d43a59a4f24d31a9731eb067ab971c' -XRP_SENDER_PRIVATE_KEY='cNHUtnWqwAwGajUGjyHLNbUcfHaDC3ujqjc6qcZik5Xa58Hj46vG' -XRP_SENDER_ADDRESS='tb1q8juz7c302wdcpfz83zvvyf4jxc8sfq4wyth3pr' -XRP_RECEIVER_ADDRESS='tb1q9uxj8p043sjkm0qzlsys7677mv98j76k8cvgtg' -XRP_TRANSFER_AMOUNT=0.00001 +XRP_SENDER_PRIVATE_KEY='shV4vVLq2QZx8zopPSSp9BwaHKGan' +XRP_SENDER_ADDRESS='rMHC2kEJBYTmB8Nk37TgqV8gdxGeexpLe9' +XRP_RECEIVER_ADDRESS='r92HmWXA7dWQ6XNMMJJsbdWJfqYwLVNmGr' +XRP_TRANSFER_AMOUNT=0.001 #XRPl #Solana diff --git a/vitest.config.ts b/vitest.config.ts index 94f1351..4e21eec 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -32,6 +32,7 @@ export default mergeConfig( './packages/networks/bitcoin/tests/setup.ts', './packages/networks/solana/tests/setup.ts', './packages/networks/tron/tests/setup.ts', + './packages/networks/xrpl/tests/setup.ts', './packages/networks/ton/tests/setup.ts' ] } From 85ce6f7e8f03e0cfab35fbb66d436b2a82fc1b8e Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 31 Jan 2025 18:47:23 +0300 Subject: [PATCH 16/19] added browser side --- packages/networks/xrpl/index.example.html | 8 +- packages/networks/xrpl/src/browser/Wallet.ts | 79 ++++++-- .../xrpl/src/browser/adapters/EIP6963.ts | 25 +++ .../xrpl/src/browser/adapters/MetaMask.ts | 169 ++++++++++++++++++ .../xrpl/src/browser/adapters/icons.ts | 2 + .../xrpl/src/browser/adapters/index.ts | 1 + packages/networks/xrpl/src/browser/index.ts | 2 +- .../networks/xrpl/src/services/Provider.ts | 26 +-- .../xrpl/src/services/TransactionSigner.ts | 13 +- packages/networks/xrpl/tests/setup.ts | 12 +- 10 files changed, 296 insertions(+), 41 deletions(-) create mode 100644 packages/networks/xrpl/src/browser/adapters/EIP6963.ts create mode 100644 packages/networks/xrpl/src/browser/adapters/MetaMask.ts create mode 100644 packages/networks/xrpl/src/browser/adapters/icons.ts diff --git a/packages/networks/xrpl/index.example.html b/packages/networks/xrpl/index.example.html index 98db5bc..be17847 100644 --- a/packages/networks/xrpl/index.example.html +++ b/packages/networks/xrpl/index.example.html @@ -206,9 +206,7 @@ } const wallet = new XRPl.browser.Wallet(adapter) - const adapterProvider = await wallet.connect({ - projectId: '113d9f5689edd84ff230c2a6d679c80c' - }) + const adapterProvider = await wallet.connect() document.querySelector('.methods').style.display = 'block' @@ -228,12 +226,12 @@ document.querySelector('.action-result .sign-message').innerText = result } - const receiver = 'tb1q9uxj8p043sjkm0qzlsys7677mv98j76k8cvgtg' + const receiver = 'r92HmWXA7dWQ6XNMMJJsbdWJfqYwLVNmGr' document.querySelector('.action-result .send-coin').innerText = '' document.querySelector('.action-btn.send-coin').onclick = async () => { const coin = new XRPl.assets.Coin() - const signer = await coin.transfer(sender, receiver, 0.0001) + const signer = await coin.transfer(sender, receiver, 0.01) const result = await wallet.sendTransaction(signer) document.querySelector('.action-result .send-coin').innerText = result } diff --git a/packages/networks/xrpl/src/browser/Wallet.ts b/packages/networks/xrpl/src/browser/Wallet.ts index add2bee..dd8a7d1 100644 --- a/packages/networks/xrpl/src/browser/Wallet.ts +++ b/packages/networks/xrpl/src/browser/Wallet.ts @@ -6,7 +6,8 @@ import { type ConnectConfig, type WalletAddress, type SignedMessage, - type TransactionId + type TransactionId, + ErrorTypeEnum } from '@multiplechain/types' import { Provider } from '../services/Provider' import type { TransactionSigner } from '../services/TransactionSigner' @@ -14,15 +15,32 @@ import type { TransactionSigner } from '../services/TransactionSigner' const rejectMap = (error: any, reject: (a: any) => any): any => { console.error('MultipleChain XRPl Wallet Error:', error) + const errorMessage = String(error.message ?? '') + + if (errorMessage.includes('User rejected the request')) { + return reject(new Error(ErrorTypeEnum.WALLET_REQUEST_REJECTED)) + } + + if (errorMessage.includes('Error calling submit')) { + return reject(new Error(ErrorTypeEnum.TRANSACTION_CREATION_FAILED)) + } + return reject(error) } +export interface WalletProvider { + getAddress: () => Promise + signMessage: (message: string) => Promise + sendXrp: (to: string, amount: string) => Promise + on: (event: string, callback: (data: any) => void) => void +} + type WalletAdapter = WalletAdapterInterface -export class Wallet implements WalletInterface { +export class Wallet implements WalletInterface { adapter: WalletAdapter - walletProvider: any + walletProvider: WalletProvider networkProvider: Provider @@ -88,28 +106,45 @@ export class Wallet implements WalletInterface * @returns WalletAddress */ async connect(config?: ConnectConfig): Promise { - return await new Promise((resolve, reject) => {}) + return await new Promise((resolve, reject) => { + this.adapter + .connect(this.networkProvider, config) + .then(async (provider) => { + this.walletProvider = provider + resolve(await this.getAddress()) + }) + .catch((error) => { + const customReject = (error: any): void => { + if (error.message === ErrorTypeEnum.WALLET_REQUEST_REJECTED) { + reject(new Error(ErrorTypeEnum.WALLET_CONNECT_REJECTED)) + } else { + reject(error) + } + } + rejectMap(error, customReject) + }) + }) } /** * @returns wallet detection status */ async isDetected(): Promise { - return this.adapter.isDetected() + return await this.adapter.isDetected() } /** * @returns connection status */ async isConnected(): Promise { - return this.adapter.isConnected() + return await this.adapter.isConnected() } /** * @returns wallet address */ async getAddress(): Promise { - return this.walletProvider.getAddress() + return await this.walletProvider.getAddress() } /** @@ -117,7 +152,16 @@ export class Wallet implements WalletInterface * @returns signed message */ async signMessage(message: string): Promise { - return await new Promise((resolve, reject) => {}) + return await new Promise((resolve, reject) => { + this.walletProvider + .signMessage(message) + .then((signature) => { + resolve(signature) + }) + .catch((error) => { + rejectMap(error, reject) + }) + }) } /** @@ -125,12 +169,27 @@ export class Wallet implements WalletInterface * @returns transaction id */ async sendTransaction(transactionSigner: TransactionSigner): Promise { - return await new Promise((resolve, reject) => {}) + const data = transactionSigner.getRawData() + return await new Promise((resolve, reject) => { + if (!data.Destination || !data.Amount) { + throw new Error('Invalid transaction data') + } + this.walletProvider + .sendXrp(data.Destination, data.Amount) + .then((txHash) => { + resolve(txHash) + }) + .catch((error) => { + rejectMap(error, reject) + }) + }) } /** * @param eventName event name * @param callback event callback */ - on(eventName: string, callback: (...args: any[]) => void): void {} + on(eventName: string, callback: (...args: any[]) => void): void { + this.walletProvider.on(eventName, callback) + } } diff --git a/packages/networks/xrpl/src/browser/adapters/EIP6963.ts b/packages/networks/xrpl/src/browser/adapters/EIP6963.ts new file mode 100644 index 0000000..861bbcd --- /dev/null +++ b/packages/networks/xrpl/src/browser/adapters/EIP6963.ts @@ -0,0 +1,25 @@ +export interface EIP1193Provider { + request: (payload: { method: string; params?: any[] | object }) => Promise + on: (eventName: string, callback: (...args: any[]) => void) => void +} + +export interface EIP6963ProviderInfo { + uuid: string + name: string + icon: string + rdns?: string +} + +export interface EIP6963ProviderDetail { + info: EIP6963ProviderInfo + provider: EIP1193Provider +} + +export interface EVMProviderDetected extends EIP6963ProviderDetail { + accounts: string[] + request?: EIP1193Provider['request'] +} + +export interface EIP6963AnnounceProviderEvent extends Event { + detail: EIP6963ProviderDetail +} diff --git a/packages/networks/xrpl/src/browser/adapters/MetaMask.ts b/packages/networks/xrpl/src/browser/adapters/MetaMask.ts new file mode 100644 index 0000000..9eb56f4 --- /dev/null +++ b/packages/networks/xrpl/src/browser/adapters/MetaMask.ts @@ -0,0 +1,169 @@ +import { metaMask } from './icons' +import type { WalletProvider } from '../Wallet' +import type { EIP1193Provider } from './EIP6963' +import type { Provider } from '../../services/Provider' +import { ErrorTypeEnum, WalletPlatformEnum } from '@multiplechain/types' +import type { WalletAdapterInterface } from '@multiplechain/types' + +export interface WindowEthereum extends EIP1193Provider { + isTrust?: boolean + isTronLink?: boolean + isMetaMask?: boolean +} + +declare global { + interface Window { + ethereum: WindowEthereum + } +} + +interface NetworkInfo { + chainId: number + explorerUrl: string + name: string + nodeUrl: string +} + +const MetaMask: WalletAdapterInterface = { + id: 'metamask', + name: 'MetaMask Snap', + icon: metaMask, + downloadLink: 'https://metamask.io/download/', + platforms: [WalletPlatformEnum.BROWSER, WalletPlatformEnum.MOBILE], + isDetected: () => { + return Boolean((window?.ethereum as unknown as WindowEthereum)?.isMetaMask) + }, + createDeepLink: (url: string): string => `https://metamask.app.link/dapp/${url}`, + isConnected: async () => { + return Boolean( + ( + await (window?.ethereum as unknown as WindowEthereum).request({ + method: 'eth_accounts' + }) + ).length + ) + }, + connect: async (provider?: Provider): Promise => { + return await new Promise((resolve, reject) => { + if (provider === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_REQUIRED) + } + + const chainId = provider !== undefined && provider?.isTestnet() ? 1 : 0 + + const metamaskProvider = window?.ethereum as unknown as WindowEthereum + try { + const walletProvider: WalletProvider = { + getAddress: async (): Promise => { + const result = await metamaskProvider.request({ + method: 'wallet_invokeSnap', + params: { + snapId: 'npm:xrpl-snap', + request: { + method: 'xrpl_getAccount' + } + } + }) + return result.account + }, + signMessage: async (message: string): Promise => { + const { signature } = await metamaskProvider.request({ + method: 'wallet_invokeSnap', + params: { + snapId: 'npm:xrpl-snap', + request: { + method: 'xrpl_signMessage', + params: { + message + } + } + } + }) + return signature + }, + sendXrp: async (to: string, amount: string): Promise => { + const result = await metamaskProvider.request({ + method: 'wallet_invokeSnap', + params: { + snapId: 'npm:xrpl-snap', + request: { + method: 'xrpl_sign', + params: { + Amount: amount, + Destination: to, + TransactionType: 'Payment', + Account: await walletProvider.getAddress() + } + } + } + }) + await provider.ws.connect() + await provider.ws.submit(result.tx_blob) // eslint-disable-line + return result.hash + }, + on: (event: string, callback: (data: any) => void) => { + metamaskProvider.on(event, callback) + } + } + + const getCurrentNetwork = async (): Promise => { + return await metamaskProvider.request({ + method: 'wallet_invokeSnap', + params: { + snapId: 'npm:xrpl-snap', + request: { + method: 'xrpl_getActiveNetwork' + } + } + }) + } + + const changeNetwork = async (chainId: number): Promise => { + try { + await metamaskProvider.request({ + method: 'wallet_invokeSnap', + params: { + snapId: 'npm:xrpl-snap', + request: { + method: 'xrpl_changeNetwork', + params: { + chainId + } + } + } + }) + } catch (error) { + reject(error) + } + } + + const connect = async (): Promise => { + try { + await metamaskProvider.request({ + method: 'wallet_requestSnaps', + params: { + 'npm:xrpl-snap': {} + } + }) + + void getCurrentNetwork().then(async (network) => { + if (network.chainId !== chainId) { + await changeNetwork(chainId) + } + + resolve(walletProvider) + }) + } catch (error) { + reject(error) + } + } + + void connect() + } catch (error) { + reject(error) + } + }) + } +} + +export default MetaMask diff --git a/packages/networks/xrpl/src/browser/adapters/icons.ts b/packages/networks/xrpl/src/browser/adapters/icons.ts new file mode 100644 index 0000000..a939146 --- /dev/null +++ b/packages/networks/xrpl/src/browser/adapters/icons.ts @@ -0,0 +1,2 @@ +export const metaMask = + 'data:image/png;base64,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' diff --git a/packages/networks/xrpl/src/browser/adapters/index.ts b/packages/networks/xrpl/src/browser/adapters/index.ts index e69de29..9c9fd07 100644 --- a/packages/networks/xrpl/src/browser/adapters/index.ts +++ b/packages/networks/xrpl/src/browser/adapters/index.ts @@ -0,0 +1 @@ +export { default as MetaMask } from './MetaMask' diff --git a/packages/networks/xrpl/src/browser/index.ts b/packages/networks/xrpl/src/browser/index.ts index ef4b5c0..a054aab 100644 --- a/packages/networks/xrpl/src/browser/index.ts +++ b/packages/networks/xrpl/src/browser/index.ts @@ -1,5 +1,5 @@ -import type { Provider } from 'sats-connect' import { Wallet, type WalletProvider } from './Wallet' +import type { Provider } from '../services/Provider' import * as adapterList from './adapters/index' import type { WalletAdapterListType, diff --git a/packages/networks/xrpl/src/services/Provider.ts b/packages/networks/xrpl/src/services/Provider.ts index 01880b7..2a60f24 100644 --- a/packages/networks/xrpl/src/services/Provider.ts +++ b/packages/networks/xrpl/src/services/Provider.ts @@ -23,6 +23,12 @@ export class Provider implements ProviderInterface { testnetWs = 'wss://s.altnet.rippletest.net:51233' + testnetRpc2 = + 'https://young-multi-liquid.xrp-testnet.quiknode.pro/d8af58c32952286972ad82bc5dc1156e70c4a2a0' + + testnetWs2 = + 'wss://young-multi-liquid.xrp-testnet.quiknode.pro/d8af58c32952286972ad82bc5dc1156e70c4a2a0' + mainnetRpc = 'https://xrplcluster.com' mainnetWs = 'wss://xrplcluster.com' @@ -115,21 +121,21 @@ export class Provider implements ProviderInterface { update(network: NetworkConfigInterface): void { this.network = network Provider._instance = this - if (!network.wsUrl) { - throw new Error(ErrorTypeEnum.WS_URL_NOT_DEFINED) - } - if (!network.rpcUrl) { - throw new Error('RPC URL is not defined') - } - this.ws = new WsClient(network.wsUrl) - this.rpc = new Client(network.rpcUrl) this.explorer = network.testnet ? 'https://testnet.xrpl.org/' : 'https://livenet.xrpl.org/' if (!network.rpcUrl) { - this.network.rpcUrl = network.testnet ? this.testnetRpc : this.mainnetRpc + this.network.rpcUrl = network.testnet ? this.testnetRpc2 : this.mainnetRpc } if (!network.wsUrl) { - this.network.wsUrl = network.testnet ? this.testnetWs : this.mainnetWs + this.network.wsUrl = network.testnet ? this.testnetWs2 : this.mainnetWs + } + if (!this.network.wsUrl) { + throw new Error(ErrorTypeEnum.WS_URL_NOT_DEFINED) + } + if (!this.network.rpcUrl) { + throw new Error('RPC URL is not defined') } + this.ws = new WsClient(this.network.wsUrl) + this.rpc = new Client(this.network.rpcUrl) } /** diff --git a/packages/networks/xrpl/src/services/TransactionSigner.ts b/packages/networks/xrpl/src/services/TransactionSigner.ts index f548742..ecbbcaf 100644 --- a/packages/networks/xrpl/src/services/TransactionSigner.ts +++ b/packages/networks/xrpl/src/services/TransactionSigner.ts @@ -7,13 +7,18 @@ export interface SignedTransaction { hash: string } +export type RawTransaction = SubmittableTransaction & { + Destination?: string + Amount?: string +} + export class TransactionSigner - implements TransactionSignerInterface + implements TransactionSignerInterface { /** * Transaction data from the blockchain network */ - rawData: SubmittableTransaction + rawData: RawTransaction /** * Signed transaction data @@ -29,7 +34,7 @@ export class TransactionSigner * @param rawData - Transaction data * @param provider - Blockchain network provider */ - constructor(rawData: SubmittableTransaction, provider?: Provider) { + constructor(rawData: RawTransaction, provider?: Provider) { this.rawData = rawData this.provider = provider ?? Provider.instance } @@ -78,7 +83,7 @@ export class TransactionSigner * Get the raw transaction data * @returns Transaction data */ - getRawData(): SubmittableTransaction { + getRawData(): RawTransaction { return this.rawData } diff --git a/packages/networks/xrpl/tests/setup.ts b/packages/networks/xrpl/tests/setup.ts index 545fc90..476b875 100644 --- a/packages/networks/xrpl/tests/setup.ts +++ b/packages/networks/xrpl/tests/setup.ts @@ -2,21 +2,11 @@ import { Provider } from '../src/services/Provider' let provider: Provider -// Mainnet -// wss://xrplcluster.com -// https://xrplcluster.com - -// Testnet -// wss://s.altnet.rippletest.net:51233/ -// https://s.altnet.rippletest.net:51234/ - try { provider = Provider.instance } catch (e) { provider = new Provider({ - testnet: true, - rpcUrl: 'https://s.altnet.rippletest.net:51234', - wsUrl: 'wss://s.altnet.rippletest.net:51233' + testnet: true }) } From 70732b78d02ea469fd3f6037f8c05c61e8098f85 Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 31 Jan 2025 18:47:57 +0300 Subject: [PATCH 17/19] added disconnect --- packages/networks/xrpl/src/browser/adapters/MetaMask.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/networks/xrpl/src/browser/adapters/MetaMask.ts b/packages/networks/xrpl/src/browser/adapters/MetaMask.ts index 9eb56f4..2ffc73f 100644 --- a/packages/networks/xrpl/src/browser/adapters/MetaMask.ts +++ b/packages/networks/xrpl/src/browser/adapters/MetaMask.ts @@ -99,6 +99,7 @@ const MetaMask: WalletAdapterInterface = { }) await provider.ws.connect() await provider.ws.submit(result.tx_blob) // eslint-disable-line + await provider.ws.disconnect() return result.hash }, on: (event: string, callback: (data: any) => void) => { From 274295d8511833b16329c93e5479a76695734cfc Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 31 Jan 2025 20:22:49 +0300 Subject: [PATCH 18/19] added gem wallet --- packages/networks/xrpl/package.json | 2 + packages/networks/xrpl/pnpm-lock.yaml | 16 +++ .../xrpl/src/browser/adapters/GemWallet.ts | 118 ++++++++++++++++++ .../xrpl/src/browser/adapters/icons.ts | 3 + .../xrpl/src/browser/adapters/index.ts | 1 + 5 files changed, 140 insertions(+) create mode 100644 packages/networks/xrpl/src/browser/adapters/GemWallet.ts diff --git a/packages/networks/xrpl/package.json b/packages/networks/xrpl/package.json index 7caa7e7..a2905c0 100644 --- a/packages/networks/xrpl/package.json +++ b/packages/networks/xrpl/package.json @@ -72,8 +72,10 @@ "url": "https://github.com/MultipleChain/js/issues" }, "dependencies": { + "@gemwallet/api": "^3.8.0", "@multiplechain/types": "^0.1.70", "@multiplechain/utils": "^0.1.21", + "add": "^2.0.6", "axios": "^1.7.9", "xrpl": "^4.1.0" } diff --git a/packages/networks/xrpl/pnpm-lock.yaml b/packages/networks/xrpl/pnpm-lock.yaml index cc113ed..d8cedc4 100644 --- a/packages/networks/xrpl/pnpm-lock.yaml +++ b/packages/networks/xrpl/pnpm-lock.yaml @@ -8,12 +8,18 @@ importers: .: dependencies: + '@gemwallet/api': + specifier: ^3.8.0 + version: 3.8.0 '@multiplechain/types': specifier: ^0.1.70 version: 0.1.70 '@multiplechain/utils': specifier: ^0.1.21 version: 0.1.23 + add: + specifier: ^2.0.6 + version: 2.0.6 axios: specifier: ^1.7.9 version: 1.7.9 @@ -23,6 +29,9 @@ importers: packages: + '@gemwallet/api@3.8.0': + resolution: {integrity: sha512-hZ6XC0mVm3Q54cgonrzk6tHS/wUMjtPHyqsqbtlnNGPouCR7OIfEDo5Y802qLZ5ah6PskhsK0DouVnwUykEM8Q==} + '@multiplechain/types@0.1.70': resolution: {integrity: sha512-o9ovdaefDHE5gorb83avugCjeixfBXrfJkIgSEEcT549yGF8CkmG/jgZIlqXKJf8Lh0tbg4zMAAanUSxUqV1FQ==} @@ -58,6 +67,9 @@ packages: '@xrplf/secret-numbers@1.0.0': resolution: {integrity: sha512-qsCLGyqe1zaq9j7PZJopK+iGTGRbk6akkg6iZXJJgxKwck0C5x5Gnwlb1HKYGOwPKyrXWpV6a2YmcpNpUFctGg==} + add@2.0.6: + resolution: {integrity: sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -273,6 +285,8 @@ packages: snapshots: + '@gemwallet/api@3.8.0': {} + '@multiplechain/types@0.1.70': {} '@multiplechain/utils@0.1.23': @@ -329,6 +343,8 @@ snapshots: - bufferutil - utf-8-validate + add@2.0.6: {} + asynckit@0.4.0: {} available-typed-arrays@1.0.7: diff --git a/packages/networks/xrpl/src/browser/adapters/GemWallet.ts b/packages/networks/xrpl/src/browser/adapters/GemWallet.ts new file mode 100644 index 0000000..455d734 --- /dev/null +++ b/packages/networks/xrpl/src/browser/adapters/GemWallet.ts @@ -0,0 +1,118 @@ +import { gemWallet } from './icons' +import type { WalletProvider } from '../Wallet' +import type { Provider } from '../../services/Provider' +import type { WalletAdapterInterface } from '@multiplechain/types' +import { ErrorTypeEnum, WalletPlatformEnum } from '@multiplechain/types' +import { getAddress, getNetwork, isInstalled, on, sendPayment, signMessage } from '@gemwallet/api' + +declare global { + interface Window { + gemWallet: boolean + } +} + +let address: string | undefined + +const GemWallet: WalletAdapterInterface = { + id: 'gem-wallet', + name: 'GemWallet', + icon: gemWallet, + downloadLink: + 'https://chromewebstore.google.com/detail/gemwallet/egebedonbdapoieedfcfkofloclfghab', + platforms: [WalletPlatformEnum.UNIVERSAL], + isDetected: () => true, + isConnected: () => window?.gemWallet, + connect: async (provider?: Provider): Promise => { + return await new Promise((resolve, reject) => { + if (provider === undefined) { + throw new Error(ErrorTypeEnum.PROVIDER_IS_REQUIRED) + } + + const chainId = provider !== undefined && provider?.isTestnet() ? 'Testnet' : 'Mainnet' + + try { + const walletProvider: WalletProvider = { + getAddress: async (): Promise => { + return address ?? '' + }, + signMessage: async (message: string): Promise => { + return await new Promise((resolve, reject) => { + signMessage(message) + .then(({ result }) => { + if (result?.signedMessage) { + resolve(result.signedMessage) + } else { + reject(new Error('Signing failed')) + } + }) + .catch(reject) + }) + }, + sendXrp: async (to: string, amount: string): Promise => { + return await new Promise((resolve, reject) => { + sendPayment({ + destination: to, + amount + }) + .then((response) => { + if (response.result?.hash) { + resolve(response.result.hash) + } else { + if (response.type === 'reject') { + reject(new Error(ErrorTypeEnum.WALLET_REQUEST_REJECTED)) + } else { + reject( + new Error(ErrorTypeEnum.TRANSACTION_CREATION_FAILED) + ) + } + } + }) + .catch(reject) + }) + }, + on + } + + const connect = async (): Promise => { + void isInstalled() + .then((res) => { + if (res.result.isInstalled) { + getAddress() + .then(({ result }) => { + if (result?.address) { + address = result.address + void getNetwork() + .then(({ result }) => { + if (result?.network === chainId) { + resolve(walletProvider) + } else { + reject( + new Error( + ErrorTypeEnum.UNACCEPTED_CHAIN + ) + ) + } + }) + .catch(reject) + } else { + reject( + new Error(ErrorTypeEnum.WALLET_CONNECTION_FAILED) + ) + } + }) + .catch(reject) + } else { + reject(new Error('GemWallet is not installed')) + } + }) + .catch(reject) + } + void connect() + } catch (error) { + reject(error) + } + }) + } +} + +export default GemWallet diff --git a/packages/networks/xrpl/src/browser/adapters/icons.ts b/packages/networks/xrpl/src/browser/adapters/icons.ts index a939146..22c71b9 100644 --- a/packages/networks/xrpl/src/browser/adapters/icons.ts +++ b/packages/networks/xrpl/src/browser/adapters/icons.ts @@ -1,2 +1,5 @@ export const metaMask = 'data:image/png;base64,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' + +export const gemWallet = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwQThFQSIgZD0iTTIwIDM5LjkxMS41OTMgMTcuNDIyaDM4LjgxNHoiLz48cGF0aCBmaWxsPSIjMzNEM0Y0IiBkPSJNMzMuMTg1IDUuMzMzSDYuODE1TC41OTMgMTcuNDIzaDM4LjgxNHoiLz48cGF0aCBmaWxsPSIjNDBFRUZGIiBkPSJtMjAgMzkuOTExLTcuMDM3LTIyLjQ4OWgxNC4wNzR6TTE0LjIyMiAxNC40IDguNjY3IDUuMzMzSDIwem0xMS4yNTkgMEwyMCA1LjMzM2gxMS4zMzN6Ii8+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTYuMjk2IDYuNDg5IDMuNDA3IDUuMzMzbDIuODktMS4xNTVMNy4yNTguNzFsLjk2MyAzLjQ2NyAyLjg5IDEuMTU1LTIuODkgMS4xNTYtLjk2MyAzLjQ2N3ptMjcuNDA4IDExLjI4OS0xLjg1Mi0uNzExIDEuODUyLS44LjY2Ni0yLjIyMy41OTMgMi4yMjMgMS44NTIuOC0xLjg1Mi43MUwzNC4zNyAyMHoiLz48cGF0aCBkPSJNMjEuODUyIDUuMzMzIDYuMjk2IDI0LjA5bC0xLjMzMy0xLjUxMSAxNC4zNy0xNy4yNDV6bTguNzQxIDBMMTAuNzQgMjkuMTU2IDcuNjMgMjUuNiAyNC40NDQgNS4zMzN6IiBvcGFjaXR5PSIuMiIgZmlsbD0iI0ZGRiIvPjwvZz48L3N2Zz4=' diff --git a/packages/networks/xrpl/src/browser/adapters/index.ts b/packages/networks/xrpl/src/browser/adapters/index.ts index 9c9fd07..ca6f997 100644 --- a/packages/networks/xrpl/src/browser/adapters/index.ts +++ b/packages/networks/xrpl/src/browser/adapters/index.ts @@ -1 +1,2 @@ export { default as MetaMask } from './MetaMask' +export { default as GemWallet } from './GemWallet' From ade9efd98ad7775a2de6006bdddbbe41ef124984 Mon Sep 17 00:00:00 2001 From: BeycanDeveloper Date: Fri, 31 Jan 2025 20:46:05 +0300 Subject: [PATCH 19/19] fix --- packages/networks/xrpl/src/models/Transaction.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/networks/xrpl/src/models/Transaction.ts b/packages/networks/xrpl/src/models/Transaction.ts index aa87f57..ec31f98 100644 --- a/packages/networks/xrpl/src/models/Transaction.ts +++ b/packages/networks/xrpl/src/models/Transaction.ts @@ -1,5 +1,7 @@ +import { sleep } from '@multiplechain/utils' import { Provider } from '../services/Provider' import { ErrorTypeEnum, TransactionStatusEnum } from '@multiplechain/types' +import { dropsToXrp, type Transaction as BaseTransactionData, type TransactionMetadata } from 'xrpl' import { TransactionTypeEnum, type BlockConfirmationCount, @@ -10,7 +12,6 @@ import { type TransactionInterface, type WalletAddress } from '@multiplechain/types' -import { dropsToXrp, type Transaction as BaseTransactionData, type TransactionMetadata } from 'xrpl' export type TransactionData = BaseTransactionData & { meta?: TransactionMetadata @@ -20,6 +21,8 @@ export type TransactionData = BaseTransactionData & { date?: number } +let counter = 0 + export class Transaction implements TransactionInterface { /** * Each transaction has its own unique ID defined by the user @@ -56,6 +59,15 @@ export class Transaction implements TransactionInterface { return (this.data = await this.provider.rpc.getTransaction(this.id)) } catch (error) { console.error('MC XRPl TX getData', error) + // Returns empty data when the transaction is first created. For this reason, it would be better to check it intermittently and give an error if it still does not exist. Average 10 seconds. + if (String((error as any).message).includes('Transaction not found')) { + if (counter > 5) { + throw new Error(ErrorTypeEnum.TRANSACTION_NOT_FOUND) + } + counter++ + await sleep(2000) + return await this.getData() + } throw new Error(ErrorTypeEnum.RPC_REQUEST_ERROR) } }