Documentation: Complete working NIP-46 flow with Amber #9

Closed
opened 2026-02-20 15:24:20 +00:00 by Hermes · 0 comments

Summary

Documenting the complete working NIP-46 flow discovered during testing with Amber.

The Two URI Formats

1. nostrconnect:// — For Amber to SCAN (client requesting approval)

nostrconnect://<AGENT_PUBKEY_HEX>?relay=<RELAY>&metadata={"name":"<AGENT_NAME>"}

Example:

nostrconnect://f9fa1b536eb83e00fb8b1ead2cc998b8a855f59d07f379ff954d572d42b3aa16?relay=wss://relay.damus.io&metadata={"name":"Hermes"}
  • Contains the agent/client pubkey
  • Amber scans this to see who wants to connect
  • metadata.name sets the display name in Amber

2. bunker:// — For NostrConnect library (internal use)

bunker://<SIGNER_PUBKEY_HEX>?relay=<RELAY>

Example:

bunker://2ab3178f2db05799129a8b4d81dbdd2e7ec4b3532151732301c0145f20567df4?relay=wss://relay.damus.io
  • Contains the signer/operator pubkey
  • Used by NostrConnect() in nostr-sdk
  • Do NOT include secret parameter (causes "ack" errors)

Complete Working Flow

Agent Side (avault daemon)

import qrcode, urllib.parse, json
from nostr_sdk import NostrConnect, NostrConnectUri, Keys, PublicKey
from datetime import timedelta

# Config
agent_nsec = "nsec1..."  # Agent's identity
agent_name = "Hermes"
signer_npub = "npub1..."  # Operator's pubkey
relay = "wss://relay.damus.io"

# Generate QR for Amber to scan
agent_keys = Keys.parse(agent_nsec)
agent_hex = agent_keys.public_key().to_hex()
metadata = json.dumps({"name": agent_name})
nostrconnect_uri = f"nostrconnect://{agent_hex}?relay={relay}&metadata={urllib.parse.quote(metadata)}"

print("Scan this QR in Amber:")
qr = qrcode.QRCode(border=1)
qr.add_data(nostrconnect_uri)
qr.print_ascii(invert=True)

# Connect using bunker:// (no secret!)
signer_hex = PublicKey.parse(signer_npub).to_hex()
bunker_uri = f"bunker://{signer_hex}?relay={relay}"

uri = NostrConnectUri.parse(bunker_uri)
nc = NostrConnect(uri, agent_keys, timedelta(seconds=120), None)

# Wait for Amber approval
signer_pk = await nc.get_public_key()
print(f"Connected to {signer_pk.to_bech32()}!")

# Now can request decryption
decrypted = await nc.nip44_decrypt(agent_keys.public_key(), ciphertext)

Operator Side (Amber)

  1. Scan the nostrconnect:// QR code
  2. See connection request from "Hermes" (not hex prefix)
  3. Approve the connection
  4. Approve NIP-44 decrypt requests when prompted

Key Learnings

Issue Cause Fix
"ack" error secret param in bunker URI Remove secret param
"Invalid URI" in Amber Using bunker:// for QR Use nostrconnect:// for QR
Hex name in Amber No metadata in URI Add metadata={"name":...}

Documented by Hermes 🪽 after testing with k9ert

## Summary Documenting the complete working NIP-46 flow discovered during testing with Amber. ## The Two URI Formats ### 1. `nostrconnect://` — For Amber to SCAN (client requesting approval) ``` nostrconnect://<AGENT_PUBKEY_HEX>?relay=<RELAY>&metadata={"name":"<AGENT_NAME>"} ``` Example: ``` nostrconnect://f9fa1b536eb83e00fb8b1ead2cc998b8a855f59d07f379ff954d572d42b3aa16?relay=wss://relay.damus.io&metadata={"name":"Hermes"} ``` - Contains the **agent/client** pubkey - Amber scans this to see who wants to connect - `metadata.name` sets the display name in Amber ### 2. `bunker://` — For NostrConnect library (internal use) ``` bunker://<SIGNER_PUBKEY_HEX>?relay=<RELAY> ``` Example: ``` bunker://2ab3178f2db05799129a8b4d81dbdd2e7ec4b3532151732301c0145f20567df4?relay=wss://relay.damus.io ``` - Contains the **signer/operator** pubkey - Used by `NostrConnect()` in nostr-sdk - **Do NOT include `secret` parameter** (causes "ack" errors) ## Complete Working Flow ### Agent Side (avault daemon) ```python import qrcode, urllib.parse, json from nostr_sdk import NostrConnect, NostrConnectUri, Keys, PublicKey from datetime import timedelta # Config agent_nsec = "nsec1..." # Agent's identity agent_name = "Hermes" signer_npub = "npub1..." # Operator's pubkey relay = "wss://relay.damus.io" # Generate QR for Amber to scan agent_keys = Keys.parse(agent_nsec) agent_hex = agent_keys.public_key().to_hex() metadata = json.dumps({"name": agent_name}) nostrconnect_uri = f"nostrconnect://{agent_hex}?relay={relay}&metadata={urllib.parse.quote(metadata)}" print("Scan this QR in Amber:") qr = qrcode.QRCode(border=1) qr.add_data(nostrconnect_uri) qr.print_ascii(invert=True) # Connect using bunker:// (no secret!) signer_hex = PublicKey.parse(signer_npub).to_hex() bunker_uri = f"bunker://{signer_hex}?relay={relay}" uri = NostrConnectUri.parse(bunker_uri) nc = NostrConnect(uri, agent_keys, timedelta(seconds=120), None) # Wait for Amber approval signer_pk = await nc.get_public_key() print(f"Connected to {signer_pk.to_bech32()}!") # Now can request decryption decrypted = await nc.nip44_decrypt(agent_keys.public_key(), ciphertext) ``` ### Operator Side (Amber) 1. Scan the `nostrconnect://` QR code 2. See connection request from "Hermes" (not hex prefix) 3. Approve the connection 4. Approve NIP-44 decrypt requests when prompted ## Key Learnings | Issue | Cause | Fix | |-------|-------|-----| | "ack" error | `secret` param in bunker URI | Remove `secret` param | | "Invalid URI" in Amber | Using `bunker://` for QR | Use `nostrconnect://` for QR | | Hex name in Amber | No metadata in URI | Add `metadata={"name":...}` | --- *Documented by Hermes 🪽 after testing with k9ert*
nazim closed this issue 2026-02-20 15:35:26 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nazim/avault#9
No description provided.