diff --git a/README.md b/README.md index 4fd4311..2fac0ef 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,6 @@ These variables must also be defined, and will be used to replace variables in t - `POLYGONSCAN_API_KEY` - `WEB3_PROVIDER_ETH_URL` - `WEB3_PROVIDER_POLYGON_URL` -- `DISCORD_BOT_TOKEN_REBASE` -- `DISCORD_BOT_WEBHOOK_REBASE` - `DISCORD_BOT_TOKEN_KLIMA_PRICE` - `DISCORD_BOT_TOKEN_BCT_PRICE` - `DISCORD_BOT_TOKEN_MCO2_PRICE` diff --git a/k8s/bond_alerts/deployment_set_bot.yaml b/k8s/bond_alerts/deployment_set_bot.yaml deleted file mode 100644 index 334181b..0000000 --- a/k8s/bond_alerts/deployment_set_bot.yaml +++ /dev/null @@ -1,26 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.bond_alerts.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_BOND_ALERTS - - name: DISCORD_WEBHOOK_BROKEN_BOND_ALERT - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_WEBHOOK_BROKEN_BOND_ALERT - - name: AIRTABLE_API_KEY - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: AIRTABLE_API_KEY diff --git a/k8s/bond_alerts/kustomization.yaml b/k8s/bond_alerts/kustomization.yaml deleted file mode 100644 index 0ddfcc0..0000000 --- a/k8s/bond_alerts/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: bond-alerts- - -commonLabels: - bot: bond-alerts - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/k8s/dao_balance/deployment_set_bot.yaml b/k8s/dao_balance/deployment_set_bot.yaml deleted file mode 100644 index 48f6cf2..0000000 --- a/k8s/dao_balance/deployment_set_bot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.dao_balance.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_DAO_BALANCE diff --git a/k8s/dao_balance/kustomization.yaml b/k8s/dao_balance/kustomization.yaml deleted file mode 100644 index 54033c3..0000000 --- a/k8s/dao_balance/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: dao-balance- - -commonLabels: - bot: dao-balance - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/k8s/dao_fee/deployment_set_bot.yaml b/k8s/dao_fee/deployment_set_bot.yaml deleted file mode 100644 index 84eaadc..0000000 --- a/k8s/dao_fee/deployment_set_bot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.dao_fee.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_DAO_FEE diff --git a/k8s/dao_fee/kustomization.yaml b/k8s/dao_fee/kustomization.yaml deleted file mode 100644 index c577a96..0000000 --- a/k8s/dao_fee/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: dao-fee- - -commonLabels: - bot: dao-fee - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/k8s/dcm_supply/deployment_set_bot.yaml b/k8s/dcm_supply/deployment_set_bot.yaml deleted file mode 100644 index 80ab579..0000000 --- a/k8s/dcm_supply/deployment_set_bot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.dcm_supply.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_DCM_SUPPLY diff --git a/k8s/dcm_supply/kustomization.yaml b/k8s/dcm_supply/kustomization.yaml deleted file mode 100644 index 211bf7e..0000000 --- a/k8s/dcm_supply/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: dcm-supply- - -commonLabels: - bot: dcm-supply - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/k8s/index/deployment_set_bot.yaml b/k8s/index/deployment_set_bot.yaml deleted file mode 100644 index 5f4681a..0000000 --- a/k8s/index/deployment_set_bot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.index.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_INDEX diff --git a/k8s/index/kustomization.yaml b/k8s/index/kustomization.yaml deleted file mode 100644 index 90d03e2..0000000 --- a/k8s/index/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: index- - -commonLabels: - bot: index - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/k8s/kustomization.yaml b/k8s/kustomization.yaml index 934c7e9..0fd7e15 100644 --- a/k8s/kustomization.yaml +++ b/k8s/kustomization.yaml @@ -4,23 +4,13 @@ kind: Kustomization resources: - namespace.yaml - ./bct_price - # TODO: fix interactions for bond discount bot - # - ./bond_alerts - - ./index - ./klima_price - ./mco2_price - ./nbo_price - ./nct_price - - ./next_rewards - ./staking_rewards - - ./supply_cc - - ./treasury_carbon - - ./treasury_market - ./ubo_price - ./retirement_fee_info - - ./dao_fee - - ./dao_balance - - ./dcm_supply - ./cco2_price namespace: discord-bots diff --git a/k8s/next_rewards/deployment_set_bot.yaml b/k8s/next_rewards/deployment_set_bot.yaml deleted file mode 100644 index 9c19e53..0000000 --- a/k8s/next_rewards/deployment_set_bot.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.next_rewards.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_REBASE - - name: DISCORD_REBASE_BOT_WEBHOOK_URL - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_WEBHOOK_REBASE diff --git a/k8s/next_rewards/kustomization.yaml b/k8s/next_rewards/kustomization.yaml deleted file mode 100644 index 2e20405..0000000 --- a/k8s/next_rewards/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: next-rewards- - -commonLabels: - bot: next-rewards - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/k8s/secret.properties.template b/k8s/secret.properties.template index 2755f32..22fc891 100644 --- a/k8s/secret.properties.template +++ b/k8s/secret.properties.template @@ -1,25 +1,14 @@ AIRTABLE_API_KEY=${AIRTABLE_API_KEY} DISCORD_BOT_TOKEN_BCT_PRICE=${DISCORD_BOT_TOKEN_BCT_PRICE} -DISCORD_BOT_TOKEN_KLIMA_BOND_ALERTS=${DISCORD_BOT_TOKEN_KLIMA_BOND_ALERTS} DISCORD_BOT_TOKEN_KLIMA_PRICE=${DISCORD_BOT_TOKEN_KLIMA_PRICE} DISCORD_BOT_TOKEN_MCO2_PRICE=${DISCORD_BOT_TOKEN_MCO2_PRICE} DISCORD_BOT_TOKEN_NBO_PRICE=${DISCORD_BOT_TOKEN_NBO_PRICE} DISCORD_BOT_TOKEN_NCT_PRICE=${DISCORD_BOT_TOKEN_NCT_PRICE} DISCORD_BOT_TOKEN_CCO2_PRICE=${DISCORD_BOT_TOKEN_CCO2_PRICE} -DISCORD_BOT_TOKEN_REBASE=${DISCORD_BOT_TOKEN_REBASE} DISCORD_BOT_TOKEN_STAKING_REWARDS=${DISCORD_BOT_TOKEN_STAKING_REWARDS} -DISCORD_BOT_TOKEN_SUPPLY_CC=${DISCORD_BOT_TOKEN_SUPPLY_CC} -DISCORD_BOT_TOKEN_INDEX=${DISCORD_BOT_TOKEN_INDEX} -DISCORD_BOT_TOKEN_TREASURY_CARBON=${DISCORD_BOT_TOKEN_TREASURY_CARBON} -DISCORD_BOT_TOKEN_TREASURY_MARKET=${DISCORD_BOT_TOKEN_TREASURY_MARKET} DISCORD_BOT_TOKEN_UBO_PRICE=${DISCORD_BOT_TOKEN_UBO_PRICE} DISCORD_BOT_TOKEN_RETIREMENT_FEE_INFO=${DISCORD_BOT_TOKEN_RETIREMENT_FEE_INFO} -DISCORD_BOT_TOKEN_DAO_FEE=${DISCORD_BOT_TOKEN_DAO_FEE} -DISCORD_BOT_TOKEN_DAO_BALANCE=${DISCORD_BOT_TOKEN_DAO_BALANCE} -DISCORD_BOT_TOKEN_DCM_SUPPLY=${DISCORD_BOT_TOKEN_DCM_SUPPLY} -DISCORD_BOT_WEBHOOK_REBASE=${DISCORD_BOT_WEBHOOK_REBASE} -DISCORD_WEBHOOK_BROKEN_BOND_ALERT=${DISCORD_WEBHOOK_BROKEN_BOND_ALERT} POLYGONSCAN_API_KEY=${POLYGONSCAN_API_KEY} WEB3_PROVIDER_ETH_URL=${WEB3_PROVIDER_ETH_URL} diff --git a/k8s/supply_cc/deployment_set_bot.yaml b/k8s/supply_cc/deployment_set_bot.yaml deleted file mode 100644 index d5b3890..0000000 --- a/k8s/supply_cc/deployment_set_bot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.supply_cc.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_SUPPLY_CC diff --git a/k8s/supply_cc/kustomization.yaml b/k8s/supply_cc/kustomization.yaml deleted file mode 100644 index 87c8d7c..0000000 --- a/k8s/supply_cc/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: supply-cc- - -commonLabels: - bot: supply-cc - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/k8s/treasury_carbon/deployment_set_bot.yaml b/k8s/treasury_carbon/deployment_set_bot.yaml deleted file mode 100644 index b406d33..0000000 --- a/k8s/treasury_carbon/deployment_set_bot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.treasury_carbon.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_TREASURY_CARBON diff --git a/k8s/treasury_carbon/kustomization.yaml b/k8s/treasury_carbon/kustomization.yaml deleted file mode 100644 index 28f6e7b..0000000 --- a/k8s/treasury_carbon/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: treasury-carbon- - -commonLabels: - bot: treasury-carbon - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/k8s/treasury_market/deployment_set_bot.yaml b/k8s/treasury_market/deployment_set_bot.yaml deleted file mode 100644 index 58c63e4..0000000 --- a/k8s/treasury_market/deployment_set_bot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: bot -spec: - template: - spec: - containers: - - name: bot - args: ["src.treasury_market.main"] - env: - - name: DISCORD_BOT_TOKEN - valueFrom: - secretKeyRef: - name: discord-bots-secret - key: DISCORD_BOT_TOKEN_TREASURY_MARKET diff --git a/k8s/treasury_market/kustomization.yaml b/k8s/treasury_market/kustomization.yaml deleted file mode 100644 index 6475775..0000000 --- a/k8s/treasury_market/kustomization.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -resources: - - ../base - -namePrefix: treasury-market- - -commonLabels: - bot: treasury-market - -patchesStrategicMerge: - - deployment_set_bot.yaml diff --git a/src/bond_alerts/__init__.py b/src/bond_alerts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/bond_alerts/airtable_utils.py b/src/bond_alerts/airtable_utils.py deleted file mode 100644 index 4b6ba62..0000000 --- a/src/bond_alerts/airtable_utils.py +++ /dev/null @@ -1,167 +0,0 @@ -import os -from pyairtable import Table -from pyairtable.formulas import match -from collections import namedtuple - -# Initiallize Airtable -api_key = os.environ['AIRTABLE_API_KEY'] - -alerts_app = 'appTX60pjw7iWGz4Y' -alerts_table = 'tblkVGjvsmwrI7vMn' -master_data_app = 'appAQXkU6sdPuQ1p6' -bonds_table = 'tbl5zIl9EZKaras54' -tokens_table = 'tblQLIlas6IBvWz6Y' - -alert_db = Table(api_key, alerts_app, alerts_table) -bond_db = Table(api_key, master_data_app, bonds_table) -token_db = Table(api_key, master_data_app, tokens_table) - -Bond = namedtuple("Bond", "bond price_usd discount max_purchase debt_reached") -Token = namedtuple("Token", "token price_klima") -Alert = namedtuple("Alert", "bond discount user") - - -def search_alert(table: Table, search_bond: str = None, search_user: str = None, search_discount: float = None, search_active: bool = None, search_type: str = None): # noqa: E501 - if search_type == 'triggered': - if search_bond is not None and search_discount is not None: - formula = "AND({active}=1,{bond}='"+str(search_bond)+"',{discount}<='"+str(search_discount)+"')" # noqa: E501 - elif search_type == 'reactivate': - if search_bond is not None and search_discount is not None: - formula = "AND({active}=0,{bond}='"+str(search_bond)+"',{discount}>'"+str(search_discount)+"')" # noqa: E501 - else: - if search_bond is not None and search_user is not None and search_discount is not None: - formula = "AND({bond}='"+str(search_bond)+"',{user}='"+str(search_user)+"',{discount}<='"+str(search_discount)+"')" # noqa: E501 - elif search_user is not None and search_discount is not None: - formula = "AND({user}='"+str(search_user)+"',{discount}<='"+str(search_discount)+"')" - elif search_bond is not None and search_user is not None: - formula = match({'bond': search_bond, 'user': search_user}) - elif search_bond is not None: - formula = match({'bond': search_bond}) - elif search_user is not None: - formula = match({'user': search_user}) - elif search_discount is not None: - formula = "AND({discount}<='"+str(search_discount)+"')" - - r = table.all(formula=formula) - rr = [] - if r is not None: - for a in r: - rr.append(Alert(a['fields']['bond'], a['fields']['discount'], a['fields']['user'])) - - return rr - - -def activate_alert(table: Table, search_bond: str, search_user: str, search_discount: float): - formula = match({'bond': search_bond, 'user': search_user, 'discount': search_discount}) - r = table.first(formula=formula) - table.update(r['id'], {'active': True}) - - -def deactivate_alert(table: Table, search_bond: str, search_user: str, search_discount: float): - formula = match({'bond': search_bond, 'user': search_user, 'discount': search_discount}) - r = table.first(formula=formula) - table.update(r['id'], {'active': False}) - - -def fetch_bond_md(table: Table, search_bond: str): - formula = match({'bond': search_bond, 'active': True}) - r = table.first(formula=formula) - if r is not None: - return r['fields']['address'], r['fields']['quote_token'] - - -def fetch_token_md(table: Table, search_token: str): - formula = match({'token': search_token}) - r = table.first(formula=formula) - if r is not None: - return r['fields']['pool_address'], r['fields']['pool_base_token'] - - -def fetch_bond_info(table: Table, search_bond: str): - formula = match({'bond': search_bond, 'active': True}) - r = table.first(formula=formula) - if r is not None: - if 'debt_reached' in r['fields']: - return Bond(r['fields']['bond'], r['fields']['price_usd'], r['fields']['discount'], r['fields']['max_purchase'], r['fields']['debt_reached']) # noqa: E501 - else: - return Bond(r['fields']['bond'], r['fields']['price_usd'], r['fields']['discount'], r['fields']['max_purchase'], False) # noqa: E501 - - -def fetch_token_info(table: Table, search_token: str): - formula = match({'token': search_token}) - r = table.first(formula=formula) - if r is not None: - return r['fields']['price_klima'], r['fields']['price_usd'] - - -def active_bonds(table: Table): - formula = match({'active': True}) - r = table.all(formula=formula) - if r is not None: - rr = [] - for b in r: - rr.append(b['fields']['bond']) - return rr - - -def active_tokens(table: Table): - formula = match({'active': True}) - r = table.all(formula=formula) - if r is not None: - rr = [] - for b in r: - rr.append(b['fields']['token']) - return rr - - -def update_bond_info(table: Table, update_bond: str, update_price: float, update_disc: float, update_capacity: float, update_debt: bool): # noqa: E501 - formula = match({'bond': update_bond}) - r = table.first(formula=formula) - table.update(r['id'], {'price_usd': update_price, 'discount': update_disc, 'max_purchase': update_capacity, 'debt_reached': update_debt}) # noqa: E501 - - -def update_token_info(table: Table, update_token: str, update_price_klima: float, update_price_usd: float): - formula = match({'token': update_token}) - r = table.first(formula=formula) - table.update(r['id'], {'price_klima': update_price_klima, 'price_usd': update_price_usd}) - - -def add_alert(table: Table, add_bond: str, add_discount: float, add_user: str): - bond_check = fetch_bond_info(bond_db, add_bond) - if bond_check is not None: - alert_check = search_alert(table, search_user=add_user) - if len(alert_check) < 5: - for a in alert_check: - if a == (add_bond, float(add_discount), add_user): - # Alert already configured - return 0 - - # All checks passed, create alert - try: - table.create({'bond': add_bond, 'user': add_user, 'discount': add_discount}) - return 1 - except Exception as e: - print(e) - return -999 - - else: - # User already has 5 alerts configured - return -1 - else: - # Bond does not exist or not active anymore - return -2 - - -def remove_alert(table: Table, delete_bond: str, delete_discount: float, delete_user: str): - formula = match({'bond': delete_bond, 'user': delete_user, 'discount': delete_discount}) - r = table.first(formula=formula) - if r is not None: - try: - table.delete(r['id']) - return 1 - except Exception as e: - print(e) - return -999 - else: - # Alert does not exist - return 0 diff --git a/src/bond_alerts/main.py b/src/bond_alerts/main.py deleted file mode 100644 index 9a49d96..0000000 --- a/src/bond_alerts/main.py +++ /dev/null @@ -1,479 +0,0 @@ -import os -import json -import datetime -import decimal - -import discord -from discord.ext import tasks -from discord.commands import Option -from web3 import Web3 - -from ..constants import STAKING_ADDRESS, SKLIMA_ADDRESS, BCT_ADDRESS, USDC_ADDRESS, KLIMA_ADDRESS, \ - BCT_USDC_POOL, KLIMA_BCT_POOL -from ..utils import get_discord_client, get_polygon_web3, load_abi -from .airtable_utils import alert_db, bond_db, token_db, search_alert, activate_alert, deactivate_alert, \ - fetch_bond_md, fetch_bond_info, active_bonds, update_bond_info, add_alert, remove_alert, \ - fetch_token_md, active_tokens, update_token_info - - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] - -# Initialized Discord client -client = get_discord_client() - -# Initialize web3 -web3 = get_polygon_web3() - -# Load ABIs -SKLIMA_ABI = load_abi('sklima.json') -BOND_ABI = load_abi('klima_bond.json') -STAKING_ABI = load_abi('klima_staking.json') -TOKEN_ABI = load_abi('erc20_token.json') - -# Init global variables -last_call = datetime.datetime.now() - datetime.timedelta(minutes=10) -klima_price_usd, bct_price_usd, rebase, staking_rewards = 0, 0, 0, 0 -bond_info, token_info = {}, {} - - -def base_token_price(lp_address, known_address, known_price): - # Retrieve token price by using the LP token - try: - address = Web3.to_checksum_address(lp_address) - abi = json.loads('[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0Out","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1Out","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"reserve0","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"reserve1","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token0","type":"address"},{"internalType":"address","name":"_token1","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"kLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"price0CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price1CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount0Out","type":"uint256"},{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sync","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]') # noqa: E501 - LP_contract = web3.eth.contract(address=address, abi=abi) - reserves = LP_contract.functions.getReserves().call() - - address0 = LP_contract.functions.token0().call() - address1 = LP_contract.functions.token1().call() - token0 = web3.eth.contract(address=address0, abi=TOKEN_ABI) - token1 = web3.eth.contract(address=address1, abi=TOKEN_ABI) - decimals0 = token0.functions.decimals().call() - decimals1 = token1.functions.decimals().call() - - if Web3.to_checksum_address(known_address) == address0: - tokenPrice = known_price * \ - reserves[0] * 10**decimals1 / (reserves[1] * 10**decimals0) - else: - tokenPrice = known_price * \ - reserves[1] * 10**decimals0 / (reserves[0] * 10**decimals1) - - return tokenPrice - - except Exception as e: - print('base_token_price error') - print(e) - - -def lp_price(lp_address): - # Retrieve price of the LP token - try: - LP_address = Web3.to_checksum_address(lp_address) - LP_abi = json.loads('[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0Out","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1Out","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"reserve0","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"reserve1","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token0","type":"address"},{"internalType":"address","name":"_token1","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"kLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"price0CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"price1CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount0Out","type":"uint256"},{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sync","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]') # noqa: E501 - LP_contract = web3.eth.contract(address=LP_address, abi=LP_abi) - - address0 = LP_contract.functions.token0().call() - address1 = LP_contract.functions.token1().call() - token0 = web3.eth.contract(address=address0, abi=TOKEN_ABI) - token1 = web3.eth.contract(address=address1, abi=TOKEN_ABI) - - get_reserves = LP_contract.functions.getReserves().call() - amount0 = get_reserves[0] / 10**token0.functions.decimals().call() - amount1 = get_reserves[1] / 10**token1.functions.decimals().call() - return decimal.Decimal(amount0 / amount1) - - except Exception as e: - print('lp_price error') - print(e) - - -def fetch_staking_rewards(): - try: - # Retrieve staking rewards and calculate 5 day ROI - distrib = web3.eth.contract(address=STAKING_ADDRESS, abi=STAKING_ABI) - distrib_info = distrib.functions.epoch().call() - rewards = distrib_info[3] - - sKLIMA = web3.eth.contract(address=SKLIMA_ADDRESS, abi=SKLIMA_ABI) - circ_supply = sKLIMA.functions.circulatingSupply().call() - - rebase = rewards / circ_supply - staking_rewards = (1 + rewards / circ_supply)**float(15) - 1 - return rebase, 100 * staking_rewards - - except Exception as e: - print('fetch_staking_rewards error') - print(e) - return None, None - - -def contract_info(bond_address, payoutTokenPrice, maxReached=False): - if maxReached is True: - return -999, -999, -999 - else: - try: - # Retrieve bond price + current discount - bond = web3.eth.contract(address=bond_address, abi=BOND_ABI) - - # Bonds are priced in BCT/MCO2 despide the function is called bondPriceInUSD - if bond_address == '0xb5aF101742EcAe095944F60C384d09453006bFde': - BondPriceUSD = bond.functions.bondPriceInUSD().call() / 1e6 - else: - BondPriceUSD = bond.functions.bondPriceInUSD().call() / 1e18 - MaxCap = bond.functions.maxPayout().call() / 1e9 - Disc = (decimal.Decimal(payoutTokenPrice) - decimal.Decimal(BondPriceUSD)) / decimal.Decimal(BondPriceUSD) # noqa: E501 - return payoutTokenPrice, 100 * Disc, MaxCap - - except Exception as e: - print('contract_info error') - print(e) - - -def max_debt_reached(bond_address): - try: - bond = web3.eth.contract(address=bond_address, abi=BOND_ABI) - currentDebt = bond.functions.currentDebt().call() - maxDebt = bond.functions.terms().call() - if currentDebt >= maxDebt[-1]: - return True - else: - return False - - except Exception as e: - print(e) - - -def check_is_worth(staking_rewards, rebase, bondDiscount): - cutoff_discount = bondDiscount + \ - ((1 + rebase)) * (1 - (1 + rebase)**15) / (1 - (1 + rebase)) / 15 - 1 - if staking_rewards >= cutoff_discount: - return ':no_entry:', cutoff_discount - else: - return ':white_check_mark:', cutoff_discount - - -def get_prices(): - global last_call - global klima_price_usd, bct_price_usd, rebase, staking_rewards, bond_info - bond_list = active_bonds(bond_db) - token_list = active_tokens(token_db) - - if datetime.datetime.now() - datetime.timedelta(seconds=120) >= last_call: - # Retrieve prices - bct_price_usd = base_token_price( - lp_address=BCT_USDC_POOL, known_address=USDC_ADDRESS, known_price=1 - ) - klima_price_usd = base_token_price( - lp_address=KLIMA_BCT_POOL, known_address=BCT_ADDRESS, known_price=bct_price_usd - ) - last_call = datetime.datetime.now() - - # Update staking rewards - rebase, staking_rewards = fetch_staking_rewards() - - # Update quote token info - for t in token_list: - pool_address, base_token = fetch_token_md(token_db, t) - if base_token == 'USDC': - price_usd = base_token_price(lp_address=pool_address, known_address=USDC_ADDRESS, known_price=1) # noqa: E501 - elif base_token == 'KLIMA': - price_usd = base_token_price(lp_address=pool_address, known_address=KLIMA_ADDRESS, known_price=klima_price_usd) # noqa: E501 - print(t, price_usd) - try: - price_klima = klima_price_usd / price_usd - except Exception as e: - print(e) - price_klima = klima_price_usd - update_token_info(token_db, update_token=t, update_price_klima=price_klima, update_price_usd=price_usd) - if t == 'USDC': - token_info[t] = price_usd - else: - token_info[t] = price_klima - - # Update bond info - for b in bond_list: - address, quote_token = fetch_bond_md(bond_db, b) - is_closed = max_debt_reached(bond_address=Web3.to_checksum_address(address)) - price, disc, bond_max = contract_info(bond_address=Web3.to_checksum_address(address), payoutTokenPrice=token_info[quote_token], maxReached=is_closed) # noqa: E501 - update_bond_info(bond_db, update_bond=b, update_price=price, update_disc=float(disc), update_capacity=bond_max, update_debt=is_closed) # noqa: E501 - print(b, price, quote_token, token_info[quote_token], f'{disc:,.2f}') - - for b in bond_list: - last_info = fetch_bond_info(bond_db, b) - bond_info[b] = last_info - - return klima_price_usd, rebase, staking_rewards, bond_info - - -# Create a class for the embed -class PageButton(discord.ui.Button): - def __init__(self, current, pages): - super().__init__( - style=discord.ButtonStyle.secondary, - label=f"Page {current + 1}/{pages + 1}", - disabled=True, custom_id='page' - ) - - -class Pagination(discord.ui.View): - def __init__(self, paginationList): - super().__init__(timeout=10) - self.value = 0 - self.pages = len(paginationList) - 1 - self.paginationList = paginationList - self.add_item(PageButton(current=self.value, pages=self.pages)) - self.message = 0 - - async def on_timeout(self): - for b in self.children: - b.disabled = True - await self.message.edit(embed=self.paginationList[self.value], view=self) - - @discord.ui.button(label="Prev Page", style=discord.ButtonStyle.primary) - async def next_page(self, button: discord.ui.Button, interaction: discord.Interaction): - if self.value - 1 < 0: - self.value = self.pages - else: - self.value = self.value - 1 - for b in self.children: - if b.custom_id == 'page': - self.remove_item(b) - self.add_item(PageButton(current=self.value, pages=self.pages)) - await interaction.response.edit_message(embed=self.paginationList[self.value], view=self) - - @discord.ui.button(label="Next Page", style=discord.ButtonStyle.primary) - async def prev_page(self, button: discord.ui.Button, interaction: discord.Interaction): - if self.value + 1 > self.pages: - self.value = 0 - else: - self.value = self.value + 1 - for b in self.children: - if b.custom_id == 'page': - self.remove_item(b) - self.add_item(PageButton(current=self.value, pages=self.pages)) - await interaction.response.edit_message(embed=self.paginationList[self.value], view=self) - - -@client.event -async def on_ready(): - print('Logged in as {0.user}'.format(client)) - if not check_discounts.is_running(): - check_discounts.start() - - -@tasks.loop(seconds=30) -async def check_discounts(): - klima_price_usd, rebase, staking_rewards, bond_info = get_prices() - - if klima_price_usd is None or rebase is None or staking_rewards is None or bond_info is None: - return - - for bond, values in bond_info.items(): - if values.debt_reached is False: - # Reactivate alerts if discounts have diminished enough - reactivate = search_alert(alert_db, search_bond=bond, search_discount=values.discount, search_type='reactivate') # noqa: E501 - for alert in reactivate: - activate_alert(alert_db, search_bond=bond, - search_discount=alert.discount, search_user=alert.user) - print( - f'Alert reset for {bond} at more than {values.discount}% discount') - - # Send alerts if discounts are big enough - triggered = search_alert(alert_db, search_bond=bond, search_discount=values.discount, search_type='triggered') # noqa: E501 - for alert in triggered: - deactivate_alert(alert_db, search_bond=bond, - search_discount=alert.discount, search_user=alert.user) - is_worth, max_bond_disc = check_is_worth( - staking_rewards, rebase, values.discount) - - embed = discord.Embed(title='Big Bond Discount!', description=f':small_blue_diamond: 5 day ROI for **staking** sits at **{staking_rewards:,.2f}%** \n :small_blue_diamond: Current **KLIMA** price is **${klima_price_usd:,.2f}** \n :small_blue_diamond: Some of your alert thresholds have been reached by current bond discounts', colour=0x17d988) # noqa: E501 - embed.add_field(name='Bond Type', - value=values.bond, inline=True) - embed.add_field(name='Discount', - value=f' {values.discount:,.2f}%', inline=True) - embed.add_field(name='\u200b', value='\u200b', inline=True) - embed.add_field(name='Could beat staking', - value=is_worth, inline=True) - embed.add_field(name='Compound Discount', - value=f' {max_bond_disc:,.2f}%', inline=True) - embed.add_field( - name='Max payout', value=f'{values.max_purchase:,.2f} KLIMA', inline=True) - embed.set_footer( - text='This alert is courtesy of your Klimate @0xRusowsky') - - user = client.get_user(int(alert.user)) - try: - await user.send(embed=embed) - except Exception as e: - print(e) - - -@client.slash_command(description="Check live information for every bond type issued by KlimaDAO.") -async def bonds(ctx): - global klima_price_usd, rebase, staking_rewards, bond_info - - info = [] - await ctx.defer(ephemeral=True) - for bond, values in bond_info.items(): - info.append((bond, check_is_worth(staking_rewards, rebase, values.discount), values.discount, values.max_purchase, values.debt_reached)) # noqa: E501 - info = sorted(info, key=lambda x: x[2], reverse=True) - - fields = [] - for i in info: - if i[4] is False: - fields = fields + [(('Bond Type', i[0]), ('Discount', f'{i[2]:,.2f}%'), ('Could be profitable?', i[1][0]), ('Compound Discount', f' {i[1][1]:,.2f}%'), ('Max payout', f'{i[3]:,.2f} KLIMA'))] # noqa: E501 - else: - fields = fields + \ - [(('Bond Type', i[0]), ('Sold Out :no_entry:', "Max Debt Reached"))] - - lfields = [fields[i * 4:(i + 1) * 4] - for i in range((len(fields) + 4 - 1) // 4)] - paginationList = [] - page = 0 - for lf in lfields: - page = page + 1 - embed = discord.Embed(title="Current KlimaDAO Bond Discounts", description=f":small_blue_diamond: 5 day ROI for **staking** sits at **{staking_rewards:,.2f}%** \n :small_blue_diamond: Current **KLIMA** price is **${klima_price_usd:,.2f}**", colour=0xFFFFFF) # noqa: E501 - for f in lf: - if len(f) > 2: - embed.add_field(name=f[0][0], value=f[0][1], inline=True) - embed.add_field(name=f[1][0], value=f[1][1], inline=True) - embed.add_field(name=f[2][0], value=f[2][1], inline=True) - embed.add_field(name='\u200b', value='\u200b', inline=True) - embed.add_field(name=f[3][0], value=f[3][1], inline=True) - embed.add_field(name=f[4][0], value=f[4][1], inline=True) - else: - embed.add_field(name=f[0][0], value=f[0][1], inline=True) - embed.add_field(name='\u200b', value='\u200b', inline=True) - embed.add_field(name=f[1][0], value=f[1][1], inline=True) - paginationList.append(embed) - - if len(paginationList) == 1: - await ctx.followup.send(embed=paginationList[0]) - else: - view = Pagination(paginationList=paginationList) - await ctx.respond(embed=paginationList[view.value], view=view) - view.message = await ctx.interaction.original_message() - - -@client.slash_command(description="Check the available bond types that KlimaDAO offers and that this bot supports.") -async def info_bonds(ctx): - if not check_discounts.is_running(): - check_discounts.start() - - bond_list = active_bonds(bond_db) - embed = discord.Embed(title="KlimaDAO Bond Types", description=f"KlimaDAO currently offers {len(bond_list)} bond types.", colour=0xFFFFFF) # noqa: E501 - for b in bond_list: - address, abi = fetch_bond_md(bond_db, b) - embed.add_field(name='Bond Type', value=b, inline=True) - embed.add_field(name='\u200b', value='\u200b', inline=True) - embed.add_field( - name='Contract', value=f'[Link](https://polygonscan.com/address/{address})', inline=True) - - await ctx.respond(embed=embed) - - -@client.slash_command(description='Create alerts that will be triggered according to bond discount.') -async def create_alert( - ctx, - bond_type: Option(str, "Input the desired bond. Use the /info_bonds command to see all the bond types."), # noqa: F722,E501 - min_discount: Option(float, "Input the min_discount threshold. Must be a number, without '%' sign."), # noqa: F722,E501 -): - bond_list = active_bonds(bond_db) - bond_str = "" - for b in bond_list: - bond_str = bond_str + " \n :black_small_square: " + b - bond = bond_type.upper() - - code = add_alert(alert_db, add_bond=bond_type, - add_discount=min_discount, add_user=str(ctx.author.id)) - if code == 1: - embed = discord.Embed(title="New Bond Alert Configured", description=f'{ctx.author.mention} has created a new alert for discounts over {min_discount}% on {bond} bonds!', colour=0x17d988) # noqa: E501 - await ctx.respond(embed=embed) - elif code == 0: - embed = discord.Embed(title="Bond Alert Already Configured", description=f'{ctx.author.mention} already had an alert for discounts over {min_discount}% on {bond} bonds.', colour=0xd14d4d) # noqa: E501 - await ctx.respond(embed=embed) - elif code == -1: - embed = discord.Embed(title="Configuration Error", description=f"{ctx.author.mention} already had 5 alerts configured. Don't be greedy ser!", colour=0xd14d4d) # noqa: E501 - await ctx.respond(embed=embed) - elif code == -2: - embed = discord.Embed(title="Configuration Error", description=f"Incorrect bond type: **{bond}**. \n The input value for a bond must be within the following: **{bond_str}** ", colour=0xd14d4d) # noqa: E501 - await ctx.respond(embed=embed) - else: - embed = discord.Embed(title="Configuration Error", description="Unexpected error. Please try again. If the error persists, contact @0xRusowsky", colour=0xd14d4d) # noqa: E501 - await ctx.respond(embed=embed) - - -@client.slash_command(description='Delete an existing bond alert') -async def delete_alert( - ctx, - bond_type: Option(str, "Input the desired bond (use the /info_bonds command to see all the bond types)."), # noqa: F722,E501 - min_discount: Option(float, "Input the min_discount threshold (must be a number, without '%' sign)."), # noqa: F722,E501 -): - bond = bond_type.upper() - - code = remove_alert(alert_db, delete_bond=bond_type, - delete_discount=min_discount, delete_user=ctx.author.id) - if code == 1: - embed = discord.Embed(title="Bond Alert Deleted", description=f'{ctx.author.mention} has deleted an alert for discounts over {min_discount}% on {bond} bonds!', colour=0x17d988) # noqa: E501 - await ctx.respond(embed=embed) - elif code == 0: - embed = discord.Embed(title="Alert Doesn't Exist", description=f'{ctx.author.mention} does not have any alerts for discounts over {min_discount}% on {bond} bonds.', colour=0xFFFFFF) # noqa: E501 - await ctx.respond(embed=embed) - else: - embed = discord.Embed(title="Configuration Error", description="Unexpected error. Please try again. If the error persists, contact @0xRusowsky", colour=0xd14d4d) # noqa: E501 - await ctx.respond(embed=embed) - - -@client.slash_command(description='Delete all the previously configured bond alerts.') -async def delete_all(ctx): - alert_list = search_alert(alert_db, search_user=str(ctx.author.id)) - if len(alert_list) > 0: - check = 0 - for a in alert_list: - code = remove_alert(alert_db, delete_bond=a.bond, - delete_discount=a.discount, delete_user=a.user) - check += code - if check == len(alert_list): - embed = discord.Embed(title="All Bond Alerts Deleted", description=f'{ctx.author.mention} has deleted all bond alerts!', colour=0x17d988) # noqa: E501 - await ctx.respond(embed=embed) - else: - embed = discord.Embed(title="Configuration Error", description="Unexpected error. Please try again. If the error persists, contact @0xRusowsky", colour=0xd14d4d) # noqa: E501 - await ctx.respond(embed=embed) - - else: - embed = discord.Embed(title="No Alerts Configured", description=f'{ctx.author.mention} does not have any configured alerts.', colour=0xFFFFFF) # noqa: E501 - await ctx.respond(embed=embed) - - -@client.slash_command(description='Check all the already existing alerts.') -async def my_alerts(ctx): - alert_list = search_alert(alert_db, search_user=str(ctx.author.id)) - if len(alert_list) > 0: - embed = discord.Embed(title="Configured Alerts", description=f'{ctx.author.mention} currently has the following alerts configured.', colour=0xFFFFFF) # noqa: E501 - for a in alert_list: - embed.add_field(name='Bond Type', value=a.bond, inline=True) - embed.add_field(name='Discount', - value=f'{a.discount}%', inline=True) - embed.add_field(name='\u200b', value='\u200b', inline=True) - await ctx.respond(embed=embed) - else: - embed = discord.Embed(title="No Alerts", description=f'{ctx.author.mention} has not configured any alerts yet. \n To create new alerts use the **/create_alert** command and follow the instructions.', colour=0xFFFFFF) # noqa: E501 - await ctx.respond(embed=embed) - - -@client.slash_command(description="Check all the commands and a brief explanation on how to use them.") -async def help_bonds(ctx): - embed = discord.Embed(title='Help Panel', description='Here you can see a list with all the KlimaDAO Alerts commands and a brief explanation on how to use them.', colour=0xFFFFFF) # noqa: E501 - embed.add_field(name=':small_blue_diamond: /info_bonds', value='Returns a list with the names of the partners, the bond types and the payout tokens that KlimaDAO offers.', inline=False) # noqa: E501 - embed.add_field(name=':small_blue_diamond: /bonds', value='Returns all the live information for every bond type issued by KlimaDAO.', inline=False) # noqa: E501 - embed.add_field(name=':small_blue_diamond: /my_alerts', value='Returns a list with all the alerts that the user has configured.', inline=False) # noqa: E501 - embed.add_field(name=':small_orange_diamond: /create_alert', value='Creates a new alert that will be triggered when bond discounts are greater than *min_discount*. \n :black_small_square: **bond_type:** must belong to the ones listed under **/info_bonds** \n :black_small_square: **min_discount:** XX.XX (number expressed in %)', inline=False) # noqa: E501 - embed.add_field(name=':small_orange_diamond: /delete_all', value='Deletes all the alerts that the user previously configured.', inline=False) # noqa: E501 - embed.add_field(name=':small_orange_diamond: /delete_alert', value='Deletes an existing bond alert. \n :black_small_square: **bond_type:** must belong to the ones listed under **/info_bonds** \n :black_small_square: **min_discount:** XX.XX (number expressed in %) \n ', inline=False) # noqa: E501 - - embed.set_footer( - text='If you have any ideas or improvement suggestions please DM your klimate @0xRusowsky') - await ctx.respond(embed=embed) - - -client.run(BOT_TOKEN) diff --git a/src/dao_balance/__init__.py b/src/dao_balance/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/dao_balance/main.py b/src/dao_balance/main.py deleted file mode 100644 index eae770c..0000000 --- a/src/dao_balance/main.py +++ /dev/null @@ -1,61 +0,0 @@ -import os -from discord.ext import tasks - -from ..constants import KLIMA_ADDRESS, DAO_WALLET_ADDRESS, \ - KLIMA_DECIMALS -from ..contract_info import balance_of -from ..utils import get_discord_client, \ - get_polygon_web3, load_abi, prettify_number, \ - update_nickname, update_presence - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] - -# Initialize web3 -web3 = get_polygon_web3() - -# Load ABI -erc_20_abi = load_abi('erc20_token.json') - -# Initialized Discord client -client = get_discord_client() - - -def get_info(): - dao_balance = get_dao_balance() - return dao_balance - - -def get_dao_balance(): - dao_balance = balance_of( - web3, KLIMA_ADDRESS, erc_20_abi, KLIMA_DECIMALS, DAO_WALLET_ADDRESS) - return dao_balance - - -@client.event -async def on_ready(): - print('Logged in as {0.user}'.format(client)) - if not update_info.is_running(): - update_info.start() - - -@tasks.loop(seconds=300) -async def update_info(): - dao_balance = get_info() - - if dao_balance is not None: - - balance_text = f'DAO: {prettify_number(dao_balance)} KLIMA' - success = await update_nickname(client, balance_text) - if not success: - return - - presence_text = "DAO wallet balance" - success = await update_presence( - client, - presence_text, - type='playing' - ) - if not success: - return - -client.run(BOT_TOKEN) diff --git a/src/dao_fee/__init__.py b/src/dao_fee/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/dao_fee/main.py b/src/dao_fee/main.py deleted file mode 100644 index 0467301..0000000 --- a/src/dao_fee/main.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -from discord.ext import tasks - -from subgrounds.subgrounds import Subgrounds - -from ..constants import KLIMA_BONDS_SUBGRAPH -from ..utils import get_discord_client, \ - prettify_number, \ - update_nickname, update_presence -from ..time_utils import get_days_ago_timestamp - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] - -# Initialized Discord client -client = get_discord_client() - -sg = Subgrounds() - - -def get_info(): - latest_dao_fee = get_latest_fee() - return latest_dao_fee - - -def get_latest_fee(): - ''' - Retrieve recent DAO Wallet fee by checking the Daily Bonds Subgraph Query - ''' - ts = get_days_ago_timestamp(7) - todays_fee = get_recent_dao_fee(sg, ts) - - return todays_fee - - -def get_recent_dao_fee(sg, ts): - try: - kbm = sg.load_subgraph(KLIMA_BONDS_SUBGRAPH) - - weekly_bonds = kbm.Query.dailyBonds( - where=[kbm.DailyBond.timestamp > ts] - ) - - fee_df = sg.query_df([weekly_bonds.daoFee]) - - if fee_df.size == 0: - return 0 - - todays_fees = fee_df["dailyBonds_daoFee"].sum() - - return todays_fees - - except Exception: - return None - - -@client.event -async def on_ready(): - print('Logged in as {0.user}'.format(client)) - if not update_info.is_running(): - update_info.start() - - -@tasks.loop(seconds=300) -async def update_info(): - latest_dao_fee = get_info() - - if latest_dao_fee is not None: - fee_text = f'Fees: {prettify_number(latest_dao_fee)} KLIMA' - - presence_text = "from the last 7d bonds" - success = await update_nickname(client, fee_text) - if not success: - return - - success = await update_presence( - client, - presence_text, - type='playing' - ) - if not success: - return - -client.run(BOT_TOKEN) diff --git a/src/dcm_supply/__init__.py b/src/dcm_supply/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/dcm_supply/main.py b/src/dcm_supply/main.py deleted file mode 100644 index e2eb2cd..0000000 --- a/src/dcm_supply/main.py +++ /dev/null @@ -1,90 +0,0 @@ -import os -import math - -from discord.ext import tasks -from subgrounds.subgrounds import Subgrounds -from web3.middleware import geth_poa_middleware - -from ..utils import ( - get_discord_client, - get_polygon_web3, - update_nickname, - update_presence, - get_last_metric, - get_last_carbon, - prettify_number, - get_rebases_per_day, - get_staking_params -) - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] - -# Initialized Discord client -client = get_discord_client() - -sg = Subgrounds() - -# Initialize web3 -web3 = get_polygon_web3() -web3.middleware_onion.inject(geth_poa_middleware, layer=0) - - -def get_info(): - last_metric = get_last_metric(sg) - total_carbon = sg.query([last_metric.treasuryCarbon]) - - last_carbon = get_last_carbon(sg) - current_sma, credit_supply = sg.query( - [last_carbon.creditSMA, last_carbon.creditSupply] - ) - return total_carbon, current_sma, credit_supply - - -@client.event -async def on_ready(): - print("Logged in as {0.user}".format(client)) - if not update_info.is_running(): - update_info.start() - - -@tasks.loop(seconds=300) -async def update_info(): - staking_reward, epoch_length = get_staking_params(web3) - rebases_per_day = get_rebases_per_day(epoch_length) - - treasury_carbon, carbon_sma, credit_supply = get_info() - - carbon_sma = carbon_sma / 1e18 - credit_supply = credit_supply / 1e18 - - print(treasury_carbon) - print(carbon_sma) - if ( - treasury_carbon is not None - and carbon_sma is not None - and rebases_per_day is not None - ): - sma_percent = carbon_sma / credit_supply - # ie, annualized reward % - supply_change_annual = math.pow(1 + sma_percent, 365 * rebases_per_day) - 1 - else: - return - - yield_text = f"{supply_change_annual*100:,.2f}% Δ DCM Supply" - print(yield_text) - - success = await update_nickname(client, yield_text) - if not success: - return - - presence_txt = f'{prettify_number(credit_supply)}t Σ DCM Supply' - success = await update_presence( - client, - presence_txt, - type='playing' - ) - if not success: - return - - -client.run(BOT_TOKEN) diff --git a/src/guerilla_marketing/__init__.py b/src/guerilla_marketing/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/guerilla_marketing/assets/carbonguzzler.png b/src/guerilla_marketing/assets/carbonguzzler.png deleted file mode 100755 index 54d15e3..0000000 Binary files a/src/guerilla_marketing/assets/carbonguzzler.png and /dev/null differ diff --git a/src/guerilla_marketing/assets/virginchad.png b/src/guerilla_marketing/assets/virginchad.png deleted file mode 100755 index 6d33ff8..0000000 Binary files a/src/guerilla_marketing/assets/virginchad.png and /dev/null differ diff --git a/src/guerilla_marketing/contracts.py b/src/guerilla_marketing/contracts.py deleted file mode 100644 index 31f7449..0000000 --- a/src/guerilla_marketing/contracts.py +++ /dev/null @@ -1,224 +0,0 @@ -AKLIMA_CONTRACT_ADDR = '0x6b4d5e9ec2acea23d4110f4803da99e25443c5df' - -ERC20_ABI = '''[ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "payable": true, - "stateMutability": "payable", - "type": "fallback" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - } -]''' diff --git a/src/guerilla_marketing/guerilla_slash.py b/src/guerilla_marketing/guerilla_slash.py deleted file mode 100644 index a9f3a69..0000000 --- a/src/guerilla_marketing/guerilla_slash.py +++ /dev/null @@ -1,94 +0,0 @@ -import os - -import discord -from discord.ext import commands -from discord_slash import SlashCommand # Importing the newly installed library. -from discord_slash.utils.manage_commands import create_option, create_choice - -from guerilla_utils import make_qr_code, overlay_qr, validate_address, STICKER_PATHS - -# https://pythondiscord.com/pages/guides/pydis-guides/contributing/setting-test-server-and-bot-account/ -token = os.getenv("DISCORD_BOT_TOKEN") - -guild_ids = [ - '887786280686075974', # Personal test server - # '841390338324824096' # KlimaDAO official community server -] - -# TODO: create dedicated channel and limit Slash commands to that channel - -bot = commands.Bot('.', intents=discord.Intents.default()) -slash = SlashCommand(bot, sync_commands=True) # Declares slash commands through the client. - - -@bot.event -async def on_ready(): - print("Ready!") - - -@slash.subcommand(base="guerilla", - name="menu", - description="Show the menu of memes to accompany guerilla marketing QR codes.") -async def guerilla_menu(ctx): - await ctx.send("Here be 🌳 memes", files=[discord.File(path) for path in STICKER_PATHS.values()]) - - -@slash.subcommand(base="guerilla", - name="request", - description="Request a customized guerilla marketing sticker with unique QR code.", - options=[ - create_option( - name="location", - description="City or country for tracking by location.", - option_type=3, - required=True - ), - create_option( - name="address", - description="Public address of your Ethereum wallet with non-zero aKLIMA balance.", - option_type=3, - required=True - ), - create_option( - name="sticker_id", - description="Choose which sticker image you would like to accompany your QR code.", - option_type=3, - required=True, - choices=[ - create_choice( - name="Carbon Guzzler", - value='carbonguzzler' - ), - create_choice( - name="Virgin vs. Chad", - value='virginchad' - ) - ] - ), - ]) -async def guerilla_request(ctx, location, address, sticker_id): - await ctx.defer() - - is_valid = validate_address(address) - if not is_valid: - await ctx.send( - "Invalid address provided - please confirm that it is a " - "valid ETH mainnet address and has a non-zero aKLIMA balance" - ) - return - - qr_img = make_qr_code(location, address, sticker_id) - - custom_img_path = f'./custom_qr_{location}_{address}_{sticker_id}.png' - overlay_qr(qr_img, sticker_id, custom_img_path) - - await ctx.send( - "Here's your guerilla marketing sticker with personalized QR code! Print some out and go paint the town 🌳", - file=discord.File(custom_img_path), - # hidden=True - ) - - # TODO: Clean up generated image files - - -bot.run(token) diff --git a/src/guerilla_marketing/guerilla_utils.py b/src/guerilla_marketing/guerilla_utils.py deleted file mode 100644 index 1ae763a..0000000 --- a/src/guerilla_marketing/guerilla_utils.py +++ /dev/null @@ -1,105 +0,0 @@ -import os - -from PIL import Image -import qrcode -from web3.auto.infura import w3 - -from contracts import AKLIMA_CONTRACT_ADDR, ERC20_ABI - -STICKER_PATHS = { - 'carbonguzzler': 'carbonguzzler.png', - 'virginchad': 'virginchad.png' -} -STICKER_PATHS = { - k: os.path.join(os.getcwd(), 'Documents/Code/klimadao/discord_bots/assets', v) - for k, v in STICKER_PATHS.items() -} - -STICKER_OFFSETS = { - 'carbonguzzler': [2150, 200], - 'virginchad': [735, 110] -} - -STICKER_RESIZES = { - 'carbonguzzler': 0, - 'virginchad': 1/4 -} - - -def assemble_url(location, address, desired_image): - return f"https://klimadao.finance?utm_source=guerilla+qr&utm_medium={location}&utm_campaign={address}" - - -def make_qr_code(location, address, desired_image): - ''' - Generate unique QR codes using each location and each user's address - ''' - qr = qrcode.QRCode( - version=1, - error_correction=qrcode.constants.ERROR_CORRECT_L, - box_size=10, - border=4, - ) - qr.add_data(assemble_url(location, address, desired_image)) - qr.make(fit=True) - - img = qr.make_image(fill_color="black", back_color="white") - return img - - -def overlay_qr(qr, sticker_id, output_path): - ''' - Overlay a generated QR code onto the provided sticker image - ''' - # Convert images to RGBA format for compatibility - qr = qr.convert("RGBA") - - sticker = Image.open(STICKER_PATHS[sticker_id]) - sticker.convert("RGBA") - - # Calculate placement box based on QR image size and specified offsets for each sticker - qr_size = qr.size[0] - x_offset, y_offset = STICKER_OFFSETS[sticker_id] - - # If resize_factor is non-zero, scale down QR code by multiplying size by factor - resize_factor = STICKER_RESIZES[sticker_id] - if resize_factor > 0: - qr_size = int(qr_size * resize_factor) - - qr = qr.resize((qr_size, qr_size)) - - box = ( - qr_size + x_offset, qr_size - y_offset, - 2 * qr_size + x_offset, 2 * qr_size - y_offset - ) - - # Resize the QR code and paste onto the image at the proper location - qr.resize((box[2] - box[0], box[3] - box[1])) - sticker.paste(qr, box) - - sticker.save(output_path) - - -def validate_address(address): - ''' - Use web3 to validate that the provided address is: - 1. A valid ETH address - 2. Has non-zero `aKLIMA` balance - - Eventually, once the DApp launches on Polygon mainnet, - we'll need to generalize this to check for either - aKLIMA on ETH mainnet, or KLIMA or sKLIMA on Polygon - - https://web3py.readthedocs.io/en/stable/examples.html#query-account-balances - ''' - - if not w3.isAddress(address): - return False - - aklima_contract = w3.eth.contract(w3.to_checksum_address(AKLIMA_CONTRACT_ADDR), abi=ERC20_ABI) - akl_balance = aklima_contract.functions.balanceOf(address).call() - - if akl_balance > 0: - return True - else: - return False diff --git a/src/index/__init__.py b/src/index/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/index/main.py b/src/index/main.py deleted file mode 100644 index 251b1ee..0000000 --- a/src/index/main.py +++ /dev/null @@ -1,66 +0,0 @@ -import os - -from discord.ext import tasks - -from ..contract_info import klima_usdc_price -from ..constants import KLIMA_DECIMALS, STAKING_ADDRESS -from ..utils import get_discord_client, \ - get_polygon_web3, load_abi, update_nickname, update_presence - - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] - -# Initialized Discord client -client = get_discord_client() - -# Initialize web3 -web3 = get_polygon_web3() - -klima_abi = load_abi('erc20_token.json') -staking_abi = load_abi('klima_staking.json') - - -def get_index(): - contract_instance = web3.eth.contract(address=STAKING_ADDRESS, abi=staking_abi) - try: - index = contract_instance.functions.index().call() / 10**KLIMA_DECIMALS - except ValueError: - index = None - - return index - - -def get_info(): - price = klima_usdc_price(web3) - index = get_index() - - return index, price - - -@client.event -async def on_ready(): - print('Logged in as {0.user}'.format(client)) - if not update_info.is_running(): - update_info.start() - - -@tasks.loop(seconds=300) -async def update_info(): - index, price = get_info() - - if index is not None and price is not None: - print(f'{price*index} wsKLIMA Value') - - success = await update_nickname(client, f'Index: {index:,.2f}') - if not success: - return - - success = await update_presence( - client, - f'wsKLIMA Value: ${price*index:,.2f}', - type='playing') - if not success: - return - - -client.run(BOT_TOKEN) diff --git a/src/next_rewards/__init__.py b/src/next_rewards/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/next_rewards/main.py b/src/next_rewards/main.py deleted file mode 100644 index be78e66..0000000 --- a/src/next_rewards/main.py +++ /dev/null @@ -1,137 +0,0 @@ -from datetime import datetime, timedelta -import os -import json -from time import time - -import discord -from discord.ext import tasks -import requests - -from ..constants import STAKING_ADDRESS -from ..utils import get_discord_client, get_polygon_web3, load_abi, update_nickname, update_presence - - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] -SCAN_API_KEY = os.environ['POLYGONSCAN_API_KEY'] - -NOTIFY_ROLE_ID = '912771496122916905' - -# Initialized Discord client -client = get_discord_client() - -# Initialize web3 -web3 = get_polygon_web3() - -staking_abi = load_abi('klima_staking.json') - -last_warning = 0 -last_alert = 0 - - -def get_webhook(): - return discord.SyncWebhook.from_url( - os.environ["DISCORD_REBASE_BOT_WEBHOOK_URL"] - ) - - -def get_epoch_info(): - contract_instance = web3.eth.contract(address=STAKING_ADDRESS, abi=staking_abi) - try: - epoch_info = contract_instance.functions.epoch().call() - except ValueError: - epoch_info = None - - return epoch_info - - -def get_next_rewards_secs(next_rewards_block): - '''Fetch the block countdown from PolygonScan''' - resp = requests.get( - f'https://api.polygonscan.com/api?module=block&action=getblockcountdown&blockno={next_rewards_block}&apikey={SCAN_API_KEY}' # noqa: E501 - ) - - try: - next_rewards_secs = float( - json.loads(resp.content)['result']['EstimateTimeInSec'] - ) - except (TypeError, json.decoder.JSONDecodeError): - next_rewards_secs = None - - return next_rewards_secs - - -@client.event -async def on_ready(): - print('Logged in as {0.user}'.format(client)) - if not update_info.is_running(): - update_info.start() - - -@tasks.loop(seconds=60) -async def update_info(): - global last_warning - global last_alert - epoch_info = get_epoch_info() - - if epoch_info is None: - return - - # unpack epoch info - next_epoch_number = epoch_info[1] - next_rewards_block = epoch_info[2] - - # Datetime calculations - next_rewards_secs = get_next_rewards_secs(next_rewards_block) - - if next_rewards_secs is None: - return - - next_rewards_delta = timedelta(seconds=next_rewards_secs) - next_rewards_datetime = datetime.utcnow() + next_rewards_delta - - # Extract hours and minutes from timedelta for formatting - hours, remainder = divmod(next_rewards_delta.total_seconds(), 3600) - minutes, seconds = divmod(remainder, 60) - - # Format time - next_rewards_time = next_rewards_datetime.time().strftime("%H:%M") - - # More than 15m since the last warning - warning_mins = 15 - warning_secs = warning_mins * 60 - if next_rewards_secs <= warning_secs and time() - last_warning > warning_secs + 30: - last_warning = time() - webhook = get_webhook() - try: - webhook.send( - f"Rewards imminent in approximately {warning_mins} minutes <@&{NOTIFY_ROLE_ID}>" - ) - except discord.errors.NotFound: - print("Webhook not found") - - # More than 120s since the last alert - if next_rewards_secs <= 90 and time() - last_alert > 120: - last_alert = time() - webhook = get_webhook() - try: - webhook.send( - f"Rewards distributed momentarily! <@&{NOTIFY_ROLE_ID}> (:deciduous_tree:, :deciduous_tree:)" - ) - except discord.errors.NotFound: - print("Webhook not found") - - countdown_text = f'Rewards in {int(hours)}h {int(minutes)}m' - - success = await update_nickname(client, countdown_text) - if not success: - return - - success = await update_presence( - client, f'Epoch {next_epoch_number} @ {next_rewards_time} UTC', - 'playing' - ) - if not success: - return - - -client.run(BOT_TOKEN) diff --git a/src/supply_cc/__init__.py b/src/supply_cc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/supply_cc/main.py b/src/supply_cc/main.py deleted file mode 100644 index 8703f87..0000000 --- a/src/supply_cc/main.py +++ /dev/null @@ -1,65 +0,0 @@ -import os - -from discord.ext import tasks - -from subgrounds.subgrounds import Subgrounds - -from ..contract_info import token_supply -from ..constants import KLIMA_ADDRESS, KLIMA_DECIMALS -from ..utils import get_discord_client, \ - get_polygon_web3, load_abi, prettify_number, \ - update_nickname, update_presence, \ - get_last_metric - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] - -# Initialized Discord client -client = get_discord_client() - -# Initialize web3 -web3 = get_polygon_web3() - -klima_abi = load_abi('erc20_token.json') -staking_abi = load_abi('klima_staking.json') - -sg = Subgrounds() - - -def get_cc(): - last_metric = get_last_metric(sg) - - return sg.query([last_metric.treasuryCarbonCustodied]) - - -def get_info(): - supply = token_supply(web3, KLIMA_ADDRESS, klima_abi, KLIMA_DECIMALS) - cc = get_cc() - - return cc, supply - - -@client.event -async def on_ready(): - print('Logged in as {0.user}'.format(client)) - if not update_info.is_running(): - update_info.start() - - -@tasks.loop(seconds=300) -async def update_info(): - cc, supply = get_info() - - if cc is not None and supply is not None: - supply_fmt = f'{prettify_number(supply)}' - print(f'{supply_fmt} KLIMA Supply') - - success = await update_nickname(client, f'Supply: {supply_fmt}') - if not success: - return - - success = await update_presence(client, f'CC per KLIMA: {cc/supply:,.2f}') - if not success: - return - - -client.run(BOT_TOKEN) diff --git a/src/treasury_carbon/__init__.py b/src/treasury_carbon/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/treasury_carbon/main.py b/src/treasury_carbon/main.py deleted file mode 100644 index c8dcaa4..0000000 --- a/src/treasury_carbon/main.py +++ /dev/null @@ -1,50 +0,0 @@ -import os - -from discord.ext import tasks - -from subgrounds.subgrounds import Subgrounds - -from ..utils import get_discord_client, prettify_number, \ - update_nickname, update_presence, \ - get_last_metric - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] - -# Initialized Discord client -client = get_discord_client() - -sg = Subgrounds() - - -def get_info(): - last_metric = get_last_metric(sg) - total_cc, total_carbon = sg.query([last_metric.treasuryCarbonCustodied, last_metric.treasuryCarbon]) - - return total_cc, total_carbon - - -@client.event -async def on_ready(): - print('Logged in as {0.user}'.format(client)) - if not update_info.is_running(): - update_info.start() - - -@tasks.loop(seconds=300) -async def update_info(): - cc, total_carbon = get_info() - - if cc is not None and total_carbon is not None: - total_fmt = f'{prettify_number(total_carbon)}t' - print(f'TTC: {total_fmt}') - - success = await update_nickname(client, f'TTC: {total_fmt}') - if not success: - return - - success = await update_presence(client, f'Total CC: {cc/1e6:,.1f}Mt') - if not success: - return - - -client.run(BOT_TOKEN) diff --git a/src/treasury_market/__init__.py b/src/treasury_market/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/treasury_market/main.py b/src/treasury_market/main.py deleted file mode 100644 index 4e4ce4a..0000000 --- a/src/treasury_market/main.py +++ /dev/null @@ -1,50 +0,0 @@ -import os - -from discord.ext import tasks - -from subgrounds.subgrounds import Subgrounds - -from ..utils import get_discord_client, prettify_number, \ - update_nickname, update_presence, \ - get_last_metric - -BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"] - -# Initialized Discord client -client = get_discord_client() - -sg = Subgrounds() - - -def get_info(): - last_metric = get_last_metric(sg) - tmv, mc = sg.query([last_metric.treasuryMarketValue, last_metric.marketCap]) - - return tmv, mc - - -@client.event -async def on_ready(): - print('Logged in as {0.user}'.format(client)) - if not update_info.is_running(): - update_info.start() - - -@tasks.loop(seconds=300) -async def update_info(): - tmv, mc = get_info() - - if tmv is not None and mc is not None: - tmv_fmt = f'${prettify_number(tmv)}' - print(f'TMV: {tmv_fmt}') - - success = await update_nickname(client, f'TMV: {tmv_fmt}') - if not success: - return - - success = await update_presence(client, f'TMV/MCap: {tmv/mc:,.2f}') - if not success: - return - - -client.run(BOT_TOKEN)