diff --git a/app/assets/v2/images/chains/nervos.svg b/app/assets/v2/images/chains/nervos.svg new file mode 100644 index 00000000000..3beec50b1cc --- /dev/null +++ b/app/assets/v2/images/chains/nervos.svg @@ -0,0 +1 @@ +nervos-network-ckb-logo \ 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 a311001458a..df33f6a7f3e 100644 --- a/app/assets/v2/js/pages/bounty_details2.js +++ b/app/assets/v2/js/pages/bounty_details2.js @@ -144,6 +144,10 @@ Vue.mixin({ url = `https://explorer.rsk.co/tx/${txn}`; break; + case 'CKB': + url = `https://explorer.nervos.org/transaction/${txn}`; + break; + case 'XDC': url = `https://explorer.xinfin.network/tx/${txn}`; break; @@ -214,6 +218,10 @@ Vue.mixin({ url = `https://explorer.rsk.co/address/${address}`; break; + case 'CKB': + url = `https://explorer.nervos.org/address/${address}`; + break; + case 'XDC': url = `https://explorer.xinfin.network/addr/${address}`; break; @@ -452,6 +460,11 @@ Vue.mixin({ tenant = 'RSK'; break; + case 'CKB': + tenant = 'NERVOS'; + vm.canChangeFunderAddress = true; + break; + case 'XDC': tenant = 'XINFIN'; break; @@ -768,6 +781,7 @@ Vue.mixin({ switch (fulfillment.payout_type) { case 'qr': case 'manual': + case 'nervos_ext': case 'sia_ext': vm.fulfillment_context.active_step = 'check_wallet_owner'; vm.getTenant(vm.bounty.token_name, fulfillment.payout_type); @@ -826,7 +840,18 @@ Vue.mixin({ vm.errors = {}; - // include validation for tokens here - switch statement + switch (token_name) { + case 'CKB': { + const ADDRESS_REGEX = new RegExp('^(ckb){1}[0-9a-zA-Z]{43,92}$'); + const isNervosValid = ADDRESS_REGEX.test(vm.bounty.bounty_owner_address); + + if (!isNervosValid && !address.toLowerCase().startsWith('0x')) { + hasError = true; + } + } + + // include validation for other tokens here + } if (hasError) { vm.$set(vm.errors, 'funderAddress', `Please enter a valid ${token_name} address`); diff --git a/app/assets/v2/js/pages/fulfill_bounty/token.js b/app/assets/v2/js/pages/fulfill_bounty/token.js index 5e1a83c9720..bad488d2981 100644 --- a/app/assets/v2/js/pages/fulfill_bounty/token.js +++ b/app/assets/v2/js/pages/fulfill_bounty/token.js @@ -96,20 +96,29 @@ const is_valid_address = (address) => { } return true; - case 'polkadot_ext': if (address.toLowerCase().startsWith('0x')) { return false; } return true; - case 'xinfin_ext': if (!address.toLowerCase().startsWith('xdc')) { return false; } return true; + case 'nervos_ext': { + const ADDRESS_REGEX = new RegExp('^(ckb){1}[0-9a-zA-Z]{43,92}$'); + const isNervosValid = ADDRESS_REGEX.test(address); + + if (isNervosValid || address.toLowerCase().startsWith('0x')) { + return true; + } + + return false; + } + case 'qr': if (token_name == 'BTC') { diff --git a/app/assets/v2/js/pages/hackathon_new_bounty.js b/app/assets/v2/js/pages/hackathon_new_bounty.js index bcb4396d016..1ed4ef331de 100644 --- a/app/assets/v2/js/pages/hackathon_new_bounty.js +++ b/app/assets/v2/js/pages/hackathon_new_bounty.js @@ -170,6 +170,10 @@ Vue.mixin({ // harmony type = 'harmony_ext'; break; + case '1995': + // nervos + type = 'nervos_ext'; + break; case '1001': // algorand type = 'algorand_ext'; diff --git a/app/assets/v2/js/pages/new_bounty.js b/app/assets/v2/js/pages/new_bounty.js index bd9650ec00d..393cd0690c1 100644 --- a/app/assets/v2/js/pages/new_bounty.js +++ b/app/assets/v2/js/pages/new_bounty.js @@ -210,6 +210,10 @@ Vue.mixin({ // harmony type = 'harmony_ext'; break; + case '1995': + // nervos + type = 'nervos_ext'; + break; case '1001': // algorand type = 'algorand_ext'; diff --git a/app/dashboard/management/commands/sync_pending_fulfillments.py b/app/dashboard/management/commands/sync_pending_fulfillments.py index b7ad91668a1..18f5b0d9136 100644 --- a/app/dashboard/management/commands/sync_pending_fulfillments.py +++ b/app/dashboard/management/commands/sync_pending_fulfillments.py @@ -36,7 +36,7 @@ def handle(self, *args, **options): ) # Extensions - ext_payout_types= ['web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'algorand_ext', 'sia_ext'] + ext_payout_types= ['web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'nervos_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(): diff --git a/app/dashboard/models.py b/app/dashboard/models.py index bea7355c39d..4733260e11a 100644 --- a/app/dashboard/models.py +++ b/app/dashboard/models.py @@ -296,6 +296,7 @@ class Bounty(SuperModel): ('harmony_ext', 'Harmony Ext'), ('rsk_ext', 'RSK Ext'), ('xinfin_ext', 'Xinfin Ext'), + ('nervos_ext', 'Nervos Ext'), ('algorand_ext', 'Algorand Ext'), ('sia_ext', 'Sia Ext'), ('fiat', 'Fiat'), @@ -1416,6 +1417,7 @@ class BountyFulfillment(SuperModel): ('harmony_ext', 'harmony_ext'), ('rsk_ext', 'rsk_ext'), ('xinfin_ext', 'xinfin_ext'), + ('nervos_ext', 'nervos_ext'), ('algorand_ext', 'algorand_ext'), ('sia_ext', 'sia_ext'), ('manual', 'manual') @@ -1434,6 +1436,7 @@ class BountyFulfillment(SuperModel): ('FILECOIN', 'FILECOIN'), ('RSK', 'RSK'), ('XINFIN', 'XINFIN'), + ('NERVOS', 'NERVOS'), ('ALGORAND', 'ALGORAND'), ('SIA', 'SIA'), ('OTHERS', 'OTHERS') diff --git a/app/dashboard/sync/nervos.py b/app/dashboard/sync/nervos.py new file mode 100644 index 00000000000..b90858c800f --- /dev/null +++ b/app/dashboard/sync/nervos.py @@ -0,0 +1,47 @@ +from django.utils import timezone + +import requests +from dashboard.sync.helpers import record_payout_activity + +HEADERS = { + 'Content-Type': 'application/vnd.api+json', + 'Accept': 'application/vnd.api+json' +} + + +def get_nervos_txn_status(txnid, network='mainnet'): + if not txnid: + return None + + if network == 'mainnet': + base_url = 'https://api.explorer.nervos.org/api/v1' + else: + base_url = 'https://api.explorer.nervos.org/testnet/api/v1' + + explorer_url = f'{base_url}/transactions/{txnid}' + tip_block_number_url = f'{base_url}/statistics/tip_block_number' + + tx_response = requests.get(explorer_url, headers=HEADERS) + + if tx_response.status_code == 200: + tx_data = tx_response.json()['data']['attributes'] + tip_block_number = requests.get( + tip_block_number_url, headers=HEADERS + ).json()['data']['attributes']['tip_block_number'] + confirmations = tip_block_number - int(tx_data['block_number']) + + if confirmations > 0 and tx_data['tx_status'] == 'committed': + return True + else: + return False + + +def sync_nervos_payout(fulfillment): + if fulfillment.payout_tx_id and fulfillment.payout_tx_id != "0x0": + txn_status = get_nervos_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/new_bounty.html b/app/dashboard/templates/bounty/new_bounty.html index 61908f697a6..fb368f9e403 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 b90a4ce0073..25fdcab9e8f 100644 --- a/app/dashboard/templates/dashboard/hackathon/new_bounty.html +++ b/app/dashboard/templates/dashboard/hackathon/new_bounty.html @@ -187,6 +187,10 @@

Fund Prize

Xinfin + + @@ -199,6 +203,7 @@

Fund Prize

Other +
[[errors.chainId]] diff --git a/app/dashboard/utils.py b/app/dashboard/utils.py index 497485423ee..62ea9d3d1aa 100644 --- a/app/dashboard/utils.py +++ b/app/dashboard/utils.py @@ -47,6 +47,7 @@ from dashboard.sync.eth import sync_eth_payout from dashboard.sync.filecoin import sync_filecoin_payout from dashboard.sync.harmony import sync_harmony_payout +from dashboard.sync.nervos import sync_nervos_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 @@ -670,6 +671,9 @@ def sync_payout(fulfillment): elif fulfillment.payout_type == 'xinfin_ext': sync_xinfin_payout(fulfillment) + elif fulfillment.payout_type == 'nervos_ext': + sync_nervos_payout(fulfillment) + elif fulfillment.payout_type == 'algorand_ext': sync_algorand_payout(fulfillment) diff --git a/app/dashboard/views.py b/app/dashboard/views.py index a88e40c9e32..dd45e2e85ff 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', 'sia_ext'] and not fulfiller_address: + elif payout_type in ['qr', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'nervos_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', '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' + if payout_type not in ['fiat', 'qr', 'web3_modal', 'polkadot_ext', 'harmony_ext' , 'binance_ext', 'rsk_ext', 'xinfin_ext', 'nervos_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 / nervos_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', 'sia_ext']: + elif payout_type in ['qr', 'web3_modal', 'polkadot_ext', 'harmony_ext', 'binance_ext', 'rsk_ext', 'xinfin_ext', 'nervos_ext', 'algorand_ext', 'sia_ext']: fulfillment.payout_status = 'pending' fulfillment.save() sync_payout(fulfillment)