Skip to content

Commit

Permalink
core: base commit for celo + zil + refactor (#6313)
Browse files Browse the repository at this point in the history
* core: base commit for celo + zil

- update mgmt command to to cater for all cross-chain pending
  fulfillments
- refactor and move all token specific logic to it's own file
  within sync/
- remove un-used url
- update bounty creation to include celo

* set fulfillments > 5 min as expired

* add migration file

* update API wiring for ZIL

* update API wiring for celo

* update bounty creation flow

* bug: rename variable

* celo: update to support cUSD and cGLD

* fixup ZIL backend

* recreate migration

* bug: fix up celo

* Update etc.py

* fix migration

Co-authored-by: Dan Lipert <[email protected]>
  • Loading branch information
thelostone-mc and danlipert authored Apr 8, 2020
1 parent cee8e8f commit f1d43c2
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 103 deletions.
3 changes: 2 additions & 1 deletion app/app/local.env
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ CONTACT_EMAIL=
FEE_ADDRESS=
FEE_ADDRESS_PRIVATE_KEY=
GIPHY_KEY=
YOUTUBE_API_KEY=
YOUTUBE_API_KEY=
VIEW_BLOCK_API_KEY=
2 changes: 1 addition & 1 deletion app/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
#social integrations
GIPHY_KEY = env('GIPHY_KEY', default='LtaY19ToaBSckiLU4QjW0kV9nIP75NFy')
YOUTUBE_API_KEY = env('YOUTUBE_API_KEY', default='YOUR-SupEr-SecRet-YOUTUBE-KeY')

VIEW_BLOCK_API_KEY = env('VIEW_BLOCK_API_KEY', default='YOUR-VIEW-BLOCK-KEY')
# Ratelimit
RATELIMIT_ENABLE = env.bool('RATELIMIT_ENABLE', default=True)
RATELIMIT_USE_CACHE = env('RATELIMIT_USE_CACHE', default='default')
Expand Down
5 changes: 0 additions & 5 deletions app/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,6 @@
re_path(r'^bounty/quickstart/?', dashboard.views.quickstart, name='quickstart'),
url(r'^bounty/new/?', dashboard.views.new_bounty, name='new_bounty'),
re_path(r'^bounty/change/(?P<bounty_id>.*)?', dashboard.views.change_bounty, name='change_bounty'),
url(
r'^bounty/sync_payout/(?P<bounty_id>.*)?',
dashboard.views.manual_sync_etc_payout,
name='manual_sync_etc_payout'
),
url(r'^funding/new/?', dashboard.views.new_bounty, name='new_funding'), # TODO: Remove
url(r'^new/?', dashboard.views.new_bounty, name='new_funding_short'), # TODO: Remove
# TODO: Rename below to bounty/
Expand Down
4 changes: 2 additions & 2 deletions app/assets/v2/js/pages/create_bounty/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ createBounty = data => {
const tokenAddress = data.denomination;
const token = tokenAddressToDetails(tokenAddress);

if (metadata.tokenName == 'ETC') {
if (qr_tokens.includes(metadata.tokenName)) {
is_featured = 'True';
coupon_code = null;
fee_amount = 0;
Expand Down Expand Up @@ -76,7 +76,7 @@ createBounty = data => {
'eventTag': metadata.eventTag,
'auto_approve_workers': data.auto_approve_workers ? 'True' : 'False',
'web3_type': 'qr',
'activity': data.activity
'activity': data.activity,
'bounty_owner_address': data.funderAddress
};

Expand Down
15 changes: 4 additions & 11 deletions app/assets/v2/js/pages/new_bounty.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
/* eslint-disable no-lonely-if */
document.web3network = 'mainnet';
load_tokens();
// listen_for_web3_changes();

const qr_tokens = ['ETC'];

const qr_tokens = [ 'ETC', 'cGLD', 'cUSD', 'ZIL' ];

const updateOnNetworkOrTokenChange = () => {
const tokenName = $('select[name=denomination]').select2('data')[0] &&
Expand Down Expand Up @@ -272,7 +270,7 @@ const handleTokenAuth = () => {
const tokenAddress = $('#token option:selected').val();
let isTokenAuthed = true;

const authedTokens = [ 'ETH', 'ETC' ];
const authedTokens = ['ETH'].concat(qr_tokens);

if (!token) {
isTokenAuthed = false;
Expand Down Expand Up @@ -331,7 +329,7 @@ const tokenAuthAlert = (isTokenAuthed, tokenName) => {

const updateViewForToken = (token_name) => {

if (token_name == 'ETC') {
if (qr_tokens.includes(token_name)) {
$('.eth-chain').hide();
FEE_PERCENTAGE = 0;
} else {
Expand Down Expand Up @@ -656,12 +654,7 @@ $('#submitBounty').validate({
const token = $('#summary-bounty-token').html();
const data = transformBountyData(form);

if (token == 'ETC') {
/*
TODO:
1. TRIGGER DB UPDATE
2. REDESIGN METAMASK LOCK NOTIFICATION
*/
if (qr_tokens.includes(token)) {
createBounty(data);
} else {
ethCreateBounty(data);
Expand Down
22 changes: 16 additions & 6 deletions app/dashboard/management/commands/sync_etc_payouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,29 @@
'''


from datetime import timedelta

from django.core.management.base import BaseCommand
from django.utils import timezone

from dashboard.models import BountyFulfillment
from dashboard.utils import sync_etc_payout
from dashboard.utils import sync_payout


class Command(BaseCommand):

help = 'checks if payments are confirmed for ETC bounties that have been paid out'
help = 'checks if payments are confirmed for cross-chain tokens bounties that have been paid out'

def handle(self, *args, **options):
fulfillments_to_check = BountyFulfillment.objects.filter(
payout_status='pending', token_name='ETC'
pending_fulfillments = BountyFulfillment.objects.filter(
payout_status='pending'
)
for fulfillment in fulfillments_to_check.all():
sync_etc_payout(fulfillment)

timeout_period = timezone.now() - timedelta(minutes=5)

pending_fulfillments.filter(created_on__lt=timeout_period).update(payout_status='expired')

fulfillments = pending_fulfillments.filter(created_on__lte=timeout_period)

for fulfillment in fulfillments.all():
sync_payout(fulfillment)
18 changes: 18 additions & 0 deletions app/dashboard/migrations/0096_auto_20200406_1436.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.4 on 2020-04-06 14:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0096_auto_20200401_2038'),
]

operations = [
migrations.AlterField(
model_name='bountyfulfillment',
name='payout_status',
field=models.CharField(blank=True, choices=[('expired', 'expired'), ('pending', 'pending'), ('done', 'done')], max_length=10),
),
]
1 change: 1 addition & 0 deletions app/dashboard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,7 @@ class BountyFulfillment(SuperModel):
"""The structure of a fulfillment on a Bounty."""

PAYOUT_STATUS = [
('expired', 'expired'),
('pending', 'pending'),
('done', 'done'),
]
Expand Down
69 changes: 69 additions & 0 deletions app/dashboard/sync/celo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from django.utils import timezone

import requests
from dashboard.sync.helpers import txn_already_used


def find_txn_on_celo_explorer(fulfillment, network='mainnet'):
token_name = fulfillment.token_name
if token_name != 'cGLD' and token_name != 'cUSD':
return None

funderAddress = fulfillment.bounty.bounty_owner_address
amount = fulfillment.payout_amount
payeeAddress = fulfillment.fulfiller_address

# TODO: UPDATE WITH MAINNET URL. Using alfajores until then
blockscout_url = f'https://alfajores-blockscout.celo-testnet.org/api?module=account&action=tokentx&address={funderAddress}'

blockscout_response = requests.get(blockscout_url).json()
if blockscout_response['message'] and blockscout_response['result']:
for txn in blockscout_response['result']:
if (
txn['from'] == funderAddress.lower() and
txn['to'] == payeeAddress.lower() and
float(txn['value']) == float(amount) and
not txn_already_used(txn['hash'], token_name)
):
return txn
return None


def get_celo_txn_status(txnid, network='mainnet'):
if not txnid:
return None

# TODO: UPDATE WITH MAINNET URL. Using alfajores until then
blockscout_url = f'https://alfajores-blockscout.celo-testnet.org/api?module=transaction&action=gettxinfo&txhash={txnid}'

blockscout_response = requests.get(blockscout_url).json()

if blockscout_response['status'] and blockscout_response['result']:

response = {
'blockNumber': int(blockscout_response['result']['blockNumber']),
'confirmations': int(blockscout_response['result']['confirmations'])
}

if response['confirmations'] > 0:
response['has_mined'] = True
else:
response['has_mined'] = False
return response

return None


def sync_celo_payout(fulfillment):
if not fulfillment.payout_tx_id:
txn = find_txn_on_celo_explorer(fulfillment)
if txn:
fulfillment.payout_tx_id = txn['hash']

if fulfillment.payout_tx_id:
txn_status = get_celo_txn_status(fulfillment.payout_tx_id)
if txn_status and txn_status.get('has_mined'):
fulfillment.payout_status = 'done'
fulfillment.accepted_on = timezone.now()
fulfillment.accepted = True
fulfillment.save()
64 changes: 64 additions & 0 deletions app/dashboard/sync/etc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from django.utils import timezone

import requests
from dashboard.sync.helpers import txn_already_used


def find_txn_on_etc_explorer(fulfillment, network='mainnet'):
token_name = fulfillment.token_name
if token_name != 'ETC':
return None

funderAddress = fulfillment.bounty.bounty_owner_address
amount = fulfillment.payout_amount
payeeAddress = fulfillment.fulfiller_address

blockscout_url = f'https://blockscout.com/etc/{network}/api?module=account&action=txlist&address={funderAddress}'
blockscout_response = requests.get(blockscout_url).json()
if blockscout_response['message'] and blockscout_response['result']:
for txn in blockscout_response['result']:
if (
txn['from'] == funderAddress.lower() and
txn['to'] == payeeAddress.lower() and
float(txn['value']) == float(amount) and
not txn_already_used(txn['hash'], token_name)
):
return txn
return None


def get_etc_txn_status(txnid, network='mainnet'):
if not txnid:
return None

blockscout_url = f'https://blockscout.com/etc/{network}/api?module=transaction&action=gettxinfo&txhash={txnid}'
blockscout_response = requests.get(blockscout_url).json()

if blockscout_response['status'] and blockscout_response['result']:

response = {
'blockNumber': int(blockscout_response['result']['blockNumber']),
'confirmations': int(blockscout_response['result']['confirmations'])
}

if response['confirmations'] > 0:
response['has_mined'] = True
else:
response['has_mined'] = False
return response

return None


def sync_etc_payout(fulfillment):
if not fulfillment.payout_tx_id:
txn = find_txn_on_etc_explorer(fulfillment)
if txn:
fulfillment.payout_tx_id = txn['hash']
if fulfillment.payout_tx_id:
txn_status = get_etc_txn_status(fulfillment.payout_tx_id)
if txn_status and txn_status.get('has_mined'):
fulfillment.payout_status = 'done'
fulfillment.accepted_on = timezone.now()
fulfillment.accepted = True
fulfillment.save()
8 changes: 8 additions & 0 deletions app/dashboard/sync/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dashboard.models import BountyFulfillment


def txn_already_used(txn, token_name):
return BountyFulfillment.objects.filter(
payout_tx_id = txn,
token_name=token_name
).exists()
74 changes: 74 additions & 0 deletions app/dashboard/sync/zil.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import json

from django.conf import settings
from django.utils import timezone

import requests
from dashboard.sync.helpers import txn_already_used

headers = {
"X-APIKEY" : settings.VIEW_BLOCK_API_KEY
}

def find_txn_on_zil_explorer(fulfillment, network='mainnet'):
token_name = fulfillment.token_name
if token_name != 'ZIL':
return None

funderAddress = fulfillment.bounty.bounty_owner_address
amount = fulfillment.payout_amount
payeeAddress = fulfillment.fulfiller_address

url = f'https://api.viewblock.io/v1/zilliqa/addresses/{funderAddress}/txs?network={network}'

response = requests.get(url, headers=headers).json()

if len(response):
for txn in response:
if (
txn['from'] == funderAddress.lower() and
txn['to'] == payeeAddress.lower() and
txn['direction'] == 'out' and
float(txn['value']) == float(amount) and
not txn_already_used(txn['hash'], token_name)
):
return txn
return None


def get_zil_txn_status(txnid, network='mainnet'):
if not txnid:
return None

url = f'https://api.viewblock.io/v1/zilliqa/txs/{txnid}?network={network}'
view_block_response = requests.get(url, headers=headers).json()

if view_block_response:

response = {
'blockHeight': int(view_block_response['blockHeight']),
'receiptSuccess': view_block_response['receiptSuccess']
}

if response['receiptSuccess']:
response['has_mined'] = True
else:
response['has_mined'] = False
return response

return None


def sync_zil_payout(fulfillment):
if not fulfillment.payout_tx_id:
txn = find_txn_on_zil_explorer(fulfillment)
if txn:
fulfillment.payout_tx_id = txn['hash']

if fulfillment.payout_tx_id:
txn_status = get_zil_txn_status(fulfillment.payout_tx_id)
if txn_status and txn_status.get('has_mined'):
fulfillment.payout_status = 'done'
fulfillment.accepted_on = timezone.now()
fulfillment.accepted = True
fulfillment.save()
Loading

0 comments on commit f1d43c2

Please sign in to comment.