Writing a Python Library for the Atto Node API
I recently wrote a Python wrapper for the Atto Node API (GitHub), and I wanted to share my journey—from figuring out how to convert Atto addresses, to designing around Python’s async model, to publishing a usable API wrapper. Hopefully, this helps someone else who's either interested in Atto or wants to see what writing an API wrapper looks like from a beginner's perspective.
🚀 Why I Started
At the time, there were no Python API wrappers available for Atto. The only libraries available targeted JS/WASM, used only by the wallet, and Kotlin.
I wanted to play around with the API, but to do that, I needed to convert an Atto public key (a base32-encoded string) into a hex string, which the API expects. I wrote some code for this in Python, the journey of which I'll expand on below.
Using curl and copying and pasting addresses to make requests was getting
tedious, so I switched to using Python for that too.
At some point, I realized: “I’m already halfway to an API wrapper. Why not
finish it?”. There weren’t that many endpoints, so it seemed like a manageable
project.
As a student, this felt like a fun way to procrastinate from real work™.
🧠 Reverse Engineering the Address Format
I started by figuring out how the Atto address (e.g., atto://...) becomes a
public key that the API understands.
After poking around the Atto org on GitHub, I found this bit of Kotlin:
private fun String.fromAddress(): ByteArray {
return Base32.decode(this.substring(SCHEMA.length).uppercase() + "===")
}
This seemed straightforward. So, I implemented the decoding part in Python:
import base64
# Should not include the schema (atto://)
address = '<address>'
address_bytes = base64.b32decode(address.upper() + '===')
for byte in address_bytes:
print(format(byte, '02X'), end='')
But my curl requests were failing. Comparing my output with a network request from the actual wallet, I noticed extra bytes in my output.
With Rotilho (the creator of Atto)'s help, we tracked it down:
- The first byte is the version.
- The last 5 bytes are a checksum.
These are only needed for human-readable addresses, and the API doesn't want them. So I just sliced them off:
for byte in address_bytes[1:-5]:
# ^^^^^^
print(format(byte, '02X'), end='')
Clean hex output, and the API was happy.
🧪 Curl Fatigue and First Scripts
I initially used curl for quick API checks, but copying and pasting addresses
and typing out endpoints repeatedly got old, fast. So I wrote a script in
Python to make it easier.
Eventually, I wanted something cleaner and more structured—a proper API wrapper.
