Skip to content

Commit 7e6c02f

Browse files
committed
pre-release draft
1 parent 2c16700 commit 7e6c02f

File tree

13 files changed

+372
-172
lines changed

13 files changed

+372
-172
lines changed

.github/workflows/publish.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: publish
2+
3+
on:
4+
release:
5+
types: [created]
6+
7+
jobs:
8+
publish:
9+
runs-on: "ubuntu-latest"
10+
steps:
11+
- name: Check out repository
12+
uses: actions/checkout@v2
13+
14+
- name: Set up python
15+
id: setup-python
16+
uses: actions/setup-python@v2
17+
with:
18+
python-version: "3.10"
19+
20+
- name: Install Poetry
21+
uses: snok/install-poetry@v1
22+
23+
- name: Build library
24+
run: poetry build
25+
26+
- name: Authenticate in PyPi
27+
run: poetry config http-basic.pypi __token__ ${{ secrets.PYPI_TOKEN }}
28+
29+
- name: Publish library
30+
run: poetry publish

.github/workflows/test.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,4 @@ jobs:
7777
- name: Run tests
7878
run: |
7979
source .venv/bin/activate
80-
pytest tests/
81-
coverage report
80+
pytest

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,6 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
# Pycharm
132+
.idea/

README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# ⚓️ Ankr Python SDK
2+
3+
Compact Python library for interacting with Ankr's [Advanced APIs](https://www.ankr.com/advanced-api/).
4+
5+
## Get started in 2 minutes
6+
7+
#### 1. Install the package from PyPi
8+
9+
```bash
10+
pip install ankr-sdk
11+
```
12+
13+
#### 2. Initialize the SDK
14+
15+
```python3
16+
from ankr import AnkrAdvancedAPI, types
17+
18+
ankr_api = AnkrAdvancedAPI()
19+
```
20+
21+
####3. Use the sdk and call one of the supported methods
22+
23+
```python3
24+
from ankr.types import BlockchainName
25+
26+
nfts = ankr_api.get_nfts(
27+
blockchain=BlockchainName.ETH,
28+
wallet_address="0x0E11A192d574b342C51be9e306694C41547185DD",
29+
filter=[
30+
{"0x700b4b9f39bb1faf5d0d16a20488f2733550bff4": []},
31+
{"0xd8682bfa6918b0174f287b888e765b9a1b4dc9c3": ["8937"]},
32+
],
33+
)
34+
```
35+
36+
## Supported chains
37+
38+
`ankr-sdk` supports the following chains at this time:
39+
40+
- ETH: `"eth"`
41+
- BSC: `"bsc"`
42+
- Polygon: `"polygon"`
43+
- Fantom: `"fantom"`
44+
- Arbitrum: `"arbitrum"`
45+
- Avalanche: `"avalanche"`
46+
- Syscoin NEVM: `"syscoin"`
47+
48+
## Available methods
49+
50+
`ankr-sdk` supports the following methods:
51+
52+
- [`get_nfts`](#get_nfts)
53+
- [`get_logs`](#get_logs)
54+
- [`get_blocks`](#get_blocks)
55+
56+
#### `get_logs`
57+
58+
Get logs matching the filter.
59+
60+
```python3
61+
logs = ankr_api.get_logs(
62+
blockchain="eth",
63+
from_block="0xdaf6b1",
64+
to_block=14350010,
65+
address=["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"],
66+
topics=[
67+
[],
68+
["0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff"],
69+
],
70+
decode_logs=True,
71+
)
72+
```
73+
74+
#### `get_blocks`
75+
76+
Query data about blocks within a specified range.
77+
78+
```python3
79+
blocks = ankr_api.get_blocks(
80+
blockchain="eth",
81+
from_block=14500001,
82+
to_block=14500001,
83+
desc_order=True,
84+
include_logs=True,
85+
include_txs=True,
86+
decode_logs=True,
87+
)
88+
```
89+
90+
#### `get_nfts`
91+
92+
Get data about all the NFTs (collectibles) owned by a wallet.
93+
94+
````python3
95+
nfts = ankr_api.get_nfts(
96+
blockchain="eth",
97+
wallet_address="0x0E11A192d574b342C51be9e306694C41547185DD",
98+
filter=[
99+
{"0x700b4b9f39bb1faf5d0d16a20488f2733550bff4": []},
100+
{"0xd8682bfa6918b0174f287b888e765b9a1b4dc9c3": ["8937"]},
101+
],
102+
)
103+
````
104+
105+
106+
### About API keys
107+
108+
For now, Ankr is offering _free_ access to these APIs with no request limits i.e. you don't need an API key at this time.
109+
110+
Later on, these APIs will become a part of Ankr Protocol's [Premium Plan](https://www.ankr.com/protocol/plan/).

ankr/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
from .provider import AnkrAdvancedAPI
1+
from __future__ import annotations
2+
3+
from ankr.advanced_api import AnkrAdvancedAPI

ankr/advanced_api.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, Dict, Iterable, List, Optional
4+
5+
from ankr import types
6+
from ankr.provider import AnkrProvider
7+
8+
9+
class AnkrAdvancedAPI:
10+
def __init__(
11+
self,
12+
api_key: Optional[str] = None,
13+
endpoint_uri: Optional[str] = None,
14+
) -> None:
15+
self.provider = AnkrProvider(api_key or "", endpoint_uri)
16+
17+
def get_logs(
18+
self,
19+
blockchain: types.BlockchainNames,
20+
from_block: Optional[types.BlockNumber] = None,
21+
to_block: Optional[types.BlockNumber] = None,
22+
address: Optional[types.AddressOrAddresses] = None,
23+
topics: Optional[types.Topics] = None,
24+
decode_logs: Optional[bool] = None,
25+
**kwargs: Any,
26+
) -> Iterable[types.Log]:
27+
for reply in self.provider.call_method_paginated(
28+
"ankr_getLogs",
29+
types.GetLogsRequest(
30+
blockchain=blockchain,
31+
from_block=from_block,
32+
to_block=to_block,
33+
address=address,
34+
topics=topics,
35+
decode_logs=decode_logs,
36+
**kwargs,
37+
),
38+
types.GetLogsReply,
39+
):
40+
yield from reply.logs
41+
42+
def get_blocks(
43+
self,
44+
blockchain: types.BlockchainName,
45+
from_block: Optional[types.BlockNumber] = None,
46+
to_block: Optional[types.BlockNumber] = None,
47+
desc_order: Optional[bool] = None,
48+
include_logs: Optional[bool] = None,
49+
include_txs: Optional[bool] = None,
50+
decode_logs: Optional[bool] = None,
51+
decode_tx_data: Optional[bool] = None,
52+
**kwargs: Any,
53+
) -> List[types.Block]:
54+
reply = self.provider.call_method(
55+
"ankr_getBlocks",
56+
types.GetBlocksRequest(
57+
blockchain=blockchain,
58+
from_block=from_block,
59+
to_block=to_block,
60+
desc_order=desc_order,
61+
include_logs=include_logs,
62+
include_txs=include_txs,
63+
decode_logs=decode_logs,
64+
decode_tx_data=decode_tx_data,
65+
**kwargs,
66+
),
67+
types.GetBlocksReply,
68+
)
69+
return reply.blocks
70+
71+
def get_nfts(
72+
self,
73+
blockchain: types.BlockchainNames,
74+
wallet_address: str,
75+
filter: Optional[List[Dict[str, List[str]]]] = None,
76+
**kwargs: Any,
77+
) -> Iterable[types.Nft]:
78+
for reply in self.provider.call_method_paginated(
79+
"ankr_getNFTsByOwner",
80+
types.GetNFTsByOwnerRequest(
81+
blockchain=blockchain,
82+
wallet_address=wallet_address,
83+
filter=filter,
84+
**kwargs,
85+
),
86+
types.GetNFTsByOwnerReply,
87+
):
88+
yield from reply.assets

ankr/exceptions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from typing import Union
1+
from __future__ import annotations
22

33
from web3.types import RPCError
44

55

66
class AdvancedAPIException(Exception):
7-
def __init__(self, error: Union[RPCError, str]):
8-
super().__init__(f"Failed to handle request: {error}")
7+
def __init__(self, error: RPCError | str) -> None:
8+
super().__init__(f"failed to handle request, {error}")

ankr/provider.py

Lines changed: 25 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,27 @@
1-
from typing import (
2-
Optional,
3-
Union,
4-
Any,
5-
List,
6-
Iterable,
7-
TypeVar,
8-
Type,
9-
Dict,
10-
)
1+
from __future__ import annotations
112

12-
from eth_typing import (
13-
URI,
14-
)
3+
from typing import Any, Iterable, Type, TypeVar
4+
5+
from eth_typing import URI
156
from web3 import HTTPProvider
16-
from web3.types import (
17-
RPCEndpoint,
18-
RPCResponse,
19-
)
7+
from web3.types import RPCEndpoint, RPCResponse
208

219
from ankr import types
2210
from ankr.exceptions import AdvancedAPIException
2311

24-
25-
TRequestPaginated = TypeVar("TRequestPaginated", bound=types.RequestPaginated)
26-
TReplyPaginated = TypeVar("TReplyPaginated", bound=types.ReplyPaginated)
12+
TRequest = TypeVar("TRequest", bound=types.RPCModel)
13+
TReply = TypeVar("TReply", bound=types.RPCModel)
14+
TRequestPaginated = TypeVar("TRequestPaginated", bound=types.RPCRequestPaginated)
15+
TReplyPaginated = TypeVar("TReplyPaginated", bound=types.RPCReplyPaginated)
2716

2817

2918
class AnkrProvider(HTTPProvider):
3019
def __init__(
3120
self,
3221
api_key: str = "",
33-
endpoint_uri: Optional[Union[URI, str]] = None,
34-
request_kwargs: Optional[Any] = None,
35-
session: Optional[Any] = None,
22+
endpoint_uri: URI | str | None = None,
23+
request_kwargs: Any | None = None,
24+
session: Any = None,
3625
) -> None:
3726
if endpoint_uri is None:
3827
endpoint_uri = "https://rpc.ankr.com/multichain/"
@@ -46,7 +35,18 @@ def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
4635
raise AdvancedAPIException("returned no result")
4736
return response
4837

49-
def make_request_paginated(
38+
def call_method(
39+
self,
40+
rpc: str,
41+
request: TRequest,
42+
reply_type: Type[TReply],
43+
) -> TReply:
44+
request_dict = request.dict(by_alias=True, exclude_none=True)
45+
response = self.make_request(RPCEndpoint(rpc), request_dict)
46+
reply = reply_type(**response["result"])
47+
return reply
48+
49+
def call_method_paginated(
5050
self,
5151
rpc: str,
5252
request: TRequestPaginated,
@@ -60,59 +60,4 @@ def make_request_paginated(
6060

6161
if reply.next_page_token:
6262
request.page_token = reply.next_page_token
63-
yield from self.make_request_paginated(
64-
RPCEndpoint(rpc), request, reply_type
65-
)
66-
67-
68-
class AnkrAdvancedAPI:
69-
def __init__(
70-
self,
71-
api_key: Optional[str] = None,
72-
endpoint_uri: Optional[str] = None,
73-
) -> None:
74-
self.provider = AnkrProvider(api_key or "", endpoint_uri)
75-
76-
def get_logs(
77-
self,
78-
blockchain: types.BlockchainNames,
79-
from_block: Optional[types.BlockNumber] = None,
80-
to_block: Optional[types.BlockNumber] = None,
81-
address: Optional[types.AddressOrAddresses] = None,
82-
topics: Optional[types.Topics] = None,
83-
decode_logs: Optional[bool] = None,
84-
**kwargs: Any,
85-
) -> Iterable[types.Log]:
86-
for reply in self.provider.make_request_paginated(
87-
"ankr_getLogs",
88-
types.GetLogsRequest(
89-
blockchain=blockchain,
90-
from_block=from_block,
91-
to_block=to_block,
92-
address=address,
93-
topics=topics,
94-
decode_logs=decode_logs,
95-
**kwargs,
96-
),
97-
types.GetLogsReply,
98-
):
99-
yield from reply.logs
100-
101-
def get_nfts(
102-
self,
103-
blockchain: types.BlockchainNames,
104-
wallet_address: str,
105-
filter: Optional[List[Dict[str, List[str]]]] = None,
106-
**kwargs: Any,
107-
) -> Iterable[types.Nft]:
108-
for reply in self.provider.make_request_paginated(
109-
"ankr_getNFTsByOwner",
110-
types.GetNFTsByOwnerRequest(
111-
blockchain=blockchain,
112-
wallet_address=wallet_address,
113-
filter=filter,
114-
**kwargs,
115-
),
116-
types.GetNFTsByOwnerReply,
117-
):
118-
yield from reply.assets
63+
yield from self.call_method_paginated(RPCEndpoint(rpc), request, reply_type)

0 commit comments

Comments
 (0)