diff --git a/app/dashboard/templates/shared/search.html b/app/dashboard/templates/shared/search.html
index 7fc65f9f38d..b2516c068f2 100644
--- a/app/dashboard/templates/shared/search.html
+++ b/app/dashboard/templates/shared/search.html
@@ -17,7 +17,7 @@
{% load i18n static humanize %}
-
diff --git a/app/dashboard/tip_views.py b/app/dashboard/tip_views.py
index fe680e49514..019a72842df 100644
--- a/app/dashboard/tip_views.py
+++ b/app/dashboard/tip_views.py
@@ -198,7 +198,7 @@ def receive_tip_v3(request, key, txid, network):
@csrf_exempt
-@ratelimit(key='ip', rate='5/m', method=ratelimit.UNSAFE, block=True)
+@ratelimit(key='ip', rate='25/m', method=ratelimit.UNSAFE, block=True)
def send_tip_4(request):
"""Handle the fourth stage of sending a tip (the POST).
diff --git a/app/dashboard/views.py b/app/dashboard/views.py
index c28b60145a3..4a6d5e8c8ab 100644
--- a/app/dashboard/views.py
+++ b/app/dashboard/views.py
@@ -3623,7 +3623,7 @@ def hackathon(request, hackathon='', panel='prizes'):
'org_name': sponsor_profile.handle,
'follower_count': sponsor_profile.tribe_members.all().count(),
'followed': True if sponsor_profile.handle in following_tribes else False,
- 'bounty_count': Bounty.objects.filter(bounty_owner_github_username=sponsor_profile.handle).count()
+ 'bounty_count': sponsor_profile.bounties.count()
}
orgs.append(org)
diff --git a/app/grants/admin.py b/app/grants/admin.py
index 3320f3e15d2..b4115a49e17 100644
--- a/app/grants/admin.py
+++ b/app/grants/admin.py
@@ -66,6 +66,13 @@ class MatchPledgeAdmin(admin.ModelAdmin):
list_display =['pk', 'profile', 'active','pledge_type','amount']
+class CLRMatchAdmin(admin.ModelAdmin):
+ """Define the CLRMatch administration layout."""
+
+ ordering = ['-id']
+ raw_id_fields = ['grant', 'payout_contribution', 'test_payout_contribution']
+
+
class GrantAdmin(GeneralAdmin):
"""Define the Grant administration layout."""
@@ -277,6 +284,6 @@ def from_ip_address(self, instance):
admin.site.register(MatchPledge, MatchPledgeAdmin)
admin.site.register(Grant, GrantAdmin)
admin.site.register(Flag, FlagAdmin)
-admin.site.register(CLRMatch, GeneralAdmin)
+admin.site.register(CLRMatch, CLRMatchAdmin)
admin.site.register(Subscription, SubscriptionAdmin)
admin.site.register(Contribution, ContributionAdmin)
diff --git a/app/grants/management/commands/ingest_givingblock_txns.py b/app/grants/management/commands/ingest_givingblock_txns.py
index 9c5b4c03b41..24501a1bc60 100644
--- a/app/grants/management/commands/ingest_givingblock_txns.py
+++ b/app/grants/management/commands/ingest_givingblock_txns.py
@@ -4,7 +4,7 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
-from dashboard.models import Profile
+from dashboard.models import Activity, Profile
from grants.models import Contribution, Grant, Subscription
@@ -71,5 +71,25 @@ def handle(self, *args, **kwargs):
)
print(f"ingested {subscription.pk} / {contrib.pk}")
+ metadata = {
+ 'id': subscription.id,
+ 'value_in_token': str(subscription.amount_per_period),
+ 'value_in_usdt_now': str(round(subscription.amount_per_period_usdt,2)),
+ 'token_name': subscription.token_symbol,
+ 'title': subscription.grant.title,
+ 'grant_url': subscription.grant.url,
+ 'num_tx_approved': subscription.num_tx_approved,
+ 'category': 'grant',
+ }
+ kwargs = {
+ 'profile': profile,
+ 'subscription': subscription,
+ 'grant': subscription.grant,
+ 'activity_type': 'new_grant_contribution',
+ 'metadata': metadata,
+ }
+
+ Activity.objects.create(**kwargs)
+
except Exception as e:
print(e)
diff --git a/app/grants/management/commands/payout_round.py b/app/grants/management/commands/payout_round.py
new file mode 100644
index 00000000000..847d155ecc7
--- /dev/null
+++ b/app/grants/management/commands/payout_round.py
@@ -0,0 +1,253 @@
+'''
+ Copyright (C) 2020 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
.
+
+'''
+
+import json
+import time
+
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.core import management
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+
+from dashboard.abi import erc20_abi as abi
+from dashboard.models import Activity, Earning, Profile
+from dashboard.utils import get_tx_status, get_web3, has_tx_mined
+from gas.utils import recommend_min_gas_price_to_confirm_in_time
+from grants.models import CLRMatch, Contribution, Grant, Subscription
+from grants.views import clr_active, clr_round, next_round_start, round_end
+from marketing.mails import (
+ grant_match_distribution_final_txn, grant_match_distribution_kyc, grant_match_distribution_test_txn,
+)
+from web3 import HTTPProvider, Web3
+
+WAIT_TIME_BETWEEN_PAYOUTS = 15
+
+class Command(BaseCommand):
+
+ help = 'finalizes + sends grants round payouts'
+
+ def add_arguments(self, parser):
+ parser.add_argument('what',
+ default='finalize',
+ type=str,
+ help="what do we do? (finalize, payout_test, payout_dai, prepare_final_payout)"
+ )
+
+
+ def handle(self, *args, **options):
+
+ # setup
+ payment_threshold_usd = 0
+ KYC_THRESHOLD = settings.GRANTS_PAYOUT_CLR_KYC_THRESHOLD
+ network = 'mainnet' if not settings.DEBUG else 'rinkeby'
+ from_address = settings.GRANTS_PAYOUT_ADDRESS
+ from_pk = settings.GRANTS_PAYOUT_PRIVATE_KEY
+ DECIMALS = 18
+ what = options['what']
+ DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f' if network=='mainnet' else '0x6a6e8b58dee0ca4b4ee147ad72d3ddd2ef1bf6f7'
+ CLR_TOKEN_ADDRESS = '0x7c19252abedce09724bfc3549925d3ea12770156' if network=='mainnet' else '0xc19b694ebd4309d7a2adcd9970f8d7f424a1528b'
+
+ # get data
+ scheduled_matches = CLRMatch.objects.filter(round_number=clr_round)
+ grants = Grant.objects.filter(active=True, network='mainnet', link_to_new_grant__isnull=True)
+
+ # finalize rankings
+ if what == 'finalize':
+ total_owed_grants = sum(grant.clr_match_estimate_this_round for grant in grants)
+ total_owed_matches = sum(sm.amount for sm in scheduled_matches)
+ print(f"there are {grants.count()} grants to finalize worth ${round(total_owed_grants,2)}")
+ print(f"there are {scheduled_matches.count()} Match Payments already created worth ${round(total_owed_matches,2)}")
+ print('------------------------------')
+ user_input = input("continue? (y/n) ")
+ if user_input != 'y':
+ return
+ for grant in grants:
+ amount = grant.clr_match_estimate_this_round
+ if not amount:
+ continue
+ already_exists = scheduled_matches.filter(grant=grant).exists()
+ if already_exists:
+ continue
+ needs_kyc = amount > KYC_THRESHOLD
+ comments = "" if not needs_kyc else "Needs KYC"
+ ready_for_test_payout = not needs_kyc
+ match = CLRMatch.objects.create(
+ round_number=clr_round,
+ amount=amount,
+ grant=grant,
+ comments=comments,
+ ready_for_test_payout=ready_for_test_payout,
+ )
+ if needs_kyc:
+ grant_match_distribution_kyc(match)
+
+
+ # payout rankings (round must be finalized first)
+ if what in ['prepare_final_payout']:
+ payout_matches = scheduled_matches.exclude(test_payout_tx='').filter(ready_for_payout=False)
+ payout_matches_amount = sum(sm.amount for sm in payout_matches)
+ print(f"there are {payout_matches.count()} UNPAID Match Payments already created worth ${round(payout_matches_amount,2)} {network} DAI")
+ print('------------------------------')
+ user_input = input("continue? (y/n) ")
+ if user_input != 'y':
+ return
+ for match in payout_matches:
+ match.ready_for_payout=True
+ match.save()
+ print('promoted')
+
+
+ # payout rankings (round must be finalized first)
+ if what in ['payout_test', 'payout_dai']:
+ is_real_payout = what == 'payout_dai'
+ TOKEN_ADDRESS = DAI_ADDRESS if is_real_payout else CLR_TOKEN_ADDRESS
+ kwargs = {}
+ token_name = 'CLR5' if not is_real_payout else 'DAI'
+ key = 'ready_for_test_payout' if not is_real_payout else 'ready_for_payout'
+ kwargs[key] = False
+ not_ready_scheduled_matches = scheduled_matches.filter(**kwargs)
+ kwargs[key] = True
+ unpaid_scheduled_matches = scheduled_matches.filter(**kwargs).filter(test_payout_tx='')
+ paid_scheduled_matches = scheduled_matches.filter(**kwargs).exclude(test_payout_tx='')
+ total_not_ready_matches = sum(sm.amount for sm in not_ready_scheduled_matches)
+ total_owed_matches = sum(sm.amount for sm in unpaid_scheduled_matches)
+ total_paid_matches = sum(sm.amount for sm in paid_scheduled_matches)
+ print(f"there are {not_ready_scheduled_matches.count()} NOT READY Match Payments already created worth ${round(total_not_ready_matches,2)} {network} {token_name}")
+ print(f"there are {unpaid_scheduled_matches.count()} UNPAID Match Payments already created worth ${round(total_owed_matches,2)} {network} {token_name}")
+ print(f"there are {paid_scheduled_matches.count()} PAID Match Payments already created worth ${round(total_paid_matches,2)} {network} {token_name}")
+ print('------------------------------')
+ user_input = input("continue? (y/n) ")
+ if user_input != 'y':
+ return
+
+ print(f"continuing with {unpaid_scheduled_matches.count()} unpaid scheduled payouts")
+
+ if is_real_payout:
+ user_input = input(F"THIS IS A REAL PAYOUT FOR {network} {token_name}. ARE YOU DOUBLE SECRET SUPER SURE? (y/n) ")
+ if user_input != 'y':
+ return
+
+ for match in unpaid_scheduled_matches.order_by('amount'):
+
+ # issue payment
+ print(f"- issuing payout {match.pk} worth {match.amount} {token_name}")
+ address = match.grant.admin_address
+ amount_owed = match.amount
+
+ w3 = get_web3(network)
+ contract = w3.eth.contract(Web3.toChecksumAddress(CLR_TOKEN_ADDRESS), abi=abi)
+ address = Web3.toChecksumAddress(address)
+
+ amount = int(amount_owed * 10**DECIMALS)
+ tx = contract.functions.transfer(address, amount).buildTransaction({
+ 'nonce': w3.eth.getTransactionCount(from_address),
+ 'gas': 100000,
+ 'gasPrice': int(float(recommend_min_gas_price_to_confirm_in_time(1)) * 10**9 * 1.4)
+ })
+
+ signed = w3.eth.account.signTransaction(tx, from_pk)
+ tx_id = w3.eth.sendRawTransaction(signed.rawTransaction).hex()
+
+ if not tx_id:
+ print("cannot pay advance, did not get a txid")
+ continue
+
+ print("paid via", tx_id)
+
+ # make save state to DB
+ if is_real_payout:
+ match.payout_tx = tx_id
+ else:
+ match.test_payout_tx = tx_id
+ match.save()
+
+ # wait for tx to clear
+ while not has_tx_mined(tx_id, network):
+ time.sleep(1)
+
+ # make save state to DB
+ if is_real_payout:
+ match.payout_tx_date = timezone.now()
+ grant_match_distribution_final_txn(match)
+ else:
+ match.test_payout_tx_date = timezone.now()
+ grant_match_distribution_test_txn(match)
+ match.save()
+
+ # create payout obj artifacts
+ profile = Profile.objects.get(handle__iexact='gitcoinbot')
+ validator_comment = f"created by ingest payout_round_script"
+ subscription = Subscription()
+ subscription.is_postive_vote = True
+ subscription.active = False
+ subscription.error = True
+ subscription.contributor_address = 'N/A'
+ subscription.amount_per_period = match.amount
+ subscription.real_period_seconds = 2592000
+ subscription.frequency = 30
+ subscription.frequency_unit = 'N/A'
+ subscription.token_address = CLR_TOKEN_ADDRESS
+ subscription.token_symbol = token_name
+ subscription.gas_price = 0
+ subscription.new_approve_tx_id = '0x0'
+ subscription.num_tx_approved = 1
+ subscription.network = network
+ subscription.contributor_profile = profile
+ subscription.grant = match.grant
+ subscription.comments = validator_comment
+ subscription.amount_per_period_usdt = match.amount
+ subscription.save()
+
+ contrib = Contribution.objects.create(
+ success=True,
+ tx_cleared=True,
+ tx_override=True,
+ tx_id=tx_id,
+ subscription=subscription,
+ validator_passed=True,
+ validator_comment=validator_comment,
+ )
+ print(f"ingested {subscription.pk} / {contrib.pk}")
+
+ if is_real_payout:
+ match.payout_contribution = contrib
+ else:
+ match.test_payout_contribution = contrib
+ match.save()
+
+ metadata = {
+ 'id': subscription.id,
+ 'value_in_token': str(subscription.amount_per_period),
+ 'value_in_usdt_now': str(round(subscription.amount_per_period_usdt,2)),
+ 'token_name': subscription.token_symbol,
+ 'title': subscription.grant.title,
+ 'grant_url': subscription.grant.url,
+ 'num_tx_approved': subscription.num_tx_approved,
+ 'category': 'grant',
+ }
+ kwargs = {
+ 'profile': profile,
+ 'subscription': subscription,
+ 'grant': subscription.grant,
+ 'activity_type': 'new_grant_contribution',
+ 'metadata': metadata,
+ }
+
+ Activity.objects.create(**kwargs)
+ time.sleep(WAIT_TIME_BETWEEN_PAYOUTS)
diff --git a/app/grants/migrations/0055_auto_20200420_1949.py b/app/grants/migrations/0055_auto_20200420_1949.py
new file mode 100644
index 00000000000..2011ff6ff60
--- /dev/null
+++ b/app/grants/migrations/0055_auto_20200420_1949.py
@@ -0,0 +1,59 @@
+# Generated by Django 2.2.4 on 2020-04-20 19:49
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('grants', '0054_auto_20200414_1141'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='clrmatch',
+ name='comments',
+ field=models.TextField(blank=True, default='', help_text='The comments.'),
+ ),
+ migrations.AddField(
+ model_name='clrmatch',
+ name='payout_contribution',
+ field=models.ForeignKey(help_text='Contribution for the payout', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clr_match_payouts', to='grants.Contribution'),
+ ),
+ migrations.AddField(
+ model_name='clrmatch',
+ name='payout_tx',
+ field=models.CharField(blank=True, help_text='The test payout txid', max_length=255),
+ ),
+ migrations.AddField(
+ model_name='clrmatch',
+ name='payout_tx_date',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.AddField(
+ model_name='clrmatch',
+ name='ready_for_payout',
+ field=models.BooleanField(default=False, help_text='Ready for regular payout or not'),
+ ),
+ migrations.AddField(
+ model_name='clrmatch',
+ name='ready_for_test_payout',
+ field=models.BooleanField(default=False, help_text='Ready for test payout or not'),
+ ),
+ migrations.AddField(
+ model_name='clrmatch',
+ name='test_payout_contribution',
+ field=models.ForeignKey(help_text='Contribution for the test payout', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='test_clr_match_payouts', to='grants.Contribution'),
+ ),
+ migrations.AddField(
+ model_name='clrmatch',
+ name='test_payout_tx',
+ field=models.CharField(blank=True, help_text='The test payout txid', max_length=255),
+ ),
+ migrations.AddField(
+ model_name='clrmatch',
+ name='test_payout_tx_date',
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ ]
diff --git a/app/grants/models.py b/app/grants/models.py
index b443984e7ed..6d831d98971 100644
--- a/app/grants/models.py
+++ b/app/grants/models.py
@@ -317,6 +317,13 @@ def updateActiveSubscriptions(self):
self.activeSubscriptions = handles
+ @property
+ def clr_match_estimate_this_round(self):
+ try:
+ return self.clr_prediction_curve[0][1]
+ except:
+ return 0
+
@property
def contributions(self):
pks = []
@@ -1205,6 +1212,39 @@ class CLRMatch(SuperModel):
null=False,
help_text=_('The associated Grant.'),
)
+ ready_for_test_payout = models.BooleanField(default=False, help_text=_('Ready for test payout or not'))
+ test_payout_tx = models.CharField(
+ max_length=255,
+ blank=True,
+ help_text=_('The test payout txid'),
+ )
+ test_payout_tx_date = models.DateTimeField(null=True, blank=True)
+ test_payout_contribution = models.ForeignKey(
+ 'grants.Contribution',
+ related_name='test_clr_match_payouts',
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ help_text=_('Contribution for the test payout')
+ )
+
+ ready_for_payout = models.BooleanField(default=False, help_text=_('Ready for regular payout or not'))
+ payout_tx = models.CharField(
+ max_length=255,
+ blank=True,
+ help_text=_('The test payout txid'),
+ )
+ payout_tx_date = models.DateTimeField(null=True, blank=True)
+ payout_contribution = models.ForeignKey(
+ 'grants.Contribution',
+ related_name='clr_match_payouts',
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ help_text=_('Contribution for the payout')
+ )
+ comments = models.TextField(default='', blank=True, help_text=_('The comments.'))
+
def __str__(self):
"""Return the string representation of a Grant."""
diff --git a/app/grants/templates/grants/shared/landing_hero.html b/app/grants/templates/grants/shared/landing_hero.html
index 82c86e17682..6eae1ae0db5 100644
--- a/app/grants/templates/grants/shared/landing_hero.html
+++ b/app/grants/templates/grants/shared/landing_hero.html
@@ -16,7 +16,7 @@
- Final amounts will be announced on April 14th.
- - Payout instructions will be circulated by April 17th.
+ - Payout instructions will be circulated by April 21st.
- The next round starts June 15th.
diff --git a/app/marketing/mails.py b/app/marketing/mails.py
index 24fb32406dd..23647b7c20d 100644
--- a/app/marketing/mails.py
+++ b/app/marketing/mails.py
@@ -793,15 +793,14 @@ def warn_account_out_of_eth(account, balance, denomination):
subject = account + str(_(" is out of gas"))
body_str = _("is down to ")
body = f"{account} {body_str} {balance} {denomination}"
- if not should_suppress_notification_email(to_email, 'admin'):
- send_mail(
- from_email,
- to_email,
- subject,
- body,
- from_name=_("No Reply from Gitcoin.co"),
- categories=['admin', func_name()],
- )
+ send_mail(
+ from_email,
+ to_email,
+ subject,
+ body,
+ from_name=_("No Reply from Gitcoin.co"),
+ categories=['admin', func_name()],
+ )
finally:
translation.activate(cur_language)
@@ -884,6 +883,147 @@ def funder_payout_reminder(to_email, bounty, github_username, live):
return html
+def grant_match_distribution_kyc(match):
+ to_email = match.grant.admin_profile.email
+ cc_emails = [profile.email for profile in match.grant.team_members.all()]
+ from_email = 'kyc@gitcoin.co'
+ cur_language = translation.get_language()
+ rounded_amount = round(match.amount, 2)
+ token_name = f"CLR{match.round_number}"
+ try:
+ setup_lang(to_email)
+ subject = f"💰 (ACTION REQUIRED) Grants Round {match.round_number} Match Distribution: {rounded_amount} DAI"
+ body = f"""
+
+Hello @{match.grant.admin_profile.handle},
+
+This email is in regards to your Gitcoin Grants Round {match.round_number} payout of {rounded_amount} DAI for https://gitcoin.co{match.grant.get_absolute_url()}.
+
+We are required by law to collect the following information from you in order to administer your payout. Please respond to this email with the following information.
+
+Full Name:
+Physical Address:
+(Only if you’re a US Citizen) Social Security Number:
+Proof of physical address (utility bill, or bank statement)
+Proof of identity (government issued ID)
+
+Thanks,
+Gitcoin Grants KYC Team
+
+
+ """
+ send_mail(
+ from_email,
+ to_email,
+ subject,
+ '',
+ body,
+ from_name=_("Gitcoin Grants"),
+ cc_emails=cc_emails,
+ categories=['admin', func_name()],
+ )
+ finally:
+ translation.activate(cur_language)
+
+
+def grant_match_distribution_test_txn(match):
+ to_email = match.grant.admin_profile.email
+ cc_emails = [profile.email for profile in match.grant.team_members.all()]
+ from_email = 'kyc@gitcoin.co'
+ cur_language = translation.get_language()
+ rounded_amount = round(match.amount, 2)
+ token_name = f"CLR{match.round_number}"
+ coupon = f"Pick up ONE item of Gitcoin Schwag at http://store.gitcoin.co/ at 25% off with coupon code {settings.GRANTS_COUPON_25_OFF}"
+ if match.amount > 100:
+ coupon = f"Pick up ONE item of Gitcoin Schwag at http://store.gitcoin.co/ at 50% off with coupon code {settings.GRANTS_COUPON_50_OFF}"
+ if match.amount > 1000:
+ coupon = f"Pick up ONE item of Gitcoin Schwag at http://store.gitcoin.co/ at 100% off with coupon code {settings.GRANTS_COUPON_100_OFF}"
+ # NOTE: IF YOURE A CLEVER BISCUT AND FOUND THIS BY READING OUR CODEBASE,
+ # THEN GOOD FOR YOU! HERE IS A 100% OFF COUPON CODE U CAN USE (LIMIT OF 1 FOR THE FIRST PERSON
+ # TO FIND THIS EASTER EGG) : GRANTS-ROUND-5-HAXXOR
+ try:
+ setup_lang(to_email)
+ subject = f"💰 Grants Round {match.round_number} Match Distribution: {rounded_amount} DAI (Email 1 of 2)"
+ body = f"""
+
+Hello @{match.grant.admin_profile.handle},
+
+This email is in regards to your Gitcoin Grants Round {match.round_number} payout of {rounded_amount} DAI for https://gitcoin.co{match.grant.get_absolute_url()}.
+
+We have sent a test transaction of {rounded_amount} {token_name} tokens to the address on file at {match.grant.admin_address}. THESE TOKENS ARE NOT WORTH *ANYTHING*, AND THIS TEST TRANSACTION WAS MADE AS A REMINDER TO MAKE SURE YOU HAVE ACCESS TO YOUR GRANTS WALLET.
+
+The txid of this test transaction is {match.test_payout_tx}.
+
+We will be issuing a final payout transaction in DAI within 24-72 hours of this email. No action is needed on your part, we will issue the final payout transaction automatically.
+
+If you're looking to kill time before your payout is administered:
+1. {coupon} (The Gitcoin Spring 2020 Edition is available at https://store.gitcoin.co/collections/ethereal-2020 )
+2. Mind helping us make Grants Round 6 even better? Fill out this donor survey: https://gitcoin.typeform.com/to/tAxEwe
+3. Attend the Gitcoin livestream this week to let us know what you think should change for Grants Round 6: https://twitter.com/owocki/status/1250760421637644288
+
+Thanks,
+Kevin, Scott, Vivek and the Gitcoin Community
+"Our mission is to Grow Open Source & provide economic opportunities to our community" https://gitcoin.co/mission
+
+
+ """
+ send_mail(
+ from_email,
+ to_email,
+ subject,
+ '',
+ body,
+ from_name=_("Gitcoin Grants"),
+ cc_emails=cc_emails,
+ categories=['admin', func_name()],
+ )
+ finally:
+ translation.activate(cur_language)
+
+def grant_match_distribution_final_txn(match):
+ to_email = match.grant.admin_profile.email
+ cc_emails = [profile.email for profile in match.grant.team_members.all()]
+ from_email = 'kyc@gitcoin.co'
+ cur_language = translation.get_language()
+ rounded_amount = round(match.amount, 2)
+ try:
+ setup_lang(to_email)
+ subject = f"🎉 Your Match Distribution of {rounded_amount} DAI has been sent! 🎉"
+ body = f"""
+
+Hello @{match.grant.admin_profile.handle},
+
+This email is in regards to your Gitcoin Grants Round {match.round_number} payout of {rounded_amount} DAI for https://gitcoin.co{match.grant.get_absolute_url()}.
+
+We have sent your {rounded_amount} DAI to the address on file at {match.grant.admin_address}. The txid of this transaction is {match.payout_tx}.
+
+Congratulations on a successful Gitcoin Grants Round {match.round_number}.
+
+What now?
+1. Send a tweet letting us know how these grant funds are being used to support your project (our twitter username is @gitcoin).
+2. Remember to update your grantees on what you use the funds for by clicking through to your grant ( https://gitcoin.co{match.grant.get_absolute_url()} ) and posting to your activity feed.
+3. Celebrate 🎉 and then get back to BUIDLing something great. ðŸ›
+
+Thanks,
+Kevin, Scott, Vivek and the Gitcoin Community
+"Our mission is to Grow Open Source & provide economic opportunities to our community" https://gitcoin.co/mission
+
+
+ """
+ send_mail(
+ from_email,
+ to_email,
+ subject,
+ '',
+ body,
+ from_name=_("Gitcoin Grants"),
+ cc_emails=cc_emails,
+ categories=['admin', func_name()],
+ )
+ finally:
+ translation.activate(cur_language)
+
+
def match_distribution(mr):
from_email = settings.PERSONAL_CONTACT_EMAIL
to_email = mr.profile.email
diff --git a/app/townsquare/management/commands/payout_mini_clr.py b/app/townsquare/management/commands/payout_mini_clr.py
index 2bc7f85b93d..eca6da81ec4 100644
--- a/app/townsquare/management/commands/payout_mini_clr.py
+++ b/app/townsquare/management/commands/payout_mini_clr.py
@@ -25,13 +25,13 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
+from dashboard.abi import erc20_abi as abi
from dashboard.models import Activity, Earning, Profile
from dashboard.utils import get_tx_status, get_web3, has_tx_mined
from gas.utils import recommend_min_gas_price_to_confirm_in_time
from townsquare.models import MatchRound
from web3 import HTTPProvider, Web3
-abi = json.loads('[{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":false,"stateMutability":"nonpayable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]')
class Command(BaseCommand):
diff --git a/pydocmd.yml b/pydocmd.yml
index 9388b663129..3eb5f038ac7 100644
--- a/pydocmd.yml
+++ b/pydocmd.yml
@@ -179,8 +179,6 @@ generate:
- marketing.management.commands.debug_test++
- marketing/admin.md:
- marketing.admin++
- - marketing/google_analytics.md:
- - marketing.google_analytics++
- marketing/mails.md:
- marketing.mails++
- marketing/models.md:
diff --git a/scripts/debug/grants_export.py b/scripts/debug/grants_export.py
new file mode 100644
index 00000000000..90594a6be49
--- /dev/null
+++ b/scripts/debug/grants_export.py
@@ -0,0 +1,33 @@
+from grants.models import *
+
+# total stats
+
+print("name, gitcoin url, project URL, created, category, sub-category, amount raised total, amount raised round 5, amount matched round 5, num contributors round 5, github_url")
+for grant in Grant.objects.filter(active=True):
+ amount_round = 0
+ try:
+ amount_round = grant.clr_prediction_curve[0][1]
+ except:
+ pass
+ print(
+ grant.title.replace(',',''),
+ ",",
+ "https://gitcoin.co" + grant.url,
+ ",",
+ grant.reference_url.replace("\n",''),
+ ",",
+ grant.created_on.strftime('%m/%d/%Y'),
+ ",",
+ grant.grant_type,
+ ",",
+ "::".join(grant.categories.values_list('category', flat=True)),
+ ",",
+ grant.amount_received,
+ ",",
+ grant.amount_received_in_round,
+ ",",
+ amount_round,
+ ",",
+ grant.positive_round_contributor_count,
+ ",",
+ )