Skip to content
Open
9 changes: 7 additions & 2 deletions queue-manager/rango-preset/src/actions/createTransaction.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const TRUST_LINE_AMOUNT = '10000000000000';
Original file line number Diff line number Diff line change
@@ -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<SwapStorage, SwapActionTypes, SwapQueueContext>
): Promise<void> {
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<NextTransactionStateError>) => {
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<XrplTransaction> = 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();
}
Original file line number Diff line number Diff line change
@@ -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];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { executeXrplTransaction } from './executeXrplTransaction.js';
9 changes: 7 additions & 2 deletions queue-manager/rango-preset/src/actions/scheduleNextStep.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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();
}

Expand Down
8 changes: 6 additions & 2 deletions queue-manager/rango-preset/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export const getCurrentStepTx = (
tronTransaction,
tonTransaction,
suiTransaction,
xrplTransaction,
} = currentStep;
return (
evmTransaction ||
Expand All @@ -186,7 +187,8 @@ export const getCurrentStepTx = (
tronApprovalTransaction ||
tronTransaction ||
tonTransaction ||
suiTransaction
suiTransaction ||
xrplTransaction
);
};

Expand All @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down
6 changes: 5 additions & 1 deletion queue-manager/rango-preset/src/queueDef.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
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 {
onBlockForChangeNetwork,
onBlockForConnectWallet,
onDependsOnOtherQueues,
} from './helpers';
import { BlockReason, SwapActionTypes } from './types';

/**
*
Expand All @@ -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],
Expand Down
10 changes: 9 additions & 1 deletion queue-manager/rango-preset/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -291,7 +298,8 @@ export function getRelatedWalletOrNull(
}
try {
return getRelatedWallet(swap, currentStep);
} catch {
} catch (e) {
console.log({ e });
return null;
}
}
Expand Down
1 change: 1 addition & 0 deletions queue-manager/rango-preset/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}

Expand Down
Loading