Skip to content

Commit

Permalink
transaction processing state added
Browse files Browse the repository at this point in the history
  • Loading branch information
unixvb committed May 14, 2024
1 parent 828c345 commit c219852
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 26 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"redux-observable": "^3.0.0-rc.2",
"redux-persist": "^6.0.0",
"rxjs": "^7.8.1",
"tonapi-sdk-js": "^1.0.9",
"ts-action": "^11.0.0",
"ts-action-operators": "^9.1.2"
},
Expand Down
10 changes: 8 additions & 2 deletions src/globals.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import {TonClient} from '@ton/ton';
import axios from 'axios';
import {Api, HttpClient} from 'tonapi-sdk-js';

import {toNano} from './utils/big-int.utils';

export const TON = 'ton';
export const USDT = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs';
export const WORKCHAIN = 0;

export const DEBOUNCE_DUE_TIME = 300;

export const GAS_AMOUNT = toNano('0.255', 9);
export const JETTON_TRANSFER_GAS_AMOUNT = toNano('0.065', 9);

export const API = axios.create({
baseURL: 'http://93.188.34.207/api'
});

export const TON_CLIENT = new TonClient({
endpoint: `http://65.109.108.204:8088/jsonRPC`
});

export const DEBOUNCE_DUE_TIME = 300;
export const TON_API_CLIENT = new Api(
new HttpClient({
baseUrl: 'https://tonapi.io'
})
);
4 changes: 4 additions & 0 deletions src/interfaces/transaction-info.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface TransactionInfo {
senderRawAddress: string;
bocHash: string;
}
48 changes: 39 additions & 9 deletions src/screens/home/swap-form/swap-form.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {isDefined} from '@rnw-community/shared';
import {Address} from '@ton/core';
import {
useTonConnectModal,
Expand All @@ -14,11 +15,18 @@ import {TON, USDT} from '../../../globals.ts';
import {CustomInput} from '../../../shared/CustomInput/CustomInput.tsx';
import {FormButton} from '../../../shared/FormButton/FormButton.tsx';
import {useDispatch} from '../../../store';
import {loadSwapRoutesActions} from '../../../store/swap-routes/swap-routes-actions.ts';
import {useSwapRoutesSelector} from '../../../store/swap-routes/swap-routes-selectors.ts';
import {
addPendingSwapTransactionActions,
loadSwapRoutesActions
} from '../../../store/swap-routes/swap-routes-actions.ts';
import {
useIsProcessingSwapTransactionSelector,
useSwapRoutesSelector
} from '../../../store/swap-routes/swap-routes-selectors.ts';
import {mapSwapRouteToRoute} from '../../../swap-routes/shared/calculated-swap-route.utils.ts';
import {getSwapRouteMessage} from '../../../swap-routes/shared/message.utils.ts';
import {toNano} from '../../../utils/big-int.utils.ts';
import {bocToHash} from '../../../utils/boc.utils.ts';

export const SwapForm = () => {
const wallet = useTonWallet();
Expand All @@ -27,6 +35,8 @@ export const SwapForm = () => {

const dispatch = useDispatch();
const swapRoutes = useSwapRoutesSelector();
const isProcessingSwapTransaction =
useIsProcessingSwapTransactionSelector();
const routes = useMemo(
() => swapRoutes.map(mapSwapRouteToRoute),
[swapRoutes]
Expand Down Expand Up @@ -63,21 +73,41 @@ export const SwapForm = () => {
setOutputAsset(inputAsset);
};
const handleSwapClick = async () => {
const senderAddress = wallet?.account.address ?? '';
const walletAddress = wallet?.account.address;

if (!isDefined(walletAddress)) {
throw new Error('Wallet address is not defined');
}

const senderAddress = Address.parse(walletAddress);
const senderRawAddress = senderAddress.toRawString();

const messages = await Promise.all(
swapRoutes.map(swapRoute =>
getSwapRouteMessage(swapRoute, Address.parse(senderAddress))
getSwapRouteMessage(swapRoute, senderAddress)
)
);

tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 1 * 60,
from: senderAddress,
messages
});
const response = await tonConnectUI
.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 1 * 60,
from: senderRawAddress,
messages
})
.catch(() => undefined);

if (isDefined(response)) {
dispatch(
addPendingSwapTransactionActions.submit({
senderRawAddress,
bocHash: bocToHash(response.boc)
})
);
}
};

console.log('isProcessingSwapTransaction', isProcessingSwapTransaction);

return (
<>
<div className={styles.body_div}>
Expand Down
5 changes: 5 additions & 0 deletions src/store/swap-routes/swap-routes-actions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {TransactionInfo} from '../../interfaces/transaction-info.interface.ts';
import {CalculatedSwapRoute} from '../../swap-routes/shared/calculated-swap-route.type.ts';
import {createActions} from '../utils/create-actions.ts';

Expand All @@ -9,3 +10,7 @@ export const loadSwapRoutesActions = createActions<
},
CalculatedSwapRoute[]
>('swap-route/LOAD_SWAP_ROUTES');

export const addPendingSwapTransactionActions = createActions<TransactionInfo>(
'swap-route/ADD_PENDING_SWAP_TRANSACTION'
);
45 changes: 32 additions & 13 deletions src/store/swap-routes/swap-routes-epics.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import {combineEpics} from 'redux-observable';
import {
catchError,
debounceTime,
from,
map,
Observable,
of,
switchMap
} from 'rxjs';
import {combineEpics, Epic} from 'redux-observable';
import {from, of} from 'rxjs';
import {catchError, debounceTime, map, switchMap} from 'rxjs/operators';
import {Action} from 'ts-action';
import {ofType, toPayload} from 'ts-action-operators';

import {loadSwapRoutesActions} from './swap-routes-actions.ts';
import {
addPendingSwapTransactionActions,
loadSwapRoutesActions
} from './swap-routes-actions.ts';
import {API, DEBOUNCE_DUE_TIME} from '../../globals';
import {CalculatedSwapRoute} from '../../swap-routes/shared/calculated-swap-route.type.ts';
import {waitTransactionConfirmation} from '../../utils/tonapi.utils.ts';

const loadSwapRoutesEpic = (action$: Observable<Action>) =>
const loadSwapRoutesEpic: Epic<Action> = action$ =>
action$.pipe(
ofType(loadSwapRoutesActions.submit),
debounceTime(DEBOUNCE_DUE_TIME),
Expand All @@ -32,4 +29,26 @@ const loadSwapRoutesEpic = (action$: Observable<Action>) =>
)
);

export const swapRoutesEpics = combineEpics(loadSwapRoutesEpic);
const addPendingSwapTransactionEpic: Epic<Action> = action$ =>
action$.pipe(
ofType(addPendingSwapTransactionActions.submit),
toPayload(),
switchMap(payload =>
from(
waitTransactionConfirmation(
payload.senderRawAddress,
payload.bocHash
)
).pipe(
map(() => addPendingSwapTransactionActions.success()),
catchError(err =>
of(addPendingSwapTransactionActions.fail(err.message))
)
)
)
);

export const swapRoutesEpics = combineEpics(
loadSwapRoutesEpic,
addPendingSwapTransactionEpic
);
24 changes: 23 additions & 1 deletion src/store/swap-routes/swap-routes-reducers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {createReducer} from '@reduxjs/toolkit';

import {loadSwapRoutesActions} from './swap-routes-actions.ts';
import {
addPendingSwapTransactionActions,
loadSwapRoutesActions
} from './swap-routes-actions.ts';
import {swapRouteInitialState, SwapRoutesState} from './swap-routes-state.ts';
import {createEntity} from '../utils/create-entity';

Expand All @@ -22,5 +25,24 @@ export const swapRoutesReducers = createReducer<SwapRoutesState>(
batch: createEntity([], false, error)
})
);

builder.addCase(
addPendingSwapTransactionActions.submit,
(state, {payload}) => ({
...state,
pendingSwapTransaction: createEntity(payload, true)
})
);
builder.addCase(addPendingSwapTransactionActions.success, state => ({
...state,
pendingSwapTransaction: createEntity(undefined, false)
}));
builder.addCase(
addPendingSwapTransactionActions.fail,
(state, {payload: error}) => ({
...state,
pendingSwapTransaction: createEntity(undefined, false, error)
})
);
}
);
3 changes: 3 additions & 0 deletions src/store/swap-routes/swap-routes-selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ import {useSelector} from '../index.ts';

export const useSwapRoutesSelector = () =>
useSelector(({swapRoutes}) => swapRoutes.batch.data);

export const useIsProcessingSwapTransactionSelector = () =>
useSelector(({swapRoutes}) => swapRoutes.pendingSwapTransaction.isLoading);
5 changes: 4 additions & 1 deletion src/store/swap-routes/swap-routes-state.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {TransactionInfo} from '../../interfaces/transaction-info.interface.ts';
import {CalculatedSwapRoute} from '../../swap-routes/shared/calculated-swap-route.type.ts';
import {LoadableEntityState} from '../types';
import {createEntity} from '../utils/create-entity';

export interface SwapRoutesState {
batch: LoadableEntityState<CalculatedSwapRoute[]>;
pendingSwapTransaction: LoadableEntityState<TransactionInfo | undefined>;
}

export const swapRouteInitialState: SwapRoutesState = {
batch: createEntity([])
batch: createEntity([]),
pendingSwapTransaction: createEntity(undefined)
};
3 changes: 3 additions & 0 deletions src/utils/boc.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ import {Buffer} from 'buffer';

export const bocToCell = (boc: string) =>
Cell.fromBoc(Buffer.from(boc, 'base64'))[0];

export const bocToHash = (boc: string) =>
bocToCell(boc).hash(0).toString('hex');
35 changes: 35 additions & 0 deletions src/utils/tonapi.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {TON_API_CLIENT} from '../globals';

const CHECK_INTERVAL = 1500;
const TRANSACTION_CONFIRMATION_TIMEOUT = 5 * 1000;

export const waitTransactionConfirmation = async (
senderRawAddress: string,
bocHash: string
) =>
new Promise<string>((resolve, reject) => {
const timeoutId = setTimeout(() => {
clearInterval(intervalId);
clearTimeout(timeoutId);

reject(
`transaction confirmation timeout (sender: ${senderRawAddress}, hash: ${bocHash})`
);
}, TRANSACTION_CONFIRMATION_TIMEOUT);

const intervalId = setInterval(async () => {
const accountEvent = await TON_API_CLIENT.accounts
.getAccountEvent(senderRawAddress, bocHash)
.catch(() => ({
event_id: 'empty_event_id',
in_progress: true
}));

if (!accountEvent.in_progress) {
clearInterval(intervalId);
clearTimeout(timeoutId);

resolve(accountEvent.event_id);
}
}, CHECK_INTERVAL);
});
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3455,6 +3455,11 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"

tonapi-sdk-js@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/tonapi-sdk-js/-/tonapi-sdk-js-1.0.9.tgz#846671e4ff96ea5a35680f22e6ed541e7a4a983e"
integrity sha512-4zv7KwepqGvaCHksccAQ1lO8ERLiOmsVgzuPhUWwaqmmXcjXyrWkGoh0RX/XBHnOGxHEAxsyG5aT3Qjtasy7EQ==

tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
Expand Down

0 comments on commit c219852

Please sign in to comment.