跳到主要内容

Python SDK

epic-python-sdk是一个为节点、钱包的Owner API v3以及epicbox中继提供的带类型异步Python客户端。它驱动已在运行的Epic软件,自身不持有任何密钥。

Pre-release

版本0.1.0尚未发布到PyPI,且公开API尚未冻结。请从检出版本安装并固定提交。

包内容

一个wheel,三个可导入的包。

导入提供扩展项
epicNodeClientWalletClientEpicboxClient以及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在所有网络上均使用端口3420get_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在进入时执行握手并解包每个响应。以下两个标签页读取相同的余额。

# 握手:明文传输的压缩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

该客户端的可运行版本(含逐步握手流程)详见钱包:连接与读取

wallet.call(method, params)通过同一会话访问客户端尚未封装的方法。对于涉及资金移动的方法,需传入allow_spend=True。方法列表见钱包Owner API

金额、花费与凭据

金额

1 EPIC等于100,000,000freemanAmount存储该整数值。from_epic()接受strDecimal

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(***)

Secretreprstr、f-string、日志事件、渲染后的回溯信息及pickle中均渲染为Secret(***)epic命令从环境变量或文件路径中读取凭据。

各接口所需凭据的说明详见身份验证

后续步骤