def grant_fund(request, grant_id, grant_slug): """Handle grant funding.""" try: grant = Grant.objects.get(pk=grant_id, slug=grant_slug) except Grant.DoesNotExist: raise Http404 profile = get_profile(request) if not grant.active: params = { 'active': 'grant_error', 'title': _('Fund - Grant Ended'), 'grant': grant, 'text': _('This Grant has ended.'), 'subtext': _('Contributions can no longer be made this grant') } return TemplateResponse(request, 'grants/shared/error.html', params) if is_grant_team_member(grant, profile): params = { 'active': 'grant_error', 'title': _('Fund - Grant funding blocked'), 'grant': grant, 'text': _('This Grant cannot be funded'), 'subtext': _('Grant team members cannot contribute to their own grant.') } return TemplateResponse(request, 'grants/shared/error.html', params) if grant.link_to_new_grant: params = { 'active': 'grant_error', 'title': _('Fund - Grant Migrated'), 'grant': grant.link_to_new_grant, 'text': f'This Grant has ended', 'subtext': 'Contributions can no longer be made to this grant. <br> Visit the new grant to contribute.', 'button_txt': 'View New Grant' } return TemplateResponse(request, 'grants/shared/error.html', params) active_subscription = Subscription.objects.select_related('grant').filter( grant=grant_id, active=True, error=False, contributor_profile=request.user.profile ) if active_subscription: params = { 'active': 'grant_error', 'title': _('Subscription Exists'), 'grant': grant, 'text': _('You already have an active subscription for this grant.') } return TemplateResponse(request, 'grants/shared/error.html', params) if grant.contract_address == '0x0': messages.info( request, _('This grant is not configured to accept funding at this time. Please contact [email protected] if you believe this message is in error!') ) logger.error(f"Grant {grant.pk} is not properly configured for funding. Please set grant.contract_address on this grant") return redirect(reverse('grants:details', args=(grant.pk, grant.slug))) if request.method == 'POST': if 'contributor_address' in request.POST: subscription = Subscription() subscription.active = False subscription.contributor_address = request.POST.get('contributor_address', '') subscription.amount_per_period = request.POST.get('amount_per_period', 0) subscription.real_period_seconds = request.POST.get('real_period_seconds', 2592000) subscription.frequency = request.POST.get('frequency', 30) subscription.frequency_unit = request.POST.get('frequency_unit', 'days') subscription.token_address = request.POST.get('token_address', '') subscription.token_symbol = request.POST.get('token_symbol', '') subscription.gas_price = request.POST.get('gas_price', 0) subscription.new_approve_tx_id = request.POST.get('sub_new_approve_tx_id', '0x0') subscription.num_tx_approved = request.POST.get('num_tx_approved', 1) subscription.network = request.POST.get('network', '') subscription.contributor_profile = profile subscription.grant = grant subscription.comments = request.POST.get('comment', '') subscription.save() # one time payments activity = None if int(subscription.num_tx_approved) == 1: subscription.successful_contribution(subscription.new_approve_tx_id); subscription.error = True #cancel subs so it doesnt try to bill again subscription.subminer_comments = "skipping subminer bc this is a 1 and done subscription, and tokens were alredy sent" subscription.save() activity = record_subscription_activity_helper('new_grant_contribution', subscription, profile) else: activity = record_subscription_activity_helper('new_grant_subscription', subscription, profile) if 'comment' in request.POST: comment = request.POST.get('comment') if comment and activity: comment = Comment.objects.create( profile=request.user.profile, activity=activity, comment=comment) # TODO - how do we attach the tweet modal WITH BULK TRANSFER COUPON next pageload?? messages.info( request, _('Your subscription has been created. It will bill within the next 5 minutes or so. Thank you for supporting Open Source !') ) return JsonResponse({ 'success': True, }) if 'hide_wallet_address' in request.POST: profile.hide_wallet_address = bool(request.POST.get('hide_wallet_address', False)) profile.save() if 'signature' in request.POST: sub_new_approve_tx_id = request.POST.get('sub_new_approve_tx_id', '') subscription = Subscription.objects.filter(new_approve_tx_id=sub_new_approve_tx_id).first() subscription.active = True subscription.subscription_hash = request.POST.get('subscription_hash', '') subscription.contributor_signature = request.POST.get('signature', '') if 'split_tx_id' in request.POST: subscription.split_tx_id = request.POST.get('split_tx_id', '') subscription.save_split_tx_to_contribution() if 'split_tx_confirmed' in request.POST: subscription.split_tx_confirmed = bool(request.POST.get('split_tx_confirmed', False)) subscription.save_split_tx_to_contribution() subscription.save() value_usdt = subscription.get_converted_amount() if value_usdt: grant.monthly_amount_subscribed += subscription.get_converted_monthly_amount() grant.save() new_supporter(grant, subscription) thank_you_for_supporting(grant, subscription) return JsonResponse({ 'success': True, 'url': reverse('grants:details', args=(grant.pk, grant.slug)) }) splitter_contract_address = settings.SPLITTER_CONTRACT_ADDRESS # handle phantom funding active_tab = 'normal' fund_reward = None round_number = 4 can_phantom_fund = request.user.is_authenticated and request.user.groups.filter(name='phantom_funders').exists() and clr_active phantom_funds = PhantomFunding.objects.filter(profile=request.user.profile, round_number=round_number).order_by('created_on').nocache() if request.user.is_authenticated else PhantomFunding.objects.none() is_phantom_funding_this_grant = can_phantom_fund and phantom_funds.filter(grant=grant).exists() show_tweet_modal = False if can_phantom_fund: active_tab = 'phantom' if can_phantom_fund and request.POST.get('toggle_phantom_fund'): if is_phantom_funding_this_grant: msg = "You are no longer signaling for this grant." phantom_funds.filter(grant=grant).delete() else: msg = "You are now signaling for this grant." show_tweet_modal = True name_search = 'grants_round_4_contributor' if not settings.DEBUG else 'pogs_eth' fund_reward = BulkTransferCoupon.objects.filter(token__name__contains=name_search).order_by('?').first() PhantomFunding.objects.create(grant=grant, profile=request.user.profile, round_number=round_number) record_grant_activity_helper('new_grant_contribution', grant, request.user.profile) messages.info( request, msg ) is_phantom_funding_this_grant = not is_phantom_funding_this_grant params = { 'profile': profile, 'active': 'fund_grant', 'title': _('Fund Grant'), 'card_desc': _('Provide sustainable funding for Open Source with Gitcoin Grants'), 'subscription': {}, 'show_tweet_modal': show_tweet_modal, 'grant_has_no_token': True if grant.token_address == '0x0000000000000000000000000000000000000000' else False, 'grant': grant, 'clr_prediction_curve': [c[1] for c in grant.clr_prediction_curve] if grant.clr_prediction_curve and len(grant.clr_prediction_curve[0]) > 1 else [0, 0, 0, 0, 0, 0], 'keywords': get_keywords(), 'recommend_gas_price': recommend_min_gas_price_to_confirm_in_time(4), 'recommend_gas_price_slow': recommend_min_gas_price_to_confirm_in_time(120), 'recommend_gas_price_avg': recommend_min_gas_price_to_confirm_in_time(15), 'recommend_gas_price_fast': recommend_min_gas_price_to_confirm_in_time(1), 'eth_usd_conv_rate': eth_usd_conv_rate(), 'conf_time_spread': conf_time_spread(), 'gas_advisories': gas_advisories(), 'splitter_contract_address': settings.SPLITTER_CONTRACT_ADDRESS, 'gitcoin_donation_address': settings.GITCOIN_DONATION_ADDRESS, 'can_phantom_fund': can_phantom_fund, 'is_phantom_funding_this_grant': is_phantom_funding_this_grant, 'active_tab': active_tab, 'fund_reward': fund_reward, 'phantom_funds': phantom_funds, 'clr_round': clr_round, 'clr_active': clr_active, 'total_clr_pot': total_clr_pot, } return TemplateResponse(request, 'grants/fund.html', params)
def process_grant_contribution(self, grant_id, grant_slug, profile_id, package, retry: bool = True) -> None: """ :param self: :param grant_id: :param grant_slug: :param profile_id: :param package: :return: """ grant = Grant.objects.get(pk=grant_id) profile = Profile.objects.get(pk=profile_id) if 'contributor_address' in package: subscription = Subscription() if grant.negative_voting_enabled: #is_postive_vote = True if package.get('is_postive_vote', 1) else False is_postive_vote = package.get('match_direction', '+') == '+' else: is_postive_vote = True subscription.is_postive_vote = is_postive_vote fee_pct = float(package.get('gitcoin-grant-input-amount', 0)) subscription.active = False subscription.contributor_address = package.get('contributor_address', '') subscription.amount_per_period = package.get('amount_per_period', 0) subscription.real_period_seconds = package.get('real_period_seconds', 2592000) subscription.frequency = package.get('frequency', 30) subscription.frequency_unit = package.get('frequency_unit', 'days') subscription.token_address = package.get('token_address', '') subscription.token_symbol = package.get('token_symbol', '') subscription.gas_price = (float(subscription.amount_per_period) * (fee_pct / 100)) subscription.new_approve_tx_id = package.get('sub_new_approve_tx_id', '0x0') subscription.split_tx_id = package.get('split_tx_id', '0x0') subscription.num_tx_approved = package.get('num_tx_approved', 1) subscription.network = package.get('network', '') subscription.contributor_profile = profile subscription.grant = grant subscription.comments = package.get('comment', '') subscription.save() # one time payments activity = None if int(subscription.num_tx_approved) == 1: subscription.successful_contribution( subscription.new_approve_tx_id) subscription.error = True #cancel subs so it doesnt try to bill again subscription.subminer_comments = "skipping subminer bc this is a 1 and done subscription, and tokens were alredy sent" subscription.save() activity = record_subscription_activity_helper( 'new_grant_contribution', subscription, profile) else: activity = record_subscription_activity_helper( 'new_grant_subscription', subscription, profile) if 'comment' in package: _profile = profile comment = package.get('comment') if comment and activity: if subscription and subscription.negative: _profile = Profile.objects.filter( handle='gitcoinbot').first() comment = f"Comment from contributor: {comment}" comment = Comment.objects.create(profile=_profile, activity=activity, comment=comment) if 'hide_wallet_address' in package: profile.hide_wallet_address = bool( package.get('hide_wallet_address', False)) profile.save() new_supporter(grant, subscription) thank_you_for_supporting(grant, subscription) update_grant_metadata.delay(grant_id)
def process_grant_contribution(self, grant_id, grant_slug, profile_id, package, send_supporter_mail: bool = True, retry: bool = True): """ :param self: :param grant_id: :param grant_slug: :param profile_id: :param package: :param send_supporter_mail: :return: """ from grants.views import record_subscription_activity_helper grant = Grant.objects.get(pk=grant_id) profile = Profile.objects.get(pk=profile_id) if 'contributor_address' in package: subscription = Subscription() if grant.negative_voting_enabled: #is_postive_vote = True if package.get('is_postive_vote', 1) else False is_postive_vote = package.get('match_direction', '+') == '+' else: is_postive_vote = True subscription.is_postive_vote = is_postive_vote fee_pct = float(package.get('gitcoin-grant-input-amount', 0)) subscription.active = False subscription.contributor_address = package.get('contributor_address', '') subscription.amount_per_period = package.get('amount_per_period', 0) subscription.real_period_seconds = package.get('real_period_seconds', 2592000) subscription.frequency = package.get('frequency', 30) subscription.frequency_unit = package.get('frequency_unit', 'days') subscription.token_address = package.get('token_address', '') subscription.token_symbol = package.get('token_symbol', '') subscription.gas_price = (float(subscription.amount_per_period) * (fee_pct / 100)) subscription.new_approve_tx_id = package.get('sub_new_approve_tx_id', '0x0') subscription.split_tx_id = package.get('split_tx_id', '0x0') subscription.num_tx_approved = package.get('num_tx_approved', 1) subscription.network = package.get('network', '') subscription.visitorId = package.get('visitorId', '') if subscription.network == 'undefined': # we unfortunately cannot trust the frontend to give us a valid network name # so this handles that case. more details are available at # https://gitcoincore.slack.com/archives/C01FQV4FX4J/p1607980714026400 if not settings.DEBUG: subscription.network = 'mainnet' subscription.contributor_profile = profile subscription.grant = grant subscription.comments = package.get('comment', '') subscription.save() value_usdt = subscription.get_converted_amount(True) include_for_clr = package.get('include_for_clr') if value_usdt < 1 or subscription.contributor_profile.shadowbanned: include_for_clr = False subscription.successful_contribution(subscription.new_approve_tx_id, include_for_clr, checkout_type=package.get( 'checkout_type', None)) # one time payments activity = None subscription.error = True #cancel subs so it doesnt try to bill again subscription.subminer_comments = "skipping subminer bc this is a 1 and done subscription, and tokens were alredy sent" subscription.save() if 'hide_wallet_address' in package: profile.hide_wallet_address = bool( package.get('hide_wallet_address', False)) profile.save() if 'anonymize_gitcoin_grants_contributions' in package: profile.anonymize_gitcoin_grants_contributions = package.get( 'anonymize_gitcoin_grants_contributions') profile.save() activity_profile = profile if not profile.anonymize_gitcoin_grants_contributions else Profile.objects.get( handle='gitcoinbot') activity = record_subscription_activity_helper( 'new_grant_contribution', subscription, activity_profile) if 'comment' in package: _profile = profile comment = package.get('comment') if value_usdt >= 1 and comment and activity: if profile.anonymize_gitcoin_grants_contributions: _profile = Profile.objects.filter( handle='gitcoinbot').first() comment = f"Comment from contributor: {comment}" comment = Comment.objects.create(profile=_profile, activity=activity, comment=comment) # emails to contributor if value_usdt >= 1 and send_supporter_mail: grants_with_subscription = [{ 'grant': grant, 'subscription': subscription }] try: thank_you_for_supporting(grants_with_subscription) except Exception as e: logger.exception(e) update_grant_metadata.delay(grant_id) return grant, subscription
def save_data(self, profile, txid, network, created_on, symbol, value_adjusted, grant, checkout_type): """ Creates contribution and subscription and saves it to database if no matching one exists """ currency = symbol amount = value_adjusted usd_val = amount * convert_token_to_usdt(symbol) # Check that subscription with these parameters does not exist existing_subscriptions = Subscription.objects.filter( grant__pk=grant.pk, contributor_profile=profile, split_tx_id=txid, token_symbol=currency ) for existing_subscription in existing_subscriptions: tolerance = 0.01 # 1% tolerance to account for floating point amount_max = amount * (1 + tolerance) amount_min = amount * (1 - tolerance) if ( existing_subscription.amount_per_period_minus_gas_price > amount_min and existing_subscription.amount_per_period_minus_gas_price < amount_max ): # Subscription exists print("Subscription exists, exiting function\n") return # No subscription found, so create subscription and contribution try: # create objects validator_comment = f"created by ingest grant txn script" subscription = Subscription() subscription.is_postive_vote = True subscription.active = False subscription.error = True subscription.contributor_address = "N/A" subscription.amount_per_period = amount subscription.real_period_seconds = 2592000 subscription.frequency = 30 subscription.frequency_unit = "N/A" subscription.token_address = "0x0" subscription.token_symbol = currency subscription.gas_price = 0 subscription.new_approve_tx_id = "0x0" subscription.num_tx_approved = 1 subscription.network = network subscription.contributor_profile = profile subscription.grant = grant subscription.comments = validator_comment subscription.amount_per_period_usdt = usd_val subscription.created_on = created_on subscription.last_contribution_date = created_on subscription.next_contribution_date = created_on subscription.split_tx_id = txid subscription.save() # Create contribution and set the contribution as successful contrib = subscription.successful_contribution( '0x0', # subscription.new_approve_tx_id, True, # include_for_clr checkout_type=checkout_type ) contrib.success=True contrib.tx_cleared=True contrib.tx_override=True contrib.validator_comment = validator_comment contrib.created_on = created_on contrib.save() print(f"ingested {subscription.pk} / {contrib.pk}") metadata = { "id": subscription.id, "value_in_token": str(subscription.amount_per_period), "value_in_usdt_now": str(round(subscription.amount_per_period_usdt, 2)), "token_name": subscription.token_symbol, "title": subscription.grant.title, "grant_url": subscription.grant.url, "num_tx_approved": subscription.num_tx_approved, "category": "grant", } kwargs = { "profile": profile, "subscription": subscription, "grant": subscription.grant, "activity_type": "new_grant_contribution", "metadata": metadata, } Activity.objects.create(**kwargs) print("Saved!\n") except Exception as e: print(e) print("\n")
def grant_fund(request, grant_id, grant_slug): """Handle grant funding.""" try: grant = Grant.objects.get(pk=grant_id, slug=grant_slug) except Grant.DoesNotExist: raise Http404 profile = get_profile(request) if not grant.active: params = { 'active': 'grant_error', 'title': _('Fund - Grant Ended'), 'grant': grant, 'text': _('This Grant has ended.'), 'subtext': _('Contributions can no longer be made this grant') } return TemplateResponse(request, 'grants/shared/error.html', params) if is_grant_team_member(grant, profile): params = { 'active': 'grant_error', 'title': _('Fund - Grant funding blocked'), 'grant': grant, 'text': _('This Grant cannot be funded'), 'subtext': _('Grant team members cannot contribute to their own grant.') } return TemplateResponse(request, 'grants/shared/error.html', params) if grant.link_to_new_grant: params = { 'active': 'grant_error', 'title': _('Fund - Grant Migrated'), 'grant': grant.link_to_new_grant, 'text': f'This Grant has ended', 'subtext': 'Contributions can no longer be made to this grant. <br> Visit the new grant to contribute.', 'button_txt': 'View New Grant' } return TemplateResponse(request, 'grants/shared/error.html', params) active_subscription = Subscription.objects.select_related('grant').filter( grant=grant_id, active=True, error=False, contributor_profile=request.user.profile, is_postive_vote=True ) if active_subscription: params = { 'active': 'grant_error', 'title': _('Subscription Exists'), 'grant': grant, 'text': _('You already have an active subscription for this grant.') } return TemplateResponse(request, 'grants/shared/error.html', params) if grant.contract_address == '0x0': messages.info( request, _('This grant is not configured to accept funding at this time. Please contact [email protected] if you believe this message is in error!') ) logger.error(f"Grant {grant.pk} is not properly configured for funding. Please set grant.contract_address on this grant") return redirect(reverse('grants:details', args=(grant.pk, grant.slug))) if request.method == 'POST': if 'contributor_address' in request.POST: subscription = Subscription() if grant.negative_voting_enabled: #is_postive_vote = True if request.POST.get('is_postive_vote', 1) else False is_postive_vote = request.POST.get('match_direction', '+') == '+' else: is_postive_vote = True subscription.is_postive_vote = is_postive_vote subscription.active = False subscription.contributor_address = request.POST.get('contributor_address', '') subscription.amount_per_period = request.POST.get('amount_per_period', 0) subscription.real_period_seconds = request.POST.get('real_period_seconds', 2592000) subscription.frequency = request.POST.get('frequency', 30) subscription.frequency_unit = request.POST.get('frequency_unit', 'days') subscription.token_address = request.POST.get('token_address', '') subscription.token_symbol = request.POST.get('token_symbol', '') subscription.gas_price = request.POST.get('gas_price', 0) subscription.new_approve_tx_id = request.POST.get('sub_new_approve_tx_id', '0x0') subscription.num_tx_approved = request.POST.get('num_tx_approved', 1) subscription.network = request.POST.get('network', '') subscription.contributor_profile = profile subscription.grant = grant subscription.comments = request.POST.get('comment', '') subscription.save() # one time payments activity = None if int(subscription.num_tx_approved) == 1: subscription.successful_contribution(subscription.new_approve_tx_id); subscription.error = True #cancel subs so it doesnt try to bill again subscription.subminer_comments = "skipping subminer bc this is a 1 and done subscription, and tokens were alredy sent" subscription.save() activity = record_subscription_activity_helper('new_grant_contribution', subscription, profile) else: activity = record_subscription_activity_helper('new_grant_subscription', subscription, profile) if 'comment' in request.POST: comment = request.POST.get('comment') if comment and activity: profile = request.user.profile if subscription and subscription.negative: profile = Profile.objects.filter(handle='gitcoinbot').first() comment = f"Comment from contributor: {comment}" comment = Comment.objects.create( profile=profile, activity=activity, comment=comment) message = 'Your contribution has succeeded. Thank you for supporting Public Goods on Gitcoin !' if request.session.get('send_notification'): msg_html = request.session.get('msg_html') cta_text = request.session.get('cta_text') cta_url = request.session.get('cta_url') to_user = request.user send_notification_to_user_from_gitcoinbot(to_user, cta_url, cta_text, msg_html) if int(subscription.num_tx_approved) > 1: message = 'Your subscription has been created. It will bill within the next 5 minutes or so. Thank you for supporting Public Goods on Gitcoin !' messages.info( request, message ) return JsonResponse({ 'success': True, }) if 'hide_wallet_address' in request.POST: profile.hide_wallet_address = bool(request.POST.get('hide_wallet_address', False)) profile.save() if 'signature' in request.POST: sub_new_approve_tx_id = request.POST.get('sub_new_approve_tx_id', '') subscription = Subscription.objects.filter(new_approve_tx_id=sub_new_approve_tx_id).first() subscription.active = True subscription.subscription_hash = request.POST.get('subscription_hash', '') subscription.contributor_signature = request.POST.get('signature', '') if 'split_tx_id' in request.POST: subscription.split_tx_id = request.POST.get('split_tx_id', '') subscription.save_split_tx_to_contribution() if 'split_tx_confirmed' in request.POST: subscription.split_tx_confirmed = bool(request.POST.get('split_tx_confirmed', False)) subscription.save_split_tx_to_contribution() subscription.save() value_usdt = subscription.get_converted_amount() if value_usdt: grant.monthly_amount_subscribed += subscription.get_converted_monthly_amount() grant.save() if not subscription.negative: new_supporter(grant, subscription) thank_you_for_supporting(grant, subscription) return JsonResponse({ 'success': True, 'url': reverse('grants:details', args=(grant.pk, grant.slug)) }) # handle phantom funding active_tab = 'normal' fund_reward = None round_number = clr_round can_phantom_fund = request.user.is_authenticated and request.user.groups.filter(name='phantom_funders_round_5').exists() and clr_active phantom_funds = PhantomFunding.objects.filter(profile=request.user.profile, round_number=round_number).order_by('created_on').nocache() if request.user.is_authenticated else PhantomFunding.objects.none() is_phantom_funding_this_grant = can_phantom_fund and phantom_funds.filter(grant=grant).exists() show_tweet_modal = False fund_reward = get_fund_reward(request, grant) if can_phantom_fund and request.POST.get('toggle_phantom_fund'): if is_phantom_funding_this_grant: msg = "You are no longer signaling for this grant." phantom_funds.filter(grant=grant).delete() else: msg = "You are now signaling for this grant." show_tweet_modal = True pt = PhantomFunding.objects.create(grant=grant, profile=request.user.profile, round_number=round_number) record_grant_activity_helper('new_grant_contribution', grant, request.user.profile, amount=pt.value, token='DAI') messages.info( request, msg ) is_phantom_funding_this_grant = not is_phantom_funding_this_grant images = [ 'new.svg', 'torchbearer.svg', 'robots.png', 'profile/fund.svg', ] img = random.choice(images) params = { 'profile': profile, 'active': 'fund_grant', 'title': matching_live + grant.title + " | Fund Now", 'card_desc': grant.description, 'avatar_url': grant.logo.url if grant.logo else None, 'subscription': {}, 'show_tweet_modal': show_tweet_modal, 'direction': request.GET.get('direction', '+'), 'grant_has_no_token': True if grant.token_address == '0x0000000000000000000000000000000000000000' else False, 'grant': grant, 'img': img, 'clr_prediction_curve': [c[1] for c in grant.clr_prediction_curve] if grant.clr_prediction_curve and len(grant.clr_prediction_curve[0]) > 1 else [0, 0, 0, 0, 0, 0], 'keywords': get_keywords(), 'recommend_gas_price': recommend_min_gas_price_to_confirm_in_time(4), 'recommend_gas_price_slow': recommend_min_gas_price_to_confirm_in_time(120), 'recommend_gas_price_avg': recommend_min_gas_price_to_confirm_in_time(15), 'recommend_gas_price_fast': recommend_min_gas_price_to_confirm_in_time(1), 'eth_usd_conv_rate': eth_usd_conv_rate(), 'conf_time_spread': conf_time_spread(), 'gas_advisories': gas_advisories(), 'splitter_contract_address': settings.SPLITTER_CONTRACT_ADDRESS, 'gitcoin_donation_address': settings.GITCOIN_DONATION_ADDRESS, 'can_phantom_fund': can_phantom_fund, 'is_phantom_funding_this_grant': is_phantom_funding_this_grant, 'active_tab': active_tab, 'fund_reward': fund_reward, 'phantom_funds': phantom_funds, 'clr_round': clr_round, 'clr_active': clr_active, 'total_clr_pot': total_clr_pot, } return TemplateResponse(request, 'grants/fund.html', params)