Skip to content

Commit

Permalink
Deploy using CDK V2.
Browse files Browse the repository at this point in the history
  • Loading branch information
bcrant committed Jan 16, 2022
1 parent 8ed0bd4 commit feb43e7
Show file tree
Hide file tree
Showing 13 changed files with 5,993 additions and 23 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DISCORD_APP_NAME=
DISCORD_APP_ID=
DISCORD_PUBLIC_KEY=
DISCORD_SECRET=
DISCORD_BOT_TOKEN=
22 changes: 7 additions & 15 deletions sol-idx/bin/sol-idx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@ import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { SolIdxStack } from '../lib/sol-idx-stack';

const app = new cdk.App();
new SolIdxStack(app, 'SolIdxStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */

/* Uncomment the next line to specialize this stack for the AWS Account
* and Region that are implied by the current CLI configuration. */
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
export const lambdaApiStackName = "SolIdxStack"
export const lambdaFunctionName = "solana-index-discord-fn"

/* Uncomment the next line if you know exactly what Account and Region you
* want to deploy the stack to. */
// env: { account: '123456789012', region: 'us-east-1' },

/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
const app = new cdk.App();
new SolIdxStack(app, lambdaApiStackName, {
functionName: lambdaFunctionName,
env: { region: "us-east-1" },
});
30 changes: 30 additions & 0 deletions sol-idx/lambda/discord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
import pprint
import requests
from dotenv import load_dotenv
load_dotenv("../../.env", verbose=True)


def register_discord_slash_command():
# This is an example CHAT_INPUT or Slash Command, with a type of 1
url = f"https://discord.com/api/v8/applications/{os.getenv('DISCORD_APP_ID')}/commands"

json = {
"name": "sol",
"type": 1,
"description": "Get Solana ecosystem DeFi token updates",
"options": []
}

# For authorization, you can use either your bot token
headers = {
"Authorization": f"Bot {os.getenv('DISCORD_BOT_TOKEN')}"
}

r = requests.post(url, headers=headers, json=json)

pprint.pprint(vars(r))


if __name__ == '__main__':
register_discord_slash_command()
69 changes: 69 additions & 0 deletions sol-idx/lambda/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import json
import os
from utils.environment import init_env
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError

PING_PONG = {"type": 1}
RESPONSE_TYPES = {
"PONG": 1,
"ACK_NO_SOURCE": 2,
"MESSAGE_NO_SOURCE": 3,
"MESSAGE_WITH_SOURCE": 4,
"ACK_WITH_SOURCE": 5
}


def handler(event, context):
"""
Query the Pyth price feed
"""
logger = init_env()
logger.info('RUNNING LAMBDA FUNCTION... inputs: ')
# verify the signature
try:
verify_signature(event)
except BadSignatureError as e:
raise Exception(f"[UNAUTHORIZED] Invalid request signature: {e}")

# check if message is a ping
body = event.get('body-json')
if ping_pong(body):
return PING_PONG

# dummy return
return {
"type": RESPONSE_TYPES['MESSAGE_NO_SOURCE'],
"data": {
"tts": False,
"content": "BEEP BOOP",
"embeds": [],
"allowed_mentions": []
}
}

# return {
# "isBase64Encoded": "false",
# "statusCode": "200",
# "body": json.dumps({"data": "bleep boop"})
# }


def verify_signature(event):
raw_body = event.get("rawBody")
auth_sig = event['params']['header'].get('x-signature-ed25519')
auth_ts = event['params']['header'].get('x-signature-timestamp')

message = auth_ts.encode() + raw_body.encode()
verify_key = VerifyKey(bytes.fromhex(os.getenv('DISCORD_PUBLIC_KEY')))
verify_key.verify(message, bytes.fromhex(auth_sig)) # raises an error if unequal


def ping_pong(body):
if body.get("type") == 1:
return True
return False


if os.getenv('AWS_EXECUTION_ENV') is None:
handler({'params': {'header': {"Authorization": f"Bot {os.getenv('DISCORD_BOT_TOKEN')}"}}}, {})
35 changes: 35 additions & 0 deletions sol-idx/lambda/old_requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
aiodns==3.0.0
aiohttp==3.8.1
aiosignal==1.2.0
async-timeout==4.0.2
attrs==21.4.0
backoff==1.11.1
base58==2.1.1
certifi==2021.10.8
cffi==1.15.0
charset-normalizer==2.0.10
dnspython==2.1.0
flake8==4.0.1
frozenlist==1.2.0
idna==3.3
loguru==0.5.3
lxml==4.7.1
mccabe==0.6.1
multidict==5.2.0
numpy==1.22.0
pandas==1.3.4
pycares==4.1.2
pycodestyle==2.8.0
pycoingecko==2.2.0
pycparser==2.21
pyflakes==2.4.0
pythclient==0.0.2
python-dateutil==2.8.2
python-dotenv==0.19.2
pytrends==4.7.3
pytz==2021.3
requests==2.27.1
six==1.16.0
typing_extensions==4.0.1
urllib3==1.26.8
yarl==1.7.2
36 changes: 36 additions & 0 deletions sol-idx/lambda/pyth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import asyncio
import pandas as pd
from pythclient.pythclient import PythClient
from pythclient.utils import get_key
from helpers import \
derive_index, \
get_unix_timestamps, \
limit_to_solana_tokens, \
validate_price_status
pd.set_option('display.max_columns', 500)


async def main():
v2_first_mapping_account_key = get_key('devnet', 'mapping')
v2_program_key = get_key('devnet', 'program')
async with PythClient(
first_mapping_account_key=v2_first_mapping_account_key,
program_key=v2_program_key or None,
) as c:
await c.refresh_all_prices()
solana_products = limit_to_solana_tokens(await c.get_products())
solana_products_prices = list()
for sp in solana_products:
valid_prices = validate_price_status(await sp.get_prices())
if valid_prices is not None:
solana_products_prices.append(valid_prices)

idx = derive_index(solana_products_prices)
#
# token_names: list = list(idx.keys())
# trends_df = get_trends_df(token_names)
# print(trends_df)
get_unix_timestamps()

if __name__ == '__main__':
asyncio.run(main())
9 changes: 9 additions & 0 deletions sol-idx/lambda/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
certifi==2021.10.8
cffi==1.15.0
charset-normalizer==2.0.10
idna==3.3
pycparser==2.21
PyNaCl==1.5.0
python-dotenv==0.19.2
requests==2.27.1
urllib3==1.26.8
34 changes: 34 additions & 0 deletions sol-idx/lambda/utils/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class SolanaTokens:
with open('../../../scripts/solana_tokens.txt', 'r') as txt:
tokens = list()
for token in txt.read().splitlines():
tokens.append(token.replace('\"', '').rstrip(','))
BASE = tokens
TOP = {
'SOL',
'RAY',
'SRM',
'MNGO',
'ATLAS',
'AURY',
'FIDA',
'SAMO',
'OXY',
'SLRS',
'STEP',
}
EXTENDED = {
'SOL', 'SRM', 'MSOL', 'RAY', 'MNGO', 'ATLAS', 'FIDA', 'AURY', 'IN', 'OXY', 'KIN', 'SAMO', 'SLIM',
'JET', 'ORCA', 'STEP', 'TULIP', 'SLRS', 'LIKE', 'DXL', 'MAPS', 'RIN', 'PRISM', 'SNY', 'PORT', 'COPE', 'MER',
'CYS', 'MEDIA', 'LIQ', 'FAB', 'IVN', 'CRP', 'SUNNY', 'SWARM', 'MOLA', 'SAIL', 'HOLY', 'KURO', 'CATO', 'GST',
'GSAIL', 'ROPE', 'SLX', 'SOLAB', 'SOLA', 'NAXAR', 'ASH', 'FRIES', 'DATE', 'SOLAPE', 'STR', 'APEX', 'SOLUM',
'SWOLE', 'GOFX', 'SOLPAD', 'INU', 'UPS', 'UXP', 'KITTY', 'NEKI', 'BASIS', 'SECO', 'RACEFI', 'BOP', 'ISOLA',
'FTR', 'GRAPE', 'SHILL', 'WOOF', 'OXS', 'NINJA', 'PRT', 'SCNSOL', 'POLIS', 'DFL'
}
TOP_20 = {
'SOL', 'LINK', 'GRT', 'REN',
'RAY', 'INJ', 'MNGO', 'BAND',
'FIDA', 'DFL', 'DIA', 'RAMP',
'PRQ', 'FRONT', 'OOE', 'SLRS',
'SNY', 'SLND', 'UPS', 'CYS',
}
102 changes: 102 additions & 0 deletions sol-idx/lambda/utils/environment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import logging
import os
import sys
import threading
from dotenv import load_dotenv

thread_local = threading.local()


class PathTruncatingFormatter(logging.Formatter):
def format(self, record):
"""Logging Formatter subclass to truncate the pathname to the two innermost directories."""
if 'pathname' in record.__dict__.keys():
fullpath = record.pathname
pathparts = fullpath.split('/')
if len(pathparts) >= 2:
trunc_path = './{}/{}'.format(pathparts[-2], pathparts[-1])
else:
trunc_path = fullpath
record.pathname = trunc_path

return super(PathTruncatingFormatter, self).format(record)


def init_env():
if os.getenv('AWS_EXECUTION_ENV') is None:
print('Operating in local context, loading .env file...')
load_dotenv("../../../.env", verbose=True)
else:
print('Operating in Lambda context...')

logger = init_logger()

return logger


def init_logger():
logger = getattr(thread_local, 'logger', None)
if logger is not None:
return logger

if os.getenv('LOCAL_LOG_FORMAT') is None:
local_log_format = \
'%(asctime)-26s [%(levelname)-5s] %(name)-25s %(pathname)s:%(lineno)s %(message)s'
else:
local_log_format = os.getenv('LOCAL_LOG_FORMAT')
aws_log_format = '[%(levelname)s] %(name)-25s %(pathname)s:%(lineno)s %(message)s'

log_level = logging.INFO

if os.getenv('AWS_EXECUTION_ENV') is None:
# NOTE: Local Dev, this formatter will be respected
new_format = local_log_format
logging.basicConfig(stream=sys.stdout,
format=new_format,
level=log_level)
else:
# NOTE: AWS has already mutated the logger, so we might want to adjust it.
new_format = aws_log_format
logging.basicConfig(format=aws_log_format,
level=log_level)

for h in logging.root.handlers:
h.setFormatter(PathTruncatingFormatter(new_format))

# Our default level is WARN...
logging.root.setLevel(logging.WARN)

#
# This will adjust all logging to DEBUG. It's very loud.
# However, you can see all subsystems and then selectively enable the
# various subsystems you really want verbose logging out of.
#
# logging.root.setLevel(logging.DEBUG)
#

logger = logging.getLogger('')
logger.setLevel(log_level)
logger.info('Initialized Logger...')

# These are some common loggers that can be enabled.
# urllib3.connectionpool
# boto3.resources.factory
# botocore.hooks
# botocore.utils
# botocore.parsers
# botocore.endpoint
# botocore.auth
# botocore.retryHandler
# botocore.retries.standard

#
# Enable the retry handler, because we should not really have any.
#
logging.getLogger('botocore.retries.standard').setLevel(logging.INFO)
logging.getLogger('botocore.retryHandler').setLevel(logging.INFO)

#
# This will enable the aws client debug level to be louder.
#
# logging.getLogger('boto3').setLevel(logging.DEBUG)
return logger
Loading

0 comments on commit feb43e7

Please sign in to comment.