diff --git a/app/assets/v2/images/chains/sia.svg b/app/assets/v2/images/chains/sia.svg new file mode 100644 index 00000000000..706198cce68 --- /dev/null +++ b/app/assets/v2/images/chains/sia.svg @@ -0,0 +1 @@ +siacoin \ No newline at end of file diff --git a/app/assets/v2/js/pages/bounty_details2.js b/app/assets/v2/js/pages/bounty_details2.js index 5131771e039..a311001458a 100644 --- a/app/assets/v2/js/pages/bounty_details2.js +++ b/app/assets/v2/js/pages/bounty_details2.js @@ -154,6 +154,10 @@ Vue.mixin({ url = `https://algoexplorer.io/tx/${txn}`; break; + case 'SC': + url = `https://siastats.info/navigator?search=${txn}`; + break; + default: url = `https://etherscan.io/tx/${txn}`; @@ -220,6 +224,10 @@ Vue.mixin({ url = `https://algoexplorer.io/tx/${address}`; break; + case 'SC': + url = `https://siastats.info/navigator?search=${address}`; + break; + default: url = `https://etherscan.io/address/${address}`; } @@ -392,6 +400,7 @@ Vue.mixin({ }, getTenant: function(token_name, web3_type) { let tenant; + let vm = this; if (web3_type == 'manual') { tenant = 'OTHERS'; @@ -453,6 +462,11 @@ Vue.mixin({ tenant = 'ALGORAND'; break; + case 'SC': + tenant = 'SIA'; + vm.canChangeFunderAddress = true; + break; + default: tenant = 'ETH'; } @@ -754,7 +768,9 @@ Vue.mixin({ switch (fulfillment.payout_type) { case 'qr': case 'manual': + case 'sia_ext': vm.fulfillment_context.active_step = 'check_wallet_owner'; + vm.getTenant(vm.bounty.token_name, fulfillment.payout_type); break; case 'fiat': @@ -803,6 +819,18 @@ Vue.mixin({ vm.fulfillment_context.active_step = 'payout_amount'; break; } + }, + validateFunderAddress: function(token_name) { + let vm = this; + let hasError = false; + + vm.errors = {}; + + // include validation for tokens here - switch statement + + if (hasError) { + vm.$set(vm.errors, 'funderAddress', `Please enter a valid ${token_name} address`); + } } }, computed: { @@ -844,6 +872,7 @@ if (document.getElementById('gc-bounty-detail')) { el: '#gc-bounty-detail', data() { return { + errors: {}, loadingState: loadingState['loading'], bounty: bounty, url: url, @@ -859,7 +888,8 @@ if (document.getElementById('gc-bounty-detail')) { inputBountyOwnerAddress: bounty.bounty_owner_address, contxt: document.contxt, quickLinks: [], - pollInterval: null + pollInterval: null, + canChangeFunderAddress: false }; }, mounted() { diff --git a/app/assets/v2/js/pages/hackathon_new_bounty.js b/app/assets/v2/js/pages/hackathon_new_bounty.js index 3c1e6b5205e..bcb4396d016 100644 --- a/app/assets/v2/js/pages/hackathon_new_bounty.js +++ b/app/assets/v2/js/pages/hackathon_new_bounty.js @@ -174,6 +174,10 @@ Vue.mixin({ // algorand type = 'algorand_ext'; break; + case '1935': + // sia + type = 'sia_ext'; + break; case '666': // paypal type = 'fiat'; diff --git a/app/assets/v2/js/pages/new_bounty.js b/app/assets/v2/js/pages/new_bounty.js index a20a430274d..bd9650ec00d 100644 --- a/app/assets/v2/js/pages/new_bounty.js +++ b/app/assets/v2/js/pages/new_bounty.js @@ -214,6 +214,10 @@ Vue.mixin({ // algorand type = 'algorand_ext'; break; + case '1935': + // sia + type = 'sia_ext'; + break; case '666': // paypal type = 'fiat'; diff --git a/app/dashboard/management/commands/sync_pending_fulfillments.py b/app/dashboard/management/commands/sync_pending_fulfillments.py index 39b3dc63009..b7ad91668a1 100644 --- a/app/dashboard/management/commands/sync_pending_fulfillments.py +++ b/app/dashboard/management/commands/sync_pending_fulfillments.py @@ -36,13 +36,12 @@ def handle(self, *args, **options): ) # Extensions - ext_payout_types= ['web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext'] + ext_payout_types= ['web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext', 'sia_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(): sync_payout(fulfillment) - # QR qr_pending_fulfillments = pending_fulfillments.filter(payout_type='qr') if qr_pending_fulfillments: diff --git a/app/dashboard/models.py b/app/dashboard/models.py index 015d06cce83..bea7355c39d 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -297,6 +297,7 @@ class Bounty(SuperModel): ('rsk_ext', 'RSK Ext'), ('xinfin_ext', 'Xinfin Ext'), ('algorand_ext', 'Algorand Ext'), + ('sia_ext', 'Sia Ext'), ('fiat', 'Fiat'), ('manual', 'Manual') ) @@ -1416,6 +1417,7 @@ class BountyFulfillment(SuperModel): ('rsk_ext', 'rsk_ext'), ('xinfin_ext', 'xinfin_ext'), ('algorand_ext', 'algorand_ext'), + ('sia_ext', 'sia_ext'), ('manual', 'manual') ] @@ -1433,6 +1435,7 @@ class BountyFulfillment(SuperModel): ('RSK', 'RSK'), ('XINFIN', 'XINFIN'), ('ALGORAND', 'ALGORAND'), + ('SIA', 'SIA'), ('OTHERS', 'OTHERS') ] diff --git a/app/dashboard/sync/sia.py b/app/dashboard/sync/sia.py new file mode 100644 index 00000000000..96d4d422aa0 --- /dev/null +++ b/app/dashboard/sync/sia.py @@ -0,0 +1,74 @@ +from django.utils import timezone + +import requests +from dashboard.sync.helpers import record_payout_activity, txn_already_used + +BASE_URL = 'https://siastats.info:3500/navigator-api' + + +def find_txn_on_sia_explorer(fulfillment): + token_name = fulfillment.token_name + + funderAddress = fulfillment.funder_address + amount = fulfillment.payout_amount + + if token_name != 'SC': + return None + + url = f'{BASE_URL}/hash/{funderAddress}' + + response = requests.get(url).json() + + if response: + last100_txns = response[1]['last100Transactions'] + if response[0]['Type'] == 'address' and last100_txns: + for txn in last100_txns: + if ( + txn['TxType'] == 'ScTx' + and txn['ScChange'] < 0 + and abs(txn['ScChange']) / 10 ** 24 == amount / 10 ** 24 + and not txn_already_used(txn['MasterHash'], token_name) + ): + return txn + return None + + +def get_sia_txn_status(txnid, network='mainnet'): + if not txnid: + return None + + tx_url = f'{BASE_URL}/hash/{txnid}' + stats_url = f'{BASE_URL}/status' + + tx_response = requests.get(tx_url).json() + + if tx_response: + last_block = requests.get(stats_url).json()[0]['lastblock'] # or consensusblock ? + + confirmations = last_block - tx_response[1]['Height'] + + if ( + tx_response[0]['Type'] == 'ScTx' + and tx_response[0]['MasterHash'].strip() == txnid + and confirmations > 0 + ): + return True + else: + return False + + +def sync_sia_payout(fulfillment): + if not fulfillment.payout_tx_id or fulfillment.payout_tx_id == "0x0": + txn = find_txn_on_sia_explorer(fulfillment) + if txn: + fulfillment.payout_tx_id = txn['MasterHash'] + fulfillment.save() + + if fulfillment.payout_tx_id and fulfillment.payout_tx_id != "0x0": + txn_status = get_sia_txn_status(fulfillment.payout_tx_id) + if txn_status: + fulfillment.payout_status = 'done' + fulfillment.accepted_on = timezone.now() + fulfillment.accepted = True + record_payout_activity(fulfillment) + fulfillment.save() diff --git a/app/dashboard/templates/bounty/details2.html b/app/dashboard/templates/bounty/details2.html index 76eb47bcb0f..12eeb736438 100644 --- a/app/dashboard/templates/bounty/details2.html +++ b/app/dashboard/templates/bounty/details2.html @@ -530,6 +530,7 @@
{% trans "SUBMISSIONS" %}

This bounty can only be paid out from the same wallet address that funded it.

+

Make sure you’re paying out from this address @@ -537,7 +538,6 @@

{% trans "SUBMISSIONS" %}
[[ bounty.bounty_owner_address ]] -

@@ -645,29 +645,45 @@

{% trans "SUBMISSIONS" %}
- -

+

+ + +
+ [[ errors.funderAddress ]] +
+
+

This bounty can only be paid out from the same wallet address that funded it.

-

+
+

Make sure you’re paying out from this address - [[ bounty.bounty_owner_address | truncateHash ]] + [[ bounty.bounty_owner_address | truncateHash ]] [[ bounty.bounty_owner_address ]]

- -

- - I don’t have access to this address - -

+
+ +
+
+ +

+ + I don’t have access to this address + +

+
@@ -801,7 +817,7 @@
{% trans "SUBMISSIONS" %}

diff --git a/app/dashboard/templates/bounty/new_bounty.html b/app/dashboard/templates/bounty/new_bounty.html index aae9d6f74b3..61908f697a6 100644 --- a/app/dashboard/templates/bounty/new_bounty.html +++ b/app/dashboard/templates/bounty/new_bounty.html @@ -127,6 +127,10 @@

Fund Issue

{% if is_staff %} + + diff --git a/app/dashboard/templates/dashboard/hackathon/new_bounty.html b/app/dashboard/templates/dashboard/hackathon/new_bounty.html index 1aeb4730fc4..b90a4ce0073 100644 --- a/app/dashboard/templates/dashboard/hackathon/new_bounty.html +++ b/app/dashboard/templates/dashboard/hackathon/new_bounty.html @@ -191,6 +191,10 @@

Fund Prize

Algorand + + diff --git a/app/dashboard/utils.py b/app/dashboard/utils.py index de5b05fb0e0..497485423ee 100644 --- a/app/dashboard/utils.py +++ b/app/dashboard/utils.py @@ -49,6 +49,7 @@ from dashboard.sync.harmony import sync_harmony_payout from dashboard.sync.polkadot import sync_polkadot_payout from dashboard.sync.rsk import sync_rsk_payout +from dashboard.sync.sia import sync_sia_payout from dashboard.sync.xinfin import sync_xinfin_payout from dashboard.sync.zil import sync_zil_payout from ens.auto import ns @@ -672,6 +673,9 @@ def sync_payout(fulfillment): elif fulfillment.payout_type == 'algorand_ext': sync_algorand_payout(fulfillment) + elif fulfillment.payout_type == 'sia_ext': + sync_sia_payout(fulfillment) + def get_bounty_id(issue_url, network): issue_url = normalize_url(issue_url) diff --git a/app/dashboard/views.py b/app/dashboard/views.py index 8b220ee4d87..a88e40c9e32 100644 --- a/app/dashboard/views.py +++ b/app/dashboard/views.py @@ -6153,7 +6153,7 @@ def fulfill_bounty_v1(request): if payout_type == 'fiat' and not fulfiller_identifier: response['message'] = 'error: missing fulfiller_identifier' return JsonResponse(response) - elif payout_type in ['qr', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext'] and not fulfiller_address: + elif payout_type in ['qr', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext', 'sia_ext'] and not fulfiller_address: response['message'] = 'error: missing fulfiller_address' return JsonResponse(response) @@ -6270,8 +6270,8 @@ def payout_bounty_v1(request, fulfillment_id): if not payout_type: response['message'] = 'error: missing parameter payout_type' return JsonResponse(response) - if payout_type not in ['fiat', 'qr', 'web3_modal', 'polkadot_ext', 'harmony_ext' , 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext', 'manual']: - response['message'] = 'error: parameter payout_type must be fiat / qr / web_modal / polkadot_ext / harmony_ext / binance_ext / rsk_ext / xinfin_ext / algorand_ext / manual' + if payout_type not in ['fiat', 'qr', 'web3_modal', 'polkadot_ext', 'harmony_ext' , 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext', 'sia_ext', 'manual']: + response['message'] = 'error: parameter payout_type must be fiat / qr / web_modal / polkadot_ext / harmony_ext / binance_ext / rsk_ext / xinfin_ext / algorand_ext / sia_ext / manual' return JsonResponse(response) if payout_type == 'manual' and not bounty.event: response['message'] = 'error: payout_type manual is eligible only for hackathons' @@ -6337,7 +6337,7 @@ def payout_bounty_v1(request, fulfillment_id): fulfillment.save() record_bounty_activity(bounty, user, 'worker_paid', None, fulfillment) - elif payout_type in ['qr', 'web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext']: + elif payout_type in ['qr', 'web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext', 'sia_ext']: fulfillment.payout_status = 'pending' fulfillment.save() sync_payout(fulfillment)