Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5984c19
cherrypick part of changes
Dec 1, 2021
613a469
create indexer.py
Dec 1, 2021
c0d0e6c
remove solana_receipts_update.py
Dec 1, 2021
3f6b5c4
Cherry pick files from old branch
Dec 1, 2021
0790298
add requirement
Dec 1, 2021
340c854
fix refactoring issues
Dec 1, 2021
7449b38
Fix inspection issues
Dec 1, 2021
b3dacfa
fix last issue
Dec 1, 2021
a50cd47
Merge branch '336_indexer_refactoring' into 337_сreate_base_airdroppe…
Dec 1, 2021
2b8f879
Merge remote-tracking branch 'origin/develop' into 337_сreate_base_ai…
Dec 2, 2021
f51f2ed
simplify tests
Dec 2, 2021
6678924
add test
Dec 2, 2021
5d454b7
Merge remote-tracking branch 'origin/develop' into 337_сreate_base_ai…
Dec 3, 2021
add136a
add price provider
Dec 1, 2021
9a4be44
fix PriceProvider, add test
Dec 1, 2021
07aaca8
Add tests. Check worn on all nets
Dec 1, 2021
7a46c12
refactoring
Dec 1, 2021
2fc1424
integrate price_provider into airdropper
Dec 2, 2021
d157d67
integrate price provider
Dec 2, 2021
8a6abfd
use new faucet method
Dec 3, 2021
3d4dec9
add new parameter to airdropper main
Dec 3, 2021
5c29832
Test discriptions for airdropper
Dec 3, 2021
6a4efdc
Comments for price provider tests
Dec 3, 2021
14aeeed
remove unnecessary comment
Dec 3, 2021
bd35791
Merge remote-tracking branch 'origin/develop' into 338_create_sol_pri…
Dec 3, 2021
ff1f557
Merge remote-tracking branch 'origin/develop' into 338_create_sol_pri…
Dec 6, 2021
c28ca8e
fix error
Dec 6, 2021
8c68755
Merge remote-tracking branch 'origin/develop' into 338_create_sol_pri…
Dec 7, 2021
5325895
Merge remote-tracking branch 'origin/develop' into 338_create_sol_pri…
Dec 8, 2021
a1cbad2
fix airdropper run
Dec 10, 2021
4ed6d78
remove duplicated code
Dec 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions proxy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
evm_loader_id = os.environ['EVM_LOADER']
faucet_url = os.environ['FAUCET_URL']
wrapper_whitelist = os.environ['INDEXER_ERC20_WRAPPER_WHITELIST'].split(',')
airdrop_amount = int(os.environ['AIRDROP_AMOUNT'])
log_level = os.environ['LOG_LEVEL']
price_update_interval = int(os.environ.get('PRICE_UPDATE_INTERVAL', '60'))
neon_decimals = int(os.environ.get('NEON_DECIMALS', '9'))
run_airdropper(solana_url,
evm_loader_id,
faucet_url,
wrapper_whitelist,
airdrop_amount,
log_level)
log_level,
price_update_interval,
neon_decimals)
else:
entry_point()
51 changes: 39 additions & 12 deletions proxy/indexer/airdropper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from proxy.indexer.indexer_base import IndexerBase, logger
from proxy.indexer.price_provider import PriceProvider, mainnet_solana, mainnet_price_accounts
import os
import requests
import base58
Expand All @@ -12,23 +13,34 @@
from .utils import check_error
from .sql_dict import SQLDict

ACCOUNT_CREATION_PRICE_SOL = 0.00472692
AIRDROP_AMOUNT_SOL = ACCOUNT_CREATION_PRICE_SOL / 2
NEON_PRICE_USD = 0.25

class Airdropper(IndexerBase):
def __init__(self,
solana_url,
evm_loader_id,
faucet_url = '',
wrapper_whitelist = [],
airdrop_amount = 10,
log_level = 'INFO'):
log_level = 'INFO',
price_upd_interval=60,
neon_decimals = 9):
IndexerBase.__init__(self, solana_url, evm_loader_id, log_level)

# collection of eth-address-to-create-accout-trx mappings
# for every addresses that was already funded with airdrop
self.airdrop_ready = SQLDict(tablename="airdrop_ready")
self.wrapper_whitelist = wrapper_whitelist
self.airdrop_amount = airdrop_amount
self.faucet_url = faucet_url

# Price provider need pyth.network be deployed onto solana
# so using mainnet solana for simplicity
self.price_provider = PriceProvider(mainnet_solana,
price_upd_interval,
mainnet_price_accounts)
self.neon_decimals = neon_decimals


# helper function checking if given contract address is in whitelist
def _is_allowed_wrapper_contract(self, contract_addr):
Expand Down Expand Up @@ -59,20 +71,32 @@ def _check_transfer(self, account_keys, create_token_acc, token_transfer) -> boo

def _airdrop_to(self, create_acc):
eth_address = "0x" + bytearray(base58.b58decode(create_acc['data'])[20:][:20]).hex()

if eth_address in self.airdrop_ready: # transaction already processed
return

logger.info(f"Airdrop to address: {eth_address}")
sol_price_usd = self.price_provider.get_price('SOL/USD')
if sol_price_usd is None:
logger.warning("Failed to get SOL/USD price")
return

json_data = { 'wallet': eth_address, 'amount': self.airdrop_amount }
resp = requests.post(self.faucet_url + '/request_eth_token', json = json_data)
logger.info(f'SOL/USD = ${sol_price_usd}')
airdrop_amount_usd = AIRDROP_AMOUNT_SOL * sol_price_usd
logger.info(f"Airdrop amount: ${airdrop_amount_usd}")
logger.info(f"NEON price: ${NEON_PRICE_USD}")
airdrop_amount_neon = airdrop_amount_usd / NEON_PRICE_USD
logger.info(f"Airdrop {airdrop_amount_neon} NEONs to address: {eth_address}")
airdrop_galans = int(airdrop_amount_neon * pow(10, self.neon_decimals))

json_data = { 'wallet': eth_address, 'amount': airdrop_galans }
resp = requests.post(self.faucet_url + '/request_neon_in_galans', json = json_data)
if not resp.ok:
logger.warning(f'Failed to airdrop: {resp.status_code}')
return

self.airdrop_ready[eth_address] = create_acc

self.airdrop_ready[eth_address] = create_acc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like a duplicate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


def process_trx_airdropper_mode(self, trx):
if check_error(trx):
Expand Down Expand Up @@ -138,8 +162,9 @@ def run_airdropper(solana_url,
evm_loader_id,
faucet_url = '',
wrapper_whitelist = [],
airdrop_amount = 10,
log_level = 'INFO'):
log_level = 'INFO',
price_update_interval = 60,
neon_decimals = 9):
logging.basicConfig(format='%(asctime)s - pid:%(process)d [%(levelname)-.1s] %(funcName)s:%(lineno)d - %(message)s')
logger.setLevel(logging.DEBUG)
logger.info(f"""Running indexer with params:
Expand All @@ -148,12 +173,14 @@ def run_airdropper(solana_url,
log_level: {log_level},
faucet_url: {faucet_url},
wrapper_whitelist: {wrapper_whitelist},
airdrop_amount: {airdrop_amount}""")
price update interval: {price_update_interval},
NEON decimals: {neon_decimals}""")

airdropper = Airdropper(solana_url,
evm_loader_id,
faucet_url,
wrapper_whitelist,
airdrop_amount,
log_level)
log_level,
price_update_interval,
neon_decimals)
airdropper.run()
117 changes: 117 additions & 0 deletions proxy/indexer/price_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from solana.rpc.api import Client
from solana.publickey import PublicKey
import base64
import base58
import struct
from datetime import datetime
from logging import Logger

logger = Logger(__name__)

field_info = {
'expo': { 'pos': 20, 'len': 4, 'format': '<i' },
'agg.price': { 'pos': 208, 'len': 8, 'format': '<q' },
'agg.conf': { 'pos': 216, 'len': 8, 'format': '<Q' },
'agg.status': { 'pos': 224, 'len': 4, 'format': '<I' },
}


def _unpack_field(raw_data: bytes, field_name: str):
field = field_info.get(field_name, None)
if field is None:
raise Exception(f'Unknown field name: {field_name}')

start_idx = field['pos']
stop_idx = field['pos'] + field['len']
return struct.unpack(field['format'], raw_data[start_idx:stop_idx])[0]


mainnet_solana = "https://api.mainnet-beta.solana.com"
devnet_solana = "https://api.devnet.solana.com"
testnet_solana = "https://api.testnet.solana.com"


# See available price accounts at https://pyth.network/developers/accounts/
mainnet_price_accounts = { 'SOL/USD': 'H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG' }
devnet_price_accounts = { 'SOL/USD': "J83w4HKfqxwcq3BEMMkPFSppX3gqekLyLJBexebFVkix" }
testnet_price_accounts = { 'SOL/USD': "7VJsBtJzgTftYzEeooSDYyjKXvYRWJHdwvbwfBvTg9K" }


PRICE_STATUS_UNKNOWN = 0
PRICE_STATUS_TRADING = 1


class PriceProvider:
def __init__(self, solana_url: str, default_upd_int: int, price_accounts=None):
self.client = Client(solana_url)
self.default_upd_int = default_upd_int
self.prices = {}
if price_accounts is not None:
self.price_accounts = price_accounts
else:
self.price_accounts = {}


def _get_current_time(self):
return datetime.now().timestamp()


def _read_price(self, pairname):
acc_id = self.price_accounts.get(pairname, None)
if acc_id is None:
logger.warning(f'No account found for pair {pairname}')
return None

response = self.client.get_account_info(PublicKey(acc_id))
result = response.get('result', None)
if result is None:
logger.warning(f'Failed to read account data for account {acc_id}')
return None

value = result.get('value', None)
if value is None:
logger.warning(f'Failed to read account data for account {acc_id}')
return None

data = value.get('data', None)
if not isinstance(data, list) or len(data) != 2:
logger.warning(f'Failed to read account data for account {acc_id}')
return None

encoding = data[1]
if encoding == 'base58':
data = base58.b58decode(data[0])
elif encoding == 'base64':
data = base64.b64decode(data[0])
else:
logger.warning(f'Unknown encoding {encoding}')
return None

status = _unpack_field(data, 'agg.status')
if status != PRICE_STATUS_TRADING: # not Trading
logger.warning(f'Price status is {status}')
return None

expo = _unpack_field(data, 'expo')
price = _unpack_field(data, 'agg.price')
return price * pow(10, expo)


def get_price(self, pairname):
price_data = self.prices.get(pairname, None)
current_time = self._get_current_time()

if price_data == None or current_time - price_data['last_update'] >= self.default_upd_int:
current_price = self._read_price(pairname)
if current_price is not None:
self.prices[pairname] = { 'price': current_price, 'last_update': current_time }
return current_price

if price_data is not None:
return price_data['price']
else:
return None
# price_data is not None and current_time - price_data['last_update'] < self.default_upd_int
return price_data['price']


Loading