kopia lustrzana https://github.com/bugout-dev/moonstream
422 wiersze
13 KiB
Python
422 wiersze
13 KiB
Python
"""
|
|
Moonstream Engine Play API.
|
|
"""
|
|
import logging
|
|
from typing import List, Optional
|
|
from uuid import UUID
|
|
|
|
from fastapi import Request, Depends, Query
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy.orm.exc import NoResultFound
|
|
from hexbytes import HexBytes
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from web3 import Web3
|
|
|
|
from ..models import DropperClaimant
|
|
from .. import actions
|
|
from .. import data
|
|
from .. import db
|
|
from .. import signatures
|
|
from ..contracts import Dropper_interface
|
|
from ..middleware import EngineHTTPException
|
|
from ..settings import BLOCKCHAIN_WEB3_PROVIDERS, DOCS_TARGET_PATH
|
|
from ..version import VERSION
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
tags_metadata = [{"name": "Play", "description": "Moonstream Engine Play API"}]
|
|
|
|
|
|
app = FastAPI(
|
|
title=f"Moonstream Engine Play API",
|
|
description="Moonstream Engine Play API endpoints.",
|
|
version=VERSION,
|
|
openapi_tags=tags_metadata,
|
|
openapi_url="/openapi.json",
|
|
docs_url=None,
|
|
redoc_url=f"/{DOCS_TARGET_PATH}",
|
|
)
|
|
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins="*",
|
|
allow_credentials=False,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
@app.get("/blockchains")
|
|
async def get_drops_blockchains_handler(
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> List[data.DropperBlockchainResponse]:
|
|
"""
|
|
Get list of blockchains.
|
|
"""
|
|
|
|
try:
|
|
results = actions.list_drops_blockchains(db_session=db_session)
|
|
except NoResultFound:
|
|
raise EngineHTTPException(status_code=404, detail="No drops found.")
|
|
except Exception as e:
|
|
logger.error(f"Can't get list of drops end with error: {e}")
|
|
raise EngineHTTPException(status_code=500, detail="Can't get drops")
|
|
|
|
response = [
|
|
data.DropperBlockchainResponse(
|
|
blockchain=result.blockchain,
|
|
)
|
|
for result in results
|
|
]
|
|
|
|
return response
|
|
|
|
|
|
@app.get("/claims/batch", response_model=List[data.DropBatchResponseItem])
|
|
async def get_drop_batch_handler(
|
|
blockchain: str,
|
|
address: str,
|
|
limit: int = 10,
|
|
offset: int = 0,
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> List[data.DropBatchResponseItem]:
|
|
"""
|
|
Get signed transaction for all user drops.
|
|
"""
|
|
|
|
address = Web3.toChecksumAddress(address)
|
|
|
|
try:
|
|
claimant_drops = actions.get_claimant_drops(
|
|
db_session, blockchain, address, limit, offset
|
|
)
|
|
except NoResultFound:
|
|
raise EngineHTTPException(
|
|
status_code=403, detail="You are not authorized to claim that reward"
|
|
)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
raise EngineHTTPException(status_code=500, detail="Can't get claimant")
|
|
|
|
# get claimants
|
|
try:
|
|
claimants = (
|
|
db_session.query(DropperClaimant)
|
|
.filter(
|
|
DropperClaimant.id.in_(
|
|
[item.dropper_claimant_id for item in claimant_drops]
|
|
)
|
|
)
|
|
.all()
|
|
)
|
|
except Exception as err:
|
|
logger.error(f"Can't get claimant objects for address: {address}")
|
|
raise EngineHTTPException(status_code=500, detail="Can't get claimant objects.")
|
|
|
|
claimants_dict = {item.id: item for item in claimants}
|
|
|
|
# generate list of claims
|
|
|
|
claims: List[data.DropBatchResponseItem] = []
|
|
|
|
commit_required = False
|
|
|
|
for claimant_drop in claimant_drops:
|
|
|
|
transformed_amount = claimant_drop.raw_amount
|
|
|
|
if transformed_amount is None:
|
|
|
|
transformed_amount = actions.transform_claim_amount(
|
|
db_session, claimant_drop.dropper_claim_id, claimant_drop.amount
|
|
)
|
|
|
|
signature = claimant_drop.signature
|
|
if signature is None or not claimant_drop.is_recent_signature:
|
|
dropper_contract = Dropper_interface.Contract(
|
|
BLOCKCHAIN_WEB3_PROVIDERS[blockchain],
|
|
claimant_drop.dropper_contract_address,
|
|
)
|
|
|
|
message_hash_raw = dropper_contract.claimMessageHash(
|
|
claimant_drop.claim_id,
|
|
claimant_drop.address,
|
|
claimant_drop.claim_block_deadline,
|
|
int(transformed_amount),
|
|
).call()
|
|
|
|
message_hash = HexBytes(message_hash_raw).hex()
|
|
|
|
try:
|
|
signature = signatures.DROP_SIGNER.sign_message(message_hash)
|
|
claimants_dict[claimant_drop.dropper_claimant_id].signature = signature
|
|
commit_required = True
|
|
except signatures.AWSDescribeInstancesFail:
|
|
raise EngineHTTPException(status_code=500)
|
|
except signatures.SignWithInstanceFail:
|
|
raise EngineHTTPException(status_code=500)
|
|
except Exception as err:
|
|
logger.error(f"Unexpected error in signing message process: {err}")
|
|
raise EngineHTTPException(status_code=500)
|
|
|
|
claims.append(
|
|
data.DropBatchResponseItem(
|
|
claimant=claimant_drop.address,
|
|
amount=transformed_amount,
|
|
amount_string=str(transformed_amount),
|
|
claim_id=claimant_drop.claim_id,
|
|
block_deadline=claimant_drop.claim_block_deadline,
|
|
signature=signature,
|
|
dropper_claim_id=claimant_drop.dropper_claim_id,
|
|
dropper_contract_address=claimant_drop.dropper_contract_address,
|
|
blockchain=claimant_drop.blockchain,
|
|
active=claimant_drop.active,
|
|
title=claimant_drop.title,
|
|
description=claimant_drop.description,
|
|
)
|
|
)
|
|
|
|
if commit_required:
|
|
db_session.commit()
|
|
|
|
return claims
|
|
|
|
|
|
@app.get("/claims/{dropper_claim_id}", response_model=data.DropResponse)
|
|
async def get_drop_handler(
|
|
dropper_claim_id: UUID,
|
|
address: str,
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> data.DropResponse:
|
|
"""
|
|
Get signed transaction for user with the given address for that claim.
|
|
"""
|
|
|
|
address = Web3.toChecksumAddress(address)
|
|
|
|
try:
|
|
claimant = actions.get_claimant(db_session, dropper_claim_id, address)
|
|
except NoResultFound:
|
|
raise EngineHTTPException(
|
|
status_code=403, detail="You are not authorized to claim that reward"
|
|
)
|
|
except Exception as e:
|
|
raise EngineHTTPException(status_code=500, detail="Can't get claimant")
|
|
|
|
try:
|
|
claimant_db_object = (
|
|
db_session.query(DropperClaimant)
|
|
.filter(DropperClaimant.id == claimant.dropper_claimant_id)
|
|
.one()
|
|
)
|
|
except Exception as err:
|
|
logger.error(
|
|
f"Can't get claimant object for drop: {dropper_claim_id} and address: {address}"
|
|
)
|
|
raise EngineHTTPException(status_code=500, detail="Can't get claimant object.")
|
|
|
|
if not claimant.active:
|
|
raise EngineHTTPException(
|
|
status_code=403, detail="Cannot claim rewards for an inactive claim"
|
|
)
|
|
|
|
# If block deadline has already been exceeded - the contract (or frontend) will handle it.
|
|
if claimant.claim_block_deadline is None:
|
|
raise EngineHTTPException(
|
|
status_code=403,
|
|
detail="Cannot claim rewards for a claim with no block deadline",
|
|
)
|
|
|
|
transformed_amount = claimant.raw_amount
|
|
if transformed_amount is None:
|
|
transformed_amount = actions.transform_claim_amount(
|
|
db_session, dropper_claim_id, claimant.amount
|
|
)
|
|
|
|
signature = claimant.signature
|
|
if signature is None or not claimant.is_recent_signature:
|
|
dropper_contract = Dropper_interface.Contract(
|
|
claimant.blockchain, claimant.dropper_contract_address
|
|
)
|
|
message_hash_raw = dropper_contract.claimMessageHash(
|
|
claimant.claim_id,
|
|
claimant.address,
|
|
claimant.claim_block_deadline,
|
|
int(transformed_amount),
|
|
).call()
|
|
|
|
message_hash = HexBytes(message_hash_raw).hex()
|
|
|
|
try:
|
|
signature = signatures.DROP_SIGNER.sign_message(message_hash)
|
|
claimant_db_object.signature = signature
|
|
db_session.commit()
|
|
except signatures.AWSDescribeInstancesFail:
|
|
raise EngineHTTPException(status_code=500)
|
|
except signatures.SignWithInstanceFail:
|
|
raise EngineHTTPException(status_code=500)
|
|
except Exception as err:
|
|
logger.error(f"Unexpected error in signing message process: {err}")
|
|
raise EngineHTTPException(status_code=500)
|
|
|
|
return data.DropResponse(
|
|
claimant=claimant.address,
|
|
amount=str(transformed_amount),
|
|
claim_id=claimant.claim_id,
|
|
block_deadline=claimant.claim_block_deadline,
|
|
signature=signature,
|
|
title=claimant.title,
|
|
description=claimant.description,
|
|
)
|
|
|
|
|
|
@app.get("/drops", response_model=data.DropListResponse)
|
|
async def get_drop_list_handler(
|
|
blockchain: str,
|
|
claimant_address: str,
|
|
dropper_contract_address: Optional[str] = Query(None),
|
|
terminus_address: Optional[str] = Query(None),
|
|
terminus_pool_id: Optional[int] = Query(None),
|
|
active: Optional[bool] = Query(None),
|
|
limit: int = 20,
|
|
offset: int = 0,
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> data.DropListResponse:
|
|
"""
|
|
Get list of drops for a given dropper contract and claimant address.
|
|
"""
|
|
|
|
if dropper_contract_address:
|
|
dropper_contract_address = Web3.toChecksumAddress(dropper_contract_address)
|
|
|
|
if claimant_address:
|
|
claimant_address = Web3.toChecksumAddress(claimant_address)
|
|
|
|
if terminus_address:
|
|
terminus_address = Web3.toChecksumAddress(terminus_address)
|
|
|
|
try:
|
|
results = actions.get_claims(
|
|
db_session=db_session,
|
|
dropper_contract_address=dropper_contract_address,
|
|
blockchain=blockchain,
|
|
claimant_address=claimant_address,
|
|
terminus_address=terminus_address,
|
|
terminus_pool_id=terminus_pool_id,
|
|
active=active,
|
|
limit=limit,
|
|
offset=offset,
|
|
)
|
|
except NoResultFound:
|
|
raise EngineHTTPException(status_code=404, detail="No drops found.")
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Can't get claims for user {claimant_address} end with error: {e}"
|
|
)
|
|
raise EngineHTTPException(status_code=500, detail="Can't get claims")
|
|
|
|
return data.DropListResponse(drops=[result for result in results])
|
|
|
|
|
|
@app.get("/drops/contracts", response_model=List[data.DropperContractResponse])
|
|
async def get_dropper_contracts_handler(
|
|
blockchain: Optional[str] = Query(None),
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> List[data.DropperContractResponse]:
|
|
"""
|
|
Get list of drops for a given dropper contract.
|
|
"""
|
|
|
|
try:
|
|
results = actions.list_dropper_contracts(
|
|
db_session=db_session, blockchain=blockchain
|
|
)
|
|
except NoResultFound:
|
|
raise EngineHTTPException(status_code=404, detail="No drops found.")
|
|
except Exception as e:
|
|
logger.error(f"Can't get list of dropper contracts end with error: {e}")
|
|
raise EngineHTTPException(status_code=500, detail="Can't get contracts")
|
|
|
|
response = [
|
|
data.DropperContractResponse(
|
|
id=result.id,
|
|
blockchain=result.blockchain,
|
|
address=result.address,
|
|
title=result.title,
|
|
description=result.description,
|
|
image_uri=result.image_uri,
|
|
)
|
|
for result in results
|
|
]
|
|
|
|
return response
|
|
|
|
|
|
@app.get("/drops/{dropper_claim_id}", response_model=data.DropperClaimResponse)
|
|
async def get_drop_handler(
|
|
request: Request,
|
|
dropper_claim_id: str,
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> data.DropperClaimResponse:
|
|
"""
|
|
Get drop.
|
|
"""
|
|
|
|
try:
|
|
drop = actions.get_drop(
|
|
db_session=db_session, dropper_claim_id=dropper_claim_id
|
|
)
|
|
except NoResultFound:
|
|
raise EngineHTTPException(status_code=404, detail="No drops found.")
|
|
except Exception as e:
|
|
logger.error(f"Can't get drop {dropper_claim_id} end with error: {e}")
|
|
raise EngineHTTPException(status_code=500, detail="Can't get drop")
|
|
|
|
return data.DropperClaimResponse(
|
|
id=drop.id,
|
|
dropper_contract_id=drop.dropper_contract_id,
|
|
title=drop.title,
|
|
description=drop.description,
|
|
active=drop.active,
|
|
claim_block_deadline=drop.claim_block_deadline,
|
|
terminus_address=drop.terminus_address,
|
|
terminus_pool_id=drop.terminus_pool_id,
|
|
claim_id=drop.claim_id,
|
|
)
|
|
|
|
|
|
@app.get("/terminus")
|
|
async def get_drops_terminus_handler(
|
|
blockchain: str = Query(None),
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> List[data.DropperTerminusResponse]:
|
|
|
|
"""
|
|
Return distinct terminus pools
|
|
"""
|
|
|
|
try:
|
|
results = actions.list_drops_terminus(
|
|
db_session=db_session, blockchain=blockchain
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Can't get list of terminus contracts end with error: {e}")
|
|
raise EngineHTTPException(
|
|
status_code=500, detail="Can't get terminus contracts"
|
|
)
|
|
|
|
response = [
|
|
data.DropperTerminusResponse(
|
|
terminus_address=result.terminus_address,
|
|
terminus_pool_id=result.terminus_pool_id,
|
|
blockchain=result.blockchain,
|
|
)
|
|
for result in results
|
|
]
|
|
|
|
return response
|