From 52044ae75ac03adf2a10f6fd882c353849876667 Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Fri, 5 Jan 2024 20:25:12 +0000 Subject: [PATCH] UIA register - Multi-step exchange --- config/default.json | 7 +- src/elements/forms/AmountField.jsx | 7 +- src/locales/en.json | 10 +- src/locales/ru-RU.json | 10 +- src/modules/register/UIARegister.jsx | 203 +++++++++++++++++---------- src/pages/api/reg/[...all].js | 135 ++++++++++++++---- src/server/reg.js | 8 +- src/utils/ApidexApiClient.js | 6 +- 8 files changed, 274 insertions(+), 112 deletions(-) diff --git a/config/default.json b/config/default.json index 6b384fc..01bbd02 100644 --- a/config/default.json +++ b/config/default.json @@ -1,5 +1,5 @@ { - "rest_api": "https://dev.golos.app", + "rest_api": "http://89.22.173.104:3000", "allowed_clients": [ "dev.golos.app", "devnotify.golos.app", @@ -14,7 +14,10 @@ "signing_key": "5K67PNheLkmxkgJ5UjvR8Nyt3GVPoLEN1dMZjFuNETzrNyMecPG", "delegation": "75000.000000 GESTS", "free_regs_per_day": 10, - "uias": ["YMUSDT", "YMPZM", "YMHIVE", "TESTF"] + "uias": { + "assets": ["YMUSDT", "YMPZM", "YMHIVE", "YMXMR", "TESTF"], + "YMXMR": ["YMXHR/YMPZM", "YMPZM/YMUSDT", "YMUSDT/GOLOS"] + } }, "server_session_secret": "exiKdyF+IwRIXJDmtGIl4vWUz4i3eVSISpfZoeYc0s4=", "session_cookie_key": "X-Reg-ISession", diff --git a/src/elements/forms/AmountField.jsx b/src/elements/forms/AmountField.jsx index 1584f28..3bc5053 100644 --- a/src/elements/forms/AmountField.jsx +++ b/src/elements/forms/AmountField.jsx @@ -13,18 +13,19 @@ class AmountField extends React.Component { const { value, } = field const { values, setFieldTouched, setFieldValue } = form return this.onChange(e, values, setFieldTouched, setFieldValue)} + {...rest} onChange={(e) => this.onChange(e, values, form)} /> } - onChange = (e, values, setFieldTouched, setFieldValue) => { + onChange = (e, values, form) => { const { name } = this.props const newAmount = values[name].withChange(e.target.value) if (newAmount.hasChange && newAmount.asset.amount >= 0) { + const { setFieldTouched, setFieldValue } = form setFieldValue(name, newAmount) setFieldTouched(name, true, false) if (this.props.onChange) { - this.props.onChange(newAmount.asset) + this.props.onChange(newAmount.asset, form) } } } diff --git a/src/locales/en.json b/src/locales/en.json index 7d3c97a..7e2dd78 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -110,9 +110,15 @@ "api_error_details": "and send the error details:", "free_poller": "Cannot calculate minimal amount. Try again later, please. Or try another currency.", "deposited": "Received ", - "deposited2": ". Placing limit order to exchange it to GOLOS to register your account.", + "deposited2_SYM": ". Placing limit order to exchange it to %(SYM)s to register your account.", "cannot_place_order": "Cannot place order: ", - "you_will_receive": "You will receive " + "you_will_receive": "You will receive ", + "cannot_check_orders": "Cannot check exchange ability. Try again later. Or try another currency. Error details: ", + "no_orders": "No orders in ", + "cannot_register_with_it": ", cannot register.", + "too_low_orders": "Too low orders in ", + "unknown_error": " - error in ", + "too_low_amount_to_register": "Too low amount to register." }, "invites_jsx": { "claim_wrong_secret": "Wrong secret", diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json index dadd0ab..c549bae 100644 --- a/src/locales/ru-RU.json +++ b/src/locales/ru-RU.json @@ -110,9 +110,15 @@ "api_error_details": "и сообщите подробности ошибки:", "free_poller": "Не удается рассчитать минимальную сумму. Попробуйте позже, или другую валюту.", "deposited": "Получено ", - "deposited2": ". Размещаем ордер для обмена их на GOLOS для регистрации вашего аккаунта...", + "deposited2_SYM": ". Размещаем ордер для обмена их на %(SYM)s для регистрации вашего аккаунта...", "cannot_place_order": "Не удается разместить ордер: ", - "you_will_receive": "Вы получите примерно " + "you_will_receive": "Вы получите примерно ", + "cannot_check_orders": "Не удается проверить возможность обмена. Попробуйте позже, или другую валюту. Подробнее об ошибке: ", + "no_orders": "Нет ордеров по направлению ", + "cannot_register_with_it": ", не можем зарегистрировать за такую сумму.", + "too_low_orders": "Слишком мало ордеров по направлению ", + "unknown_error": " - ошибка по направлению ", + "too_low_amount_to_register": "Слишком малая сумма для регистрации." }, "invites_jsx": { "claim_wrong_secret": "Неверно указан ключ", diff --git a/src/modules/register/UIARegister.jsx b/src/modules/register/UIARegister.jsx index 8a2f4cd..c0beb34 100644 --- a/src/modules/register/UIARegister.jsx +++ b/src/modules/register/UIARegister.jsx @@ -13,7 +13,7 @@ import AmountField from '@/elements/forms/AmountField' import AccountName from '@/elements/register/AccountName' import VerifyWayTabs from '@/elements/register/VerifyWayTabs' import TransferWaiter from '@/modules/register/TransferWaiter' -import { apidexGetPrices, apidexExchange } from '@/utils/ApidexApiClient' +import { apidexGetPrices } from '@/utils/ApidexApiClient' import KeyFile from '@/utils/KeyFile' import { delay, } from '@/utils/misc' import { emptyAuthority } from '@/utils/RecoveryUtils' @@ -57,6 +57,10 @@ class UIARegister extends React.Component { error: '', } + constructor(props) { + super(props) + } + async componentDidMount() { const { clientCfg } = this.props @@ -66,6 +70,7 @@ class UIARegister extends React.Component { const { apidex_service } = clientCfg.config const { uias } = clientCfg.config.registrar + const syms = uias.assets const path = this.getPath() if (path[1]) { @@ -73,10 +78,10 @@ class UIARegister extends React.Component { const sym = params.get('uia') if (sym) { let assets - assets = await golos.api.getAssetsAsync('', uias) + assets = await golos.api.getAssetsAsync('', syms) let error - if (!uias.includes(sym)) { + if (!syms.includes(sym)) { error = sym + tt('uia_register_jsx.no_such_asset') } else { for (const asset of assets) { @@ -188,8 +193,8 @@ class UIARegister extends React.Component { } let assets = [] - if (uias.length) { - assets = await golos.api.getAssetsAsync('', uias) + if (syms.length) { + assets = await golos.api.getAssetsAsync('', syms) } this.setState({ loading: false, @@ -354,32 +359,63 @@ class UIARegister extends React.Component { const { waitAmount, registrar, onTransfer } = this.state if (!onTransfer) { onTransfer = async (deposited) => { - this.setState({ - deposited - }) + let order_receipts + try { + let mor = await callApi('/api/reg/make_order_receipt/' + deposited.toString() + '/true') + mor = await mor.json() + if (mor.status !== 'ok' || mor.error) { + throw new Error(mor.error || 'Unknown error') + } - let error - for (let tr = 1; tr <= 10; ++tr) { - try { - let orr = await callApi('/api/reg/place_order/' + deposited.toString(), {}) - orr = await orr.json() - if (orr.status === 'ok') { - console.log(orr) - this.props.updateApiState(orr) - return + order_receipts = mor.order_receipts + this.setState({ + deposited, + depositedToSym: order_receipts[0][1].split(' ')[1] + }) + } catch (err) { + console.error(err) + this.setState({ + deposited, + error: err.message || err.toString() + }) + return + } + + while (order_receipts.length) { + let error + for (let tr = 1; tr <= 10; ++tr) { + try { + let orr = await callApi('/api/reg/place_order', {}) + orr = await orr.json() + if (orr.status === 'ok') { + console.log(orr) + if (orr.order_receipts && orr.order_receipts[0]) { + order_receipts = orr.order_receipts + this.setState({ + depositedToSym: order_receipts[0][1].split(' ')[1] + }) + } else { + this.props.updateApiState(orr) + return + } + break + } + console.error('place_order', orr) + throw new Error(orr.error) + } catch (err) { + console.error('place_order throws', err) + error = (err && err.toString) && err.toString() } - console.error('place_order', orr) - throw new Error(orr.error) - } catch (err) { - console.error('place_order throws', err) - error = (err && err.toString) && err.toString() + await delay(3000) } - await delay(3000) - } - this.setState({ - error: tt('uia_register_jsx.cannot_place_order') + error - }) + if (error) { + this.setState({ + error: tt('uia_register_jsx.cannot_place_order') + error + }) + break + } + } } } return
@@ -392,7 +428,7 @@ class UIARegister extends React.Component {
} - amountValidate = (values) => { + amountValidate = async (values) => { const errors = {} const { minAmount } = this.state if (values.amount.asset.lt(minAmount)) { @@ -401,23 +437,39 @@ class UIARegister extends React.Component { this.setState({ amountSubmitError: '' }) + + if (!errors.amount) { + try { + let fp = await callApi('/api/reg/make_order_receipt/' + values.amount.asset.toString() + '/false') + fp = await fp.json() + let receive + if (fp.error) { + errors.amount = fp.error + } else { + receive = await Asset(fp.result) + + const cprops = await golos.api.getChainPropertiesAsync() + let { create_account_min_golos_fee } = cprops + create_account_min_golos_fee = await Asset(create_account_min_golos_fee) + if (create_account_min_golos_fee.gt(receive)) { + errors.amount = tt('uia_register_jsx.too_low_amount_to_register') + } + } + if (receive) + this.setState({ + receive, + }) + } catch (err) { + console.error(err) + errors.amount = 'Internal error' + return + } + } + return errors } - _onAmountChange = async (amount) => { - const { clientCfg } = this.props - const { apidex_service } = clientCfg.config - let res - try { - res = await apidexExchange(apidex_service, amount, 'GOLOS') - } catch (err) { - console.error(err) - } - if (res) { - this.setState({ - receive: res - }) - } + _onAmountChange = async (amount, form) => { } _onAmountSubmit = async (values) => { @@ -449,37 +501,40 @@ class UIARegister extends React.Component { initialValues={initialForm} validate={this.amountValidate} onSubmit={this._onAmountSubmit} + validateOnMount={true} > {({ - handleSubmit, isSubmitting, isValid, dirty, errors, touched, values, handleChange, setFieldValue, - }) => ( -
-
- {tt('uia_register_jsx.enter_amount')} -
-
- -
- - {amountSubmitError &&
{amountSubmitError}
} - {!errors.amount && !amountSubmitError && receive ?
- {tt('uia_register_jsx.you_will_receive')}~{receive.floatString}. -
: null} - - {isSubmitting && } - - - )} + handleSubmit, isSubmitting, isValid, dirty, errors, touched, values, handleChange, setFieldValue, setFieldError, + }) => { + return ( +
+
+ {tt('uia_register_jsx.enter_amount')} +
+
+ +
+ {errors.amount &&
{errors.amount}
} + {amountSubmitError &&
{amountSubmitError}
} + {!errors.amount && !amountSubmitError && receive ?
+ {tt('uia_register_jsx.you_will_receive')}~{receive.floatString}. +
: null} + + {isSubmitting && } + + + ) + }} } @@ -514,13 +569,15 @@ class UIARegister extends React.Component { if (error) { form =
{error}
} else if (sym) { - const { deposited } = this.state + const { deposited, depositedToSym } = this.state if (deposited) { form =
{tt('uia_register_jsx.deposited')} {deposited.floatString} - {tt('uia_register_jsx.deposited2')} + {tt('uia_register_jsx.deposited2_SYM', { + SYM: depositedToSym || 'GOLOS' + })}
diff --git a/src/pages/api/reg/[...all].js b/src/pages/api/reg/[...all].js index 0885063..049d330 100644 --- a/src/pages/api/reg/[...all].js +++ b/src/pages/api/reg/[...all].js @@ -4,6 +4,7 @@ import golos, { api, broadcast } from 'golos-lib-js' import { hash, } from 'golos-lib-js/lib/auth/ecc'; import { Asset } from 'golos-lib-js/lib/utils' import secureRandom from 'secure-random'; +import tt from 'counterpart' import nextConnect from '@/server/nextConnect'; import { throwErr, } from '@/server/error'; @@ -15,6 +16,7 @@ import { getDailyLimit, obtainUid, getClientCfg, } from '@/server/reg'; import { regSessionMiddleware, } from '@/server/regSession'; import Tarantool from '@/server/tarantool'; import { delay, } from '@/utils/misc' +import { apidexExchange } from '@/utils/ApidexApiClient' initGolos(); @@ -507,10 +509,86 @@ let handler = nextConnect({ attachParams: true, }) } }) - .post('/api/reg/place_order/:amount', async (req, res) => { + .get('/api/reg/make_order_receipt/:amount/:submitting', async (req, res) => { await slowDownLimitReq(req) - const amountStr = req.params.amount.split('%20').join(' ') + const clientCfg = getClientCfg(req, req.params) + const { apidex_service } = clientCfg.config + const { uias } = clientCfg.config.registrar + + const amount = await Asset(req.params.amount.split('%20').join(' ')) + let exAmount = amount.clone() + const exchanges = uias[exAmount.symbol] + let error = null + let result = null + let order_receipts = [] + for (const ex of exchanges) { + const [ sym1, sym2 ] = ex.split('/') + let resEx + try { + resEx = await apidexExchange(apidex_service, exAmount, sym2) + } catch (err) { + console.error(err) + } + if (!resEx) { + error = tt('uia_register_jsx.cannot_check_orders') + exAmount.symbol + '/' + sym2 + } + if (error) break + if (resEx.remain) { + error = tt('uia_register_jsx.too_low_orders') + exAmount.symbol + '/' + sym2 + tt('uia_register_jsx.cannot_register_with_it') + break + } + if (resEx.error === 'no_orders') { + error = tt('uia_register_jsx.no_orders') + exAmount.symbol + '/' + sym2 + tt('uia_register_jsx.cannot_register_with_it') + break + } else if (resEx.error) { + error = resEx.error + tt('uia_register_jsx.unknown_error') + exAmount.symbol + '/' + sym2 + tt('uia_register_jsx.cannot_register_with_it') + break + } + const pair = [exAmount.toString(), resEx.result.toString()] + order_receipts.push(pair) + exAmount = resEx.result.clone() + result = exAmount.clone() + } + + const deposited = req.session.deposited && req.session.deposited[amount.symbol] + if (deposited && deposited === amount.toString()) { + req.session.order_receipts = order_receipts + req.session.order_receipt_time = Date.now() + + delete req.session.deposited[amount.symbol] + + await req.session.save() + } else { + order_receipts = [] + } + + if (req.params.submitting === 'true' && !order_receipts.length) { + console.error('make_order_receipt error:', req.session.deposited, amount.symbol) + error = 'wrong deposited' + result = null + } + + res.json({ + status: 'ok', + result: result && result.toString(), + error, + order_receipts + }) + }) + + .post('/api/reg/place_order', async (req, res) => { + await slowDownLimitReq(req) + + const { order_receipts, order_receipt_time } = req.session + if (!order_receipts || !order_receipts.length || !order_receipt_time) { + throwErr(req, 400, ['You have no deposited']) + } + if (Date.now() - order_receipt_time > 60*60*1000) { + throwErr(req, 400, ['You timeouted order receipt']) + } + + const amountStr = order_receipts[0][0] let amount try { amount = await Asset(amountStr) @@ -522,17 +600,6 @@ let handler = nextConnect({ attachParams: true, }) return } - if (!req.session.deposited) { - throwErr(req, 400, ['You have no deposited']) - } - const deposited = req.session.deposited[amount.symbol] - if (!deposited) { - throwErr(req, 400, ['You have no deposited in ' + amount.symbol]) - } - if (deposited !== amountStr) { - throwErr(req, 400, ['You have wrong deposited in ' + amount.symbol]) - } - let chainProps for (let i = 0; i < 3; ++i) { try { @@ -558,7 +625,7 @@ let handler = nextConnect({ attachParams: true, }) owner: username, orderid, amount_to_sell: amountStr, - min_to_receive: chainProps.create_account_min_golos_fee, + min_to_receive: order_receipts[0][1], fill_or_kill: true, expiration: 0xffffffff }]] @@ -568,13 +635,10 @@ let handler = nextConnect({ attachParams: true, }) operations }, [signingKey]) } catch (err) { - console.error('/api/reg/place_order - Cannot sell tokens', err) + console.error('/api/reg/place_order - Cannot sell tokens', err, amountStr, order_receipts[0][1]) throwErr(req, 400, ['Cannot sell tokens']) } - delete req.session.deposited[amount.symbol] - await req.session.save() - await delay(1500) let hist @@ -585,7 +649,9 @@ let handler = nextConnect({ attachParams: true, }) throwErr(req, 400, ['Blockchain unavailable']) } - let golos_received + const toReceive = await Asset(order_receipts[0][1]) + + let received, new_receipts for (let i = hist.length - 1; i >= 0; --i) { const timestamp = +new Date(hist[i][1].timestamp + 'Z') / 1000 if (orderid - timestamp > 10) { @@ -595,25 +661,40 @@ let handler = nextConnect({ attachParams: true, }) const [ opType, opData ] = hist[i][1].op if (opType === 'fill_order') { if (opData.current_orderid == orderid || opData.open_orderid == orderid) { - const golosAmount = await Asset(opData.current_orderid == orderid ? - opData.open_pays : opData.current_pays) + if (order_receipts[0][1].split(' ')[1] === 'GOLOS') { + const golosAmount = await Asset(opData.current_orderid == orderid ? + opData.open_pays : opData.current_pays) - golos_received = golos_received || await Asset('0.000 GOLOS') - golos_received = golos_received.plus(golosAmount) - req.session.golos_received = golos_received.toString() - await req.session.save() + if (!received) { + received = await Asset(0, toReceive.precision, toReceive.symbol) + } + received = received.plus(golosAmount) + req.session.golos_received = received.toString() + delete req.session.order_receipts + await req.session.save() + } else { + req.session.order_receipts.shift() + await req.session.save() + new_receipts = req.session.order_receipts + } } } } - if (golos_received) { + if (received) { res.json({ status: 'ok', - golos_received, + received, step: 'verified', verification_way: 'uia' }) return + } else if (new_receipts) { + res.json({ + status: 'ok', + order_receipts: new_receipts + }) + return } throwErr(req, 400, ['Cannot find fill_order operation']) diff --git a/src/server/reg.js b/src/server/reg.js index 019b8f5..a4b9155 100644 --- a/src/server/reg.js +++ b/src/server/reg.js @@ -145,8 +145,12 @@ export function getClientCfg(req, params, locale = '') { && config.get('fake_emails_allowed'); cfg.registrar = config.has('registrar') ? config.get('registrar') : {} - if (!cfg.registrar.uias) - cfg.registrar.uias = [] + const { registrar } = cfg + if (!registrar.uias) registrar.uias = {} + if (!registrar.uias.assets) registrar.uias.assets = [] + for (const sym of registrar.uias.assets) { + if (!registrar.uias[sym]) registrar.uias[sym] = [sym + '/GOLOS'] + } cfg.apidex_service = config.has('apidex_service') && config.get('apidex_service') diff --git a/src/utils/ApidexApiClient.js b/src/utils/ApidexApiClient.js index daea079..f8bedc3 100644 --- a/src/utils/ApidexApiClient.js +++ b/src/utils/ApidexApiClient.js @@ -89,7 +89,11 @@ export async function apidexExchange(apidex_service, sell, buySym) { try { let resp = await fetchEx(apidexUrl(apidex_service, `/api/v1/exchange/` + sell.toString() + '/' + buySym), request) resp = await resp.json() - return await Asset(resp.result) + return { + result: resp.result ? await Asset(resp.result) : null, + remain: resp.remain ? await Asset(resp.remain) : null, + error: resp.error + } } catch (err) { console.error('apidexExchange', err) return null