diff --git a/app/assets/v2/js/cart-data.js b/app/assets/v2/js/cart-data.js index c7a3ef352e6..d3df9a38cda 100644 --- a/app/assets/v2/js/cart-data.js +++ b/app/assets/v2/js/cart-data.js @@ -106,15 +106,20 @@ class CartData { grantData.grant_donation_currency = 'ZIL'; } } else if (grantData.tenants.includes('HARMONY')) { - if (!grantData.grant_donation_amount) { grantData.grant_donation_amount = 1; } if (!grantData.grant_donation_currency) { grantData.grant_donation_currency = 'ONE'; } + } else if (grantData.tenants.includes('BINANCE')) { + if (!grantData.grant_donation_amount) { + grantData.grant_donation_amount = 1; + } + if (!grantData.grant_donation_currency) { + grantData.grant_donation_currency = 'BNB'; + } } else if (grantData.tenants.includes('POLKADOT')) { - if (!grantData.grant_donation_amount) { grantData.grant_donation_amount = 1; } @@ -122,7 +127,6 @@ class CartData { grantData.grant_donation_currency = 'DOT'; } } else if (grantData.tenants.includes('KUSAMA')) { - if (!grantData.grant_donation_amount) { grantData.grant_donation_amount = 1; } diff --git a/app/assets/v2/js/cart.js b/app/assets/v2/js/cart.js index f41ed9d8aba..f4d9db1ec52 100644 --- a/app/assets/v2/js/cart.js +++ b/app/assets/v2/js/cart.js @@ -335,6 +335,10 @@ Vue.component('grants-cart', { return window.onewallet && window.onewallet.isOneWallet; }, + isBinanceExtInstalled() { + return window.BinanceChain || false; + }, + isPolkadotExtInstalled() { return polkadot_extension_dapp.isWeb3Injected; } @@ -380,6 +384,9 @@ Vue.component('grants-cart', { case 'HARMONY': vm.chainId = '1000'; break; + case 'BINANCE': + vm.chainId = '56'; + break; case 'KUSAMA': vm.chainId = '59'; break; @@ -452,6 +459,9 @@ Vue.component('grants-cart', { case 'HARMONY': contributeWithHarmonyExtension(grant, vm); break; + case 'BINANCE': + contributeWithBinanceExtension(grant, vm); + break; case 'POLKADOT': case 'KUSAMA': if (data) { diff --git a/app/assets/v2/js/grants/_new.js b/app/assets/v2/js/grants/_new.js index 3ab773b2630..aba3a2b1f45 100644 --- a/app/assets/v2/js/grants/_new.js +++ b/app/assets/v2/js/grants/_new.js @@ -119,6 +119,8 @@ Vue.mixin({ vm.$set(vm.errors, 'zil_payout_address', 'Please enter Zilliqa address'); } else if (vm.chainId == 'harmony' && !vm.form.harmony_payout_address) { vm.$set(vm.errors, 'harmony_payout_address', 'Please enter Harmony address'); + } else if (vm.chainId == 'binance' && !vm.form.binance_payout_address) { + vm.$set(vm.errors, 'binance_payout_address', 'Please enter Binance address'); } else if (vm.chainId == 'polkadot' && !vm.form.polkadot_payout_address) { vm.$set(vm.errors, 'polkadot_payout_address', 'Please enter Polkadot address'); } else if (vm.chainId == 'kusama' && !vm.form.kusama_payout_address) { @@ -169,6 +171,7 @@ Vue.mixin({ 'celo_payout_address': form.celo_payout_address, 'zil_payout_address': form.zil_payout_address, 'harmony_payout_address': form.harmony_payout_address, + 'binance_payout_address': form.binance_payout_address, 'polkadot_payout_address': form.polkadot_payout_address, 'kusama_payout_address': form.kusama_payout_address, 'grant_type': form.grant_type, @@ -330,6 +333,7 @@ if (document.getElementById('gc-new-grant')) { celo_payout_address: '', zil_payout_address: '', harmony_payout_address: '', + binance_payout_address: '', polkadot_payout_address: '', kusama_payout_address: '', grant_type: '', diff --git a/app/assets/v2/js/grants/cart/binance_extension.js b/app/assets/v2/js/grants/cart/binance_extension.js new file mode 100644 index 00000000000..f7d6becc57b --- /dev/null +++ b/app/assets/v2/js/grants/cart/binance_extension.js @@ -0,0 +1,92 @@ +const contributeWithBinanceExtension = async (grant, vm) => { + let token_name = grant.grant_donation_currency; + let decimals = vm.filterByChainId.filter(token => { + return token.name == token_name })[0].decimals + let amount = grant.grant_donation_amount * 10 ** decimals; + let to_address = grant.binance_payout_address; + let from_address; + + try { + from_address = await binance_utils.getSelectedAccount(); + } catch (error) { + _alert({ message: `Please ensure your Binance Chain Extension wallet is installed and enabled`}, 'error'); + return; + } + + if (!token_name) { + token_name = 'BNB'; + } + + if (token_name === 'BNB') { + const account_balance = await binance_utils.getAddressBalance(from_address); + + if (Number(account_balance) < amount) { + _alert({ message: `Account needs to have more than ${amount / 10 ** decimals} BNB for payout` }, 'error'); + return; + } + } else if (token_name === 'BUSD') { + const busd_contract_address = '0xe9e7cea3dedca5984780bafc599bd69add087d56' + + const account_balance = await binance_utils.getAddressTokenBalance(from_address, busd_contract_address); + + if (Number(account_balance) < amount ) { + _alert({ message: `Account needs to have more than ${amount / 10 ** decimals} BUSD for payout` }, 'error'); + return; + } + } + + binance_utils.transferViaExtension( + amount, + to_address, + from_address, + token_name + ).then(txn => { + if (txn) { + callback(null, from_address, txn); + } else { + callback('error in signing transaction'); + } + }).catch(err => { + callback(err); + }); + + function callback(error, from_address, txn) { + if (error) { + vm.updatePaymentStatus(grant.grant_id, 'failed'); + _alert({ message: gettext('Unable to contribute to grant due to ' + error) }, 'error'); + console.log(error); + } else { + + const payload = { + 'contributions': [{ + 'grant_id': grant.grant_id, + 'contributor_address': from_address, + 'tx_id': txn, + 'token_symbol': grant.grant_donation_currency, + 'tenant': 'BINANCE', + 'comment': grant.grant_comments, + 'amount_per_period': grant.grant_donation_amount + }] + }; + + const apiUrlGrant = `v1/api/contribute`; + + fetchData(apiUrlGrant, 'POST', JSON.stringify(payload)).then(response => { + if (200 <= response.status && response.status <= 204) { + console.log('success', response); + + vm.updatePaymentStatus(grant.grant_id, 'done', txn); + + } else { + vm.updatePaymentStatus(grant.grant_id, 'failed'); + _alert('Unable to make contribute to grant. Please try again later', 'error'); + console.error(`error: grant contribution failed with status: ${response.status} and message: ${response.message}`); + } + }).catch(function (error) { + vm.updatePaymentStatus(grant.grant_id, 'failed'); + _alert('Unable to make contribute to grant. Please try again later', 'error'); + console.log(error); + }); + } + } +}; \ No newline at end of file diff --git a/app/assets/v2/js/grants/funding.js b/app/assets/v2/js/grants/funding.js index 08bbb84b4d1..0aa4a081297 100644 --- a/app/assets/v2/js/grants/funding.js +++ b/app/assets/v2/js/grants/funding.js @@ -174,6 +174,9 @@ function tokenOptionsForGrant(grant) { } else if (grant.tenants && grant.tenants.includes('HARMONY')) { tokenDataList = tokenDataList.filter(token => token.chainId === 1000); tokenDefault = 'ONE'; + } else if (grant.tenants && grant.tenants.includes('BINANCE')) { + tokenDataList = tokenDataList.filter(token => token.chainId === 56); + tokenDefault = 'BNB'; } else if (grant.tenants && grant.tenants.includes('POLKADOT')) { tokenDataList = tokenDataList.filter(token => token.chainId === 58); tokenDefault = 'DOT'; diff --git a/app/grants/admin.py b/app/grants/admin.py index 1722c99efbb..7c197b41948 100644 --- a/app/grants/admin.py +++ b/app/grants/admin.py @@ -108,8 +108,8 @@ class GrantAdmin(GeneralAdmin): 'link', 'clr_prediction_curve', 'hidden', 'next_clr_calc_date', 'last_clr_calc_date', 'metadata', 'twitter_handle_1', 'twitter_handle_2', 'view_count', 'is_clr_eligible', 'in_active_clrs', 'last_update', 'funding_info', 'twitter_verified', 'twitter_verified_by', 'twitter_verified_at', 'stats_history', - 'zcash_payout_address', 'celo_payout_address','zil_payout_address', 'harmony_payout_address', 'polkadot_payout_address', - 'kusama_payout_address', 'emails' + 'zcash_payout_address', 'celo_payout_address','zil_payout_address', 'harmony_payout_address', 'binance_payout_address', + 'polkadot_payout_address', 'kusama_payout_address', 'emails' ] readonly_fields = [ 'logo_svg_asset', 'logo_asset', diff --git a/app/grants/management/commands/sync_pending_contributions.py b/app/grants/management/commands/sync_pending_contributions.py index 10c9461130f..da9cea256cd 100644 --- a/app/grants/management/commands/sync_pending_contributions.py +++ b/app/grants/management/commands/sync_pending_contributions.py @@ -42,7 +42,7 @@ def handle(self, *args, **options): # Auto expire pending transactions timeout_period = timezone.now() - timedelta(minutes=60) - tenants = ['ZCASH', 'ZIL', 'CELO', 'POLKADOT', 'HARMONY', 'KUSAMA'] + tenants = ['ZCASH', 'ZIL', 'CELO', 'POLKADOT', 'HARMONY', 'BINANCE', 'KUSAMA'] for tenant in tenants: tenant_pending_contributions = pending_contribution.filter(subscription__tenant=tenant) diff --git a/app/grants/migrations/0110_auto_20210208_1839.py b/app/grants/migrations/0110_auto_20210208_1839.py new file mode 100644 index 00000000000..d7a7504d667 --- /dev/null +++ b/app/grants/migrations/0110_auto_20210208_1839.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.4 on 2021-02-08 18:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('grants', '0109_auto_20210203_1419'), + ] + + operations = [ + migrations.AddField( + model_name='grant', + name='binance_payout_address', + field=models.CharField(blank=True, default='0x0', help_text='The binance wallet address where subscription funds will be sent.', max_length=255, null=True), + ), + migrations.AlterField( + model_name='contribution', + name='checkout_type', + field=models.CharField(blank=True, choices=[('eth_std', 'eth_std'), ('eth_zksync', 'eth_zksync'), ('zcash_std', 'zcash_std'), ('celo_std', 'celo_std'), ('zil_std', 'zil_std'), ('polkadot_std', 'polkadot_std'), ('harmony_std', 'harmony_std'), ('binance_std', 'binance_std')], help_text='The checkout method used while making the contribution', max_length=30, null=True), + ), + migrations.AlterField( + model_name='subscription', + name='tenant', + field=models.CharField(blank=True, choices=[('ETH', 'ETH'), ('ZCASH', 'ZCASH'), ('CELO', 'CELO'), ('ZIL', 'ZIL'), ('POLKADOT', 'POLKADOT'), ('KUSAMA', 'KUSAMA'), ('HARMONY', 'HARMONY'), ('BINANCE', 'BINANCE')], default='ETH', help_text='specific tenant in which contribution is made', max_length=10, null=True), + ), + ] diff --git a/app/grants/models.py b/app/grants/models.py index fc4116b32a9..9601b09de01 100644 --- a/app/grants/models.py +++ b/app/grants/models.py @@ -352,6 +352,13 @@ class Meta: blank=True, help_text=_('The harmony wallet address where subscription funds will be sent.'), ) + binance_payout_address = models.CharField( + max_length=255, + default='0x0', + null=True, + blank=True, + help_text=_('The binance wallet address where subscription funds will be sent.'), + ) # TODO-GRANTS: remove contract_owner_address = models.CharField( max_length=255, @@ -572,6 +579,8 @@ def tenants(self): tenants.append('KUSAMA') if self.harmony_payout_address and self.harmony_payout_address != '0x0': tenants.append('HARMONY') + if self.binance_payout_address and self.binance_payout_address != '0x0': + tenants.append('BINANCE') return tenants @@ -804,6 +813,8 @@ def cart_payload(self, build_absolute_uri): 'celo_payout_address': self.celo_payout_address, 'zil_payout_address': self.zil_payout_address, 'polkadot_payout_address': self.polkadot_payout_address, + 'harmony_payout_address': self.harmony_payout_address, + 'binance_payout_address': self.binance_payout_address, 'kusama_payout_address': self.kusama_payout_address, 'harmony_payout_address': self.harmony_payout_address } @@ -861,6 +872,7 @@ def repr(self, user, build_absolute_uri): 'polkadot_payout_address': self.polkadot_payout_address, 'kusama_payout_address': self.kusama_payout_address, 'harmony_payout_address': self.harmony_payout_address, + 'binance_payout_address': self.binance_payout_address, 'token_address': self.token_address, 'image_css': self.image_css, 'verified': self.twitter_verified, @@ -914,7 +926,8 @@ class Subscription(SuperModel): ('ZIL', 'ZIL'), ('POLKADOT', 'POLKADOT'), ('KUSAMA', 'KUSAMA'), - ('HARMONY', 'HARMONY') + ('HARMONY', 'HARMONY'), + ('BINANCE', 'BINANCE'), ] active = models.BooleanField(default=True, db_index=True, help_text=_('Whether or not the Subscription is active.')) @@ -1575,7 +1588,8 @@ class Contribution(SuperModel): ('celo_std', 'celo_std'), ('zil_std', 'zil_std'), ('polkadot_std', 'polkadot_std'), - ('harmony_std', 'harmony_std') + ('harmony_std', 'harmony_std'), + ('binance_std', 'binance_std') ] success = models.BooleanField(default=True, help_text=_('Whether or not success.')) diff --git a/app/grants/sync/binance.py b/app/grants/sync/binance.py new file mode 100644 index 00000000000..e4cc6ad5cd2 --- /dev/null +++ b/app/grants/sync/binance.py @@ -0,0 +1,64 @@ +import requests +from grants.sync.helpers import record_contribution_activity + + +def get_binance_txn_status(contribution): + txnid = contribution.tx_id + + if not txnid or txnid == "0x0": + return None + + response = { 'status': 'pending' } + + try: + binance_url = f'https://bsc-dataseed.binance.org' + + data = { + 'id': 0, + 'jsonrpc': '2.0', + 'method': 'eth_getTransactionReceipt', + 'params': [ txnid ] + } + + headers = { + 'Host': 'gitcoin.co' + } + + binance_response = requests.post(binance_url, json=data).json() + + result = binance_response['result'] + + response = { 'status': 'pending' } + + if result: + tx_status = int(result.get('status'), 16) # convert hex to decimal + + if tx_status == 1: + response = { 'status': 'done' } + elif tx_status == 0: + response = { 'status': 'expired' } + + except Exception as e: + logger.error(f'error: get_binance_txn_status - {e}') + + finally: + return response + + +def sync_binance_payout(contribution): + if contribution.tx_id or contribution.tx_id == "0x0": + txn_status = get_binance_txn_status(contribution) + + if txn_status: + status_description = txn_status.get('status') + + if status_description == 'done': + contribution.success = True + contribution.tx_cleared = True + contribution.checkout_type = 'binance_std' + record_contribution_activity(contribution) + contribution.save() + elif status_description == 'expired': + contribution.success = True + contribution.tx_cleared = False + contribution.save() diff --git a/app/grants/templates/grants/_new.html b/app/grants/templates/grants/_new.html index 5bba7fc5dcb..c61237ff664 100644 --- a/app/grants/templates/grants/_new.html +++ b/app/grants/templates/grants/_new.html @@ -221,6 +221,10 @@