diff --git a/app/assets/v2/js/amounts.js b/app/assets/v2/js/amounts.js index 0acfae5a379..7100db9671d 100644 --- a/app/assets/v2/js/amounts.js +++ b/app/assets/v2/js/amounts.js @@ -86,7 +86,9 @@ var getUSDEstimate = function(amount, denomination, callback) { } var request_url = '/sync/get_amount?amount=' + amount + '&denomination=' + denomination; - jQuery.get(request_url, function(result) { + jQuery.get(request_url, function(results) { + const result = results[0]; + amount_usdt = result['usdt']; eth_amount = parseFloat(result['eth']); conv_rate = amount_usdt / amount; @@ -137,7 +139,9 @@ var getAmountEstimate = function(usd_amount, denomination, callback) { } var request_url = '/sync/get_amount?amount=' + amount + '&denomination=' + denomination; - jQuery.get(request_url, function(result) { + jQuery.get(request_url, function(results) { + const result = results[0]; + amount_usdt = result['usdt']; eth_amount = parseFloat(result['eth']); conv_rate = amount_usdt / amount; diff --git a/app/assets/v2/js/cart.js b/app/assets/v2/js/cart.js index c8adf9cf9d1..65f6c3c1cbe 100644 --- a/app/assets/v2/js/cart.js +++ b/app/assets/v2/js/cart.js @@ -513,6 +513,26 @@ Vue.component('grants-cart', { return amount.div(factor).toString(10); }, + async applyAmountToAllGrants(grant) { + const preferredAmount = grant.grant_donation_amount; + const preferredTokenName = grant.grant_donation_currency; + const fallbackAmount = await this.valueToEth(preferredAmount, preferredTokenName); + + this.grantData.forEach((grant, index) => { + const acceptedCurrencies = this.currencies[index]; // tokens accepted by this grant + + if (!acceptedCurrencies.includes(preferredTokenName)) { + // If the selected token is not available, fallback to ETH + this.grantData[index].grant_donation_amount = fallbackAmount; + this.grantData[index].grant_donation_currency = 'ETH'; + } else { + // Otherwise use the user selected option + this.grantData[index].grant_donation_amount = preferredAmount; + this.grantData[index].grant_donation_currency = preferredTokenName; + } + }); + }, + /** * @notice Checkout flow */ @@ -804,19 +824,23 @@ Vue.component('grants-cart', { return y_lower + (((y_upper - y_lower) * (x - x_lower)) / (x_upper - x_lower)); }, - async valueToDai(amount, tokenSymbol) { + valueToDai(amount, tokenSymbol, tokenPrices) { + const tokenIndex = tokenPrices.findIndex(item => item.token === tokenSymbol); + const amountOfOne = tokenPrices[tokenIndex].usdt; // value of 1 tokenSymbol + + return Number(amount) * Number(amountOfOne); // convert based on quantity and return + }, + + async valueToEth(amount, tokenSymbol) { const url = `${window.location.origin}/sync/get_amount?amount=${amount}&denomination=${tokenSymbol}`; const response = await fetch(url); const newAmount = await response.json(); - return newAmount.usdt; + return newAmount.eth; }, - async predictCLRMatch(grant) { - const rawAmount = Number(grant.grant_donation_amount); - let amount = await this.valueToDai(rawAmount, grant.grant_donation_currency); - - const clr_prediction_curve_2d = JSON.parse(grant.grant_clr_prediction_curve); + async predictCLRMatch(grant, amount) { + const clr_prediction_curve_2d = grant.grant_clr_prediction_curve; const clr_prediction_curve = clr_prediction_curve_2d.map(row => row[2]); if (amount > 10000) { @@ -877,6 +901,12 @@ Vue.component('grants-cart', { grantData: { async handler() { CartData.setCart(this.grantData); + const tokenNames = Array.from(new Set(this.grantData.map(grant => grant.grant_donation_currency))); + + const priceUrl = `${window.location.origin}/sync/get_amount?denomination=${tokenNames}`; + const priceResponse = await fetch(priceUrl); + const tokenPrices = (await priceResponse.json()); + for (let i = 0; i < this.grantData.length; i += 1) { const verification_required_to_get_match = false; @@ -887,7 +917,16 @@ Vue.component('grants-cart', { this.grantData[i].grant_donation_clr_match = 0; } else { const grant = this.grantData[i]; - const matchAmount = await this.predictCLRMatch(grant); + // Convert amount to DAI + const rawAmount = Number(grant.grant_donation_amount); + const STABLE_COINS = [ 'DAI', 'SAI', 'USDT', 'TUSD', 'aDAI', 'USDC' ]; + // All stable coins are handled with USDT (see app/app/settings.py for list) + const tokenName = STABLE_COINS.includes(grant.grant_donation_currency) + ? 'USDT' + : grant.grant_donation_currency; + + const amount = this.valueToDai(rawAmount, tokenName, tokenPrices); + const matchAmount = await this.predictCLRMatch(grant, amount); this.grantData[i].grant_donation_clr_match = matchAmount ? matchAmount.toFixed(2) : 0; } @@ -922,6 +961,26 @@ Vue.component('grants-cart', { this.grantData = CartData.loadCart(); // Initialize array of empty comments this.comments = this.grantData.map(grant => undefined); + + // Get list of all grant IDs and unique tokens in the cart + const grantIds = this.grantData.map(grant => grant.grant_id); + + // Fetch updated CLR curves for all grants + const url = `${window.location.origin}/grants/v1/api/clr?pks=${grantIds.join(',')}`; + const response = await fetch(url); + const clrCurves = (await response.json()).grants; + + // Update CLR curves + this.grantData.forEach((grant, index) => { + // Find the clrCurves entry with the same grant ID as this grant + const clrIndex = clrCurves.findIndex(item => { + return Number(item.pk) === Number(grant.grant_id); + }); + + // Replace the CLR prediction curve + this.grantData[index].grant_clr_prediction_curve = clrCurves[clrIndex].clr_prediction_curve; + }); + // Wait until we can load token list let elapsedTime = 0; let delay = 50; // 50 ms debounce diff --git a/app/assets/v2/js/grants/funding.js b/app/assets/v2/js/grants/funding.js index 457c11cc8d4..486a35dcfc1 100644 --- a/app/assets/v2/js/grants/funding.js +++ b/app/assets/v2/js/grants/funding.js @@ -56,41 +56,84 @@ $(document).ready(function() { $('#close-side-cart').click(function() { hideSideCart(); }); + + $('#side-cart-data').on('click', '#apply-to-all', async function() { + // Get preferred cart data + let cartData = CartData.loadCart(); + const network = document.web3network || 'mainnet'; + const selected_grant_index = $(this).data('id'); + const preferredAmount = cartData[selected_grant_index].grant_donation_amount; + const preferredTokenName = cartData[selected_grant_index].grant_donation_currency; + const preferredTokenAddress = tokens(network) + .filter(token => token.name === preferredTokenName) + .map(token => token.addr)[selected_grant_index]; + + // Get fallback amount in ETH (used when token is not available for a grant) + const url = `${window.location.origin}/sync/get_amount?amount=${preferredAmount}&denomination=${preferredTokenName}`; + const response = await fetch(url); + const fallbackAmount = (await response.json()).eth; + + // Update cart values + cartData.forEach((grant, index) => { + const acceptsAllTokens = (grant.grant_token_address === '0x0000000000000000000000000000000000000000'); + const acceptsSelectedToken = grant.grant_token_address === preferredTokenAddress; + + if (acceptsAllTokens || acceptsSelectedToken) { + // Use the user selected option + cartData[index].grant_donation_amount = preferredAmount; + cartData[index].grant_donation_currency = preferredTokenName; + } else { + // If the selected token is not available, fallback to ETH + cartData[index].grant_donation_amount = fallbackAmount; + cartData[index].grant_donation_currency = 'ETH'; + } + }); // end cartData.forEach + + // Update cart + CartData.setCart(cartData); + showSideCart(); + }); }); // HELPERS -function sideCartRowForGrant(grant) { +function sideCartRowForGrant(grant, index) { let cartRow = ` -