diff --git a/queue-manager/rango-preset/src/actions/createTransaction.ts b/queue-manager/rango-preset/src/actions/createTransaction.ts index 57da1fdbf2..6cceecad7d 100644 --- a/queue-manager/rango-preset/src/actions/createTransaction.ts +++ b/queue-manager/rango-preset/src/actions/createTransaction.ts @@ -1,8 +1,8 @@ import type { SwapQueueContext, SwapStorage } from '../types'; import type { ExecuterActions } from '@rango-dev/queue-manager-core'; -import type { CreateTransactionRequest } from 'rango-sdk'; import { warn } from '@rango-dev/logging-core'; +import { type CreateTransactionRequest, TransactionType } from 'rango-sdk'; import { createStepFailedEvent, @@ -71,7 +71,12 @@ export async function createTransaction( } setStorage({ ...getStorage(), swapDetails: swap }); - schedule(SwapActionTypes.EXECUTE_TRANSACTION); + + if (transaction?.blockChain === TransactionType.XRPL) { + schedule(SwapActionTypes.EXECUTE_XRPL_TRANSACTION); + } else { + schedule(SwapActionTypes.EXECUTE_TRANSACTION); + } next(); } catch (error: unknown) { swap.status = 'failed'; diff --git a/queue-manager/rango-preset/src/actions/executeXrplTransaction/constants.ts b/queue-manager/rango-preset/src/actions/executeXrplTransaction/constants.ts new file mode 100644 index 0000000000..b9d5ba0cdf --- /dev/null +++ b/queue-manager/rango-preset/src/actions/executeXrplTransaction/constants.ts @@ -0,0 +1 @@ +export const TRUST_LINE_AMOUNT = '10000000000000'; diff --git a/queue-manager/rango-preset/src/actions/executeXrplTransaction/executeXrplTransaction.ts b/queue-manager/rango-preset/src/actions/executeXrplTransaction/executeXrplTransaction.ts new file mode 100644 index 0000000000..4bedd4e6f3 --- /dev/null +++ b/queue-manager/rango-preset/src/actions/executeXrplTransaction/executeXrplTransaction.ts @@ -0,0 +1,216 @@ +import type { + SwapActionTypes, + SwapQueueContext, + SwapStorage, +} from '../../types'; +import type { NextTransactionStateError } from '../common/produceNextStateForTransaction'; +import type { ExecuterActions } from '@rango-dev/queue-manager-core'; +import type { + GenericSigner, + XrplTransaction, + XrplTrustSetTransactionData, +} from 'rango-types'; + +import { isXrplTransaction } from 'rango-types'; +import { Err } from 'ts-results'; + +import { + getCurrentStep, + getCurrentStepTx, + handleRejectedSign, + handleSuccessfulSign, +} from '../../helpers'; +import { getCurrentAddressOf, getRelatedWallet } from '../../shared'; +import { checkEnvironmentBeforeExecuteTransaction } from '../common/checkEnvironmentBeforeExecuteTransaction'; +import { + onNextStateError, + onNextStateOk, + produceNextStateForTransaction, +} from '../common/produceNextStateForTransaction'; +import { requestBlockQueue } from '../common/utils'; + +import { TRUST_LINE_AMOUNT } from './constants'; +import { extractFromSymbolAddress } from './helpers'; + +export async function executeXrplTransaction( + actions: ExecuterActions +): Promise { + const checkResult = await checkEnvironmentBeforeExecuteTransaction(actions); + if (checkResult.err) { + requestBlockQueue(actions, checkResult.val); + return; + } + + const { failed, getStorage, context } = actions; + const { meta, getSigners } = context; + + const swap = getStorage().swapDetails; + const currentStep = getCurrentStep(swap)!; + const onFinish = () => { + if (actions.context.resetClaimedBy) { + actions.context.resetClaimedBy(); + } + }; + + const handleErr = (err: Err) => { + onNextStateError(actions, err.val); + failed(); + onFinish(); + }; + + /* + * Checking the current transaction state to determine the next step. + * It will either be Err, indicating process should stop, or Ok, indicating process should continue. + */ + const nextStateResult = produceNextStateForTransaction(actions); + const tx = getCurrentStepTx(currentStep); + + if (nextStateResult.err) { + handleErr(nextStateResult); + return; + } + + // This has been checked inproduceNextStateForTransaction. Checking it in here would make it more resilient for future changes. + if (!tx) { + handleErr( + new Err({ + nextStatus: 'failed', + nextStepStatus: 'failed', + message: 'Unexpected Error: tx is null!', + details: undefined, + errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR', + }) + ); + return; + } + + if (!isXrplTransaction(tx)) { + handleErr( + new Err({ + nextStatus: 'failed', + nextStepStatus: 'failed', + message: + "Unexpected Error: Expected XRPL transaction but it doesn't match with the structure.", + details: undefined, + errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR', + }) + ); + return; + } + + if (tx.data.TransactionType !== 'Payment') { + handleErr( + new Err({ + nextStatus: 'failed', + nextStepStatus: 'failed', + message: + 'Unexpected Error: We only support XRPL transactions with payment type', + details: undefined, + errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR', + }) + ); + return; + } + + // On success, we should update Swap object and also call notifier + onNextStateOk(actions, nextStateResult.val); + + const sourceWallet = getRelatedWallet(swap, currentStep); + const walletAddress = getCurrentAddressOf(swap, currentStep); + + const chainId = meta.blockchains?.[tx.blockChain]?.chainId; + const walletSigners = await getSigners(sourceWallet.walletType); + + const transactionQueue: XrplTransaction[] = [tx]; + + // null means it's native token (xrpl). + if (currentStep.toSymbolAddress) { + const [currency, account] = extractFromSymbolAddress( + currentStep.toSymbolAddress + ); + if (!currency || !account) { + handleErr( + new Err({ + nextStatus: 'failed', + nextStepStatus: 'failed', + message: 'Unexpected token format for XRPL transaction.', + details: undefined, + errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR', + }) + ); + return; + } + + // We need to work with namespace instance + const provider = context.hubProvider(sourceWallet.walletType); + const xrplNamespace = provider.get('xrpl'); + if (!xrplNamespace) { + handleErr( + new Err({ + nextStatus: 'failed', + nextStepStatus: 'failed', + message: 'XRPL is not available on your wallet.', + details: undefined, + errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR', + }) + ); + return; + } + + // Check if a trust line exists or not and also have capacity or not, if not, open a new one. + + // TODO: it's better to add some logic around `balance` to ensure we have enough capacity for the trust line. Now we only check it's already open or not. + const lines = await xrplNamespace.accountLines(sourceWallet.address, { + peer: account, + }); + const isTruslineAlreadyOpened = lines.some((trustline) => { + return ( + trustline.currency === currency && + trustline.account === account && + trustline.limit !== '0' + ); + }); + + if (!isTruslineAlreadyOpened) { + const trustlineTx: XrplTrustSetTransactionData = { + TransactionType: 'TrustSet', + Account: tx.data.Account, + LimitAmount: { + currency: currency, + issuer: account, + value: TRUST_LINE_AMOUNT, + }, + }; + + transactionQueue.unshift({ + ...tx, + data: trustlineTx, + }); + } + } + + const signer: GenericSigner = walletSigners.getSigner( + tx.type + ); + + for (const transaction of transactionQueue) { + try { + const result = await signer.signAndSendTx( + transaction, + walletAddress, + chainId + ); + + // TODO: approval has different meaning for EVM, we may need to add a third type called trustline for the following function. + handleSuccessfulSign(actions, { + isApproval: transaction.data.TransactionType === 'TrustSet', + })(result); + } catch (e) { + handleRejectedSign(actions)(e); + break; + } + } + + // this works as `finally` for the iterator. + onFinish(); +} diff --git a/queue-manager/rango-preset/src/actions/executeXrplTransaction/helpers.ts b/queue-manager/rango-preset/src/actions/executeXrplTransaction/helpers.ts new file mode 100644 index 0000000000..29f66a12ec --- /dev/null +++ b/queue-manager/rango-preset/src/actions/executeXrplTransaction/helpers.ts @@ -0,0 +1,8 @@ +// Extracting currency and account from the following format (it's a convention on rango's backend): currency-accountAddress +export function extractFromSymbolAddress( + symbolAddress: string +): [string, string | undefined] { + const [currency, account] = symbolAddress.split('-'); + + return [currency, account]; +} diff --git a/queue-manager/rango-preset/src/actions/executeXrplTransaction/index.ts b/queue-manager/rango-preset/src/actions/executeXrplTransaction/index.ts new file mode 100644 index 0000000000..7b17bbb42b --- /dev/null +++ b/queue-manager/rango-preset/src/actions/executeXrplTransaction/index.ts @@ -0,0 +1 @@ +export { executeXrplTransaction } from './executeXrplTransaction.js'; diff --git a/queue-manager/rango-preset/src/actions/scheduleNextStep.ts b/queue-manager/rango-preset/src/actions/scheduleNextStep.ts index f2193386c8..67160f1dcd 100644 --- a/queue-manager/rango-preset/src/actions/scheduleNextStep.ts +++ b/queue-manager/rango-preset/src/actions/scheduleNextStep.ts @@ -1,6 +1,7 @@ import type { SwapQueueContext, SwapStorage } from '../types'; import type { ExecuterActions } from '@rango-dev/queue-manager-core'; -import type { PendingSwapStep } from 'rango-types'; + +import { type PendingSwapStep, TransactionType } from 'rango-types'; import { getCurrentStep, @@ -37,7 +38,11 @@ export function scheduleNextStep({ if (!!currentStep && !isFailed) { if (isTxAlreadyCreated(swap, currentStep)) { - schedule(SwapActionTypes.EXECUTE_TRANSACTION); + if (currentStep.fromBlockchain === TransactionType.XRPL) { + schedule(SwapActionTypes.EXECUTE_XRPL_TRANSACTION); + } else { + schedule(SwapActionTypes.EXECUTE_TRANSACTION); + } return next(); } diff --git a/queue-manager/rango-preset/src/helpers.ts b/queue-manager/rango-preset/src/helpers.ts index f94712b763..786285161a 100644 --- a/queue-manager/rango-preset/src/helpers.ts +++ b/queue-manager/rango-preset/src/helpers.ts @@ -174,6 +174,7 @@ export const getCurrentStepTx = ( tronTransaction, tonTransaction, suiTransaction, + xrplTransaction, } = currentStep; return ( evmTransaction || @@ -186,7 +187,8 @@ export const getCurrentStepTx = ( tronApprovalTransaction || tronTransaction || tonTransaction || - suiTransaction + suiTransaction || + xrplTransaction ); }; @@ -210,6 +212,7 @@ export const setCurrentStepTx = ( currentStep.tronTransaction = null; currentStep.tonTransaction = null; currentStep.suiTransaction = null; + currentStep.xrplTransaction = null; const txType = transaction.type; switch (txType) { @@ -774,6 +777,7 @@ export const isTxAlreadyCreated = ( swap.wallets[step.solanaTransaction?.blockChain || ''] || swap.wallets[step.tonTransaction?.blockChain || ''] || swap.wallets[step.suiTransaction?.blockChain || ''] || + swap.wallets[step.xrplTransaction?.blockChain || ''] || step.transferTransaction?.fromWalletAddress || null; @@ -1104,11 +1108,11 @@ export function handleRejectedSign( const currentStep = getCurrentStep(swap)!; - const sourceWallet = getRelatedWallet(swap, currentStep); if (swap.status === 'failed') { return; } + const sourceWallet = getRelatedWallet(swap, currentStep); const { extraMessage, extraMessageDetail, extraMessageErrorCode } = prettifyErrorMessage(error); diff --git a/queue-manager/rango-preset/src/queueDef.ts b/queue-manager/rango-preset/src/queueDef.ts index d8560bb622..3db190d996 100644 --- a/queue-manager/rango-preset/src/queueDef.ts +++ b/queue-manager/rango-preset/src/queueDef.ts @@ -1,7 +1,9 @@ -import { BlockReason, SwapActionTypes, SwapQueueDef } from './types'; +import type { SwapQueueDef } from './types'; + import { checkStatus } from './actions/checkStatus'; import { createTransaction } from './actions/createTransaction'; import { executeTransaction } from './actions/executeTransaction'; +import { executeXrplTransaction } from './actions/executeXrplTransaction'; import { scheduleNextStep } from './actions/scheduleNextStep'; import { start } from './actions/start'; import { @@ -9,6 +11,7 @@ import { onBlockForConnectWallet, onDependsOnOtherQueues, } from './helpers'; +import { BlockReason, SwapActionTypes } from './types'; /** * @@ -24,6 +27,7 @@ export const swapQueueDef: SwapQueueDef = { [SwapActionTypes.SCHEDULE_NEXT_STEP]: scheduleNextStep, [SwapActionTypes.CREATE_TRANSACTION]: createTransaction, [SwapActionTypes.EXECUTE_TRANSACTION]: executeTransaction, + [SwapActionTypes.EXECUTE_XRPL_TRANSACTION]: executeXrplTransaction, [SwapActionTypes.CHECK_TRANSACTION_STATUS]: checkStatus, }, run: [SwapActionTypes.START], diff --git a/queue-manager/rango-preset/src/shared.ts b/queue-manager/rango-preset/src/shared.ts index 74bc0d6994..76dff54d18 100644 --- a/queue-manager/rango-preset/src/shared.ts +++ b/queue-manager/rango-preset/src/shared.ts @@ -118,6 +118,7 @@ export const getCurrentNamespaceOf = ( const solanaNetwork = step.solanaTransaction?.blockChain; const tonNetwork = step.tonTransaction?.blockChain; const suiNetwork = step.suiTransaction?.blockChain; + const xrplNetwork = step.xrplTransaction?.blockChain; if (evmNetwork) { return { @@ -154,6 +155,11 @@ export const getCurrentNamespaceOf = ( namespace: 'Sui', network: suiNetwork, }; + } else if (xrplNetwork) { + return { + namespace: 'XRPL', + network: xrplNetwork, + }; } else if (!!step.transferTransaction) { const transferAddress = step.transferTransaction.fromWalletAddress; if (!transferAddress) { @@ -232,6 +238,7 @@ export const getCurrentWalletTypeAndAddress = ( swap.wallets[step.solanaTransaction?.blockChain || ''] || swap.wallets[step.tonTransaction?.blockChain || ''] || swap.wallets[step.suiTransaction?.blockChain || ''] || + swap.wallets[step.xrplTransaction?.blockChain || ''] || (step.transferTransaction?.fromWalletAddress ? { address: step.transferTransaction.fromWalletAddress, @@ -291,7 +298,8 @@ export function getRelatedWalletOrNull( } try { return getRelatedWallet(swap, currentStep); - } catch { + } catch (e) { + console.log({ e }); return null; } } diff --git a/queue-manager/rango-preset/src/types.ts b/queue-manager/rango-preset/src/types.ts index 8a8113a0a2..ca653e4d0f 100644 --- a/queue-manager/rango-preset/src/types.ts +++ b/queue-manager/rango-preset/src/types.ts @@ -43,6 +43,7 @@ export enum SwapActionTypes { SCHEDULE_NEXT_STEP = 'SCHEDULE_NEXT_STEP', CREATE_TRANSACTION = 'CREATE_TRANSACTION', EXECUTE_TRANSACTION = 'EXECUTE_TRANSACTION', + EXECUTE_XRPL_TRANSACTION = 'EXECUTE_XRPL_TRANSACTION', CHECK_TRANSACTION_STATUS = 'CHECK_TRANSACTION_STATUS', } diff --git a/wallets/core/namespaces/xrpl/package.json b/wallets/core/namespaces/xrpl/package.json new file mode 100644 index 0000000000..85bece8b6a --- /dev/null +++ b/wallets/core/namespaces/xrpl/package.json @@ -0,0 +1,8 @@ +{ + "name": "@rango-dev/wallets-core/namespaces/xrpl", + "type": "module", + "main": "../../dist/namespaces/xrpl/mod.js", + "module": "../../dist/namespaces/xrpl/mod.js", + "types": "../../dist/namespaces/xrpl/mod.d.ts", + "sideEffects": false +} diff --git a/wallets/core/package.json b/wallets/core/package.json index 205f5ebe07..5733512d23 100644 --- a/wallets/core/package.json +++ b/wallets/core/package.json @@ -46,6 +46,10 @@ "./namespaces/utxo": { "types": "./dist/namespaces/utxo/mod.d.ts", "default": "./dist/namespaces/utxo/mod.js" + }, + "./namespaces/xrpl": { + "types": "./dist/namespaces/xrpl/mod.d.ts", + "default": "./dist/namespaces/xrpl/mod.js" } }, "files": [ @@ -54,7 +58,7 @@ "legacy" ], "scripts": { - "build": "node ../../scripts/build/command.mjs --path wallets/core --inputs src/mod.ts,src/utils/mod.ts,src/legacy/mod.ts,src/hub/store/mod.ts,src/namespaces/evm/mod.ts,src/namespaces/solana/mod.ts,src/namespaces/cosmos/mod.ts,src/namespaces/utxo/mod.ts,src/namespaces/sui/mod.ts,src/namespaces/common/mod.ts", + "build": "node ../../scripts/build/command.mjs --path wallets/core --inputs src/mod.ts,src/utils/mod.ts,src/legacy/mod.ts,src/hub/store/mod.ts,src/namespaces/evm/mod.ts,src/namespaces/solana/mod.ts,src/namespaces/cosmos/mod.ts,src/namespaces/utxo/mod.ts,src/namespaces/sui/mod.ts,src/namespaces/xrpl/mod.ts,src/namespaces/common/mod.ts", "ts-check": "tsc --declaration --emitDeclarationOnly -p ./tsconfig.json", "clean": "rimraf dist", "format": "prettier --write '{.,src}/**/*.{ts,tsx}'", @@ -66,7 +70,8 @@ "@mysten/wallet-standard": "^0.13.26", "@types/react": "^17.0.0 || ^18.0.0", "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "react-dom": "^17.0.0 || ^18.0.0", + "xrpl": "^4.2.0" }, "dependencies": { "caip": "^1.1.1", @@ -74,6 +79,9 @@ "rango-types": "^0.1.89", "zustand": "^4.5.2" }, + "devDependencies": { + "xrpl": "^4.2.0" + }, "publishConfig": { "access": "public" } diff --git a/wallets/core/src/hub/provider/types.ts b/wallets/core/src/hub/provider/types.ts index 13e49aac4a..678a1e9063 100644 --- a/wallets/core/src/hub/provider/types.ts +++ b/wallets/core/src/hub/provider/types.ts @@ -6,6 +6,7 @@ import type { EvmActions } from '../../namespaces/evm/mod.js'; import type { SolanaActions } from '../../namespaces/solana/mod.js'; import type { SuiActions } from '../../namespaces/sui/mod.js'; import type { UtxoActions } from '../../namespaces/utxo/mod.js'; +import type { XRPLActions } from '../../namespaces/xrpl/mod.js'; import type { AnyFunction, FunctionWithContext } from '../../types/actions.js'; import type { Prettify } from '../../types/utils.js'; @@ -32,6 +33,7 @@ export interface CommonNamespaces { cosmos: CosmosActions; sui: SuiActions; utxo: UtxoActions; + xrpl: XRPLActions; } export type CommonNamespaceKeys = Prettify; diff --git a/wallets/core/src/legacy/types.ts b/wallets/core/src/legacy/types.ts index d150c32a4d..b83d9694b2 100644 --- a/wallets/core/src/legacy/types.ts +++ b/wallets/core/src/legacy/types.ts @@ -62,6 +62,8 @@ export enum Networks { TON = 'TON', BASE = 'BASE', SUI = 'SUI', + XRPL = 'XRPL', + // Using instead of null Unknown = 'Unkown', } diff --git a/wallets/core/src/namespaces/common/types.ts b/wallets/core/src/namespaces/common/types.ts index 0f3131fabc..48b4fe7f73 100644 --- a/wallets/core/src/namespaces/common/types.ts +++ b/wallets/core/src/namespaces/common/types.ts @@ -10,7 +10,8 @@ type RangoNamespace = | 'Starknet' | 'Tron' | 'Ton' - | 'Sui'; + | 'Sui' + | 'XRPL'; export type Namespace = RangoNamespace | (string & {}); diff --git a/wallets/core/src/namespaces/xrpl/builders.ts b/wallets/core/src/namespaces/xrpl/builders.ts new file mode 100644 index 0000000000..630e4dbc8d --- /dev/null +++ b/wallets/core/src/namespaces/xrpl/builders.ts @@ -0,0 +1,14 @@ +import type { XRPLActions } from './types.js'; + +import { ActionBuilder } from '../../mod.js'; +import { + connectAndUpdateStateForSingleNetwork, + intoConnecting, + intoConnectionFinished, +} from '../common/mod.js'; + +export const connect = () => + new ActionBuilder('connect') + .and(connectAndUpdateStateForSingleNetwork) + .before(intoConnecting) + .after(intoConnectionFinished); diff --git a/wallets/core/src/namespaces/xrpl/constants.ts b/wallets/core/src/namespaces/xrpl/constants.ts new file mode 100644 index 0000000000..d106684ed3 --- /dev/null +++ b/wallets/core/src/namespaces/xrpl/constants.ts @@ -0,0 +1,7 @@ +/* + * @see https://namespaces.chainagnostic.org/xrpl/README + */ + +// if the value neeeds to be change make sure you will update mapCaipNamespaceToLegacyNetworkName as well +export const CAIP_NAMESPACE = 'xrpl'; +export const CAIP_XRPL_CHAIN_ID = 'mainnet'; diff --git a/wallets/core/src/namespaces/xrpl/mod.ts b/wallets/core/src/namespaces/xrpl/mod.ts new file mode 100644 index 0000000000..62581966b3 --- /dev/null +++ b/wallets/core/src/namespaces/xrpl/mod.ts @@ -0,0 +1,5 @@ +export * as builders from './builders.js'; +export type { XRPLActions } from './types.js'; + +export * as utils from './utils.js'; +export { CAIP_XRPL_CHAIN_ID, CAIP_NAMESPACE } from './constants.js'; diff --git a/wallets/core/src/namespaces/xrpl/types.ts b/wallets/core/src/namespaces/xrpl/types.ts new file mode 100644 index 0000000000..d3aa832303 --- /dev/null +++ b/wallets/core/src/namespaces/xrpl/types.ts @@ -0,0 +1,17 @@ +import type { Accounts } from '../common/mod.js'; +import type { + AutoImplementedActionsByRecommended, + CommonActions, +} from '../common/types.js'; +import type { AccountLinesTrustline } from 'xrpl'; + +export interface XRPLActions + extends AutoImplementedActionsByRecommended, + CommonActions { + connect: () => Promise; + canEagerConnect: () => Promise; + accountLines: ( + account: string, + options?: { peer?: string } + ) => Promise; +} diff --git a/wallets/core/src/namespaces/xrpl/utils.ts b/wallets/core/src/namespaces/xrpl/utils.ts new file mode 100644 index 0000000000..581fc48faf --- /dev/null +++ b/wallets/core/src/namespaces/xrpl/utils.ts @@ -0,0 +1,15 @@ +import type { CaipAccount } from '../common/mod.js'; + +import { AccountId } from 'caip'; + +import { CAIP_NAMESPACE, CAIP_XRPL_CHAIN_ID } from './constants.js'; + +export function formatAddressToCAIP(address: string): string { + return AccountId.format({ + address, + chainId: { + namespace: CAIP_NAMESPACE, + reference: CAIP_XRPL_CHAIN_ID, + }, + }) as CaipAccount; +} diff --git a/wallets/provider-all/package.json b/wallets/provider-all/package.json index 6293f90aa3..4610b62830 100644 --- a/wallets/provider-all/package.json +++ b/wallets/provider-all/package.json @@ -32,6 +32,7 @@ "@rango-dev/provider-enkrypt": "^0.52.1-next.1", "@rango-dev/provider-exodus": "^0.53.1-next.1", "@rango-dev/provider-frontier": "^0.53.1-next.1", + "@rango-dev/provider-gemwallet": "^0.1.0", "@rango-dev/provider-halo": "^0.52.1-next.1", "@rango-dev/provider-keplr": "^0.52.1-next.1", "@rango-dev/provider-leap-cosmos": "^0.52.1-next.1", @@ -63,4 +64,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/wallets/provider-all/src/index.ts b/wallets/provider-all/src/index.ts index 5d6d04bd7d..9e2e086e59 100644 --- a/wallets/provider-all/src/index.ts +++ b/wallets/provider-all/src/index.ts @@ -15,6 +15,7 @@ import * as defaultInjected from '@rango-dev/provider-default'; import * as enkrypt from '@rango-dev/provider-enkrypt'; import { versions as exodus } from '@rango-dev/provider-exodus'; import * as frontier from '@rango-dev/provider-frontier'; +import { versions as gemwallet } from '@rango-dev/provider-gemwallet'; import * as halo from '@rango-dev/provider-halo'; import * as keplr from '@rango-dev/provider-keplr'; import * as leapCosmos from '@rango-dev/provider-leap-cosmos'; @@ -132,5 +133,6 @@ export const allProviders = ( lazyProvider(legacyProviderImportsToVersionsInterface(solflare)), slush, unisat, + gemwallet, ]; }; diff --git a/wallets/provider-gemwallet/package.json b/wallets/provider-gemwallet/package.json new file mode 100644 index 0000000000..68f10acbf0 --- /dev/null +++ b/wallets/provider-gemwallet/package.json @@ -0,0 +1,32 @@ +{ + "name": "@rango-dev/provider-gemwallet", + "version": "0.1.0", + "license": "MIT", + "type": "module", + "source": "./src/mod.ts", + "main": "./dist/mod.js", + "exports": { + ".": "./dist/mod.js" + }, + "typings": "dist/mod.d.ts", + "files": [ + "dist", + "src" + ], + "scripts": { + "build": "node ../../scripts/build/command.mjs --path wallets/provider-gemwallet --inputs src/mod.ts", + "ts-check": "tsc --declaration --emitDeclarationOnly -p ./tsconfig.json", + "clean": "rimraf dist", + "format": "prettier --write '{.,src}/**/*.{ts,tsx}'", + "lint": "eslint \"**/*.{ts,tsx}\"" + }, + "dependencies": { + "@rango-dev/wallets-shared": "0.52.1-next.1", + "@gemwallet/api": "^3.8.0", + "xrpl": "^4.2.0", + "rango-types": "^0.1.89" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/wallets/provider-gemwallet/readme.md b/wallets/provider-gemwallet/readme.md new file mode 100644 index 0000000000..d0d1755f8c --- /dev/null +++ b/wallets/provider-gemwallet/readme.md @@ -0,0 +1,18 @@ +# GemWallet Provider +GemWallet integration for hub. +[Homepage](https://gemwallet.app/) | [Docs](https://gemwallet.app/docs/user-guide/introduction) + +More about implementation status can be found [here](../readme.md). + +## Implementation notes/limitation + +### Feature + +#### ⚠️ Auto Connect + +It doesn't have the feature to silently connect to wallet, it shows a popup and a loading for a few seconds. + + +--- + +More wallet information can be found in [readme.md](../readme.md). diff --git a/wallets/provider-gemwallet/src/constants.ts b/wallets/provider-gemwallet/src/constants.ts new file mode 100644 index 0000000000..7ba29c4f47 --- /dev/null +++ b/wallets/provider-gemwallet/src/constants.ts @@ -0,0 +1,40 @@ +import type { BlockchainMeta } from 'rango-types'; + +import { type ProviderMetadata } from '@rango-dev/wallets-core'; +import { Networks } from '@rango-dev/wallets-shared'; + +import getSigners from './signer.js'; + +export const XRPL_PUBLIC_SERVER = 'wss://xrplcluster.com/'; +export const WALLET_ID = 'gemwallet'; + +export const info: ProviderMetadata = { + name: 'GemWallet', + icon: 'https://raw.githubusercontent.com/rango-exchange/assets/main/wallets/gemwallet/icon.svg', + extensions: { + chrome: + 'https://chromewebstore.google.com/detail/gemwallet/egebedonbdapoieedfcfkofloclfghab', + homepage: 'https://gemwallet.app/', + }, + properties: [ + { + name: 'namespaces', + value: { + selection: 'multiple', + data: [ + { + label: 'XRPL', + value: 'XRPL', + id: 'XRPL', + getSupportedChains: (allBlockchains: BlockchainMeta[]) => + allBlockchains.filter((chain) => chain.name === Networks.XRPL), + }, + ], + }, + }, + { + name: 'signers', + value: { getSigners: async () => getSigners() }, + }, + ], +}; diff --git a/wallets/provider-gemwallet/src/mod.ts b/wallets/provider-gemwallet/src/mod.ts new file mode 100644 index 0000000000..3ac86548e6 --- /dev/null +++ b/wallets/provider-gemwallet/src/mod.ts @@ -0,0 +1,8 @@ +import { defineVersions } from '@rango-dev/wallets-core/utils'; + +import { buildProvider } from './provider.js'; + +const versions = () => + defineVersions().version('1.0.0', buildProvider()).build(); + +export { versions }; diff --git a/wallets/provider-gemwallet/src/namespaces/xrpl/helpers.ts b/wallets/provider-gemwallet/src/namespaces/xrpl/helpers.ts new file mode 100644 index 0000000000..02f0722233 --- /dev/null +++ b/wallets/provider-gemwallet/src/namespaces/xrpl/helpers.ts @@ -0,0 +1,88 @@ +import type { + Memo, + SendPaymentRequest, + SetTrustlineRequest, +} from '@gemwallet/api'; +import type { + XrplPaymentTransactionData, + XrplTransactionDataIssuedCurrencyAmount, + XrplTransactionDataMPTAmount, + XrplTrustSetTransactionData, +} from 'rango-types/mainApi'; + +function isIssuedCurrencyAmount( + amount: XrplPaymentTransactionData['Amount'] +): amount is XrplTransactionDataIssuedCurrencyAmount { + return ( + typeof amount === 'object' && + // @ts-expect-error it never throw an runtime error, since we are checking it should be an object first + typeof amount.currency === 'string' && + // @ts-expect-error it never throw an runtime error, since we are checking it should be an object first + typeof amount.issuer === 'string' && + typeof amount.value === 'string' + ); +} + +function isMPTokenAmount( + amount: XrplPaymentTransactionData['Amount'] +): amount is XrplTransactionDataMPTAmount { + return ( + typeof amount === 'object' && + // @ts-expect-error it never throw an runtime error, since we are checking it should be an object first + typeof amount.mpt_issuance_id === 'string' && + typeof amount.value === 'string' + ); +} + +function fromPaymentTransactionMemoToGemWalletMemo( + memos: XrplPaymentTransactionData['Memos'] +): Memo[] { + if (!memos) { + return []; + } + + return memos.map((memo) => { + return { + memo: { + memoType: memo.Memo.MemoType, + memoData: memo.Memo.MemoData, + memoFormat: memo.Memo.MemoFormat, + }, + }; + }); +} + +export function fromTrustSetTransactionDataToGemWalletRequest( + data: XrplTrustSetTransactionData +): SetTrustlineRequest { + return { + limitAmount: data.LimitAmount, + memos: fromPaymentTransactionMemoToGemWalletMemo(data.Memos), + flags: data.Flags, + }; +} + +export function fromPaymentTransactionDataToGemWalletRequest( + data: XrplPaymentTransactionData +): SendPaymentRequest { + let amount: SendPaymentRequest['amount']; + if (isMPTokenAmount(data.Amount)) { + throw new Error("Current implemented signer doesn't have support for MPT"); + } else if (isIssuedCurrencyAmount(data.Amount)) { + amount = data.Amount; + } else if (typeof data.Amount === 'string') { + amount = data.Amount; + } else { + throw new Error( + "There is an unexpected type for Amount. current signer doesn't have support for that." + ); + } + + return { + amount, + destination: data.Destination, + destinationTag: data.DestinationTag, + memos: fromPaymentTransactionMemoToGemWalletMemo(data.Memos), + flags: data.Flags, + }; +} diff --git a/wallets/provider-gemwallet/src/namespaces/xrpl/hooks.ts b/wallets/provider-gemwallet/src/namespaces/xrpl/hooks.ts new file mode 100644 index 0000000000..2865ffd5a1 --- /dev/null +++ b/wallets/provider-gemwallet/src/namespaces/xrpl/hooks.ts @@ -0,0 +1,31 @@ +import type { XRPLActions } from '@rango-dev/wallets-core/namespaces/xrpl'; + +import { on } from '@gemwallet/api'; +import { ChangeAccountSubscriberBuilder } from '@rango-dev/wallets-core/namespaces/common'; +import { utils } from '@rango-dev/wallets-core/namespaces/xrpl'; + +type WalletChangedEventPayload = { + wallet: { + publicAddress: string; + }; +}; + +export function changeAccountSubscriberBuilder() { + // `true` instead of ProviderAPI is just a workaround. we don't need to have instance here. + return new ChangeAccountSubscriberBuilder() + .getInstance(() => true) + .format(async (_, payload) => [ + utils.formatAddressToCAIP(payload.wallet.publicAddress), + ]) + .addEventListener((_, callback) => { + on('walletChanged', callback); + }) + .removeEventListener((_instance, _callback) => { + /* + * TODO: gem wallet doesn't have support for unsubscribing. + * Making a variable and keep the callback refrence then here make it `undefined` is a quick fix + * but it makes new bugs, where if two subscribers added at once, we will loose the track of the first one and it will be staled. + */ + }) + .build(); +} diff --git a/wallets/provider-gemwallet/src/namespaces/xrpl/mod.ts b/wallets/provider-gemwallet/src/namespaces/xrpl/mod.ts new file mode 100644 index 0000000000..fe6b180e31 --- /dev/null +++ b/wallets/provider-gemwallet/src/namespaces/xrpl/mod.ts @@ -0,0 +1,2 @@ +export { namespace } from './namespace.js'; +export { Signer } from './singer.js'; diff --git a/wallets/provider-gemwallet/src/namespaces/xrpl/namespace.ts b/wallets/provider-gemwallet/src/namespaces/xrpl/namespace.ts new file mode 100644 index 0000000000..19198d4cf7 --- /dev/null +++ b/wallets/provider-gemwallet/src/namespaces/xrpl/namespace.ts @@ -0,0 +1,75 @@ +import type { XRPLActions } from '@rango-dev/wallets-core/namespaces/xrpl'; + +import { getAddress } from '@gemwallet/api'; +import { ActionBuilder, NamespaceBuilder } from '@rango-dev/wallets-core'; +import { builders, utils } from '@rango-dev/wallets-core/namespaces/xrpl'; +import { Client } from 'xrpl'; + +import { WALLET_ID, XRPL_PUBLIC_SERVER } from '../../constants.js'; +import { checkInstallationOnLoad } from '../../utils.js'; + +import { changeAccountSubscriberBuilder } from './hooks.js'; + +const [changeAccountSubscriber, changeAccountCleanup] = + changeAccountSubscriberBuilder(); + +const connect = builders + .connect() + .action(async function () { + const response = await getAddress(); + if (!response.result?.address) { + throw new Error(`Couldn't access to your wallet address.`); + } + + return [utils.formatAddressToCAIP(response.result.address)]; + }) + .before(changeAccountSubscriber) + .or(changeAccountCleanup) + .build(); + +const canEagerConnect = new ActionBuilder( + 'canEagerConnect' +) + .action(async () => { + const isInstalled = await checkInstallationOnLoad(); + if (!isInstalled) { + throw new Error( + 'Trying to eagerly connect to your EVM wallet, but seems its instance is not available.' + ); + } + + try { + const response = await getAddress(); + const address = response.result?.address; + + return !!address; + } catch { + return false; + } + }) + .build(); + +const accountLines = new ActionBuilder( + 'accountLines' +) + .action(async (_, account, options) => { + const client = new Client(XRPL_PUBLIC_SERVER); + await client.connect(); + + const response = await client.request({ + command: 'account_lines', + ledger_index: 'current', + account: account, + peer: options?.peer, + }); + + await client.disconnect(); + return response.result.lines; + }) + .build(); + +export const namespace = new NamespaceBuilder('XRPL', WALLET_ID) + .action(connect) + .action(canEagerConnect) + .action(accountLines) + .build(); diff --git a/wallets/provider-gemwallet/src/namespaces/xrpl/singer.ts b/wallets/provider-gemwallet/src/namespaces/xrpl/singer.ts new file mode 100644 index 0000000000..d13708c47b --- /dev/null +++ b/wallets/provider-gemwallet/src/namespaces/xrpl/singer.ts @@ -0,0 +1,61 @@ +import type { XrplTransaction } from 'rango-types/mainApi'; + +import { sendPayment, setTrustline } from '@gemwallet/api'; +import { type GenericSigner, SignerError } from 'rango-types'; + +import { + fromPaymentTransactionDataToGemWalletRequest, + fromTrustSetTransactionDataToGemWalletRequest, +} from './helpers.js'; + +export class Signer implements GenericSigner { + async signMessage(): Promise { + throw SignerError.UnimplementedError('signMessage'); + } + + async signAndSendTx(tx: XrplTransaction): Promise<{ hash: string }> { + if (tx.data.TransactionType === 'TrustSet') { + const result = await setTrustline( + fromTrustSetTransactionDataToGemWalletRequest(tx.data) + ); + + if (result.type === 'reject') { + throw new Error('The request has been rejected', { + cause: result, + }); + } + + if (!result.result) { + throw new Error( + 'Unexpected error where the result is not returned. (type: UnreachableCode)' + ); + } + + return { + hash: result.result.hash, + }; + } else if (tx.data.TransactionType === 'Payment') { + const result = await sendPayment( + fromPaymentTransactionDataToGemWalletRequest(tx.data) + ); + + if (result.type === 'reject') { + throw new Error('The request has been rejected', { + cause: result, + }); + } + + if (!result.result) { + throw new Error( + 'Unexpected error where the result is not returned. (type: UnreachableCode)' + ); + } + + return { + hash: result.result.hash, + }; + } + + throw new Error('Unsupported transaction type'); + } +} diff --git a/wallets/provider-gemwallet/src/provider.ts b/wallets/provider-gemwallet/src/provider.ts new file mode 100644 index 0000000000..6d53d13077 --- /dev/null +++ b/wallets/provider-gemwallet/src/provider.ts @@ -0,0 +1,20 @@ +import { ProviderBuilder } from '@rango-dev/wallets-core'; + +import { info, WALLET_ID } from './constants.js'; +import { namespace as xrpl } from './namespaces/xrpl/mod.js'; +import { checkInstallationOnLoad } from './utils.js'; + +const buildProvider = () => + new ProviderBuilder(WALLET_ID) + .init(function (context) { + const [, setState] = context.state(); + const setInstallState = (result: boolean) => + setState('installed', result); + + checkInstallationOnLoad().then(setInstallState).catch(console.error); + }) + .config('metadata', info) + .add('xrpl', xrpl) + .build(); + +export { buildProvider }; diff --git a/wallets/provider-gemwallet/src/signer.ts b/wallets/provider-gemwallet/src/signer.ts new file mode 100644 index 0000000000..9a4c76ac0a --- /dev/null +++ b/wallets/provider-gemwallet/src/signer.ts @@ -0,0 +1,10 @@ +import type { SignerFactory } from 'rango-types'; + +import { DefaultSignerFactory, TransactionType as TxType } from 'rango-types'; + +export default async function getSigners(): Promise { + const { Signer: XrplSigner } = await import('./namespaces/xrpl/mod.js'); + const signers = new DefaultSignerFactory(); + signers.registerSigner(TxType.XRPL, new XrplSigner()); + return signers; +} diff --git a/wallets/provider-gemwallet/src/utils.ts b/wallets/provider-gemwallet/src/utils.ts new file mode 100644 index 0000000000..0dac2fc96e --- /dev/null +++ b/wallets/provider-gemwallet/src/utils.ts @@ -0,0 +1,17 @@ +import { isInstalled } from '@gemwallet/api'; + +export async function checkInstallationOnLoad(): Promise { + return new Promise((resolve, reject) => { + window.addEventListener('load', () => { + isInstalled() + .then((response) => { + if (response.result.isInstalled) { + resolve(true); + } else { + resolve(false); + } + }) + .catch(reject); + }); + }); +} diff --git a/wallets/provider-gemwallet/tsconfig.build.json b/wallets/provider-gemwallet/tsconfig.build.json new file mode 100644 index 0000000000..fc43a1c995 --- /dev/null +++ b/wallets/provider-gemwallet/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs + "extends": "../../tsconfig.libnext.json", + "include": ["src", "types"], + "files": ["../../global-wallets-env.d.ts"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "./src", + "lib": ["dom", "esnext"] + // match output dir to input dir. e.g. dist/index instead of dist/src/index + } +} diff --git a/wallets/provider-gemwallet/tsconfig.json b/wallets/provider-gemwallet/tsconfig.json new file mode 100644 index 0000000000..a3a0b0f59d --- /dev/null +++ b/wallets/provider-gemwallet/tsconfig.json @@ -0,0 +1 @@ +{ "extends": "./tsconfig.build.json", "include": ["src", "tests"] } diff --git a/wallets/react/src/hub/helpers.ts b/wallets/react/src/hub/helpers.ts index c22310528a..bf4a749050 100644 --- a/wallets/react/src/hub/helpers.ts +++ b/wallets/react/src/hub/helpers.ts @@ -16,7 +16,7 @@ export function mapCaipNamespaceToLegacyNetworkName( if (typeof chainId === 'string') { return chainId; } - const useNamespaceAsNetworkFor = ['solana']; + const useNamespaceAsNetworkFor = ['solana', 'xrpl']; if (useNamespaceAsNetworkFor.includes(chainId.namespace.toLowerCase())) { return chainId.namespace.toUpperCase(); diff --git a/wallets/readme.md b/wallets/readme.md index 89800258d7..fb3b8a1f49 100644 --- a/wallets/readme.md +++ b/wallets/readme.md @@ -132,7 +132,7 @@ For better user experience, wallet provider tries to connect to a wallet only wh ## By Group | Wallet | EVM | UTXO | Solana | Cosmos | TON | SUI | -| ----------------------------------------------- | --- | ---- | ------ | ------ | --- | --- | +| ----------------------------------------------- | --- | ---- | ------ | ------ | --- | --- | --- | | [CoinBase](provider-coinbase/readme.md) | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | | [Exodus](provider-exodus/readme.md) | ⚠️ | 🚧 | ✅ | ❌ | ❌ | ❌ | | [Ledger](provider-ledger/readme.md) | ⚠️ | ❌ | ✅ | ❌ | ❌ | ❌ | @@ -144,6 +144,7 @@ For better user experience, wallet provider tries to connect to a wallet only wh | [SafePal](provider-safepal/readme.md) | ✅ | 🚧 | 🚧 | ❌ | ❌ | ❌ | | [Trust Wallet](provider-trust-wallet/readme.md) | ✅ | ❌ | ✅ | 🚧 | 🚧 | 🚧 | | [UniSat](provider-unisat/readme.md) | ❌ | ⚠️ | ❌ | ❌ | ❌ | ❌ | +| [GemWallet](provider-gemwallet/readme.md) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ## By Feature @@ -160,6 +161,7 @@ For better user experience, wallet provider tries to connect to a wallet only wh | SafePal | ✅ | ✅ | ❌ | Injected | ✅ | | Trust Wallet | 🚧 | ✅ | ❌ | Injected | ✅ | | Unisat | ✅ | 🚧 | ❌ | Injected | ❌ | +| GemWallet | ✅ | ❌ | ⚠️ | Injected | ❌ | # Supported Wallets (Legacy) diff --git a/wallets/shared/src/rango.ts b/wallets/shared/src/rango.ts index 0cda39de7c..bb1972fd64 100644 --- a/wallets/shared/src/rango.ts +++ b/wallets/shared/src/rango.ts @@ -142,6 +142,10 @@ export const namespaces: Record< mainBlockchain: 'SUI', title: 'Sui', }, + XRPL: { + mainBlockchain: 'XRPL', + title: 'XRPL', + }, }; export type DerivationPath = { diff --git a/widget/app/src/App.tsx b/widget/app/src/App.tsx index fca2fdbcc3..e8378a2096 100644 --- a/widget/app/src/App.tsx +++ b/widget/app/src/App.tsx @@ -37,7 +37,8 @@ export function App() { */ config = { - apiKey: '', + apiKey: '4a624ab5-16ff-4f96-90b7-ab00ddfc342c', + apiUrl: 'https://api-104.rng-dev.online', walletConnectProjectId: WC_PROJECT_ID, trezorManifest: TREZOR_MANIFEST, tonConnect: { manifestUrl: TON_CONNECT_MANIFEST_URL }, diff --git a/yarn.lock b/yarn.lock index ee47ca9795..09f9b1ba92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4003,6 +4003,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== +"@gemwallet/api@^3.8.0": + version "3.8.0" + resolved "https://registry.yarnpkg.com/@gemwallet/api/-/api-3.8.0.tgz#46bc47789848c7ac9cc620613e0a1757dc8668a1" + integrity sha512-hZ6XC0mVm3Q54cgonrzk6tHS/wUMjtPHyqsqbtlnNGPouCR7OIfEDo5Y802qLZ5ah6PskhsK0DouVnwUykEM8Q== + "@gql.tada/cli-utils@1.6.3": version "1.6.3" resolved "https://registry.yarnpkg.com/@gql.tada/cli-utils/-/cli-utils-1.6.3.tgz#b893cec74908da4df0602691e2e0b1497fda8cda" @@ -4779,7 +4784,7 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@^1.4.2", "@noble/curves@~1.8.1": +"@noble/curves@^1.0.0", "@noble/curves@^1.4.2", "@noble/curves@~1.8.1": version "1.8.1" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== @@ -6218,6 +6223,11 @@ resolved "https://registry.yarnpkg.com/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.13.2.tgz#f03884c7eb766f5508085d95ab96063a28e20920" integrity sha512-kGlJecJHBzGrGTq/yhLANh56t+Zur6Ubpt+/w03ARX1poDb4TM8vKU3iV8tuYpk359PPWp+Qvjnqb9oW2YQcYw== +"@scure/base@^1.1.3", "@scure/base@~1.2.2", "@scure/base@~1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.4.tgz#002eb571a35d69bdb4c214d0995dff76a8dcd2a9" + integrity sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ== + "@scure/base@~1.1.0", "@scure/base@~1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" @@ -6233,11 +6243,6 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== -"@scure/base@~1.2.2", "@scure/base@~1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.4.tgz#002eb571a35d69bdb4c214d0995dff76a8dcd2a9" - integrity sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ== - "@scure/bip32@1.3.2": version "1.3.2" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" @@ -6256,7 +6261,7 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" -"@scure/bip32@^1.4.0": +"@scure/bip32@^1.3.1", "@scure/bip32@^1.4.0": version "1.6.2" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.2.tgz#093caa94961619927659ed0e711a6e4bf35bffd0" integrity sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw== @@ -6289,7 +6294,7 @@ "@noble/hashes" "~1.5.0" "@scure/base" "~1.1.8" -"@scure/bip39@^1.3.0", "@scure/bip39@^1.5.1": +"@scure/bip39@^1.2.1", "@scure/bip39@^1.3.0", "@scure/bip39@^1.5.1": version "1.5.4" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.4.tgz#07fd920423aa671be4540d59bdd344cc1461db51" integrity sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA== @@ -9784,6 +9789,23 @@ "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" +"@xrplf/isomorphic@^1.0.0", "@xrplf/isomorphic@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@xrplf/isomorphic/-/isomorphic-1.0.1.tgz#d7676e0ec0e55a39f37ddc1f3cc30eeab52e0739" + integrity sha512-0bIpgx8PDjYdrLFeC3csF305QQ1L7sxaWnL5y71mCvhenZzJgku9QsA+9QCXBC1eNYtxWO/xR91zrXJy2T/ixg== + dependencies: + "@noble/hashes" "^1.0.0" + eventemitter3 "5.0.1" + ws "^8.13.0" + +"@xrplf/secret-numbers@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@xrplf/secret-numbers/-/secret-numbers-1.0.0.tgz#cc19ff84236cc2737b38f2e42a29924f2b8ffc0e" + integrity sha512-qsCLGyqe1zaq9j7PZJopK+iGTGRbk6akkg6iZXJJgxKwck0C5x5Gnwlb1HKYGOwPKyrXWpV6a2YmcpNpUFctGg== + dependencies: + "@xrplf/isomorphic" "^1.0.0" + ripple-keypairs "^2.0.0" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -13373,16 +13395,16 @@ ethers@^6.13.2: tslib "2.4.0" ws "8.17.1" +eventemitter3@5.0.1, eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -18888,6 +18910,14 @@ ripple-address-codec@^4.1.1, ripple-address-codec@^4.3.1: base-x "^3.0.9" create-hash "^1.1.2" +ripple-address-codec@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-5.0.0.tgz#97059f7bba6f9ed7a52843de8aa427723fb529f6" + integrity sha512-de7osLRH/pt5HX2xw2TRJtbdLLWHu0RXirpQaEeCnWKY5DYHykh3ETSkofvm0aX0LJiV7kwkegJxQkmbO94gWw== + dependencies: + "@scure/base" "^1.1.3" + "@xrplf/isomorphic" "^1.0.0" + ripple-binary-codec@^1.1.3: version "1.11.0" resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.11.0.tgz#d99c848c51a19746b738785001fb7208704bfe30" @@ -18900,6 +18930,15 @@ ripple-binary-codec@^1.1.3: decimal.js "^10.2.0" ripple-address-codec "^4.3.1" +ripple-binary-codec@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-2.3.0.tgz#2eb07c276392d10dc35e2f5244a231e70b754757" + integrity sha512-CPMzkknXlgO9Ow5Qa5iqQm0vOIlJyN8M1bc8etyhLw2Xfrer6bPzLA8/apuKlGQ+XdznYSKPBz5LAhwYjaDAcA== + dependencies: + "@xrplf/isomorphic" "^1.0.1" + bignumber.js "^9.0.0" + ripple-address-codec "^5.0.0" + ripple-keypairs@^1.0.3: version "1.3.1" resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.3.1.tgz#7fa531df36b138134afb53555a87d7f5eb465b2e" @@ -18911,6 +18950,15 @@ ripple-keypairs@^1.0.3: hash.js "^1.0.3" ripple-address-codec "^4.3.1" +ripple-keypairs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-2.0.0.tgz#4a1a8142e9a58c07e61b3cc6cfe7317db718d289" + integrity sha512-b5rfL2EZiffmklqZk1W+dvSy97v3V/C7936WxCCgDynaGPp7GE6R2XO7EU9O2LlM/z95rj870IylYnOQs+1Rag== + dependencies: + "@noble/curves" "^1.0.0" + "@xrplf/isomorphic" "^1.0.0" + ripple-address-codec "^5.0.0" + ripple-lib-transactionparser@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz#7aaad3ba1e1aeee1d5bcff32334a7a838f834dce" @@ -19683,16 +19731,7 @@ string-argv@0.3.2: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -19819,14 +19858,7 @@ stringify-object@^5.0.0: is-obj "^3.0.0" is-regexp "^3.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -21510,7 +21542,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -21528,15 +21560,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -21595,7 +21618,7 @@ ws@^7.2.0, ws@^7.5.10: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.17.1, ws@^8.18.0: +ws@^8.13.0, ws@^8.17.1, ws@^8.18.0: version "8.18.1" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb" integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w== @@ -21605,6 +21628,21 @@ ws@^8.2.3, ws@^8.5.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== +xrpl@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/xrpl/-/xrpl-4.2.0.tgz#2d9b38a209028ff598e8b00342e5edb282f9cf29" + integrity sha512-RR6lJhRyQHPoI/rZGuKBzUc45KSC3thpYVzn3ATu78GXDZ6vsyn8SXmNveMhAJ2AVRvpRiyyztbU2TmGRUp2Xg== + dependencies: + "@scure/bip32" "^1.3.1" + "@scure/bip39" "^1.2.1" + "@xrplf/isomorphic" "^1.0.1" + "@xrplf/secret-numbers" "^1.0.0" + bignumber.js "^9.0.0" + eventemitter3 "^5.0.1" + ripple-address-codec "^5.0.0" + ripple-binary-codec "^2.3.0" + ripple-keypairs "^2.0.0" + xstream@^11.14.0: version "11.14.0" resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.14.0.tgz#2c071d26b18310523b6877e86b4e54df068a9ae5"