{% trans "In order to continue, you'll need some Ether." %}
{% trans "'Ether' is a digital asset that can be used to pay for the computational resources needed to run an application or program. In this case, Gitcoin." %}
-
{% trans "The easiest way to get Ether is to request it via the Gitcoin Faucet:" %}
diff --git a/app/dashboard/templatetags/bundle.py b/app/dashboard/templatetags/bundle.py
index 1900bdf5cc1..2629e771948 100644
--- a/app/dashboard/templatetags/bundle.py
+++ b/app/dashboard/templatetags/bundle.py
@@ -38,8 +38,8 @@
"""
-# check for production env
-isProduction = settings.ENV in ['prod']
+# If in development, we won't push assets to S3
+isDevelopment = settings.ENV not in ['prod', 'test']
# define variables to include in every script (and react to any changes)
@@ -76,7 +76,7 @@ def get_file_ts(asset, reportException=False):
def clean_block_and_hash(block):
# clean up the block -- we want to drop anything that gets added by staticfinder (we could remove this if we purge {% static ... %} from tags)
- if isProduction:
+ if not isDevelopment:
# in prod - staticfinder will attach static_url and an additional hash to the resource which doesn't exist on the local disk
block = re.sub(re.compile(r'(' + re.escape(settings.STATIC_URL) + r')([^>]*)(\.[0-9a-zA-Z]{12}?)\.(css|scss|js)'), r'\2.\4', block)
else:
@@ -111,9 +111,9 @@ def check_for_changes(elems, attr, kind, outputFile):
if not changed:
for el in elems:
if el.get(attr):
- # check if we're loading an alternative source in production
+ # check if we're loading an alternative source
file = el[attr]
- if isProduction and el.get('prod'):
+ if not isDevelopment and el.get('prod'):
file = el['prod']
# discover ts using the absolute path of the given asset
@@ -161,7 +161,7 @@ def get_bundled(elems, attr, kind, merge):
# check if we're loading an alternative source in production
file = el[attr]
- if isProduction and el.get('prod'):
+ if not isDevelopment and el.get('prod'):
file = el['prod']
# absolute path of the given asset
@@ -193,12 +193,13 @@ def get_bundled(elems, attr, kind, merge):
content = sass.compile(string='%s \n %s' % (get_sass_extras(), content))
# minify the content in production
- if isProduction and 'js' in kind:
- import rjsmin
- content = rjsmin.jsmin(content)
- elif isProduction and 'css' in kind:
- import rcssmin
- content = rcssmin.cssmin(content)
+ if not isDevelopment:
+ if 'js' in kind:
+ import rjsmin
+ content = rjsmin.jsmin(content)
+ elif 'css' in kind:
+ import rcssmin
+ content = rcssmin.cssmin(content)
# content is compiled and minified (if in production)
return content
@@ -222,7 +223,7 @@ def render(block, kind, mode, name='asset', forced=False):
block, blockHash = clean_block_and_hash(block)
# in production we don't need to generate new content unless we're running this via the bundle command
- if not isProduction or forced == True:
+ if isDevelopment or forced == True:
# concat all input in the block
content = ''
# pull the appropriate tags from the block
diff --git a/app/dashboard/utils.py b/app/dashboard/utils.py
index ef740839344..24cdd554203 100644
--- a/app/dashboard/utils.py
+++ b/app/dashboard/utils.py
@@ -1149,7 +1149,7 @@ def list_urls(lis, acc=None):
def get_url_first_indexes():
- return ['_administration','about','action','actions','activity','api','avatar','blog','bounties','bounty','btctalk','casestudies','casestudy','chat','community','contributor','contributor_dashboard','credit','dashboard','docs','dynamic','explorer','extension','faucet','fb','feedback','funder','funder_dashboard','funding','gas','ghlogin','github','gitter','grant','grants','hackathon','hackathonlist','hackathons','health','help','home','how','impersonate','inbox','interest','issue','itunes','jobs','jsi18n','kudos','l','labs','landing','lazy_load_kudos','lbcheck','leaderboard','legacy','legal','livestream','login','logout','mailing_list','medium','mission','modal','new','not_a_token','o','onboard','podcast','postcomment','press','presskit','products','profile','quests','reddit','refer','register_hackathon','requestincrease','requestmoney','requests','results','revenue','robotstxt','schwag','send','service','settings','sg_sendgrid_event_processor','sitemapsectionxml','sitemapxml','slack','spec','strbounty_network','submittoken','sync','terms','tip','townsquare','tribe','tribes','twitter','users','verified','vision','wallpaper','wallpapers','web3','whitepaper','wiki','wikiazAZ09azdAZdazd','youtube']
+ return ['_administration','about','action','actions','activity','api','avatar','blog','bounties','bounty','btctalk','casestudies','casestudy','chat','community','contributor','contributor_dashboard','credit','dashboard','docs','dynamic','explorer','extension','fb','feedback','funder','funder_dashboard','funding','gas','ghlogin','github','gitter','grant','grants','hackathon','hackathonlist','hackathons','health','help','home','how','impersonate','inbox','interest','issue','itunes','jobs','jsi18n','kudos','l','labs','landing','lazy_load_kudos','lbcheck','leaderboard','legacy','legal','livestream','login','logout','mailing_list','medium','mission','modal','new','not_a_token','o','onboard','podcast','postcomment','press','presskit','products','profile','quests','reddit','refer','register_hackathon','requestincrease','requestmoney','requests','results','revenue','robotstxt','schwag','send','service','settings','sg_sendgrid_event_processor','sitemapsectionxml','sitemapxml','slack','spec','strbounty_network','submittoken','sync','terms','tip','townsquare','tribe','tribes','twitter','users','verified','vision','wallpaper','wallpapers','web3','whitepaper','wiki','wikiazAZ09azdAZdazd','youtube']
# TODO: figure out the recursion issue with the URLs at a later date
# or just cache them in the backend dynamically
diff --git a/app/faucet/__init__.py b/app/faucet/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/app/faucet/admin.py b/app/faucet/admin.py
deleted file mode 100644
index b9040f05814..00000000000
--- a/app/faucet/admin.py
+++ /dev/null
@@ -1,93 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Define admin related functionality for faucet.
-
-Copyright (C) 2021 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 .
-
-"""
-from __future__ import unicode_literals
-
-from django.contrib import admin
-from django.utils.safestring import mark_safe
-
-from .models import FaucetRequest
-
-
-class GeneralAdmin(admin.ModelAdmin):
- """Define the Faucet specific admin handling."""
-
- ordering = ['-id']
- list_display = ['created_on', '__str__']
-
-
-class FaucetRequestAdmin(admin.ModelAdmin):
- """Setup the FaucetRequest admin results display."""
-
- raw_id_fields = ['profile']
- ordering = ['-created_on']
- list_display = [
- 'created_on', 'fulfilled', 'rejected', 'link', 'get_profile_handle',
- 'get_profile_email', 'email', 'address', 'comment',
- ]
- search_fields = [
- 'created_on', 'fulfilled', 'rejected', 'profile__handle',
- 'email', 'address', 'comment',
- ]
-
- def get_queryset(self, request):
- """Override the get_queryset method to include FK lookups."""
- return super(FaucetRequestAdmin, self).get_queryset(request).select_related('profile')
-
- def get_profile_email(self, obj):
- """Get the profile email address."""
- profile = getattr(obj, 'profile', None)
- if profile:
- return profile.email
- return 'N/A'
-
- get_profile_email.admin_order_field = 'email'
- get_profile_email.short_description = 'Profile Email'
-
- def get_profile_handle(self, obj):
- """Get the profile handle."""
- profile = getattr(obj, 'profile', None)
- if profile and profile.handle:
- return mark_safe(
- f'{profile.handle}'
- )
- if obj.github_username:
- return obj.github_username
- return 'N/A'
-
- get_profile_handle.admin_order_field = 'handle'
- get_profile_handle.short_description = 'Profile Handle'
-
- def link(self, instance):
- """Handle faucet request specific links.
-
- Args:
- instance (FaucetRequest): The faucet request to build a link for.
-
- Returns:
- str: The HTML element for the faucet request link.
-
- """
- if instance.fulfilled or instance.rejected:
- return 'n/a'
- return mark_safe(f"process me")
- link.allow_tags = True
-
-
-admin.site.register(FaucetRequest, FaucetRequestAdmin)
diff --git a/app/faucet/management/__init__.py b/app/faucet/management/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/app/faucet/management/commands/__init__.py b/app/faucet/management/commands/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/app/faucet/migrations/0001_initial.py b/app/faucet/migrations/0001_initial.py
deleted file mode 100644
index 65e3311d223..00000000000
--- a/app/faucet/migrations/0001_initial.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Generated by Django 2.1.4 on 2018-12-26 17:16
-
-import django.contrib.postgres.fields.jsonb
-from django.db import migrations, models
-import django.db.models.deletion
-import economy.models
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- ('dashboard', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='FaucetRequest',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_on', models.DateTimeField(db_index=True, default=economy.models.get_time)),
- ('modified_on', models.DateTimeField(default=economy.models.get_time)),
- ('fulfilled', models.BooleanField(default=False)),
- ('rejected', models.BooleanField(default=False)),
- ('github_username', models.CharField(db_index=True, max_length=255)),
- ('github_meta', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
- ('address', models.CharField(max_length=50)),
- ('email', models.CharField(max_length=255)),
- ('comment', models.TextField(blank=True, max_length=500)),
- ('comment_admin', models.TextField(blank=True, max_length=500)),
- ('fulfill_date', models.DateTimeField(blank=True, null=True)),
- ('amount', models.FloatField(default=0.00025)),
- ('profile', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='faucet_requests', to='dashboard.Profile')),
- ],
- options={
- 'abstract': False,
- },
- ),
- ]
diff --git a/app/faucet/migrations/__init__.py b/app/faucet/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/app/faucet/models.py b/app/faucet/models.py
deleted file mode 100644
index d79a078e42c..00000000000
--- a/app/faucet/models.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Define faucet related models.
-
-Copyright (C) 2021 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 .
-
-"""
-from __future__ import unicode_literals
-
-from django.conf import settings
-from django.contrib.postgres.fields import JSONField
-from django.db import models
-
-from economy.models import SuperModel
-
-
-class FaucetRequestManager(models.Manager):
- """Define the Faucet Request query manager."""
-
- def user(self, profile):
- """Fetch the FaucetRequests matching the provided profile.
-
- Args:
- profile (str): The Github username.
-
- Returns:
- QuerySet: The filtered FaucetRequest results.
-
- """
- return self.select_related('profile').filter(profile__username=profile)
-
-
-class FaucetRequest(SuperModel):
- """Define the Faucet Request model."""
-
- fulfilled = models.BooleanField(default=False)
- rejected = models.BooleanField(default=False)
- github_username = models.CharField(max_length=255, db_index=True)
- github_meta = JSONField(default=dict, blank=True)
- address = models.CharField(max_length=50)
- email = models.CharField(max_length=255)
- comment = models.TextField(max_length=500, blank=True)
- comment_admin = models.TextField(max_length=500, blank=True)
- fulfill_date = models.DateTimeField(null=True, blank=True)
- amount = models.FloatField(default=.00025)
- profile = models.ForeignKey(
- 'dashboard.Profile',
- null=True,
- on_delete=models.SET_NULL,
- related_name='faucet_requests',
- )
-
- objects = FaucetRequestManager()
-
- def __str__(self):
- """Return the string representation of FaucetRequest."""
- return f"{self.github_username} / {self.created_on}"
diff --git a/app/faucet/templates/bulk_email.html b/app/faucet/templates/bulk_email.html
deleted file mode 100644
index 353c6bebd81..00000000000
--- a/app/faucet/templates/bulk_email.html
+++ /dev/null
@@ -1,65 +0,0 @@
-{% extends "admin/base_site.html" %}
-{% comment %}
- Copyright (C) 2021 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 .
-
-{% endcomment %}
-
-{% load i18n static bundle %}
-
-{% block extrastyle %}{{ block.super }}
- {% bundle css file admin_dashboard_bootstrap %}
-
-
- {% endbundle %}
-{% endblock %}
-
-{% block coltype %}colMS{% endblock %}
-
-{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
-
-{% block breadcrumbs %}{% endblock %}
-
-{% block content %}
-
-
-
{% trans "Send Bulk Email" %} {{obj.pk}}
-
-
-
-
-
-
-
-{% endblock %}
-{% block extrahead %}{{ block.super }}
- {% include 'shared/footer_scripts.html' %}
-{% endblock %}
diff --git a/app/faucet/templates/faucet_form.html b/app/faucet/templates/faucet_form.html
deleted file mode 100644
index ef4c304da93..00000000000
--- a/app/faucet/templates/faucet_form.html
+++ /dev/null
@@ -1,129 +0,0 @@
-{% comment %}
- Copyright (C) 2021 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 .
-{% endcomment %}
-{% load i18n static %}
-
-
-
-
-
- {% include 'shared/head.html' %}
- {% include 'shared/cards.html' %}
-
-
-
-
- {% include 'shared/top_nav.html' with class='d-md-flex' %}
- {% include 'shared/nav.html' %}
-
-
-
-
- {% include 'shared/no_metamask_error.html' %}
- {% include 'shared/unlock_metamask.html' %}
- {% include 'shared/connect_metamask.html' %}
- {% include 'shared/faucet_over_balance_error.html' %}
-
-
-
-
{% trans "Faucet Request" %} {% trans "Alpha" %}
-
- {% blocktrans %}
- Pursuant to making Ethereum and Gitcoin broadly accessible to everyone Gitcoin
- may provide a minimal faucet distribution of ether so you may easily
- claim issues and gain more ether.
- {% endblocktrans %}
-
-
-
- {% include 'svgs/gas.svg' %}
-
-
-
-
- {% trans "This form requires human review (thanks to automated bots that have tried to abuse the system!). Please allow up to 1-2 business days for review. " %}
-
-
- All input fields are required.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{% trans "Request Received" %}
- {% include 'svgs/success.svg' %}
-
-
{% trans "Your request for a faucet distribution has been received." %}
-
{% trans "The Gitcoin team will review your request shortly." %}
-
-
-
-
-
{% trans "There was an error" %}:
-
-
- {% trans "Something went wrong submitting your request" %}
-
-
-
-
-
- {% include 'shared/newsletter.html' %}
-
-
-
-
- {% include 'shared/footer.html' %}
- {% include 'shared/footer_scripts.html' %}
-
-
- {% csrf_token %}
-
-
-
-
-
-
-
-
-
diff --git a/app/faucet/templates/process_faucet_request.html b/app/faucet/templates/process_faucet_request.html
deleted file mode 100644
index 38fe8c6d899..00000000000
--- a/app/faucet/templates/process_faucet_request.html
+++ /dev/null
@@ -1,129 +0,0 @@
-{% extends "admin/base_site.html" %}
-{% comment %}
- Copyright (C) 2021 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 .
-
-{% endcomment %}
-
-{% load i18n static bundle %}
-
-{% block extrastyle %}{{ block.super }}
- {% bundle css file admin_dashboard %}
-
- {% endbundle %}
-
-
-{% endblock %}
-{% block coltype %}colMS{% endblock %}
-
-{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
-
-{% block breadcrumbs %}{% endblock %}
-
-{% block content %}
-
-
-
{% trans "Process Faucet Request" %} {{obj.pk}}
-
- {% trans "Github Profile" %}: @{{obj.github_username}}
-
- {% trans "Comments" %} {{obj.comment}}
-
-
{% trans "MetaMask Extension Not Detected" %}
-
{% trans "Please Unlock MetaMask To Continue" %}
-
{% trans "Active Web3 Wallet Account Is Empty" %}
-
-
-
-
- {% trans "Transaction Failed" %} :
-
-
-
- {{pk}}
-
-
-
-
-
-
-
-
Reject Faucet Request For {{obj.github_username}}
- {% for rejection_reason in common_rejection_reasons %}
-
- {% endfor %}
-
-
-
-{% endblock %}
-{% block extrahead %}{{ block.super }}
-
- {% include 'shared/footer_scripts.html' %}
-
-
-
-{% endblock %}
diff --git a/app/faucet/templates/shared/faucet_over_balance_error.html b/app/faucet/templates/shared/faucet_over_balance_error.html
deleted file mode 100644
index fe892a6fc03..00000000000
--- a/app/faucet/templates/shared/faucet_over_balance_error.html
+++ /dev/null
@@ -1,40 +0,0 @@
-{% comment %}
- Copyright (C) 2021 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 .
-{% endcomment %}
-{% load i18n static %}
-
-
-
-
{% trans "You Appear to Have Ether!" %}
-
- {% trans "Thank you for your interest in receiving a faucet distribution." %}
- {% trans "You have more ether currently in your wallet than the distribution would provide. Please use that ether to pay the transaction cost." %}
-
-
-
-
-
-
-
-
diff --git a/app/faucet/views.py b/app/faucet/views.py
deleted file mode 100644
index e5ab88d7adc..00000000000
--- a/app/faucet/views.py
+++ /dev/null
@@ -1,149 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Define faucet views.
-
-Copyright (C) 2021 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 .
-
-"""
-from django.conf import settings
-from django.contrib import messages
-from django.contrib.admin.views.decorators import staff_member_required
-from django.core.validators import validate_email, validate_slug
-from django.http import Http404, JsonResponse
-from django.shortcuts import redirect
-from django.template.response import TemplateResponse
-from django.utils import timezone
-from django.utils.html import escape, strip_tags
-from django.utils.translation import gettext_lazy as _
-from django.views.decorators.csrf import csrf_exempt
-from django.views.decorators.http import require_GET, require_http_methods, require_POST
-
-from faucet.models import FaucetRequest
-from gas.utils import recommend_min_gas_price_to_confirm_in_time
-from marketing.mails import new_faucet_request, processed_faucet_request, reject_faucet_request
-
-
-@require_GET
-def faucet(request):
- params = {
- 'title': 'Faucet',
- 'card_title': _('Gitcoin Faucet'),
- 'card_desc': _('Request a distribution of ETH so you can use the Ethereum network and Gitcoin.'),
- 'faucet_amount': settings.FAUCET_AMOUNT
- }
-
- return TemplateResponse(request, 'faucet_form.html', params)
-
-
-@csrf_exempt
-@require_POST
-def save_faucet(request):
- """Handle saving faucet requests."""
- email_address = request.POST.get('emailAddress')
- eth_address = request.POST.get('ethAddress')
- is_authenticated = request.user.is_authenticated
- profile = request.user.profile if is_authenticated and hasattr(request.user, 'profile') else None
-
- if not profile:
- return JsonResponse({
- 'message': _('You must be authenticated via github to use this feature!')
- }, status=401)
-
- try:
- validate_slug(eth_address)
- if email_address:
- validate_email(email_address)
- except Exception as e:
- return JsonResponse({'message': str(e)}, status=400)
-
- comment = escape(strip_tags(request.POST.get('comment', '')))
- if not profile.trust_profile and profile.github_created_on > (timezone.now() - timezone.timedelta(days=7)):
- return JsonResponse({
- 'message': _('For SPAM prevention reasons, you may not perform this action right now. Please contact support if you believe this message is in error.')
- }, status=403)
- if profile.faucet_requests.filter(fulfilled=True):
- return JsonResponse({
- 'message': _('The submitted github profile shows a previous faucet distribution.')
- }, status=403)
- elif profile.faucet_requests.filter(rejected=False):
- return JsonResponse({
- 'message': _('The submitted github profile shows a pending faucet distribution.')
- }, status=403)
- fr = FaucetRequest.objects.create(
- fulfilled=False,
- github_username=request.user.username,
- github_meta={},
- address=eth_address,
- email=email_address if email_address else request.user.email,
- comment=comment,
- profile=profile,
- )
- new_faucet_request(fr)
-
- return JsonResponse({'message': _('Created.')}, status=201)
-
-
-@require_http_methods(['GET', 'POST'])
-@staff_member_required
-def process_faucet_request(request, pk):
- try:
- faucet_request = FaucetRequest.objects.get(pk=pk)
- except FaucetRequest.DoesNotExist:
- raise Http404
-
- redir_link = '/_administrationfaucet/faucetrequest/?fulfilled=f&rejected=f'
- faucet_amount = float(settings.FAUCET_AMOUNT) * float(recommend_min_gas_price_to_confirm_in_time(5))
-
- if faucet_request.fulfilled:
- messages.info(request, 'already fulfilled')
- return redirect(redir_link)
-
- if faucet_request.rejected:
- messages.info(request, 'already rejected')
- return redirect(redir_link)
-
- reject_comments = request.POST.get('reject_comments')
- if reject_comments:
- faucet_request.comment_admin = reject_comments
- faucet_request.rejected = True
- faucet_request.save()
- reject_faucet_request(faucet_request)
- messages.success(request, 'rejected')
- return redirect(redir_link)
-
- if request.POST.get('destinationAccount'):
- faucet_request.fulfilled = True
- faucet_request.fulfill_date = timezone.now()
- faucet_request.amount = faucet_amount
- faucet_request.save()
- processed_faucet_request(faucet_request)
- messages.success(request, 'sent')
- return redirect(redir_link)
-
- common_rejection_reasons = [
- "Please tell us what you're planning on using these funds for in the comments section! Thanks.",
- "This is a faucet for Gitcoin-specific functionality (like posting Bounties or fulfilling Bounties). Please re-submit your request if you need to do something Gitcoin specific.",
- "You don't need ETH to start work on a bounty. Please submit another request if you finish your work and need to submit work.",
- "",
- ]
-
- context = {
- 'obj': faucet_request,
- 'faucet_amount': faucet_amount,
- 'recommend_gas_price': round(recommend_min_gas_price_to_confirm_in_time(1), 1),
- 'common_rejection_reasons': common_rejection_reasons,
- }
-
- return TemplateResponse(request, 'process_faucet_request.html', context)
diff --git a/app/grants/admin.py b/app/grants/admin.py
index 76d594efde7..a9efa9b8f06 100644
--- a/app/grants/admin.py
+++ b/app/grants/admin.py
@@ -104,7 +104,7 @@ class GrantAdmin(GeneralAdmin):
'active', 'visible', 'is_clr_eligible',
'migrated_to', 'region',
'grant_type', 'categories', 'description', 'description_rich', 'github_project_url', 'reference_url', 'admin_address',
- 'amount_received', 'amount_received_in_round', 'monthly_amount_subscribed',
+ 'amount_received', 'amount_received_in_round', 'monthly_amount_subscribed', 'defer_clr_to',
'deploy_tx_id', 'cancel_tx_id', 'admin_profile', 'token_symbol',
'token_address', 'contract_address', 'contract_version', 'network', 'required_gas_price', 'logo_svg_asset',
'logo_asset', 'created_on', 'modified_on', 'team_member_list',
@@ -116,7 +116,7 @@ class GrantAdmin(GeneralAdmin):
'polkadot_payout_address', 'kusama_payout_address', 'rsk_payout_address', 'algorand_payout_address', 'emails', 'admin_message', 'has_external_funding'
]
readonly_fields = [
- 'logo_svg_asset', 'logo_asset',
+ 'defer_clr_to', 'logo_svg_asset', 'logo_asset',
'team_member_list', 'clr_prediction_curve',
'subscriptions_links', 'contributions_links', 'link',
'migrated_to', 'view_count', 'in_active_clrs', 'stats_history',
diff --git a/app/grants/clr.py b/app/grants/clr.py
index 5be90eb2fa8..d7968bea8fc 100644
--- a/app/grants/clr.py
+++ b/app/grants/clr.py
@@ -415,7 +415,12 @@ def predict_clr(save_to_db=False, from_date=None, clr_round=None, network='mainn
print(f"- starting slim grant calc at {round(time.time(),1)}")
grants_clr = run_clr_calcs(grant_contributions_curr, v_threshold, uv_threshold, total_pot)
print(f"- saving slim grant calc at {round(time.time(),1)}")
+ total_count = len(grants_clr.items())
for grant_calc in grants_clr:
+ counter += 1
+ if counter % 10 == 0 or True:
+ print(f"- {counter}/{total_count} grants iter, pk:{grant_id}, at {round(time.time(),1)}")
+
pk = grant_calc['id']
grant = clr_round.grants.using('default').get(pk=pk)
latest_calc = grant.clr_calculations.using('default').filter(latest=True, grantclr=clr_round).order_by('-pk').first()
@@ -424,10 +429,12 @@ def predict_clr(save_to_db=False, from_date=None, clr_round=None, network='mainn
continue
clr_prediction_curve = copy.deepcopy(latest_calc.clr_prediction_curve)
clr_prediction_curve[0][1] = grant_calc['clr_amount'] # update only the existing match estimate
+ print(clr_prediction_curve)
clr_round.record_clr_prediction_curve(grant, clr_prediction_curve)
grant.save()
# if we are only calculating slim CLR calculations, return here and save 97% compute power
print(f"- done calculating at {round(time.time(),1)}")
+ print(f"\nTotal execution time: {(timezone.now() - clr_calc_start_time)}")
return
print(f"- starting grants iter at {round(time.time(),1)}")
@@ -460,7 +467,6 @@ def predict_clr(save_to_db=False, from_date=None, clr_round=None, network='mainn
else:
for amount in potential_donations:
# calculate clr with each additional donation and save to grants model
- # print(f'using {total_pot_close}')
predicted_clr, grants_clr, _, _ = calculate_clr_for_donation(
grant, amount, grant_contributions_curr, total_pot, v_threshold, uv_threshold
)
@@ -480,6 +486,8 @@ def predict_clr(save_to_db=False, from_date=None, clr_round=None, network='mainn
else:
clr_prediction_curve = [[0.0, 0.0, 0.0] for x in range(0, 6)]
+ print(clr_prediction_curve)
+
clr_round.record_clr_prediction_curve(_grant, clr_prediction_curve)
if from_date > (clr_calc_start_time - timezone.timedelta(hours=1)):
@@ -487,4 +495,6 @@ def predict_clr(save_to_db=False, from_date=None, clr_round=None, network='mainn
debug_output.append({'grant': grant.id, "clr_prediction_curve": (potential_donations, potential_clr), "grants_clr": grants_clr})
+ print(f"\nTotal execution time: {(timezone.now() - clr_calc_start_time)}")
+
return debug_output
diff --git a/app/grants/clr2.py b/app/grants/clr2.py
new file mode 100644
index 00000000000..7a9bf054945
--- /dev/null
+++ b/app/grants/clr2.py
@@ -0,0 +1,330 @@
+# -*- coding: utf-8 -*-
+"""Define the Grants application configuration.
+
+Copyright (C) 2021 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 copy
+
+from django.db import connection
+from django.utils import timezone
+
+import numpy as np
+from dashboard.models import Profile
+from grants.models import Grant, GrantCollection
+from townsquare.models import SquelchProfile
+
+CLR_PERCENTAGE_DISTRIBUTED = 0
+
+
+def get_summed_contribs_query(grants, created_after, created_before, multiplier, network):
+ # only consider contribs from current grant set
+ grantIds = ''
+ for i in range(len(grants)):
+ grantIds += "'" + str(grants[i].id) + "'" + (', ' if i+1 != len(grants) else '')
+
+ summedContribs = f'''
+ -- drop the table if it exists
+ DROP TABLE IF EXISTS tempUserTotals;
+
+ -- group by ... sum the contributions $ value for each user
+ SELECT
+ grants.use_grant_id as grant_id,
+ grants_contribution.profile_for_clr_id as user_id,
+ SUM((grants_contribution.normalized_data ->> 'amount_per_period_usdt')::FLOAT * {float(multiplier)}),
+ MAX(dashboard_profile.as_dict ->> 'trust_bonus')::FLOAT as trust_bonus
+ INTO TEMP TABLE tempUserTotals
+ FROM grants_contribution
+ INNER JOIN dashboard_profile ON (grants_contribution.profile_for_clr_id = dashboard_profile.id)
+ INNER JOIN grants_subscription ON (grants_contribution.subscription_id = grants_subscription.id)
+ RIGHT JOIN (
+ SELECT
+ grants_grant.id as grant_id,
+ (
+ CASE
+ WHEN grants_grant.defer_clr_to_id IS NOT NULL THEN grants_grant.defer_clr_to_id
+ ELSE grants_grant.id
+ END
+ ) as use_grant_id
+ FROM grants_grant
+ ) grants ON ((grants_contribution.normalized_data ->> 'id')::FLOAT = grants.grant_id)
+ WHERE (
+ grants_contribution.normalized_data ->> 'id' IN ({grantIds}) AND
+ grants_contribution.created_on >= '{created_after}' AND
+ grants_contribution.created_on <= '{created_before}' AND
+ grants_contribution.match = True AND
+ grants_subscription.network = '{network}' AND
+ grants_contribution.success = True AND
+ (grants_contribution.normalized_data ->> 'amount_per_period_usdt')::FLOAT >= 0 AND
+ NOT (
+ grants_contribution.profile_for_clr_id IN (
+ SELECT squelched.profile_id FROM townsquare_squelchprofile squelched WHERE squelched.active = True
+ ) AND grants_contribution.profile_for_clr_id IS NOT NULL
+ )
+ )
+ GROUP BY grants.use_grant_id, grants_contribution.profile_for_clr_id;
+
+ -- index before joining in clr_query
+ CREATE INDEX ON tempUserTotals (grant_id, user_id);
+
+ SELECT * FROM tempUserTotals;
+ '''
+
+ return summedContribs
+
+
+def add_prediction_contrib_query(grant_id, amount):
+ predictionContrib = f'''
+ -- delete any previous prediction values from the contributions table
+ DELETE FROM tempUserTotals WHERE user_id = 999999999;
+
+ -- insert the prediction value into contributions (grant_id, user_id, amount, trust_bonus)
+ {"INSERT INTO tempUserTotals VALUES(" + str(grant_id) + ", 999999999, " + str(amount) + ", 1);" if amount != 0 else ""}
+ '''
+ return predictionContrib
+
+
+def get_calc_query(v_threshold):
+ pairwise = '''
+ -- produce the pairwise sums
+ SELECT
+ c1.user_id,
+ c2.user_id as user_id_2,
+ SUM((c1.sum * c2.sum) ^ 0.5) pairwise
+ FROM tempUserTotals c1
+ INNER JOIN tempUserTotals c2 ON (c1.grant_id = c2.grant_id AND c2.user_id > c1.user_id)
+ GROUP BY c1.user_id, c2.user_id
+ '''
+
+ clrAmount = f'''
+ -- calculate the CLR amount for each grant
+ SELECT
+ c1.grant_id,
+ -- add trust scores and threshold here
+ SUM((c1.sum * c2.sum) ^ 0.5 / (pw.pairwise / ({v_threshold} * GREATEST(c2.trust_bonus, c1.trust_bonus)) + 1)) final_clr
+ FROM tempUserTotals c1
+ INNER JOIN tempUserTotals c2 ON (c1.grant_id = c2.grant_id AND c2.user_id > c1.user_id)
+ INNER JOIN ({pairwise}) pw ON (c1.user_id = pw.user_id AND c2.user_id = pw.user_id_2)
+ GROUP BY c1.grant_id
+ ORDER BY c1.grant_id;
+ '''
+
+ # # CTE of pairwise, clrAmount and clrResult (this will return the clr_amount, number_contribtions and contribution_amount for each grant)
+ # clrResult = f'''
+ # -- group by ... sum the contributions $ value for each grant and place the clr
+ # SELECT
+ # c1.grant_id,
+ # -- use MAX/MIN because we know we will only match a single CLR here
+ # MAX(clr.final_clr) clr_amount,
+ # SUM(1) number_contributions,
+ # SUM(c1.sum) contribution_amount
+ # FROM tempUserTotals c1
+ # INNER JOIN ({clrAmount}) clr ON (c1.grant_id = clr.grant_id)
+ # GROUP BY c1.grant_id;
+ # '''
+
+ return clrAmount
+
+
+def fetch_grants(clr_round, network='mainnet'):
+ grant_filters = clr_round.grant_filters
+ collection_filters = clr_round.collection_filters
+
+ grants = clr_round.grants.filter(network=network, hidden=False, active=True, is_clr_eligible=True, link_to_new_grant=None)
+
+ if grant_filters:
+ # Grant Filters (grant_type, category)
+ grants = grants.filter(**grant_filters)
+ elif collection_filters:
+ # Collection Filters
+ grant_ids = GrantCollection.objects.filter(**collection_filters).values_list('grants', flat=True)
+ grants = grants.filter(pk__in=grant_ids)
+
+ return grants
+
+
+def calculate_clr_for_donation(grant_id, amount, cursor, total_pot, v_threshold):
+ # collect results
+ bigtot = 0
+ totals = {}
+
+ # find grant in contributions list and add donation
+ clr_query = add_prediction_contrib_query(grant_id, amount) + get_calc_query(v_threshold)
+ cursor.execute(clr_query)
+ for _row in cursor.fetchall():
+ bigtot += _row[1] if _row[1] else 0
+ totals[_row[0]] = {'clr_amount': _row[1]}
+
+ global CLR_PERCENTAGE_DISTRIBUTED
+
+ # check if saturation is reached
+ if bigtot >= total_pot: # saturation reached
+ # print(f'saturation reached. Total Pot: ${total_pot} | Total Allocated ${bigtot}. Normalizing')
+ CLR_PERCENTAGE_DISTRIBUTED = 100
+ for pk, grant_calc in totals.items():
+ grant_calc['clr_amount'] = ((grant_calc['clr_amount'] / bigtot) * total_pot)
+ else:
+ CLR_PERCENTAGE_DISTRIBUTED = (bigtot / total_pot) * 100
+ if bigtot == 0:
+ bigtot = 1
+ percentage_increase = np.log(total_pot / bigtot) / 100
+ for pk, grant_calc in totals.items():
+ grant_calc['clr_amount'] = grant_calc['clr_amount'] * (1 + percentage_increase) if grant_calc['clr_amount'] else 0
+
+ # find grant we added the contribution to and get the new clr amount
+ if grant_id and totals.get(grant_id):
+ clr = totals[grant_id]
+ return (
+ clr,
+ clr['clr_amount']
+ )
+
+ # print(f'info: no contributions found for grant {grant_id}')
+ return (totals, 0.0)
+
+
+def predict_clr(save_to_db=False, from_date=None, clr_round=None, network='mainnet', only_grant_pk=None, what='full'):
+ import time
+
+ # setup
+ clr_calc_start_time = timezone.now()
+ debug_output = []
+
+ # one-time data call
+ total_pot = float(clr_round.total_pot)
+ v_threshold = float(clr_round.verified_threshold)
+
+ print(f"- starting fetch_grants at {round(time.time(),1)}")
+ grants = fetch_grants(clr_round, network)
+
+ # override the grants list to the one selected
+ if only_grant_pk:
+ grants = grants.filter(pk=only_grant_pk)
+
+ print(f"- starting get data and sum at {round(time.time(),1)}")
+ # collect contributions for clr_round into temp table
+ initial_query = get_summed_contribs_query(grants, clr_round.start_date, clr_round.end_date, clr_round.contribution_multiplier, network)
+ # open cursor and execute the groupBy sum for the round
+ with connection.cursor() as cursor:
+ counter = 0
+ curr_agg = {}
+ # execute to populate shared state for the round
+ cursor.execute(initial_query) # (we could potential do better here by sharing this temp table between rounds)
+ for _row in cursor.fetchall():
+ if not curr_agg.get(_row[0]):
+ curr_agg[_row[0]] = {}
+ curr_agg[_row[0]][_row[1]] = _row[2]
+
+ if len(curr_agg) == 0:
+ print(f'- done - No Contributions for CLR {clr_round.round_num}. Exiting')
+ print(f"\nTotal execution time: {(timezone.now() - clr_calc_start_time)}\n")
+ return
+
+ print(f"- starting current grant calc (free of predictions) at {round(time.time(),1)}")
+ curr_grants_clr, _ = calculate_clr_for_donation(
+ None, 0, cursor, total_pot, v_threshold
+ )
+
+ if what == 'slim':
+ # if we are only calculating slim CLR calculations, return here and save 97% compute power
+ print(f"- saving slim grant calc at {round(time.time(),1)}")
+ total_count = len(curr_grants_clr.items())
+ for grant_id, grant_calc in curr_grants_clr.items():
+ counter += 1
+ if counter % 10 == 0 or True:
+ print(f"- {counter}/{total_count} grants iter, pk:{grant_id}, at {round(time.time(),1)}")
+
+ # update latest calcs with current distribution
+ grant = clr_round.grants.using('default').get(pk=grant_id)
+ latest_calc = grant.clr_calculations.using('default').filter(latest=True, grantclr=clr_round).order_by('-pk').first()
+ if not latest_calc:
+ print(f"- - could not find latest clr calc for {grant_id} ")
+ continue
+ clr_prediction_curve = copy.deepcopy(latest_calc.clr_prediction_curve)
+ clr_prediction_curve[0][1] = grant_calc['clr_amount'] if grant_calc else 0.0 # update only the existing match estimate
+ print(clr_prediction_curve)
+ clr_round.record_clr_prediction_curve(grant, clr_prediction_curve)
+ grant.save()
+ # if we are only calculating slim CLR calculations, return here and save 97% compute power
+ print(f"- done calculating at {round(time.time(),1)}")
+ print(f"\nTotal execution time: {(timezone.now() - clr_calc_start_time)}\n")
+ return
+
+ # calculate clr given additional donations
+ total_count = grants.count()
+
+ print(f"- starting grants iter at {round(time.time(),1)}")
+ # calculate each grant as a distinct input
+ for grant in grants:
+ # five potential additional donations plus the base case of 0
+ potential_donations = [0, 1, 10, 100, 1000, 10000]
+
+ # debug the run...
+ counter += 1
+ if counter % 10 == 0 or True:
+ print(f"- {counter}/{total_count} grants iter, pk:{grant.id}, at {round(time.time(),1)}")
+
+ # if no contributions have been made for this grant then the pairwise will fail and no match will be discovered
+ if not curr_agg.get(grant.id):
+ grants_clr = None
+ potential_clr = [0.0 for x in range(0, 6)]
+ else:
+ potential_clr = []
+ for amount in potential_donations:
+ # no need to run the calculation multiple times for amount=0 (will always be the same result)
+ if amount == 0:
+ # use the current distribution calc
+ grants_clr = curr_grants_clr.get(grant.id)
+ predicted_clr = grants_clr['clr_amount'] if grants_clr else 0.0
+ else:
+ # this is used when you want to count final distribution and ignore the prediction
+ if what == 'final':
+ # ignore the other ones
+ grants_clr = None
+ predicted_clr = 0.0
+ else:
+ # calculate clr with each additional donation
+ grants_clr, predicted_clr = calculate_clr_for_donation(
+ grant.id, amount, cursor, total_pot, v_threshold
+ )
+ # record each point of the predicition
+ potential_clr.append(predicted_clr)
+
+ # save the result of the prediction
+ if save_to_db:
+ _grant = Grant.objects.get(pk=grant.id)
+ clr_prediction_curve = list(zip(potential_donations, potential_clr))
+ base = clr_prediction_curve[0][1]
+ _grant.last_clr_calc_date = timezone.now()
+ _grant.next_clr_calc_date = timezone.now() + timezone.timedelta(minutes=60)
+
+ # check that we have enough data to set the curve
+ can_estimate = True if base or clr_prediction_curve[1][1] or clr_prediction_curve[2][1] or clr_prediction_curve[3][1] else False
+ if can_estimate:
+ clr_prediction_curve = [[ele[0], ele[1], ele[1] - base] for ele in clr_prediction_curve ]
+ else:
+ clr_prediction_curve = [[0.0, 0.0, 0.0] for x in range(0, 6)]
+ print(clr_prediction_curve)
+
+ # save the new predicition curve via the model
+ clr_round.record_clr_prediction_curve(_grant, clr_prediction_curve)
+ _grant.save()
+
+ debug_output.append({'grant': grant.id, "clr_prediction_curve": (potential_donations, potential_clr), "grants_clr": grants_clr})
+
+ print(f"\nTotal execution time: {(timezone.now() - clr_calc_start_time)}\n")
+
+ return debug_output
diff --git a/app/grants/clr3.py b/app/grants/clr3.py
new file mode 100644
index 00000000000..27d821beebe
--- /dev/null
+++ b/app/grants/clr3.py
@@ -0,0 +1,412 @@
+# -*- coding: utf-8 -*-
+"""Define the Grants application configuration.
+
+Copyright (C) 2021 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 copy
+
+from django.db import connection
+from django.utils import timezone
+
+import numpy as np
+from grants.models import Contribution, Grant, GrantCollection
+from townsquare.models import SquelchProfile
+
+CLR_PERCENTAGE_DISTRIBUTED = 0
+
+
+def fetch_grants(clr_round, network='mainnet'):
+ grant_filters = clr_round.grant_filters
+ collection_filters = clr_round.collection_filters
+
+ grants = clr_round.grants.filter(network=network, hidden=False, active=True, is_clr_eligible=True, link_to_new_grant=None)
+
+ if grant_filters:
+ # Grant Filters (grant_type, category)
+ grants = grants.filter(**grant_filters)
+ elif collection_filters:
+ # Collection Filters
+ grant_ids = GrantCollection.objects.filter(**collection_filters).values_list('grants', flat=True)
+ grants = grants.filter(pk__in=grant_ids)
+
+ return grants
+
+
+def get_summed_contribs_query(grants, created_after, created_before, multiplier, network):
+ # only consider contribs from current grant set
+ grantIds = ''
+ for i in range(len(grants)):
+ grantIds += "'" + str(grants[i].id) + "'" + (', ' if i+1 != len(grants) else '')
+
+ summedContribs = f'''
+ -- group by ... sum the contributions $ value for each user
+ SELECT
+ grants.use_grant_id as grant_id,
+ grants_contribution.profile_for_clr_id as user_id,
+ SUM((grants_contribution.normalized_data ->> 'amount_per_period_usdt')::FLOAT * {float(multiplier)}),
+ MAX(dashboard_profile.as_dict ->> 'trust_bonus')::FLOAT as trust_bonus
+ FROM grants_contribution
+ INNER JOIN dashboard_profile ON (grants_contribution.profile_for_clr_id = dashboard_profile.id)
+ INNER JOIN grants_subscription ON (grants_contribution.subscription_id = grants_subscription.id)
+ RIGHT JOIN (
+ SELECT
+ grants_grant.id as grant_id,
+ (
+ CASE
+ WHEN grants_grant.defer_clr_to_id IS NOT NULL THEN grants_grant.defer_clr_to_id
+ ELSE grants_grant.id
+ END
+ ) as use_grant_id
+ FROM grants_grant
+ ) grants ON ((grants_contribution.normalized_data ->> 'id')::FLOAT = grants.grant_id)
+ WHERE (
+ grants_contribution.normalized_data ->> 'id' IN ({grantIds}) AND
+ grants_contribution.created_on >= '{created_after}' AND
+ grants_contribution.created_on <= '{created_before}' AND
+ grants_contribution.match = True AND
+ grants_subscription.network = '{network}' AND
+ grants_contribution.success = True AND
+ (grants_contribution.normalized_data ->> 'amount_per_period_usdt')::FLOAT >= 0 AND
+ NOT (
+ grants_contribution.profile_for_clr_id IN (
+ SELECT squelched.profile_id FROM townsquare_squelchprofile squelched WHERE squelched.active = True
+ ) AND grants_contribution.profile_for_clr_id IS NOT NULL
+ )
+ )
+ GROUP BY grants.use_grant_id, grants_contribution.profile_for_clr_id;
+ '''
+
+ return summedContribs
+
+
+def get_totals_by_pair(contrib_dict):
+ '''
+ gets pair totals between current round, current round
+
+ args:
+ aggregated contributions by pair nested dict
+ {
+ grant_id (str): {
+ user_id (str): aggregated_amount (float)
+ }
+ }
+
+ returns:
+ pair totals between current round
+ {user_id (str): {user_id (str): pair_total (float)}}
+
+ '''
+ tot_overlap = {}
+
+ # start pairwise match
+ for _, contribz in contrib_dict.items():
+ for k1, v1 in contribz.items():
+ if k1 not in tot_overlap:
+ tot_overlap[k1] = {}
+
+ # pairwise matches to current round
+ for k2, v2 in contribz.items():
+ if k2 not in tot_overlap[k1]:
+ tot_overlap[k1][k2] = 0
+ tot_overlap[k1][k2] += (v1 * v2) ** 0.5
+
+ return tot_overlap
+
+
+def calculate_clr(aggregated_contributions, pair_totals, trust_dict, v_threshold, total_pot):
+ '''
+ calculates the clr amount at the given threshold and total pot
+ args:
+ aggregated contributions by pair nested dict
+ {
+ grant_id (str): {
+ user_id (str): aggregated_amount (float)
+ }
+ }
+ pair_totals
+ {user_id (str): {user_id (str): pair_total (float)}}
+ trust_dict
+ {user_id (str): trust_score (float)}
+ v_threshold
+ float
+ total_pot
+ float
+
+ returns:
+ total clr award by grant, analytics, normalized by the normalization factor
+ [{'id': proj, 'number_contributions': _num, 'contribution_amount': _sum, 'clr_amount': tot}]
+ saturation point
+ boolean
+ '''
+
+ bigtot = 0
+ totals = {}
+
+ for proj, contribz in aggregated_contributions.items():
+ tot = 0
+ _num = 0
+ _sum = 0
+
+ # start pairwise matches
+ for k1, v1 in contribz.items():
+ _num += 1
+ _sum += v1
+
+ # pairwise matches to current round
+ for k2, v2 in contribz.items():
+ if int(k2) > int(k1):
+ tot += ((v1 * v2) ** 0.5) / (pair_totals[k1][k2] / (v_threshold * max(trust_dict[k2], trust_dict[k1])) + 1)
+
+ if type(tot) == complex:
+ tot = float(tot.real)
+
+ bigtot += tot
+ totals[proj] = {'number_contributions': _num, 'contribution_amount': _sum, 'clr_amount': tot}
+
+ global CLR_PERCENTAGE_DISTRIBUTED
+
+ if bigtot >= total_pot: # saturation reached
+ # print(f'saturation reached. Total Pot: ${total_pot} | Total Allocated ${bigtot}. Normalizing')
+ CLR_PERCENTAGE_DISTRIBUTED = 100
+ for key, t in totals.items():
+ t['clr_amount'] = ((t['clr_amount'] / bigtot) * total_pot)
+ else:
+ CLR_PERCENTAGE_DISTRIBUTED = (bigtot / total_pot) * 100
+ if bigtot == 0:
+ bigtot = 1
+ percentage_increase = np.log(total_pot / bigtot) / 100
+ for key, t in totals.items():
+ t['clr_amount'] = t['clr_amount'] * (1 + percentage_increase)
+ return totals
+
+
+def run_clr_calcs(curr_agg, trust_dict, v_threshold, total_pot):
+ '''
+ clubbed function that runs all calculation functions
+
+ args:
+ curr_agg :
+ {
+ grantId (int): {
+ profileId (str): amount (float)
+ }
+ }
+ trust_dict :
+ {
+ profileId (str): trust_bonus (float)
+ }
+ v_threshold : float
+ total_pot : float
+
+ returns:
+ grants clr award amounts (dict)
+ '''
+
+ # get pair totals
+ ptots = get_totals_by_pair(curr_agg)
+
+ # clr calcluation
+ totals = calculate_clr(curr_agg, ptots, trust_dict, v_threshold, total_pot)
+
+ return totals
+
+
+def calculate_clr_for_donation(curr_agg, trust_dict, grant_id, amount, v_threshold, total_pot):
+ '''
+ clubbed function that runs all calculation functions and returns the result for a single grant_id
+
+ args:
+ curr_agg :
+ {
+ grantId (int): {
+ profileId (str): amount (float)
+ }
+ }
+ trust_dict :
+ {
+ profileId (str): trust_bonus (float)
+ }
+ grant_id ; int
+ amount ; int
+ v_threshold : float
+ total_pot : float
+
+ returns:
+ (grant clr award amounts (dict), clr_amount (float), number_contributions (int), contribution_amount (float))
+ '''
+
+ # make sure contributions exist
+ if curr_agg.get(grant_id) or not grant_id:
+ # find grant in contributions list and add donation
+ if amount:
+ trust_dict['999999999999'] = 1
+ curr_agg[grant_id]['999999999999'] = amount
+
+ grants_clr = run_clr_calcs(curr_agg, trust_dict, v_threshold, total_pot)
+
+ # find grant we added the contribution to and get the new clr amount
+ if grants_clr.get(grant_id):
+ grant_clr = grants_clr.get(grant_id)
+ return (
+ grant_clr,
+ grant_clr['clr_amount'],
+ grant_clr['number_contributions'],
+ grant_clr['contribution_amount']
+ )
+ else:
+ grants_clr = None
+
+ # print(f'info: no contributions found for grant {grant}')
+ return (grants_clr, 0.0, 0, 0.0)
+
+
+def predict_clr(save_to_db=False, from_date=None, clr_round=None, network='mainnet', only_grant_pk=None, what='full'):
+ import time
+
+ # setup
+ clr_calc_start_time = timezone.now()
+ debug_output = []
+
+ # one-time data call
+ total_pot = float(clr_round.total_pot)
+ v_threshold = float(clr_round.verified_threshold)
+
+ print(f"- starting fetch_grants at {round(time.time(),1)}")
+ # grants, contributions = fetch_data(clr_round, network)
+ grants = fetch_grants(clr_round, network)
+
+ if only_grant_pk:
+ grants = grants.filter(pk=only_grant_pk)
+
+ print(f"- starting get data and sum at {round(time.time(),1)}")
+ # collect contributions for clr_round into temp table
+ initial_query = get_summed_contribs_query(grants, clr_round.start_date, clr_round.end_date, clr_round.contribution_multiplier, network)
+ # open cursor and execute the groupBy sum for the round
+ with connection.cursor() as cursor:
+ counter = 0
+ curr_agg = {}
+ trust_dict = {}
+ # execute to populate shared state for the round
+ cursor.execute(initial_query) # (we could potential do better here by sharing this temp table between rounds)
+ for _row in cursor.fetchall():
+ if not curr_agg.get(_row[0]):
+ curr_agg[_row[0]] = {}
+ trust_dict[_row[1]] = _row[3]
+ curr_agg[_row[0]][_row[1]] = _row[2]
+
+ if len(curr_agg) == 0:
+ print(f'- done - no Contributions for CLR {clr_round.round_num}. Exiting')
+ print(f"\nTotal execution time: {(timezone.now() - clr_calc_start_time)}\n")
+ return
+
+ print(f"- starting current grant calc (free of predictions) at {round(time.time(),1)}")
+ curr_grants_clr = run_clr_calcs(curr_agg, trust_dict, v_threshold, total_pot)
+
+ if what == 'slim':
+ # if we are only calculating slim CLR calculations, return here and save 97% compute power
+ print(f"- saving slim grant calc at {round(time.time(),1)}")
+ total_count = len(curr_grants_clr.items())
+ for pk, grant_calc in curr_grants_clr.items():
+ counter += 1
+ if counter % 10 == 0 or True:
+ print(f"- {counter}/{total_count} grants iter, pk:{pk}, at {round(time.time(),1)}")
+
+ # update latest calcs with current distribution
+ grant_calc = curr_grants_clr[pk]
+ grant = clr_round.grants.using('default').get(pk=pk)
+ latest_calc = grant.clr_calculations.using('default').filter(latest=True, grantclr=clr_round).order_by('-pk').first()
+ if not latest_calc:
+ print(f"- - could not find latest clr calc for {grant.pk} ")
+ continue
+ clr_prediction_curve = copy.deepcopy(latest_calc.clr_prediction_curve)
+ clr_prediction_curve[0][1] = grant_calc['clr_amount'] # update only the existing match estimate
+ print(clr_prediction_curve)
+ clr_round.record_clr_prediction_curve(grant, clr_prediction_curve)
+ grant.save()
+ # if we are only calculating slim CLR calculations, return here and save 97% compute power
+ print(f"- done calculating at {round(time.time(),1)}")
+ print(f"\nTotal execution time: {(timezone.now() - clr_calc_start_time)}\n")
+ return
+
+ print(f"- starting grants iter at {round(time.time(),1)}")
+ # calculate clr given additional donations
+ total_count = grants.count()
+ for grant in grants:
+ # five potential additional donations plus the base case of 0
+ potential_donations = [0, 1, 10, 100, 1000, 10000]
+
+ # debug the run...
+ counter += 1
+ if counter % 10 == 0 or True:
+ print(f"- {counter}/{total_count} grants iter, pk:{grant.id}, at {round(time.time(),1)}")
+
+ # if no contributions have been made for this grant then the pairwise will fail and no match will be discovered
+ if not curr_agg.get(grant.id):
+ grants_clr = None
+ potential_clr = [0.0 for x in range(0, 6)]
+ else:
+ potential_clr = []
+ for amount in potential_donations:
+ # no need to run the calculation multiple times for amount=0 (will always be the same result)
+ if amount == 0:
+ # use the current distribution calc
+ grants_clr = curr_grants_clr.get(grant.id)
+ predicted_clr = grants_clr['clr_amount'] if grants_clr else 0.0
+ else:
+ # this is used when you want to count final distribution and ignore the prediction
+ if what == 'final':
+ # ignore the other ones
+ grants_clr = None
+ predicted_clr = 0.0
+ else:
+ # calculate clr with each additional donation
+ grants_clr, predicted_clr, _, _ = calculate_clr_for_donation(
+ curr_agg, trust_dict, grant.id, amount, v_threshold, total_pot
+ )
+
+ # reset potential_donations
+ if amount and curr_agg.get(grant.id):
+ del curr_agg[grant.id]['999999999999']
+
+ # record each point of the predicition
+ potential_clr.append(predicted_clr)
+
+ # save the result of the prediction
+ if save_to_db:
+ _grant = Grant.objects.get(pk=grant.id)
+ clr_prediction_curve = list(zip(potential_donations, potential_clr))
+ base = clr_prediction_curve[0][1]
+ _grant.last_clr_calc_date = timezone.now()
+ _grant.next_clr_calc_date = timezone.now() + timezone.timedelta(minutes=60)
+
+ # check that we have enough data to set the curve
+ can_estimate = True if base or clr_prediction_curve[1][1] or clr_prediction_curve[2][1] or clr_prediction_curve[3][1] else False
+ if can_estimate:
+ clr_prediction_curve = [[ele[0], ele[1], ele[1] - base] for ele in clr_prediction_curve ]
+ else:
+ clr_prediction_curve = [[0.0, 0.0, 0.0] for x in range(0, 6)]
+ print(clr_prediction_curve)
+
+ # save the new predicition curve via the model
+ clr_round.record_clr_prediction_curve(_grant, clr_prediction_curve)
+ _grant.save()
+
+ debug_output.append({'grant': grant.id, "clr_prediction_curve": (potential_donations, potential_clr), "grants_clr": grants_clr})
+
+ print(f"\nTotal execution time: {(timezone.now() - clr_calc_start_time)}\n")
+
+ return debug_output
diff --git a/app/grants/management/commands/analytics_clr2.py b/app/grants/management/commands/analytics_clr2.py
new file mode 100644
index 00000000000..fa0f6f1bada
--- /dev/null
+++ b/app/grants/management/commands/analytics_clr2.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+"""Define the Grant subminer management command.
+
+Copyright (C) 2021 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 .
+
+"""
+
+from django.core.management.base import BaseCommand
+from django.db import connection
+from django.utils import timezone
+
+from dashboard.models import Profile
+from grants.clr2 import calculate_clr_for_donation, fetch_grants, get_summed_contribs_query
+from grants.models import GrantCLR
+
+
+def analytics_clr(from_date=None, clr_round=None, network='mainnet'):
+ # setup
+ # clr_calc_start_time = timezone.now()
+ debug_output = [['grant_id', 'grant_title', 'number_contributions', 'contribution_amount', 'clr_amount']]
+
+ # one-time data call
+ total_pot = float(clr_round.total_pot)
+ v_threshold = float(clr_round.verified_threshold)
+
+ print(total_pot)
+
+ grants = fetch_grants(clr_round, network)
+ # collect contributions for clr_round into temp table
+ initial_query = get_summed_contribs_query(grants, clr_round.start_date, clr_round.end_date, clr_round.contribution_multiplier, network)
+ # open cursor and execute the groupBy sum for the round
+ with connection.cursor() as cursor:
+ # execute to populate shared state for the round
+ cursor.execute(initial_query)
+ # calculate clr analytics output
+ for grant in grants:
+ _, clr_amount = calculate_clr_for_donation(
+ grant.id,
+ 0,
+ cursor,
+ total_pot,
+ v_threshold
+ )
+ debug_output.append([grant.id, grant.title, grant.positive_round_contributor_count, float(grant.amount_received_in_round), clr_amount])
+
+ return debug_output
+
+
+
+class Command(BaseCommand):
+
+ help = 'calculate clr base analytic results for all clr rounds or for a specific clr round'
+
+ def add_arguments(self, parser):
+ parser.add_argument('network', type=str, default='mainnet', choices=['rinkeby', 'mainnet'])
+ parser.add_argument('clr_pk', type=str, default="all")
+
+
+ def handle(self, *args, **options):
+
+ network = options['network']
+ clr_pk = options['clr_pk']
+
+ if clr_pk == "all":
+ active_clr_rounds = GrantCLR.objects.filter(is_active=True)
+ else:
+ active_clr_rounds = GrantCLR.objects.filter(pk=clr_pk)
+
+ if active_clr_rounds:
+ for clr_round in active_clr_rounds:
+ print(f"calculating CLR results for round: {clr_round.round_num} {clr_round.sub_round_slug}")
+ analytics = analytics_clr(
+ from_date=timezone.now(),
+ clr_round=clr_round,
+ network=network
+ )
+ print(analytics)
+ print(f"finished CLR results for round: {clr_round.round_num} {clr_round.sub_round_slug}")
+
+ else:
+ print("No active CLRs found")
diff --git a/app/grants/management/commands/analytics_clr3.py b/app/grants/management/commands/analytics_clr3.py
new file mode 100644
index 00000000000..6bc715e633e
--- /dev/null
+++ b/app/grants/management/commands/analytics_clr3.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+"""Define the Grant subminer management command.
+
+Copyright (C) 2021 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 .
+
+"""
+
+from django.core.management.base import BaseCommand
+from django.db import connection
+from django.utils import timezone
+
+from grants.clr3 import calculate_clr_for_donation, fetch_grants, get_summed_contribs_query
+from grants.models import GrantCLR
+
+
+def analytics_clr(from_date=None, clr_round=None, network='mainnet'):
+ # setup
+ # clr_calc_start_time = timezone.now()
+ debug_output = [['grant_id', 'grant_title', 'number_contributions', 'contribution_amount', 'clr_amount']]
+
+ # one-time data call
+ total_pot = float(clr_round.total_pot)
+ v_threshold = float(clr_round.verified_threshold)
+
+ print(total_pot)
+
+ grants = fetch_grants(clr_round, network)
+
+ # collect contributions for clr_round into temp table
+ initial_query = get_summed_contribs_query(grants, clr_round.start_date, clr_round.end_date, clr_round.contribution_multiplier, network)
+
+ # open cursor and execute the groupBy sum for the round
+ with connection.cursor() as cursor:
+ curr_agg = {}
+ trust_dict = {}
+ # execute to populate shared state for the round
+ cursor.execute(initial_query) # (we could potential do better here by sharing this temp table between rounds)
+ for _row in cursor.fetchall():
+ if not curr_agg.get(_row[0]):
+ curr_agg[_row[0]] = {}
+
+ trust_dict[_row[1]] = _row[3]
+ curr_agg[_row[0]][_row[1]] = _row[2]
+
+ # calculate clr analytics output
+ for grant in grants:
+ _, clr_amount, num_contribs, contrib_amount = calculate_clr_for_donation(
+ curr_agg, trust_dict, grant.id, 0, v_threshold, total_pot
+ )
+ # debug_output.append([grant.id, grant.title, num_contribs, contrib_amount, clr_amount])
+ debug_output.append([grant.id, grant.title, grant.positive_round_contributor_count, float(grant.amount_received_in_round), clr_amount])
+
+ return debug_output
+
+
+
+class Command(BaseCommand):
+
+ help = 'calculate clr base analytic results for all clr rounds or for a specific clr round'
+
+ def add_arguments(self, parser):
+ parser.add_argument('network', type=str, default='mainnet', choices=['rinkeby', 'mainnet'])
+ parser.add_argument('clr_pk', type=str, default="all")
+
+
+ def handle(self, *args, **options):
+
+ network = options['network']
+ clr_pk = options['clr_pk']
+
+ if clr_pk == "all":
+ active_clr_rounds = GrantCLR.objects.filter(is_active=True)
+ else:
+ active_clr_rounds = GrantCLR.objects.filter(pk=clr_pk)
+
+ if active_clr_rounds:
+ for clr_round in active_clr_rounds:
+ print(f"calculating CLR results for round: {clr_round.round_num} {clr_round.sub_round_slug}")
+ analytics = analytics_clr(
+ from_date=timezone.now(),
+ clr_round=clr_round,
+ network=network
+ )
+ print(analytics)
+ print(f"finished CLR results for round: {clr_round.round_num} {clr_round.sub_round_slug}")
+
+ else:
+ print("No active CLRs found")
diff --git a/app/grants/management/commands/estimate_clr.py b/app/grants/management/commands/estimate_clr.py
index a425e1b2633..33cec050cae 100644
--- a/app/grants/management/commands/estimate_clr.py
+++ b/app/grants/management/commands/estimate_clr.py
@@ -45,7 +45,7 @@ def handle(self, *args, **options):
clr_pk = options['clr_pk']
what = options['what']
sync = options['sync']
- print (network, clr_pk, what, sync)
+ print ('clr', network, clr_pk, what, sync)
if clr_pk and clr_pk.isdigit():
active_clr_rounds = GrantCLR.objects.filter(pk=clr_pk)
diff --git a/app/grants/management/commands/estimate_clr2.py b/app/grants/management/commands/estimate_clr2.py
new file mode 100644
index 00000000000..ac395a9471a
--- /dev/null
+++ b/app/grants/management/commands/estimate_clr2.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+"""Define the Grant subminer management command.
+
+Copyright (C) 2021 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 .
+
+"""
+
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+
+from grants.clr2 import predict_clr
+from grants.models import GrantCLR
+from grants.tasks import process_predict_clr
+
+
+class Command(BaseCommand):
+
+ help = 'calculate CLR estimates for all grants'
+
+ def add_arguments(self, parser):
+ parser.add_argument('network', type=str, default='mainnet', choices=['rinkeby', 'mainnet'])
+ parser.add_argument('clr_pk', type=str, default="all")
+ parser.add_argument('what', type=str, default="full")
+ parser.add_argument('sync', type=str, default="false")
+ # slim = just run 0 contribution match upcate calcs
+ # full, run [0, 1, 10, 100, calcs across all grants]
+
+
+ def handle(self, *args, **options):
+
+ network = options['network']
+ clr_pk = options['clr_pk']
+ what = options['what']
+ sync = options['sync']
+ print ('clr2', network, clr_pk, what, sync)
+
+ if clr_pk and clr_pk.isdigit():
+ active_clr_rounds = GrantCLR.objects.filter(pk=clr_pk)
+ else:
+ active_clr_rounds = GrantCLR.objects.filter(is_active=True)
+
+ if active_clr_rounds:
+ for clr_round in active_clr_rounds:
+ if sync == 'true':
+ # run it sync -> useful for payout / debugging
+ predict_clr(
+ save_to_db=True,
+ from_date=timezone.now(),
+ clr_round=clr_round,
+ network=network,
+ what=what,
+ )
+ else:
+ # runs it as celery task.
+ process_predict_clr(
+ save_to_db=True,
+ from_date=timezone.now(),
+ clr_round=clr_round,
+ network=network,
+ what=what,
+ )
+ else:
+ print("No active CLRs found")
diff --git a/app/grants/management/commands/estimate_clr3.py b/app/grants/management/commands/estimate_clr3.py
new file mode 100644
index 00000000000..8941d53ae30
--- /dev/null
+++ b/app/grants/management/commands/estimate_clr3.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+"""Define the Grant subminer management command.
+
+Copyright (C) 2021 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 .
+
+"""
+
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+
+from grants.clr3 import predict_clr
+from grants.models import GrantCLR
+from grants.tasks import process_predict_clr
+
+
+class Command(BaseCommand):
+
+ help = 'calculate CLR estimates for all grants'
+
+ def add_arguments(self, parser):
+ parser.add_argument('network', type=str, default='mainnet', choices=['rinkeby', 'mainnet'])
+ parser.add_argument('clr_pk', type=str, default="all")
+ parser.add_argument('what', type=str, default="full")
+ parser.add_argument('sync', type=str, default="false")
+ # slim = just run 0 contribution match upcate calcs
+ # full, run [0, 1, 10, 100, calcs across all grants]
+
+
+ def handle(self, *args, **options):
+
+ network = options['network']
+ clr_pk = options['clr_pk']
+ what = options['what']
+ sync = options['sync']
+ print ('clr3', network, clr_pk, what, sync)
+
+ if clr_pk and clr_pk.isdigit():
+ active_clr_rounds = GrantCLR.objects.filter(pk=clr_pk)
+ else:
+ active_clr_rounds = GrantCLR.objects.filter(is_active=True)
+
+ if active_clr_rounds:
+ for clr_round in active_clr_rounds:
+ if sync == 'true':
+ # run it sync -> useful for payout / debugging
+ predict_clr(
+ save_to_db=True,
+ from_date=timezone.now(),
+ clr_round=clr_round,
+ network=network,
+ what=what,
+ )
+ else:
+ # runs it as celery task.
+ process_predict_clr(
+ save_to_db=True,
+ from_date=timezone.now(),
+ clr_round=clr_round,
+ network=network,
+ what=what,
+ )
+ else:
+ print("No active CLRs found")
diff --git a/app/grants/templates/grants/cart-vue.html b/app/grants/templates/grants/cart-vue.html
index 361777bde0d..4ef27e7bfb0 100644
--- a/app/grants/templates/grants/cart-vue.html
+++ b/app/grants/templates/grants/cart-vue.html
@@ -70,6 +70,12 @@
- zkSync checkout not supported due to [[ zkSyncUnsupportedTokens.join(', ') ]]
+
+
+ zkSync checkout not supported due to [[ zkSyncUnsupportedTokens.join(', ') ]]
+
+
+ Polygon checkout not supported due to [[ polygonUnsupportedTokens.join(', ') ]]
+
💡 Save ~[[ checkoutRecommendation.savingsInPercent ]]%
@@ -161,6 +166,112 @@
Summary
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Pay with Polygon (Matic)
+
+
+
What is Polygon (Matic)?
+
+ Polygon (Matic) is an Ethereum sidechain solution, which means you can checkout
+ your favorite grants with much lower transaction fees and much faster confirmation time.
+
+
How do I pay with Polygon (Matic)?
+
+ To proceed, you’ll need to make a minimum deposit of
+ [[ requiredAmountsString ]] to the Polygon (Matic) network. Upon clicking Deposit Funds,
+ a new browser tab will be opened for you to deposit funds to Polygon (Matic) network.
+
+
+
+
+
+
+
+
+
+
+
Complete fund deposit on Polygon (Matic) Web Wallet
+
How much do I have to deposit?
+
+ To proceed, you’ll need to make a minimum deposit of
+ [[ requiredAmountsString ]] to the Polygon (Matic) network.
+
+
+ You can close this modal and click Checkout with Polygon again once your deposit is completed.
+
+
Having issues with depositing funds to Polygon (Matic)?
+
+ Please contact Polygon Support if you experience any issues or errors
+ with depositing funds to Polygon (Matic).
+
diff --git a/app/retail/templates/shared/head.html b/app/retail/templates/shared/head.html
index bd17cfc4a8f..895df9b1582 100644
--- a/app/retail/templates/shared/head.html
+++ b/app/retail/templates/shared/head.html
@@ -42,7 +42,6 @@
-
diff --git a/docs/API.md b/docs/API.md
index ced24c28095..abef74911a2 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -3,7 +3,7 @@
## Grants API
* Get a full list of grants at `https://gitcoin.co/grants/grants.json`
-* Get a list of contributors to each Gitcoin Grants Round at `https://gitcoin.co/grants/v1/api/export_addresses/roundX.json` where X is the round number, (1-7 supported as of Oct 2020)
+* Get a list of contributors to each Gitcoin Grants Round at `https://gitcoin.co/grants/v1/api/export_addresses/roundX.json` where X is the round number, (1-10 supported as of Aug 2021)
* Get a list of contributors to a Gitcoin Grant at `https://gitcoin.co/grants/v1/api/export_addresses/grantX.json` where X is the ID of the grant. You must be authenticated as a team member of the grant to access the data.
* Get a list of contributors to a Gitcoin Grant at a specififc round `https://gitcoin.co/grants/v1/api/export_addresses/grantX_roundY.json` where X is the ID of the grant and Y is the round number. You must be authenticated as a team member of the grant to access the data.
* We've got an `https://gitcoin.co/grants/v1/api/export_addresses/all.json` endpoint available for those who'd like to just get all addresses that've ever funded a Gitcoin Grant.
diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md
index 75be7185a0e..48ef59adec0 100644
--- a/docs/ENVIRONMENT_VARIABLES.md
+++ b/docs/ENVIRONMENT_VARIABLES.md
@@ -143,7 +143,6 @@ If you opt to modify the port or listener interface, you must update your `launc
| Variable | Description | Type | Default |
| --- | --- | --- | --- |
-| FAUCET_AMOUNT | The amount of ETH to be distributed for approved faucet requests. | `float` | .0005 |
| GITTER_TOKEN | The Gitter chat API token. | `str` | False |
diff --git a/package.json b/package.json
index cc781e69252..0a8717f60ed 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,6 @@
"vue": "2.6.11",
"vue-plyr": "^7.0.0",
"vue-select": "3.10.8",
- "vue-tel-input": "^5.5.0"
+ "vue-tel-input": "^4.4.1"
}
}
diff --git a/pydocmd.yml b/pydocmd.yml
index 9a499b186b6..192d27ed1e8 100644
--- a/pydocmd.yml
+++ b/pydocmd.yml
@@ -75,12 +75,6 @@ generate:
- economy.models++
- economy/utils.md:
- economy.utils++
- - faucet/admin.md:
- - faucet.admin++
- - faucet/models.md:
- - faucet.models++
- - faucet/views.md:
- - faucet.views++
- gas/admin.md:
- gas.admin++
- gas/models.md:
@@ -254,10 +248,6 @@ pages:
- Admin: economy/admin.md
- Models: economy/models.md
- Utilities: economy/utils.md
- - Faucet:
- - Admin: faucet/admin.md
- - Models: faucet/models.md
- - Views: faucet/views.md
- Gas:
- Admin: gas/admin.md
- Models: gas/models.md
diff --git a/requirements/base.txt b/requirements/base.txt
index c572555ecda..d92969111af 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -55,7 +55,7 @@ svgutils==0.3.0
watchdog==0.9.0
Werkzeug[watchdog]==0.15.5
imageio
-boto3==1.7.81
+boto3==1.18.22
django-storages==1.11.1
eth-account==0.2.2
django-classy-tags==0.8.0
@@ -99,7 +99,7 @@ django-queryset-csv
django-proxy==1.2.1
ed25519
tweepy
-attrs==19.3.0
+attrs==20.3.0
oogway==0.7.0
icalendar==4.0.7
duniterpy==0.61.0
@@ -116,3 +116,4 @@ libsass==0.20.1
graphqlclient==0.2.4
docutils==0.17.1
unidecode==1.2.0
+drf-flex-fields==0.9.1
\ No newline at end of file
diff --git a/scripts/crontab b/scripts/crontab
index d2559e800da..1ec1dee1f79 100644
--- a/scripts/crontab
+++ b/scripts/crontab
@@ -127,7 +127,6 @@ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/us
10 * * * * cd gitcoin/coin; bash scripts/run_management_command.bash post_to_craigslist 1 >> /var/log/gitcoin/post_to_craigslist.log 2>&1
10 1 * * * cd gitcoin/coin; bash scripts/run_management_command.bash output_gas_viz >> /var/log/gitcoin/output_gas_viz.log 2>&1
25,45 * * * * cd gitcoin/coin; bash scripts/run_management_command.bash check_gh_ratelimit >> /var/log/gitcoin/gh_ratelimit.log 2>&1
-55 * * * * cd gitcoin/coin; bash scripts/run_management_command.bash process_faucet_requests >> /var/log/gitcoin/process_faucet_requests.log 2>&1
1 */4 * * * cd gitcoin/coin; bash scripts/run_management_command.bash grant_vitalik_shuffle > /var/log/gitcoin/grant_vitalik_shuffle.log 2>&1
1 */3 * * * cd gitcoin/coin; bash scripts/run_management_command.bash grant_collections_shuffle > /var/log/gitcoin/grant_collections_shuffle.log 2>&1
diff --git a/yarn.lock b/yarn.lock
index 6d6c76b1580..9db567d5145 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1540,6 +1540,11 @@ autoprefixer@^9.0.0:
postcss "^7.0.32"
postcss-value-parser "^4.1.0"
+awesome-phonenumber@^2.39.0:
+ version "2.57.0"
+ resolved "https://registry.yarnpkg.com/awesome-phonenumber/-/awesome-phonenumber-2.57.0.tgz#0d2986247553b0e3d97e459bb365a82c13a4f259"
+ integrity sha512-RWrCCQpnmkYeL3AGFdlUOpWkpkTauZm7FE9kgDz6xJG6PNUiiIm+rKI95wnre0TSV01PHvgFFwQZhDixPCM9ZA==
+
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@@ -2261,11 +2266,16 @@ core-js-compat@^3.14.0, core-js-compat@^3.15.0:
browserslist "^4.16.6"
semver "7.0.0"
-core-js@^3.10.1, core-js@^3.14.0, core-js@^3.8.1:
+core-js@^3.10.1, core-js@^3.8.1:
version "3.15.2"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61"
integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==
+core-js@^3.6.4:
+ version "3.16.3"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.16.3.tgz#1f2d43c51a9ed014cc6c83440af14697ae4b75f2"
+ integrity sha512-lM3GftxzHNtPNUJg0v4pC2RC6puwMd6VZA7vXUczi+SKmCWSf4JwO89VJGMqbzmB7jlK7B5hr3S64PqwFL49cA==
+
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -4517,11 +4527,6 @@ libp2p-crypto@~0.16.1:
tweetnacl "^1.0.0"
ursa-optional "~0.10.0"
-libphonenumber-js@^1.9.6:
- version "1.9.21"
- resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.21.tgz#ae7ce30d68a4b697024287e108f391f3938e8b1e"
- integrity sha512-hMt+UTcXjRj7ETZBCxdPSw362Lq16Drf4R9vYgw19WoJLLpi/gMwseW62tevBKfF3pfXfr5rNnbc/hSl7XgWnQ==
-
linkify-it@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.2.tgz#f55eeb8bc1d3ae754049e124ab3bb56d97797fb8"
@@ -7471,21 +7476,21 @@ vue-select@3.10.8:
resolved "https://registry.yarnpkg.com/vue-select/-/vue-select-3.10.8.tgz#5d0839cba228a9a5efa1a88e5436840eb3fd0c7a"
integrity sha512-PnjtZWCTiSr04bs8ctPIiU41qnBK+oh/SOe6Pb4gElMMxofDFwUxiUe++mz0+84aTy4zrleGxtvVVyWbiPYBiw==
-vue-tel-input@^5.5.0:
- version "5.5.0"
- resolved "https://registry.yarnpkg.com/vue-tel-input/-/vue-tel-input-5.5.0.tgz#c62cf17f00ae2b2c320619de42b0038ed22f2738"
- integrity sha512-NtO/4KI70jS/6j8zcguO0ZeRLeBc5dRGeTqkGcFwyIOuL7C8xajI9XAMqgY+der6jcuLIAk3Xm0o56ijm5jFxg==
+vue-tel-input@^4.4.1:
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/vue-tel-input/-/vue-tel-input-4.4.2.tgz#a673f706f1e452b862908b9d7a0496df0e114a35"
+ integrity sha512-fZm9/gE2MFAe+EoF/R66t9oc0U9oI87TiPTxv6pHG8MGhrc62a+uvPqyE/clNhdEJJ0slGzVqkelBuw23HHWtw==
dependencies:
- core-js "^3.14.0"
- libphonenumber-js "^1.9.6"
- vue "^2.6.14"
+ "@babel/runtime" "^7.8.4"
+ awesome-phonenumber "^2.39.0"
+ core-js "^3.6.4"
vue@2.6.11:
version "2.6.11"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
-vue@^2.6.12, vue@^2.6.14:
+vue@^2.6.12:
version "2.6.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==