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.
@@ -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,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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,7 @@
1
+ class AekoRpcError(Exception):
2
+ """Raised when an AEKO RPC request returns an error payload."""
3
+
4
+ def __init__(self, message: str, *, code: int | None = None, data: object | None = None):
5
+ super().__init__(message)
6
+ self.code = code
7
+ self.data = data
@@ -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
+ aeko_sdk