-
Notifications
You must be signed in to change notification settings - Fork 5
Feature: Voucher integrations #225
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
Open
1yam
wants to merge
7
commits into
main
Choose a base branch
from
1yam-voucher-service
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
08914d0
feat: new field in conf for voucher
1yam d30c1a8
feat: Vouchers BaseModel
1yam 166706b
Feat: voucher integrations
1yam 7196d4f
Feat: AuthenticatedVoucher integrations
1yam 0e06e1e
fix: move fixture / metdata for voucher to conftest.py
1yam fcbd014
fix: add vouchers and authenticated voucher to client
1yam ddc8f29
fix: remove debug print
1yam File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from typing import TYPE_CHECKING, Optional, overload | ||
|
||
from typing_extensions import override | ||
|
||
from aleph.sdk.types import Voucher | ||
|
||
from .voucher import Vouchers | ||
|
||
if TYPE_CHECKING: | ||
from aleph.sdk.client.abstract import AuthenticatedAlephClient | ||
|
||
|
||
class AuthenticatedVoucher(Vouchers): | ||
""" | ||
This service is same logic than Vouchers but allow to don't pass address | ||
to use account address | ||
""" | ||
|
||
def __init__(self, client: "AuthenticatedAlephClient"): | ||
super().__init__(client) | ||
|
||
@overload | ||
def _resolve_address(self, address: str) -> str: ... | ||
|
||
@overload | ||
def _resolve_address(self, address: None) -> str: ... | ||
|
||
@override | ||
def _resolve_address(self, address: Optional[str] = None) -> str: | ||
""" | ||
Resolve the address to use. Prefer the provided address, fallback to account. | ||
""" | ||
if address: | ||
return address | ||
if self._client.account: | ||
return self._client.account.get_address() | ||
|
||
raise ValueError("No address provided and no account configured") | ||
|
||
@override | ||
async def get_vouchers(self, address: Optional[str] = None) -> list[Voucher]: | ||
""" | ||
Retrieve all vouchers for the account / specific address, across EVM and Solana chains. | ||
""" | ||
address = address or self._client.account.get_address() | ||
return await super().get_vouchers(address=address) | ||
|
||
@override | ||
async def get_evm_vouchers(self, address: Optional[str] = None) -> list[Voucher]: | ||
""" | ||
Retrieve vouchers specific to EVM chains for a specific address. | ||
""" | ||
address = address or self._client.account.get_address() | ||
return await super().get_evm_vouchers(address=address) | ||
|
||
@override | ||
async def get_solana_vouchers(self, address: Optional[str] = None) -> list[Voucher]: | ||
""" | ||
Fetch Solana vouchers for a specific address. | ||
""" | ||
address = address or self._client.account.get_address() | ||
return await super().get_solana_vouchers(address=address) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
from typing import Optional | ||
|
||
import aiohttp | ||
from aiohttp import ClientResponseError | ||
from aleph_message.models import Chain | ||
|
||
from aleph.sdk.conf import settings | ||
from aleph.sdk.query.filters import PostFilter | ||
from aleph.sdk.query.responses import Post, PostsResponse | ||
from aleph.sdk.types import Voucher, VoucherMetadata | ||
|
||
|
||
class Vouchers: | ||
""" | ||
This service is made to fetch voucher (SOL / EVM) | ||
""" | ||
|
||
def __init__(self, client): | ||
self._client = client | ||
|
||
# Utils | ||
def _resolve_address(self, address: str) -> str: | ||
return address # Not Authenticated client so address need to be given | ||
|
||
async def _fetch_voucher_update(self): | ||
""" | ||
Fetch the latest EVM voucher update (unfiltered). | ||
""" | ||
|
||
post_filter = PostFilter( | ||
types=["vouchers-update"], addresses=[settings.VOUCHER_SENDER] | ||
) | ||
vouchers_post: PostsResponse = await self._client.get_posts( | ||
post_filter=post_filter, page_size=1 | ||
) | ||
|
||
if not vouchers_post.posts: | ||
return [] | ||
|
||
message_post: Post = vouchers_post.posts[0] | ||
|
||
nft_vouchers = message_post.content.get("nft_vouchers", {}) | ||
return list(nft_vouchers.items()) # [(voucher_id, voucher_data)] | ||
|
||
async def _fetch_solana_voucher_list(self): | ||
""" | ||
Fetch full Solana voucher registry (unfiltered). | ||
""" | ||
try: | ||
async with aiohttp.ClientSession() as session: | ||
async with session.get(settings.VOUCHER_SOL_REGISTRY) as resp: | ||
resp.raise_for_status() | ||
return await resp.json() | ||
except ClientResponseError: | ||
return {} | ||
|
||
async def fetch_voucher_metadata( | ||
self, metadata_id: str | ||
) -> Optional[VoucherMetadata]: | ||
""" | ||
Fetch metadata for a given voucher. | ||
""" | ||
url = f"https://claim.twentysix.cloud/sbt/metadata/{metadata_id}.json" | ||
try: | ||
async with aiohttp.ClientSession() as session: | ||
async with session.get(url) as resp: | ||
resp.raise_for_status() | ||
data = await resp.json() | ||
return VoucherMetadata.model_validate(data) | ||
except ClientResponseError: | ||
return None | ||
|
||
async def get_solana_vouchers(self, address: str) -> list[Voucher]: | ||
""" | ||
Fetch Solana vouchers for a specific address. | ||
""" | ||
resolved_address = self._resolve_address(address=address) | ||
vouchers: list[Voucher] = [] | ||
|
||
registry_data = await self._fetch_solana_voucher_list() | ||
|
||
claimed_tickets = registry_data.get("claimed_tickets", {}) | ||
batches = registry_data.get("batches", {}) | ||
|
||
for ticket_hash, ticket_data in claimed_tickets.items(): | ||
claimer = ticket_data.get("claimer") | ||
if claimer != resolved_address: | ||
continue | ||
|
||
batch_id = ticket_data.get("batch_id") | ||
metadata_id = None | ||
|
||
if str(batch_id) in batches: | ||
metadata_id = batches[str(batch_id)].get("metadata_id") | ||
|
||
if metadata_id: | ||
metadata = await self.fetch_voucher_metadata(metadata_id) | ||
if metadata: | ||
voucher = Voucher( | ||
id=ticket_hash, | ||
metadata_id=metadata_id, | ||
name=metadata.name, | ||
description=metadata.description, | ||
external_url=metadata.external_url, | ||
image=metadata.image, | ||
icon=metadata.icon, | ||
attributes=metadata.attributes, | ||
) | ||
vouchers.append(voucher) | ||
|
||
return vouchers | ||
|
||
async def get_evm_vouchers(self, address: str) -> list[Voucher]: | ||
""" | ||
Retrieve vouchers specific to EVM chains for a specific address. | ||
""" | ||
resolved_address = self._resolve_address(address=address) | ||
vouchers: list[Voucher] = [] | ||
|
||
nft_vouchers = await self._fetch_voucher_update() | ||
for voucher_id, voucher_data in nft_vouchers: | ||
if voucher_data.get("claimer") != resolved_address: | ||
continue | ||
|
||
metadata_id = voucher_data.get("metadata_id") | ||
metadata = await self.fetch_voucher_metadata(metadata_id) | ||
if not metadata: | ||
continue | ||
|
||
voucher = Voucher( | ||
id=voucher_id, | ||
metadata_id=metadata_id, | ||
name=metadata.name, | ||
description=metadata.description, | ||
external_url=metadata.external_url, | ||
image=metadata.image, | ||
icon=metadata.icon, | ||
attributes=metadata.attributes, | ||
) | ||
vouchers.append(voucher) | ||
return vouchers | ||
|
||
async def fetch_vouchers_by_chain(self, chain: Chain, address: str): | ||
if chain == Chain.SOL: | ||
return await self.get_solana_vouchers(address=address) | ||
else: | ||
return await self.get_evm_vouchers(address=address) | ||
|
||
async def get_vouchers(self, address: str) -> list[Voucher]: | ||
""" | ||
Retrieve all vouchers for the account / specific adress, across EVM and Solana chains. | ||
""" | ||
vouchers = [] | ||
|
||
# Get EVM vouchers | ||
if address.startswith("0x") and len(address) == 42: | ||
evm_vouchers = await self.get_evm_vouchers(address=address) | ||
vouchers.extend(evm_vouchers) | ||
else: | ||
# Get Solana vouchers | ||
solana_vouchers = await self.get_solana_vouchers(address=address) | ||
vouchers.extend(solana_vouchers) | ||
|
||
return vouchers |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -92,6 +92,12 @@ class Settings(BaseSettings): | |||||
) | ||||||
SCHEDULER_URL: ClassVar[str] = "https://scheduler.api.aleph.cloud/" | ||||||
|
||||||
VOUCHER_METDATA_TEMPLATE_URL: str = ( | ||||||
"https://claim.twentysix.cloud/sbt/metadata/{}.json" | ||||||
) | ||||||
VOUCHER_SOL_REGISTRY: str = "https://api.claim.twentysix.cloud/v1/registry/sol" | ||||||
VOUCHER_SENDER: str = "0xB34f25f2c935bCA437C061547eA12851d719dEFb" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that instead
Suggested change
|
||||||
|
||||||
# Web3Provider settings | ||||||
TOKEN_DECIMALS: ClassVar[int] = 18 | ||||||
TX_TIMEOUT: ClassVar[int] = 60 * 3 | ||||||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this import should be in the same form than other ones below:
https://github.com/aleph-im/aleph-sdk-python/pull/225/files#diff-98b2f220317fe0379eef848b853313affd30595845e3c4483da9fdb54cdc3a4bR36-R40