Skip to content

Commit

Permalink
xinfin: bounties integration (gitcoinco#8421)
Browse files Browse the repository at this point in the history
* feat: integrate xinfin/bounties

* add mig file

* recreate mig
  • Loading branch information
thelostone-mc authored and iRhonin committed Apr 23, 2021
1 parent 323271b commit 1204af8
Show file tree
Hide file tree
Showing 19 changed files with 325 additions and 12 deletions.
1 change: 1 addition & 0 deletions app/app/local.env
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ VIEW_BLOCK_API_KEY=
ETHERSCAN_API_KEY=
FORTMATIC_LIVE_KEY=
FORTMATIC_TEST_KEY=
XINFIN_API_KEY=

PYPL_CLIENT_ID=

Expand Down
1 change: 1 addition & 0 deletions app/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
FORTMATIC_LIVE_KEY = env('FORTMATIC_LIVE_KEY', default='YOUR-SupEr-SecRet-LiVe-FoRtMaTiC-KeY')
FORTMATIC_TEST_KEY = env('FORTMATIC_TEST_KEY', default='YOUR-SupEr-SecRet-TeSt-FoRtMaTiC-KeY')
PYPL_CLIENT_ID = env('PYPL_CLIENT_ID', default='')
XINFIN_API_KEY = env('XINFIN_API_KEY', default='')

# Ratelimit

Expand Down
1 change: 1 addition & 0 deletions app/assets/v2/images/chains/xinfin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions app/assets/v2/js/lib/xinfin/xdc3.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions app/assets/v2/js/lib/xinfin/xdc3.min.js.map

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions app/assets/v2/js/pages/bounty_detail/xinfin_extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const payWithXinfinExtension = async (fulfillment_id, to_address, vm, modal) => {

const amount = vm.fulfillment_context.amount;
const token_name = vm.bounty.token_name;

try {
web3.version.getNetwork(async (err, providerNetworkId) => {
// 1. init provider
await ethereum.enable();
xdc3Client = await new xdc3(web3.currentProvider);

// 2. check if default account is selected
if (web3.eth.defaultAccount == null) {
modal.closeModal();
_alert({ message: 'Please unlock XinPay Wallet extension' }, 'error');
return;
}

// 3. check if token is XDC
if (token_name == 'XDC') {

const xdcBalance = await xdc3Client.eth.getBalance(web3.eth.defaultAccount);

if (Number(xdcBalance) < Number(amount * 10 ** 18)) {
_alert({ message: `Insufficent balance in address ${web3.eth.defaultAccount}` }, 'error');
return;
}

let txArgs = {
to: to_address.toLowerCase(),
from: web3.eth.defaultAccount,
value: (amount * 10 ** 18).toString(),
};

xdc3Client.eth.sendTransaction(
txArgs,
(error, result) => callback(error, web3.eth.defaultAccount, result)
);

} else {
modal.closeModal();
_alert({ message: 'XinPay supports payouts in XDC only.' }, 'error');
return;
}
});

} catch (e) {
modal.closeModal();
_alert({ message: 'Please download or enable XinPay Wallet extension' }, 'error');
return;
}

function callback(error, from_address, txn) {
if (error) {
_alert({ message: gettext('Unable to payout bounty due to: ' + error) }, 'error');
console.log(error);
} else {

const payload = {
payout_type: 'xinfin_ext',
tenant: 'XINFIN',
amount: amount,
token_name: token_name,
funder_address: from_address.replace(/^(0x)/,"xdc"),
payout_tx_id: txn
};

modal.closeModal();
const apiUrlBounty = `/api/v1/bounty/payout/${fulfillment_id}`;

fetchData(apiUrlBounty, 'POST', payload).then(response => {
if (200 <= response.status && response.status <= 204) {
console.log('success', response);

vm.fetchBounty();
_alert('Payment Successful');

} else {
_alert('Unable to make payout bounty. Please try again later', 'error');
console.error(`error: bounty payment failed with status: ${response.status} and message: ${response.message}`);
}
}).catch(function(error) {
_alert('Unable to make payout bounty. Please try again later', 'error');
console.log(error);
});
}
}
};
22 changes: 18 additions & 4 deletions app/assets/v2/js/pages/bounty_details2.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ Vue.mixin({
url = `https://explorer.rsk.co/tx/${txn}`;
break;

case 'XDC':
url = `https://explorer.xinfin.network/tx/${txn}`;
break;

default:
url = `https://etherscan.io/tx/${txn}`;

Expand Down Expand Up @@ -200,6 +204,10 @@ Vue.mixin({
url = `https://explorer.rsk.co/address/${address}`;
break;

case 'XDC':
url = `https://explorer.xinfin.network/addr/${address}`;
break;

default:
url = `https://etherscan.io/address/${address}`;
}
Expand Down Expand Up @@ -423,6 +431,10 @@ Vue.mixin({
tenant = 'RSK';
break;

case 'XDC':
tenant = 'XINFIN';
break;

default:
tenant = 'ETH';
}
Expand Down Expand Up @@ -509,6 +521,10 @@ Vue.mixin({
case 'rsk_ext':
payWithRSKExtension(fulfillment_id, fulfiller_address, vm, modal);
break;

case 'xinfin_ext':
payWithXinfinExtension(fulfillment_id, fulfiller_address, vm, modal);
break;
}
},
closeBounty: function() {
Expand Down Expand Up @@ -714,18 +730,16 @@ Vue.mixin({
let vm = this;

switch (fulfillment.payout_type) {
case 'fiat':
vm.fulfillment_context.active_step = 'payout_amount';
break;

case 'qr':
case 'manual':
vm.fulfillment_context.active_step = 'check_wallet_owner';
break;

case 'fiat':
case 'web3_modal':
case 'polkadot_ext':
case 'rsk_ext':
case 'xinfin_ext':
vm.fulfillment_context.active_step = 'payout_amount';
break;
}
Expand Down
4 changes: 4 additions & 0 deletions app/assets/v2/js/pages/hackathon_new_bounty.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ Vue.mixin({
// rsk
type = 'rsk_ext';
break;
case '50':
// xinfin
type = 'xinfin_ext';
break;
case '58':
// polkadot
type = 'polkadot_ext';
Expand Down
4 changes: 4 additions & 0 deletions app/assets/v2/js/pages/new_bounty.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ Vue.mixin({
// rsk
type = 'rsk_ext';
break;
case '50':
// xinfin
type = 'xinfin_ext';
break;
case '59':
case '58':
// 58 - polkadot, 59 - kusama
Expand Down
2 changes: 1 addition & 1 deletion app/assets/v2/js/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const WalletConnectProvider = window.WalletConnectProvider.default;
const eventWalletReady = new Event('walletReady', {bubbles: true});
const eventDataWalletReady = new Event('dataWalletReady', {bubbles: true});

let web3;
var web3 = typeof (web3) != 'undefined' ? web3 : null;
let web3Modal;
let provider;
let selectedAccount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def handle(self, *args, **options):
)

# Extensions
ext_payout_types= ['web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext']
ext_payout_types= ['web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext']
for ext_payout_type in ext_payout_types:
ext_pending_fulfillments = pending_fulfillments.filter(payout_type=ext_payout_type)
for fulfillment in ext_pending_fulfillments.all():
Expand Down
28 changes: 28 additions & 0 deletions app/dashboard/migrations/0175_auto_20210324_1140.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 2.2.4 on 2021-03-24 11:40

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dashboard', '0174_profile_google_user_id'),
]

operations = [
migrations.AlterField(
model_name='bounty',
name='web3_type',
field=models.CharField(choices=[('legacy_gitcoin', 'Legacy Bounty'), ('bounties_network', 'Bounties Network'), ('qr', 'QR Code'), ('web3_modal', 'Web3 Modal'), ('polkadot_ext', 'Polkadot Ext'), ('binance_ext', 'Binance Ext'), ('harmony_ext', 'Harmony Ext'), ('rsk_ext', 'RSK Ext'), ('xinfin_ext', 'Xinfin Ext'), ('fiat', 'Fiat'), ('manual', 'Manual')], default='bounties_network', max_length=50),
),
migrations.AlterField(
model_name='bountyfulfillment',
name='payout_type',
field=models.CharField(blank=True, choices=[('bounties_network', 'bounties_network'), ('qr', 'qr'), ('fiat', 'fiat'), ('web3_modal', 'web3_modal'), ('polkadot_ext', 'polkadot_ext'), ('binance_ext', 'binance_ext'), ('harmony_ext', 'harmony_ext'), ('rsk_ext', 'rsk_ext'), ('xinfin_ext', 'xinfin_ext'), ('manual', 'manual')], help_text='payment type used to make the payment', max_length=20, null=True),
),
migrations.AlterField(
model_name='bountyfulfillment',
name='tenant',
field=models.CharField(blank=True, choices=[('BTC', 'BTC'), ('ETH', 'ETH'), ('ETC', 'ETC'), ('ZIL', 'ZIL'), ('CELO', 'CELO'), ('PYPL', 'PYPL'), ('POLKADOT', 'POLKADOT'), ('BINANCE', 'BINANCE'), ('HARMONY', 'HARMONY'), ('FILECOIN', 'FILECOIN'), ('RSK', 'RSK'), ('XINFIN', 'XINFIN'), ('OTHERS', 'OTHERS')], help_text='specific tenant type under the payout_type', max_length=10, null=True),
),
]
3 changes: 3 additions & 0 deletions app/dashboard/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ class Bounty(SuperModel):
('binance_ext', 'Binance Ext'),
('harmony_ext', 'Harmony Ext'),
('rsk_ext', 'RSK Ext'),
('xinfin_ext', 'Xinfin Ext'),
('fiat', 'Fiat'),
('manual', 'Manual')
)
Expand Down Expand Up @@ -1412,6 +1413,7 @@ class BountyFulfillment(SuperModel):
('binance_ext', 'binance_ext'),
('harmony_ext', 'harmony_ext'),
('rsk_ext', 'rsk_ext'),
('xinfin_ext', 'xinfin_ext'),
('manual', 'manual')
]

Expand All @@ -1427,6 +1429,7 @@ class BountyFulfillment(SuperModel):
('HARMONY', 'HARMONY'),
('FILECOIN', 'FILECOIN'),
('RSK', 'RSK'),
('XINFIN', 'XINFIN'),
('OTHERS', 'OTHERS')
]

Expand Down
93 changes: 93 additions & 0 deletions app/dashboard/sync/xinfin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import json

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

import requests
from dashboard.sync.helpers import record_payout_activity, txn_already_used

API_KEY = settings.XINFIN_API_KEY

def find_txn_on_xinfin_explorer(fulfillment):
token_name = fulfillment.token_name

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

if token_name not in ['XDC']:
return None

url = f'https://xdc.network/publicAPI?module=account&action=txlist&address={funderAddress}&page=0&pageSize=10&apikey={API_KEY}'
response = requests.get(url).json()

if response['message'] and response['result']:
for txn in response['result']:
to_address_match = txn['to'].lower() == payeeAddress.lower() if token_name == 'XDC' else True
if (
txn['from'].lower() == funderAddress.lower() and
to_address_match and
float(txn['value']) == float(amount * 10 ** 18) and
not txn_already_used(txn['hash'], token_name)
):
return txn
return None


def get_xinfin_txn_status(fulfillment):

txnid = fulfillment.payout_tx_id
token_name = fulfillment.token_name
funderAddress = fulfillment.bounty.bounty_owner_address
payeeAddress = fulfillment.fulfiller_address

amount = fulfillment.payout_amount


if token_name not in ['XDC']:
return None

if not txnid or txnid == "0x0":
return None

url = f'https://explorer.xinfin.network/publicAPI?module=transaction&action=gettxdetails&txhash={txnid}&apikey={API_KEY}'
response = requests.get(url).json()

if response['status'] == '0':
return 'expired'
elif response['result']:
txn = response['result']

to_address_match = txn['to'].lower() == payeeAddress.lower() if token_name == 'XDC' else True

if (
txn['from'].lower() == funderAddress.lower() and
to_address_match and
float(float(txn['value'])/ 10**18) == float(amount) and
not txn_already_used(txn['hash'], token_name)
):
return 'success'

return None


def sync_xinfin_payout(fulfillment):
if not fulfillment.payout_tx_id or fulfillment.payout_tx_id == "0x0":
txn = find_txn_on_xinfin_explorer(fulfillment)
if txn:
fulfillment.payout_tx_id = txn['hash']
fulfillment.save()

if fulfillment.payout_tx_id and fulfillment.payout_tx_id != "0x0":
txn_status = get_xinfin_txn_status(fulfillment)

if txn_status == 'success':
fulfillment.payout_status = 'done'
fulfillment.accepted_on = timezone.now()
fulfillment.accepted = True
record_payout_activity(fulfillment)

elif txn_status == 'expired':
fulfillment.payout_status = 'expired'

fulfillment.save()
9 changes: 7 additions & 2 deletions app/dashboard/templates/bounty/details2.html
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ <h5 class="font-body font-weight-semibold">{% trans "SUBMISSIONS" %}</h5>
</div>

<!-- WEB3 MODAL FLOW -->
<div v-else-if="fulfillment.payout_type == 'rsk_ext' || fulfillment.payout_type == 'web3_modal' || fulfillment.payout_type == 'polkadot_ext' || fulfillment.payout_type == 'harmony_ext' || fulfillment.payout_type == 'binance_ext'" class="px-sm-4">
<div v-else-if="fulfillment.payout_type == 'xinfin_ext' || fulfillment.payout_type == 'rsk_ext' || fulfillment.payout_type == 'web3_modal' || fulfillment.payout_type == 'polkadot_ext' || fulfillment.payout_type == 'harmony_ext' || fulfillment.payout_type == 'binance_ext'" class="px-sm-4">

<div class="text-center pb-3">
<p class="mb-3 font-subheader font-weight-bold">Payout</p>
Expand Down Expand Up @@ -1125,10 +1125,15 @@ <h3>{{ noscript.keywords }}</h3>
<script src="{% static "v2/js/pages/bounty_detail/harmony_extension.js" %}"></script>

{% elif web3_type == 'rsk_ext' %}

<script src="{% static "v2/js/pages/bounty_detail/rsk_extension.js" %}"></script>

{% elif web3_type == 'xinfin_ext' %}

<script src="{% static "v2/js/lib/xinfin/xdc3.min.js" %}"></script>
<script src="{% static "v2/js/pages/bounty_detail/xinfin_extension.js" %}"></script>

{% elif web3_type == 'fiat' %}

{% if PYPL_CLIENT_ID %}
<script src="https://www.paypal.com/sdk/js?client-id={{PYPL_CLIENT_ID}}"></script>
{% endif %}
Expand Down
4 changes: 4 additions & 0 deletions app/dashboard/templates/bounty/new_bounty.html
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ <h1 class="text-center">Fund Issue</h1>
</label>

{% if is_staff %}
<label class="btn btn-radio chain-btn d-flex align-items-center mr-2 mb-2 font-weight-bold py-2 px-4" :class="{'active': chainId === '50'}">
<input type="radio" name="bounty_chain" id="50_chain" value="50" v-model="chainId"><img class="mr-2" src="{% static 'v2/images/chains/xinfin.svg' %}" alt="" width="16"> Xinfin
</label>

<label class="btn btn-radio chain-btn d-flex align-items-center mr-2 mb-2 font-weight-bold py-2 px-4" :class="{'active': chainId === '717171'}">
<input type="radio" name="bounty_chain" id="717171_chain" value="717171" v-model="chainId"> Other
</label>
Expand Down
Loading

0 comments on commit 1204af8

Please sign in to comment.