From 8286aa7344346bcc04d5cb09125bac9aac1685cc Mon Sep 17 00:00:00 2001 From: Muzaffar Ahmad Bhat <90454199+muzaffarbhat07@users.noreply.github.com> Date: Thu, 7 Nov 2024 11:35:13 +0530 Subject: [PATCH] feat: Adds xrp support (#563) Co-authored-by: TejasvOnly --- .changeset/seven-planets-cough.md | 11 + apps/cli/package.json | 4 +- apps/cli/src/utils/baseCommand.ts | 3 + packages/coin-support-xrp/.eslintrc.js | 8 + packages/coin-support-xrp/.gitignore | 3 + packages/coin-support-xrp/.prettierrc | 1 + packages/coin-support-xrp/README.md | 3 + packages/coin-support-xrp/jest.config.js | 5 + packages/coin-support-xrp/package.json | 66 +++ packages/coin-support-xrp/src/config.ts | 5 + packages/coin-support-xrp/src/index.ts | 122 +++++ .../operations/broadcastTransaction/index.ts | 83 ++++ .../operations/broadcastTransaction/types.ts | 8 + .../src/operations/createAccounts/index.ts | 113 +++++ .../createAccounts/schemes/index.ts | 19 + .../createAccounts/schemes/types.ts | 12 + .../src/operations/createAccounts/types.ts | 22 + .../src/operations/getAccountHistory/index.ts | 12 + .../operations/getCoinAllocations/index.ts | 15 + .../src/operations/getExplorerLink/index.ts | 18 + .../coin-support-xrp/src/operations/index.ts | 13 + .../operations/initializeTransaction/index.ts | 42 ++ .../operations/prepareTransaction/index.ts | 149 +++++++ .../operations/prepareTransaction/types.ts | 8 + .../src/operations/receive/index.ts | 60 +++ .../src/operations/receive/types.ts | 20 + .../src/operations/signTransaction/index.ts | 114 +++++ .../src/operations/signTransaction/types.ts | 20 + .../src/operations/syncAccount/index.ts | 169 +++++++ .../src/operations/syncAccount/types.ts | 3 + .../operations/syncPriceHistories/index.ts | 10 + .../src/operations/syncPrices/index.ts | 10 + .../src/operations/transaction.ts | 37 ++ .../coin-support-xrp/src/operations/types.ts | 6 + .../src/operations/validateAddress/index.ts | 13 + .../src/services/api/account.ts | 68 +++ .../src/services/api/index.ts | 3 + .../src/services/api/transaction.ts | 99 ++++ .../src/services/api/types.ts | 43 ++ .../coin-support-xrp/src/services/index.ts | 1 + packages/coin-support-xrp/src/utils/app.ts | 5 + .../src/utils/deriveAddress.ts | 4 + .../coin-support-xrp/src/utils/getCoinIds.ts | 18 + packages/coin-support-xrp/src/utils/index.ts | 5 + packages/coin-support-xrp/src/utils/logger.ts | 20 + packages/coin-support-xrp/src/utils/xrpLib.ts | 16 + packages/coin-support-xrp/tests/01.create.ts | 9 + .../coin-support-xrp/tsconfig.eslint.json | 9 + packages/coin-support-xrp/tsconfig.json | 15 + packages/coin-support-xrp/tsconfig_cjs.json | 16 + packages/coin-support/package.json | 1 + packages/coin-support/src/index.ts | 2 + packages/coin-support/tests/index.ts | 2 +- packages/coins/src/aggregate.ts | 9 +- packages/coins/src/index.ts | 1 + packages/coins/src/types.ts | 1 + packages/coins/src/xrp/coins.ts | 28 ++ packages/coins/src/xrp/index.ts | 40 ++ packages/coins/tests/01.uniqueIds.ts | 14 +- .../src/i18n/lang/ar-AE.json | 16 +- .../src/i18n/lang/de-DE.json | 16 +- .../src/i18n/lang/en.json | 16 +- .../src/i18n/lang/id-ID.json | 16 +- .../src/i18n/lang/zh-CN.json | 16 +- .../cysync-core-constants/src/i18n/types.ts | 6 + packages/cysync-core/package.json | 4 +- .../cysync-core/scripts/dependencies/index.js | 1 + .../cysync-core/src/components/CoinIcon.tsx | 10 +- .../src/components/ErrorHandlerDialog.tsx | 4 +- .../Dialogs/AccountDetails.tsx | 4 +- .../cysync-core/src/dialogs/HistoryDialog.tsx | 13 + .../DestinationTagInput.tsx | 83 ++++ .../SingleTransaction.tsx | 46 ++ .../AddressAndAmountSection/index.tsx | 8 + .../Components/FeeSection/FeesHeader.tsx | 13 +- .../Components/FeeSection/FeesTitle.tsx | 17 +- .../Components/FeeSection/XrpInput.tsx | 30 ++ .../Dialogs/Components/FeeSection/index.tsx | 68 ++- .../src/dialogs/Send/Dialogs/Recipient.tsx | 23 +- .../dialogs/Send/Dialogs/SummaryDialog.tsx | 18 + .../src/dialogs/Send/context/index.tsx | 55 +++ .../src/dialogs/Send/hooks/useLabelSuffix.ts | 1 + .../cysync-core/src/hooks/useTransactions.tsx | 4 + .../src/utils/dependencies/withoutCss.ts | 2 + packages/ui/icons/xrp-icon.svg | 8 + pnpm-lock.yaml | 422 ++++++++++++++---- scripts/clean.js | 2 + submodules/sdk | 2 +- 88 files changed, 2443 insertions(+), 117 deletions(-) create mode 100644 .changeset/seven-planets-cough.md create mode 100644 packages/coin-support-xrp/.eslintrc.js create mode 100644 packages/coin-support-xrp/.gitignore create mode 100644 packages/coin-support-xrp/.prettierrc create mode 100644 packages/coin-support-xrp/README.md create mode 100644 packages/coin-support-xrp/jest.config.js create mode 100644 packages/coin-support-xrp/package.json create mode 100644 packages/coin-support-xrp/src/config.ts create mode 100644 packages/coin-support-xrp/src/index.ts create mode 100644 packages/coin-support-xrp/src/operations/broadcastTransaction/index.ts create mode 100644 packages/coin-support-xrp/src/operations/broadcastTransaction/types.ts create mode 100644 packages/coin-support-xrp/src/operations/createAccounts/index.ts create mode 100644 packages/coin-support-xrp/src/operations/createAccounts/schemes/index.ts create mode 100644 packages/coin-support-xrp/src/operations/createAccounts/schemes/types.ts create mode 100644 packages/coin-support-xrp/src/operations/createAccounts/types.ts create mode 100644 packages/coin-support-xrp/src/operations/getAccountHistory/index.ts create mode 100644 packages/coin-support-xrp/src/operations/getCoinAllocations/index.ts create mode 100644 packages/coin-support-xrp/src/operations/getExplorerLink/index.ts create mode 100644 packages/coin-support-xrp/src/operations/index.ts create mode 100644 packages/coin-support-xrp/src/operations/initializeTransaction/index.ts create mode 100644 packages/coin-support-xrp/src/operations/prepareTransaction/index.ts create mode 100644 packages/coin-support-xrp/src/operations/prepareTransaction/types.ts create mode 100644 packages/coin-support-xrp/src/operations/receive/index.ts create mode 100644 packages/coin-support-xrp/src/operations/receive/types.ts create mode 100644 packages/coin-support-xrp/src/operations/signTransaction/index.ts create mode 100644 packages/coin-support-xrp/src/operations/signTransaction/types.ts create mode 100644 packages/coin-support-xrp/src/operations/syncAccount/index.ts create mode 100644 packages/coin-support-xrp/src/operations/syncAccount/types.ts create mode 100644 packages/coin-support-xrp/src/operations/syncPriceHistories/index.ts create mode 100644 packages/coin-support-xrp/src/operations/syncPrices/index.ts create mode 100644 packages/coin-support-xrp/src/operations/transaction.ts create mode 100644 packages/coin-support-xrp/src/operations/types.ts create mode 100644 packages/coin-support-xrp/src/operations/validateAddress/index.ts create mode 100644 packages/coin-support-xrp/src/services/api/account.ts create mode 100644 packages/coin-support-xrp/src/services/api/index.ts create mode 100644 packages/coin-support-xrp/src/services/api/transaction.ts create mode 100644 packages/coin-support-xrp/src/services/api/types.ts create mode 100644 packages/coin-support-xrp/src/services/index.ts create mode 100644 packages/coin-support-xrp/src/utils/app.ts create mode 100644 packages/coin-support-xrp/src/utils/deriveAddress.ts create mode 100644 packages/coin-support-xrp/src/utils/getCoinIds.ts create mode 100644 packages/coin-support-xrp/src/utils/index.ts create mode 100644 packages/coin-support-xrp/src/utils/logger.ts create mode 100644 packages/coin-support-xrp/src/utils/xrpLib.ts create mode 100644 packages/coin-support-xrp/tests/01.create.ts create mode 100644 packages/coin-support-xrp/tsconfig.eslint.json create mode 100644 packages/coin-support-xrp/tsconfig.json create mode 100644 packages/coin-support-xrp/tsconfig_cjs.json create mode 100644 packages/coins/src/xrp/coins.ts create mode 100644 packages/coins/src/xrp/index.ts create mode 100644 packages/cysync-core/src/dialogs/Send/Dialogs/Components/AddressAndAmountSection/DestinationTagInput.tsx create mode 100644 packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/XrpInput.tsx create mode 100644 packages/ui/icons/xrp-icon.svg diff --git a/.changeset/seven-planets-cough.md b/.changeset/seven-planets-cough.md new file mode 100644 index 000000000..882052293 --- /dev/null +++ b/.changeset/seven-planets-cough.md @@ -0,0 +1,11 @@ +--- +'@cypherock/coin-support-xrp': major +'@cypherock/cysync-core-constants': patch +'@cypherock/coin-support': patch +'@cypherock/cysync-core': patch +'@cypherock/coins': patch +'@cypherock/cysync-ui': patch +'@cypherock/cysync-cli': patch +--- + +Added XRP support diff --git a/apps/cli/package.json b/apps/cli/package.json index 6032eafcb..8a808ce81 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -34,6 +34,7 @@ "@cypherock/coin-support-interfaces": "workspace:^", "@cypherock/coin-support-near": "workspace:^", "@cypherock/coin-support-solana": "workspace:^", + "@cypherock/coin-support-xrp": "workspace:^", "@cypherock/coin-support-utils": "workspace:^", "@cypherock/coins": "workspace:^", "@cypherock/cysync-core-services": "workspace:^", @@ -59,7 +60,8 @@ "near-api-js": "^2.1.4", "ora": "^6.3.1", "rxjs": "^7.8.1", - "semver": "^7.5.3" + "semver": "^7.5.3", + "xrpl": "^4.0.0" }, "devDependencies": { "@cypherock/eslint-config": "workspace:^", diff --git a/apps/cli/src/utils/baseCommand.ts b/apps/cli/src/utils/baseCommand.ts index ae3fd19b4..be6ad5adc 100644 --- a/apps/cli/src/utils/baseCommand.ts +++ b/apps/cli/src/utils/baseCommand.ts @@ -3,6 +3,7 @@ import { BtcSupport } from '@cypherock/coin-support-btc'; import { EvmSupport } from '@cypherock/coin-support-evm'; import { NearSupport } from '@cypherock/coin-support-near'; import { SolanaSupport } from '@cypherock/coin-support-solana'; +import { XrpSupport } from '@cypherock/coin-support-xrp'; import { IDatabase, IKeyValueStore } from '@cypherock/db-interfaces'; import { IDeviceConnection } from '@cypherock/sdk-interfaces'; import { Command, Flags, Interfaces } from '@oclif/core'; @@ -11,6 +12,7 @@ import * as bitcoin from 'bitcoinjs-lib'; import * as eip712 from 'eip-712'; import { ethers } from 'ethers'; import * as nearApiJs from 'near-api-js'; +import * as xrpl from 'xrpl'; import { initializeAndGetDb } from './db'; import { cleanUpDeviceConnection, createConnection } from './device'; @@ -94,6 +96,7 @@ export abstract class BaseCommand extends Command { EvmSupport.setEip712Library(eip712); NearSupport.setNearApiJs(nearApiJs); SolanaSupport.setWeb3Library(solanaWeb3); + XrpSupport.setXrpLib(xrpl); } protected async catch(err: Error & { exitCode?: number }): Promise { diff --git a/packages/coin-support-xrp/.eslintrc.js b/packages/coin-support-xrp/.eslintrc.js new file mode 100644 index 000000000..0eab828b7 --- /dev/null +++ b/packages/coin-support-xrp/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + root: true, + extends: ['@cypherock/eslint-config/browser'], + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.eslint.json'], + }, +}; diff --git a/packages/coin-support-xrp/.gitignore b/packages/coin-support-xrp/.gitignore new file mode 100644 index 000000000..49a97d4c8 --- /dev/null +++ b/packages/coin-support-xrp/.gitignore @@ -0,0 +1,3 @@ +.turbo +dist +coverage diff --git a/packages/coin-support-xrp/.prettierrc b/packages/coin-support-xrp/.prettierrc new file mode 100644 index 000000000..3add8a130 --- /dev/null +++ b/packages/coin-support-xrp/.prettierrc @@ -0,0 +1 @@ +"@cypherock/prettier-config" diff --git a/packages/coin-support-xrp/README.md b/packages/coin-support-xrp/README.md new file mode 100644 index 000000000..799af38d0 --- /dev/null +++ b/packages/coin-support-xrp/README.md @@ -0,0 +1,3 @@ +# Coin Support XRP + +- All coin operations related to XRP diff --git a/packages/coin-support-xrp/jest.config.js b/packages/coin-support-xrp/jest.config.js new file mode 100644 index 000000000..b81cbd01f --- /dev/null +++ b/packages/coin-support-xrp/jest.config.js @@ -0,0 +1,5 @@ +const baseConfig = require('@cypherock/jest-config/node'); + +module.exports = { + ...baseConfig, +}; diff --git a/packages/coin-support-xrp/package.json b/packages/coin-support-xrp/package.json new file mode 100644 index 000000000..3d36f0d35 --- /dev/null +++ b/packages/coin-support-xrp/package.json @@ -0,0 +1,66 @@ +{ + "name": "@cypherock/coin-support-xrp", + "version": "0.0.0", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "license": "AGPL-3.0", + "private": true, + "scripts": { + "lint": "eslint --ext .ts,tsx,js,jsx src/ --fix", + "lint:check": "eslint --ext .ts,tsx,js,jsx src/", + "pretty": "prettier --write \"src/**/*.ts?(x)\" \"tests/**/*.ts?(x)\"", + "pretty:check": "prettier --check \"src/**/*.ts?(x)\" \"tests/**/*.ts?(x)\"", + "build": "rimraf dist && pnpm build:esm && pnpm build:cjs", + "build:cjs": "tsc -p tsconfig_cjs.json", + "build:esm": "tsc -p tsconfig.json", + "build:dirty": "pnpm build:esm", + "test": "jest", + "pre-commit": "lint-staged" + }, + "devDependencies": { + "@cypherock/eslint-config": "workspace:^", + "@cypherock/jest-config": "workspace:^", + "@cypherock/prettier-config": "workspace:^", + "@cypherock/tsconfig": "workspace:^", + "@jest/globals": "^29.5.0", + "@stryker-mutator/core": "^7.0.2", + "@stryker-mutator/jest-runner": "^7.0.2", + "@stryker-mutator/typescript-checker": "^7.0.2", + "@types/jest": "^29.5.2", + "@types/lodash": "^4.14.195", + "@types/multicoin-address-validator": "^0.5.0", + "@types/node": "18.15.11", + "eslint": "^8.43.0", + "jest": "^29.5.0", + "lint-staged": "^13.2.2", + "prettier": "^2.8.8", + "rimraf": "^5.0.1", + "ts-jest": "^29.1.0", + "typescript": "^4.9.5", + "xrpl": "^4.0.0" + }, + "dependencies": { + "@cypherock/coin-support-interfaces": "workspace:^", + "@cypherock/coin-support-utils": "workspace:^", + "@cypherock/coins": "workspace:^", + "@cypherock/cysync-interfaces": "workspace:^", + "@cypherock/cysync-utils": "workspace:^", + "@cypherock/db-interfaces": "workspace:^", + "@cypherock/sdk-app-xrp": "^1.0.0", + "@cypherock/sdk-interfaces": "^0.0.15", + "@cypherock/sdk-utils": "^0.0.18", + "lodash": "^4.17.21", + "multicoin-address-validator": "^0.5.12", + "rxjs": "^7.8.1" + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --ext ts,tsx --quiet --fix --", + "prettier --write" + ], + "*.{js,jsx,md,mdx,mjs,yml,yaml,css,json}": [ + "prettier --write" + ] + } +} diff --git a/packages/coin-support-xrp/src/config.ts b/packages/coin-support-xrp/src/config.ts new file mode 100644 index 000000000..08f604522 --- /dev/null +++ b/packages/coin-support-xrp/src/config.ts @@ -0,0 +1,5 @@ +import { getEnvVariable } from '@cypherock/cysync-utils'; + +export const config = { + API_CYPHEROCK: getEnvVariable('API_CYPHEROCK', 'https://api.cypherock.com'), +}; diff --git a/packages/coin-support-xrp/src/index.ts b/packages/coin-support-xrp/src/index.ts new file mode 100644 index 000000000..3ee05b0b7 --- /dev/null +++ b/packages/coin-support-xrp/src/index.ts @@ -0,0 +1,122 @@ +/* eslint-disable class-methods-use-this */ +import { + CoinSupport, + ICreateAccountEvent, + ICreateAccountParams, + IFormatAddressParams, + IGetAccountHistoryParams, + IGetAccountHistoryResult, + IGetCoinAllocationsParams, + IGetCoinAllocationsResult, + IGetExplorerLink, + IInitializeTransactionParams, + IPreparedTransaction, + IReceiveEvent, + IReceiveParams, + ISignMessageEvent, + ISignMessageParams, + ISignTransactionParams, + ISyncAccountsParams, + ISyncPriceHistoriesParams, + ISyncPricesParams, + IValidateAddressParams, +} from '@cypherock/coin-support-interfaces'; +import { ITransaction } from '@cypherock/db-interfaces'; +import { setXrpLib } from '@cypherock/sdk-app-xrp'; +import { Observable } from 'rxjs'; + +import * as operations from './operations'; +import { + IBroadcastXrpTransactionParams, + IPrepareXrpTransactionParams, + ISignXrpTransactionEvent, +} from './operations/types'; +import { setCoinSupportXrpLib, XrpLibType } from './utils'; + +export * from './operations/types'; +export { updateLogger } from './utils/logger'; + +export class XrpSupport implements CoinSupport { + public static setXrpLib(xrplib: XrpLibType): void { + setXrpLib(xrplib); + setCoinSupportXrpLib(xrplib); + } + + public receive(params: IReceiveParams): Observable { + return operations.receive(params); + } + + public createAccounts( + params: ICreateAccountParams, + ): Observable { + return operations.createAccounts(params); + } + + public syncAccount(params: ISyncAccountsParams): Observable { + return operations.syncAccount(params); + } + + public async initializeTransaction( + params: IInitializeTransactionParams, + ): Promise { + return operations.initializeTransaction(params); + } + + public async prepareTransaction( + params: IPrepareXrpTransactionParams, + ): Promise { + return operations.prepareTransaction(params); + } + + public signTransaction( + params: ISignTransactionParams, + ): Observable { + return operations.signTransaction(params); + } + + public broadcastTransaction( + params: IBroadcastXrpTransactionParams, + ): Promise { + return operations.broadcastTransaction(params); + } + + public signMessage( + params: ISignMessageParams, + ): Observable { + throw new Error(`Method not implemented Params: ${params}`); + } + + public getCoinAllocations( + params: IGetCoinAllocationsParams, + ): Promise { + return operations.getCoinAllocations(params); + } + + public getAccountHistory( + params: IGetAccountHistoryParams, + ): Promise { + return operations.getAccountHistory(params); + } + + public validateAddress(params: IValidateAddressParams): boolean { + return operations.validateAddress(params); + } + + public syncPrices(params: ISyncPricesParams): Observable { + return operations.syncPrices(params); + } + + public syncPriceHistories( + params: ISyncPriceHistoriesParams, + ): Observable { + return operations.syncPriceHistories(params); + } + + public getExplorerLink(params: IGetExplorerLink): string { + return operations.getExplorerLink(params); + } + + public formatAddress(params: IFormatAddressParams): string { + return params.address; + } +} diff --git a/packages/coin-support-xrp/src/operations/broadcastTransaction/index.ts b/packages/coin-support-xrp/src/operations/broadcastTransaction/index.ts new file mode 100644 index 000000000..23fa4ecf3 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/broadcastTransaction/index.ts @@ -0,0 +1,83 @@ +import { + getAccountAndCoin, + insertOrUpdateTransactions, +} from '@cypherock/coin-support-utils'; +import { xrpCoinList } from '@cypherock/coins'; +import { BigNumber } from '@cypherock/cysync-utils'; +import { + ITransaction, + TransactionStatusMap, + TransactionTypeMap, +} from '@cypherock/db-interfaces'; + +import { IBroadcastXrpTransactionParams } from './types'; + +import { broadcastTransactionToBlockchain } from '../../services'; +import { deriveAddress } from '../../utils'; + +export const broadcastTransaction = async ( + params: IBroadcastXrpTransactionParams, +): Promise => { + const { db, signedTransaction, transaction } = params; + const { account, coin } = await getAccountAndCoin( + db, + xrpCoinList, + transaction.accountId, + ); + + const myAddress = deriveAddress(account.xpubOrAddress); + const isMine = params.transaction.computedData.output.address === myAddress; + + const result = await broadcastTransactionToBlockchain( + signedTransaction, + coin.id, + ); + + const parsedTransaction: ITransaction = { + hash: result.tx_json.hash, + fees: transaction.computedData.fees, + amount: '0', + status: TransactionStatusMap.pending, + type: TransactionTypeMap.send, + timestamp: Date.now(), + blockHeight: -1, + inputs: [ + { + address: myAddress, + amount: '0', + isMine: true, + }, + ], + outputs: [ + { + ...params.transaction.userInputs.outputs[0], + isMine, + }, + ], + confirmations: 0, + accountId: account.__id, + walletId: account.walletId, + assetId: account.assetId, + parentAssetId: account.parentAssetId, + familyId: account.familyId, + parentAccountId: account.parentAccountId, + remarks: [transaction.userInputs.outputs[0].remarks ?? ''], + extraData: { + destinationTag: result.tx_json.DestinationTag, + flags: result.tx_json.Flags, + sequence: result.tx_json.Sequence, + lastLedgerSequence: result.tx_json.LastLedgerSequence, + }, + }; + + const amount = parsedTransaction.outputs.reduce( + (sum, output) => (output.isMine ? sum : sum.plus(output.amount)), + new BigNumber(0), + ); + parsedTransaction.amount = amount.abs().toString(); + parsedTransaction.inputs[0].amount = amount.abs().toString(); + + const [addedTxn] = await insertOrUpdateTransactions(db, [parsedTransaction]); + + return addedTxn; +}; diff --git a/packages/coin-support-xrp/src/operations/broadcastTransaction/types.ts b/packages/coin-support-xrp/src/operations/broadcastTransaction/types.ts new file mode 100644 index 000000000..90b9399a2 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/broadcastTransaction/types.ts @@ -0,0 +1,8 @@ +import { IBroadcastTransactionParams } from '@cypherock/coin-support-interfaces'; + +import { IPreparedXrpTransaction } from '../transaction'; + +export interface IBroadcastXrpTransactionParams + extends IBroadcastTransactionParams { + transaction: IPreparedXrpTransaction; +} diff --git a/packages/coin-support-xrp/src/operations/createAccounts/index.ts b/packages/coin-support-xrp/src/operations/createAccounts/index.ts new file mode 100644 index 000000000..52c1b0859 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/createAccounts/index.ts @@ -0,0 +1,113 @@ +import { CreateAccountDeviceEvent } from '@cypherock/coin-support-interfaces'; +import { + GetAddressesFromDevice, + IMakeCreateAccountsObservableParams, + makeCreateAccountsObservable, +} from '@cypherock/coin-support-utils'; +import { xrpCoinList } from '@cypherock/coins'; +import { AccountTypeMap } from '@cypherock/db-interfaces'; +import { GetPublicKeysEvent, XrpApp } from '@cypherock/sdk-app-xrp'; +import { hexToUint8Array } from '@cypherock/sdk-utils'; +import { Observable } from 'rxjs'; + +import { derivationPathSchemes } from './schemes'; +import { + ICreateXrpAccountEvent, + ICreateXrpAccountParams, + ICreatedXrpAccount, +} from './types'; + +import * as services from '../../services'; +import { createApp, deriveAddress } from '../../utils'; + +const DERIVATION_PATH_LIMIT = 30; + +const getAddressesFromDevice: GetAddressesFromDevice = async params => { + const { app, walletId, derivationPaths, observer } = params; + + const events: Record = + {} as any; + + const xrpToDeviceEventMap: Partial< + Record + > = { + [GetPublicKeysEvent.INIT]: CreateAccountDeviceEvent.INIT, + [GetPublicKeysEvent.CONFIRM]: CreateAccountDeviceEvent.CONFIRMED, + [GetPublicKeysEvent.PASSPHRASE]: + CreateAccountDeviceEvent.PASSPHRASE_ENTERED, + [GetPublicKeysEvent.PIN_CARD]: CreateAccountDeviceEvent.CARD_TAPPED, + }; + + const { publicKeys } = await app.getPublicKeys({ + walletId: hexToUint8Array(walletId), + derivationPaths: derivationPaths.map(e => ({ path: e.derivationPath })), + onEvent: event => { + const deviceEvent = xrpToDeviceEventMap[event]; + if (deviceEvent !== undefined) { + events[deviceEvent] = true; + } + + observer.next({ type: 'Device', device: { isDone: false, events } }); + }, + }); + + observer.next({ type: 'Device', device: { isDone: true, events } }); + + return publicKeys; +}; + +const createAccountFromAddress: IMakeCreateAccountsObservableParams['createAccountFromAddress'] = + async (addressDetails, params) => { + const coin = xrpCoinList[params.coinId]; + const name = `${coin.name} ${addressDetails.index + 1}`; + + const account: ICreatedXrpAccount = { + name, + xpubOrAddress: addressDetails.address, + balance: addressDetails.balance, + unit: coin.units[0].abbr, + derivationPath: addressDetails.derivationPath, + type: AccountTypeMap.account, + familyId: coin.family, + assetId: params.coinId, + parentAssetId: params.coinId, + walletId: params.walletId, + derivationScheme: addressDetails.schemeName as any, + isNew: addressDetails.txnCount <= 0, + extraData: {}, + isHidden: false, + }; + + return account; + }; + +const getBalanceAndTxnCount = async ( + publicKey: string, + params: ICreateXrpAccountParams, +) => { + const address = deriveAddress(publicKey); + return { + balance: await services.getBalance(address, params.coinId), + txnCount: ( + await services.getTransactions({ + address, + assetId: params.coinId, + limit: 1, + binary: true, + }) + ).transactions.length, + }; +}; + +export const createAccounts = ( + params: ICreateXrpAccountParams, +): Observable => + makeCreateAccountsObservable({ + ...params, + createAccountFromAddress, + getBalanceAndTxnCount, + getAddressesFromDevice, + createApp, + derivationPathSchemes, + derivationPathLimit: DERIVATION_PATH_LIMIT, + }); diff --git a/packages/coin-support-xrp/src/operations/createAccounts/schemes/index.ts b/packages/coin-support-xrp/src/operations/createAccounts/schemes/index.ts new file mode 100644 index 000000000..1a2eba9f0 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/createAccounts/schemes/index.ts @@ -0,0 +1,19 @@ +import { createDerivationPathGenerator } from '@cypherock/coin-support-utils'; + +import { + XrpDerivationSchemeMap, + XrpDerivationSchemeName, + IXrpDerivationScheme, +} from './types'; + +export const derivationPathSchemes: Record< + XrpDerivationSchemeName, + IXrpDerivationScheme +> = { + [XrpDerivationSchemeMap.default]: { + name: XrpDerivationSchemeMap.default, + generator: createDerivationPathGenerator("m/44'/144'/0'/0/i"), + threshold: 2, + newAccountLimit: 1, + }, +}; diff --git a/packages/coin-support-xrp/src/operations/createAccounts/schemes/types.ts b/packages/coin-support-xrp/src/operations/createAccounts/schemes/types.ts new file mode 100644 index 000000000..97dc726aa --- /dev/null +++ b/packages/coin-support-xrp/src/operations/createAccounts/schemes/types.ts @@ -0,0 +1,12 @@ +import { IDerivationScheme } from '@cypherock/coin-support-interfaces'; + +export const XrpDerivationSchemeMap = { + default: '', +} as const; + +export type XrpDerivationSchemeName = + (typeof XrpDerivationSchemeMap)[keyof typeof XrpDerivationSchemeMap]; + +export interface IXrpDerivationScheme extends IDerivationScheme { + name: XrpDerivationSchemeName; +} diff --git a/packages/coin-support-xrp/src/operations/createAccounts/types.ts b/packages/coin-support-xrp/src/operations/createAccounts/types.ts new file mode 100644 index 000000000..024a6833e --- /dev/null +++ b/packages/coin-support-xrp/src/operations/createAccounts/types.ts @@ -0,0 +1,22 @@ +import { + ICreatedAccount, + ICreateAccountParams, + ICreateAccountEvent, +} from '@cypherock/coin-support-interfaces'; +import { IAccount } from '@cypherock/db-interfaces'; + +import { XrpDerivationSchemeName } from './schemes/types'; + +export interface IXrpAccount extends IAccount { + derivationScheme: XrpDerivationSchemeName; +} + +export interface ICreatedXrpAccount extends ICreatedAccount { + derivationScheme: XrpDerivationSchemeName; +} + +export type ICreateXrpAccountParams = ICreateAccountParams; + +export interface ICreateXrpAccountEvent extends ICreateAccountEvent { + account?: ICreatedXrpAccount; +} diff --git a/packages/coin-support-xrp/src/operations/getAccountHistory/index.ts b/packages/coin-support-xrp/src/operations/getAccountHistory/index.ts new file mode 100644 index 000000000..01b18a35f --- /dev/null +++ b/packages/coin-support-xrp/src/operations/getAccountHistory/index.ts @@ -0,0 +1,12 @@ +import { + IGetAccountHistoryParams, + IGetAccountHistoryResult, +} from '@cypherock/coin-support-interfaces'; +import { createGetAccountHistory } from '@cypherock/coin-support-utils'; + +export const getAccountHistory = async ( + params: IGetAccountHistoryParams, +): Promise => + createGetAccountHistory({ + ...params, + }); diff --git a/packages/coin-support-xrp/src/operations/getCoinAllocations/index.ts b/packages/coin-support-xrp/src/operations/getCoinAllocations/index.ts new file mode 100644 index 000000000..f9a1afbf1 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/getCoinAllocations/index.ts @@ -0,0 +1,15 @@ +import { + IGetCoinAllocationsParams, + IGetCoinAllocationsResult, +} from '@cypherock/coin-support-interfaces'; +import { createGetCoinAllocations } from '@cypherock/coin-support-utils'; + +import { getCoinIds } from '../../utils'; + +export const getCoinAllocations = async ( + params: IGetCoinAllocationsParams, +): Promise => + createGetCoinAllocations({ + ...params, + getCoinIds, + }); diff --git a/packages/coin-support-xrp/src/operations/getExplorerLink/index.ts b/packages/coin-support-xrp/src/operations/getExplorerLink/index.ts new file mode 100644 index 000000000..2d56a0e01 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/getExplorerLink/index.ts @@ -0,0 +1,18 @@ +import { IGetExplorerLink } from '@cypherock/coin-support-interfaces'; +import { xrpCoinList } from '@cypherock/coins'; + +import { config } from '../../config'; + +export const getExplorerLink = (params: IGetExplorerLink) => { + const queryParams = { + network: xrpCoinList[params.transaction.assetId].network, + txHash: params.transaction.hash, + }; + + const query = new URLSearchParams(''); + for (const [key, value] of Object.entries(queryParams)) { + query.append(key, value.toString()); + } + + return `${config.API_CYPHEROCK}/xrp/transaction/open-txn?${query.toString()}`; +}; diff --git a/packages/coin-support-xrp/src/operations/index.ts b/packages/coin-support-xrp/src/operations/index.ts new file mode 100644 index 000000000..a38c522e9 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/index.ts @@ -0,0 +1,13 @@ +export * from './createAccounts'; +export * from './receive'; +export * from './syncPrices'; +export * from './syncPriceHistories'; +export * from './syncAccount'; +export * from './getAccountHistory'; +export * from './getCoinAllocations'; +export * from './validateAddress'; +export * from './getExplorerLink'; +export * from './initializeTransaction'; +export * from './prepareTransaction'; +export * from './signTransaction'; +export * from './broadcastTransaction'; diff --git a/packages/coin-support-xrp/src/operations/initializeTransaction/index.ts b/packages/coin-support-xrp/src/operations/initializeTransaction/index.ts new file mode 100644 index 000000000..0fc7e1b49 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/initializeTransaction/index.ts @@ -0,0 +1,42 @@ +import { IInitializeTransactionParams } from '@cypherock/coin-support-interfaces'; +import { getAccountAndCoin } from '@cypherock/coin-support-utils'; +import { xrpCoinList } from '@cypherock/coins'; + +import { getFees } from '../../services'; +import { IPreparedXrpTransaction } from '../transaction'; + +export const initializeTransaction = async ( + params: IInitializeTransactionParams, +): Promise => { + const { accountId, db } = params; + const { account } = await getAccountAndCoin(db, xrpCoinList, accountId); + + const fees = await getFees(account.assetId); + + return { + accountId, + validation: { + outputs: [], + hasEnoughBalance: true, + isValidFee: true, + isFeeBelowMin: false, + ownOutputAddressNotAllowed: [], + zeroAmountNotAllowed: false, + isAmountBelowXrpReserve: false, + isBalanceBelowXrpReserve: false, + isInvalidDestinationTag: false, + }, + userInputs: { + outputs: [], + isSendAll: false, + fees, + }, + staticData: { + fees, + }, + computedData: { + output: { address: '', amount: '0' }, + fees: '0', + }, + }; +}; diff --git a/packages/coin-support-xrp/src/operations/prepareTransaction/index.ts b/packages/coin-support-xrp/src/operations/prepareTransaction/index.ts new file mode 100644 index 000000000..23db185b7 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/prepareTransaction/index.ts @@ -0,0 +1,149 @@ +import { getAccountAndCoin } from '@cypherock/coin-support-utils'; +import { xrpCoinList, ICoinInfo } from '@cypherock/coins'; +import { assert, BigNumber } from '@cypherock/cysync-utils'; + +import { IPrepareXrpTransactionParams } from './types'; + +import { getIsAccountActivated } from '../../services'; +import { deriveAddress } from '../../utils'; +import { IPreparedXrpTransaction } from '../transaction'; +import { validateAddress } from '../validateAddress'; + +const MAX_UNSIGNED_32BIT_INT = 0xffffffff; + +const validateAddresses = ( + params: IPrepareXrpTransactionParams, + coin: ICoinInfo, +) => { + const outputAddressValidation: boolean[] = []; + + for (const output of params.txn.userInputs.outputs) { + let isValid = true; + + /** + * We allow empty string in the validation (error prompt should not + * appear for empty string). And validate only non-empty strings. + */ + if ( + output.address && + !validateAddress({ address: output.address, coinId: coin.id }) + ) { + isValid = false; + } + + outputAddressValidation.push(isValid); + } + + return outputAddressValidation; +}; + +export const prepareTransaction = async ( + params: IPrepareXrpTransactionParams, +): Promise => { + const { accountId, db, txn } = params; + const { account, coin } = await getAccountAndCoin(db, xrpCoinList, accountId); + + assert( + txn.userInputs.outputs.length === 1, + new Error('Xrp transaction requires exactly 1 output'), + ); + + const outputsValidation = validateAddresses(params, coin); + let isActivated: boolean | undefined; + if (txn.userInputs.outputs[0].address === txn.computedData.output.address) { + isActivated = txn.computedData.output.isActivated; + } + + const output = { ...txn.userInputs.outputs[0], isActivated }; + + if ( + output.address && + outputsValidation[0] && + output.isActivated === undefined + ) { + output.isActivated = await getIsAccountActivated( + output.address, + account.assetId, + ); + txn.computedData.output.isActivated = output.isActivated; + } + + // Amount shouldn't have any decimal value as it's in lowest unit + output.amount = new BigNumber(output.amount).toFixed(0); + let sendAmount = new BigNumber(output.amount); + + const myAddress = deriveAddress(account.xpubOrAddress); + const isOwnOutputAddress = output.address === myAddress; + + const { fees } = txn.userInputs; + + const calculateMaxSend = () => { + sendAmount = new BigNumber( + BigNumber.max( + new BigNumber(account.balance).minus(fees).minus(coin.reserveXrp), + 0, + ).toFixed(0), + ); + output.amount = sendAmount.toString(10); + // update userInput so that the max amount is editable & not reset to 0 + txn.userInputs.outputs[0].amount = output.amount; + }; + + let hasEnoughBalance: boolean; + + if (txn.userInputs.isSendAll) { + calculateMaxSend(); + } + + hasEnoughBalance = + sendAmount.isNaN() || + new BigNumber(account.balance).isGreaterThanOrEqualTo( + sendAmount.plus(fees), + ); + + hasEnoughBalance = + new BigNumber(txn.userInputs.outputs[0].amount).isNaN() || hasEnoughBalance; + + let isBalanceBelowXrpReserve = false; + if (hasEnoughBalance) { + isBalanceBelowXrpReserve = !( + sendAmount.isNaN() || + new BigNumber(account.balance).isGreaterThanOrEqualTo( + sendAmount.plus(fees).plus(coin.reserveXrp), + ) + ); + } + + let isAmountBelowXrpReserve = !output.isActivated; + if (isAmountBelowXrpReserve) { + isAmountBelowXrpReserve = sendAmount.isLessThan(coin.reserveXrp); + } + + const isValidFee = new BigNumber(fees).isGreaterThan(0); + const isFeeBelowMin = + isValidFee && new BigNumber(fees).isLessThan(txn.staticData.fees); + + const isInvalidDestinationTag = + output.destinationTag !== undefined + ? output.destinationTag >= MAX_UNSIGNED_32BIT_INT + : false; + + return { + ...txn, + validation: { + outputs: outputsValidation, + hasEnoughBalance, + isValidFee, + isFeeBelowMin, + ownOutputAddressNotAllowed: [isOwnOutputAddress], + zeroAmountNotAllowed: sendAmount.isZero(), + isAmountBelowXrpReserve, + isBalanceBelowXrpReserve, + isInvalidDestinationTag, + }, + computedData: { + fees, + output, + }, + }; +}; diff --git a/packages/coin-support-xrp/src/operations/prepareTransaction/types.ts b/packages/coin-support-xrp/src/operations/prepareTransaction/types.ts new file mode 100644 index 000000000..2eb8b0428 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/prepareTransaction/types.ts @@ -0,0 +1,8 @@ +import { IPrepareTransactionParams } from '@cypherock/coin-support-interfaces'; + +import { IPreparedXrpTransaction } from '../transaction'; + +export interface IPrepareXrpTransactionParams + extends IPrepareTransactionParams { + txn: IPreparedXrpTransaction; +} diff --git a/packages/coin-support-xrp/src/operations/receive/index.ts b/packages/coin-support-xrp/src/operations/receive/index.ts new file mode 100644 index 000000000..00834f59b --- /dev/null +++ b/packages/coin-support-xrp/src/operations/receive/index.ts @@ -0,0 +1,60 @@ +import { + IReceiveEvent, + ReceiveDeviceEvent, +} from '@cypherock/coin-support-interfaces'; +import { + makeReceiveObservable, + IGenerateReceiveAddressParams, + IReceiveAddressInfo, + IGetReceiveAddressFromDevice, + mapDerivationPath, +} from '@cypherock/coin-support-utils'; +import { XrpApp, GetPublicKeysEvent } from '@cypherock/sdk-app-xrp'; +import { hexToUint8Array } from '@cypherock/sdk-utils'; +import { Observable } from 'rxjs'; + +import { IXrpReceiveEvent, IXrpReceiveParams, statusMap } from './types'; + +import { createApp, deriveAddress } from '../../utils'; + +const getExternalAddress = async ( + params: IGenerateReceiveAddressParams, +): Promise => { + const { xpubOrAddress, derivationPath } = params.account; + const address = deriveAddress(xpubOrAddress); + + return { + address, + derivationPath, + expectedFromDevice: address, + }; +}; + +const getReceiveAddressFromDevice = async ( + params: IGetReceiveAddressFromDevice, +): Promise => { + const { app, derivationPath, walletId, observer } = params; + + const events: Record = {} as any; + + const { address } = await app.getUserVerifiedPublicKey({ + walletId: hexToUint8Array(walletId), + derivationPath: mapDerivationPath(derivationPath), + onEvent: (event: GetPublicKeysEvent) => { + const receiveEvent = statusMap[event]; + if (receiveEvent !== undefined) events[receiveEvent] = true; + observer.next({ type: 'Device', device: { isDone: false, events } }); + }, + }); + + observer.next({ type: 'Device', device: { isDone: true, events } }); + return address; +}; + +export const receive = (params: IXrpReceiveParams): Observable => + makeReceiveObservable({ + ...params, + createApp, + generateReceiveAddress: getExternalAddress, + getReceiveAddressFromDevice, + }); diff --git a/packages/coin-support-xrp/src/operations/receive/types.ts b/packages/coin-support-xrp/src/operations/receive/types.ts new file mode 100644 index 000000000..3e865c0ef --- /dev/null +++ b/packages/coin-support-xrp/src/operations/receive/types.ts @@ -0,0 +1,20 @@ +import { + IReceiveEvent, + IReceiveParams, + ReceiveDeviceEvent, +} from '@cypherock/coin-support-interfaces'; +import { GetPublicKeysEvent } from '@cypherock/sdk-app-xrp'; + +export type IXrpReceiveParams = IReceiveParams; + +export type IXrpReceiveEvent = IReceiveEvent; + +export const statusMap: Partial< + Record +> = { + [GetPublicKeysEvent.INIT]: ReceiveDeviceEvent.INIT, + [GetPublicKeysEvent.CONFIRM]: ReceiveDeviceEvent.CONFIRMED, + [GetPublicKeysEvent.PASSPHRASE]: ReceiveDeviceEvent.PASSPHRASE_ENTERED, + [GetPublicKeysEvent.PIN_CARD]: ReceiveDeviceEvent.CARD_TAPPED, + [GetPublicKeysEvent.VERIFY]: ReceiveDeviceEvent.VERIFIED, +}; diff --git a/packages/coin-support-xrp/src/operations/signTransaction/index.ts b/packages/coin-support-xrp/src/operations/signTransaction/index.ts new file mode 100644 index 000000000..a5626164e --- /dev/null +++ b/packages/coin-support-xrp/src/operations/signTransaction/index.ts @@ -0,0 +1,114 @@ +import { SignTransactionDeviceEvent } from '@cypherock/coin-support-interfaces'; +import { + makeSignTransactionsObservable, + mapDerivationPath, + SignTransactionFromDevice, +} from '@cypherock/coin-support-utils'; +import { IXrpCoinInfo } from '@cypherock/coins'; +import { IAccount } from '@cypherock/db-interfaces'; +import { IUnsignedTransaction, XrpApp } from '@cypherock/sdk-app-xrp'; +import { assert, hexToUint8Array } from '@cypherock/sdk-utils'; +import { Observable } from 'rxjs'; + +import { + ISignXrpTransactionParams, + ISignXrpTransactionEvent, + signXrpToDeviceEventMap, +} from './types'; + +import * as services from '../../services'; +import { createApp, getCoinSupportXrpLib } from '../../utils'; +import logger from '../../utils/logger'; +import { IPreparedXrpTransaction } from '../transaction'; + +const prepareUnsignedTxn = async ( + transaction: IPreparedXrpTransaction, + coin: IXrpCoinInfo, + account: IAccount, +): Promise => { + const xrpl = getCoinSupportXrpLib(); + const address = xrpl.deriveAddress(account.xpubOrAddress); + + const { flags, sequence } = await services.getFlagsAndSequence( + address, + account.assetId, + ); + const lastLedgerSequence = await services.getLastLedgerSequence( + account.assetId, + ); + + const rawTxn = { + Account: address, + Destination: transaction.computedData.output.address, + Amount: transaction.computedData.output.amount, + Fee: transaction.computedData.fees, + DestinationTag: transaction.computedData.output.destinationTag, + Flags: flags, + Sequence: sequence, + LastLedgerSequence: lastLedgerSequence, + SigningPubKey: account.xpubOrAddress, + }; + const txnHex = xrpl.encodeForSigning({ + ...rawTxn, + TransactionType: 'Payment', + }); + + const unsignedTxn: IUnsignedTransaction = { + txnHex, + rawTxn: { + ...rawTxn, + TransactionType: 'Payment', + }, + }; + + return unsignedTxn; +}; + +const signTransactionFromDevice: SignTransactionFromDevice< + XrpApp, + string +> = async params => { + const { app, observer, transaction, account, coin } = params; + logger.info({ transaction }); + + const events: Record = + {} as any; + + const txn = await prepareUnsignedTxn( + transaction as IPreparedXrpTransaction, + coin as IXrpCoinInfo, + account, + ); + + assert(txn, 'Missing unsigned transaction'); + + const { serializedTxn } = await app.signTxn({ + walletId: hexToUint8Array(account.walletId), + derivationPath: mapDerivationPath(account.derivationPath), + txn, + serializeTxn: true, + onEvent: event => { + const deviceEvent = signXrpToDeviceEventMap[event]; + if (deviceEvent !== undefined) { + events[deviceEvent] = true; + } + + observer.next({ type: 'Device', device: { isDone: false, events } }); + }, + }); + + observer.next({ type: 'Device', device: { isDone: true, events } }); + + assert(serializedTxn, new Error('Failed to sign transaction')); + + return serializedTxn; +}; + +export const signTransaction = ( + params: ISignXrpTransactionParams, +): Observable => + makeSignTransactionsObservable({ + ...params, + signTransactionFromDevice, + createApp, + }); diff --git a/packages/coin-support-xrp/src/operations/signTransaction/types.ts b/packages/coin-support-xrp/src/operations/signTransaction/types.ts new file mode 100644 index 000000000..658247b5c --- /dev/null +++ b/packages/coin-support-xrp/src/operations/signTransaction/types.ts @@ -0,0 +1,20 @@ +import { + ISignTransactionParams, + ISignTransactionEvent, + SignTransactionDeviceEvent, +} from '@cypherock/coin-support-interfaces'; +import { SignTxnEvent } from '@cypherock/sdk-app-xrp'; + +export type ISignXrpTransactionParams = ISignTransactionParams; + +export type ISignXrpTransactionEvent = ISignTransactionEvent; + +export const signXrpToDeviceEventMap: Partial< + Record +> = { + [SignTxnEvent.INIT]: SignTransactionDeviceEvent.INIT, + [SignTxnEvent.CONFIRM]: SignTransactionDeviceEvent.CONFIRMED, + [SignTxnEvent.VERIFY]: SignTransactionDeviceEvent.VERIFIED, + [SignTxnEvent.PASSPHRASE]: SignTransactionDeviceEvent.PASSPHRASE_ENTERED, + [SignTxnEvent.PIN_CARD]: SignTransactionDeviceEvent.CARD_TAPPED, +}; diff --git a/packages/coin-support-xrp/src/operations/syncAccount/index.ts b/packages/coin-support-xrp/src/operations/syncAccount/index.ts new file mode 100644 index 000000000..b2ce9ed35 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/syncAccount/index.ts @@ -0,0 +1,169 @@ +import { + createSyncAccountsObservable, + getLatestTransactionBlock, + IGetAddressDetails, +} from '@cypherock/coin-support-utils'; +import { + IAccount, + ITransaction, + TransactionStatus, + TransactionStatusMap, + TransactionTypeMap, +} from '@cypherock/db-interfaces'; + +import { ISyncXrpAccountsParams } from './types'; + +import * as services from '../../services'; +import { deriveAddress } from '../../utils'; +import { IXrpAccount } from '../types'; + +const PER_PAGE_TXN_LIMIT = 100; +const JANUARY_2000_UNIX_TIMESTAMP_OFFSET = 946684800000; + +const parseTransaction = ( + address: string, + account: IAccount, + txn: services.IDetailedXrpResponseTransaction, +): ITransaction => { + const myAddress = address; + const fromAddress = txn.tx.Account; + const toAddress = txn.tx.Destination; + const fees = txn.tx.Fee; + const amount = txn.tx.Amount; + let status: TransactionStatus = TransactionStatusMap.failed; + if (txn.meta.TransactionResult.startsWith('tes')) { + status = TransactionStatusMap.success; + } else if (txn.meta.TransactionResult.startsWith('ter')) { + status = TransactionStatusMap.pending; + } + + const transaction: ITransaction = { + accountId: account.__id ?? '', + walletId: account.walletId, + assetId: account.assetId, + familyId: account.familyId, + parentAssetId: account.parentAssetId, + hash: txn.tx.hash, + confirmations: 1, + fees, + amount, + status, + type: + myAddress === fromAddress + ? TransactionTypeMap.send + : TransactionTypeMap.receive, + timestamp: new Date( + parseInt(txn.tx.date.toString(), 10) * 1000 + + JANUARY_2000_UNIX_TIMESTAMP_OFFSET, + ).getTime(), // the received date is w.r.t January 1, 2000, hence the conversion to unix + blockHeight: txn.tx.ledger_index, + inputs: [ + { + address: fromAddress, + amount, + isMine: myAddress === fromAddress, + }, + ], + outputs: [ + { + address: txn.tx.Destination, + amount, + isMine: myAddress === toAddress, + }, + ], + extraData: { + destinationTag: txn.tx.DestinationTag, + flags: txn.tx.Flags, + sequence: txn.tx.Sequence, + lastLedgerSequence: txn.tx.LastLedgerSequence, + }, + }; + return transaction; +}; + +const fetchAndParseTransactions = async (params: { + address: string; + account: IAccount; + limit: number; + ledgerIndexMin: number; +}) => { + const { address, account, limit, ledgerIndexMin } = params; + const response = await services.getTransactions({ + address, + assetId: account.assetId, + limit, + forward: true, + ledgerIndexMin, + }); + + const transactions: ITransaction[] = []; + for (const rawTransaction of response.transactions) { + if ( + rawTransaction.tx.TransactionType !== 'Payment' || + typeof rawTransaction.tx.Amount !== 'string' + ) { + continue; + } + + const transaction = parseTransaction(address, account, rawTransaction); + + transactions.push({ ...transaction }); + } + + const hasMore = response.limit === response.transactions.length; + const nextLedgerIndexMin = hasMore ? response.marker.ledger : -1; + + return { + transactions, + hasMore, + nextLedgerIndexMin, + }; +}; + +const getAddressDetails: IGetAddressDetails<{ + perPage: number; + afterBlock?: number; + updatedBalance?: string; +}> = async ({ db, account, iterationContext }) => { + const address = deriveAddress(account.xpubOrAddress); + + const updatedBalance = + iterationContext?.updatedBalance ?? + (await services.getBalance(address, account.assetId)); + + const afterBlock = + iterationContext?.afterBlock ?? + (await getLatestTransactionBlock(db, { + accountId: account.__id, + })) ?? + -1; + + const perPage = iterationContext?.perPage ?? PER_PAGE_TXN_LIMIT; + + const transactionDetails = await fetchAndParseTransactions({ + address, + account, + limit: perPage, + ledgerIndexMin: afterBlock, + }); + + const updatedAccountInfo: Partial = { + balance: updatedBalance, + }; + + return { + hasMore: transactionDetails.hasMore, + nextIterationContext: { + perPage, + afterBlock: transactionDetails.nextLedgerIndexMin, + }, + transactions: transactionDetails.transactions, + updatedAccountInfo, + }; +}; + +export const syncAccount = (params: ISyncXrpAccountsParams) => + createSyncAccountsObservable({ + ...params, + getAddressDetails, + }); diff --git a/packages/coin-support-xrp/src/operations/syncAccount/types.ts b/packages/coin-support-xrp/src/operations/syncAccount/types.ts new file mode 100644 index 000000000..c3c77add4 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/syncAccount/types.ts @@ -0,0 +1,3 @@ +import { ISyncAccountsParams } from '@cypherock/coin-support-interfaces'; + +export type ISyncXrpAccountsParams = ISyncAccountsParams; diff --git a/packages/coin-support-xrp/src/operations/syncPriceHistories/index.ts b/packages/coin-support-xrp/src/operations/syncPriceHistories/index.ts new file mode 100644 index 000000000..5b5546ddc --- /dev/null +++ b/packages/coin-support-xrp/src/operations/syncPriceHistories/index.ts @@ -0,0 +1,10 @@ +import { ISyncPriceHistoriesParams } from '@cypherock/coin-support-interfaces'; +import { createSyncPriceHistoriesObservable } from '@cypherock/coin-support-utils'; + +import { getCoinIds } from '../../utils'; + +export const syncPriceHistories = (params: ISyncPriceHistoriesParams) => + createSyncPriceHistoriesObservable({ + ...params, + getCoinIds, + }); diff --git a/packages/coin-support-xrp/src/operations/syncPrices/index.ts b/packages/coin-support-xrp/src/operations/syncPrices/index.ts new file mode 100644 index 000000000..14f6cf42f --- /dev/null +++ b/packages/coin-support-xrp/src/operations/syncPrices/index.ts @@ -0,0 +1,10 @@ +import { ISyncPricesParams } from '@cypherock/coin-support-interfaces'; +import { createSyncPricesObservable } from '@cypherock/coin-support-utils'; + +import { getCoinIds } from '../../utils'; + +export const syncPrices = (params: ISyncPricesParams) => + createSyncPricesObservable({ + ...params, + getCoinIds, + }); diff --git a/packages/coin-support-xrp/src/operations/transaction.ts b/packages/coin-support-xrp/src/operations/transaction.ts new file mode 100644 index 000000000..5069926d5 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/transaction.ts @@ -0,0 +1,37 @@ +import { + IPreparedTransaction, + IPreparedTransactionOutput, +} from '@cypherock/coin-support-interfaces'; + +export interface IPreparedXrpTransactionOutput + extends IPreparedTransactionOutput { + destinationTag?: number; +} + +export interface IPreparedXrpTransaction extends IPreparedTransaction { + userInputs: { + outputs: IPreparedXrpTransactionOutput[]; + isSendAll: boolean; + fees: string; + }; + validation: { + outputs: boolean[]; + hasEnoughBalance: boolean; + isValidFee: boolean; + isFeeBelowMin: boolean; + ownOutputAddressNotAllowed: boolean[]; + zeroAmountNotAllowed: boolean; + isAmountBelowXrpReserve: boolean; + isBalanceBelowXrpReserve: boolean; + isInvalidDestinationTag: boolean; + }; + staticData: { + fees: string; + }; + computedData: { + output: IPreparedXrpTransactionOutput & { + isActivated?: boolean; + }; + fees: string; + }; +} diff --git a/packages/coin-support-xrp/src/operations/types.ts b/packages/coin-support-xrp/src/operations/types.ts new file mode 100644 index 000000000..2e1bbb999 --- /dev/null +++ b/packages/coin-support-xrp/src/operations/types.ts @@ -0,0 +1,6 @@ +export * from './createAccounts/types'; +export * from './receive/types'; +export * from './prepareTransaction/types'; +export * from './signTransaction/types'; +export * from './broadcastTransaction/types'; +export * from './transaction'; diff --git a/packages/coin-support-xrp/src/operations/validateAddress/index.ts b/packages/coin-support-xrp/src/operations/validateAddress/index.ts new file mode 100644 index 000000000..8080a30ca --- /dev/null +++ b/packages/coin-support-xrp/src/operations/validateAddress/index.ts @@ -0,0 +1,13 @@ +import { IValidateAddressParams } from '@cypherock/coin-support-interfaces'; +import { xrpCoinList } from '@cypherock/coins'; +import { assert } from '@cypherock/cysync-utils'; +import WAValidator from 'multicoin-address-validator'; + +export const validateAddress = (params: IValidateAddressParams) => { + const { address, coinId } = params; + const coin = xrpCoinList[coinId]; + + assert(coin, new Error(`Cannot find coin details for coin: ${coinId}`)); + + return WAValidator.validate(address, 'xrp'); +}; diff --git a/packages/coin-support-xrp/src/services/api/account.ts b/packages/coin-support-xrp/src/services/api/account.ts new file mode 100644 index 000000000..07c9d9a63 --- /dev/null +++ b/packages/coin-support-xrp/src/services/api/account.ts @@ -0,0 +1,68 @@ +import { xrpCoinList } from '@cypherock/coins'; +import { makePostRequest } from '@cypherock/cysync-utils'; + +import { config } from '../../config'; + +const baseURL = `${config.API_CYPHEROCK}/xrp/wallet`; + +export const getAccountInfo = async ( + address: string, + assetId: string, +): Promise => { + const url = `${baseURL}/account-info`; + const response = await makePostRequest(url, { + address, + network: xrpCoinList[assetId].network, + }); + + return response.data?.account_info; +}; + +export const getBalance = async ( + address: string, + assetId: string, +): Promise => { + const accountInfo = await getAccountInfo(address, assetId); + + let balance = accountInfo?.Balance ?? '0'; + + if (typeof balance === 'number') balance = balance.toString(); + + if (typeof balance !== 'string') + throw new Error('Invalid xrp balance returned from server'); + + return balance; +}; + +export const getFlagsAndSequence = async ( + address: string, + assetId: string, +): Promise<{ flags: number; sequence: number }> => { + const accountInfo = await getAccountInfo(address, assetId); + + if (!accountInfo) + throw new Error('Failed to fetch xrp account info from server'); + + const flags = accountInfo?.Flags; + const sequence = accountInfo?.Sequence; + + if (flags === undefined && flags === null) + throw new Error('Invalid xrp flags returned from server'); + + if (sequence === undefined && flags === null) + throw new Error('Invalid xrp sequence returned from server'); + + return { + flags, + sequence, + }; +}; + +export const getIsAccountActivated = async ( + address: string, + assetId: string, +): Promise => { + const accountInfo = await getAccountInfo(address, assetId); + + return accountInfo?.Balance && accountInfo.Balance !== '0'; +}; diff --git a/packages/coin-support-xrp/src/services/api/index.ts b/packages/coin-support-xrp/src/services/api/index.ts new file mode 100644 index 000000000..c74c22712 --- /dev/null +++ b/packages/coin-support-xrp/src/services/api/index.ts @@ -0,0 +1,3 @@ +export * from './account'; +export * from './transaction'; +export * from './types'; diff --git a/packages/coin-support-xrp/src/services/api/transaction.ts b/packages/coin-support-xrp/src/services/api/transaction.ts new file mode 100644 index 000000000..7364629ca --- /dev/null +++ b/packages/coin-support-xrp/src/services/api/transaction.ts @@ -0,0 +1,99 @@ +import { xrpCoinList } from '@cypherock/coins'; +import { assert, makePostRequest } from '@cypherock/cysync-utils'; + +import { IXrpTransactionParams, IXrpTransactionResult } from './types'; + +import { config } from '../../config'; + +const baseURL = `${config.API_CYPHEROCK}/xrp/transaction`; + +// Expire unconfirmed transactions after 70 ledger versions, approximately 1.5 minutes +// This is manually set if the transaction is done early it will just take time to broadcast +// otherwise, the transaction will fail +const LEDGER_OFFSET = 70; + +export const getTransactions = async ( + params: IXrpTransactionParams, +): Promise => { + const url = `${baseURL}/history`; + + const query: Record = { + ...params, + network: xrpCoinList[params.assetId].network, + }; + delete query.assetId; + + const response = await makePostRequest(url, query); + + assert( + typeof response.data.transactions === 'object', + 'Invalid transaction response from server', + ); + + return response.data; +}; + +export const getFees = async (assetId: string) => { + const url = `${baseURL}/fees`; + + const query: Record = { + network: xrpCoinList[assetId].network, + }; + + const response = await makePostRequest(url, query); + + let fees = response.data?.fees?.minimum_fee ?? '10'; + + if (typeof fees === 'number') fees = fees.toString(); + + if (typeof fees !== 'string') + throw new Error('Invalid xrp fees returned from server'); + + return fees; +}; + +export const getLastLedgerSequence = async (assetId: string) => { + const url = `${baseURL}/ledger-index`; + + const query: Record = { + network: xrpCoinList[assetId].network, + }; + + const response = await makePostRequest(url, query); + + const ledgerIndex = response.data?.ledger_index; + + if (ledgerIndex === undefined) + throw new Error('Failed to fetch xrp ledger index from server'); + + if (typeof ledgerIndex !== 'number') + throw new Error('Invalid xrp ledger index returned from server'); + + return ledgerIndex + LEDGER_OFFSET; +}; + +export const broadcastTransactionToBlockchain = async ( + transaction: string, + assetId: string, +): Promise => { + const url = `${baseURL}/broadcast`; + const response = await makePostRequest( + url, + { + transaction, + network: xrpCoinList[assetId].network, + }, + { + maxTries: 0, + }, + ); + + assert( + !response.data.error, + new Error('Server: Invalid txn hash from server'), + ); + + console.log({ TransactionBroadcastResult: response.data }); + + return response.data; +}; diff --git a/packages/coin-support-xrp/src/services/api/types.ts b/packages/coin-support-xrp/src/services/api/types.ts new file mode 100644 index 000000000..b83077c20 --- /dev/null +++ b/packages/coin-support-xrp/src/services/api/types.ts @@ -0,0 +1,43 @@ +export interface IXrpTransactionParams { + address: string; + assetId: string; + limit?: number; + forward?: boolean; + binary?: boolean; + ledgerIndexMin?: number; +} + +interface IXrpResponseTransaction { + hash: string; + TransactionType: string; + Account: string; + Amount: string; + Destination: string; + Fee: string; + Flags: number; + LastLedgerSequence: number; + Sequence: number; + SigningPubKey: string; + TxnSignature: string; + DestinationTag: number; + SourceTag: number; + date: number; + ledger_index: number; +} + +export interface IDetailedXrpResponseTransaction { + meta: { + TransactionResult: string; + delivered_amount: string; + }; + tx: IXrpResponseTransaction; +} + +export interface IXrpTransactionResult { + account: string; + transactions: IDetailedXrpResponseTransaction[]; + limit: number; + marker: { + ledger: number; + }; +} diff --git a/packages/coin-support-xrp/src/services/index.ts b/packages/coin-support-xrp/src/services/index.ts new file mode 100644 index 000000000..b1c13e734 --- /dev/null +++ b/packages/coin-support-xrp/src/services/index.ts @@ -0,0 +1 @@ +export * from './api'; diff --git a/packages/coin-support-xrp/src/utils/app.ts b/packages/coin-support-xrp/src/utils/app.ts new file mode 100644 index 000000000..0f1fdc205 --- /dev/null +++ b/packages/coin-support-xrp/src/utils/app.ts @@ -0,0 +1,5 @@ +import { XrpApp } from '@cypherock/sdk-app-xrp'; +import { IDeviceConnection } from '@cypherock/sdk-interfaces'; + +export const createApp = (connection: IDeviceConnection) => + XrpApp.create(connection); diff --git a/packages/coin-support-xrp/src/utils/deriveAddress.ts b/packages/coin-support-xrp/src/utils/deriveAddress.ts new file mode 100644 index 000000000..ac076e20d --- /dev/null +++ b/packages/coin-support-xrp/src/utils/deriveAddress.ts @@ -0,0 +1,4 @@ +import { getCoinSupportXrpLib } from './xrpLib'; + +export const deriveAddress = (publicKey: string) => + getCoinSupportXrpLib().deriveAddress(publicKey); diff --git a/packages/coin-support-xrp/src/utils/getCoinIds.ts b/packages/coin-support-xrp/src/utils/getCoinIds.ts new file mode 100644 index 000000000..e83ed38a0 --- /dev/null +++ b/packages/coin-support-xrp/src/utils/getCoinIds.ts @@ -0,0 +1,18 @@ +import { coinFamiliesMap } from '@cypherock/coins'; +import { IDatabase } from '@cypherock/db-interfaces'; +import lodash from 'lodash'; + +export const getCoinIds = async (db: IDatabase) => { + const accounts = await db.account.getAll({ + familyId: coinFamiliesMap.xrp, + }); + const assetList = accounts.map(account => ({ + assetId: account.assetId, + parentAssetId: account.parentAssetId, + })); + + return lodash.uniqWith( + assetList, + (a, b) => a.assetId === b.assetId && a.parentAssetId === b.parentAssetId, + ); +}; diff --git a/packages/coin-support-xrp/src/utils/index.ts b/packages/coin-support-xrp/src/utils/index.ts new file mode 100644 index 000000000..6928f71ea --- /dev/null +++ b/packages/coin-support-xrp/src/utils/index.ts @@ -0,0 +1,5 @@ +export * from './app'; +export * from './logger'; +export * from './getCoinIds'; +export * from './xrpLib'; +export * from './deriveAddress'; diff --git a/packages/coin-support-xrp/src/utils/logger.ts b/packages/coin-support-xrp/src/utils/logger.ts new file mode 100644 index 000000000..ffede86d9 --- /dev/null +++ b/packages/coin-support-xrp/src/utils/logger.ts @@ -0,0 +1,20 @@ +import { ILogger, LogCreator } from '@cypherock/cysync-interfaces'; +import { + createDefaultConsoleLogger, + updateLoggerObject, +} from '@cypherock/cysync-utils'; + +export const loggerServiceName = 'coin-support-xrp'; + +const logger: ILogger = { + ...createDefaultConsoleLogger(loggerServiceName), +}; + +export const updateLogger = (createLogger: LogCreator) => { + updateLoggerObject({ + currentLogger: logger, + newLogger: createLogger(loggerServiceName), + }); +}; + +export default logger; diff --git a/packages/coin-support-xrp/src/utils/xrpLib.ts b/packages/coin-support-xrp/src/utils/xrpLib.ts new file mode 100644 index 000000000..77462c204 --- /dev/null +++ b/packages/coin-support-xrp/src/utils/xrpLib.ts @@ -0,0 +1,16 @@ +import type xrpl from 'xrpl'; + +export type XrpLibType = typeof xrpl; + +let xrpLibInstance: XrpLibType | undefined; + +export const getCoinSupportXrpLib = () => { + if (!xrpLibInstance) { + throw new Error('xrpLib has not been set yet'); + } + return xrpLibInstance; +}; + +export const setCoinSupportXrpLib = (xrpLib: XrpLibType) => { + xrpLibInstance = xrpLib; +}; diff --git a/packages/coin-support-xrp/tests/01.create.ts b/packages/coin-support-xrp/tests/01.create.ts new file mode 100644 index 000000000..197956e0b --- /dev/null +++ b/packages/coin-support-xrp/tests/01.create.ts @@ -0,0 +1,9 @@ +import { describe, expect, test } from '@jest/globals'; +import { XrpSupport } from '../src'; + +describe('01. Create', () => { + test('should be able to create XRP Coin Support', async () => { + const support = new XrpSupport(); + expect(support).toBeDefined(); + }); +}); diff --git a/packages/coin-support-xrp/tsconfig.eslint.json b/packages/coin-support-xrp/tsconfig.eslint.json new file mode 100644 index 000000000..078e96315 --- /dev/null +++ b/packages/coin-support-xrp/tsconfig.eslint.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "dist", + "lib": ["ES2015"] + }, + "extends": "./tsconfig.json", + "include": ["./src/**/*", "./tests/**/*"], + "exclude": ["node_modules", "src/coverage"] +} diff --git a/packages/coin-support-xrp/tsconfig.json b/packages/coin-support-xrp/tsconfig.json new file mode 100644 index 000000000..ee8ce37b3 --- /dev/null +++ b/packages/coin-support-xrp/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "outDir": "dist/esm", + "lib": ["DOM"] + }, + "extends": "@cypherock/tsconfig/browser.json", + "include": ["src/**/*"], + "exclude": [ + "node_modules", + "src/coverage", + "src/**/__fixtures__/*.ts", + "src/**/__tests__/*.ts", + "src/**/__mocks__/*.ts" + ] +} diff --git a/packages/coin-support-xrp/tsconfig_cjs.json b/packages/coin-support-xrp/tsconfig_cjs.json new file mode 100644 index 000000000..40929696b --- /dev/null +++ b/packages/coin-support-xrp/tsconfig_cjs.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "dist/cjs", + "lib": ["DOM"], + "module": "CommonJS" + }, + "extends": "@cypherock/tsconfig/browser.json", + "include": ["./src/**/*"], + "exclude": [ + "node_modules", + "src/coverage", + "src/**/__fixtures__/*.ts", + "src/**/__tests__/*.ts", + "src/**/__mocks__/*.ts" + ] +} diff --git a/packages/coin-support/package.json b/packages/coin-support/package.json index 1a4b2ea39..65d7c99e8 100644 --- a/packages/coin-support/package.json +++ b/packages/coin-support/package.json @@ -44,6 +44,7 @@ "@cypherock/coin-support-near": "workspace:^", "@cypherock/coin-support-solana": "workspace:^", "@cypherock/coin-support-tron": "workspace:^", + "@cypherock/coin-support-xrp": "workspace:^", "@cypherock/coins": "workspace:^", "@cypherock/cysync-utils": "workspace:^" }, diff --git a/packages/coin-support/src/index.ts b/packages/coin-support/src/index.ts index ccf48b245..30ee16a9b 100644 --- a/packages/coin-support/src/index.ts +++ b/packages/coin-support/src/index.ts @@ -4,6 +4,7 @@ import { CoinSupport } from '@cypherock/coin-support-interfaces'; import { NearSupport } from '@cypherock/coin-support-near'; import { SolanaSupport } from '@cypherock/coin-support-solana'; import { TronSupport } from '@cypherock/coin-support-tron'; +import { XrpSupport } from '@cypherock/coin-support-xrp'; import { coinFamiliesMap, CoinFamily } from '@cypherock/coins'; const coinSupportMap: Record = { @@ -12,6 +13,7 @@ const coinSupportMap: Record = { [coinFamiliesMap.near]: new NearSupport(), [coinFamiliesMap.solana]: new SolanaSupport(), [coinFamiliesMap.tron]: new TronSupport(), + [coinFamiliesMap.xrp]: new XrpSupport(), }; export const getCoinSupport = (coinFamily: string) => { diff --git a/packages/coin-support/tests/index.ts b/packages/coin-support/tests/index.ts index e84654187..c9ddb47f7 100644 --- a/packages/coin-support/tests/index.ts +++ b/packages/coin-support/tests/index.ts @@ -4,7 +4,7 @@ import { describe, expect, test } from '@jest/globals'; import { getCoinSupport } from '../src'; const coinFamiliesList = Object.values(coinFamiliesMap); -const unsupportedCoinFamilies = ['xrp', 'crown', 'vivo']; +const unsupportedCoinFamilies = ['crown', 'vivo']; describe('getCoinSupport', () => { describe('should return coin support for supported coin families', () => { diff --git a/packages/coins/src/aggregate.ts b/packages/coins/src/aggregate.ts index 7c49f27bb..f698f3d83 100644 --- a/packages/coins/src/aggregate.ts +++ b/packages/coins/src/aggregate.ts @@ -3,15 +3,20 @@ import { evmCoinList, EvmId, IEvmErc20Token } from './evm'; import { nearCoinList, NearId } from './near'; import { solanaCoinList, SolanaId } from './solana'; import { tronCoinList, ITronTrc20Token } from './tron'; +import { xrpCoinList, XrpId } from './xrp'; import { ICoinInfo } from './types'; -export const coinList: Record = { +export const coinList: Record< + BtcId | EvmId | SolanaId | NearId | XrpId, + ICoinInfo +> = { ...btcCoinList, ...evmCoinList, ...nearCoinList, ...solanaCoinList, ...tronCoinList, + ...xrpCoinList, }; -export type CoinTypes = BtcId | EvmId | SolanaId | NearId; +export type CoinTypes = BtcId | EvmId | SolanaId | NearId | XrpId; export type TokenTypes = IEvmErc20Token | ITronTrc20Token; diff --git a/packages/coins/src/index.ts b/packages/coins/src/index.ts index 28d2af37e..887b28b29 100644 --- a/packages/coins/src/index.ts +++ b/packages/coins/src/index.ts @@ -4,4 +4,5 @@ export * from './evm'; export * from './near'; export * from './solana'; export * from './tron'; +export * from './xrp'; export * from './types'; diff --git a/packages/coins/src/types.ts b/packages/coins/src/types.ts index 95dea0d36..2fdfe603b 100644 --- a/packages/coins/src/types.ts +++ b/packages/coins/src/types.ts @@ -10,6 +10,7 @@ export const coinFamiliesMap = { near: 'near', solana: 'solana', tron: 'tron', + xrp: 'xrp', } as const; export type CoinFamily = (typeof coinFamiliesMap)[keyof typeof coinFamiliesMap]; diff --git a/packages/coins/src/xrp/coins.ts b/packages/coins/src/xrp/coins.ts new file mode 100644 index 000000000..029eb4f4f --- /dev/null +++ b/packages/coins/src/xrp/coins.ts @@ -0,0 +1,28 @@ +export default [ + { + id: 'xrp', + abbr: 'XRP', + name: 'XRP', + isTest: false, + coinGeckoId: 'ripple', + coinIndex: '80000090', + feesUnit: 'XRP', + color: '#FFFFFF', + family: 'xrp', + network: 'mainnet', + // TODO: this should be fetched from an api eventually and shouldn't be present in coins package + reserveXrp: '10000000', // in drops + units: [ + { + name: 'xrp', + abbr: 'XRP', + magnitude: 6, + }, + { + name: 'drop', + abbr: 'drop', + magnitude: 0, + }, + ], + }, +]; diff --git a/packages/coins/src/xrp/index.ts b/packages/coins/src/xrp/index.ts new file mode 100644 index 000000000..b08f387aa --- /dev/null +++ b/packages/coins/src/xrp/index.ts @@ -0,0 +1,40 @@ +import coinList from './coins'; + +import { ICoinInfo, coinFamiliesMap } from '../types'; + +type XrpFamily = typeof coinFamiliesMap.xrp; + +export interface IXrpCoinInfo extends ICoinInfo { + family: XrpFamily; + network: string; + reserveXrp: string; +} + +export const XrpIdMap = { + xrp: 'xrp', +} as const; + +export type XrpId = (typeof XrpIdMap)[keyof typeof XrpIdMap]; + +export const xrpCoinList: Record = coinList.reduce< + Record +>( + (list, coin) => ({ + ...list, + [coin.id as XrpId]: { + family: coinFamiliesMap.xrp, + id: coin.id, + name: coin.name, + abbr: coin.abbr, + isTest: coin.isTest, + coinGeckoId: coin.coinGeckoId, + coinIndex: coin.coinIndex, + feesUnit: coin.feesUnit, + network: coin.network, + units: coin.units, + color: coin.color, + reserveXrp: coin.reserveXrp, + }, + }), + {}, +); diff --git a/packages/coins/tests/01.uniqueIds.ts b/packages/coins/tests/01.uniqueIds.ts index 5a6785b14..a760470d9 100644 --- a/packages/coins/tests/01.uniqueIds.ts +++ b/packages/coins/tests/01.uniqueIds.ts @@ -1,12 +1,19 @@ import { describe, expect, test } from '@jest/globals'; -import { btcCoinList, evmCoinList, solanaCoinList, nearCoinList } from '../src'; +import { + btcCoinList, + evmCoinList, + solanaCoinList, + nearCoinList, + xrpCoinList, +} from '../src'; describe('01. All ids should be unique', () => { const btcIds = Object.keys(btcCoinList); const evmIds = Object.keys(evmCoinList); const nearIds = Object.keys(nearCoinList); const solanaIds = Object.keys(solanaCoinList); + const xrpIds = Object.keys(xrpCoinList); const idSet = new Set(); @@ -31,6 +38,11 @@ describe('01. All ids should be unique', () => { listName: 'solanaCoinList', ids: solanaIds, }, + { + coin: 'Xrp', + listName: 'xrpCoinList', + ids: xrpIds, + }, ]; testCases.forEach(testCase => { diff --git a/packages/cysync-core-constants/src/i18n/lang/ar-AE.json b/packages/cysync-core-constants/src/i18n/lang/ar-AE.json index c90a4e7ca..3781f1cfc 100644 --- a/packages/cysync-core-constants/src/i18n/lang/ar-AE.json +++ b/packages/cysync-core-constants/src/i18n/lang/ar-AE.json @@ -258,7 +258,9 @@ "dollar": "$", "error": "أموال غير كافية", "zeroAmount": "لا يمكن أن يكون المبلغ صفرًا", - "notOverDustThreshold": "المبلغ أقل من الحد الأدنى" + "notOverDustThreshold": "المبلغ أقل من الحد الأدنى", + "amountBelowXrpReserve": "", + "balanceBelowXrpReserve": "" }, "fees": { "title": "الرسوم", @@ -269,8 +271,14 @@ "placeholder": "أدخل ملاحظاتك الشخصية هنا", "error": "تم تجاوز الحد الأقصى للحرف (120 حرفاً)" }, + "destinationTag": { + "label": "", + "placeholder": "", + "error": "" + }, "warning": "قد تلغى المعاملة إذا كانت الرسوم منخفضة جدًا", "feeError": "المعاملة ذات الرسوم 0 غير مسموح بها", + "feeBelowMinError": "", "notEnoughBalance": "أموال غير كافية لإجراء المعاملة", "toggleText": { "replace": "السماح باستبدال المعاملة (استبدال برسوم)", @@ -286,7 +294,8 @@ "amount": "المبلغ", "network": "رسوم الشبكة", "debit": "المبلغ الإجمالي الذي سيتم خصمه", - "remarks": "ملاحظات شخصية" + "remarks": "ملاحظات شخصية", + "destinationTag": "" }, "finalMessage": { "button": "تحقق من المعاملات", @@ -352,7 +361,8 @@ "feePrefix": { "optimism": "L2 " }, - "remarks": "ملاحظات شخصية" + "remarks": "ملاحظات شخصية", + "destinationTag": "" }, "noData": { "text": "لا توجد تعاملات حتى الآن", diff --git a/packages/cysync-core-constants/src/i18n/lang/de-DE.json b/packages/cysync-core-constants/src/i18n/lang/de-DE.json index 6f2cc052b..61acc7c45 100644 --- a/packages/cysync-core-constants/src/i18n/lang/de-DE.json +++ b/packages/cysync-core-constants/src/i18n/lang/de-DE.json @@ -258,7 +258,9 @@ "dollar": "$", "error": "Unzureichende Mittel", "zeroAmount": "Betrag kann nicht null sein", - "notOverDustThreshold": "Betrag ist geringer als dust limit" + "notOverDustThreshold": "Betrag ist geringer als dust limit", + "amountBelowXrpReserve": "", + "balanceBelowXrpReserve": "" }, "fees": { "title": "Gebühren", @@ -269,8 +271,14 @@ "placeholder": "Geben Sie hier Ihre persönlichen Notizen ein", "error": "Maximale Zeichengrenze überschritten (120 Zeichen)" }, + "destinationTag": { + "label": "", + "placeholder": "", + "error": "" + }, "warning": "Die Transaktion könnte abgebrochen werden, wenn die Gebühren sehr niedrig sind", "feeError": "Transaktionen ohne Gebühr sind nicht erlaubt", + "feeBelowMinError": "", "notEnoughBalance": "Unzureichende Mittel für die Transaktion", "toggleText": { "replace": "Erlauben Sie, die Transaktion zu ersetzen (Replace by fees)", @@ -286,7 +294,8 @@ "amount": "Betrag", "network": "Netzwerkgebühr", "debit": "Gesamtbetrag", - "remarks": "Persönliche Notizen" + "remarks": "Persönliche Notizen", + "destinationTag": "" }, "finalMessage": { "button": "Transaktionen prüfen", @@ -352,7 +361,8 @@ "feePrefix": { "optimism": "L2 " }, - "remarks": "Persönliche Notizen" + "remarks": "Persönliche Notizen", + "destinationTag": "" }, "noData": { "text": "Noch keine Transaktionen", diff --git a/packages/cysync-core-constants/src/i18n/lang/en.json b/packages/cysync-core-constants/src/i18n/lang/en.json index 0b0d84085..d6dcd25f3 100644 --- a/packages/cysync-core-constants/src/i18n/lang/en.json +++ b/packages/cysync-core-constants/src/i18n/lang/en.json @@ -258,7 +258,9 @@ "dollar": "$", "error": "Insufficient funds", "zeroAmount": "Amount can't be zero", - "notOverDustThreshold": "Amount is lower than dust limit" + "notOverDustThreshold": "Amount is lower than dust limit", + "amountBelowXrpReserve": "Recipient address is inactive. Send atleast 10 XRP to activate it.", + "balanceBelowXrpReserve": "Balance cannot go below 10 XRP" }, "fees": { "title": "Fees", @@ -269,8 +271,14 @@ "placeholder": "Enter your personal notes here", "error": "Maximum character limit exceeded (120 characters)" }, + "destinationTag": { + "label": "Destination Tag", + "placeholder": "Optional", + "error": "Value too high" + }, "warning": "Transaction might cancel if fees is very low", "feeError": "Transaction with 0 fee is not allowed", + "feeBelowMinError": "Transaction with fee lesser than minimum will fail", "notEnoughBalance": "Insufficient funds for transaction", "toggleText": { "replace": "Allow the transaction to be replaced (Replace by fees)", @@ -286,7 +294,8 @@ "amount": "Amount", "network": "Network Fee", "debit": "Total to debit", - "remarks": "Personal Note" + "remarks": "Personal Note", + "destinationTag": "Destination Tag" }, "finalMessage": { "button": "Check transactions", @@ -352,7 +361,8 @@ "feePrefix": { "optimism": "L2 " }, - "remarks": "Personal Note" + "remarks": "Personal Note", + "destinationTag": "Destination Tag" }, "noData": { "text": "No transactions yet", diff --git a/packages/cysync-core-constants/src/i18n/lang/id-ID.json b/packages/cysync-core-constants/src/i18n/lang/id-ID.json index 8bfa75058..354a9eeb0 100644 --- a/packages/cysync-core-constants/src/i18n/lang/id-ID.json +++ b/packages/cysync-core-constants/src/i18n/lang/id-ID.json @@ -258,7 +258,9 @@ "dollar": "$", "error": "Dana tidak mencukupi", "zeroAmount": "Jumlah tidak boleh nol", - "notOverDustThreshold": "Jumlah lebih rendah dari dust limit" + "notOverDustThreshold": "Jumlah lebih rendah dari dust limit", + "amountBelowXrpReserve": "", + "balanceBelowXrpReserve": "" }, "fees": { "title": "Biaya", @@ -269,8 +271,14 @@ "placeholder": "Masukkan catatan pribadi Anda di sini", "error": "Batas karakter maksimum terlampaui (120 karakter)" }, + "destinationTag": { + "label": "", + "placeholder": "", + "error": "" + }, "warning": "Transaksi bisa dibatalkan jika biaya terlalu rendah", "feeError": "Transaksi dengan 0 fee tidak diizinkan", + "feeBelowMinError": "", "notEnoughBalance": "Dana tidak mencukupi untuk transaksi", "toggleText": { "replace": "Izinkan transaksi untuk digantikan (Ganti dengan biaya)", @@ -286,7 +294,8 @@ "amount": "Jumlah", "network": "Biaya Jaringan", "debit": "Total yang didebet", - "remarks": "Catatan Pribadi" + "remarks": "Catatan Pribadi", + "destinationTag": "" }, "finalMessage": { "button": "Periksa transaksi", @@ -352,7 +361,8 @@ "feePrefix": { "optimism": "L2 " }, - "remarks": "Catatan Pribadi" + "remarks": "Catatan Pribadi", + "destinationTag": "" }, "noData": { "text": "Belum ada transaksi", diff --git a/packages/cysync-core-constants/src/i18n/lang/zh-CN.json b/packages/cysync-core-constants/src/i18n/lang/zh-CN.json index ec82d9778..644d1f936 100644 --- a/packages/cysync-core-constants/src/i18n/lang/zh-CN.json +++ b/packages/cysync-core-constants/src/i18n/lang/zh-CN.json @@ -258,7 +258,9 @@ "dollar": "$", "error": "资金不足", "zeroAmount": "金额不能为零", - "notOverDustThreshold": "金额低于 dust limit" + "notOverDustThreshold": "金额低于 dust limit", + "amountBelowXrpReserve": "", + "balanceBelowXrpReserve": "" }, "fees": { "title": "费用", @@ -269,8 +271,14 @@ "placeholder": "在此输入您的个人便笺", "error": "超过最大字符限制 (120 个字符)" }, + "destinationTag": { + "label": "", + "placeholder": "", + "error": "" + }, "warning": "如果 fee 非常低,交易可能会被取消", "feeError": "不允许 0 fee 的交易", + "feeBelowMinError": "", "notEnoughBalance": "交易资金不足", "toggleText": { "replace": "允许替换交易(通过 fee 替换)", @@ -286,7 +294,8 @@ "amount": "金额", "network": "网络费用", "debit": "总扣款", - "remarks": "个人笔记" + "remarks": "个人笔记", + "destinationTag": "" }, "finalMessage": { "button": "检查交易", @@ -352,7 +361,8 @@ "feePrefix": { "optimism": "L2 " }, - "remarks": "个人笔记" + "remarks": "个人笔记", + "destinationTag": "" }, "noData": { "text": "暂无交易记录", diff --git a/packages/cysync-core-constants/src/i18n/types.ts b/packages/cysync-core-constants/src/i18n/types.ts index 2c1e796e2..fd7a37015 100644 --- a/packages/cysync-core-constants/src/i18n/types.ts +++ b/packages/cysync-core-constants/src/i18n/types.ts @@ -240,15 +240,19 @@ interface LangSend { error: string; zeroAmount: string; notOverDustThreshold: string; + amountBelowXrpReserve: string; + balanceBelowXrpReserve: string; }; fees: { title: string; label: string }; warning: string; feeError: string; + feeBelowMinError: string; notEnoughBalance: string; toggleText: { replace: string; unconfirmed: string }; infoBox: string; addButton: string; remarks: { label: string; placeholder: string; error: string }; + destinationTag: { label: string; placeholder: string; error: string }; }; summary: { title: string; @@ -258,6 +262,7 @@ interface LangSend { network: string; debit: string; remarks: string; + destinationTag: string; }; finalMessage: { button: string; @@ -313,6 +318,7 @@ interface LangHistory { description: string; feePrefix: { optimism: string }; remarks: string; + destinationTag: string; }; noData: { text: string; subText: string; buttonText: string }; search: { placeholder: string; notFound: { text: string; subText: string } }; diff --git a/packages/cysync-core/package.json b/packages/cysync-core/package.json index 60de7b5f2..fab4ca6d8 100644 --- a/packages/cysync-core/package.json +++ b/packages/cysync-core/package.json @@ -60,7 +60,8 @@ "ts-jest": "^29.1.0", "tsc-alias": "^1.8.6", "typescript": "^4.9.5", - "web3": "^4.1.2" + "web3": "^4.1.2", + "xrpl": "^4.0.0" }, "dependencies": { "@aws-crypto/sha256-browser": "^4.0.0", @@ -71,6 +72,7 @@ "@cypherock/coin-support-near": "workspace:^", "@cypherock/coin-support-solana": "workspace:^", "@cypherock/coin-support-tron": "workspace:^", + "@cypherock/coin-support-xrp": "workspace:^", "@cypherock/coin-support-utils": "workspace:^", "@cypherock/coins": "workspace:^", "@cypherock/cysync-core-constants": "workspace:^", diff --git a/packages/cysync-core/scripts/dependencies/index.js b/packages/cysync-core/scripts/dependencies/index.js index bfad1f6bf..7fa13491b 100644 --- a/packages/cysync-core/scripts/dependencies/index.js +++ b/packages/cysync-core/scripts/dependencies/index.js @@ -8,3 +8,4 @@ globalThis.ethers = require('ethers'); globalThis.TronWeb = require('tronweb'); globalThis.web3 = require('web3'); globalThis.solanaWeb3 = require('@solana/web3.js'); +globalThis.xrpl = require('xrpl'); diff --git a/packages/cysync-core/src/components/CoinIcon.tsx b/packages/cysync-core/src/components/CoinIcon.tsx index e09f10b36..02ac9bb6d 100644 --- a/packages/cysync-core/src/components/CoinIcon.tsx +++ b/packages/cysync-core/src/components/CoinIcon.tsx @@ -1,5 +1,11 @@ import { getAsset } from '@cypherock/coin-support-utils'; -import { BtcIdMap, EvmIdMap, SolanaIdMap, NearIdMap } from '@cypherock/coins'; +import { + BtcIdMap, + EvmIdMap, + SolanaIdMap, + NearIdMap, + XrpIdMap, +} from '@cypherock/coins'; import { ContainerProps, Container, @@ -16,6 +22,7 @@ import { OptimismIcon, SolanaIcon, NearIcon, + XrpIcon, MediaQuery, Image, UtilsProps, @@ -53,6 +60,7 @@ const coinToIconMap: Record | undefined> = { [EvmIdMap.avalanche]: AvalancheIcon, [NearIdMap.near]: NearIcon, [SolanaIdMap.solana]: SolanaIcon, + [XrpIdMap.xrp]: XrpIcon, } as Record | undefined>; const fallbackIcon = `https://static.cypherock.com/images/fallback-crypto-icon.png`; diff --git a/packages/cysync-core/src/components/ErrorHandlerDialog.tsx b/packages/cysync-core/src/components/ErrorHandlerDialog.tsx index e41d4d7cb..f2ddd6c8b 100644 --- a/packages/cysync-core/src/components/ErrorHandlerDialog.tsx +++ b/packages/cysync-core/src/components/ErrorHandlerDialog.tsx @@ -52,7 +52,9 @@ export const ErrorHandlerDialog: React.FC = ({ } onPrimaryClick={suppressActions ? undefined : onPrimaryClick} onSecondaryClick={suppressActions ? undefined : onSecondaryClick} - title={`${errorToShow.heading} (${errorToShow.code})`} + title={`${errorToShow.heading}${ + errorToShow.code ? `(${errorToShow.code})` : '' + }`} subtext={errorToShow.subtext} deviceNavigationText={errorToShow.deviceNavigationText} advanceText={errorToShow.advanceText} diff --git a/packages/cysync-core/src/dialogs/EditAccountDialog/Dialogs/AccountDetails.tsx b/packages/cysync-core/src/dialogs/EditAccountDialog/Dialogs/AccountDetails.tsx index f45e8b027..8899dc712 100644 --- a/packages/cysync-core/src/dialogs/EditAccountDialog/Dialogs/AccountDetails.tsx +++ b/packages/cysync-core/src/dialogs/EditAccountDialog/Dialogs/AccountDetails.tsx @@ -59,7 +59,9 @@ export const AccountDetails: React.FC = () => { const accountData = useMemo( () => ({ derivationPath: selectedAccount?.derivationPath, - derivationScheme: selectedAccount?.derivationScheme, + ...(selectedAccount?.derivationScheme + ? { derivationScheme: selectedAccount?.derivationScheme } + : {}), xpubOrAddress: selectedAccount?.xpubOrAddress, }), [selectedAccount], diff --git a/packages/cysync-core/src/dialogs/HistoryDialog.tsx b/packages/cysync-core/src/dialogs/HistoryDialog.tsx index 762419c2c..77ad52979 100644 --- a/packages/cysync-core/src/dialogs/HistoryDialog.tsx +++ b/packages/cysync-core/src/dialogs/HistoryDialog.tsx @@ -406,6 +406,19 @@ export const HistoryDialog: FC = ({ txn: _txn }) => { ))} + {displayTransaction.destinationTag !== undefined && ( + + + + {displayTransaction.destinationTag} + + + + )} Promise; + error?: string; +} + +export const DestinationTagInput: React.FC = ({ + label, + placeholder, + initialValue, + onChange, + error, +}) => { + const [value, setValue] = useState(initialValue?.toString() ?? ''); + + const debouncedOnValueChange = useCallback( + lodash.debounce(onChange, 300), + [], + ); + + const handleValueChange = (newValue: string) => { + let filteredValue = newValue.replace(/[^0-9]/g, ''); + let bigNum = new BigNumber(filteredValue); + + if (bigNum.isNaN()) { + filteredValue = ''; + bigNum = new BigNumber(-1); + } + + setValue(filteredValue); + debouncedOnValueChange(bigNum.toNumber()); + }; + + return ( + + + + + + + + + + {error && ( + + {error} + + )} + + ); +}; + +DestinationTagInput.defaultProps = { + initialValue: undefined, + error: '', +}; diff --git a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/AddressAndAmountSection/SingleTransaction.tsx b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/AddressAndAmountSection/SingleTransaction.tsx index 2058bd0ef..d5782acf7 100644 --- a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/AddressAndAmountSection/SingleTransaction.tsx +++ b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/AddressAndAmountSection/SingleTransaction.tsx @@ -1,5 +1,6 @@ import { IPreparedBtcTransaction } from '@cypherock/coin-support-btc'; import { IPreparedEvmTransaction } from '@cypherock/coin-support-evm'; +import { IPreparedXrpTransaction } from '@cypherock/coin-support-xrp'; import { IPreparedTransaction } from '@cypherock/coin-support-interfaces'; import { getDefaultUnit, getParsedAmount } from '@cypherock/coin-support-utils'; import { CoinFamily } from '@cypherock/coins'; @@ -14,6 +15,7 @@ import { AmountInput } from './AmountInput'; import { NotesInput } from './NotesInput'; import { useSendDialog } from '../../../context'; +import { DestinationTagInput } from './DestinationTagInput'; interface SingleTransactionProps { disableInputs?: boolean; @@ -32,11 +34,13 @@ export const SingleTransaction: React.FC = ({ prepareAmountChanged, prepareTransactionRemarks, prepareSendMax, + prepareDestinationTag, priceConverter, updateUserInputs, prepare, getOutputError, getAmountError, + getDestinationTagError, } = useSendDialog(); useEffect(() => { @@ -68,6 +72,45 @@ export const SingleTransaction: React.FC = ({ near: () => '', solana: () => '', tron: () => '', + xrp: () => '', + }; + + const getXrpDestinationTagInputProps = () => { + const txn = transaction as IPreparedXrpTransaction; + return { + label: displayText.destinationTag.label, + placeholder: displayText.destinationTag.placeholder, + initialValue: txn?.userInputs.outputs[0]?.destinationTag, + onChange: prepareDestinationTag, + error: getDestinationTagError(), + }; + }; + + const destinationTagInputPropsMap: Record< + CoinFamily, + () => Record + > = { + bitcoin: () => ({}), + evm: () => ({}), + near: () => ({}), + solana: () => ({}), + tron: () => ({}), + xrp: getXrpDestinationTagInputProps, + }; + + const destinationTagInputMap: Partial>> = { + xrp: DestinationTagInput, + }; + + const getDestinationTagInputComponent = () => { + if (!selectedAccount) return null; + const coinFamily = selectedAccount.familyId as CoinFamily; + + const Component = destinationTagInputMap[coinFamily]; + if (!Component) return null; + + const props = destinationTagInputPropsMap[coinFamily](); + return ; }; useEffect(() => { @@ -128,6 +171,9 @@ export const SingleTransaction: React.FC = ({ converter={priceConverter} isDisabled={disableInputs} /> + + {getDestinationTagInputComponent()} + = ({ disableInputs }) => ( ); +const XrpAddressAndAmount: React.FC = ({ disableInputs }) => ( + + + +); + const defaultAnaProps = { disableInputs: undefined, }; @@ -61,6 +67,7 @@ BitcoinAddressAndAmount.defaultProps = defaultAnaProps; EvmAddressAndAmount.defaultProps = defaultAnaProps; SolanaAddressAndAmount.defaultProps = defaultAnaProps; TronAddressAndAmount.defaultProps = defaultAnaProps; +XrpAddressAndAmount.defaultProps = defaultAnaProps; const anaInputMap: Record> = { bitcoin: BitcoinAddressAndAmount, @@ -68,6 +75,7 @@ const anaInputMap: Record> = { solana: SolanaAddressAndAmount, near: SolanaAddressAndAmount, tron: TronAddressAndAmount, + xrp: XrpAddressAndAmount, }; const getAnaComponent = (coinFamily: CoinFamily, props: AnaProps) => { diff --git a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/FeesHeader.tsx b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/FeesHeader.tsx index 82075a6b3..ac1854e81 100644 --- a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/FeesHeader.tsx +++ b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/FeesHeader.tsx @@ -7,15 +7,26 @@ export interface FeesHeaderProps { initialState: boolean; onChange: (isToggled: boolean) => void; title: string; + isToggleButtonHidden?: boolean; } export const FeesHeader: React.FC = ({ initialState, onChange, title, + isToggleButtonHidden, }) => ( <> - + ); + +FeesHeader.defaultProps = { + isToggleButtonHidden: false, +}; diff --git a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/FeesTitle.tsx b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/FeesTitle.tsx index 59f33231f..7f7ade1c1 100644 --- a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/FeesTitle.tsx +++ b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/FeesTitle.tsx @@ -14,6 +14,7 @@ export const FeesTitle: React.FC = ({ initialState, onChange, title, + isToggleButtonHidden, }) => { const lang = useAppSelector(selectLanguage); const buttons = lang.strings.send.fees.header; @@ -33,13 +34,15 @@ export const FeesTitle: React.FC = ({ - - - + {!isToggleButtonHidden && ( + + + + )} ); }; diff --git a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/XrpInput.tsx b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/XrpInput.tsx new file mode 100644 index 000000000..7b9571f68 --- /dev/null +++ b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/XrpInput.tsx @@ -0,0 +1,30 @@ +import { FeesInput } from '@cypherock/cysync-ui'; +import React, { useState } from 'react'; + +interface XrpInputProps { + initialValue: number; + unit: string; + onChange: (newValue: number) => void; +} + +export const XrpInput: React.FC = ({ + initialValue, + unit, + onChange, +}) => { + const [value, setValue] = useState(initialValue); + + const handleChange = (val: number) => { + setValue(val); + onChange(val); + }; + + return ( + + ); +}; diff --git a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/index.tsx b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/index.tsx index 5e5d57101..fa0f7d5d9 100644 --- a/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/index.tsx +++ b/packages/cysync-core/src/dialogs/Send/Dialogs/Components/FeeSection/index.tsx @@ -1,5 +1,6 @@ import { IPreparedBtcTransaction } from '@cypherock/coin-support-btc'; import { IPreparedEvmTransaction } from '@cypherock/coin-support-evm'; +import { IPreparedXrpTransaction } from '@cypherock/coin-support-xrp'; import { convertToUnit, getDefaultUnit, @@ -7,7 +8,12 @@ import { getZeroUnit, formatDisplayPrice, } from '@cypherock/coin-support-utils'; -import { CoinFamily, EvmIdMap, coinList } from '@cypherock/coins'; +import { + CoinFamily, + EvmIdMap, + coinFamiliesMap, + coinList, +} from '@cypherock/coins'; import { Container, MessageBox } from '@cypherock/cysync-ui'; import { BigNumber } from '@cypherock/cysync-utils'; import lodash from 'lodash'; @@ -20,6 +26,7 @@ import { selectLanguage, selectPriceInfos, useAppSelector } from '~/store'; import { BitcoinInput } from './BitcoinInput'; import { EthereumInput } from './EthereumInput'; +import { XrpInput } from './XrpInput'; import { FeesDisplay } from './FeesDisplay'; import { FeesHeader } from './FeesHeader'; import { OptimismFeesHeader } from './OptimismFeesHeader'; @@ -29,6 +36,7 @@ import { useSendDialog } from '../../../context'; const feeInputMap: Partial>> = { bitcoin: BitcoinInput, evm: EthereumInput, + xrp: XrpInput, }; const getDefaultHeader = () => FeesHeader; const getEvmHeader = (assetId?: string) => { @@ -41,6 +49,7 @@ const feeHeaderMap: Partial< > = { bitcoin: getDefaultHeader, evm: getEvmHeader, + xrp: getDefaultHeader, }; export interface FeeSectionProps { @@ -92,12 +101,36 @@ export const FeeSection: React.FC = ({ showErrors }) => { }; }; + const getXrpProps = () => { + let { feesUnit } = coinList[selectedAccount?.assetId ?? '']; + const txn = transaction as IPreparedXrpTransaction; + let { fees } = txn.staticData; + + if (selectedAccount) { + const { amount: convertedFees, unit } = convertToUnit({ + amount: txn.staticData.fees, + fromUnitAbbr: getZeroUnit(selectedAccount.parentAssetId).abbr, + coinId: selectedAccount.parentAssetId, + toUnitAbbr: getDefaultUnit(selectedAccount.parentAssetId).abbr, + }); + fees = convertedFees; + feesUnit = unit.abbr; + } + + return { + unit: feesUnit, + initialValue: fees, + onChange: debouncedXrpPrepareFeeChanged, + }; + }; + const feeInputPropsMap: Record Record> = { bitcoin: getBitcoinProps, evm: getEthereumProps, near: () => ({}), solana: () => ({}), tron: () => ({}), + xrp: getXrpProps, }; const getFeeInputComponent = () => { @@ -111,6 +144,9 @@ export const FeeSection: React.FC = ({ showErrors }) => { return ; }; + const isToggleAllowed = (coinFamily: CoinFamily) => + coinFamily !== coinFamiliesMap.xrp; + const getFeeHeaderComponent = () => { if (!selectedAccount) return null; const coinFamily = selectedAccount.familyId as CoinFamily; @@ -123,6 +159,7 @@ export const FeeSection: React.FC = ({ showErrors }) => { title={displayText.fees.title} initialState={isTextInput} onChange={setIsTextInput} + isToggleButtonHidden={!isToggleAllowed(coinFamily)} /> ); }; @@ -180,6 +217,31 @@ export const FeeSection: React.FC = ({ showErrors }) => { [], ); + const XrpPrepareFeeChanged = async (value: number) => { + setIsFeeLoading(true); + const txn = transactionRef.current.transaction as IPreparedXrpTransaction; + let fees = value.toString(); + + if (selectedAccount) { + const { amount: convertedFees } = convertToUnit({ + amount: fees, + fromUnitAbbr: getDefaultUnit(selectedAccount.parentAssetId).abbr, + coinId: selectedAccount.parentAssetId, + toUnitAbbr: getZeroUnit(selectedAccount.parentAssetId).abbr, + }); + fees = convertedFees; + } + + txn.userInputs.fees = fees; + await prepare(txn); + setIsFeeLoading(false); + }; + + const debouncedXrpPrepareFeeChanged = useCallback( + lodash.debounce(XrpPrepareFeeChanged, 300), + [], + ); + const getFeeDetails = () => { const result = { fee: '', value: '' }; const account = selectedAccount; @@ -246,6 +308,10 @@ export const FeeSection: React.FC = ({ showErrors }) => { {!transaction?.validation.isValidFee && ( )} + {(transaction?.validation as IPreparedXrpTransaction['validation']) + .isFeeBelowMin && ( + + )} {showErrors && transaction?.validation.hasEnoughBalance === false && ( )} diff --git a/packages/cysync-core/src/dialogs/Send/Dialogs/Recipient.tsx b/packages/cysync-core/src/dialogs/Send/Dialogs/Recipient.tsx index e1cb45e66..9210e9adf 100644 --- a/packages/cysync-core/src/dialogs/Send/Dialogs/Recipient.tsx +++ b/packages/cysync-core/src/dialogs/Send/Dialogs/Recipient.tsx @@ -1,4 +1,5 @@ import { IPreparedBtcTransaction } from '@cypherock/coin-support-btc'; +import { IPreparedXrpTransaction } from '@cypherock/coin-support-xrp'; import { getDefaultUnit, getParsedAmount } from '@cypherock/coin-support-utils'; import { BlockchainIcon, @@ -24,6 +25,7 @@ import logger from '~/utils/logger'; import { AddressAndAmountSection, FeeSection } from './Components'; import { useSendDialog } from '../context'; +import { coinFamiliesMap, xrpCoinList } from '@cypherock/coins'; export const Recipient: React.FC = () => { const { @@ -42,13 +44,20 @@ export const Recipient: React.FC = () => { const getBalanceToDisplay = () => { const account = selectedAccount; if (!account) return `0`; + + let { balance } = account; + if (account.familyId === coinFamiliesMap.xrp) + balance = new BigNumber(balance) + .minus(xrpCoinList[account.assetId].reserveXrp) + .toString(); + const { amount: _amount, unit } = getParsedAmount({ coinId: account.parentAssetId, assetId: account.assetId, unitAbbr: account.unit ?? getDefaultUnit(account.parentAssetId, account.assetId).abbr, - amount: account.balance, + amount: balance, }); return `${_amount} ${unit.abbr}`; }; @@ -70,7 +79,15 @@ export const Recipient: React.FC = () => { transaction.validation.ownOutputAddressNotAllowed.every( output => !output, ) && - !transaction.validation.zeroAmountNotAllowed, + !transaction.validation.zeroAmountNotAllowed && + !(transaction.validation as IPreparedXrpTransaction['validation']) + .isBalanceBelowXrpReserve && + !(transaction.validation as IPreparedXrpTransaction['validation']) + .isAmountBelowXrpReserve && + !(transaction.validation as IPreparedXrpTransaction['validation']) + .isFeeBelowMin && + !(transaction.validation as IPreparedXrpTransaction['validation']) + .isInvalidDestinationTag, ); }, [transaction]); @@ -110,7 +127,7 @@ export const Recipient: React.FC = () => { } pt={2} text={displayText.infoBox} - altText={getBalanceToDisplay()} + altText={`~${getBalanceToDisplay()}`} textVariant="span" fontSize={12} disabledInnerFlex diff --git a/packages/cysync-core/src/dialogs/Send/Dialogs/SummaryDialog.tsx b/packages/cysync-core/src/dialogs/Send/Dialogs/SummaryDialog.tsx index 80cbcf841..37fb00644 100644 --- a/packages/cysync-core/src/dialogs/Send/Dialogs/SummaryDialog.tsx +++ b/packages/cysync-core/src/dialogs/Send/Dialogs/SummaryDialog.tsx @@ -28,6 +28,7 @@ import { selectLanguage, selectPriceInfos, useAppSelector } from '~/store'; import { useSendDialog } from '../context'; import { useLabelSuffix } from '../hooks'; +import { IPreparedXrpTransaction } from '@cypherock/coin-support-xrp'; export const SummaryDialog: React.FC = () => { const { @@ -232,6 +233,22 @@ export const SummaryDialog: React.FC = () => { return transactionDetails; }; + const getDestinationTagDetails = () => { + if (!transaction || !transaction.userInputs.outputs) return []; + const txn = transaction as IPreparedXrpTransaction; + if (txn.userInputs.outputs[0]?.destinationTag === undefined) return []; + + const destinationTagDetails = txn.userInputs.outputs + .filter(output => output.destinationTag !== undefined) + .map((output, index) => ({ + id: `destinationTag-${txn.accountId}-${index}`, + leftText: displayText.destinationTag, + rightText: output.destinationTag?.toString() ?? '', + })); + + return destinationTagDetails; + }; + const isSingleTransaction = transaction?.userInputs.outputs.length === 1; return ( @@ -259,6 +276,7 @@ export const SummaryDialog: React.FC = () => { }, { isDivider: true, id: '2' }, ...getToDetails(), + ...getDestinationTagDetails(), ...(isSingleTransaction && transaction.userInputs.outputs[0].remarks ? [...getTransactionRemarks(), { isDivider: true, id: '5' }] diff --git a/packages/cysync-core/src/dialogs/Send/context/index.tsx b/packages/cysync-core/src/dialogs/Send/context/index.tsx index 2f99eee07..861cab493 100644 --- a/packages/cysync-core/src/dialogs/Send/context/index.tsx +++ b/packages/cysync-core/src/dialogs/Send/context/index.tsx @@ -10,6 +10,7 @@ import { } from '@cypherock/coin-support-interfaces'; import { IPreparedSolanaTransaction } from '@cypherock/coin-support-solana'; import { IPreparedTronTransaction } from '@cypherock/coin-support-tron'; +import { IPreparedXrpTransaction } from '@cypherock/coin-support-xrp'; import { convertToUnit, formatDisplayAmount, @@ -103,6 +104,7 @@ export interface SendDialogContextInterface { prepareAmountChanged: (val: string) => Promise; prepareTransactionRemarks: (val: string) => Promise; prepareSendMax: (state: boolean) => Promise; + prepareDestinationTag: (tag: number) => Promise; priceConverter: (val: string, inverse?: boolean) => string; updateUserInputs: (count: number) => void; isAccountSelectionDisabled: boolean | undefined; @@ -115,6 +117,7 @@ export interface SendDialogContextInterface { defaultAccountId?: string; getOutputError: (index: number) => string; getAmountError: () => string; + getDestinationTagError: () => string; } export const SendDialogContext: Context = @@ -514,6 +517,25 @@ export const SendDialogProvider: FC = ({ return formatDisplayAmount(convertedAmount.amount).complete; }; + const prepareDestinationTag = async (tag: number) => { + const txn = transactionRef.current as IPreparedXrpTransaction; + if (!txn) return; + + const valueToSet = tag < 0 ? undefined : tag; + if (txn.userInputs.outputs.length > 0) { + txn.userInputs.outputs[0].destinationTag = valueToSet; + } else { + txn.userInputs.outputs = [ + { + address: '', + amount: '', + destinationTag: valueToSet, + }, + ]; + } + await prepare(txn); + }; + const priceConverter = (val: string, invert?: boolean) => { const coinPrice = priceInfos.find( p => @@ -575,6 +597,12 @@ export const SendDialogProvider: FC = ({ return computedData.fee || '0'; }; + const getXrpFeeAmount = (txn: IPreparedTransaction | undefined) => { + if (!txn) return '0'; + const { computedData } = txn as IPreparedXrpTransaction; + return computedData.fees || '0'; + }; + const computedFeeMap: Record< CoinFamily, (txn: IPreparedTransaction | undefined) => string @@ -584,6 +612,7 @@ export const SendDialogProvider: FC = ({ near: () => '0', solana: getSolanaFeeAmount, tron: getTronFeeAmount, + xrp: getXrpFeeAmount, }; const getComputedFee = (coinFamily: CoinFamily, txn?: IPreparedTransaction) => @@ -633,6 +662,28 @@ export const SendDialogProvider: FC = ({ return lang.strings.send.recipient.amount.error; } + const xrpValdation = + transaction?.validation as IPreparedXrpTransaction['validation']; + + if (xrpValdation?.isBalanceBelowXrpReserve) { + return lang.strings.send.recipient.amount.balanceBelowXrpReserve; + } + + if (xrpValdation?.isAmountBelowXrpReserve) { + return lang.strings.send.recipient.amount.amountBelowXrpReserve; + } + + return ''; + }, [transaction, lang]); + + const getDestinationTagError = useCallback(() => { + if ( + (transaction?.validation as IPreparedXrpTransaction['validation']) + .isInvalidDestinationTag + ) { + return lang.strings.send.recipient.destinationTag.error; + } + return ''; }, [transaction, lang]); @@ -672,6 +723,7 @@ export const SendDialogProvider: FC = ({ prepareAmountChanged, prepareTransactionRemarks, prepareSendMax, + prepareDestinationTag, priceConverter, updateUserInputs, isAccountSelectionDisabled: disableAccountSelection, @@ -679,6 +731,7 @@ export const SendDialogProvider: FC = ({ getComputedFee, getOutputError, getAmountError, + getDestinationTagError, }), [ defaultWalletId, @@ -715,6 +768,7 @@ export const SendDialogProvider: FC = ({ prepareAmountChanged, prepareTransactionRemarks, prepareSendMax, + prepareDestinationTag, priceConverter, updateUserInputs, disableAccountSelection, @@ -722,6 +776,7 @@ export const SendDialogProvider: FC = ({ getComputedFee, getOutputError, getAmountError, + getDestinationTagError, ], ); diff --git a/packages/cysync-core/src/dialogs/Send/hooks/useLabelSuffix.ts b/packages/cysync-core/src/dialogs/Send/hooks/useLabelSuffix.ts index b4266a5ed..b7098500f 100644 --- a/packages/cysync-core/src/dialogs/Send/hooks/useLabelSuffix.ts +++ b/packages/cysync-core/src/dialogs/Send/hooks/useLabelSuffix.ts @@ -17,6 +17,7 @@ export const useLabelSuffix = () => { near: getDefaultSuffix, solana: getDefaultSuffix, tron: getDefaultSuffix, + xrp: getDefaultSuffix, }; const getFeeLabelSuffix = (selectedAccount?: IAccount) => { if (!selectedAccount) return ''; diff --git a/packages/cysync-core/src/hooks/useTransactions.tsx b/packages/cysync-core/src/hooks/useTransactions.tsx index 5cef2ec6a..bd0758a86 100644 --- a/packages/cysync-core/src/hooks/useTransactions.tsx +++ b/packages/cysync-core/src/hooks/useTransactions.tsx @@ -83,6 +83,7 @@ export interface TransactionRowData { groupIcon?: React.FC<{ width: string; height: string }>; remarks: string[]; network: string; + destinationTag?: number; } export const transactionComparatorMap: Record< @@ -254,6 +255,8 @@ export const mapTransactionForDisplay = (params: { } } + const destinationTag = transaction.extraData?.destinationTag; + return { id: transaction.__id ?? '', xpubOrAddress: account?.xpubOrAddress ?? '', @@ -309,6 +312,7 @@ export const mapTransactionForDisplay = (params: { isGroupHeader: false, remarks, network: networkName, + destinationTag, }; }; diff --git a/packages/cysync-core/src/utils/dependencies/withoutCss.ts b/packages/cysync-core/src/utils/dependencies/withoutCss.ts index 506e222ce..74c7b9257 100644 --- a/packages/cysync-core/src/utils/dependencies/withoutCss.ts +++ b/packages/cysync-core/src/utils/dependencies/withoutCss.ts @@ -4,6 +4,7 @@ import { EvmSupport } from '@cypherock/coin-support-evm'; import { NearSupport } from '@cypherock/coin-support-near'; import { SolanaSupport } from '@cypherock/coin-support-solana'; import { TronSupport } from '@cypherock/coin-support-tron'; +import { XrpSupport } from '@cypherock/coin-support-xrp'; import { setWalletConnect, setWalletConnectCore } from '../walletConnect'; @@ -19,4 +20,5 @@ export const setGlobalDependencies = () => { TronSupport.setTronWeb( new (globalThis as any).TronWeb({ fullHost: 'https://api.trongrid.io' }), ); + XrpSupport.setXrpLib((globalThis as any).xrpl); }; diff --git a/packages/ui/icons/xrp-icon.svg b/packages/ui/icons/xrp-icon.svg new file mode 100644 index 000000000..194ebc10f --- /dev/null +++ b/packages/ui/icons/xrp-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15f3140b6..071f48dc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,6 +98,9 @@ importers: '@cypherock/coin-support-utils': specifier: workspace:^ version: link:../../packages/coin-support-utils + '@cypherock/coin-support-xrp': + specifier: workspace:^ + version: link:../../packages/coin-support-xrp '@cypherock/coins': specifier: workspace:^ version: link:../../packages/coins @@ -173,6 +176,9 @@ importers: semver: specifier: ^7.5.3 version: 7.5.3 + xrpl: + specifier: ^4.0.0 + version: 4.0.0 devDependencies: '@cypherock/eslint-config': specifier: workspace:^ @@ -503,6 +509,9 @@ importers: '@cypherock/coin-support-tron': specifier: workspace:^ version: link:../coin-support-tron + '@cypherock/coin-support-xrp': + specifier: workspace:^ + version: link:../coin-support-xrp '@cypherock/coins': specifier: workspace:^ version: link:../coins @@ -1205,6 +1214,106 @@ importers: specifier: ^4.9.5 version: 4.9.5 + packages/coin-support-xrp: + dependencies: + '@cypherock/coin-support-interfaces': + specifier: workspace:^ + version: link:../coin-support-interfaces + '@cypherock/coin-support-utils': + specifier: workspace:^ + version: link:../coin-support-utils + '@cypherock/coins': + specifier: workspace:^ + version: link:../coins + '@cypherock/cysync-interfaces': + specifier: workspace:^ + version: link:../interfaces + '@cypherock/cysync-utils': + specifier: workspace:^ + version: link:../utils + '@cypherock/db-interfaces': + specifier: workspace:^ + version: link:../db-interfaces + '@cypherock/sdk-app-xrp': + specifier: ^1.0.0 + version: 1.0.0 + '@cypherock/sdk-interfaces': + specifier: ^0.0.15 + version: 0.0.15 + '@cypherock/sdk-utils': + specifier: ^0.0.18 + version: 0.0.18 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + multicoin-address-validator: + specifier: ^0.5.12 + version: 0.5.12 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + devDependencies: + '@cypherock/eslint-config': + specifier: workspace:^ + version: link:../util-eslint-config + '@cypherock/jest-config': + specifier: workspace:^ + version: link:../util-jest-config + '@cypherock/prettier-config': + specifier: workspace:^ + version: link:../util-prettier-config + '@cypherock/tsconfig': + specifier: workspace:^ + version: link:../util-tsconfig + '@jest/globals': + specifier: ^29.5.0 + version: 29.7.0 + '@stryker-mutator/core': + specifier: ^7.0.2 + version: 7.0.2 + '@stryker-mutator/jest-runner': + specifier: ^7.0.2 + version: 7.0.2(@stryker-mutator/core@7.0.2) + '@stryker-mutator/typescript-checker': + specifier: ^7.0.2 + version: 7.0.2(@stryker-mutator/core@7.0.2)(typescript@4.9.5) + '@types/jest': + specifier: ^29.5.2 + version: 29.5.2 + '@types/lodash': + specifier: ^4.14.195 + version: 4.14.195 + '@types/multicoin-address-validator': + specifier: ^0.5.0 + version: 0.5.0 + '@types/node': + specifier: 18.15.11 + version: 18.15.11 + eslint: + specifier: ^8.43.0 + version: 8.43.0 + jest: + specifier: ^29.5.0 + version: 29.5.0(@types/node@18.15.11)(ts-node@10.9.1) + lint-staged: + specifier: ^13.2.2 + version: 13.2.2 + prettier: + specifier: ^2.8.8 + version: 2.8.8 + rimraf: + specifier: ^5.0.1 + version: 5.0.1 + ts-jest: + specifier: ^29.1.0 + version: 29.1.0(@babel/core@7.24.7)(esbuild@0.18.20)(jest@29.5.0)(typescript@4.9.5) + typescript: + specifier: ^4.9.5 + version: 4.9.5 + xrpl: + specifier: ^4.0.0 + version: 4.0.0 + packages/coins: devDependencies: '@cypherock/eslint-config': @@ -1382,6 +1491,9 @@ importers: '@cypherock/coin-support-utils': specifier: workspace:^ version: link:../coin-support-utils + '@cypherock/coin-support-xrp': + specifier: workspace:^ + version: link:../coin-support-xrp '@cypherock/coins': specifier: workspace:^ version: link:../coins @@ -1626,6 +1738,9 @@ importers: web3: specifier: ^4.1.2 version: 4.1.2(typescript@4.9.5)(zod@3.21.4) + xrpl: + specifier: ^4.0.0 + version: 4.0.0 packages/cysync-core-constants: dependencies: @@ -2970,6 +3085,20 @@ packages: transitivePeerDependencies: - supports-color + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.24.7): + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) + '@babel/helper-plugin-utils': 7.24.7 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/plugin-proposal-decorators@7.22.15(@babel/core@7.22.5): resolution: {integrity: sha512-kc0VvbbUyKelvzcKOSyQUSVVXS5pT3UhRB0e3c9An86MvLqs+gx0dN4asllrDluqSa3m9YyooXKGOFVomnyFkg==} engines: {node: '>=6.9.0'} @@ -2986,16 +3115,16 @@ packages: - supports-color dev: true - /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.22.5): + /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.24.7): resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.5) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) dev: true /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.22.5): @@ -3013,17 +3142,17 @@ packages: '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.22.5) dev: false - /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.22.5): + /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.24.7): resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} engines: {node: '>=6.9.0'} deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.5) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) transitivePeerDependencies: - supports-color dev: true @@ -3171,13 +3300,13 @@ packages: '@babel/helper-plugin-utils': 7.24.7 dev: true - /@babel/plugin-syntax-flow@7.24.7(@babel/core@7.22.5): + /@babel/plugin-syntax-flow@7.24.7(@babel/core@7.24.7): resolution: {integrity: sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 dev: true @@ -3301,6 +3430,7 @@ packages: dependencies: '@babel/core': 7.22.5 '@babel/helper-plugin-utils': 7.24.7 + dev: false /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7): resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} @@ -3372,6 +3502,7 @@ packages: dependencies: '@babel/core': 7.22.5 '@babel/helper-plugin-utils': 7.24.7 + dev: false /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7): resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} @@ -3827,15 +3958,15 @@ packages: '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) dev: true - /@babel/plugin-transform-flow-strip-types@7.24.7(@babel/core@7.22.5): + /@babel/plugin-transform-flow-strip-types@7.24.7(@babel/core@7.24.7): resolution: {integrity: sha512-cjRKJ7FobOH2eakx7Ja+KpJRj8+y+/SiB3ooYm/n2UJfxu0oEaOoxOinitkJcPqv9KxS0kxTGPUaR7L2XcXDXA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.22.5) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.24.7) dev: true /@babel/plugin-transform-for-of@7.24.7(@babel/core@7.22.5): @@ -4395,6 +4526,16 @@ packages: '@babel/helper-plugin-utils': 7.24.7 dev: true + /@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.7): + resolution: {integrity: sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + dev: true + /@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.22.5): resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} engines: {node: '>=6.9.0'} @@ -4405,6 +4546,16 @@ packages: '@babel/helper-plugin-utils': 7.24.7 dev: true + /@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.24.7): + resolution: {integrity: sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-plugin-utils': 7.24.7 + dev: true + /@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.22.5): resolution: {integrity: sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==} engines: {node: '>=6.9.0'} @@ -4877,16 +5028,16 @@ packages: - supports-color dev: true - /@babel/preset-flow@7.24.7(@babel/core@7.22.5): + /@babel/preset-flow@7.24.7(@babel/core@7.24.7): resolution: {integrity: sha512-NL3Lo0NorCU607zU3NwRyJbpaB6E3t0xtd3LfAQKDfkeX4/ggcDXvkmkW42QWT5owUeW/jAe4hn+2qvkV1IbfQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-transform-flow-strip-types': 7.24.7(@babel/core@7.22.5) + '@babel/plugin-transform-flow-strip-types': 7.24.7(@babel/core@7.24.7) dev: true /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.22.5): @@ -4957,6 +5108,7 @@ packages: '@babel/plugin-transform-typescript': 7.24.7(@babel/core@7.22.5) transitivePeerDependencies: - supports-color + dev: false /@babel/preset-typescript@7.22.5(@babel/core@7.24.7): resolution: {integrity: sha512-YbPaal9LxztSGhmndR46FmAbkJ/1fAsw293tSU+I5E5h+cnJ3d4GTwyUgGYmOXJYdGA+uNePle4qbaRzj2NISQ==} @@ -4974,13 +5126,13 @@ packages: - supports-color dev: true - /@babel/register@7.24.6(@babel/core@7.22.5): + /@babel/register@7.24.6(@babel/core@7.24.7): resolution: {integrity: sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 clone-deep: 4.0.1 find-cache-dir: 2.1.0 make-dir: 2.1.0 @@ -5505,6 +5657,20 @@ packages: protobufjs: 7.3.2 dev: false + /@cypherock/sdk-app-xrp@1.0.0: + resolution: {integrity: sha512-P64Bx4tkDdmfeIhOntISGA26CAe1dhCjIn4O27HMPBGfAb686Y9/fYox1m925x4vS5ThqY2qzdrKUXzJSwUIEQ==} + dependencies: + '@cypherock/sdk-core': 0.0.25 + '@cypherock/sdk-interfaces': 0.0.15 + '@cypherock/sdk-utils': 0.0.18 + long: 5.2.3 + protobufjs: 7.3.2 + xrpl: 4.0.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /@cypherock/sdk-core@0.0.25: resolution: {integrity: sha512-g/a/pGoReevjkKKwZHxkIUUmv+jIN3UZXOCAQLydRi0/NuhRT2MQlz3dUb9AVddqo0KfRlCpy9827M+/NXaAZA==} dependencies: @@ -5551,7 +5717,7 @@ packages: '@cypherock/sdk-interfaces': 0.0.15 compare-versions: 6.0.0-rc.1 protobufjs: 7.3.2 - semver: 7.6.2 + semver: 7.5.3 uuid: 9.0.0 dev: false @@ -6650,7 +6816,7 @@ packages: resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 @@ -7640,7 +7806,7 @@ packages: json5: 2.2.3 msgpackr: 1.10.2 nullthrows: 1.1.1 - semver: 7.5.3 + semver: 7.6.2 dev: true /@parcel/diagnostic@2.9.3: @@ -7819,7 +7985,7 @@ packages: '@parcel/types': 2.9.3(@parcel/core@2.9.3) '@parcel/utils': 2.9.3 '@parcel/workers': 2.9.3(@parcel/core@2.9.3) - semver: 7.5.3 + semver: 7.6.2 dev: true /@parcel/packager-css@2.9.3(@parcel/core@2.9.3): @@ -9149,7 +9315,6 @@ packages: /@scure/base@1.1.7: resolution: {integrity: sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==} - dev: true /@scure/bip32@1.4.0: resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} @@ -9157,14 +9322,12 @@ packages: '@noble/curves': 1.4.0 '@noble/hashes': 1.4.0 '@scure/base': 1.1.7 - dev: true /@scure/bip39@1.3.0: resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} dependencies: '@noble/hashes': 1.4.0 '@scure/base': 1.1.7 - dev: true /@serialport/binding-abstract@10.0.0: resolution: {integrity: sha512-1IwOMDOWqKO0csrTOv95Ah0Av012DZB8C0OF11SmE3eyh8ab1+y4/Yah/8byMAMG7TXw+2LqkNs1oZtOJGlY1Q==} @@ -9749,7 +9912,7 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/telemetry': 7.2.0 + '@storybook/telemetry': 7.6.20 react: 18.2.0 react-confetti: 6.1.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0) @@ -10479,6 +10642,22 @@ packages: - supports-color dev: false + /@storybook/csf-tools@7.6.20: + resolution: {integrity: sha512-rwcwzCsAYh/m/WYcxBiEtLpIW5OH1ingxNdF/rK9mtGWhJxXRDV8acPkFrF8rtFWIVKoOCXu5USJYmc3f2gdYQ==} + dependencies: + '@babel/generator': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + '@storybook/csf': 0.1.9 + '@storybook/types': 7.6.20 + fs-extra: 11.2.0 + recast: 0.23.9 + ts-dedent: 2.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /@storybook/csf@0.0.1: resolution: {integrity: sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==} dependencies: @@ -10902,6 +11081,22 @@ packages: - supports-color dev: true + /@storybook/telemetry@7.6.20: + resolution: {integrity: sha512-dmAOCWmOscYN6aMbhCMmszQjoycg7tUPRVy2kTaWg6qX10wtMrvEtBV29W4eMvqdsoRj5kcvoNbzRdYcWBUOHQ==} + dependencies: + '@storybook/client-logger': 7.6.20 + '@storybook/core-common': 7.6.20 + '@storybook/csf-tools': 7.6.20 + chalk: 4.1.2 + detect-package-manager: 2.0.1 + fetch-retry: 5.0.6 + fs-extra: 11.2.0 + read-pkg-up: 7.0.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + /@storybook/testing-library@0.2.0: resolution: {integrity: sha512-Ff6jNnrsosmDshgCf0Eb5Cz7IA34p/1Ps5N3Kp3598kfXpBSccSkQQvVFUXC3kIHw/isIXWPqntZuKqnWUz7Gw==} dependencies: @@ -11101,93 +11296,93 @@ packages: lodash.flatmap: 4.5.0 dev: true - /@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.22.5): + /@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true - /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.22.5): + /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true - /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.22.5): + /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true - /@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.22.5): + /@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true - /@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.22.5): + /@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true - /@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.22.5): + /@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true - /@svgr/babel-plugin-transform-react-native-svg@8.0.0(@babel/core@7.22.5): + /@svgr/babel-plugin-transform-react-native-svg@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-UKrY3860AQICgH7g+6h2zkoxeVEPLYwX/uAjmqo4PIq2FIHppwhIqZstIyTz0ZtlwreKR41O3W3BzsBBiJV2Aw==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true - /@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.22.5): + /@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} engines: {node: '>=12'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true - /@svgr/babel-preset@8.0.0(@babel/core@7.22.5): + /@svgr/babel-preset@8.0.0(@babel/core@7.24.7): resolution: {integrity: sha512-KLcjiZychInVrhs86OvcYPLTFu9L5XV2vj0XAaE1HwE3J3jLmIzRY8ttdeAg/iFyp8nhavJpafpDZTt+1LIpkQ==} engines: {node: '>=14'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.22.5) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.22.5) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.22.5) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.22.5) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.22.5) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.22.5) - '@svgr/babel-plugin-transform-react-native-svg': 8.0.0(@babel/core@7.22.5) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.22.5) + '@babel/core': 7.24.7 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.24.7) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.24.7) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.24.7) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.24.7) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.24.7) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.24.7) + '@svgr/babel-plugin-transform-react-native-svg': 8.0.0(@babel/core@7.24.7) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.24.7) dev: true /@svgr/cli@8.0.1(typescript@4.9.5): @@ -11214,8 +11409,8 @@ packages: resolution: {integrity: sha512-aJKtc+Pie/rFYsVH/unSkDaZGvEeylNv/s2cP+ta9/rYWxRVvoV/S4Qw65Kmrtah4CBK5PM6ISH9qUH7IJQCng==} engines: {node: '>=14'} dependencies: - '@babel/core': 7.22.5 - '@svgr/babel-preset': 8.0.0(@babel/core@7.22.5) + '@babel/core': 7.24.7 + '@svgr/babel-preset': 8.0.0(@babel/core@7.24.7) camelcase: 6.3.0 cosmiconfig: 8.3.6(typescript@4.9.5) snake-case: 3.0.4 @@ -11238,8 +11433,8 @@ packages: peerDependencies: '@svgr/core': '*' dependencies: - '@babel/core': 7.22.5 - '@svgr/babel-preset': 8.0.0(@babel/core@7.22.5) + '@babel/core': 7.24.7 + '@svgr/babel-preset': 8.0.0(@babel/core@7.24.7) '@svgr/core': 8.0.0(typescript@4.9.5) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 @@ -12096,7 +12291,7 @@ packages: debug: 4.3.5(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.3 + semver: 7.6.2 tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: @@ -12192,9 +12387,9 @@ packages: peerDependencies: vite: ^4.1.0-beta.0 dependencies: - '@babel/core': 7.22.5 - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.22.5) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.22.5) + '@babel/core': 7.24.7 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) magic-string: 0.27.0 react-refresh: 0.14.2 vite: 4.3.9(@types/node@18.15.11)(less@4.2.0) @@ -12751,6 +12946,26 @@ packages: requiresBuild: true dev: true + /@xrplf/isomorphic@1.0.1: + resolution: {integrity: sha512-0bIpgx8PDjYdrLFeC3csF305QQ1L7sxaWnL5y71mCvhenZzJgku9QsA+9QCXBC1eNYtxWO/xR91zrXJy2T/ixg==} + engines: {node: '>=16.0.0'} + dependencies: + '@noble/hashes': 1.4.0 + eventemitter3: 5.0.1 + ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + /@xrplf/secret-numbers@1.0.0: + resolution: {integrity: sha512-qsCLGyqe1zaq9j7PZJopK+iGTGRbk6akkg6iZXJJgxKwck0C5x5Gnwlb1HKYGOwPKyrXWpV6a2YmcpNpUFctGg==} + dependencies: + '@xrplf/isomorphic': 1.0.1 + ripple-keypairs: 2.0.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + /@xtuc/ieee754@1.2.0: resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} dev: true @@ -13106,7 +13321,7 @@ packages: minimatch: 3.1.2 read-config-file: 6.2.0 sanitize-filename: 1.6.3 - semver: 7.5.3 + semver: 7.6.2 tar: 6.2.1 temp-file: 3.4.0 transitivePeerDependencies: @@ -13424,12 +13639,12 @@ packages: deep-equal-json: 1.0.0 dev: true - /babel-core@7.0.0-bridge.0(@babel/core@7.22.5): + /babel-core@7.0.0-bridge.0(@babel/core@7.24.7): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 dev: true /babel-jest@29.7.0(@babel/core@7.24.7): @@ -13653,7 +13868,6 @@ packages: /bignumber.js@9.1.2: resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} - dev: false /bin-links@3.0.3: resolution: {integrity: sha512-zKdnMPWEdh4F5INR07/eBrodC7QrF5JKvqskjz/ZZRXg5YSAZIbn8zGhbhUrElzHBZ2fvEQdOU59RHcTG3GiwA==} @@ -14028,7 +14242,7 @@ packages: resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==} dependencies: base64-js: 1.5.1 - ieee754: 1.1.13 + ieee754: 1.2.1 isarray: 1.0.0 dev: true @@ -14037,6 +14251,7 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: true /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -14358,7 +14573,6 @@ packages: /chalk@5.3.0: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: true /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} @@ -14991,7 +15205,7 @@ packages: /crc@3.8.0(patch_hash=2elhtkirubv6k56zon6hryqaqe): resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} dependencies: - buffer: 5.2.1 + buffer: 5.7.1 patched: true /create-ecdh@4.0.4: @@ -16985,7 +17199,6 @@ packages: /eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - dev: true /events@1.1.1: resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==} @@ -19199,7 +19412,7 @@ packages: resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} engines: {node: '>=8'} dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -19526,7 +19739,7 @@ packages: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1(@types/node@18.15.11)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@20.5.1)(typescript@4.9.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -20025,17 +20238,17 @@ packages: peerDependencies: '@babel/preset-env': ^7.1.6 dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@babel/parser': 7.24.7 - '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.22.5) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.22.5) - '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.22.5) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.22.5) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.24.7) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.24.7) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.24.7) + '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) '@babel/preset-env': 7.24.7(@babel/core@7.24.7) - '@babel/preset-flow': 7.24.7(@babel/core@7.22.5) - '@babel/preset-typescript': 7.22.5(@babel/core@7.22.5) - '@babel/register': 7.24.6(@babel/core@7.22.5) - babel-core: 7.0.0-bridge.0(@babel/core@7.22.5) + '@babel/preset-flow': 7.24.7(@babel/core@7.24.7) + '@babel/preset-typescript': 7.22.5(@babel/core@7.24.7) + '@babel/register': 7.24.6(@babel/core@7.24.7) + babel-core: 7.0.0-bridge.0(@babel/core@7.24.7) chalk: 4.1.2 flow-parser: 0.238.3 graceful-fs: 4.2.11 @@ -20709,7 +20922,7 @@ packages: resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} engines: {node: '>=12'} dependencies: - chalk: 5.2.0 + chalk: 5.3.0 is-unicode-supported: 1.3.0 /log-update@4.0.0: @@ -23444,7 +23657,7 @@ packages: engines: {node: '>=12.0.0'} hasBin: true dependencies: - '@babel/core': 7.22.5 + '@babel/core': 7.24.7 '@babel/generator': 7.24.7 ast-types: 0.14.2 commander: 2.20.3 @@ -24197,6 +24410,38 @@ packages: inherits: 2.0.4 dev: true + /ripple-address-codec@5.0.0: + resolution: {integrity: sha512-de7osLRH/pt5HX2xw2TRJtbdLLWHu0RXirpQaEeCnWKY5DYHykh3ETSkofvm0aX0LJiV7kwkegJxQkmbO94gWw==} + engines: {node: '>= 16'} + dependencies: + '@scure/base': 1.1.7 + '@xrplf/isomorphic': 1.0.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + /ripple-binary-codec@2.1.0: + resolution: {integrity: sha512-q0GAx+hj3UVcDbhXVjk7qeNfgUMehlElYJwiCuIBwqs/51GVTOwLr39Ht3eNsX5ow2xPRaC5mqHwcFDvLRm6cA==} + engines: {node: '>= 16'} + 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: + resolution: {integrity: sha512-b5rfL2EZiffmklqZk1W+dvSy97v3V/C7936WxCCgDynaGPp7GE6R2XO7EU9O2LlM/z95rj870IylYnOQs+1Rag==} + engines: {node: '>= 16'} + dependencies: + '@noble/curves': 1.4.0 + '@xrplf/isomorphic': 1.0.1 + ripple-address-codec: 5.0.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + /roarr@2.15.4: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} @@ -26535,7 +26780,7 @@ packages: /unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} dependencies: - buffer: 5.2.1 + buffer: 5.7.1 through: 2.3.8 dev: true @@ -27843,6 +28088,23 @@ packages: /xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + /xrpl@4.0.0: + resolution: {integrity: sha512-VZm1lQWHQ6PheAAFGdH+ISXKvqB2hZDQ0w4ZcdAEtmqZQXtSIVQHOKPz95rEgGANbos7+XClxJ73++joPhA8Cw==} + engines: {node: '>=18.0.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.1.0 + ripple-keypairs: 2.0.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/scripts/clean.js b/scripts/clean.js index b7cca9e67..4cf11ae2d 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -20,6 +20,8 @@ const packages = { 'packages/coin-support-btc': [...commonFolders], 'packages/coin-support-solana': [...commonFolders], 'packages/coin-support-near': [...commonFolders], + 'packages/coin-support-tron': [...commonFolders], + 'packages/coin-support-xrp': [...commonFolders], 'packages/coin-support-utils': [...commonFolders], 'packages/coin-support-interfaces': [...commonFolders], 'packages/coins': [...commonFolders], diff --git a/submodules/sdk b/submodules/sdk index cc6bdb68b..117bfba4d 160000 --- a/submodules/sdk +++ b/submodules/sdk @@ -1 +1 @@ -Subproject commit cc6bdb68b22970093a85f83bd49c9ad93931d07e +Subproject commit 117bfba4d128db428415bacf75c710a96e2ebdd5