diff --git a/app/app/urls.py b/app/app/urls.py
index b71eb9433da..ebb32c8fbef 100644
--- a/app/app/urls.py
+++ b/app/app/urls.py
@@ -75,6 +75,7 @@
url(r'^bounty/details/?', dashboard.views.bounty_details, name='bounty_details'),
url(r'^funding/details/?', dashboard.views.bounty_details, name='funding_details'),
url(r'^legacy/funding/details/?', dashboard.views.bounty_details, name='legacy_funding_details'),
+ url(r'^funding/increase/?', dashboard.views.increase_bounty, name='increase_bounty'),
url(r'^funding/kill/?', dashboard.views.kill_bounty, name='kill_bounty'),
url(r'^tip/receive/?', dashboard.views.receive_tip, name='receive_tip'),
url(r'^tip/send/2/?', dashboard.views.send_tip_2, name='send_tip_2'),
diff --git a/app/assets/v2/js/pages/bounty_details.js b/app/assets/v2/js/pages/bounty_details.js
index 61890063331..f9986a76485 100644
--- a/app/assets/v2/js/pages/bounty_details.js
+++ b/app/assets/v2/js/pages/bounty_details.js
@@ -342,9 +342,11 @@ var do_actions = function(result) {
var show_github_link = result['github_url'].substring(0, 4) == 'http';
var show_submit_work = true;
var show_kill_bounty = !is_status_done && !is_status_expired && !is_status_cancelled;
+ var show_increase_bounty = !is_status_done && !is_status_expired && !is_status_cancelled;
var kill_bounty_enabled = isBountyOwner(result);
var submit_work_enabled = !isBountyOwner(result);
var start_stop_work_enabled = !isBountyOwner(result);
+ var increase_bounty_enabled = isBountyOwner(result);
if (is_legacy) {
show_start_stop_work = false;
@@ -420,6 +422,19 @@ var do_actions = function(result) {
actions.push(_entry);
}
+ if (show_increase_bounty) {
+ var _entry = {
+ href: '/funding/increase?source=' + result['github_url'],
+ text: 'Add Contribution',
+ parent: 'right_actions',
+ color: increase_bounty_enabled ? 'darkBlue' : 'darkGrey',
+ extraClass: increase_bounty_enabled ? '' : 'disabled',
+ title: increase_bounty_enabled ? 'Increase the funding of this bounty' : 'Can only be performed if you are the funder.'
+ };
+
+ actions.push(_entry);
+ }
+
if (show_kill_bounty) {
var enabled = kill_bounty_enabled;
var _entry = {
diff --git a/app/assets/v2/js/pages/increase_bounty.js b/app/assets/v2/js/pages/increase_bounty.js
new file mode 100644
index 00000000000..9ec546c653b
--- /dev/null
+++ b/app/assets/v2/js/pages/increase_bounty.js
@@ -0,0 +1,157 @@
+load_tokens();
+
+// Wait until page is loaded, then run the function
+$(document).ready(function() {
+ $('input[name=amount]').keyup(setUsdAmount);
+ $('input[name=amount]').blur(setUsdAmount);
+ $('select[name=deonomination]').change(setUsdAmount);
+
+ $('input[name=amount]').focus();
+
+ var denomSelect = $('select[name=deonomination]');
+
+ localStorage['tokenAddress'] = denomSelect.data('tokenAddress');
+ localStorage['amount'] = 0;
+ denomSelect.select2();
+ $('.js-select2').each(function() {
+ $(this).select2();
+ });
+
+ // submit bounty button click
+ $('#submitBounty').click(function(e) {
+ mixpanel.track('Increase Bounty Clicked (funder)', {});
+
+ // setup
+ e.preventDefault();
+ loading_button($(this));
+
+ var issueURL = $('input[name=issueURL]').val();
+ var amount = $('input[name=amount]').val();
+ var tokenAddress = $('select[name=deonomination]').val();
+ var token = tokenAddressToDetails(tokenAddress);
+ var decimals = token['decimals'];
+ var tokenName = token['name'];
+ var decimalDivisor = Math.pow(10, decimals);
+
+ // validation
+ var isError = false;
+
+ if ($('#terms:checked').length == 0) {
+ _alert({ message: 'Please accept the terms of service.' });
+ isError = true;
+ } else {
+ localStorage['acceptTOS'] = true;
+ }
+ var is_issueURL_invalid = issueURL == '' ||
+ issueURL.indexOf('http') != 0 ||
+ issueURL.indexOf('github') == -1 ||
+ issueURL.indexOf('javascript:') != -1
+
+ ;
+ if (is_issueURL_invalid) {
+ _alert({ message: 'Please enter a valid github issue URL.' });
+ isError = true;
+ }
+ if (amount == '') {
+ _alert({ message: 'Please enter an amount.' });
+ isError = true;
+ }
+ if (isError) {
+ unloading_button($(this));
+ return;
+ }
+ $(this).attr('disabled', 'disabled');
+
+ // setup web3
+ // TODO: web3 is using the web3.js file. In the future we will move
+ // to the node.js package. github.com/ethereum/web3.js
+ var isETH = tokenAddress == '0x0000000000000000000000000000000000000000';
+ var token_contract = web3.eth.contract(token_abi).at(tokenAddress);
+ var account = web3.eth.coinbase;
+
+ amount = amount * decimalDivisor;
+ // Create the bounty object.
+ // This function instantiates a contract from the existing deployed Standard Bounties Contract.
+ // bounty_abi is a giant object containing the different network options
+ // bounty_address() is a function that looks up the name of the network and returns the hash code
+ var bounty = web3.eth.contract(bounty_abi).at(bounty_address());
+
+ // setup inter page state
+ localStorage[issueURL] = JSON.stringify({
+ 'timestamp': null,
+ 'txid': null
+ });
+
+ function web3Callback(error, result) {
+ if (error) {
+ mixpanel.track('Increase Bounty Error (funder)', {step: 'post_bounty', error: error});
+ _alert({ message: 'There was an error. Please try again or contact support.' });
+ unloading_button($('#submitBounty'));
+ return;
+ }
+
+ // update localStorage issuePackage
+ var issuePackage = JSON.parse(localStorage[issueURL]);
+
+ issuePackage['txid'] = result;
+ issuePackage['timestamp'] = timestamp();
+ localStorage[issueURL] = JSON.stringify(issuePackage);
+
+ _alert({ message: 'Submission sent to web3.' }, 'info');
+ setTimeout(function() {
+ mixpanel.track('Submit New Bounty Success', {});
+ document.location.href = '/funding/details/?url=' + issueURL;
+ }, 1000);
+ }
+
+ var bountyAmount = parseInt($('input[name=valueInToken]').val(), 10);
+ var ethAmount = isETH ? amount : 0;
+ var bountyId = $('input[name=standardBountiesId]').val();
+ var fromAddress = $('input[name=bountyOwnerAddress]').val();
+
+ var errormsg = undefined;
+
+ if (bountyAmount == 0 || open == false) {
+ errormsg = 'No active funded issue found at this address. Are you sure this is an active funded issue?';
+ }
+ if (fromAddress != web3.eth.coinbase) {
+ errormsg = 'Only the address that submitted this funded issue may increase the payout.';
+ }
+
+ if (errormsg) {
+ _alert({ message: errormsg });
+ unloading_button($('#submitBounty'));
+ return;
+ }
+
+ function approveSuccessCallback() {
+ bounty.increasePayout(
+ bountyId,
+ bountyAmount + amount,
+ amount,
+ {
+ from: account,
+ value: ethAmount,
+ gasPrice: web3.toHex($('#gasPrice').val()) * Math.pow(10, 9)
+ },
+ web3Callback
+ );
+ }
+
+ if (isETH) {
+ // no approvals needed for ETH
+ approveSuccessCallback();
+ } else {
+ token_contract.approve(
+ bounty_address(),
+ amount,
+ {
+ from: account,
+ value: 0,
+ gasPrice: web3.toHex($('#gasPrice').val()) * Math.pow(10, 9)
+ },
+ approveSuccessCallback
+ );
+ }
+ });
+});
diff --git a/app/assets/v2/js/pages/new_bounty.js b/app/assets/v2/js/pages/new_bounty.js
index 1ad09e7cc44..64932812dc8 100644
--- a/app/assets/v2/js/pages/new_bounty.js
+++ b/app/assets/v2/js/pages/new_bounty.js
@@ -1,13 +1,6 @@
/* eslint-disable no-console */
/* eslint-disable nonblock-statement-body-position */
load_tokens();
-var setUsdAmount = function(event) {
- var amount = $('input[name=amount]').val();
- var denomination = $('#token option:selected').text();
- var estimate = getUSDEstimate(amount, denomination, function(estimate) {
- $('#usd_amount').html(estimate);
- });
-};
// Wait until page is loaded, then run the function
$(document).ready(function() {
diff --git a/app/assets/v2/js/shared.js b/app/assets/v2/js/shared.js
index bf70629d7f2..0c51b2660a7 100644
--- a/app/assets/v2/js/shared.js
+++ b/app/assets/v2/js/shared.js
@@ -679,3 +679,11 @@ $(document).ready(function() {
window.addEventListener('load', function() {
setInterval(listen_for_web3_changes, 300);
});
+
+var setUsdAmount = function(event) {
+ var amount = $('input[name=amount]').val();
+ var denomination = $('#token option:selected').text();
+ var estimate = getUSDEstimate(amount, denomination, function(estimate) {
+ $('#usd_amount').html(estimate);
+ });
+};
diff --git a/app/dashboard/helpers.py b/app/dashboard/helpers.py
index a930e2ffc4c..5a857e44457 100644
--- a/app/dashboard/helpers.py
+++ b/app/dashboard/helpers.py
@@ -508,6 +508,8 @@ def process_bounty_changes(old_bounty, new_bounty):
event_name = 'killed_bounty'
else:
event_name = 'work_done'
+ elif old_bounty.value_in_token < new_bounty.value_in_token:
+ event_name = 'increased_bounty'
else:
event_name = 'unknown_event'
logging.error(f'got an unknown event from bounty {old_bounty.pk} => {new_bounty.pk}: {json_diff}')
diff --git a/app/dashboard/notifications.py b/app/dashboard/notifications.py
index 2172dd98538..94359e26664 100644
--- a/app/dashboard/notifications.py
+++ b/app/dashboard/notifications.py
@@ -91,6 +91,11 @@ def maybe_market_to_twitter(bounty, event_name):
"Hot off the blockchain! 🔥🔥🔥 There's a new task worth {} {} {} \n\n{}",
"💰 New Task Alert.. 💰 Earn {} {} {} for working on this 👇 \n\n{}",
]
+ if event_name == 'increased_bounty':
+ tweet_txts = tweet_txts + [
+ "Looking for paid Open Source work? Earn {} {} {} and boost your reputation by completing this task \n\n{}",
+ "Ding ding ding!! A bounty was just increased to {} {} {}! Someone must really want you to work on this task \n\n{}"
+ ]
random.shuffle(tweet_txts)
tweet_txt = tweet_txts[0]
@@ -239,6 +244,14 @@ def build_github_notification(bounty, event_name, profile_pairs=None):
f"[here]({absolute_url})\n * Questions? Get help on the " \
f"Gitcoin Slack\n * ${amount_open_work}" \
" more Funded OSS Work Available at: https://gitcoin.co/explorer\n"
+ if event_name == 'increased_bounty':
+ msg = f"__The funding of this issue was increased to {natural_value} " \
+ f"{bounty.token_name} {usdt_value}.__\n\n * If you would " \
+ f"like to work on this issue you can claim it [here]({absolute_url}).\n " \
+ "* If you've completed this issue and want to claim the bounty you can do so " \
+ f"[here]({absolute_url})\n * Questions? Get help on the " \
+ f"Gitcoin Slack\n * ${amount_open_work}" \
+ " more Funded OSS Work Available at: https://gitcoin.co/explorer\n"
elif event_name == 'killed_bounty':
msg = f"__The funding of {natural_value} {bounty.token_name} " \
f"{usdt_value} attached to this issue has been **killed** by the bounty submitter__\n\n " \
diff --git a/app/dashboard/templates/increase_bounty.html b/app/dashboard/templates/increase_bounty.html
new file mode 100644
index 00000000000..7755c8571eb
--- /dev/null
+++ b/app/dashboard/templates/increase_bounty.html
@@ -0,0 +1,109 @@
+{% comment %}
+Copyright (C) 2017 Gitcoin Core
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see