aeko-sdk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aeko_sdk-0.1.0/PKG-INFO +49 -0
- aeko_sdk-0.1.0/README.md +29 -0
- aeko_sdk-0.1.0/pyproject.toml +35 -0
- aeko_sdk-0.1.0/setup.cfg +4 -0
- aeko_sdk-0.1.0/src/aeko_sdk/__init__.py +42 -0
- aeko_sdk-0.1.0/src/aeko_sdk/accounts.py +255 -0
- aeko_sdk-0.1.0/src/aeko_sdk/base58.py +24 -0
- aeko_sdk-0.1.0/src/aeko_sdk/borsh.py +53 -0
- aeko_sdk-0.1.0/src/aeko_sdk/builders.py +258 -0
- aeko_sdk-0.1.0/src/aeko_sdk/client.py +113 -0
- aeko_sdk-0.1.0/src/aeko_sdk/errors.py +7 -0
- aeko_sdk-0.1.0/src/aeko_sdk/types.py +17 -0
- aeko_sdk-0.1.0/src/aeko_sdk.egg-info/PKG-INFO +49 -0
- aeko_sdk-0.1.0/src/aeko_sdk.egg-info/SOURCES.txt +14 -0
- aeko_sdk-0.1.0/src/aeko_sdk.egg-info/dependency_links.txt +1 -0
- aeko_sdk-0.1.0/src/aeko_sdk.egg-info/top_level.txt +1 -0
aeko_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aeko-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AEKO Chain Python SDK
|
|
5
|
+
Author-email: AEKO Chain Maintainers <maintainers@aeko.chain>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://chain.aeko.social/
|
|
8
|
+
Project-URL: Repository, https://github.com/MilliHub-dev/aeko-chain
|
|
9
|
+
Project-URL: Documentation, https://github.com/MilliHub-dev/aeko-chain/tree/main/sdk/python
|
|
10
|
+
Keywords: aeko,blockchain,python,sdk,rpc
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# AEKO Python SDK
|
|
22
|
+
|
|
23
|
+
`aeko-sdk` is the Python SDK for AEKO Chain scripting, automation, analytics, and operational tooling.
|
|
24
|
+
|
|
25
|
+
Current scope:
|
|
26
|
+
|
|
27
|
+
- JSON-RPC connection wrapper
|
|
28
|
+
- account and balance queries
|
|
29
|
+
- transaction submit and signature status queries
|
|
30
|
+
- AEKO-721 account decoders and instruction builders
|
|
31
|
+
- wallet-permissions account decoder and instruction builders
|
|
32
|
+
- lightweight helpers for analytics and monitoring scripts
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
|
|
36
|
+
- [`examples/basic_usage.py`](/Users/ok/Documents/projects/aeko-chain/sdk/python/examples/basic_usage.py)
|
|
37
|
+
- [`examples/account_watch.py`](/Users/ok/Documents/projects/aeko-chain/sdk/python/examples/account_watch.py)
|
|
38
|
+
|
|
39
|
+
## Local Verification
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python3 -m compileall sdk/python/src sdk/python/examples
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Local Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -e sdk/python
|
|
49
|
+
```
|
aeko_sdk-0.1.0/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# AEKO Python SDK
|
|
2
|
+
|
|
3
|
+
`aeko-sdk` is the Python SDK for AEKO Chain scripting, automation, analytics, and operational tooling.
|
|
4
|
+
|
|
5
|
+
Current scope:
|
|
6
|
+
|
|
7
|
+
- JSON-RPC connection wrapper
|
|
8
|
+
- account and balance queries
|
|
9
|
+
- transaction submit and signature status queries
|
|
10
|
+
- AEKO-721 account decoders and instruction builders
|
|
11
|
+
- wallet-permissions account decoder and instruction builders
|
|
12
|
+
- lightweight helpers for analytics and monitoring scripts
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
|
|
16
|
+
- [`examples/basic_usage.py`](/Users/ok/Documents/projects/aeko-chain/sdk/python/examples/basic_usage.py)
|
|
17
|
+
- [`examples/account_watch.py`](/Users/ok/Documents/projects/aeko-chain/sdk/python/examples/account_watch.py)
|
|
18
|
+
|
|
19
|
+
## Local Verification
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python3 -m compileall sdk/python/src sdk/python/examples
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Local Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install -e sdk/python
|
|
29
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aeko-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AEKO Chain Python SDK"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "AEKO Chain Maintainers", email = "maintainers@aeko.chain" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["aeko", "blockchain", "python", "sdk", "rpc"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: Apache Software License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://chain.aeko.social/"
|
|
28
|
+
Repository = "https://github.com/MilliHub-dev/aeko-chain"
|
|
29
|
+
Documentation = "https://github.com/MilliHub-dev/aeko-chain/tree/main/sdk/python"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
package-dir = {"" = "src"}
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
aeko_sdk-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from .accounts import (
|
|
2
|
+
TOKEN_721_PROGRAM_ID,
|
|
3
|
+
WALLET_PERMISSIONS_PROGRAM_ID,
|
|
4
|
+
WalletPermissionAccount,
|
|
5
|
+
get_token_721_collection,
|
|
6
|
+
get_token_721_token,
|
|
7
|
+
get_wallet_permission_account,
|
|
8
|
+
)
|
|
9
|
+
from .builders import (
|
|
10
|
+
MetadataAttributeInput,
|
|
11
|
+
Token721MetadataInput,
|
|
12
|
+
build_freeze_wallet_instruction,
|
|
13
|
+
build_initialize_collection_instruction,
|
|
14
|
+
build_initialize_wallet_permissions_instruction,
|
|
15
|
+
build_mint_nft_instruction,
|
|
16
|
+
build_transfer_nft_instruction,
|
|
17
|
+
build_update_metadata_instruction,
|
|
18
|
+
)
|
|
19
|
+
from .client import AekoClient
|
|
20
|
+
from .errors import AekoRpcError
|
|
21
|
+
from .types import RpcResponse, SignatureStatusResponse
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"AekoClient",
|
|
25
|
+
"AekoRpcError",
|
|
26
|
+
"RpcResponse",
|
|
27
|
+
"SignatureStatusResponse",
|
|
28
|
+
"TOKEN_721_PROGRAM_ID",
|
|
29
|
+
"WALLET_PERMISSIONS_PROGRAM_ID",
|
|
30
|
+
"WalletPermissionAccount",
|
|
31
|
+
"MetadataAttributeInput",
|
|
32
|
+
"Token721MetadataInput",
|
|
33
|
+
"get_token_721_collection",
|
|
34
|
+
"get_token_721_token",
|
|
35
|
+
"get_wallet_permission_account",
|
|
36
|
+
"build_initialize_collection_instruction",
|
|
37
|
+
"build_mint_nft_instruction",
|
|
38
|
+
"build_transfer_nft_instruction",
|
|
39
|
+
"build_update_metadata_instruction",
|
|
40
|
+
"build_initialize_wallet_permissions_instruction",
|
|
41
|
+
"build_freeze_wallet_instruction",
|
|
42
|
+
]
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from base64 import b64decode
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .base58 import b58encode
|
|
8
|
+
from .borsh import BorshReader
|
|
9
|
+
from .client import AekoClient
|
|
10
|
+
|
|
11
|
+
TOKEN_721_PROGRAM_ID = b58encode(bytes([10] * 32))
|
|
12
|
+
WALLET_PERMISSIONS_PROGRAM_ID = b58encode(bytes([10] * 32))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(slots=True)
|
|
16
|
+
class MetadataAttribute:
|
|
17
|
+
trait_type: str
|
|
18
|
+
value: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class Token721Metadata:
|
|
23
|
+
name: str
|
|
24
|
+
description: str | None
|
|
25
|
+
uri: str
|
|
26
|
+
image_uri: str | None
|
|
27
|
+
attributes: list[MetadataAttribute]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True)
|
|
31
|
+
class Token721Collection:
|
|
32
|
+
authority: str
|
|
33
|
+
name: str
|
|
34
|
+
symbol: str
|
|
35
|
+
base_uri: str | None
|
|
36
|
+
total_minted: int
|
|
37
|
+
is_initialized: bool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(slots=True)
|
|
41
|
+
class Token721Token:
|
|
42
|
+
collection: str
|
|
43
|
+
token_id: int
|
|
44
|
+
owner: str
|
|
45
|
+
creator: str
|
|
46
|
+
royalty_bps: int
|
|
47
|
+
metadata: Token721Metadata
|
|
48
|
+
frozen: bool
|
|
49
|
+
is_initialized: bool
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(slots=True)
|
|
53
|
+
class TokenSpendCap:
|
|
54
|
+
mint: str
|
|
55
|
+
max_single_tx: int | None
|
|
56
|
+
max_daily: int | None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(slots=True)
|
|
60
|
+
class SpendLimitPolicy:
|
|
61
|
+
max_single_tx_aeko: int | None
|
|
62
|
+
max_daily_aeko: int | None
|
|
63
|
+
token_caps: list[TokenSpendCap]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(slots=True)
|
|
67
|
+
class DelegatePermission:
|
|
68
|
+
delegate: str
|
|
69
|
+
role: str
|
|
70
|
+
label: str | None
|
|
71
|
+
status: str
|
|
72
|
+
valid_from_epoch: int
|
|
73
|
+
valid_until_epoch: int | None
|
|
74
|
+
spend_limit: SpendLimitPolicy
|
|
75
|
+
program_allowlist: list[str]
|
|
76
|
+
token_allowlist: list[str]
|
|
77
|
+
app_scope_hashes: list[str]
|
|
78
|
+
requires_reauth: bool
|
|
79
|
+
last_used_epoch: int | None
|
|
80
|
+
last_used_slot: int | None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(slots=True)
|
|
84
|
+
class WalletPermissionAccount:
|
|
85
|
+
wallet: str
|
|
86
|
+
did: str
|
|
87
|
+
version: int
|
|
88
|
+
policy_nonce: int
|
|
89
|
+
is_frozen: bool
|
|
90
|
+
freeze_reason_code: int | None
|
|
91
|
+
reauth_required_until_epoch: int | None
|
|
92
|
+
owner: str
|
|
93
|
+
delegates: list[DelegatePermission]
|
|
94
|
+
default_program_policy: str
|
|
95
|
+
created_at_epoch: int
|
|
96
|
+
updated_at_epoch: int
|
|
97
|
+
is_initialized: bool
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _decode_account_bytes(account_info: dict[str, Any]) -> bytes:
|
|
101
|
+
raw_data = account_info["data"]
|
|
102
|
+
encoded = raw_data[0] if isinstance(raw_data, list) else raw_data
|
|
103
|
+
return b64decode(encoded)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _decode_metadata(reader: BorshReader) -> Token721Metadata:
|
|
107
|
+
return Token721Metadata(
|
|
108
|
+
name=reader.read_string(),
|
|
109
|
+
description=reader.read_option_string(),
|
|
110
|
+
uri=reader.read_string(),
|
|
111
|
+
image_uri=reader.read_option_string(),
|
|
112
|
+
attributes=reader.read_vec(
|
|
113
|
+
lambda: MetadataAttribute(
|
|
114
|
+
trait_type=reader.read_string(),
|
|
115
|
+
value=reader.read_string(),
|
|
116
|
+
)
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def decode_token_721_collection(account_info: dict[str, Any]) -> Token721Collection:
|
|
122
|
+
reader = BorshReader(_decode_account_bytes(account_info))
|
|
123
|
+
return Token721Collection(
|
|
124
|
+
authority=reader.read_pubkey(),
|
|
125
|
+
name=reader.read_string(),
|
|
126
|
+
symbol=reader.read_string(),
|
|
127
|
+
base_uri=reader.read_option_string(),
|
|
128
|
+
total_minted=reader.read_u64(),
|
|
129
|
+
is_initialized=reader.read_bool(),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def decode_token_721_token(account_info: dict[str, Any]) -> Token721Token:
|
|
134
|
+
reader = BorshReader(_decode_account_bytes(account_info))
|
|
135
|
+
return Token721Token(
|
|
136
|
+
collection=reader.read_pubkey(),
|
|
137
|
+
token_id=reader.read_u64(),
|
|
138
|
+
owner=reader.read_pubkey(),
|
|
139
|
+
creator=reader.read_pubkey(),
|
|
140
|
+
royalty_bps=reader.read_u16(),
|
|
141
|
+
metadata=_decode_metadata(reader),
|
|
142
|
+
frozen=reader.read_bool(),
|
|
143
|
+
is_initialized=reader.read_bool(),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _read_permission_role(value: int) -> str:
|
|
148
|
+
return ["owner", "spender", "viewer"][value]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _read_permission_status(value: int) -> str:
|
|
152
|
+
return ["active", "revoked", "expired", "frozen"][value]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _read_program_policy_mode(value: int) -> str:
|
|
156
|
+
return ["deny_by_default", "allow_by_default"][value]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _decode_spend_limit_policy(reader: BorshReader) -> SpendLimitPolicy:
|
|
160
|
+
return SpendLimitPolicy(
|
|
161
|
+
max_single_tx_aeko=reader.read_option_u64(),
|
|
162
|
+
max_daily_aeko=reader.read_option_u64(),
|
|
163
|
+
token_caps=reader.read_vec(
|
|
164
|
+
lambda: TokenSpendCap(
|
|
165
|
+
mint=reader.read_pubkey(),
|
|
166
|
+
max_single_tx=reader.read_option_u64(),
|
|
167
|
+
max_daily=reader.read_option_u64(),
|
|
168
|
+
)
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def decode_wallet_permission_account(account_info: dict[str, Any]) -> WalletPermissionAccount:
|
|
174
|
+
reader = BorshReader(_decode_account_bytes(account_info))
|
|
175
|
+
wallet = reader.read_pubkey()
|
|
176
|
+
did = reader.read_string()
|
|
177
|
+
version = reader.read_u8()
|
|
178
|
+
policy_nonce = reader.read_u64()
|
|
179
|
+
is_frozen = reader.read_bool()
|
|
180
|
+
freeze_reason_code = reader.read_option_u16()
|
|
181
|
+
reauth_required_until_epoch = reader.read_option_u64()
|
|
182
|
+
owner = reader.read_pubkey()
|
|
183
|
+
delegates = reader.read_vec(
|
|
184
|
+
lambda: DelegatePermission(
|
|
185
|
+
delegate=reader.read_pubkey(),
|
|
186
|
+
role=_read_permission_role(reader.read_u8()),
|
|
187
|
+
label=reader.read_option_string(),
|
|
188
|
+
status=_read_permission_status(reader.read_u8()),
|
|
189
|
+
valid_from_epoch=reader.read_u64(),
|
|
190
|
+
valid_until_epoch=reader.read_option_u64(),
|
|
191
|
+
spend_limit=_decode_spend_limit_policy(reader),
|
|
192
|
+
program_allowlist=reader.read_vec(reader.read_pubkey),
|
|
193
|
+
token_allowlist=reader.read_vec(reader.read_pubkey),
|
|
194
|
+
app_scope_hashes=reader.read_vec(lambda: reader._take(32).hex()),
|
|
195
|
+
requires_reauth=reader.read_bool(),
|
|
196
|
+
last_used_epoch=reader.read_option_u64(),
|
|
197
|
+
last_used_slot=reader.read_option_u64(),
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
reader.read_vec( # usage windows, currently skipped in the public Python view
|
|
201
|
+
lambda: (
|
|
202
|
+
reader.read_pubkey(),
|
|
203
|
+
reader.read_u64(),
|
|
204
|
+
reader.read_u64(),
|
|
205
|
+
reader.read_vec(lambda: (reader.read_pubkey(), reader.read_u64())),
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
default_program_policy = _read_program_policy_mode(reader.read_u8())
|
|
209
|
+
created_at_epoch = reader.read_u64()
|
|
210
|
+
updated_at_epoch = reader.read_u64()
|
|
211
|
+
is_initialized = reader.read_bool()
|
|
212
|
+
return WalletPermissionAccount(
|
|
213
|
+
wallet=wallet,
|
|
214
|
+
did=did,
|
|
215
|
+
version=version,
|
|
216
|
+
policy_nonce=policy_nonce,
|
|
217
|
+
is_frozen=is_frozen,
|
|
218
|
+
freeze_reason_code=freeze_reason_code,
|
|
219
|
+
reauth_required_until_epoch=reauth_required_until_epoch,
|
|
220
|
+
owner=owner,
|
|
221
|
+
delegates=delegates,
|
|
222
|
+
default_program_policy=default_program_policy,
|
|
223
|
+
created_at_epoch=created_at_epoch,
|
|
224
|
+
updated_at_epoch=updated_at_epoch,
|
|
225
|
+
is_initialized=is_initialized,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def get_token_721_collection(client: AekoClient, account: str) -> Token721Collection:
|
|
230
|
+
info = client.get_account_info(account)
|
|
231
|
+
if info is None:
|
|
232
|
+
raise ValueError(f"Collection account {account} was not found.")
|
|
233
|
+
if info["owner"] != TOKEN_721_PROGRAM_ID:
|
|
234
|
+
raise ValueError(f"Collection account {account} is not owned by the AEKO-721 program.")
|
|
235
|
+
return decode_token_721_collection(info)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def get_token_721_token(client: AekoClient, account: str) -> Token721Token:
|
|
239
|
+
info = client.get_account_info(account)
|
|
240
|
+
if info is None:
|
|
241
|
+
raise ValueError(f"Token account {account} was not found.")
|
|
242
|
+
if info["owner"] != TOKEN_721_PROGRAM_ID:
|
|
243
|
+
raise ValueError(f"Token account {account} is not owned by the AEKO-721 program.")
|
|
244
|
+
return decode_token_721_token(info)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def get_wallet_permission_account(client: AekoClient, account: str) -> WalletPermissionAccount:
|
|
248
|
+
info = client.get_account_info(account)
|
|
249
|
+
if info is None:
|
|
250
|
+
raise ValueError(f"Wallet permission account {account} was not found.")
|
|
251
|
+
if info["owner"] != WALLET_PERMISSIONS_PROGRAM_ID:
|
|
252
|
+
raise ValueError(
|
|
253
|
+
f"Wallet permission account {account} is not owned by the wallet-permissions program."
|
|
254
|
+
)
|
|
255
|
+
return decode_wallet_permission_account(info)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
2
|
+
ALPHABET_INDEX = {char: index for index, char in enumerate(ALPHABET)}
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def b58encode(data: bytes) -> str:
|
|
6
|
+
number = int.from_bytes(data, "big")
|
|
7
|
+
encoded = ""
|
|
8
|
+
|
|
9
|
+
while number > 0:
|
|
10
|
+
number, remainder = divmod(number, 58)
|
|
11
|
+
encoded = ALPHABET[remainder] + encoded
|
|
12
|
+
|
|
13
|
+
leading_zeroes = len(data) - len(data.lstrip(b"\x00"))
|
|
14
|
+
return ("1" * leading_zeroes) + (encoded or "")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def b58decode(value: str) -> bytes:
|
|
18
|
+
number = 0
|
|
19
|
+
for char in value:
|
|
20
|
+
number = number * 58 + ALPHABET_INDEX[char]
|
|
21
|
+
|
|
22
|
+
decoded = b"" if number == 0 else number.to_bytes((number.bit_length() + 7) // 8, "big")
|
|
23
|
+
leading_zeroes = len(value) - len(value.lstrip("1"))
|
|
24
|
+
return (b"\x00" * leading_zeroes) + decoded
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .base58 import b58encode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class BorshReader:
|
|
10
|
+
data: bytes
|
|
11
|
+
offset: int = 0
|
|
12
|
+
|
|
13
|
+
def _take(self, length: int) -> bytes:
|
|
14
|
+
next_offset = self.offset + length
|
|
15
|
+
if next_offset > len(self.data):
|
|
16
|
+
raise ValueError("Borsh payload ended unexpectedly.")
|
|
17
|
+
chunk = self.data[self.offset:next_offset]
|
|
18
|
+
self.offset = next_offset
|
|
19
|
+
return chunk
|
|
20
|
+
|
|
21
|
+
def read_u8(self) -> int:
|
|
22
|
+
return self._take(1)[0]
|
|
23
|
+
|
|
24
|
+
def read_u16(self) -> int:
|
|
25
|
+
return int.from_bytes(self._take(2), "little")
|
|
26
|
+
|
|
27
|
+
def read_u32(self) -> int:
|
|
28
|
+
return int.from_bytes(self._take(4), "little")
|
|
29
|
+
|
|
30
|
+
def read_u64(self) -> int:
|
|
31
|
+
return int.from_bytes(self._take(8), "little")
|
|
32
|
+
|
|
33
|
+
def read_bool(self) -> bool:
|
|
34
|
+
return self.read_u8() == 1
|
|
35
|
+
|
|
36
|
+
def read_pubkey(self) -> str:
|
|
37
|
+
return b58encode(self._take(32))
|
|
38
|
+
|
|
39
|
+
def read_string(self) -> str:
|
|
40
|
+
return self._take(self.read_u32()).decode("utf-8")
|
|
41
|
+
|
|
42
|
+
def read_option_string(self) -> str | None:
|
|
43
|
+
return self.read_string() if self.read_u8() == 1 else None
|
|
44
|
+
|
|
45
|
+
def read_option_u64(self) -> int | None:
|
|
46
|
+
return self.read_u64() if self.read_u8() == 1 else None
|
|
47
|
+
|
|
48
|
+
def read_option_u16(self) -> int | None:
|
|
49
|
+
return self.read_u16() if self.read_u8() == 1 else None
|
|
50
|
+
|
|
51
|
+
def read_vec(self, item_reader):
|
|
52
|
+
length = self.read_u32()
|
|
53
|
+
return [item_reader() for _ in range(length)]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from base64 import b64encode
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from .accounts import TOKEN_721_PROGRAM_ID, WALLET_PERMISSIONS_PROGRAM_ID
|
|
7
|
+
from .base58 import b58decode
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _encode_u8(value: int) -> bytes:
|
|
11
|
+
return value.to_bytes(1, "little")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _encode_u16(value: int) -> bytes:
|
|
15
|
+
return value.to_bytes(2, "little")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _encode_u32(value: int) -> bytes:
|
|
19
|
+
return value.to_bytes(4, "little")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _encode_u64(value: int) -> bytes:
|
|
23
|
+
return value.to_bytes(8, "little")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _encode_bool(value: bool) -> bytes:
|
|
27
|
+
return _encode_u8(1 if value else 0)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _encode_string(value: str) -> bytes:
|
|
31
|
+
encoded = value.encode("utf-8")
|
|
32
|
+
return _encode_u32(len(encoded)) + encoded
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _encode_option_string(value: str | None) -> bytes:
|
|
36
|
+
return b"\x00" if value is None else b"\x01" + _encode_string(value)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _encode_option_u64(value: int | None) -> bytes:
|
|
40
|
+
return b"\x00" if value is None else b"\x01" + _encode_u64(value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _encode_pubkey(value: str) -> bytes:
|
|
44
|
+
return b58decode(value)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _encode_vec(items: list, encoder) -> bytes:
|
|
48
|
+
return _encode_u32(len(items)) + b"".join(encoder(item) for item in items)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _encode_metadata(metadata: "Token721MetadataInput") -> bytes:
|
|
52
|
+
return (
|
|
53
|
+
_encode_string(metadata.name)
|
|
54
|
+
+ _encode_option_string(metadata.description)
|
|
55
|
+
+ _encode_string(metadata.uri)
|
|
56
|
+
+ _encode_option_string(metadata.image_uri)
|
|
57
|
+
+ _encode_vec(metadata.attributes, lambda item: _encode_string(item.trait_type) + _encode_string(item.value))
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(slots=True)
|
|
62
|
+
class InstructionAccount:
|
|
63
|
+
pubkey: str
|
|
64
|
+
is_signer: bool
|
|
65
|
+
is_writable: bool
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(slots=True)
|
|
69
|
+
class MetadataAttributeInput:
|
|
70
|
+
trait_type: str
|
|
71
|
+
value: str
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(slots=True)
|
|
75
|
+
class Token721MetadataInput:
|
|
76
|
+
name: str
|
|
77
|
+
uri: str
|
|
78
|
+
description: str | None = None
|
|
79
|
+
image_uri: str | None = None
|
|
80
|
+
attributes: list[MetadataAttributeInput] | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _instruction(program_id: str, accounts: list[InstructionAccount], data: bytes) -> dict:
|
|
84
|
+
return {
|
|
85
|
+
"programId": program_id,
|
|
86
|
+
"accounts": [
|
|
87
|
+
{
|
|
88
|
+
"pubkey": account.pubkey,
|
|
89
|
+
"isSigner": account.is_signer,
|
|
90
|
+
"isWritable": account.is_writable,
|
|
91
|
+
}
|
|
92
|
+
for account in accounts
|
|
93
|
+
],
|
|
94
|
+
"dataBase64": b64encode(data).decode("utf-8"),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def build_initialize_collection_instruction(
|
|
99
|
+
*,
|
|
100
|
+
collection: str,
|
|
101
|
+
authority: str,
|
|
102
|
+
name: str,
|
|
103
|
+
symbol: str,
|
|
104
|
+
base_uri: str | None = None,
|
|
105
|
+
program_id: str = TOKEN_721_PROGRAM_ID,
|
|
106
|
+
) -> dict:
|
|
107
|
+
data = _encode_u8(0) + _encode_string(name) + _encode_string(symbol) + _encode_option_string(base_uri)
|
|
108
|
+
return _instruction(
|
|
109
|
+
program_id,
|
|
110
|
+
[
|
|
111
|
+
InstructionAccount(collection, False, True),
|
|
112
|
+
InstructionAccount(authority, True, True),
|
|
113
|
+
],
|
|
114
|
+
data,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def build_mint_nft_instruction(
|
|
119
|
+
*,
|
|
120
|
+
collection: str,
|
|
121
|
+
token: str,
|
|
122
|
+
authority: str,
|
|
123
|
+
token_id: int,
|
|
124
|
+
owner: str,
|
|
125
|
+
creator: str,
|
|
126
|
+
royalty_bps: int,
|
|
127
|
+
metadata: Token721MetadataInput,
|
|
128
|
+
program_id: str = TOKEN_721_PROGRAM_ID,
|
|
129
|
+
) -> dict:
|
|
130
|
+
attributes = metadata.attributes or []
|
|
131
|
+
metadata_bytes = _encode_metadata(
|
|
132
|
+
Token721MetadataInput(
|
|
133
|
+
name=metadata.name,
|
|
134
|
+
description=metadata.description,
|
|
135
|
+
uri=metadata.uri,
|
|
136
|
+
image_uri=metadata.image_uri,
|
|
137
|
+
attributes=attributes,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
data = (
|
|
141
|
+
_encode_u8(1)
|
|
142
|
+
+ _encode_u64(token_id)
|
|
143
|
+
+ _encode_pubkey(owner)
|
|
144
|
+
+ _encode_pubkey(creator)
|
|
145
|
+
+ _encode_u16(royalty_bps)
|
|
146
|
+
+ metadata_bytes
|
|
147
|
+
)
|
|
148
|
+
return _instruction(
|
|
149
|
+
program_id,
|
|
150
|
+
[
|
|
151
|
+
InstructionAccount(collection, False, True),
|
|
152
|
+
InstructionAccount(token, False, True),
|
|
153
|
+
InstructionAccount(authority, True, False),
|
|
154
|
+
],
|
|
155
|
+
data,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def build_transfer_nft_instruction(
|
|
160
|
+
*,
|
|
161
|
+
token: str,
|
|
162
|
+
owner: str,
|
|
163
|
+
new_owner: str,
|
|
164
|
+
program_id: str = TOKEN_721_PROGRAM_ID,
|
|
165
|
+
) -> dict:
|
|
166
|
+
return _instruction(
|
|
167
|
+
program_id,
|
|
168
|
+
[
|
|
169
|
+
InstructionAccount(token, False, True),
|
|
170
|
+
InstructionAccount(owner, True, False),
|
|
171
|
+
],
|
|
172
|
+
_encode_u8(4) + _encode_pubkey(new_owner),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def build_update_metadata_instruction(
|
|
177
|
+
*,
|
|
178
|
+
token: str,
|
|
179
|
+
authority: str,
|
|
180
|
+
metadata: Token721MetadataInput,
|
|
181
|
+
program_id: str = TOKEN_721_PROGRAM_ID,
|
|
182
|
+
) -> dict:
|
|
183
|
+
return _instruction(
|
|
184
|
+
program_id,
|
|
185
|
+
[
|
|
186
|
+
InstructionAccount(token, False, True),
|
|
187
|
+
InstructionAccount(authority, True, False),
|
|
188
|
+
],
|
|
189
|
+
_encode_u8(5)
|
|
190
|
+
+ _encode_metadata(
|
|
191
|
+
Token721MetadataInput(
|
|
192
|
+
name=metadata.name,
|
|
193
|
+
description=metadata.description,
|
|
194
|
+
uri=metadata.uri,
|
|
195
|
+
image_uri=metadata.image_uri,
|
|
196
|
+
attributes=metadata.attributes or [],
|
|
197
|
+
)
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def build_initialize_wallet_permissions_instruction(
|
|
203
|
+
*,
|
|
204
|
+
permission_state: str,
|
|
205
|
+
audit_log: str,
|
|
206
|
+
owner: str,
|
|
207
|
+
wallet: str,
|
|
208
|
+
did: str,
|
|
209
|
+
current_epoch: int,
|
|
210
|
+
default_program_policy: int,
|
|
211
|
+
program_id: str = WALLET_PERMISSIONS_PROGRAM_ID,
|
|
212
|
+
) -> dict:
|
|
213
|
+
data = (
|
|
214
|
+
_encode_u8(0)
|
|
215
|
+
+ _encode_pubkey(wallet)
|
|
216
|
+
+ _encode_string(did)
|
|
217
|
+
+ _encode_u64(current_epoch)
|
|
218
|
+
+ _encode_u8(default_program_policy)
|
|
219
|
+
)
|
|
220
|
+
return _instruction(
|
|
221
|
+
program_id,
|
|
222
|
+
[
|
|
223
|
+
InstructionAccount(permission_state, False, True),
|
|
224
|
+
InstructionAccount(audit_log, False, True),
|
|
225
|
+
InstructionAccount(owner, True, False),
|
|
226
|
+
],
|
|
227
|
+
data,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def build_freeze_wallet_instruction(
|
|
232
|
+
*,
|
|
233
|
+
permission_state: str,
|
|
234
|
+
audit_log: str,
|
|
235
|
+
owner: str,
|
|
236
|
+
reason_code: int | None,
|
|
237
|
+
reauth_required_until_epoch: int | None,
|
|
238
|
+
current_epoch: int,
|
|
239
|
+
current_slot: int,
|
|
240
|
+
program_id: str = WALLET_PERMISSIONS_PROGRAM_ID,
|
|
241
|
+
) -> dict:
|
|
242
|
+
reason_bytes = b"\x00" if reason_code is None else b"\x01" + _encode_u16(reason_code)
|
|
243
|
+
data = (
|
|
244
|
+
_encode_u8(4)
|
|
245
|
+
+ reason_bytes
|
|
246
|
+
+ _encode_option_u64(reauth_required_until_epoch)
|
|
247
|
+
+ _encode_u64(current_epoch)
|
|
248
|
+
+ _encode_u64(current_slot)
|
|
249
|
+
)
|
|
250
|
+
return _instruction(
|
|
251
|
+
program_id,
|
|
252
|
+
[
|
|
253
|
+
InstructionAccount(permission_state, False, True),
|
|
254
|
+
InstructionAccount(audit_log, False, True),
|
|
255
|
+
InstructionAccount(owner, True, False),
|
|
256
|
+
],
|
|
257
|
+
data,
|
|
258
|
+
)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from base64 import b64encode
|
|
5
|
+
from itertools import count
|
|
6
|
+
from typing import Any
|
|
7
|
+
from urllib import request
|
|
8
|
+
|
|
9
|
+
from .errors import AekoRpcError
|
|
10
|
+
from .types import RpcResponse, SignatureStatusResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AekoClient:
|
|
14
|
+
def __init__(self, rpc_url: str, *, timeout: float = 30.0) -> None:
|
|
15
|
+
self.rpc_url = rpc_url
|
|
16
|
+
self.timeout = timeout
|
|
17
|
+
self._id_counter = count(1)
|
|
18
|
+
|
|
19
|
+
def rpc(self, method: str, params: list[Any] | None = None) -> Any:
|
|
20
|
+
body = {
|
|
21
|
+
"jsonrpc": "2.0",
|
|
22
|
+
"id": next(self._id_counter),
|
|
23
|
+
"method": method,
|
|
24
|
+
"params": params or [],
|
|
25
|
+
}
|
|
26
|
+
raw_body = json.dumps(body).encode("utf-8")
|
|
27
|
+
http_request = request.Request(
|
|
28
|
+
self.rpc_url,
|
|
29
|
+
data=raw_body,
|
|
30
|
+
headers={"Content-Type": "application/json"},
|
|
31
|
+
method="POST",
|
|
32
|
+
)
|
|
33
|
+
with request.urlopen(http_request, timeout=self.timeout) as response:
|
|
34
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
35
|
+
|
|
36
|
+
if "error" in payload:
|
|
37
|
+
error = payload["error"]
|
|
38
|
+
raise AekoRpcError(
|
|
39
|
+
error.get("message", "AEKO RPC request failed"),
|
|
40
|
+
code=error.get("code"),
|
|
41
|
+
data=error.get("data"),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
parsed = RpcResponse(
|
|
45
|
+
jsonrpc=payload.get("jsonrpc", "2.0"),
|
|
46
|
+
id=payload.get("id", 0),
|
|
47
|
+
result=payload.get("result"),
|
|
48
|
+
)
|
|
49
|
+
return parsed.result
|
|
50
|
+
|
|
51
|
+
def get_latest_blockhash(self) -> str:
|
|
52
|
+
result = self.rpc("getLatestBlockhash")
|
|
53
|
+
return result["value"]["blockhash"]
|
|
54
|
+
|
|
55
|
+
def get_balance(self, pubkey: str) -> int:
|
|
56
|
+
result = self.rpc("getBalance", [pubkey])
|
|
57
|
+
return int(result["value"])
|
|
58
|
+
|
|
59
|
+
def get_account_info(
|
|
60
|
+
self,
|
|
61
|
+
pubkey: str,
|
|
62
|
+
*,
|
|
63
|
+
encoding: str = "base64",
|
|
64
|
+
) -> dict[str, Any] | None:
|
|
65
|
+
result = self.rpc("getAccountInfo", [pubkey, {"encoding": encoding}])
|
|
66
|
+
return result["value"]
|
|
67
|
+
|
|
68
|
+
def get_program_accounts(
|
|
69
|
+
self,
|
|
70
|
+
program_id: str,
|
|
71
|
+
*,
|
|
72
|
+
encoding: str = "base64",
|
|
73
|
+
filters: list[dict[str, Any]] | None = None,
|
|
74
|
+
) -> list[dict[str, Any]]:
|
|
75
|
+
config: dict[str, Any] = {"encoding": encoding}
|
|
76
|
+
if filters:
|
|
77
|
+
config["filters"] = filters
|
|
78
|
+
return self.rpc("getProgramAccounts", [program_id, config])
|
|
79
|
+
|
|
80
|
+
def send_transaction(self, transaction_bytes: bytes, *, encoding: str = "base64") -> str:
|
|
81
|
+
encoded = (
|
|
82
|
+
transaction_bytes.decode("utf-8")
|
|
83
|
+
if encoding == "base64" and _looks_like_text(transaction_bytes)
|
|
84
|
+
else b64encode(transaction_bytes).decode("utf-8")
|
|
85
|
+
)
|
|
86
|
+
return self.rpc("sendTransaction", [encoded, {"encoding": encoding}])
|
|
87
|
+
|
|
88
|
+
def get_signature_statuses(
|
|
89
|
+
self, signatures: list[str]
|
|
90
|
+
) -> list[SignatureStatusResponse | None]:
|
|
91
|
+
result = self.rpc("getSignatureStatuses", [signatures])
|
|
92
|
+
statuses = []
|
|
93
|
+
for item in result["value"]:
|
|
94
|
+
if item is None:
|
|
95
|
+
statuses.append(None)
|
|
96
|
+
continue
|
|
97
|
+
statuses.append(
|
|
98
|
+
SignatureStatusResponse(
|
|
99
|
+
slot=item.get("slot"),
|
|
100
|
+
confirmations=item.get("confirmations"),
|
|
101
|
+
confirmation_status=item.get("confirmationStatus"),
|
|
102
|
+
err=item.get("err"),
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
return statuses
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _looks_like_text(value: bytes) -> bool:
|
|
109
|
+
try:
|
|
110
|
+
value.decode("utf-8")
|
|
111
|
+
except UnicodeDecodeError:
|
|
112
|
+
return False
|
|
113
|
+
return True
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(slots=True)
|
|
6
|
+
class RpcResponse:
|
|
7
|
+
jsonrpc: str
|
|
8
|
+
id: int
|
|
9
|
+
result: Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(slots=True)
|
|
13
|
+
class SignatureStatusResponse:
|
|
14
|
+
slot: int | None
|
|
15
|
+
confirmations: int | None
|
|
16
|
+
confirmation_status: str | None
|
|
17
|
+
err: Any
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aeko-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AEKO Chain Python SDK
|
|
5
|
+
Author-email: AEKO Chain Maintainers <maintainers@aeko.chain>
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://chain.aeko.social/
|
|
8
|
+
Project-URL: Repository, https://github.com/MilliHub-dev/aeko-chain
|
|
9
|
+
Project-URL: Documentation, https://github.com/MilliHub-dev/aeko-chain/tree/main/sdk/python
|
|
10
|
+
Keywords: aeko,blockchain,python,sdk,rpc
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# AEKO Python SDK
|
|
22
|
+
|
|
23
|
+
`aeko-sdk` is the Python SDK for AEKO Chain scripting, automation, analytics, and operational tooling.
|
|
24
|
+
|
|
25
|
+
Current scope:
|
|
26
|
+
|
|
27
|
+
- JSON-RPC connection wrapper
|
|
28
|
+
- account and balance queries
|
|
29
|
+
- transaction submit and signature status queries
|
|
30
|
+
- AEKO-721 account decoders and instruction builders
|
|
31
|
+
- wallet-permissions account decoder and instruction builders
|
|
32
|
+
- lightweight helpers for analytics and monitoring scripts
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
|
|
36
|
+
- [`examples/basic_usage.py`](/Users/ok/Documents/projects/aeko-chain/sdk/python/examples/basic_usage.py)
|
|
37
|
+
- [`examples/account_watch.py`](/Users/ok/Documents/projects/aeko-chain/sdk/python/examples/account_watch.py)
|
|
38
|
+
|
|
39
|
+
## Local Verification
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python3 -m compileall sdk/python/src sdk/python/examples
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Local Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -e sdk/python
|
|
49
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/aeko_sdk/__init__.py
|
|
4
|
+
src/aeko_sdk/accounts.py
|
|
5
|
+
src/aeko_sdk/base58.py
|
|
6
|
+
src/aeko_sdk/borsh.py
|
|
7
|
+
src/aeko_sdk/builders.py
|
|
8
|
+
src/aeko_sdk/client.py
|
|
9
|
+
src/aeko_sdk/errors.py
|
|
10
|
+
src/aeko_sdk/types.py
|
|
11
|
+
src/aeko_sdk.egg-info/PKG-INFO
|
|
12
|
+
src/aeko_sdk.egg-info/SOURCES.txt
|
|
13
|
+
src/aeko_sdk.egg-info/dependency_links.txt
|
|
14
|
+
src/aeko_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aeko_sdk
|