Verify a Nano (XNO) Payment
Verify that a Nano (XNO) payment has settled: confirm a send/receive block, read its amount and pay-to account from the block (not the request), and check confirmation — read-only against the public N
Files2
View on GitHubSKILL.mdskills/verify-nano-payment/SKILL.md
GitHubVerify a Nano (XNO) Payment
Nano (XNO) is a feeless, instant, green digital currency settled peer-to-peer on a
single ledger with no issuer. When an agent is paid for work in Nano, the sender
tells it a block hash or an amount — but a claim is not a settlement. This skill
shows an agent how to confirm, from the ledger itself, that a payment actually
landed: the block exists, it is confirmed, it pays the agent's own
receive address, and its amount matches what was owed. It is strictly
read-only and needs no wallet, no seed and no API key — the public Nano RPC
(rpc.nano.to) answers every query used here.
Why this is a real need
The whole point of agent payments is that an agent should not ship work or release credentials on a promise. If you can read the ledger, you never have to trust the payer's word. A pay-per-call or bounty agent should, before delivering, answer three questions:
- Does a block with the claimed hash exist and is it confirmed?
- Was it sent to MY address? For a
sendblock that iscontents.link_as_account(the block's own account is the sender); for areceive/openblock it isblock_account. - Is the raw
amountat least what was owed?
Each is one read-only RPC call. Nothing here signs, spends or touches funds.
Installation
Direct repo/manual install
Clone the Agent Skill Exchange repository and copy this skill directory into the skill folder used by your agent runtime:
git clone https://github.com/agentskillexchange/skills.git
cp -R skills/skills/verify-nano-payment ~/.agent-skills/verify-nano-payment
No install needed for the core technique
The skill's core technique needs no install at all: it is one read-only JSON
POST to the public Nano RPC over plain curl (shown below). The optional
verify_payment.py helper uses only the Python standard library, so it needs
no pip install. An HTTP client in any language (Node fetch, Python
urllib, requests, or a plain shell curl) can replicate it without
adding a dependency.
Reading the ledger (curl, no key)
The Nano RPC is a simple JSON POST to a public node. Set a short timeout and,
for safety, only ever READ — never pass a private_key or wallet in an action.
1. Confirm a block exists and is confirmed — block_info
curl -sS -m 15 -X POST -H "Content-Type: application/json" \
-d '{"action":"block_info","json_block":"true","hash":"8161FEE8C0A1676D7965A4771DBFA9937D88ECB4FFBE5DF36E431672D107D604"}' \
https://rpc.nano.to/
The response names the block's account ("block_account"), the raw amount,
the balance, and — the key field — "confirmed":"true". Only a confirmed
block is final; Nano finality is sub-second but a node reports confirmation
explicitly, so read it before acting.
2. Read the pay-to account from the block — not the request body
Which field names the recipient depends on the block's subtype:
send:block_account/contents.accountis the sender; the recipient iscontents.link_as_account.receive/open:block_account/contents.accountis the recipient.
Compare that field to the address you expect to be paid into, so either the
payer's send hash or your own receive hash can be checked. Never trust a payTo field sent alongside a request; the ledger block is
the source of truth.
curl -sS -m 15 -X POST -H "Content-Type: application/json" \
-d '{"action":"block_info","json_block":"true","hash":"<BLOCK_HASH>"}' \
https://rpc.nano.to/ \
| jq '{subtype, block_account, to: .contents.link_as_account, amount, confirmed}'
3. Check the account state — account_info
To confirm the receiving account is live and see its current balance (a balance increase is the practical proof a receive landed):
curl -sS -m 15 -X POST -H "Content-Type: application/json" \
-d '{"action":"account_info","account":"nano_1yo6c1t64ahfjdw1dxizmbbnpdmbrckwhw9phbg5pdkeubrizga4qhnjmnx7"}' \
https://rpc.nano.to/
A small helper script
Save verify_payment.py (dependency-free, stdlib only) next to this skill and
call it before you ship work after being told "you have been paid in Nano":
#!/usr/bin/env python3
"""Verify a Nano (XNO) payment read-only. Exit 0 only when the block is
confirmed AND it pays the expected receive address: for a send block that is
contents.link_as_account, for a receive/open block it is block_account."""
import json, sys, urllib.request
RPC = "https://rpc.nano.to/"
def rpc(action, **params):
req = urllib.request.Request(
RPC, data=json.dumps({"action": action, **params}).encode(),
headers={"Content-Type": "application/json",
"User-Agent": "nano-payment-verify/1.0"}, method="POST")
with urllib.request.urlopen(req, timeout=15) as r:
return json.load(r)
def main():
block = sys.argv[1]
expect_addr = sys.argv[2] # your Nano receive address
expect_min_raw = int(sys.argv[3]) # minimum raw units owed (1 XNO = 10**30)
info = rpc("block_info", json_block="true", hash=block)
subtype = info.get("subtype")
contents = info.get("contents") or {}
if subtype == "send":
# A send block belongs to the sender; the recipient is link_as_account.
addr = contents.get("link_as_account")
elif subtype in ("receive", "open"):
addr = info.get("block_account") or contents.get("account")
else:
print(f"NOT_A_PAYMENT {subtype}"); sys.exit(1)
amount = int(info.get("amount", "0"))
if info.get("confirmed") != "true":
print("NOT_CONFIRMED"); sys.exit(1)
if addr != expect_addr:
print(f"WRONG_ADDRESS {addr}"); sys.exit(1)
if amount < expect_min_raw:
print(f"AMOUNT_SHORT {amount}"); sys.exit(1)
print(f"OK {amount} raw to {addr}")
if __name__ == "__main__":
main()
Usage:
python3 verify_payment.py 8161FEE8C0A1676D7965A4771DBFA9937D88ECB4FFBE5DF36E431672D107D604 \
nano_1yo6c1t64ahfjdw1dxizmbbnpdmbrckwhw9phbg5pdkeubrizga4qhnjmnx7 0
Only a 0/OK exit means the agent may deliver: the block is on the ledger,
confirmed, pays the expected account, for at least the expected raw amount.
The hash may be the payer's send block or your own receive/open block; any
other subtype (change, epoch) is rejected with NOT_A_PAYMENT.
Safety
- This skill only READS the public ledger. Never add
private_key,wallet,seed, ordestinationto any request — that would be trying to spend, which a verify skill must never do without explicit wallet tooling. - Treat the ledger as the source of truth; a payment claim from chat is not settled until the block says so.
Source
- Nano public RPC — rpc.nano.to
- Nano developer resources: https://hub.nano.org