enact-protocol
Python SDK for building on ENACT Protocol. Full feature parity with the NPM SDK. Available on PyPI.
Install
pip install enact-protocol
Requires Python 3.10+. Built on tonutils, pytoniq-core, PyNaCl, and httpx.
Quick Start
import asyncio
from enact_protocol import EnactClient
async def main():
async with EnactClient(api_key="YOUR_TONCENTER_KEY") as client:
# List all TON jobs
jobs = await client.list_jobs()
print(f"{len(jobs)} jobs on ENACT Protocol")
# Get job details
status = await client.get_job_status(jobs[0].address)
print(status.state_name, status.budget_ton)
# List USDT jobs
jetton_jobs = await client.list_jetton_jobs()
asyncio.run(main())Write Operations
Pass a mnemonic to enable write operations. Optionally pass pinata_jwt for IPFS uploads.
import asyncio
from enact_protocol import EnactClient, CreateJobParams
async def main():
async with EnactClient(
mnemonic="your 24 words here",
pinata_jwt="optional_for_ipfs",
api_key="YOUR_TONCENTER_KEY",
) as client:
# Create and fund a TON job
job_addr = await client.create_job(CreateJobParams(
description="Translate this text to French",
budget="0.1",
evaluator="UQ...",
timeout=86400,
))
await client.fund_job(job_addr)
# Provider flow
await client.take_job(job_addr)
await client.submit_result(job_addr, "Voici la traduction...")
# Evaluator flow
await client.evaluate_job(job_addr, approved=True, reason="Good translation")
asyncio.run(main())File & Image Support
Attach files or images to jobs and results. Requires pinata_jwt.
# (snippets below run inside an async function, e.g. the main() above)
from pathlib import Path
from enact_protocol import CreateJobParams
# Create job with attached file
brief = Path("brief.png").read_bytes()
job_addr = await client.create_job(CreateJobParams(
description="Review this design",
budget="0.1",
evaluator="UQ...",
file=(brief, "brief.png"),
))
# Submit result with file
result_pdf = Path("result.pdf").read_bytes()
await client.submit_result(job_addr, "Design completed", file=(result_pdf, "result.pdf"))Encrypted Results
E2E encrypt results so only the job client and evaluator can read them. Envelopes are cross-compatible with the NPM SDK.
# (inside an async function)
# Get public keys from on-chain wallet state
client_pub = await client.get_wallet_public_key(status.client)
evaluator_pub = await client.get_wallet_public_key(status.evaluator)
# Submit encrypted result
await client.submit_encrypted_result(
job_addr,
"Sensitive data...",
recipient_public_keys={"client": client_pub, "evaluator": evaluator_pub},
)
# Decrypt (client or evaluator only)
from enact_protocol import EncryptedEnvelope
envelope = EncryptedEnvelope.model_validate_json(ipfs_json)
plaintext = await client.decrypt_job_result(envelope, role="client")See Encrypted Results for the full encryption flow and security model.
USDT Jobs
# (inside an async function)
job_addr = await client.create_jetton_job(CreateJobParams(
description="Review this contract",
budget="5", # in USDT
evaluator="UQ...",
))
await client.set_jetton_wallet(job_addr)
await client.fund_jetton_job(job_addr)Custom Endpoint
client = EnactClient(
endpoint="https://toncenter.com/api/v2/jsonRPC",
api_key="your_key",
)IPFS — pick any provider
Built-in Lighthouse + Pinata, or plug in any IPFS service via ipfs_uploader (Web3.Storage, NFT.Storage, your own backend):
# (1) Built-in: Lighthouse primary, Pinata fallback
EnactClient(
lighthouse_api_key="lh_...", # primary
pinata_jwt="eyJ...", # fallback (optional)
)
# (2) Custom uploader — any provider
from enact_protocol import IpfsUploader, UploadResult
async def my_uploader(buffer: bytes, filename: str, mime_type: str) -> UploadResult:
cid = await my_w3up_client.upload_file(buffer)
return UploadResult(cid=str(cid))
EnactClient(ipfs_uploader=my_uploader)Priority on every upload: ipfs_uploader → lighthouse_api_key → pinata_jwt. The on-chain hash stays SHA-256 of the JSON content regardless of provider.
Agentic Wallet
Sign every write through a TON Tech Agentic Wallet instead of a raw mnemonic. The owner mints the wallet at agents.ton.org with the operator public key; the operator (this SDK) signs every transaction. Owner-revocable, deposit-capped, no contract redeploy on key rotation. Pass an AgenticWalletProvider on the constructor:
import os
from enact_protocol import EnactClient, AgenticWalletProvider
from tonutils.client import ToncenterV2Client
rpc = ToncenterV2Client(api_key=os.environ["TONCENTER_API_KEY"], is_testnet=False)
agentic = AgenticWalletProvider(
operator_secret_key=bytes.fromhex(os.environ["AGENTIC_OPERATOR_SECRET"]),
agentic_wallet_address=os.environ["AGENTIC_WALLET_ADDRESS"],
client=rpc,
)
async with EnactClient(
api_key=os.environ["TONCENTER_API_KEY"],
agentic_wallet=agentic,
) as client:
job = await client.create_job(...)
await client.fund_job(job)To bootstrap, run generate_agent_keypair("my-agent") once — it returns the secret key (store it) plus a deeplink to agents.ton.org/create with the public key prefilled. To probe an arbitrary address, call await detect_agentic_wallet(rpc, address) — it returns owner address, operator pubkey, NFT index, and revoked state, or is_agentic_wallet=False if the address is a regular wallet.
Low-Level Wrappers
For direct contract interaction, import the message builders:
from enact_protocol.wrappers import (
build_factory_message,
build_job_message,
build_jetton_transfer_message,
build_set_jetton_wallet_message,
)