Skip to content

Commit

Permalink
implement twitter grant verification (#7377)
Browse files Browse the repository at this point in the history
* implement twitter grant verification

* Add missing migration

Co-authored-by: Octavio Amuchástegui <[email protected]>
  • Loading branch information
zoek1 and octavioamu authored Sep 14, 2020
1 parent b707d1a commit 063d3cc
Show file tree
Hide file tree
Showing 12 changed files with 258 additions and 18 deletions.
17 changes: 16 additions & 1 deletion app/assets/v2/css/grants/profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,20 @@ h2.title {
.title {
padding-left: 10px;
}
}

.verification__warning {
background-color: #f8f8f0;
border: 2px solid #ffce08;
color: #b88b16;
font-weight: bold;
}

}
.verification__warning__icon {
font-size: 1.8rem;
margin-top: 7px;
}

.error {
color: var(--gc-pink);
}
2 changes: 1 addition & 1 deletion app/grants/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class GrantAdmin(GeneralAdmin):
'subscriptions_links', 'contributions_links', 'logo', 'logo_svg', 'image_css',
'link', 'clr_prediction_curve', 'hidden', 'grant_type', 'next_clr_calc_date', 'last_clr_calc_date',
'metadata', 'categories', 'twitter_handle_1', 'twitter_handle_2', 'view_count', 'is_clr_eligible', 'in_active_clrs',
'last_update', 'funding_info'
'last_update', 'funding_info', 'twitter_verified', 'twitter_verified_by', 'twitter_verified_at'
]
readonly_fields = [
'logo_svg_asset', 'logo_asset',
Expand Down
30 changes: 30 additions & 0 deletions app/grants/migrations/0080_auto_20200914_2146.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 2.2.4 on 2020-09-14 21:46

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0148_add_brightid_status'),
('grants', '0079_auto_20200914_2031'),
]

operations = [
migrations.AddField(
model_name='grant',
name='twitter_verified',
field=models.BooleanField(default=False, help_text='The owner grant has verified the twitter account'),
),
migrations.AddField(
model_name='grant',
name='twitter_verified_at',
field=models.DateTimeField(blank=True, help_text='At what time and date what verified this grant', null=True),
),
migrations.AddField(
model_name='grant',
name='twitter_verified_by',
field=models.ForeignKey(blank=True, help_text='Team member who verified this grant', null=True, on_delete=django.db.models.deletion.SET_NULL, to='dashboard.Profile'),
),
]
20 changes: 12 additions & 8 deletions app/grants/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,10 @@ class Meta:
is_clr_active = models.BooleanField(default=False, help_text=_('CLR Round active or not? (auto computed)'))
clr_round_num = models.CharField(default='', max_length=255, help_text=_('the CLR round number thats active'), blank=True)

twitter_verified = models.BooleanField(default=False, help_text='The owner grant has verified the twitter account')
twitter_verified_by = models.ForeignKey('dashboard.Profile', null=True, blank=True, on_delete=models.SET_NULL, help_text='Team member who verified this grant')
twitter_verified_at = models.DateTimeField(blank=True, null=True, help_text='At what time and date what verified this grant')

# Grant Query Set used as manager.
objects = GrantQuerySet.as_manager()

Expand Down Expand Up @@ -1259,38 +1263,38 @@ def update_tx_status(self):

# We use the transaction hashes of this object to help identify zkSync checkouts. This
# works as follows:
#
#
# self.split_tx_id holds one of:
# Case 1: The tx hash of an L1 transaction to the BulkCheckout contract for an
# Case 1: The tx hash of an L1 transaction to the BulkCheckout contract for an
# ordinary checkout
# Case 2: The tx hash of an L1 transaction that deposits funds into zkSync. This
# occurs when a user did not have existing funds in zkSync
# Case 3: The address of the Gitcoin zkSync wallet that executed the donations. This
# occurs when a user already had funds in zkSync
#
#
# Case 1 has already been handled by everything above. For Case 2, we mark a
# contribution as cleared once both of the below conditions are met:
# 1. The L1 deposit transaction has been confirmed, and
# 2. The L2 transfers have been completed
#
# For case 3, we mark a contribution as cleared once the L2 transfers are completed.

# Prepare web3 provider
network = self.subscription.network
PROVIDER = "wss://" + network + ".infura.io/ws/v3/" + settings.INFURA_V3_PROJECT_ID
w3 = Web3(Web3.WebsocketProvider(PROVIDER))

# Get case number
is_split_tx_id_address = len(self.split_tx_id) == 42 and self.split_tx_id[0:2] == '0x'
if is_split_tx_id_address:
case_number = 3

else:
# Figure out if we are in Case 1 or Case 2
# handle replace of split_tx_id
if not self.split_tx_id:
return

split_tx_status, _ = get_tx_status(self.split_tx_id, self.subscription.network, self.created_on)
if split_tx_status in ['pending', 'dropped', 'unknown', '']:
new_tx = getReplacedTX(self.split_tx_id)
Expand All @@ -1311,7 +1315,7 @@ def update_tx_status(self):
batch_zksync_deposit_contract_addr = '0x9D37F793E5eD4EbD66d62D505684CD9f756504F6'.lower()
zkSync_recipients = [zksync_contract_addr.lower(), batch_zksync_deposit_contract_addr.lower()]
case_number = 2 if recipient_L1 in zkSync_recipients else 1

# If case 1, proceed as normal
if case_number == 1:
# actually validate token transfers
Expand Down
12 changes: 12 additions & 0 deletions app/grants/templates/grants/components/card.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
<div class="grant-item__content col-md-8 px-3">
<h2 class="grant-item__title font-subheader">
<a :href="grant.details_url">[[ grant.title.slice(0,60) ]][[ grant.title.length > 60 ? '...' : '']]</a>
<button v-if="grant.verified" class="btn btn-sm animate-verify p-0" data-container="body" data-toggle="popover" data-html="true" data-placement="bottom" data-trigger="click" data-content='
<p class="h6 my-2 text-left">Verified Ownership <img width="18" src="{% static "v2/images/badge-verify.svg" %}"></p>
<p>Grant owner has verified ownership of their twitter account.</p>
<a href="#">Learn more.</p>'
><img width="14" src="{% static 'v2/images/badge-verify.svg' %}" alt="">
</button>
</h2>
<div>
<p class="text-muted font-smaller-5 mb-0" style="height:2rem;" data-toggle="tooltip" data-placement="top" title="The last time the grant admin updated the grant." >
Expand Down Expand Up @@ -121,6 +127,12 @@ <h2 class="grant-item__title font-subheader">
<div class="grant-item__content px-3">
<h2 class="grant-item__title font-subheader">
<a :href="grant.details_url">[[ grant.title.slice(0,60) ]][[ grant.title.length > 60 ? '...' : '']]</a>
<button v-if="grant.verified" class="btn btn-sm animate-verify p-0" data-container="body" data-toggle="popover" data-html="true" data-placement="bottom" data-trigger="hover click" data-content='
<p class="h6 my-2 text-left">Verified Ownership <img width="18" src="{% static "v2/images/badge-verify.svg" %}"></p>
<p>Grant owner has verified ownership of their twitter account.</p>
<a href="#">Learn more.</p>'
><img width="16" src="{% static 'v2/images/badge-verify.svg' %}" alt="">
</button>
</h2>
<p class="grant-item__pitch font-caption mb-2">[[ grant.description.slice(0, 145) ]][[ grant.description.length > 145 ? '...' : '']]</p>
<p class="grant-item__pitch font-caption mb-0" style="height:2rem;" data-toggle="tooltip" data-placement="top" title="The last time the grant admin updated the grant." >
Expand Down
17 changes: 16 additions & 1 deletion app/grants/templates/grants/detail/funding.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,22 @@

{% load static humanize i18n grants_extra %}

<div class="px-0 pr-md-3">
<div class="px-0 pr-md-3 my-0 mx-0 mt-md-5 mr-md-5">
{% if not grant.twitter_verified %}
<div class="my-0 mx-0 mt-md-5 mr-md-5 py-2">
<div class="verification__warning py-2 px-2 d-flex">
<i class="fas fa-exclamation-triangle verification__warning__icon mr-2"></i>
<div>
<div class="uppercase">Warning: This grant has not verified their ownership of the property listed</div>
{% if is_team_member %}
<div class="text-right mt-3">
<button class="btn btn-sm btn-gc-blue" data-toggle="modal" data-target="#startVerification">Verify your grant</button>
</div>
{% endif %}
</div>
</div>
</div>
{% endif %}
<div id="funding-card" class="card my-0 mx-0 mt-md-5 mr-md-5 shadow-sm">
<div class="card-body">
<div class="row progress-container {% if clr_active %} mb-2 {% endif %} {% if not is_team_member %} mt-4 {% endif %}">
Expand Down
71 changes: 71 additions & 0 deletions app/grants/templates/grants/detail/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,48 @@
<div id="side-cart" class="grant-side-cart px-5 px-md-4 pb-5 pb-md-0 pt-md-0" style="display: none;">
{% include 'grants/detail/side-cart.html' %}
</div>
</div>
<div class="modal fade" id="startVerification" tabindex="-1" role="dialog" aria-labelledby="startVerification" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content text-center">
<div class="modal-header" style="border-bottom: none">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body px-5">
<img height="45" class="mb-4" src="{% static "v2/images/badge-verify.svg" %}" >
<h5 class="font-weight-bold mb-3">Verify Grant Ownership</h5>
<p class="mb-4">Verify your grant ownership to ensure that your supporters are contributing to the correct grant.</p>
<button id="triggerTwitter" class="btn btn-lg btn-gc-blue" data-toggle="modal" data-target="#startTwitterVerification">Verify with twitter</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="startTwitterVerification" tabindex="-1" role="dialog" aria-labelledby="startTwitterVerification" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content text-center">
<div class="modal-header" style="border-bottom: none">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body px-5">
<img height="45" class="mb-4" src="{% static "v2/images/badge-verify.svg" %}" >
<h5 class="font-weight-bold mb-3">Verify Grant Ownership with Twitter</h5>
<p class="mb-4 text-left"><b>Step 1:</b> Send a tweet from your account (<b>{{ grant.twitter_handle_1 }}</b>) saying <b>
{% with verification_tweet|add:" "|add:user_code as tweet %}
{{ tweet }} <a class="font-caption btn btn-outline-gc-blue btn-sm" href="https://twitter.com/intent/tweet?text={{ tweet|urlencode}}" target="_blank" ><i class="fab fa-twitter"></i> Tweet it</a></b> <br>
{% endwith %}
</p>
<p class="mb-4 text-left"><b>Step 2:</b>Click the "Verify" button below</p>
<button class="btn btn-lg btn-gc-blue mb-4" id="twitterVerification">Verify</button>
<br>
<span class="error" id="validation-errors"></span>

</div>
</div>
</div>
</div>
<input type="hidden" id="contract_version" name="contract_version" value="{{ grant.contract_version }}"/>
{% include 'shared/current_profile.html' %}
Expand Down Expand Up @@ -121,6 +163,35 @@
<script type="text/javascript">
$(document).ready(function() {
$("img").unveil();

$('#twitterVerification').on('click', async () => {
const response = await fetchData('/grants/v1/api/{{grant.id}}/verify');

if (!response.ok) {
_alert(response.msg, 'error');
return;
}
if (response.verified) {
_alert('Congratulations, your grant is now verified!', 'success')
$('.verification__warning').remove();
$('#startTwitterVerification .close').click()
}

if (!response.has_text) {
$('#validation-errors').text(`Don't remove the intent "{{ verification_tweet }}"`);
return;
}

if (!response.has_code) {
$('#validation-errors').text(`Missing emoji code "{{ user_code }}", please don't remove this unique code before validate your grant.`);
return;
}
});


$('#triggerTwitter').on('click', async () => {
$("#startVerification .close").click();
});
});

</script>
Expand Down
6 changes: 6 additions & 0 deletions app/grants/templates/grants/detail/info.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ <h1 class="font-title-xl my-4 font-weight-bold">
<i style="width: 14px;" class="fab fa-twitter-square mr-2 text-center"></i>
<p class="d-inline-block mb-0">
<a href="https://twitter.com/{{grant.twitter_handle_1}}" target="new" rel="nofollow" data-toggle="tooltip" data-html="true" data-placement="top" title="Project Twitter Account">{{grant.twitter_handle_1}}</a>
<button class="btn btn-sm animate-verify {% if not grant.twitter_verified %}d-none{% endif %}" data-container="body" data-toggle="popover" data-html="true" data-placement="bottom" data-trigger="hover click" data-content='
<p class="h6 my-2 text-left">Verified Ownership <img width="18" src="{% static "v2/images/badge-verify.svg" %}"></p>
<p>Grant owner has verified ownership of their twitter account.</p>
<a href="#">Learn more.</p>'
><img width="18" src="{% static 'v2/images/badge-verify.svg' %}" alt="">
</button>
</p>
</div>
{% endif %}
Expand Down
4 changes: 2 additions & 2 deletions app/grants/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
bulk_fund, flag, get_grants, get_replaced_tx, grant_activity, grant_categories, grant_details, grant_fund,
grant_new, grant_new_whitelabel, grants, grants_addr_as_json, grants_bulk_add, grants_by_grant_type,
grants_cart_view, grants_clr, grants_stats_view, invoice, leaderboard, new_matching_partner, profile, quickstart,
subscription_cancel, toggle_grant_favorite, zksync_get_interrupt_status, zksync_set_interrupt_status,
subscription_cancel, toggle_grant_favorite, zksync_get_interrupt_status, zksync_set_interrupt_status, verify_grant,
)

app_name = 'grants'
Expand Down Expand Up @@ -65,5 +65,5 @@
path('<slug:grant_type>', grants_by_grant_type, name='grants_by_category2'),
path('<slug:grant_type>/', grants_by_grant_type, name='grants_by_category'),
path('v1/api/clr', grants_clr, name='grants_clr'),

path('v1/api/<int:grant_id>/verify', verify_grant, name='verify_grant')
]
16 changes: 14 additions & 2 deletions app/grants/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import logging
import os
from decimal import Decimal
from random import randint, seed
from secrets import token_hex

from economy.utils import ConversionRateNotFoundError, convert_amount
Expand All @@ -28,6 +29,10 @@

logger = logging.getLogger(__name__)

block_codes = ('▖', '▗', '▘', '▙', '▚', '▛', '▜', '▝', '▞', '▟')
emoji_codes = ('🎉', '🎈', '🎁', '🎊', '🙌', '🥂', '🎆', '🔥', '⚡', '👍')


def get_upload_filename(instance, filename):
salt = token_hex(16)
file_path = os.path.basename(filename)
Expand Down Expand Up @@ -115,10 +120,10 @@ def which_clr_round(timestamp):

if round_start < timestamp < round_end:
return round

return None

def get_converted_amount(amount, token_symbol):
def get_converted_amount(amount, token_symbol):
try:
if token_symbol == "ETH" or token_symbol == "WETH":
return Decimal(float(amount) * float(eth_usd_conv_rate()))
Expand All @@ -142,3 +147,10 @@ def get_converted_amount(amount, token_symbol):
except ConversionRateNotFoundError as no_conversion_e:
logger.info(no_conversion_e)
return None


def get_user_code(user_id, grant, coding_set=block_codes, length=6):
seed(user_id ** grant.id)
coding_id = [coding_set[randint(0, 9)] for _ in range(length)]

return ''.join(coding_id)
Loading

0 comments on commit 063d3cc

Please sign in to comment.