diff --git a/app/App.jsx b/app/App.jsx index a8e1c100b4..6ebe35d201 100644 --- a/app/App.jsx +++ b/app/App.jsx @@ -196,6 +196,14 @@ const QuickTrade = Loadable({ loading: LoadingIndicator }); +const PoolmartPage = Loadable({ + loader: () => + import( + /* webpackChunkName: "poolmart" */ "./components/Poolmart/PoolmartPage" + ), + loading: LoadingIndicator +}); + import LoginSelector from "./components/LoginSelector"; import Login from "./components/Login/Login"; import RegistrationSelector from "./components/Registration/RegistrationSelector"; @@ -638,6 +646,7 @@ class App extends React.Component { path="/instant-trade/:marketID" component={QuickTrade} /> + diff --git a/app/actions/AccountActions.js b/app/actions/AccountActions.js index 252b0452da..f41effc7db 100644 --- a/app/actions/AccountActions.js +++ b/app/actions/AccountActions.js @@ -1,7 +1,6 @@ import alt from "alt-instance"; import accountUtils from "common/account_utils"; import AccountApi from "api/accountApi"; - import WalletApi from "api/WalletApi"; import ApplicationApi from "api/ApplicationApi"; import WalletDb from "stores/WalletDb"; diff --git a/app/actions/AssetActions.js b/app/actions/AssetActions.js index 995acd4169..5e11241b93 100644 --- a/app/actions/AssetActions.js +++ b/app/actions/AssetActions.js @@ -254,7 +254,6 @@ class AssetActions { ); let tr = WalletApi.new_transaction(); let precision = utils.get_asset_precision(createObject.precision); - big.config({DECIMAL_PLACES: createObject.precision}); let max_supply = new big(createObject.max_supply) .times(precision) @@ -262,11 +261,9 @@ class AssetActions { let max_market_fee = new big(createObject.max_market_fee || 0) .times(precision) .toString(); - let corePrecision = utils.get_asset_precision( ChainStore.getAsset(cer.base.asset_id).get("precision") ); - let operationJSON = { fee: { amount: 0, @@ -309,11 +306,9 @@ class AssetActions { is_prediction_market: is_prediction_market, extensions: null }; - if (isBitAsset) { operationJSON.bitasset_opts = bitasset_opts; } - tr.add_type_operation("asset_create", operationJSON); return dispatch => { return WalletDb.process_transaction(tr, null, true) @@ -590,9 +585,64 @@ class AssetActions { }; } + getAssetsByIssuer(issuer, count, start, includeGateways = false){ + let id = issuer + "_" + count; + console.log("getAssetsByIssuer id = ", id); + return dispatch => { + if (!inProgress[id]) { + let assets; + inProgress[id] = true; + dispatch({loading: true}); + + assets = Apis.instance() + .db_api() + .exec("get_assets_by_issuer", [issuer, start, count]) + .then(assets => { + let bitAssetIDS = []; + let dynamicIDS = []; + + assets.forEach(asset => { + ChainStore._updateObject(asset, false); + dynamicIDS.push(asset.dynamic_asset_data_id); + }); + let dynamicPromise = Apis.instance() + .db_api() + .exec("get_objects", [dynamicIDS]); + Promise.all([dynamicPromise]).then( + results => { + delete inProgress[id]; + dispatch({ + assets: assets, + dynamic: results[0], + loading: false + }); + return assets && assets.length; + } + ); + }) + .catch(error => { + console.log( + "Error in AssetActions.getAssetList: ", + error + ); + dispatch({loading: false}); + delete inProgress[id]; + }); + + // Fetch next 10 assets for each gateAsset on request + if (includeGateways) { + gatewayPrefixes.forEach(a => { + this.getAssetList(a + "." + start, 10); + }); + } + + return assets; + } + }; + } + lookupAsset(symbol, searchID) { let asset = ChainStore.getAsset(symbol); - if (asset) { return { assets: [asset], diff --git a/app/actions/PoolActions.js b/app/actions/PoolActions.js new file mode 100644 index 0000000000..a13ac81719 --- /dev/null +++ b/app/actions/PoolActions.js @@ -0,0 +1,114 @@ +import alt from "alt-instance"; +import {Apis} from "bitsharesjs-ws"; +import utils from "common/utils"; +import WalletApi from "api/WalletApi"; +import WalletDb from "stores/WalletDb"; +import {ChainStore} from "bitsharesjs"; +import big from "bignumber.js"; +import {gatewayPrefixes} from "common/gateways"; +import {price} from "bitsharesjs/es/serializer/src/operations"; +let inProgress = {}; + +class PoolActions { + createPool( + account_id, + createObject, + flags, + permissions, + cer, + isBitAsset, + is_prediction_market, + bitasset_opts, + description + ) { + // Create pool action here... + console.log( + "create pool:", + createObject, + "flags:", + flags, + "isBitAsset:", + isBitAsset, + "bitasset_opts:", + bitasset_opts + ); + let tr = WalletApi.new_transaction(); + let precision = utils.get_asset_precision(createObject.precision); + big.config({DECIMAL_PLACES: createObject.precision}); + let max_supply = new big(createObject.max_supply) + .times(precision) + .toString(); + let max_market_fee = new big(createObject.max_market_fee || 0) + .times(precision) + .toString(); + let corePrecision = utils.get_asset_precision( + ChainStore.getAsset(cer.base.asset_id).get("precision") + ); + let operationJSON = { + fee: { + amount: 0, + asset_id: 0 + }, + issuer: account_id, + symbol: createObject.symbol, + precision: parseInt(createObject.precision, 10), + common_options: { + max_supply: max_supply, + market_fee_percent: createObject.market_fee_percent * 100 || 0, + max_market_fee: max_market_fee, + issuer_permissions: permissions, + flags: flags, + core_exchange_rate: { + base: { + amount: cer.base.amount * corePrecision, + asset_id: cer.base.asset_id + }, + quote: { + amount: cer.quote.amount * precision, + asset_id: "1.3.1" + } + }, + whitelist_authorities: [], + blacklist_authorities: [], + whitelist_markets: [], + blacklist_markets: [], + description: description, + extensions: { + reward_percent: createObject.reward_percent * 100 || 0, + whitelist_market_fee_sharing: [] + } + }, + is_prediction_market: is_prediction_market, + extensions: null + }; + if (isBitAsset) { + operationJSON.bitasset_opts = bitasset_opts; + } + tr.add_type_operation("asset_create", operationJSON); + return dispatch => { + return WalletDb.process_transaction(tr, null, true) + .then(result => { + // console.log("pool create result:", result); + dispatch(true); + }) + .catch(error => { + console.log("----- createAsset error ----->", error); + dispatch(false); + }); + }; + } + + create_liquidity_pool( + my_username, + asset_a, + asset_b, + share_asset, + taker_fee_percent, + withdrawal_fee_percent + ){ + + + } +} + +export default alt.createActions(PoolActions); diff --git a/app/actions/PoolmartActions.js b/app/actions/PoolmartActions.js new file mode 100644 index 0000000000..52b5900b87 --- /dev/null +++ b/app/actions/PoolmartActions.js @@ -0,0 +1,304 @@ +import alt from "alt-instance"; +import {Apis} from "bitsharesjs-ws"; +import Immutable from "immutable"; +import utils from "common/utils"; +import WalletApi from "api/WalletApi"; +import WalletDb from "stores/WalletDb"; +import {ChainStore} from "bitsharesjs"; +import big from "bignumber.js"; +import {gatewayPrefixes} from "common/gateways"; + +let inProgress = {}; + +class PoolmartActions { + /** + * getLiquidityPools + * @param {string} assetA (asset symbol or id) + * @param {string} assetB (asset symbol or id) + * @param {int} limit + * @param {string} start (pool id) + */ + getLiquidityPools(assetA, assetB, limit, start) { + let method = ""; + let params = []; + if (assetA && assetB) { + method = "get_liquidity_pools_by_both_assets"; + params = [assetA, assetB, limit, start]; + } else if (assetA) { + method = "get_liquidity_pools_by_asset_a"; + params = [assetA, limit, start]; + } else if (assetB) { + method = "get_liquidity_pools_by_asset_b"; + params = [assetB, limit, start]; + } + if (method === "") { + return dispatch => + dispatch({loading: false, liquidityPools: Immutable.Map()}); + } + const id = `${assetA}_${assetB}_${start}_${limit}`; + return dispatch => { + if (!inProgress[id]) { + inProgress[id] = true; + dispatch({loading: true}); + + Apis.instance() + .db_api() + .exec(method, params) + .then(liquidityPools => { + const tmpAssetIds = []; + liquidityPools.forEach(pool => { + if (tmpAssetIds.indexOf(pool.asset_a) === -1) { + tmpAssetIds.push(pool.asset_a); + } + if (tmpAssetIds.indexOf(pool.asset_b) === -1) { + tmpAssetIds.push(pool.asset_b); + } + if (tmpAssetIds.indexOf(pool.share_asset) === -1) { + tmpAssetIds.push(pool.share_asset); + } + }); + Apis.instance() + .db_api() + .exec("lookup_asset_symbols", [tmpAssetIds]) + .then(assetObjects => { + let tmpAssets = Immutable.Map(); + if (assetObjects.length) { + assetObjects.forEach(asset => { + tmpAssets = tmpAssets.set( + asset.id, + Immutable.fromJS(asset) + ); + }); + } + liquidityPools.map(pool => { + if (tmpAssets.has(pool.asset_a)) { + pool.asset_a_obj = tmpAssets.get( + pool.asset_a + ); + } else { + pool.asset_a_obj = undefined; + } + if (tmpAssets.has(pool.asset_b)) { + pool.asset_b_obj = tmpAssets.get( + pool.asset_b + ); + } else { + pool.asset_b_obj = undefined; + } + if (tmpAssets.has(pool.share_asset)) { + pool.share_asset_obj = tmpAssets.get( + pool.share_asset + ); + } else { + pool.share_asset_obj = undefined; + } + return pool; + }); + delete inProgress[id]; + dispatch({loading: false, liquidityPools}); + }); + }) + .catch(error => { + console.log( + "Error in PoolmartActions.getLiquidityPools: ", + error + ); + delete inProgress[id]; + dispatch({ + loading: false, + liquidityPools: Immutable.Map(), + reset: true + }); + }); + } + }; + } + + /** + * getLiquidityPools + * @param {string} shareAsset (asset symbol or id) + * @param {int} limit + * @param {string} start (pool id) + */ + getLiquidityPoolsByShareAsset(shareAsset) { + const id = `${shareAsset}_poolmart`; + return dispatch => { + if (!inProgress[id]) { + inProgress[id] = true; + dispatch({loading: true}); + + Apis.instance() + .db_api() + .exec("get_liquidity_pools_by_share_asset", [ + [shareAsset], + false + ]) + .then(liquidityPools => { + const tmpAssetIds = []; + liquidityPools.forEach(pool => { + if (pool === null) return; + if (tmpAssetIds.indexOf(pool.asset_a) === -1) { + tmpAssetIds.push(pool.asset_a); + } + if (tmpAssetIds.indexOf(pool.asset_b) === -1) { + tmpAssetIds.push(pool.asset_b); + } + if (tmpAssetIds.indexOf(pool.share_asset) === -1) { + tmpAssetIds.push(pool.share_asset); + } + }); + if (tmpAssetIds.length > 0) { + Apis.instance() + .db_api() + .exec("lookup_asset_symbols", [tmpAssetIds]) + .then(assetObjects => { + let tmpAssets = Immutable.Map(); + if (assetObjects.length) { + assetObjects.forEach(asset => { + tmpAssets = tmpAssets.set( + asset.id, + Immutable.fromJS(asset) + ); + }); + } + liquidityPools.map(pool => { + if (tmpAssets.has(pool.asset_a)) { + pool.asset_a_obj = tmpAssets.get( + pool.asset_a + ); + } else { + pool.asset_a_obj = undefined; + } + if (tmpAssets.has(pool.asset_b)) { + pool.asset_b_obj = tmpAssets.get( + pool.asset_b + ); + } else { + pool.asset_b_obj = undefined; + } + if (tmpAssets.has(pool.share_asset)) { + pool.share_asset_obj = tmpAssets.get( + pool.share_asset + ); + } else { + pool.share_asset_obj = undefined; + } + return pool; + }); + delete inProgress[id]; + dispatch({loading: false, liquidityPools}); + }); + } else { + delete inProgress[id]; + dispatch({loading: false, liquidityPools: []}); + } + }) + .catch(error => { + console.log( + "Error in PoolmartActions.getLiquidityPoolsByShareAsset: ", + error + ); + delete inProgress[id]; + dispatch({ + loading: false, + liquidityPools: Immutable.Map(), + reset: true + }); + }); + } + }; + } + + resetLiquidityPools() { + return dispatch => dispatch(true); + } + + /** + * getLiquidityPoolsAccount + * @param {string} account_name (asset symbol or id) + */ + getLiquidityPoolsAccount(account_name) { + const id = `${account_name}_account`; + return dispatch => { + if (!inProgress[id]) { + inProgress[id] = true; + dispatch({loading: true}); + + Apis.instance() + .db_api() + .exec("get_liquidity_pools_by_owner", [ + account_name + ]) + .then(liquidityPools => { + const tmpAssetIds = []; + liquidityPools.forEach(pool => { + if (pool === null) return; + if (tmpAssetIds.indexOf(pool.asset_a) === -1) { + tmpAssetIds.push(pool.asset_a); + } + if (tmpAssetIds.indexOf(pool.asset_b) === -1) { + tmpAssetIds.push(pool.asset_b); + } + if (tmpAssetIds.indexOf(pool.share_asset) === -1) { + tmpAssetIds.push(pool.share_asset); + } + }); + Apis.instance() + .db_api() + .exec("lookup_asset_symbols", [tmpAssetIds]) + .then(assetObjects => { + let tmpAssets = Immutable.Map(); + if (assetObjects.length) { + assetObjects.forEach(asset => { + tmpAssets = tmpAssets.set( + asset.id, + Immutable.fromJS(asset) + ); + }); + } + liquidityPools.map(pool => { + if (tmpAssets.has(pool.asset_a)) { + pool.asset_a_obj = tmpAssets.get( + pool.asset_a + ); + } else { + pool.asset_a_obj = undefined; + } + if (tmpAssets.has(pool.asset_b)) { + pool.asset_b_obj = tmpAssets.get( + pool.asset_b + ); + } else { + pool.asset_b_obj = undefined; + } + if (tmpAssets.has(pool.share_asset)) { + pool.share_asset_obj = tmpAssets.get( + pool.share_asset + ); + } else { + pool.share_asset_obj = undefined; + } + return pool; + }); + delete inProgress[id]; + dispatch({loading: false, liquidityPools}); + }); + }) + .catch(error => { + console.log( + "Error in PoolmartActions.getLiquidityPoolsByShareAsset: ", + error + ); + delete inProgress[id]; + dispatch({ + loading: false, + liquidityPools: Immutable.Map(), + reset: true + }); + }); + } + }; + } +} + +export default alt.createActions(PoolmartActions); diff --git a/app/api/ApplicationApi.js b/app/api/ApplicationApi.js index 0203e943f7..a4c7d44fe2 100644 --- a/app/api/ApplicationApi.js +++ b/app/api/ApplicationApi.js @@ -846,6 +846,231 @@ const ApplicationApi = { }, extensions: {} }); + transactionBuilder.add_operation(op); + await WalletDb.process_transaction(transactionBuilder, null, broadcast); + if (!transactionBuilder.tr_buffer) { + throw "Something went finalization the transaction, this should not happen"; + } + }, + + async liquidityPoolCreate( + account, + assetA, + assetB, + shareAsset, + takerFeePercent, + withdrawalFeePercent, + feeAsset = "1.3.0", + broadcast = true + ) { + // account must be unlocked + await WalletUnlockActions.unlock(); + + // ensure all arguments are chain objects + let objects = { + account: await this._ensureAccount(account), + assetA: await this._ensureAsset(assetA), + assetB: await this._ensureAsset(assetB), + shareAsset: await this._ensureAsset(shareAsset), + feeAsset: await this._ensureAsset(feeAsset) + }; + + let transactionBuilder = new TransactionBuilder(); + let op = transactionBuilder.get_type_operation( + "liquidity_pool_create", + { + fee: { + amount: 0, + asset_id: objects.feeAsset.get("id") + }, + account: objects.account.get("id"), + asset_a: objects.assetA.get("id"), + asset_b: objects.assetB.get("id"), + share_asset: objects.shareAsset.get("id"), + taker_fee_percent: takerFeePercent, + withdrawal_fee_percent: withdrawalFeePercent, + extensions: {} + } + ); + + transactionBuilder.add_operation(op); + await WalletDb.process_transaction(transactionBuilder, null, broadcast); + if (!transactionBuilder.tr_buffer) { + throw "Something went finalization the transaction, this should not happen"; + } + }, + + async liquidityPoolDelete( + account, + liquidityPoolId, + feeAsset = "1.3.0", + broadcast = true + ) { + // account must be unlocked + await WalletUnlockActions.unlock(); + + // ensure all arguments are chain objects + let objects = { + account: await this._ensureAccount(account), + feeAsset: await this._ensureAsset(feeAsset) + }; + + let transactionBuilder = new TransactionBuilder(); + let op = transactionBuilder.get_type_operation( + "liquidity_pool_delete", + { + fee: { + amount: 0, + asset_id: objects.feeAsset.get("id") + }, + account: objects.account.get("id"), + pool: liquidityPoolId, + extensions: {} + } + ); + + transactionBuilder.add_operation(op); + await WalletDb.process_transaction(transactionBuilder, null, broadcast); + if (!transactionBuilder.tr_buffer) { + throw "Something went finalization the transaction, this should not happen"; + } + }, + + async liquidityPoolDeposit( + account, + liquidityPoolId, + assetA, + assetB, + amountA, + amountB, + feeAsset = "1.3.0", + broadcast = true + ) { + // account must be unlocked + await WalletUnlockActions.unlock(); + + // ensure all arguments are chain objects + let objects = { + account: await this._ensureAccount(account), + assetA: await this._ensureAsset(assetA), + assetB: await this._ensureAsset(assetB), + feeAsset: await this._ensureAsset(feeAsset) + }; + + let transactionBuilder = new TransactionBuilder(); + let op = transactionBuilder.get_type_operation( + "liquidity_pool_deposit", + { + fee: { + amount: 0, + asset_id: objects.feeAsset.get("id") + }, + account: objects.account.get("id"), + pool: liquidityPoolId, + amount_a: { + amount: amountA, + asset_id: objects.assetA.get("id") + }, + amount_b: { + amount: amountB, + asset_id: objects.assetB.get("id") + }, + extensions: {} + } + ); + + transactionBuilder.add_operation(op); + await WalletDb.process_transaction(transactionBuilder, null, broadcast); + if (!transactionBuilder.tr_buffer) { + throw "Something went finalization the transaction, this should not happen"; + } + }, + + async liquidityPoolWithdraw( + account, + liquidityPoolId, + shareAsset, + shareAmount, + feeAsset = "1.3.0", + broadcast = true + ) { + // account must be unlocked + await WalletUnlockActions.unlock(); + + // ensure all arguments are chain objects + let objects = { + account: await this._ensureAccount(account), + shareAsset: await this._ensureAsset(shareAsset), + feeAsset: await this._ensureAsset(feeAsset) + }; + + let transactionBuilder = new TransactionBuilder(); + let op = transactionBuilder.get_type_operation( + "liquidity_pool_withdraw", + { + fee: { + amount: 0, + asset_id: objects.feeAsset.get("id") + }, + account: objects.account.get("id"), + pool: liquidityPoolId, + share_amount: { + amount: shareAmount, + asset_id: objects.shareAsset.get("id") + }, + extensions: {} + } + ); + + transactionBuilder.add_operation(op); + await WalletDb.process_transaction(transactionBuilder, null, broadcast); + if (!transactionBuilder.tr_buffer) { + throw "Something went finalization the transaction, this should not happen"; + } + }, + + async liquidityPoolExchange( + account, + liquidityPoolId, + saleAsset, + amountToSell, + receiveAsset, + minToReceive, + feeAsset = "1.3.0", + broadcast = true + ) { + // account must be unlocked + await WalletUnlockActions.unlock(); + + // ensure all arguments are chain objects + let objects = { + account: await this._ensureAccount(account), + saleAsset: await this._ensureAsset(saleAsset), + receiveAsset: await this._ensureAsset(receiveAsset), + feeAsset: await this._ensureAsset(feeAsset) + }; + + let transactionBuilder = new TransactionBuilder(); + let op = transactionBuilder.get_type_operation( + "liquidity_pool_exchange", + { + fee: { + amount: 0, + asset_id: objects.feeAsset.get("id") + }, + account: objects.account.get("id"), + pool: liquidityPoolId, + amount_to_sell: { + amount: amountToSell, + asset_id: objects.saleAsset.get("id") + }, + min_to_receive: { + amount: minToReceive, + asset_id: objects.receiveAsset.get("id") + }, + extensions: {} + } + ); transactionBuilder.add_operation(op); await WalletDb.process_transaction(transactionBuilder, null, broadcast); diff --git a/app/assets/icons/arrow-down-1.svg b/app/assets/icons/arrow-down-1.svg new file mode 100644 index 0000000000..b100e517e1 --- /dev/null +++ b/app/assets/icons/arrow-down-1.svg @@ -0,0 +1 @@ + diff --git a/app/assets/icons/arrow-up-down.svg b/app/assets/icons/arrow-up-down.svg new file mode 100644 index 0000000000..8576e84b4e --- /dev/null +++ b/app/assets/icons/arrow-up-down.svg @@ -0,0 +1 @@ + diff --git a/app/assets/icons/delete.svg b/app/assets/icons/delete.svg new file mode 100644 index 0000000000..262e74c8cb --- /dev/null +++ b/app/assets/icons/delete.svg @@ -0,0 +1 @@ + diff --git a/app/assets/icons/icons-loader.js b/app/assets/icons/icons-loader.js index 9e86d3b410..94ba47edeb 100644 --- a/app/assets/icons/icons-loader.js +++ b/app/assets/icons/icons-loader.js @@ -17,6 +17,7 @@ let icons = [ "connect", "cross-circle", "dashboard", + "delete", "deposit", "disconnected", "direct_debit", @@ -75,6 +76,10 @@ let icons = [ "create_account", "swap", "instant-trade", + "poolmart", + "arrow-down-1", + "arrow-up-down", + "pools", "qr-scan", "deployment-unit" ]; diff --git a/app/assets/icons/poolmart.svg b/app/assets/icons/poolmart.svg new file mode 100644 index 0000000000..16a2e4a733 --- /dev/null +++ b/app/assets/icons/poolmart.svg @@ -0,0 +1 @@ + diff --git a/app/assets/icons/pools.svg b/app/assets/icons/pools.svg new file mode 100644 index 0000000000..55ac5093f6 --- /dev/null +++ b/app/assets/icons/pools.svg @@ -0,0 +1,50 @@ + + + + +Created by potrace 1.10, written by Peter Selinger 2001-2011 + + + + + + + + diff --git a/app/assets/locales/locale-en.json b/app/assets/locales/locale-en.json index 12fb16eff5..8ea7b88d57 100644 --- a/app/assets/locales/locale-en.json +++ b/app/assets/locales/locale-en.json @@ -137,6 +137,7 @@ "new_user": "New user?", "no_order_history": "No order history", "no_orders": "No open orders", + "no_trades": "No last trades", "no_price": "--", "open_orders": "Open Orders", "optional": { @@ -352,6 +353,30 @@ "disable_bsrm_update": "Disable owner update BSRM", "disable_collateral_bidding": "Disable collateral bidding" }, + "liquidity_pools": { + "asset_a": "Asset A", + "asset_a_balance": "Asset A Balance", + "asset_b": "Asset B", + "asset_b_balance": "Asset B Balance", + "create_pool": "Create Pool", + "delete": "Delete", + "liquidity_pools": "Liquidity Pools", + "pool_name": "Pool Name", + "pool_balance": "Pool Balance", + "stake_status": "Stake/Unstake", + "title": "Pools", + "taker_fee": "Taker Fee", + "unstake_fee": "Unstake Fee", + "alert_asset_a": "ID of ASSET A should be greater than ID of ASSET B", + "alert_asset_b": "ID of ASSET B should be smaller than ID of ASSET A", + "alert_taker_fee": "", + "alert_unstack_fee": "", + "alert_request_input_pool": "Please select pool", + "alert_request_input_asset_a": "Please select ASSET A", + "alert_request_input_asset_b": "Please select ASSET B", + "alert_request_input_taker_fee": "Please input taker fee", + "alert_request_input_unstack_fee": "Please input unstacke fee" + }, "vesting": { "balance_number": "Balance #%(id)s", "explain": "Vesting balances contain any fees earned through the referral program or from worker pay, for example. They have a certain vesting period and are continually unlocked during that vesting period until all of the balances are available", @@ -1364,6 +1389,7 @@ "logout": "Logout", "payments": "Send", "payments_legacy": "or Legacy Send", + "poolmart": "Liquidity Pools", "settings": "Settings", "showcases": "Spotlight", "title": "%(wallet_name)s UI", @@ -1464,6 +1490,9 @@ "show_asset": "Show asset", "show_market": "Show market" }, + "poolmart": { + "title": "Liquidity Pools" + }, "power": "Login", "question_circle": "Help", "reverse_orderbook": "Reverse orderbook", @@ -1870,6 +1899,29 @@ "newer": "Newer", "older": "Older" }, + "poolmart": { + "liquidity_pools": { + "amount_to_sell": "Amount to sell", + "asset_a": "Asset A", + "asset_b": "Asset B", + "asset_a_qty": "Asset A QTY", + "asset_b_qty": "Asset B QTY", + "exchange": "Exchange", + "min_to_receive": "Min to receive", + "need_stake_first": "Need Stake The Pool First!", + "pool_id": "Pool Id", + "share_asset": "Share Asset", + "stake_unstake": "Stake/Unstake", + "taker_fee_percent": "Taker Fee", + "taker_fee_percent_rate": "Taker Fee Rate", + "title": "Pools", + "withdrawal_fee_percent": "Withdrawal Fee", + "stake": "Stake", + "unstake": "Unstake", + "delete_pool": "Delete", + "confirm_delete_pool": "Are you sure you want to delete the pool?" + } + }, "prediction": { "add_opinion_modal": { "amount": "Amount", diff --git a/app/assets/stylesheets/components/_account-create.scss b/app/assets/stylesheets/components/_account-create.scss index b314717cac..9bf3a793e7 100644 --- a/app/assets/stylesheets/components/_account-create.scss +++ b/app/assets/stylesheets/components/_account-create.scss @@ -78,7 +78,7 @@ div.account-creation { margin: 0 auto 15px; margin-top: 3px; text-transform: uppercase; - font-size: 0.875rem; + font-size: 0.9rem; width: 40%; color: #fff; display: flex; @@ -134,3 +134,6 @@ div.account-creation { } } } +.d-flex { + display: flex; +} diff --git a/app/assets/stylesheets/components/_all.scss b/app/assets/stylesheets/components/_all.scss index 8c9d028273..40d4197746 100644 --- a/app/assets/stylesheets/components/_all.scss +++ b/app/assets/stylesheets/components/_all.scss @@ -33,6 +33,7 @@ @import "voting"; @import "invoice"; @import "quick_trade"; +@import "poolmart"; .lifetime { background: url("../fresh-bolt2.png") no-repeat 100% 50%; diff --git a/app/assets/stylesheets/components/_exchange.scss b/app/assets/stylesheets/components/_exchange.scss index 00191540aa..eeb27393b6 100644 --- a/app/assets/stylesheets/components/_exchange.scss +++ b/app/assets/stylesheets/components/_exchange.scss @@ -120,13 +120,13 @@ div#CenterContent { &.fixed-table { table-layout: fixed; - text-align: right; + text-align: left; } > thead > tr > th { font-size: 14px; padding: 5px 5px; - text-align: right; + text-align: left !important; } > tbody > tr > td { diff --git a/app/assets/stylesheets/components/_poolmart.scss b/app/assets/stylesheets/components/_poolmart.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/app/assets/stylesheets/components/_poolmart.scss @@ -0,0 +1 @@ + diff --git a/app/assets/stylesheets/layout/_page_layout.scss b/app/assets/stylesheets/layout/_page_layout.scss index abd23c5a69..b55d4bce54 100644 --- a/app/assets/stylesheets/layout/_page_layout.scss +++ b/app/assets/stylesheets/layout/_page_layout.scss @@ -164,3 +164,15 @@ div.bordered-header { padding-right: 5px; } } +.mt-10 { + margin-top: 10px; +} +.mb-10 { + margin-bottom: 10px; +} +.mt-16 { + margin-top: 16px; +} +.mb-16 { + margin-bottom: 16px; +} diff --git a/app/assets/stylesheets/themes/_theme-template.scss b/app/assets/stylesheets/themes/_theme-template.scss index 29fbe17b20..e9053366ec 100644 --- a/app/assets/stylesheets/themes/_theme-template.scss +++ b/app/assets/stylesheets/themes/_theme-template.scss @@ -2123,7 +2123,7 @@ } } .ant-table-placeholder { - min-height: 130px; + min-height: 140px; z-index: 0; } div.account-creation { diff --git a/app/components/Account/AccountPage.jsx b/app/components/Account/AccountPage.jsx index f33aecbc05..4279537941 100644 --- a/app/components/Account/AccountPage.jsx +++ b/app/components/Account/AccountPage.jsx @@ -13,6 +13,7 @@ import {Route, Switch, Redirect} from "react-router-dom"; /* Nested routes */ import AccountAssets from "./AccountAssets"; +import AccountPools from "./AccountPools"; import {AccountAssetCreate} from "./AccountAssetCreate"; import AccountAssetUpdate from "./AccountAssetUpdate"; import AccountMembership from "./AccountMembership"; @@ -115,6 +116,11 @@ class AccountPage extends React.Component { exact render={() => } /> + } + /> 0 && + liquidityPools.last().id !== this.props.lastPoolId + ) { + this.setState( + { + start: liquidityPools.last().id + }, + () => this._getLiquidityPools() + ); + } + } + } + + _getLiquidityPools() { + + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + PoolmartActions.getLiquidityPoolsAccount.defer(this.props.account_name); + }, 500); + } + + _resetLiquidityPools() { + this.setState({ + start: "1.19.0", + lastPoolId: null, + total: 0 + }); + PoolmartActions.resetLiquidityPools(); + } + + _onFilterAssetA(e) { + if (e.target.value) { + this.setState( + { + filterAssetA: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterAssetA: ""}); + this._resetLiquidityPools(); + } + } + + _onFilterAssetB(e) { + if (e.target.value) { + this.setState( + { + filterAssetB: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterAssetB: ""}); + this._resetLiquidityPools(); + } + } + + _onFilterShareAsset(e) { + if (e.target.value) { + this.setState( + { + filterAssetA: null, + filterAssetB: null, + filterShareAsset: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterShareAsset: ""}); + this._resetLiquidityPools(); + } + } + + _handleRowsChange(limit) { + this.setState( + { + limit: parseInt(limit, 10), + start: "1.19.0" + }, + () => { + this._resetLiquidityPools(); + this._getLiquidityPools(); + } + ); + } + + _showExchangeModal(pool) { + this.setState({ + isExchangeModalVisible: true, + selectedPool: pool + }); + } + + _hideExchangeModal() { + this.setState({ + isExchangeModalVisible: false, + selectedPool: null + }); + } + + _showStakeModal(pool) { + this.setState({ + isStakeModalVisible: true, + selectedPool: pool + }); + } + + _hideStakeModal() { + this.setState({ + isStakeModalVisible: false, + selectedPool: null + }); + } + + _createButtonClick(account_name) { + this.showCreatePoolModal(); + } + + showCreatePoolModal() { + this.setState({isCreatePoolModalVisible: true}); + } + + hideCreatePoolModal() { + this.setState({isCreatePoolModalVisible: false}); + } + + _showDeleteModal(pool) { + this.setState({ + isDeletePoolModalVisible: true, + selectedPool: pool + }); + } + + _hideDeleteModal(pool) { + this.setState({ + isDeletePoolModalVisible: false, + selectedPool: pool + }); + } + + _deletePool(pool){ + console.log("_deletePool invoked."); + + const {account} = this.props; + const selectedPool = this.state.selectedPool; + + this.setState({ + isDeletePoolModalVisible: false + }); + + ApplicationApi.liquidityPoolDelete( + account, + selectedPool["id"] + ).then(() => { + + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + PoolmartActions.getLiquidityPoolsAccount.defer(this.props.account_name); + }, 500); + + }); + + + + + + } + + render() { + const columns = [ + { + key: "id", + dataIndex: "id", + title: counterpart.translate( + "poolmart.liquidity_pools.pool_id" + ), + sorter: (a, b) => { + const aId = a.id.split(".")[2]; + const bId = b.id.split(".")[2]; + return aId - bId; + } + }, + { + key: "share_asset_str", + dataIndex: "share_asset_str", + title: counterpart.translate( + "poolmart.liquidity_pools.share_asset" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.share_asset_str > b.share_asset_str + ? 1 + : a.share_asset_str < b.share_asset_str + ? -1 + : 0 + }, + { + key: "asset_a_str", + dataIndex: "asset_a_str", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_a" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.asset_a_str > b.asset_a_str + ? 1 + : a.asset_a_str < b.asset_a_str + ? -1 + : 0 + }, + { + key: "asset_a_qty", + dataIndex: "asset_a_qty", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_a_qty" + ), + sorter: (a, b) => a.asset_a_qty - b.asset_a_qty + }, + { + key: "asset_b_str", + dataIndex: "asset_b_str", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_b" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.asset_b_str > b.asset_b_str + ? 1 + : a.asset_b_str < b.asset_b_str + ? -1 + : 0 + }, + { + key: "asset_b_qty", + dataIndex: "asset_b_qty", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_b_qty" + ), + sorter: (a, b) => a.asset_b_qty - b.asset_b_qty + }, + { + key: "taker_fee_percent", + dataIndex: "taker_fee_percent_str", + title: counterpart.translate( + "poolmart.liquidity_pools.taker_fee_percent" + ) + }, + { + key: "withdrawal_fee_percent", + dataIndex: "withdrawal_fee_percent_str", + title: counterpart.translate( + "poolmart.liquidity_pools.withdrawal_fee_percent" + ) + }, + { + key: "exchange", + title: counterpart.translate( + "poolmart.liquidity_pools.exchange" + ), + render: item => ( + this._showExchangeModal(item)}> + + + ) + }, + { + key: "stake_unstake", + title: counterpart.translate( + "poolmart.liquidity_pools.stake_unstake" + ), + render: item => ( + this._showStakeModal(item)}> + + + ) + }, + { + key: "delete_pool", + title: counterpart.translate( + "poolmart.liquidity_pools.delete_pool" + ), + render: item => ( + this._showDeleteModal(item)}> + + + ) + } + ]; + let {account, account_name, assets, assetsList} = this.props; + + if (assetsList.length) { + assets = assets.clear(); + assetsList.forEach(a => { + if (a) assets = assets.set(a.get("id"), a.toJS()); + }); + } + + const dataSource = []; + this.props.liquidityPools.forEach(pool => { + const row = pool; + row.share_asset_str = pool.share_asset_obj + ? pool.share_asset_obj.get("symbol") + : pool.share_asset; + row.asset_a_str = pool.asset_a_obj + ? pool.asset_a_obj.get("symbol") + : pool.asset_a; + row.asset_b_str = pool.asset_b_obj + ? pool.asset_b_obj.get("symbol") + : pool.asset_b; + row.asset_a_qty = pool.asset_a_obj + ? pool.balance_a / + Math.pow(10, pool.asset_a_obj.get("precision")) + : 0; + row.asset_b_qty = pool.asset_b_obj + ? pool.balance_b / + Math.pow(10, pool.asset_b_obj.get("precision")) + : 0; + row.taker_fee_percent_str = `${pool.taker_fee_percent / 100}%`; + row.withdrawal_fee_percent_str = `${pool.withdrawal_fee_percent / + 100}%`; + dataSource.push(row); + }); + return ( +
+ + +
+
+ + + + +
+
+ + +
+ +
+ {this.state.isExchangeModalVisible && ( + + )} + {this.state.isStakeModalVisible && ( + + )} + {this.state.isDeletePoolModalVisible && ( + + )} + + + + + + ); + } +} + +AccountPools = BindToChainState(AccountPools, {show_loader: true}); + +class AccountPoolsStoreWrapper extends React.Component { + render() { + return ; + } +} + +export default connect( + AccountPoolsStoreWrapper, + { + listenTo() { + return [PoolmartStore, AssetStore]; + }, + getProps(props) { + let assets = Map(), + assetsList = List(); + if (props.account.get("assets", []).size) { + props.account.get("assets", []).forEach(id => { + assetsList = assetsList.push(id); + }); + } + let liquidityPools = PoolmartStore.getState().liquidityPools; + assets = AssetStore.getState().assets; + return { + liquidityPools: PoolmartStore.getState().liquidityPools, + liquidityPoolsLoading: PoolmartStore.getState() + .liquidityPoolsLoading, + lastPoolId: PoolmartStore.getState().lastPoolId, + assets: assets, + assetsList: assetsList + }; + } + } +); diff --git a/app/components/Account/CreditOffer/CreditOfferAccountPage.jsx b/app/components/Account/CreditOffer/CreditOfferAccountPage.jsx index 56bacd56cd..005e5e1709 100644 --- a/app/components/Account/CreditOffer/CreditOfferAccountPage.jsx +++ b/app/components/Account/CreditOffer/CreditOfferAccountPage.jsx @@ -2,7 +2,7 @@ import React from "react"; import {connect} from "alt-react"; import counterpart from "counterpart"; import {Tabs, Tab} from "../../Utility/Tabs"; -import CreditOfferList from "./CreditOfferList"; +import erList from "./CreditOfferList"; import CreditDebtList from "./CreditDebtList"; import CreditRightsList from "./CreditRightsList"; diff --git a/app/components/Exchange/ExchangeHeader.jsx b/app/components/Exchange/ExchangeHeader.jsx index c2ee83b150..229ee3342e 100644 --- a/app/components/Exchange/ExchangeHeader.jsx +++ b/app/components/Exchange/ExchangeHeader.jsx @@ -279,10 +279,12 @@ export default class ExchangeHeader extends React.Component { {`${quoteSymbol} : ${baseSymbol}`} )} -
+
diff --git a/app/components/Exchange/View/MarketHistoryView.jsx b/app/components/Exchange/View/MarketHistoryView.jsx index 8c9b29e7c2..7445530521 100644 --- a/app/components/Exchange/View/MarketHistoryView.jsx +++ b/app/components/Exchange/View/MarketHistoryView.jsx @@ -70,7 +70,7 @@ class MarketHistoryView extends React.Component { }} colSpan="5" > - + ); diff --git a/app/components/Explorer/Explorer.jsx b/app/components/Explorer/Explorer.jsx index d9045bdd80..05be7909ea 100644 --- a/app/components/Explorer/Explorer.jsx +++ b/app/components/Explorer/Explorer.jsx @@ -5,6 +5,7 @@ import FeesContainer from "../Blockchain/FeesContainer"; import BlocksContainer from "./BlocksContainer"; import AssetsContainer from "./AssetsContainer"; import AccountsContainer from "./AccountsContainer"; +import LiquidityPools from "./LiquidityPools"; import counterpart from "counterpart"; import MarketsContainer from "../Exchange/MarketsContainer"; import {Tabs} from "bitshares-ui-style-guide"; @@ -27,6 +28,12 @@ class Explorer extends React.Component { translate: "explorer.assets.title", content: AssetsContainer }, + { + name: "pools", + link: "/explorer/pools", + translate: "poolmart.liquidity_pools.title", + content: LiquidityPools + }, { name: "accounts", link: "/explorer/accounts", diff --git a/app/components/Explorer/LiquidityPools.jsx b/app/components/Explorer/LiquidityPools.jsx new file mode 100644 index 0000000000..f08e28b36a --- /dev/null +++ b/app/components/Explorer/LiquidityPools.jsx @@ -0,0 +1,478 @@ +import React from "react"; +import {connect} from "alt-react"; +import {Table, Select} from "bitshares-ui-style-guide"; +import Immutable from "immutable"; +import {Link} from "react-router-dom"; +import counterpart from "counterpart"; +import {ChainStore} from "bitsharesjs"; +import {debounce} from "lodash-es"; +import Translate from "react-translate-component"; +import ChainTypes from "../Utility/ChainTypes"; +import AssetName from "../Utility/AssetName"; +import BindToChainState from "../Utility/BindToChainState"; +import SearchInput from "../Utility/SearchInput"; +import PoolmartStore from "../../stores/PoolmartStore"; +import PoolmartActions from "../../actions/PoolmartActions"; +import Icon from "../Icon/Icon"; +import PoolExchangeModal from "../Modal/PoolExchangeModal"; +import PoolStakeModal from "../Modal/PoolStakeModal"; +import AccountStore from "../../stores/AccountStore"; + + +class LiquidityPools extends React.Component { + static propTypes = { + defaultAsset: ChainTypes.ChainAsset.isRequired + }; + + static defaultProps = { + defaultAsset: "1.3.0" + }; + + constructor(props) { + super(props); + + this.state = { + filterAssetA: this.props.defaultAsset + ? this.props.defaultAsset.get("symbol") + : null, + filterAssetB: null, + filterShareAsset: null, + start: "1.19.0", + limit: 10, + total: 0, + isExchangeModalVisible: false, + isStakeModalVisible: false, + selectedPool: null + }; + + this.timer = null; + } + + componentDidMount() { + this._getLiquidityPools(); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.liquidityPools !== this.props.liquidityPools) { + const {liquidityPools} = nextProps; + if ( + liquidityPools.size > 0 && + liquidityPools.last().id !== this.props.lastPoolId + ) { + this.setState( + { + start: liquidityPools.last().id + }, + () => this._getLiquidityPools() + ); + } + } + } + + _getLiquidityPools() { + const { + filterAssetA, + filterAssetB, + filterShareAsset, + GetLimit, + start + } = this.state; + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + if (filterShareAsset) { + PoolmartActions.getLiquidityPoolsByShareAsset.defer( + filterShareAsset + ); + } else { + PoolmartActions.getLiquidityPools.defer( + filterAssetA, + filterAssetB, + GetLimit, + start + ); + } + }, 500); + } + + _resetLiquidityPools() { + this.setState({ + start: "1.19.0", + lastPoolId: null, + total: 0 + }); + PoolmartActions.resetLiquidityPools(); + } + + _onFilterAssetA(e) { + if (e.target.value) { + this.setState( + { + filterAssetA: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterAssetA: ""}); + this._resetLiquidityPools(); + } + } + + _onFilterAssetB(e) { + if (e.target.value) { + this.setState( + { + filterAssetB: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterAssetB: ""}); + this._resetLiquidityPools(); + } + } + + _onFilterShareAsset(e) { + if (e.target.value) { + this.setState( + { + filterAssetA: null, + filterAssetB: null, + filterShareAsset: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterShareAsset: ""}); + this._resetLiquidityPools(); + } + } + + _handleRowsChange(limit) { + this.setState( + { + limit: parseInt(limit, 10), + start: "1.19.0" + }, + () => { + this._resetLiquidityPools(); + this._getLiquidityPools(); + } + ); + } + + _showExchangeModal(pool) { + this.setState({ + isExchangeModalVisible: true, + selectedPool: pool + }); + } + + _hideExchangeModal() { + this.setState({ + isExchangeModalVisible: false, + selectedPool: null + }); + } + + _showStakeModal(pool) { + this.setState({ + isStakeModalVisible: true, + selectedPool: pool + }); + } + + _hideStakeModal() { + this.setState({ + isStakeModalVisible: false, + selectedPool: null + }); + } + + render() { + + let hasLoggedIn = + AccountStore.getState().myActiveAccounts.length > 0 || + !!AccountStore.getState().currentAccount; + + const tile = { + disabled: hasLoggedIn + ? false + : "Please login to use this functionality" + }; + + + const columns = [ + { + key: "id", + dataIndex: "id", + title: counterpart.translate( + "poolmart.liquidity_pools.pool_id" + ), + sorter: (a, b) => { + const aId = a.id.split(".")[2]; + const bId = b.id.split(".")[2]; + return aId - bId; + } + }, + { + key: "share_asset_str", + dataIndex: "share_asset_str", + title: counterpart.translate( + "poolmart.liquidity_pools.share_asset" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.share_asset_str > b.share_asset_str + ? 1 + : a.share_asset_str < b.share_asset_str + ? -1 + : 0 + }, + { + key: "asset_a_str", + dataIndex: "asset_a_str", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_a" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.asset_a_str > b.asset_a_str + ? 1 + : a.asset_a_str < b.asset_a_str + ? -1 + : 0 + }, + { + key: "asset_a_qty", + dataIndex: "asset_a_qty", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_a_qty" + ), + sorter: (a, b) => a.asset_a_qty - b.asset_a_qty + }, + { + key: "asset_b_str", + dataIndex: "asset_b_str", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_b" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.asset_b_str > b.asset_b_str + ? 1 + : a.asset_b_str < b.asset_b_str + ? -1 + : 0 + }, + { + key: "asset_b_qty", + dataIndex: "asset_b_qty", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_b_qty" + ), + sorter: (a, b) => a.asset_b_qty - b.asset_b_qty + }, + { + key: "taker_fee_percent", + dataIndex: "taker_fee_percent_str", + title: counterpart.translate( + "poolmart.liquidity_pools.taker_fee_percent" + ) + }, + { + key: "withdrawal_fee_percent", + dataIndex: "withdrawal_fee_percent_str", + title: counterpart.translate( + "poolmart.liquidity_pools.withdrawal_fee_percent" + ) + }, + { + key: "exchange", + title: counterpart.translate( + "poolmart.liquidity_pools.exchange" + ), + render: item => ( + hasLoggedIn ? + this._showExchangeModal(item)}> + + : + ) + }, + { + key: "stake_unstake", + title: counterpart.translate( + "poolmart.liquidity_pools.stake_unstake" + ), + render: item => ( + hasLoggedIn ? + this._showStakeModal(item)}> + + : + ) + } + ]; + + + const dataSource = []; + this.props.liquidityPools.forEach(pool => { + const row = pool; + row.share_asset_str = pool.share_asset_obj + ? pool.share_asset_obj.get("symbol") + : pool.share_asset; + row.asset_a_str = pool.asset_a_obj + ? pool.asset_a_obj.get("symbol") + : pool.asset_a; + row.asset_b_str = pool.asset_b_obj + ? pool.asset_b_obj.get("symbol") + : pool.asset_b; + row.asset_a_qty = pool.asset_a_obj + ? pool.balance_a / + Math.pow(10, pool.asset_a_obj.get("precision")) + : 0; + row.asset_b_qty = pool.asset_b_obj + ? pool.balance_b / + Math.pow(10, pool.asset_b_obj.get("precision")) + : 0; + row.taker_fee_percent_str = `${pool.taker_fee_percent / 100}%`; + row.withdrawal_fee_percent_str = `${pool.withdrawal_fee_percent / + 100}%`; + dataSource.push(row); + }); + return ( +
+
+ + + + +
+
+
+ + {this.state.isExchangeModalVisible && ( + + )} + {this.state.isStakeModalVisible && ( + + )} + + ); + } +} + +LiquidityPools = BindToChainState(LiquidityPools, {show_loader: true}); +class LiquidityPoolsStoreWrapper extends React.Component { + render() { + return ; + } +} + +export default connect( + LiquidityPoolsStoreWrapper, + { + listenTo() { + return [PoolmartStore]; + }, + getProps() { + return { + liquidityPools: PoolmartStore.getState().liquidityPools, + liquidityPoolsLoading: PoolmartStore.getState() + .liquidityPoolsLoading, + lastPoolId: PoolmartStore.getState().lastPoolId + }; + } + } +); diff --git a/app/components/Layout/Header.jsx b/app/components/Layout/Header.jsx index ae7f16ce4a..86899e4112 100644 --- a/app/components/Layout/Header.jsx +++ b/app/components/Layout/Header.jsx @@ -476,13 +476,13 @@ class Header extends React.Component { let hamburger = this.state.dropdownActive ? ( ) : ( @@ -494,7 +494,6 @@ class Header extends React.Component { let renderingProps = { currentAccount: currentAccount, tradeUrl: tradeUrl, - createAccountLink: createAccountLink }; @@ -546,7 +545,7 @@ class Header extends React.Component { )} className="button outline small" > - > + {">"} diff --git a/app/components/Layout/MenuDataStructure.js b/app/components/Layout/MenuDataStructure.js index 23bcd53b29..4dfcbe558d 100644 --- a/app/components/Layout/MenuDataStructure.js +++ b/app/components/Layout/MenuDataStructure.js @@ -17,9 +17,7 @@ class MenuDataStructure { showAccountLinks, tradeUrl, enableDepositWithdraw, - passwordLogin, - currentAccount, createAccountLink } @@ -103,6 +101,7 @@ class MenuDataStructure { let submenu = [ allItems.account_voting, allItems.account_assets, + allItems.account_pools, allItems.account_signedmessages, allItems.account_stats, allItems.account_vesting, @@ -121,6 +120,7 @@ class MenuDataStructure { allItems.dashboard, allItems.market, allItems.lending, + allItems.poolmart, allItems.explorer, allItems.divider, allItems.transfer, @@ -218,6 +218,14 @@ class MenuDataStructure { inHeaderBehavior: MenuItemType.Always, inDropdownBehavior: MenuItemType.WhenNotInHeader }), + poolmart: state => ({ + includePattern: "/pools", + //target: state.poolmartUrl, + target: "/pools", + icon: {name: "poolmart", title: "icons.poolmart.title"}, + text: "header.poolmart", + inDropdownBehavior: MenuItemType.Always + }), lending: state => ({ includePattern: "/credit-offer", target: "/credit-offer", @@ -379,6 +387,15 @@ class MenuDataStructure { inHeaderBehavior: MenuItemType.Dynamic, inDropdownBehavior: MenuItemType.WhenAccount }), + account_pools: state => ({ + includePattern: "/pools", + excludePattern: "explorer", + target: `/account/${state.currentAccount}/pools`, + icon: "pools", + text: "account.liquidity_pools.title", + inHeaderBehavior: MenuItemType.Dynamic, + inDropdownBehavior: MenuItemType.WhenAccount + }), account_signedmessages: state => ({ includePattern: "/signedmessages", target: `/account/${state.currentAccount}/signedmessages`, diff --git a/app/components/Modal/CreatePoolModal.jsx b/app/components/Modal/CreatePoolModal.jsx new file mode 100644 index 0000000000..875fd66364 --- /dev/null +++ b/app/components/Modal/CreatePoolModal.jsx @@ -0,0 +1,771 @@ +import React from "react"; +import {ChainValidation} from "bitsharesjs"; +import PropTypes from "prop-types"; +import counterpart from "counterpart"; +import Translate from "react-translate-component"; +import QRCode from "qrcode.react"; +import {Aes} from "bitsharesjs"; + +import { + Form, + Modal, + Button, + Input, + Select, + Icon as AntIcon, + Alert +} from "bitshares-ui-style-guide"; +import AssetName from "../Utility/AssetName"; +import SearchInput from "../Utility/SearchInput"; +import PoolAction from "../../actions/PoolActions"; +import {debounce} from "lodash-es"; +import utils from "common/utils"; + +import { + lookupAssets, + assetFilter, + fetchIssuerName, + lookupAccountAssets +} from "./MarketPickerHelpers"; + +import ApplicationApi from "../../api/ApplicationApi" +import AssetActions from "actions/AssetActions"; + +class SearchListItem extends React.Component { + static propTypes = { + itemSelect: PropTypes.func, + itemLabel: PropTypes.string, + itemData: PropTypes.object, + marketPickerAsset: PropTypes.string + }; + + onClick(e) { + this.props.itemSelect(this.props.itemData); + } + + render() { + const {itemData, marketPickerAsset} = this.props; + const {onClose} = this.props; + + return ( +
  • + {this.props.itemLabel} +
  • + ); + } +} + +class CreatePoolModal extends React.Component { + constructor(props) { + super(props); + this.state = this._getInitialState(); + this.onCreatePool = this.onCreatePool.bind(this); + this.onCancel = this.onCancel.bind(this); + this.onClose = this.onClose.bind(this); + this.onPoolNameChange = this.onPoolNameChange.bind(this); + this.onAssetASearch = this.onAssetASearch.bind(this); + this.onAssetBSearch = this.onAssetBSearch.bind(this); + this.onSetAssetAArray = this.onSetAssetAArray.bind(this); + this.onSetAssetBArray = this.onSetAssetBArray.bind(this); + this.onSetPoolName = this.onSetPoolName.bind(this); + this.onSetAssetA = this.onSetAssetA.bind(this); + this.onSetAssetB = this.onSetAssetB.bind(this); + this.setState = this.setState.bind(this); + this.onSetTakerFee = this.onSetTakerFee.bind(this); + this.onSetUnstackFee = this.onSetUnstackFee.bind(this); + this.getAssetList = debounce(AssetActions.getAssetList.defer, 150); + this.getAssetsByIssuer = debounce(AssetActions.getAssetsByIssuer.defer, 150); + this._checkAndUpdateMarketList = this._checkAndUpdateMarketList.bind( + this + ); + } + + _getInitialState() { + return { + poolName: null, + filterAssetA: null, + filterAssetB: null, + lookupQuote: null, + keyString: null, + poolNameArray: [], + assetsAArray: [], + assetsBArray: [], + assetsA: null, + assetsB: null, + takerFee: 0, + unstakeFee: 0, + marketsList: [], + searchPoolName: false, + searchAssetA: false, + searchAssetB: false, + showAlertChangeAssetA: false, + showAlertChangeAssetB: false, + showAlertChangeTrankerFee: false, + showAlertChangeUnstakeFee: false, + showAlertInputPool: false, + showAlertInputAssetA: false, + showAlertInputAssetB: false, + showAlertInputTrankerFee: false, + showAlertInputUnstakeFee: false + }; + } + + initialState() { + return { + marketsList: [], + assetsAArray: [], + assetsBArray: [], + searchAssetA: false, + searchAssetB: false, + lookupQuote: null, + inputValue: "" + }; + } + + initAlertState(){ + return { + showAlertChangeAssetA: false, + showAlertChangeAssetB: false, + showAlertChangeTrankerFee: false, + showAlertChangeUnstakeFee: false, + showAlertInputAssetA: false, + showAlertInputAssetB: false, + showAlertInputTrankerFee: false, + showAlertInputUnstakeFee: false, + showAlertInputPool: false + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.marketPickerAsset !== this.props.marketPickerAsset) + console.log("componentWillReceiveProps is invoked."); + if (nextProps.searchList !== this.props.searchList) + if (this.state.searchAssetA) { + assetFilter( + { + searchAssets: this.props.searchList, + marketPickerAsset: this.props.marketPickerAsset + }, + { + inputValue: this.state.filterAssetA, + lookupQuote: this.state.lookupQuote + }, + this.setState, + this._checkAndUpdateMarketList + ); + } + else if (this.state.searchAssetB){ + assetFilter( + { + searchAssets: this.props.searchList, + marketPickerAsset: this.props.marketPickerAsset + }, + { + inputValue: this.state.filterAssetB, + lookupQuote: this.state.lookupQuote + }, + this.setState, + this._checkAndUpdateMarketList + ); + } + else if (this.state.searchPoolName){ + assetFilter( + { + searchAssets: this.props.searchList, + marketPickerAsset: this.props.marketPickerAsset + }, + { + inputValue: this.state.poolName, + lookupQuote: this.state.lookupQuote + }, + this.setState, + this._checkAndUpdateMarketList + ); + } + + } + + shouldComponentUpdate(np, ns) { + return ( + np.visible !== this.props.visible || + np.marketPickerAsset !== this.props.marketPickerAsset || + np.searchList !== this.props.searchList || + ns.marketsList !== this.state.marketsList || + !utils.are_equal_shallow(ns, this.state) + ); + } + + componentWillUnmount() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + + _checkAndUpdateMarketList(marketsList) { + clearInterval(this.intervalId); + console.log("CreatePooModal marketsList: ", marketsList); + this.intervalId = setInterval(() => { + let needFetchIssuer = 0; + for (let [, market] of marketsList) { + if (!market.issuer) { + market.issuer = fetchIssuerName(market.issuerId); + if (!market.issuer) needFetchIssuer++; + } + } + if (needFetchIssuer) return; + clearInterval(this.intervalId); + + + if (this.state.searchPoolName){ + this.setState({ + poolNameArray: marketsList, + activeSearch: false + }); + } + else if (this.state.searchAssetA){ + this.setState({ + assetsAArray: marketsList, + activeSearch: false + }); + } + else if (this.state.searchAssetB){ + this.setState({ + assetsBArray: marketsList, + activeSearch: false + }); + } + }, 300); + } + + onCancel() { + this.props.hideModal(); + this.setState(this._getInitialState()); + this.onClose(); + } + + onClose() { + this.setState(this._getInitialState()); + } + + onCreatePool() { + if (!this.state.poolName){ + this.setState( + { + showAlertInputPool: true + } + ); + return; + } + else if (!this.state.assetsA) + { + this.setState( + { + showAlertInputAssetA: true + } + ); + return; + } else if (!this.state.assetsB){ + this.setState( + { + showAlertInputAssetB: true + } + ); + return; + } else if (!this.state.takerFee){ + this.setState( + { + showAlertInputTrankerFee: true + } + ); + return; + } else if (!this.state.unstakeFee){ + this.setState( + { + showAlertInputUnstakeFee: true + } + ); + return; + } + + if (this.state.assetsA && this.state.assetsB && this.state.takerFee && this.state.unstakeFee){ + let assetA_id = Number(this.state.assetsA[1]["id"].replace("1.3.", "")); + let assetB_id = Number(this.state.assetsB[1]["id"].replace("1.3.", "")); + if (assetA_id > assetB_id) + { +console.log(this.state.assetsB[1]["quote"], this.state.assetsA[1]["quote"]); +ApplicationApi.liquidityPoolCreate(this.props.account, this.state.assetsB[1]["quote"], this.state.assetsA[1]["quote"], this.state.poolName, this.state.takerFee * 100.0, +this.state.unstakeFee * 100.0);//.then(() => { +this.props.hideModal(); +}else{ +console.log(this.state.assetsA[1]["quote"], this.state.assetsB[1]["quote"]); + ApplicationApi.liquidityPoolCreate(this.props.account, this.state.assetsA[1]["quote"], this.state.assetsB[1]["quote"], this.state.poolName, this.state.takerFee * 100.0, + this.state.unstakeFee * 100.0);//.then(() => { + this.props.hideModal(); + } +} + this.onCancel(); + } + + onPoolNameChange(getBackedAssets, e) { + this.setState(this.initAlertState()); + let toFind = e.target.value.trim().toUpperCase(); + let isValidName = !ChainValidation.is_valid_symbol_error(toFind, true); + + if (!isValidName) { + this.setState({ + poolName: toFind, + poolNameArray: [], + searchPoolName: false + }); + return; + } else { + this.setState({ + poolName: toFind, + marketsList: [], + searchPoolName: true + }); + } + + if (this.state.poolName !== toFind) { + this.timer && clearTimeout(this.timer); + } + + let account_name = this.props.account.get("name"); + let assets = [...this.props.account.get("assets")]; + let keys = [...this.props.account.keys()]; + + this.timer = setTimeout(() => { + lookupAccountAssets( + account_name, + toFind, + assets[0], + getBackedAssets, + this.getAssetsByIssuer, + this.setState + ); + }, 1500); + } + + onAssetASearch(getBackedAssets, e) { + this.setState(this.initAlertState()); + let toFind = e.target.value.trim().toUpperCase(); + let isValidName = !ChainValidation.is_valid_symbol_error(toFind, true); + + if (!isValidName) { + /* Don't lookup invalid asset names */ + this.setState({ + filterAssetA: toFind, + activeSearch: false, + marketsList: [] + }, + () => { + this.onSetAssetAArray(); + }); + + return; + } else { + this.setState({ + filterAssetA: toFind, + activeSearch: true, + marketsList: [], + searchAssetA: true, + searchAssetB: false + }); + } + + + if (this.state.filterAssetA !== toFind) { + this.timer && clearTimeout(this.timer); + } + + this.timer = setTimeout(() => { + lookupAssets( + toFind, + getBackedAssets, + this.getAssetList, + this.setState + ); + }, 1500); + } + + onAssetBSearch(getBackedAssets, e) { + this.setState(this.initAlertState()); + + let toFind = e.target.value.trim().toUpperCase(); + let isValidName = !ChainValidation.is_valid_symbol_error(toFind, true); + + if (!isValidName) { + /* Don't lookup invalid asset names */ + this.setState({ + filterAssetB: toFind, + activeSearch: false, + marketsList: [] + }, + () => { + this.onSetAssetAArray(); + }); + + return; + } else { + this.setState({ + filterAssetB: toFind, + activeSearch: true, + marketsList: [], + searchAssetA: false, + searchAssetB: true + }); + } + + + if (this.state.filterAssetB !== toFind) { + this.timer && clearTimeout(this.timer); + } + + this.timer = setTimeout(() => { + lookupAssets( + toFind, + getBackedAssets, + this.getAssetList, + this.setState + ); + }, 1500); + + } + + onSetAssetAArray() { + } + + onSetAssetBArray() { + } + + onSetPoolName(name) { + this.setState({poolName: name, searchPoolName: false}); + } + + onSetAssetA(e) { + this.setState(this.initialState()); + console.log("onSetAssetA ", e); + console.log("takerFee: ", this.state.takerFee); + this.setState({ + assetsA: e, + activeSearch: false, + filterAssetA: e[0] + }); + } + + onSetAssetB(e) { + this.setState(this.initialState()); + console.log("onSetAssetB ", e); + this.setState({ + assetsB: e, + activeSearch: false, + filterAssetB: e[0] + }); + } + + onSetTakerFee(e){ + this.setState(this.initAlertState()); + // this.setState({ + // takerFee: parseFloat(e.target.value.trim()) + // }); + this.setState({ + takerFee: e.target.value + }); + } + + onFormatTakerFee(e){ + const takerFee = this.state.takerFee + this.setState({ + takerFee: parseFloat(takerFee).toFixed(2) + }) + } + + + + onSetUnstackFee(e){ + this.setState(this.initAlertState()); + this.setState({ + unstakeFee: e.target.value + }); + } + + onFormatUnstackFee(e){ + const unstakeFee = this.state.unstakeFee + this.setState({ + unstakeFee: parseFloat(unstakeFee).toFixed(2) + }) + } + + + render() { + const footer = []; + + footer.push( + + ); + + const empty = ( + +                + + ); + + return ( + +
    +
    + +
    +
    +
    +
    + + } + /> + {this.state.showAlertInputPool ? ():null} + +
    + {this.state.searchPoolName && ( +
    + {this.state.poolNameArray.map( + (poolName, index) => { + return ( + + ); + } + )} +
    + )} +
    +
    +
    + + } + /> + {this.state.showAlertChangeAssetA ? ():null} + {this.state.showAlertInputAssetA ? ():null} + +
    + {this.state.searchAssetA && ( +
    + {this.state.assetsAArray.map( + (asset, index) => { + return ( + + ); + } + )} +
    + )} +
    +
    +
    + + } + /> + {this.state.showAlertChangeAssetB ? ():null} + {this.state.showAlertInputAssetB ? ():null} + + +
    + {this.state.searchAssetB && ( +
    + {this.state.assetsBArray.map( + (asset, index) => { + return ( + + ); + } + )} +
    + )} + +
    +
    + + {this.state.showAlertChangeTrankerFee ? ():null} + {this.state.showAlertInputTrankerFee ? ():null} +
    + +
    + + {this.state.showAlertChangeUnstakeFee ? ():null} + {this.state.showAlertInputUnstakeFee ? ():null} +
    + +
    + +
    + + ); + } +} + +CreatePoolModal.propTypes = { + modalId: PropTypes.string.isRequired, + keyValue: PropTypes.string +}; +CreatePoolModal.defaultProps = { + modalId: "qr_code_password_modal" +}; +export default CreatePoolModal; diff --git a/app/components/Modal/DeletePoolModal.jsx b/app/components/Modal/DeletePoolModal.jsx new file mode 100644 index 0000000000..68a1de854f --- /dev/null +++ b/app/components/Modal/DeletePoolModal.jsx @@ -0,0 +1,78 @@ +import React from "react"; +import Translate from "react-translate-component"; +import Immutable from "immutable"; +import big from "bignumber.js"; +import counterpart from "counterpart"; +import {connect} from "alt-react"; +import {Form, Modal, Button, Row, Col, Tabs} from "bitshares-ui-style-guide"; +import ApplicationApi from "api/ApplicationApi"; +import AccountStore from "stores/AccountStore"; +import AmountSelector from "../Utility/AmountSelectorStyleGuide"; +import Icon from "../Icon/Icon"; +import AccountBalance from "../Account/AccountBalance"; +import BindToChainState from "../Utility/BindToChainState"; +import ChainTypes from "../Utility/ChainTypes"; + +class DeletePoolModal extends React.Component { + static propTypes = { + pool: ChainTypes.ChainLiquidityPool.isRequired + }; + constructor(props) { + super(props); + this.state = { + isModalVisible: props.isModalVisible, + }; + this.hideModal = this.hideModal.bind(this); + this.onSubmit = this.onSubmit.bind(this); + } + + componentWillReceiveProps(newProps) { + console.log("DeletePoolModal: "); + } + + hideModal() { + this.props.onHideModal(); + } + + onSubmit() { + this.props.onDeletePool(this.props.pool); + } + + render() { + return ( + + {counterpart.translate("poolmart.liquidity_pools.delete_pool")} + , + + ]} + > +
    + +
    + {counterpart.translate( + "poolmart.liquidity_pools.confirm_delete_pool" + )} + + + + + ); + } +} + +export default DeletePoolModal; diff --git a/app/components/Modal/MarketPickerHelpers.js b/app/components/Modal/MarketPickerHelpers.js new file mode 100644 index 0000000000..d2ffa651e7 --- /dev/null +++ b/app/components/Modal/MarketPickerHelpers.js @@ -0,0 +1,130 @@ +import {hasGatewayPrefix} from "common/gatewayUtils"; +import {ChainStore} from "bitsharesjs"; + +function lookupAssets(value, gatewayAssets = false, getAssetList, setState) { + if (!value && value !== "") return; + + let quote = value.toUpperCase(); + + if (quote.startsWith("BIT") && quote.length >= 6) { + quote = value.substr(3, quote.length - 1); + } + + getAssetList(quote, 10, gatewayAssets); + + setState({lookupQuote: quote}); +} + +function lookupAccountAssets(value, toFind, start, gatewayAssets = false, getAssetList, setState) { + if (!value && value !== "") return; + + getAssetList(value, 10, start, gatewayAssets); + + setState({lookupQuote: toFind}); +} + + + +function assetFilter( + {searchAssets, marketPickerAsset/*, baseAsset, quoteAsset*/}, + {inputValue, lookupQuote}, + setState, + checkAndUpdateMarketList +) { + setState({activeSearch: true}); + + let assetCount = 0; + let allMarkets = []; + + if (searchAssets.size && !!inputValue && inputValue.length > 2) { + searchAssets + .filter(a => { + try { + if (a.options.description) { + let description = JSON.parse(a.options.description); + if ("visible" in description) { + if (!description.visible) return false; + } + } + } catch (e) {} + + return a.symbol.indexOf(lookupQuote) !== -1; + }) + .forEach(asset => { + if (assetCount > 100) return; + assetCount++; + + let issuerName = fetchIssuerName(asset.issuer); + let marketID = asset.symbol; + allMarkets.push([ + marketID, + { + id: asset.id, + quote: asset.symbol, + base: '', + issuerId: asset.issuer, + issuer: issuerName + } + ]); + }); + } + + const marketsList = sortMarketsList(allMarkets, inputValue); + checkAndUpdateMarketList(marketsList); +} + +function getMarketSortComponents(market) { + const weight = {}; + const quote = market.quote; + if (quote.indexOf(".") !== -1) { + const [gateway, asset] = quote.split("."); + weight.gateway = gateway; + weight.asset = asset; + } else { + weight.asset = quote; + } + if (market.issuerId === "1.2.0") weight.isCommittee = true; + return weight; +} + +function sortMarketsList(allMarkets, inputValue) { + if (inputValue.startsWith("BIT") && inputValue.length >= 6) { + inputValue = inputValue.substr(3, inputValue.length - 1); + } + return allMarkets.sort(([, marketA], [, marketB]) => { + const weightA = getMarketSortComponents(marketA); + const weightB = getMarketSortComponents(marketB); + + if (weightA.asset !== weightB.asset) { + if (weightA.asset === inputValue) return -1; + if (weightB.asset === inputValue) return 1; + if (weightA.asset > weightB.asset) return -1; + if (weightA.asset < weightB.asset) return 1; + } + + if (weightA.isCommittee ^ weightB.isCommittee) { + if (weightA.isCommittee) return -1; + if (weightB.isCommittee) return 1; + } + + const aIsKnownGateway = hasGatewayPrefix(marketA.quote); + const bIsKnownGateway = hasGatewayPrefix(marketB.quote); + if (aIsKnownGateway && !bIsKnownGateway) return -1; + if (bIsKnownGateway && !aIsKnownGateway) return 1; + + if (weightA.gateway > weightB.gateway) return 1; + if (weightA.gateway < weightB.gateway) return -1; + return 0; + }); +} + +function fetchIssuerName(issuerId) { + let issuer = ChainStore.getObject(issuerId, false, false); + if (!issuer) { + return; + } else { + return issuer.get("name"); + } +} + +export {lookupAssets, assetFilter, fetchIssuerName, lookupAccountAssets}; diff --git a/app/components/Modal/PoolExchangeModal.jsx b/app/components/Modal/PoolExchangeModal.jsx new file mode 100644 index 0000000000..368ed0d744 --- /dev/null +++ b/app/components/Modal/PoolExchangeModal.jsx @@ -0,0 +1,569 @@ +import React from "react"; +import Translate from "react-translate-component"; +import Immutable from "immutable"; +import big from "bignumber.js"; +import counterpart from "counterpart"; +import {connect} from "alt-react"; +import {Form, Modal, Button, Input, Row, Col, Alert} from "bitshares-ui-style-guide"; +import ApplicationApi from "api/ApplicationApi"; +import AccountStore from "stores/AccountStore"; +import AmountSelector from "../Utility/AmountSelectorStyleGuide"; +import Icon from "../Icon/Icon"; +import AccountBalance from "../Account/AccountBalance"; +import BindToChainState from "../Utility/BindToChainState"; +import ChainTypes from "../Utility/ChainTypes"; +import AssetName from "../Utility/AssetName"; +import {ChainStore} from "bitsharesjs"; + +class PoolExchangeModal extends React.Component { + static propTypes = { + pool: ChainTypes.ChainLiquidityPool.isRequired + }; + constructor(props) { + super(props); + this.state = { + isModalVisible: props.isModalVisible, + amountToSellTag: "a", + minToReceiveTag: "b", + amountToSell: null, + minToReceive: null, + fee: null, + err: null + }; + this.hideModal = this.hideModal.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.switchAsset = this.switchAsset.bind(this); + this.getPairs = this.getPairs.bind(this); + this.onChangeAmountToSell = this.onChangeAmountToSell.bind(this); + this.onChangeMinToReceive = this.onChangeMinToReceive.bind(this); + } + + componentWillReceiveProps(newProps) { + if (this.props.isModalVisible !== newProps.isModalVisible) { + this.setState({ + isModalVisible: newProps.isModalVisible + }); + } + } + + hideModal() { + this.props.onHideModal(); + } + + onSubmit = () => { + const {pool, account} = this.props; + const { + amountToSellTag, + minToReceiveTag, + amountToSell, + minToReceive + } = this.state; + if (!amountToSell || !minToReceive) { + this.setState({ + err: counterpart.translate("exchange.no_data") + }); + return; + } + this.setState({ + err: null + }); + const amountToSellPrecision = new big(10).toPower( + new big(pool.getIn([`asset_${amountToSellTag}`, "precision"])) + ); + const minToReceivePrecision = new big(10).toPower( + new big(pool.getIn([`asset_${minToReceiveTag}`, "precision"])) + ); + + ApplicationApi.liquidityPoolExchange( + account, + pool.get("id"), + pool.getIn([`asset_${amountToSellTag}`, "symbol"]), + parseFloat(amountToSell) * amountToSellPrecision, + pool.getIn([`asset_${minToReceiveTag}`, "symbol"]), + parseFloat(minToReceive) * minToReceivePrecision + + ) + .then(() => { + console.log("exchange:"); + this.hideModal(); + }) + .catch(e => { + console.error("exchange:", e); + }); + } + + switchAsset() { + const tmp1 = this.state.amountToSellTag; + const tmp2 = this.state.minToReceiveTag; + this.setState({ + amountToSellTag: tmp2, + minToReceiveTag: tmp1, + amountToSell: null, + minToReceive: null, + fee: null + }); + + const {pool, account} = this.props; + const {amountToSellTag, minToReceiveTag} = this.state; + const accountObj = ChainStore.getAccount(account); + + let asset_a = pool.get(`asset_${amountToSellTag}`); + let asset_b = pool.get(`asset_${minToReceiveTag}`); + let precision_a = asset_a.get('precision'); + let precision_b = asset_b.get('precision'); + + const balances_a = accountObj.getIn(["balances", asset_a.get("id")]); + const balances_b = accountObj.getIn(["balances", asset_b.get("id")]); + + let balance_a = new big(0); + let balance_b = new big(0); + + if (balances_a){ + const balObj_A = ChainStore.getObject(balances_a); + balance_a = new big(balObj_A.get("balance")).dividedBy(new big(10).toPower(precision_a)); + } + + if (balances_b){ + const balObj_B = ChainStore.getObject(balances_b); + balance_b = new big(balObj_B.get("balance")).dividedBy(new big(10).toPower(precision_b)); + } + + if (pool === null || pool === undefined) return null; + return { + amountToSell: { + id: pool.getIn([`asset_${amountToSellTag}`, "id"]), + balance: balance_a, + name: pool.getIn([`asset_${amountToSellTag}`, "symbol"]), + precision: precision_a + }, + minToReceive: { + id: pool.getIn([`asset_${minToReceiveTag}`, "id"]), + balance: balance_b, + name: pool.getIn([`asset_${minToReceiveTag}`, "symbol"]), + precision: precision_b + } + }; + + + + } + + getPairs() { + + const {pool, account} = this.props; + const {amountToSellTag, minToReceiveTag} = this.state; + const accountObj = ChainStore.getAccount(account); + + let asset_a = pool.get(`asset_${amountToSellTag}`); + let asset_b = pool.get(`asset_${minToReceiveTag}`); + let precision_a = asset_a.get('precision'); + let precision_b = asset_b.get('precision'); + + const balances_a = accountObj.getIn(["balances", asset_a.get("id")]); + const balances_b = accountObj.getIn(["balances", asset_b.get("id")]); + + let balance_a = new big(0); + let balance_b = new big(0); + + if (balances_a){ + const balObj_A = ChainStore.getObject(balances_a); + balance_a = new big(balObj_A.get("balance")).dividedBy(new big(10).toPower(precision_a)); + } + + if (balances_b){ + const balObj_B = ChainStore.getObject(balances_b); + balance_b = new big(balObj_B.get("balance")).dividedBy(new big(10).toPower(precision_b)); + } + + if (pool === null || pool === undefined) return null; + return { + amountToSell: { + id: pool.getIn([`asset_${amountToSellTag}`, "id"]), + balance: balance_a, + name: pool.getIn([`asset_${amountToSellTag}`, "symbol"]), + precision: precision_a + }, + minToReceive: { + id: pool.getIn([`asset_${minToReceiveTag}`, "id"]), + balance: balance_b, + name: pool.getIn([`asset_${minToReceiveTag}`, "symbol"]), + precision: precision_b + } + }; + } + + onChangeAmountToSell(e) { + const tmp1 = this.state.amountToSellTag; + const tmp2 = this.state.minToReceiveTag; + this.setState({ + amountToSellTag: tmp1, + minToReceiveTag: tmp2, + amountToSell: null, + minToReceive: null, + fee: null + }); + const {pool, account} = this.props; + const {amountToSellTag, minToReceiveTag} = this.state; + const accountObj = ChainStore.getAccount(account); + + const asset_a = pool.get(`asset_${tmp1}`); + const asset_b = pool.get(`asset_${tmp2}`); + + let poolamounta = Number(pool.get(`balance_${tmp1}`)); + let poolamountap = Number(new big(10).toPower(pool.get(`asset_${tmp1}`).get("precision"))); + let poolamountb = Number(pool.get(`balance_${tmp2}`)); + let poolamountbp = Number(new big(10).toPower(pool.get(`asset_${tmp2}`).get("precision"))); + + const maker_market_fee_percenta = asset_a.getIn(["options", "market_fee_percent"]); + const maker_market_fee_percentb = asset_b.getIn(["options", "market_fee_percent"]); + + let maker_fee_a = (Number(e.amount) * Number(poolamountap)) * Number(maker_market_fee_percenta) / 10000; + let maker_fee_b = (Number(e.amount) * Number(poolamountbp)) * Number(maker_market_fee_percentb) / 10000; + + const assetaflags = asset_a.getIn(["options", "flags"]); + const assetbflags = asset_b.getIn(["options", "flags"]); + + const max_market_feea = asset_a.getIn(["options", "max_market_fee"]); // / Number(new big(10).toPower(pool.get(`asset_${tmp1}`).get("precision"))); + const max_market_feeb = asset_b.getIn(["options", "max_market_fee"]); // / Number(new big(10).toPower(pool.get(`asset_${tmp2}`).get("precision"))); + + const taker_fee_percenta = asset_a.getIn(["options", "extensions", "taker_fee_percent"]); + const taker_fee_percentb = asset_b.getIn(["options", "extensions", "taker_fee_percent"]); + + function flagsa() { + if (assetaflags % 2 == 0) { return 0;} + if (maker_market_fee_percenta === 0) {return 0;} + if (maker_market_fee_percenta > 0) { + return Math.min(Number(max_market_feea), Math.ceil((Number(e.amount) * Number(poolamountap)) * (Number(maker_market_fee_percenta) / 10000))) + } + } + function flagsb() { + if (assetbflags % 2 == 0) { return 0;} + if (maker_market_fee_percentb === 0) {return 0;} + if (maker_market_fee_percentb > 0) { + return Math.min(Number(max_market_feeb), Math.ceil((Number(e.amount) * Number(poolamountbp)) * (Number(maker_market_fee_percentb) / 10000))) + } + } + + + function taker_market_fee_percenta() { + if (assetaflags % 2 == 0) { return 0;} + if (typeof taker_fee_percenta == 'undefined' && maker_market_fee_percenta > 0) { + return Number(maker_market_fee_percenta) / 10000;} + if (typeof taker_fee_percenta == 'undefined' && maker_market_fee_percenta === 0) { return 0; + } + else { + return Number(taker_fee_percenta) / 10000; + } + } + + function taker_market_fee_percentb() { + if (assetbflags % 2 == 0) { return 0;} + if (typeof taker_fee_percentb == 'undefined' && maker_market_fee_percentb > 0) { + return Number(maker_market_fee_percentb) / 10000;} + if (typeof taker_fee_percentb == 'undefined' && maker_market_fee_percentb === 0) { return 0; + } + else { + return Number(taker_fee_percentb) / 10000; + } + } + + let tmp_delta_a = Number(poolamounta) - Math.ceil( Number(poolamounta) * Number(poolamountb) / ( Number(poolamountb) + ( (Number(e.amount) * Number(poolamountbp)) - Number(flagsb())))) + let tmp_delta_b = Number(poolamountb) - Math.ceil( Number(poolamountb) * Number(poolamounta) / ( Number(poolamounta) + ( (Number(e.amount) * Number(poolamountap)) - Number(flagsa())))) + + let tmp_a = (Number(tmp_delta_a) * Number(pool.get("taker_fee_percent")) / 10000); + let tmp_b = (Number(tmp_delta_b) * Number(pool.get("taker_fee_percent")) / 10000); + + let taker_market_fee_percent_a = (Number(taker_market_fee_percenta())); + let taker_market_fee_percent_b = (Number(taker_market_fee_percentb())); + + let tmp_delta_b_floor = Math.floor(Number(tmp_delta_b)) + let tmp_b_ceil = Math.ceil(Number(tmp_b)) + let max_mar = Number(max_market_feeb) + let tmp_b_taker_ceil = Math.ceil(tmp_b_ceil * Number(taker_market_fee_percent_b)) + let total = tmp_delta_b_floor - tmp_b_ceil - tmp_b_taker_ceil + + this.setState({ + amountToSell: Number(e.amount), + minToReceive: (Number(tmp_delta_b) - Math.floor(Number(tmp_b)) - Math.ceil(Math.min(Number(max_market_feeb),Math.ceil(Math.ceil(Number(tmp_delta_b) * Number(taker_market_fee_percent_b)))))) / Number(poolamountbp) + }); + } + + onChangeMinToReceive(e) { + const tmp1 = this.state.amountToSellTag; + const tmp2 = this.state.minToReceiveTag; + this.setState({ + minToReceive: null, + fee: null + }); + const {pool, account} = this.props; + const {amountToSellTag, minToReceiveTag} = this.state; + const accountObj = ChainStore.getAccount(account); + + const asset_a = pool.get(`asset_${tmp1}`); + const asset_b = pool.get(`asset_${tmp2}`); + + let poolamounta = Number(pool.get(`balance_${tmp1}`)); + let poolamountap = Number(new big(10).toPower(pool.get(`asset_${tmp1}`).get("precision"))); + let poolamountb = Number(pool.get(`balance_${tmp2}`)); + let poolamountbp = Number(new big(10).toPower(pool.get(`asset_${tmp2}`).get("precision"))); + + const maker_market_fee_percenta = asset_a.getIn(["options", "market_fee_percent"]); + const maker_market_fee_percentb = asset_b.getIn(["options", "market_fee_percent"]); + + let maker_fee_a = (Number(e.amount) * Number(poolamountap)) * Number(maker_market_fee_percenta) / 10000; + let maker_fee_b = (Number(e.amount) * Number(poolamountbp)) * Number(maker_market_fee_percentb) / 10000; + + const assetaflags = asset_a.getIn(["options", "flags"]); + const assetbflags = asset_b.getIn(["options", "flags"]); + + const max_market_feea = asset_a.getIn(["options", "max_market_fee"]); // / Number(new big(10).toPower(pool.get(`asset_${tmp1}`).get("precision"))); + const max_market_feeb = asset_b.getIn(["options", "max_market_fee"]); // / Number(new big(10).toPower(pool.get(`asset_${tmp2}`).get("precision"))); + + const taker_fee_percenta = asset_a.getIn(["options", "extensions", "taker_fee_percent"]); + const taker_fee_percentb = asset_b.getIn(["options", "extensions", "taker_fee_percent"]); + + function flagsa() { + if (assetaflags % 2 == 0) { return 0;} + if (maker_market_fee_percenta === 0) {return 0;} + if (maker_market_fee_percenta > 0) { + return Math.min(Number(max_market_feea), Math.ceil((Number(e.amount) * Number(poolamountap)) * (Number(maker_market_fee_percenta) / 10000))) + } + } + function flagsb() { + if (assetbflags % 2 == 0) { return 0;} + if (maker_market_fee_percentb === 0) {return 0;} + if (maker_market_fee_percentb > 0) { + return Math.min(Number(max_market_feeb), Math.ceil((Number(e.amount) * Number(poolamountbp)) * (Number(maker_market_fee_percentb) / 10000))) + } + } + + + function taker_market_fee_percenta() { + if (assetaflags % 2 == 0) { return 0;} + if (typeof taker_fee_percenta == 'undefined' && maker_market_fee_percenta > 0) { + return Number(maker_market_fee_percenta) / 10000;} + if (typeof taker_fee_percenta == 'undefined' && maker_market_fee_percenta === 0) { return 0; + } + else { + return Number(taker_fee_percenta) / 10000; + } + } + + function taker_market_fee_percentb() { + if (assetbflags % 2 == 0) { return 0;} + if (typeof taker_fee_percentb == 'undefined' && maker_market_fee_percentb > 0) { + return Number(maker_market_fee_percentb) / 10000;} + if (typeof taker_fee_percentb == 'undefined' && maker_market_fee_percentb === 0) { return 0; + } + else { + return Number(taker_fee_percentb) / 10000; + } + } + + let tmp_delta_a = Number(poolamounta) - Math.ceil( Number(poolamounta) * Number(poolamountb) / ( Number(poolamountb) + ( (Number(e.amount) * Number(poolamountbp)) - Number(flagsb())))) + let tmp_delta_b = Number(poolamountb) - Math.ceil( Number(poolamountb) * Number(poolamounta) / ( Number(poolamounta) + ( (Number(e.amount) * Number(poolamountap)) - Number(flagsa())))) + + let tmp_a = (Number(tmp_delta_a) * Number(pool.get("taker_fee_percent")) / 10000); + let tmp_b = (Number(tmp_delta_b) * Number(pool.get("taker_fee_percent")) / 10000); + + let taker_market_fee_percent_a = (Number(taker_market_fee_percenta())); + let taker_market_fee_percent_b = (Number(taker_market_fee_percentb())); + + let tmp_delta_b_floor = Math.floor(Number(tmp_delta_b)) + let tmp_b_ceil = Math.ceil(Number(tmp_b)) + let max_mar = Number(max_market_feeb) + let tmp_b_taker_ceil = Math.ceil(tmp_b_ceil * Number(taker_market_fee_percent_b)) + + this.setState({ + minToReceive: Number(e.amount), + }); + } + render() { + const {pool, account} = this.props; + if (!pool || pool.size === 0) return null; + const {amountToSell, minToReceive} = this.state; + const pairs = this.getPairs(); + return ( + + {counterpart.translate("wallet.submit")} + , + + ]} + > + {pool.get("virtual_value") > 0 && ( +
    + {pairs !== null && ( + +
    +

    + {counterpart.translate( + "poolmart.liquidity_pools.amount_to_sell" + )} +

    + + {account && ( + + )} + + + + + +

    + {counterpart.translate( + "poolmart.liquidity_pools.min_to_receive" + )} +

    + + {account && ( + + )} + + {this.state.fee && ( + + + {counterpart.translate( + "poolmart.liquidity_pools.taker_fee_percent" + )} + :{" "} + + {`${this.state.fee} `} + + + )} + {this.state.err && ( + + + + )} + + )} + + )} + {pool.get("virtual_value") == 0 && ( +
    + +
    + {counterpart.translate( + "poolmart.liquidity_pools.need_stake_first" + )} + + + + )} + + ); + } +} + +export default connect( + BindToChainState(PoolExchangeModal), + { + listenTo() { + return [AccountStore]; + }, + getProps(props) { + return { + account: AccountStore.getState().currentAccount + }; + } + } +); diff --git a/app/components/Modal/PoolStakeModal.jsx b/app/components/Modal/PoolStakeModal.jsx new file mode 100644 index 0000000000..040fc3458b --- /dev/null +++ b/app/components/Modal/PoolStakeModal.jsx @@ -0,0 +1,632 @@ +import React from "react"; +import Translate from "react-translate-component"; +import Immutable from "immutable"; +import big from "bignumber.js"; +import counterpart from "counterpart"; +import {connect} from "alt-react"; +import {Form, Modal, Button, Input, Row, Col, Tabs} from "bitshares-ui-style-guide"; +import ApplicationApi from "api/ApplicationApi"; +import AccountStore from "stores/AccountStore"; +import AmountSelector from "../Utility/AmountSelectorStyleGuide"; +import Icon from "../Icon/Icon"; +import AccountBalance from "../Account/AccountBalance"; +import {ChainStore} from "bitsharesjs"; +import BindToChainState from "../Utility/BindToChainState"; +import ChainTypes from "../Utility/ChainTypes"; + +class PoolStakeModal extends React.Component { + static propTypes = { + pool: ChainTypes.ChainLiquidityPool.isRequired + }; + constructor(props) { + super(props); + this.state = { + isModalVisible: props.isModalVisible, + assetAAmount: null, + assetBAmount: null, + shareAssetAmount: null, + assetAErr: { + msg: null, + status: null + }, + assetBErr: { + msg: null, + status: null + }, + shareAssetErr: { + msg: null, + status: null + }, + currentTab: "stake" + }; + this.hideModal = this.hideModal.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.onTabChange = this.onTabChange.bind(this); + this.resetErr = this.resetErr.bind(this); + this.onChangeAssetAAmount = this.onChangeAssetAAmount.bind(this); + this.onChangeAssetBAmount = this.onChangeAssetBAmount.bind(this); + this.onChangeShareAssetAmount = this.onChangeShareAssetAmount.bind( + this + ); + } + + componentWillReceiveProps(newProps) { + if (this.props.isModalVisible !== newProps.isModalVisible) { + this.setState({ + isModalVisible: newProps.isModalVisible + }); + } + } + + hideModal() { + this.props.onHideModal(); + } + + onSubmit() { + + const { + assetAAmount, + assetBAmount, + shareAssetAmount, + assetAErr, + assetBErr, + shareAssetErr, + currentTab + } = this.state; + + + const {pool, account} = this.props; + + + const assetAPrecision = new big(10).toPower( + new big(pool.getIn(['asset_a', 'precision'])) + ); + + const assetBPrecision = new big(10).toPower( + new big(pool.getIn(['asset_b', 'precision'])) + ); + + const sharedAssetPrecision = new big(10).toPower(pool.getIn(['share_asset','precision'])); + + if (currentTab === "stake") { + + ApplicationApi.liquidityPoolDeposit( + account, + pool.get("id"), + pool.getIn(['asset_a', "symbol"]), + pool.getIn(['asset_b', "symbol"]), + Math.floor(Number(assetAAmount) * Number(assetAPrecision)), + Math.floor(Number(assetBAmount) * Number(assetBPrecision)) + ) + .then(res => { + console.log("exchange:", res); + this.hideModal(); + }) + .catch(e => { + console.error("exchange:", e); + }); + + } else if (currentTab === "unstake") { + ApplicationApi.liquidityPoolWithdraw( + account, + pool.get("id"), + pool.getIn(['share_asset', "symbol"]), + Math.floor(Number(shareAssetAmount) * Number(sharedAssetPrecision)), + pool.getIn(['asset_a', "symbol"]), + pool.getIn(['asset_b', "symbol"]), + Math.floor(Number(assetAAmount) * Number(assetAPrecision)), + Math.floor(Number(assetBAmount) * Number(assetBPrecision)) + + ) + .then(res => { + console.log("exchange:", res); + this.hideModal(); + }) + .catch(e => { + console.error("exchange:", e); + }); + } + } + + onTabChange(tabVal) { + this.setState({ + currentTab: tabVal + }); + this.resetErr(); + } + + resetErr() { + this.setState({ + assetAAmount: null, + assetBAmount: null, + shareAssetAmount: null, + assetAErr: { + msg: null, + status: null + }, + assetBErr: { + msg: null, + status: null + }, + shareAssetErr: { + msg: null, + status: null + } + }); + } + + getShareAssetCurrentSupply() { + const {pool, account} = this.props; + + const shareAsset = pool.get('share_asset'); + const precision = shareAsset.get("precision"); + + const accountObj = ChainStore.getAccount(account); + + const balances = accountObj.getIn(["balances", shareAsset.get("id")]); + + if (balances){ + const balObj = ChainStore.getObject(balances); + const balance = balObj.get("balance"); + return new big(balance).dividedBy(new big(10).toPower(precision)); + console.log("banace:",big(balance).dividedBy(new big(10).toPower(precision))); + } + + return new big(0); + console.log(big(0)); + } + + getBalanceA(){ + const {pool, account} = this.props; + + const assetA = pool.get('asset_a'); + + const precision = assetA.get("precision"); + + const accountObj = ChainStore.getAccount(account); + + const balances = accountObj.getIn(["balances", assetA.get("id")]); + + if (balances){ + const balObj = ChainStore.getObject(balances); + return new big(balObj.get("balance")).dividedBy(new big(10).toPower(precision)); + } + + return new big(0); + } + + getBalanceB(){ + const {pool, account} = this.props; + + const assetB = pool.get('asset_b'); + + const precision = assetB.get("precision"); + + const accountObj = ChainStore.getAccount(account); + + const balances = accountObj.getIn(["balances", assetB.get("id")]); + + if (balances){ + const balObj = ChainStore.getObject(balances); + return new big(balObj.get("balance")).dividedBy(new big(10).toPower(precision)); + } + + return new big(0); + } + + onChangeAssetAAmount(v) { + const {currentTab} = this.state; + const {pool, account} = this.props; + const currentSupply = this.getShareAssetCurrentSupply(); + if (currentSupply !== undefined) { + this.setState({ + assetAAmount: v.amount + }); + } + if (currentTab === "stake") { + const {assetBAmount} = this.state; + + const assetA = pool.get('asset_a'); + + const precisionA = assetA.get("precision"); + + const assetB = pool.get('asset_b'); + + const precisionB = assetB.get("precision"); + + const shareAssetPP = pool.get('share_asset'); + const precisionPP = shareAssetPP.get("precision"); + let poolamounta = pool.get('balance_a'); + let poolamountap = new big(10).toPower(pool.get('asset_a').get("precision")); + let poolamountb = pool.get('balance_b'); + let poolamountbp = new big(10).toPower(pool.get('asset_b').get("precision")); + + + let poolsupply = Number(pool.getIn(["dynamic_share_asset", "current_supply"])) / Number(new big(10).toPower(precisionPP)); + + + if (assetBAmount && assetBAmount > 0){ + this.setState({ + shareAssetAmount: Math.min( (((Number(poolsupply) * Number(v.amount) * Number(new big(10).toPower(precisionA)) ) / (Number(poolamounta) / Number(new big(10).toPower(precisionA)))) / Number(new big(10).toPower(precisionPP))), ((( Number(poolsupply) * assetBAmount * Number(new big(10).toPower(precisionB)) ) / (Number(poolamounta) / Number(new big(10).toPower(precisionA)))) / Number(new big(10).toPower(precisionPP))) ) + }); + } + } + } + + onChangeAssetBAmount(v) { + const {currentTab} = this.state; + const {pool} = this.props; + const currentSupply = pool.getIn([ + "dynamic_share_asset", + "current_supply" + ]); + if (currentSupply !== undefined) { + this.setState({ + assetBAmount: v.amount + }); + } + + if (currentTab === "stake") { + const {assetAAmount} = this.state; + + const assetA = pool.get('asset_a'); + + const precisionA = assetA.get("precision"); + + const assetB = pool.get('asset_b'); + + const precisionB = assetB.get("precision"); + const shareAssetPP = pool.get('share_asset'); + const precisionPP = shareAssetPP.get("precision"); + let poolamounta = pool.get('balance_a'); + let poolamountap = new big(10).toPower(pool.get('asset_a').get("precision")); + let poolamountb = pool.get('balance_b'); + let poolamountbp = new big(10).toPower(pool.get('asset_b').get("precision")); + + let poolsupply = Number(pool.getIn(["dynamic_share_asset", "current_supply"])) / Number(new big(10).toPower(precisionPP)); + + + if (v.amount > 0){ + this.setState({ + shareAssetAmount: Math.min( ((( Number(poolsupply) * Number(v.amount) * Number(new big(10).toPower(precisionB)) ) / (Number(poolamounta) / Number(new big(10).toPower(precisionA)))) / Number(new big(10).toPower(precisionPP))), (((Number(poolsupply) * assetAAmount * Number(new big(10).toPower(precisionA)) ) / (Number(poolamounta) / Number(new big(10).toPower(precisionA)))) / Number(new big(10).toPower(precisionPP))) ) + + }); +console.log(assetAAmount,precisionA,v.amount,precisionB); + } + } + } + + onChangeShareAssetAmount(v) { + const {currentTab} = this.state; + const {pool, account} = this.props; + const currentSupply = pool.getIn([ + "dynamic_share_asset", + "current_supply" + ]); + if (currentTab === "stake"){ + if (currentSupply !== undefined){ + this.setState({ + shareAssetAmount: v.amount + }); + } + } + else if (currentTab === "unstake") { + if (currentSupply !== undefined){ + this.setState({ + shareAssetAmount: v.amount + }); + } + let bigSharedAssets = this.getShareAssetCurrentSupply(); + let bigAssetA = this.getBalanceA(); + let bigAssetB = this.getBalanceB(); + + let amountA = bigAssetA.toNumber(); + let amountB = bigAssetB.toNumber(); + + let withDrawalPercentFee = pool.get('withdrawal_fee_percent') / 100; + let poolamounta = pool.get('balance_a'); + let poolamountap = new big(10).toPower(pool.get('asset_a').get("precision")); + let poolamountb = pool.get('balance_b'); + let poolamountbp = new big(10).toPower(pool.get('asset_b').get("precision")); + const shareAssetPP = pool.get('share_asset'); + const precisionPP = shareAssetPP.get("precision"); + +let poolsupply = Number(pool.getIn(["dynamic_share_asset", "current_supply"])) / Number(new big(10).toPower(precisionPP)); + + if (bigSharedAssets.toNumber() == 0){ + amountA = 0; + amountB = 0; + } + else{ +amountA = ( (((Number(poolamounta) / Number(poolamountap)) * Number(v.amount)) / Number(poolsupply)) - ((((Number(poolamounta) / Number(poolamountap)) * Number(v.amount)) / Number(poolsupply)) * Number(withDrawalPercentFee / 100 )) ); +amountB = ( (((Number(poolamountb) / Number(poolamountbp)) * Number(v.amount)) / Number(poolsupply)) - ((((Number(poolamountb) / Number(poolamountbp)) * Number(v.amount)) / Number(poolsupply)) * Number(withDrawalPercentFee / 100 )) ); + +console.log("PoolS:",poolsupply,"AmountA:",(Number(poolamounta) / Number(poolamountap)),"AmountB:",(Number(poolamountb) / Number(poolamountbp)), Number(bigSharedAssets.toNumber()) ); +console.log(( (Number(poolamounta) / Number(poolamountap)) * Number(v.amount)) / Number(bigSharedAssets.toNumber()), Number(withDrawalPercentFee)); +console.log(( (Number(poolamountb) / Number(poolamountbp)) * Number(v.amount)) / Number(bigSharedAssets.toNumber()), Number(withDrawalPercentFee)); + } + + this.setState({ + assetAAmount: amountA, + assetBAmount: amountB + }); + + } + } + + render() { + const {TabPane} = Tabs; + const {pool, account} = this.props; + const { + assetAAmount, + assetBAmount, + shareAssetAmount, + assetAErr, + assetBErr, + shareAssetErr, + currentTab + } = this.state; + const assetA = pool.get("asset_a"); + const assetB = pool.get("asset_b"); + const shareAsset = pool.get("share_asset"); + return ( + + {counterpart.translate("wallet.submit")} + , + + ]} + > + + +
    + +
    +

    + {counterpart.translate( + "poolmart.liquidity_pools.asset_a" + )} +

    + + {account && ( + + )} + + +

    + {counterpart.translate( + "poolmart.liquidity_pools.asset_b" + )} +

    + + {account && ( + + )} + + + + + +

    + {counterpart.translate( + "poolmart.liquidity_pools.share_asset" + )} +

    + + + + + + +
    + +
    +

    + {counterpart.translate( + "poolmart.liquidity_pools.share_asset" + )} +

    + + {account && ( + + )} + + + + + +

    + {counterpart.translate( + "poolmart.liquidity_pools.asset_a" + )} +

    + + + +

    + {counterpart.translate( + "poolmart.liquidity_pools.asset_b" + )} +

    + + + + + + + + ); + } +} + +export default connect( + BindToChainState(PoolStakeModal), + { + listenTo() { + return [AccountStore]; + }, + getProps(props) { + return { + account: AccountStore.getState().currentAccount + }; + } + } +); diff --git a/app/components/Poolmart/LiquidityPools.jsx b/app/components/Poolmart/LiquidityPools.jsx new file mode 100644 index 0000000000..74befeeae9 --- /dev/null +++ b/app/components/Poolmart/LiquidityPools.jsx @@ -0,0 +1,478 @@ +import React from "react"; +import {connect} from "alt-react"; +import {Table, Select} from "bitshares-ui-style-guide"; +import Immutable from "immutable"; +import {Link} from "react-router-dom"; +import counterpart from "counterpart"; +import {ChainStore} from "bitsharesjs"; +import {debounce} from "lodash-es"; +import Translate from "react-translate-component"; +import ChainTypes from "../Utility/ChainTypes"; +import AssetName from "../Utility/AssetName"; +import BindToChainState from "../Utility/BindToChainState"; +import SearchInput from "../Utility/SearchInput"; +import PoolmartStore from "../../stores/PoolmartStore"; +import PoolmartActions from "../../actions/PoolmartActions"; +import Icon from "../Icon/Icon"; +import PoolExchangeModal from "../Modal/PoolExchangeModal"; +import PoolStakeModal from "../Modal/PoolStakeModal"; +import AccountStore from "../../stores/AccountStore"; + + +class LiquidityPools extends React.Component { + static propTypes = { + defaultAsset: ChainTypes.ChainAsset.isRequired + }; + + static defaultProps = { + defaultAsset: "1.3.0" + }; + + constructor(props) { + super(props); + + this.state = { + filterAssetA: this.props.defaultAsset + ? this.props.defaultAsset.get("symbol") + : null, + filterAssetB: null, + filterShareAsset: null, + start: "1.19.0", + limit: 10, + total: 0, + isExchangeModalVisible: false, + isStakeModalVisible: false, + selectedPool: null + }; + + this.timer = null; + } + + componentDidMount() { + this._getLiquidityPools(); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.liquidityPools !== this.props.liquidityPools) { + const {liquidityPools} = nextProps; + if ( + liquidityPools.size > 0 && + liquidityPools.last().id !== this.props.lastPoolId + ) { + this.setState( + { + start: liquidityPools.last().id + }, + () => this._getLiquidityPools() + ); + } + } + } + + _getLiquidityPools() { + const { + filterAssetA, + filterAssetB, + filterShareAsset, + GetLimit, + start + } = this.state; + if (this.timer) { + clearTimeout(this.timer); + } + this.timer = setTimeout(() => { + if (filterShareAsset) { + PoolmartActions.getLiquidityPoolsByShareAsset.defer( + filterShareAsset + ); + } else { + PoolmartActions.getLiquidityPools.defer( + filterAssetA, + filterAssetB, + GetLimit, + start + ); + } + }, 500); + } + + _resetLiquidityPools() { + this.setState({ + start: "1.19.0", + lastPoolId: null, + total: 0 + }); + PoolmartActions.resetLiquidityPools(); + } + + _onFilterAssetA(e) { + if (e.target.value) { + this.setState( + { + filterAssetA: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterAssetA: ""}); + this._resetLiquidityPools(); + } + } + + _onFilterAssetB(e) { + if (e.target.value) { + this.setState( + { + filterAssetB: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterAssetB: ""}); + this._resetLiquidityPools(); + } + } + + _onFilterShareAsset(e) { + if (e.target.value) { + this.setState( + { + filterAssetA: null, + filterAssetB: null, + filterShareAsset: e.target.value.toUpperCase() + }, + () => { + this._getLiquidityPools(); + this._resetLiquidityPools(); + } + ); + } else { + this.setState({filterShareAsset: ""}); + this._resetLiquidityPools(); + } + } + + _handleRowsChange(limit) { + this.setState( + { + limit: parseInt(limit, 10), + start: "1.19.0" + }, + () => { + this._resetLiquidityPools(); + this._getLiquidityPools(); + } + ); + } + + _showExchangeModal(pool) { + this.setState({ + isExchangeModalVisible: true, + selectedPool: pool + }); + } + + _hideExchangeModal() { + this.setState({ + isExchangeModalVisible: false, + selectedPool: null + }); + } + + _showStakeModal(pool) { + this.setState({ + isStakeModalVisible: true, + selectedPool: pool + }); + } + + _hideStakeModal() { + this.setState({ + isStakeModalVisible: false, + selectedPool: null + }); + } + + render() { + + let hasLoggedIn = + AccountStore.getState().myActiveAccounts.length > 0 || + !!AccountStore.getState().currentAccount; + console.log( ); + const tile = { + disabled: hasLoggedIn + ? false + : "Please login to use this functionality" + }; + + + const columns = [ + { + key: "id", + dataIndex: "id", + title: counterpart.translate( + "poolmart.liquidity_pools.pool_id" + ), + sorter: (a, b) => { + const aId = a.id.split(".")[2]; + const bId = b.id.split(".")[2]; + return aId - bId; + } + }, + { + key: "share_asset_str", + dataIndex: "share_asset_str", + title: counterpart.translate( + "poolmart.liquidity_pools.share_asset" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.share_asset_str > b.share_asset_str + ? 1 + : a.share_asset_str < b.share_asset_str + ? -1 + : 0 + }, + { + key: "asset_a_str", + dataIndex: "asset_a_str", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_a" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.asset_a_str > b.asset_a_str + ? 1 + : a.asset_a_str < b.asset_a_str + ? -1 + : 0 + }, + { + key: "asset_a_qty", + dataIndex: "asset_a_qty", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_a_qty" + ), + sorter: (a, b) => a.asset_a_qty - b.asset_a_qty + }, + { + key: "asset_b_str", + dataIndex: "asset_b_str", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_b" + ), + render: item => { + return item ? ( + + + + ) : null; + }, + sorter: (a, b) => + a.asset_b_str > b.asset_b_str + ? 1 + : a.asset_b_str < b.asset_b_str + ? -1 + : 0 + }, + { + key: "asset_b_qty", + dataIndex: "asset_b_qty", + title: counterpart.translate( + "poolmart.liquidity_pools.asset_b_qty" + ), + sorter: (a, b) => a.asset_b_qty - b.asset_b_qty + }, + { + key: "taker_fee_percent", + dataIndex: "taker_fee_percent_str", + title: counterpart.translate( + "poolmart.liquidity_pools.taker_fee_percent" + ) + }, + { + key: "withdrawal_fee_percent", + dataIndex: "withdrawal_fee_percent_str", + title: counterpart.translate( + "poolmart.liquidity_pools.withdrawal_fee_percent" + ) + }, + { + key: "exchange", + title: counterpart.translate( + "poolmart.liquidity_pools.exchange" + ), + render: item => ( + hasLoggedIn ? + this._showExchangeModal(item)}> + + : + ) + }, + { + key: "stake_unstake", + title: counterpart.translate( + "poolmart.liquidity_pools.stake_unstake" + ), + render: item => ( + hasLoggedIn ? + this._showStakeModal(item)}> + + : + ) + } + ]; + + + const dataSource = []; + this.props.liquidityPools.forEach(pool => { + const row = pool; + row.share_asset_str = pool.share_asset_obj + ? pool.share_asset_obj.get("symbol") + : pool.share_asset; + row.asset_a_str = pool.asset_a_obj + ? pool.asset_a_obj.get("symbol") + : pool.asset_a; + row.asset_b_str = pool.asset_b_obj + ? pool.asset_b_obj.get("symbol") + : pool.asset_b; + row.asset_a_qty = pool.asset_a_obj + ? pool.balance_a / + Math.pow(10, pool.asset_a_obj.get("precision")) + : 0; + row.asset_b_qty = pool.asset_b_obj + ? pool.balance_b / + Math.pow(10, pool.asset_b_obj.get("precision")) + : 0; + row.taker_fee_percent_str = `${pool.taker_fee_percent / 100}%`; + row.withdrawal_fee_percent_str = `${pool.withdrawal_fee_percent / + 100}%`; + dataSource.push(row); + }); + return ( +
    +
    + + + + +
    +
    +
    + + {this.state.isExchangeModalVisible && ( + + )} + {this.state.isStakeModalVisible && ( + + )} + + ); + } +} + +LiquidityPools = BindToChainState(LiquidityPools, {show_loader: true}); +class LiquidityPoolsStoreWrapper extends React.Component { + render() { + return ; + } +} + +export default connect( + LiquidityPoolsStoreWrapper, + { + listenTo() { + return [PoolmartStore]; + }, + getProps() { + return { + liquidityPools: PoolmartStore.getState().liquidityPools, + liquidityPoolsLoading: PoolmartStore.getState() + .liquidityPoolsLoading, + lastPoolId: PoolmartStore.getState().lastPoolId + }; + } + } +); diff --git a/app/components/Poolmart/PoolmartPage.jsx b/app/components/Poolmart/PoolmartPage.jsx new file mode 100644 index 0000000000..51bc7e0f7f --- /dev/null +++ b/app/components/Poolmart/PoolmartPage.jsx @@ -0,0 +1,22 @@ +import React from "react"; +import LiquidityPools from "./LiquidityPools"; + +class PoolmartPage extends React.Component { + constructor(props) { + super(props); + + this.state = {}; + } + + render() { + return ( +
    +
    + +
    +
    + ); + } +} + +export default PoolmartPage; diff --git a/app/components/Utility/AmountSelectorStyleGuide.jsx b/app/components/Utility/AmountSelectorStyleGuide.jsx index d6a6bd4e61..973c7f69f1 100644 --- a/app/components/Utility/AmountSelectorStyleGuide.jsx +++ b/app/components/Utility/AmountSelectorStyleGuide.jsx @@ -11,7 +11,7 @@ class AmountSelector extends DecimalChecker { static propTypes = { label: PropTypes.string, // a translation key for the label assets: PropTypes.array, - amount: PropTypes.any, + amount: PropTypes.string, placeholder: PropTypes.string, onChange: PropTypes.func, tabIndex: PropTypes.number, @@ -33,7 +33,7 @@ class AmountSelector extends DecimalChecker { // TODO: use asset's precision to format the number if (!v && typeof v !== "number") v = ""; if (typeof v === "number") v = v.toString(); - let value = v.trim().replace(/,/g, ""); + let value = v.toString().trim().replace(/,/g, ""); return value; } @@ -116,6 +116,7 @@ class AmountSelector extends DecimalChecker { > ", key, prop); + if (prop) { + const pools = await ChainStore.getLiquidityPoolsByShareAsset( + [prop], + this.default_props["autosubscribe"] + ); + if (pools.size > 0) { + new_state[key] = pools.first(); + ++all_objects_counter; + ++resolved_objects_counter; + } else { + new_state[key] = null; + } + } else { + if (this.state[key]) new_state[key] = null; + } + } /* Resolve lists of pure objects */ for (let key of this.chain_objects_list) { //console.log("-- Wrapper.update -->", this.chain_objects_list); @@ -515,7 +548,7 @@ function BindToChainState(Component, options = {}) { if (this.state[key]) new_state[key] = null; } } - + //console.log("----- Wrapper update ----->", this.all_chain_props, this.all_chain_props.length, all_objects_counter, resolved_objects_counter); if (all_objects_counter <= resolved_objects_counter) new_state.resolved = true; @@ -579,16 +612,16 @@ function BindToChainState(Component, options = {}) { typeof options !== "undefined" && options.show_loader ) { - console.error( - "Required prop " + - prop + - " isn't given, but still loading, this indicates that the rendering transitions are not well defined" - ); + //console.error( + // "Required prop " + + // prop + + // " isn't given, but still loading, this indicates that the rendering transitions are not well defined" + //); return ( - Component re-rendering ... + Loading ... ); diff --git a/app/components/Utility/ChainTypes.js b/app/components/Utility/ChainTypes.js index 34fb4b7175..98845b01eb 100644 --- a/app/components/Utility/ChainTypes.js +++ b/app/components/Utility/ChainTypes.js @@ -123,6 +123,23 @@ function accountChecker(props, propName, componentName) { // assume all ok return null; } +function liquidityPoolChecker(props, propName, componentName) { + componentName = componentName || "ANONYMOUS"; + if (props[propName]) { + let value = props[propName]; + if (typeof value === "string") { + return null; + } else if (typeof value === "object") { + // TODO: check object type + } else { + return new Error( + `${propName} in ${componentName} should be Immutable.List` + ); + } + } + // assume all ok + return null; +} function objectsListChecker(props, propName, componentName) { componentName = componentName || "ANONYMOUS"; @@ -163,6 +180,25 @@ function assetsListChecker(props, propName, componentName) { // assume all ok return null; } +function liquidityPoolsListChecker(props, propName, componentName) { + componentName = componentName || "ANONYMOUS"; + if (props[propName]) { + let value = props[propName]; + if ( + Immutable.List.isList(value) || + Immutable.Set.isSet(value) || + value instanceof Object + ) { + return null; + } else { + return new Error( + `${propName} in ${componentName} should be Immutable.List` + ); + } + } + // assume all ok + return null; +} function accountsListChecker(props, propName, componentName) { componentName = componentName || "ANONYMOUS"; @@ -206,9 +242,13 @@ let ChainAccountName = createChainableTypeChecker(accountNameChecker); let ChainKeyRefs = createChainableTypeChecker(keyChecker); let ChainAddressBalances = createChainableTypeChecker(keyChecker); let ChainAsset = createChainableTypeChecker(assetChecker); +let ChainLiquidityPool = createChainableTypeChecker(liquidityPoolChecker); let ChainObjectsList = createChainableTypeChecker(objectsListChecker); let ChainAccountsList = createChainableTypeChecker(accountsListChecker); let ChainAssetsList = createChainableTypeChecker(assetsListChecker); +let ChainLiquidityPoolsList = createChainableTypeChecker( + liquidityPoolsListChecker +); export default { ChainObject, @@ -217,7 +257,9 @@ export default { ChainKeyRefs, ChainAddressBalances, ChainAsset, + ChainLiquidityPool, ChainObjectsList, ChainAccountsList, - ChainAssetsList + ChainAssetsList, + ChainLiquidityPoolsList }; diff --git a/app/components/Utility/LiquidityPoolsList.jsx b/app/components/Utility/LiquidityPoolsList.jsx new file mode 100644 index 0000000000..a239a699fa --- /dev/null +++ b/app/components/Utility/LiquidityPoolsList.jsx @@ -0,0 +1,85 @@ +import React from "react"; +import Translate from "react-translate-component"; +import counterpart from "counterpart"; +import {Link} from "react-router-dom"; +import {Row, Col} from "bitshares-ui-style-guide"; +import {ChainStore} from "bitsharesjs"; +import ChainTypes from "./ChainTypes"; +import BindToChainState from "./BindToChainState"; +import AssetName from "./AssetName"; + +class LiquidityPoolsList extends React.Component { + static propTypes = { + pools: ChainTypes.ChainLiquidityPoolsList.isRequired + }; + + constructor(props) { + super(props); + } + + render() { + const {pools, useAs} = this.props; + if (pools === null) { + return
    ; + } + if (useAs === "single") { + const pool = pools.size > 0 ? pools.get(0) : null; + if (pool === null) { + return null; + } + const assetA = ChainStore.getAsset(pool.get("asset_a")); + const assetAQty = + pool.get("balance_a") / Math.pow(10, assetA.get("precision")); + const assetB = ChainStore.getAsset(pool.get("asset_b")); + const assetBQty = + pool.get("balance_b") / Math.pow(10, assetB.get("precision")); + return ( +
    + +
    + {counterpart.translate( + "poolmart.liquidity_pools.asset_a" + )} + + + + {assetAQty} +   + + + + + + + {counterpart.translate( + "poolmart.liquidity_pools.asset_b" + )} + + + + {assetBQty} +   + + + + + + + {counterpart.translate( + "poolmart.liquidity_pools.taker_fee_percent" + )} + + + {pool.get("taker_fee_percent") / 100} % + + + + ); + } else if (useAs === "list") { + return
    ; + } else { + return
    ; + } + } +} +export default BindToChainState(LiquidityPoolsList); diff --git a/app/stores/AssetStore.js b/app/stores/AssetStore.js index 3c9122ff8a..32230d25d7 100644 --- a/app/stores/AssetStore.js +++ b/app/stores/AssetStore.js @@ -14,7 +14,8 @@ class AssetStore extends BaseStore { this.bindListeners({ onGetAssetList: AssetActions.getAssetList, - onLookupAsset: AssetActions.lookupAsset + onLookupAsset: AssetActions.lookupAsset, + onGetAssetsByIssuer: AssetActions.getAssetsByIssuer }); } @@ -56,6 +57,29 @@ class AssetStore extends BaseStore { } } + onGetAssetsByIssuer(payload) { + if (!payload) { + return false; + } + this.assetsLoading = payload.loading; + + if (payload.assets) { + payload.assets.forEach(asset => { + for (var i = 0; i < payload.dynamic.length; i++) { + if (payload.dynamic[i].id === asset.dynamic_asset_data_id) { + asset.dynamic = payload.dynamic[i]; + break; + } + } + + this.assets = this.assets.set(asset.id, asset); + + this.asset_symbol_to_id[asset.symbol] = asset.id; + }); + } + } + + onLookupAsset(payload) { this.searchTerms[payload.searchID] = payload.symbol; this.lookupResults = payload.assets; diff --git a/app/stores/PoolmartStore.js b/app/stores/PoolmartStore.js new file mode 100644 index 0000000000..f270ad1aea --- /dev/null +++ b/app/stores/PoolmartStore.js @@ -0,0 +1,94 @@ +import BaseStore from "./BaseStore"; +import Immutable from "immutable"; +import alt from "alt-instance"; +import {ChainStore} from "bitsharesjs"; +import PoolmartActions from "actions/PoolmartActions"; + +class PoolmartStore extends BaseStore { + constructor() { + super(); + this.liquidityPools = Immutable.Map(); + this.liquidityPoolsLoading = false; + this.assets = Immutable.Map(); + this.lastPoolId = null; + + this.bindListeners({ + onGetLiquidityPools: PoolmartActions.GET_LIQUIDITY_POOLS, + onGetLiquidityPoolsByShareAsset: + PoolmartActions.GET_LIQUIDITY_POOLS_BY_SHARE_ASSET, + onResetLiquidityPools: PoolmartActions.RESET_LIQUIDITY_POOLS, + onGetLiquidityPoolsAccount:PoolmartActions.GET_LIQUIDITY_POOLS_ACCOUNT + }); + } + + onGetLiquidityPools(payload) { + if (!payload) { + return false; + } + this.liquidityPoolsLoading = payload.loading; + + if (payload.liquidityPools) { + let tmp = Immutable.Map(); + payload.liquidityPools.forEach(pool => { + tmp = tmp.set(pool.id, pool); + }); + if (tmp.size === 0) return; + this.lastPoolId = tmp.last().id; + this.liquidityPools = this.liquidityPools.merge(tmp); + } + + if (payload.reset === true) { + this.lastPoolId = null; + } + } + + onGetLiquidityPoolsByShareAsset(payload) { + if (!payload) { + return false; + } + this.liquidityPoolsLoading = payload.loading; + + if (payload.liquidityPools) { + let tmp = Immutable.Map(); + payload.liquidityPools.forEach(pool => { + tmp = tmp.set(pool.id, pool); + }); + if (tmp.size === 0) return; + this.lastPoolId = tmp.last().id; + this.liquidityPools = this.liquidityPools.merge(tmp); + } + + if (payload.reset === true) { + this.lastPoolId = null; + } + } + + onGetLiquidityPoolsAccount(payload){ + console.log("onGetLiquidityPoolsAccount"); + if (!payload) { + return false; + } + this.liquidityPoolsLoading = payload.loading; + + if (payload.liquidityPools) { + let tmp = Immutable.Map(); + payload.liquidityPools.forEach(pool => { + tmp = tmp.set(pool.id, pool); + }); + if (tmp.size === 0) return; + this.lastPoolId = tmp.last().id; + // this.liquidityPools = this.liquidityPools.merge(tmp); + this.liquidityPools = tmp; + } + + if (payload.reset === true) { + this.lastPoolId = null; + } + } + + onResetLiquidityPools(payload) { + this.liquidityPools = Immutable.Map(); + } +} + +export default alt.createStore(PoolmartStore, "PoolmartStore");