Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add lock events endpoint to token API #481

Merged
merged 3 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion app/model/schema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,13 @@
IbetShareTransfer,
IbetShareAdditionalIssue,
IbetShareRedeem,
ListAllTokenLockEventsQuery,
ListAllTokenLockEventsSortItem,
# Response
TokenAddressResponse,
IbetStraightBondResponse,
IbetShareResponse
IbetShareResponse,
ListAllTokenLockEventsResponse
)
from .token_holders import (
# Request
Expand Down
39 changes: 38 additions & 1 deletion app/model/schema/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,32 @@

SPDX-License-Identifier: Apache-2.0
"""
from enum import Enum
from typing import (
List,
Optional
)
import math
from fastapi import Query

from pydantic import (
BaseModel,
Field,
validator
)
from pydantic.dataclasses import dataclass
from web3 import Web3
from . import (
LockEventCategory
)
from .position import LockEvent

from .types import (
MMDD_constr,
YYYYMMDD_constr,
EMPTY_str
EMPTY_str,
SortOrder,
ResultSet
)


Expand Down Expand Up @@ -327,6 +336,28 @@ def account_address_is_valid_address(cls, v):
return v


class ListAllTokenLockEventsSortItem(str, Enum):
account_address = "account_address"
lock_address = "lock_address"
recipient_address = "recipient_address"
value = "value"
block_timestamp = "block_timestamp"


@dataclass
class ListAllTokenLockEventsQuery:
offset: Optional[int] = Query(default=None, description="Start position", ge=0)
limit: Optional[int] = Query(default=None, description="Number of set", ge=0)

account_address: Optional[str] = Query(default=None, description="Account address")
lock_address: Optional[str] = Query(default=None, description="Lock address")
recipient_address: Optional[str] = Query(default=None, description="Recipient address")
category: Optional[LockEventCategory] = Query(default=None, description="Event category")

sort_item: ListAllTokenLockEventsSortItem = Query(default=ListAllTokenLockEventsSortItem.block_timestamp, description="Sort item")
sort_order: SortOrder = Query(default=SortOrder.DESC, description="Sort order(0: ASC, 1: DESC)")


############################
# RESPONSE
############################
Expand Down Expand Up @@ -391,3 +422,9 @@ class IbetShareResponse(BaseModel):
token_status: int
is_canceled: bool
memo: str


class ListAllTokenLockEventsResponse(BaseModel):
"""List All Lock/Unlock events (Response)"""
result_set: ResultSet
events: List[LockEvent] = Field(description="Lock/Unlock event list")
151 changes: 149 additions & 2 deletions app/routers/bond.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@
func,
literal_column,
cast,
String
String,
literal,
null,
column
)
from sqlalchemy.orm import (
Session,
Expand All @@ -64,6 +67,9 @@
UpdateTransferApprovalRequest,
ListTransferHistorySortItem,
ListTransferHistoryQuery,
ListAllTokenLockEventsQuery,
ListAllTokenLockEventsSortItem,
LockEventCategory,
# Response
IbetStraightBondResponse,
TokenAddressResponse,
Expand All @@ -85,6 +91,7 @@
BatchRegisterPersonalInfoUploadResponse,
ListBatchRegisterPersonalInfoUploadResponse,
GetBatchRegisterPersonalInfoResponse,
ListAllTokenLockEventsResponse
)
from app.model.db import (
Account,
Expand All @@ -111,7 +118,9 @@
BatchRegisterPersonalInfoUploadStatus,
BatchRegisterPersonalInfo,
TransferApprovalHistory,
TransferApprovalOperationType
TransferApprovalOperationType,
IDXLock,
IDXUnlock
)
from app.model.blockchain import (
IbetStraightBondContract,
Expand Down Expand Up @@ -1801,6 +1810,144 @@ def retrieve_batch_register_personal_info(
})


# GET: /bond/tokens/{token_address}/lock_events
@router.get(
"/tokens/{token_address}/lock_events",
Comment on lines +1813 to +1815
Copy link
Member Author

Choose a reason for hiding this comment

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

Added lock events endpoints to /bond/{token_address}/* line-up.

summary="List all lock/unlock events related to given bond token",
response_model=ListAllTokenLockEventsResponse,
responses=get_routers_responses(422)
)
def list_all_lock_events_by_bond(
token_address: str,
issuer_address: Optional[str] = Header(None),
request_query: ListAllTokenLockEventsQuery = Depends(),
db: Session = Depends(db_session)
):
# Validate Headers
validate_headers(issuer_address=(issuer_address, address_is_valid_address))

# Request parameters
offset = request_query.offset
limit = request_query.limit
sort_item = request_query.sort_item
sort_order = request_query.sort_order

# Base query
query_lock = (
db.query(
literal(value=LockEventCategory.Lock.value, type_=String).label("category"),
IDXLock.transaction_hash.label("transaction_hash"),
IDXLock.token_address.label("token_address"),
IDXLock.lock_address.label("lock_address"),
IDXLock.account_address.label("account_address"),
null().label("recipient_address"),
IDXLock.value.label("value"),
IDXLock.data.label("data"),
IDXLock.block_timestamp.label("block_timestamp"),
Token
).
join(Token, IDXLock.token_address == Token.token_address).
filter(Token.type == TokenType.IBET_STRAIGHT_BOND.value).
filter(Token.token_address == token_address).
filter(Token.token_status != 2)
)
if issuer_address is not None:
query_lock = query_lock.filter(Token.issuer_address == issuer_address)

query_unlock = (
db.query(
literal(value=LockEventCategory.Unlock.value, type_=String).label("category"),
IDXUnlock.transaction_hash.label("transaction_hash"),
IDXUnlock.token_address.label("token_address"),
IDXUnlock.lock_address.label("lock_address"),
IDXUnlock.account_address.label("account_address"),
IDXUnlock.recipient_address.label("recipient_address"),
IDXUnlock.value.label("value"),
IDXUnlock.data.label("data"),
IDXUnlock.block_timestamp.label("block_timestamp"),
Token
).
join(Token, IDXUnlock.token_address == Token.token_address).
filter(Token.type == TokenType.IBET_STRAIGHT_BOND.value).
filter(Token.token_address == token_address).
filter(Token.token_status != 2)
)
if issuer_address is not None:
query_unlock = query_unlock.filter(Token.issuer_address == issuer_address)

total = query_lock.count() + query_unlock.count()

# Filter
match request_query.category:
case LockEventCategory.Lock.value:
query = query_lock
case LockEventCategory.Unlock.value:
query = query_unlock
case _:
query = query_lock.union_all(query_unlock)

if request_query.account_address is not None:
query = query.filter(column("account_address") == request_query.account_address)
if request_query.lock_address is not None:
query = query.filter(column("lock_address") == request_query.lock_address)
if request_query.recipient_address is not None:
query = query.filter(column("recipient_address") == request_query.recipient_address)

count = query.count()

# Sort
sort_attr = column(sort_item)
if sort_order == 0: # ASC
query = query.order_by(sort_attr)
else: # DESC
query = query.order_by(desc(sort_attr))

if sort_item != ListAllTokenLockEventsSortItem.block_timestamp.value:
# NOTE: Set secondary sort for consistent results
query = query.order_by(desc(column(ListAllTokenLockEventsSortItem.block_timestamp.value)))

# Pagination
if offset is not None:
query = query.offset(offset)
if limit is not None:
query = query.limit(limit)

lock_events = query.all()

resp_data = []
for lock_event in lock_events:
_token = lock_event[9]
_bond = IbetStraightBondContract(_token.token_address).get()
token_name = _bond.name

block_timestamp_utc = timezone("UTC").localize(lock_event[8])
resp_data.append({
"category": lock_event[0],
"transaction_hash": lock_event[1],
"issuer_address": _token.issuer_address,
"token_address": lock_event[2],
"token_type": _token.type,
"token_name": token_name,
"lock_address": lock_event[3],
"account_address": lock_event[4],
"recipient_address": lock_event[5],
"value": lock_event[6],
"data": lock_event[7],
"block_timestamp": block_timestamp_utc.astimezone(local_tz).isoformat()
})

data = {
"result_set": {
"count": count,
"offset": offset,
"limit": limit,
"total": total
},
"events": resp_data
}
return json_response(data)


# POST: /bond/transfers
@router.post(
"/transfers",
Expand Down
1 change: 1 addition & 0 deletions app/routers/position.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ def list_all_locked_position(
}
return json_response(resp)


# GET: /positions/{account_address}/lock/events
@router.get(
"/{account_address}/lock/events",
Expand Down
Loading