kopia lustrzana https://github.com/bugout-dev/moonstream
338 wiersze
10 KiB
Python
338 wiersze
10 KiB
Python
"""
|
|
Contract registration API
|
|
|
|
Moonstream users can register contracts on Moonstream Engine. This allows them to use these contracts
|
|
as part of their chain-adjacent activities (like performing signature-based token distributions on the
|
|
Dropper contract).
|
|
"""
|
|
import logging
|
|
from typing import Dict, List, Optional
|
|
from uuid import UUID
|
|
|
|
from fastapi import Body, Depends, FastAPI, Query, Request, Path
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from sqlalchemy.exc import NoResultFound
|
|
from sqlalchemy.orm import Session
|
|
|
|
from .. import contracts_actions, data, db
|
|
from ..middleware import BroodAuthMiddleware, EngineHTTPException
|
|
from ..settings import DOCS_TARGET_PATH, ORIGINS
|
|
from ..version import VERSION
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
TITLE = "Moonstream Engine Contracts API"
|
|
DESCRIPTION = "Users can register contracts on the Moonstream Engine for use in chain-adjacent activities, like setting up signature-based token distributions."
|
|
|
|
|
|
tags_metadata = [
|
|
{
|
|
"name": "contracts",
|
|
"description": DESCRIPTION,
|
|
},
|
|
{"name": "requests", "description": "Call requests for registered contracts."},
|
|
]
|
|
|
|
|
|
whitelist_paths = {
|
|
"/metatx/openapi.json": "GET",
|
|
f"/metatx/{DOCS_TARGET_PATH}": "GET",
|
|
"/metatx/contracts/types": "GET",
|
|
"/metatx/requests": "GET",
|
|
}
|
|
|
|
app = FastAPI(
|
|
title=TITLE,
|
|
description=DESCRIPTION,
|
|
version=VERSION,
|
|
openapi_tags=tags_metadata,
|
|
openapi_url="/openapi.json",
|
|
docs_url=None,
|
|
redoc_url=f"/{DOCS_TARGET_PATH}",
|
|
)
|
|
|
|
|
|
app.add_middleware(BroodAuthMiddleware, whitelist=whitelist_paths)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
@app.get("/contracts/types", tags=["contracts"])
|
|
async def contract_types() -> Dict[str, str]:
|
|
"""
|
|
Describes the contract_types that users can register contracts as against this API.
|
|
"""
|
|
return {
|
|
data.ContractType.raw.value: "A generic smart contract. You can ask users to submit arbitrary calldata to this contract.",
|
|
data.ContractType.dropper.value: "A Dropper contract. You can authorize users to submit claims against this contract.",
|
|
}
|
|
|
|
|
|
@app.get("/contracts", tags=["contracts"], response_model=List[data.RegisteredContract])
|
|
async def list_registered_contracts(
|
|
request: Request,
|
|
blockchain: Optional[str] = Query(None),
|
|
address: Optional[str] = Query(None),
|
|
contract_type: Optional[data.ContractType] = Query(None),
|
|
limit: int = Query(10),
|
|
offset: Optional[int] = Query(None),
|
|
db_session: Session = Depends(db.yield_db_read_only_session),
|
|
) -> List[data.RegisteredContract]:
|
|
"""
|
|
Users can use this endpoint to look up the contracts they have registered against this API.
|
|
"""
|
|
try:
|
|
contracts = contracts_actions.lookup_registered_contracts(
|
|
db_session=db_session,
|
|
moonstream_user_id=request.state.user.id,
|
|
blockchain=blockchain,
|
|
address=address,
|
|
contract_type=contract_type,
|
|
limit=limit,
|
|
offset=offset,
|
|
)
|
|
except Exception as err:
|
|
logger.error(repr(err))
|
|
raise EngineHTTPException(status_code=500)
|
|
return [contract for contract in contracts]
|
|
|
|
|
|
@app.get(
|
|
"/contracts/{contract_id}",
|
|
tags=["contracts"],
|
|
response_model=data.RegisteredContract,
|
|
)
|
|
async def get_registered_contract(
|
|
request: Request,
|
|
contract_id: UUID = Path(...),
|
|
db_session: Session = Depends(db.yield_db_read_only_session),
|
|
) -> List[data.RegisteredContract]:
|
|
"""
|
|
Get the contract by ID.
|
|
"""
|
|
try:
|
|
contract = contracts_actions.get_registered_contract(
|
|
db_session=db_session,
|
|
moonstream_user_id=request.state.user.id,
|
|
contract_id=contract_id,
|
|
)
|
|
except NoResultFound:
|
|
raise EngineHTTPException(
|
|
status_code=404,
|
|
detail="Either there is not contract with that ID or you do not have access to that contract.",
|
|
)
|
|
except Exception as err:
|
|
logger.error(repr(err))
|
|
raise EngineHTTPException(status_code=500)
|
|
return contract
|
|
|
|
|
|
@app.post("/contracts", tags=["contracts"], response_model=data.RegisteredContract)
|
|
async def register_contract(
|
|
request: Request,
|
|
contract: data.RegisterContractRequest = Body(...),
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> data.RegisteredContract:
|
|
"""
|
|
Allows users to register contracts.
|
|
"""
|
|
try:
|
|
registered_contract = contracts_actions.register_contract(
|
|
db_session=db_session,
|
|
moonstream_user_id=request.state.user.id,
|
|
blockchain=contract.blockchain,
|
|
address=contract.address,
|
|
contract_type=contract.contract_type,
|
|
title=contract.title,
|
|
description=contract.description,
|
|
image_uri=contract.image_uri,
|
|
)
|
|
except contracts_actions.ContractAlreadyRegistered:
|
|
raise EngineHTTPException(
|
|
status_code=409,
|
|
detail="Contract already registered",
|
|
)
|
|
return registered_contract
|
|
|
|
|
|
@app.put(
|
|
"/contracts/{contract_id}",
|
|
tags=["contracts"],
|
|
response_model=data.RegisteredContract,
|
|
)
|
|
async def update_contract(
|
|
request: Request,
|
|
contract_id: UUID = Path(...),
|
|
update_info: data.UpdateContractRequest = Body(...),
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> data.RegisteredContract:
|
|
try:
|
|
contract = contracts_actions.update_registered_contract(
|
|
db_session,
|
|
request.state.user.id,
|
|
contract_id,
|
|
update_info.title,
|
|
update_info.description,
|
|
update_info.image_uri,
|
|
update_info.ignore_nulls,
|
|
)
|
|
except NoResultFound:
|
|
raise EngineHTTPException(
|
|
status_code=404,
|
|
detail="Either there is not contract with that ID or you do not have access to that contract.",
|
|
)
|
|
except Exception as err:
|
|
logger.error(repr(err))
|
|
raise EngineHTTPException(status_code=500)
|
|
|
|
return contract
|
|
|
|
|
|
@app.delete(
|
|
"/contracts/{contract_id}",
|
|
tags=["contracts"],
|
|
response_model=data.RegisteredContract,
|
|
)
|
|
async def delete_contract(
|
|
request: Request,
|
|
contract_id: UUID,
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> data.RegisteredContract:
|
|
"""
|
|
Allows users to delete contracts that they have registered.
|
|
"""
|
|
try:
|
|
deleted_contract = contracts_actions.delete_registered_contract(
|
|
db_session=db_session,
|
|
moonstream_user_id=request.state.user.id,
|
|
registered_contract_id=contract_id,
|
|
)
|
|
except Exception as err:
|
|
logger.error(repr(err))
|
|
raise EngineHTTPException(status_code=500)
|
|
|
|
return deleted_contract
|
|
|
|
|
|
@app.get("/requests", tags=["requests"], response_model=List[data.CallRequest])
|
|
async def list_requests(
|
|
contract_id: Optional[UUID] = Query(None),
|
|
contract_address: Optional[str] = Query(None),
|
|
caller: str = Query(...),
|
|
limit: int = Query(100),
|
|
offset: Optional[int] = Query(None),
|
|
show_expired: Optional[bool] = Query(False),
|
|
db_session: Session = Depends(db.yield_db_read_only_session),
|
|
) -> List[data.CallRequest]:
|
|
"""
|
|
Allows API user to see all unexpired call requests for a given caller against a given contract.
|
|
|
|
At least one of `contract_id` or `contract_address` must be provided as query parameters.
|
|
"""
|
|
try:
|
|
requests = contracts_actions.list_call_requests(
|
|
db_session=db_session,
|
|
contract_id=contract_id,
|
|
contract_address=contract_address,
|
|
caller=caller,
|
|
limit=limit,
|
|
offset=offset,
|
|
show_expired=show_expired,
|
|
)
|
|
except ValueError as e:
|
|
logger.error(repr(e))
|
|
raise EngineHTTPException(status_code=400, detail=str(e))
|
|
except Exception as e:
|
|
logger.error(repr(e))
|
|
raise EngineHTTPException(status_code=500)
|
|
|
|
return requests
|
|
|
|
|
|
@app.get("/requests/{request_id}", tags=["requests"], response_model=data.CallRequest)
|
|
async def get_request(
|
|
request_id: UUID = Path(...),
|
|
db_session: Session = Depends(db.yield_db_read_only_session),
|
|
) -> List[data.CallRequest]:
|
|
"""
|
|
Allows API user to see call request.
|
|
|
|
At least one of `contract_id` or `contract_address` must be provided as query parameters.
|
|
"""
|
|
try:
|
|
result = contracts_actions.get_call_requests(
|
|
db_session=db_session,
|
|
request_id=request_id,
|
|
)
|
|
except contracts_actions.CallRequestNotFound:
|
|
raise EngineHTTPException(
|
|
status_code=404,
|
|
detail="There is no call request with that ID.",
|
|
)
|
|
except Exception as e:
|
|
logger.error(repr(e))
|
|
raise EngineHTTPException(status_code=500)
|
|
|
|
return result
|
|
|
|
|
|
@app.post("/requests", tags=["requests"], response_model=int)
|
|
async def create_requests(
|
|
request: Request,
|
|
data: data.CreateCallRequestsAPIRequest = Body(...),
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> int:
|
|
"""
|
|
Allows API user to register call requests from given contract details, TTL, and call specifications.
|
|
|
|
At least one of `contract_id` or `contract_address` must be provided in the request body.
|
|
"""
|
|
try:
|
|
num_requests = contracts_actions.request_calls(
|
|
db_session=db_session,
|
|
moonstream_user_id=request.state.user.id,
|
|
registered_contract_id=data.contract_id,
|
|
contract_address=data.contract_address,
|
|
call_specs=data.specifications,
|
|
ttl_days=data.ttl_days,
|
|
)
|
|
except contracts_actions.InvalidAddressFormat as err:
|
|
raise EngineHTTPException(
|
|
status_code=400,
|
|
detail=f"Address not passed web3checksum validation, err: {err}",
|
|
)
|
|
except Exception as err:
|
|
logger.error(repr(err))
|
|
raise EngineHTTPException(status_code=500)
|
|
|
|
return num_requests
|
|
|
|
|
|
@app.delete("/requests", tags=["requests"], response_model=int)
|
|
async def delete_requests(
|
|
request: Request,
|
|
request_ids: List[UUID] = Body(...),
|
|
db_session: Session = Depends(db.yield_db_session),
|
|
) -> int:
|
|
"""
|
|
Allows users to delete requests.
|
|
"""
|
|
try:
|
|
deleted_requests = contracts_actions.delete_requests(
|
|
db_session=db_session,
|
|
moonstream_user_id=request.state.user.id,
|
|
request_ids=request_ids,
|
|
)
|
|
except Exception as err:
|
|
logger.error(repr(err))
|
|
raise EngineHTTPException(status_code=500)
|
|
|
|
return deleted_requests
|