Python SDK
epic-python-sdk是一个为节点、钱包的Owner API v3以及epicbox中继提供的带类型异步Python客户端。它驱动已在运行的Epic软件,自身不持有任何密钥。
版本0.1.0尚未发布到PyPI,且公开API尚未冻结。请从检出版本安装并固定提交。
包内容
一个wheel,三个可导入的包。
| 导入 | 提供 | 扩展项 |
|---|---|---|
epic | NodeClient、WalletClient、EpicboxClient以及epic命令。 | 无 |
epictk | 获取发布版本二进制文件、写入配置、布局链,并监管节点、钱包和矿工进程。epic-sdk命令。 | [cli] |
epicdash | 基于epictk的Web界面:进程、余额、转账以及本地链的区块浏览器。 | [cli,service] |
epic无需编译器即可安装。所有密码学原语均来自cryptography。
安装
git clone https://github.com/blacktyger/epic-python-sdk
cd epic-python-sdk
uv sync # 仅客户端
uv sync --extra cli --extra service # 加上epic-sdk和仪表板
Python 3.12或更高版本。
首次调用
每个客户端都是异步上下文管理器。进入时建立连接,退出时关闭连接,构造函数不执行任何I/O。
节点
import asyncio
from epic import NodeClient, Secret
async def main():
secret = Secret.from_env("EPIC_NODE_API_SECRET")
async with NodeClient("https://node.example:3413",
api_secret=secret) as node:
print(await node.get_height())
print(await node.is_synced(tolerance=10))
print(await node.get_pool_size())
asyncio.run(main())
NodeClient默认绑定到端口3413的回环地址。
is_synced()将本地链顶端与对等节点集合进行比较,tolerance是本地链顶端允许落后的区块数。
钱包
from epic import Secret, WalletClient
url = "http://127.0.0.1:3420/v3/owner"
password = Secret.from_env("EPIC_WALLET_PASSWORD")
async with WalletClient(url, password=password) as wallet:
balance = await wallet.get_balance()
print(balance.spendable, balance.awaiting_confirmation)
for tx in await wallet.get_transactions():
print(tx.slate_id, tx.state, tx.amount)
print(await wallet.get_address())
Owner API在所有网络上均使用端口3420。get_address()返回epicbox地址,包含端口号。
发送
from epic import Amount, Secret, WalletClient
async with WalletClient(url, password=password,
can_spend=True) as wallet:
result = await wallet.send(
Amount.from_epic("1.5"),
"esYG...@epicbox.epiccash.com:443",
wait=True,
)
print(result.state)
Can spend funds send()在发送前选择并锁定输出(output)。这些输出在转账确认或cancel(slate_id)释放之前保持不可用状态。使用wait=True时,调用在交换的第二轮完成或超时到期后返回,默认超时为120秒。
通过epicbox进行的转账在两个钱包之间分两轮完成。流程详见交互式交易。
加密会话
Owner API v3通过ECDH协商会话密钥,使用AES-256-GCM对每个请求体进行加密,并将每个结果包裹在三层信封中返回。WalletClient在进入时执行握手并解包每个响应。以下两个标签页读取相同的余额。
- 直接调用Owner API v3
- 通过客户端调用
# 握手:明文传输的压缩secp256k1公钥。会话
# 密钥为共享点的x坐标。
post("init_secure_api", {"ecdh_pubkey": my_pubkey.hex()})
# open_wallet已加密,因此密码放入密封的
# 请求体,而非JSON-RPC参数中。
token = call_encrypted("open_wallet",
{"name": "default", "password": password})
# 后续每次调用:密封内层请求,以
# encrypted_request_v3形式附带新的12字节nonce发送。
inner = {"jsonrpc": "2.0", "id": 1,
"method": "retrieve_summary_info",
"params": {"token": token, "refresh_from_node": True,
"minimum_confirmations": 10}}
ciphertext, nonce = aes_gcm_seal(session_key,
json.dumps(inner).encode())
reply = post("encrypted_request_v3",
{"nonce": nonce.hex(),
"body_enc": b64encode(ciphertext).decode()})
body = json.loads(aes_gcm_open( # 信封1:密封的请求体
session_key,
b64decode(reply["body_enc"]),
bytes.fromhex(reply["nonce"])))
result = body["result"] # 信封2:JSON-RPC
ok = result["Ok"] # 信封3:Ok / Err
# 金额为以freemans为单位的带引号字符串。
spendable = int(ok[1]["amount_currently_spendable"]) / 100_000_000
该客户端的可运行版本(含逐步握手流程)详见钱包:连接与读取。
from epic import Secret, WalletClient
password = Secret.from_env("EPIC_WALLET_PASSWORD")
async with WalletClient(url, password=password) as wallet:
balance = await wallet.get_balance()
print(balance.spendable) # 金额,以整数freemans为单位
wallet.call(method, params)通过同一会话访问客户端尚未封装的方法。对于涉及资金移动的方法,需传入allow_spend=True。方法列表见钱包Owner API。
金额、花费与凭据
金额
1 EPIC等于100,000,000个freeman。Amount存储该整数值。from_epic()接受str和Decimal。
Amount.from_epic("1.5") # 150_000_000 freemans
Amount.from_epic(Decimal("1.5"))
Amount.from_freemans(150_000_000)
Amount.from_epic(1.5) # AmountError
花费
WalletClient仅在以can_spend=True构造时才执行花费操作。若不传入,send()、finalize()、reshape_outputs()和call(..., allow_spend=True)将抛出NotSpendable。
凭据
钱包密码和API机密均为Secret值。Secret.from_env(name)从环境变量中读取该值。
password = Secret.from_env("EPIC_WALLET_PASSWORD")
print(password) # Secret(***)
log.info("opening", pw=password) # Secret(***)
Secret在repr、str、f-string、日志事件、渲染后的回溯信息及pickle中均渲染为Secret(***)。epic命令从环境变量或文件路径中读取凭据。
各接口所需凭据的说明详见身份验证。