Skip to content

Commit 2b1006d

Browse files
committed
chore: check trustline using new 'preconidtions' data from backend
1 parent b057197 commit 2b1006d

File tree

11 files changed

+392
-175
lines changed

11 files changed

+392
-175
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import type { TargetToken } from './types';
2+
import type { SwapQueueContext, SwapStorage } from '../../types';
3+
import type { NextTransactionStateError } from '../common/produceNextStateForTransaction';
4+
import type { ExecuterActions } from '@rango-dev/queue-manager-core';
5+
import type { GenericSigner, XrplTransaction } from 'rango-types';
6+
import type { Err } from 'ts-results';
7+
8+
import {
9+
getCurrentStep,
10+
getCurrentStepTx,
11+
handleRejectedSign,
12+
updateStorageOnSuccessfulSign,
13+
} from '../../helpers';
14+
import { getCurrentAddressOf, getRelatedWallet } from '../../shared';
15+
import { SwapActionTypes } from '../../types';
16+
import { checkEnvironmentBeforeExecuteTransaction } from '../common/checkEnvironmentBeforeExecuteTransaction';
17+
import {
18+
onNextStateError,
19+
onNextStateOk,
20+
produceNextStateForTransaction,
21+
} from '../common/produceNextStateForTransaction';
22+
import { requestBlockQueue } from '../common/utils';
23+
24+
import {
25+
checkTruslineAlreadyOpened,
26+
createTrustlineTransaction,
27+
ensureXrplNamespaceExists,
28+
ensureXrplTransactionIsValid,
29+
} from './utils';
30+
31+
export async function checkXrplTrustline(
32+
actions: ExecuterActions<SwapStorage, SwapActionTypes, SwapQueueContext>
33+
): Promise<void> {
34+
/*
35+
*
36+
* 1. Ensure wallet is connected with a correct address.
37+
*
38+
*/
39+
const checkResult = await checkEnvironmentBeforeExecuteTransaction(actions);
40+
if (checkResult.err) {
41+
requestBlockQueue(actions, checkResult.val);
42+
return;
43+
}
44+
45+
const { failed, context, schedule, getStorage, next } = actions;
46+
const { meta, getSigners } = context;
47+
48+
const swap = getStorage().swapDetails;
49+
const currentStep = getCurrentStep(swap)!;
50+
const currentTransactionFromStorage = getCurrentStepTx(currentStep);
51+
52+
const sourceWallet = getRelatedWallet(swap, currentStep);
53+
const walletAddress = getCurrentAddressOf(swap, currentStep);
54+
55+
const onFinish = () => {
56+
if (actions.context.resetClaimedBy) {
57+
actions.context.resetClaimedBy();
58+
}
59+
};
60+
const onSuccessfulFinish = () => {
61+
schedule(SwapActionTypes.EXECUTE_XRPL_TRANSACTION);
62+
next();
63+
onFinish();
64+
};
65+
66+
const handleErr = (err: Err<NextTransactionStateError>) => {
67+
onNextStateError(actions, err.val);
68+
failed();
69+
onFinish();
70+
};
71+
72+
/*
73+
* Checking the current transaction state to determine the next step.
74+
* It will either be Err, indicating process should stop, or Ok, indicating process should continue.
75+
*/
76+
const nextStateResult = produceNextStateForTransaction(actions);
77+
78+
if (nextStateResult.err) {
79+
handleErr(nextStateResult);
80+
return;
81+
}
82+
83+
// On success, we should update Swap object and also call notifier
84+
onNextStateOk(actions, nextStateResult.val);
85+
86+
/*
87+
*
88+
* 2. Ensure tx is supported, and namespace exists.
89+
*
90+
*/
91+
const transaction = await ensureXrplTransactionIsValid(
92+
currentTransactionFromStorage
93+
);
94+
if (transaction.err) {
95+
handleErr(transaction);
96+
return;
97+
}
98+
99+
const namespace = await ensureXrplNamespaceExists(
100+
context,
101+
sourceWallet.walletType
102+
);
103+
if (namespace.err) {
104+
handleErr(namespace);
105+
return;
106+
}
107+
108+
/*
109+
* 3. Do we need open a trustline for this transaction?
110+
*
111+
* Trust line only should be opened for issued token (not native), server is putting that data as precondition for us.
112+
* If there is no need for that, we are skipping this step and consider it as done.
113+
*/
114+
const trustlinePrecondition = transaction.val.preconditions.find(
115+
(item) => item.type === 'XRPL_CHANGE_TRUSTLINE'
116+
);
117+
if (!trustlinePrecondition) {
118+
onSuccessfulFinish();
119+
return;
120+
}
121+
122+
/*
123+
*
124+
* 4. Ensure trusline has been opened, then execute if needed.
125+
*
126+
*/
127+
const chainId = meta.blockchains[transaction.val.blockChain]?.chainId;
128+
const walletSigners = await getSigners(sourceWallet.walletType);
129+
130+
const token: TargetToken = {
131+
currency: trustlinePrecondition.currency,
132+
account: trustlinePrecondition.issuer,
133+
amount: trustlinePrecondition.value,
134+
};
135+
136+
// 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.
137+
const isTruslineAlreadyOpened = await checkTruslineAlreadyOpened(
138+
trustlinePrecondition.wallet,
139+
token,
140+
{
141+
namespace: namespace.val,
142+
}
143+
);
144+
145+
if (!isTruslineAlreadyOpened) {
146+
const trustlineTx = createTrustlineTransaction(
147+
trustlinePrecondition.wallet,
148+
token
149+
);
150+
151+
const signer: GenericSigner<XrplTransaction> = walletSigners.getSigner(
152+
transaction.val.type
153+
);
154+
155+
try {
156+
const trustlineTransaction: XrplTransaction = {
157+
...transaction.val,
158+
data: trustlineTx,
159+
};
160+
const result = await signer.signAndSendTx(
161+
trustlineTransaction,
162+
walletAddress,
163+
chainId
164+
);
165+
166+
updateStorageOnSuccessfulSign(actions, result, {
167+
// TODO: approval has different meaning for EVM, we may need to add a third type called trustline for the following function.
168+
isApproval: true,
169+
});
170+
onSuccessfulFinish();
171+
} catch (e) {
172+
handleRejectedSign(actions)(e);
173+
onFinish();
174+
}
175+
} else {
176+
onSuccessfulFinish();
177+
return;
178+
}
179+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export { checkXrplTrustline } from './checkXrplTrustline.js';
2+
export {
3+
ensureXrplNamespaceExists,
4+
ensureXrplTransactionIsValid,
5+
} from './utils.js';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { ProxiedNamespace } from '@rango-dev/wallets-core';
2+
import type { XRPLActions } from '@rango-dev/wallets-core/namespaces/xrpl';
3+
4+
export type XrplNamespace = ProxiedNamespace<XRPLActions>;
5+
export type TargetToken = {
6+
currency: string;
7+
account: string;
8+
amount: string;
9+
};
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import type { TargetToken, XrplNamespace } from './types';
2+
import type { SwapQueueContext } from '../../types';
3+
import type { NextTransactionStateError } from '../common/produceNextStateForTransaction';
4+
import type { Transaction } from 'rango-sdk';
5+
import type { XrplTransaction, XrplTrustSetTransactionData } from 'rango-types';
6+
import type { Result } from 'ts-results';
7+
8+
import { isXrplTransaction } from 'rango-types';
9+
import { Err, Ok } from 'ts-results';
10+
11+
export async function ensureXrplTransactionIsValid(
12+
tx: Transaction | null
13+
): Promise<Result<XrplTransaction, NextTransactionStateError>> {
14+
if (!tx) {
15+
return new Err({
16+
nextStatus: 'failed',
17+
nextStepStatus: 'failed',
18+
message: 'Unexpected Error: tx is null!',
19+
details: undefined,
20+
errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR',
21+
});
22+
}
23+
24+
if (!isXrplTransaction(tx)) {
25+
return new Err({
26+
nextStatus: 'failed',
27+
nextStepStatus: 'failed',
28+
message:
29+
"Unexpected Error: Expected XRPL transaction but it doesn't match with the structure.",
30+
details: undefined,
31+
errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR',
32+
});
33+
}
34+
35+
if (tx.data.TransactionType !== 'Payment') {
36+
return new Err({
37+
nextStatus: 'failed',
38+
nextStepStatus: 'failed',
39+
message:
40+
'Unexpected Error: We only support XRPL transactions with payment type',
41+
details: undefined,
42+
errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR',
43+
});
44+
}
45+
46+
return Ok(tx);
47+
}
48+
49+
export async function ensureXrplNamespaceExists(
50+
context: SwapQueueContext,
51+
walletType: string
52+
): Promise<Result<XrplNamespace, NextTransactionStateError>> {
53+
// We need to work with namespace instance
54+
const provider = context.hubProvider(walletType);
55+
const xrplNamespace = provider.get('xrpl');
56+
if (!xrplNamespace) {
57+
return new Err({
58+
nextStatus: 'failed',
59+
nextStepStatus: 'failed',
60+
message: 'XRPL is not available on your wallet.',
61+
details: undefined,
62+
errorCode: 'CLIENT_UNEXPECTED_BEHAVIOUR',
63+
});
64+
}
65+
return Ok(xrplNamespace);
66+
}
67+
68+
export async function checkTruslineAlreadyOpened(
69+
account: string,
70+
token: TargetToken,
71+
options: { namespace: XrplNamespace }
72+
): Promise<boolean> {
73+
const lines = await options.namespace.accountLines(account, {
74+
peer: token.account,
75+
});
76+
return !!lines.some((trustline) => {
77+
return (
78+
trustline.currency === token.currency &&
79+
trustline.account === token.account &&
80+
trustline.limit !== '0'
81+
);
82+
});
83+
}
84+
85+
export function createTrustlineTransaction(
86+
account: string,
87+
token: TargetToken
88+
): XrplTrustSetTransactionData {
89+
return {
90+
TransactionType: 'TrustSet',
91+
Account: account,
92+
LimitAmount: {
93+
currency: token.currency,
94+
issuer: token.account,
95+
value: token.amount,
96+
},
97+
};
98+
}

queue-manager/rango-preset/src/actions/common/produceNextStateForTransaction.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,7 @@ export function produceNextStateForTransaction(
6363
});
6464
}
6565

66-
const hasAlreadyProceededToSign =
67-
typeof swap.hasAlreadyProceededToSign === 'boolean';
66+
const hasAlreadyProceededToSign = !!swap.hasAlreadyProceededToSign;
6867

6968
if (isApproval) {
7069
return new Ok({

queue-manager/rango-preset/src/actions/createTransaction.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { SwapQueueContext, SwapStorage } from '../types';
22
import type { ExecuterActions } from '@rango-dev/queue-manager-core';
3+
import type { CreateTransactionRequest } from 'rango-sdk';
34

45
import { warn } from '@rango-dev/logging-core';
5-
import { type CreateTransactionRequest, TransactionType } from 'rango-sdk';
66

77
import {
88
createStepFailedEvent,
@@ -72,11 +72,7 @@ export async function createTransaction(
7272

7373
setStorage({ ...getStorage(), swapDetails: swap });
7474

75-
if (transaction?.blockChain === TransactionType.XRPL) {
76-
schedule(SwapActionTypes.EXECUTE_XRPL_TRANSACTION);
77-
} else {
78-
schedule(SwapActionTypes.EXECUTE_TRANSACTION);
79-
}
75+
schedule(SwapActionTypes.SCHEDULE_NEXT_STEP);
8076
next();
8177
} catch (error: unknown) {
8278
swap.status = 'failed';

0 commit comments

Comments
 (0)