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 @@ + \ 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 @@