Expand description
sidestr-agent: an agent wallet for sidestr sidechains, where a
did:nostr key is the wallet.
A Nostr identity is a secp256k1 key whose x-only public key is the
did:nostr:<hex> identifier and the npub. On a sidestr chain that same
32-byte x-only key is a taproot output key: its coins pay OP_1 <pubkey> (5120‖pubkey, the key used untweaked, as siding’s wallet does),
and its address is that script in bech32m under the chain’s
addressPrefix. So an agent needs no second key. It reads its balance
from a producer, pays another agent by npub, and signs the spend with
the key its identity already holds. The kind-23500 event that carries the
transaction to the producer’s relays is signed with the same key, so the
event names the agent that paid.
This crate is the library behind the sidestr-agent binary. The binary is
a generalisation of the tool that ran the first live loop on
sidestr:dreamlab, beside Bitcoin testnet4. That loop was a peg-in, three
trades between two agents as kind-23500 events each signed by its agent’s
own Nostr key, and a peg-out. Everything here is for testnet4 and
experimental sidechains: coins on sidestr:dreamlab have no value.
| item | what |
|---|---|
AgentKey | the secret: a key file as 64 hex characters or an nsec (NIP-19) |
parse_pubkey, npub, Identity | npub / hex / did:nostr: ↔ x-only key ↔ script ↔ chain address |
destination, refuse_secret | a pay-to: an npub, a did:nostr:, a chain address or a script hex; never secret-shaped text |
prepare | a spend or peg-out burn, signed by the key, and its kind-23500 event, signed by the same key |
ChainView | a mirror’s block file replayed, with the SPEC 12 assets view: coins, plain coins, asset balances |
prepare_transfer, prepare_issue | move or issue an asset (with memo records such as tip:nostr:<event id>), and the event |
pegin_plan | what a parent wallet pays to peg in: the peg address (and its refund descriptor), the marker |
It is a port in the AGPL sense: it builds on sidestr-core,
sidestr-wallet and sidestr-nostr, which port siding, the
reference implementation by Melvin Carvalho
(https://github.com/sidestr/spec). It carries the same licence,
AGPL-3.0-only.
§An agent pays another agent by npub
use bitcoin::secp256k1::SecretKey;
use sidestr_agent::{destination, identity, prepare, AgentKey, Payment};
use sidestr_core::block::{challenge_for, pubkey_of};
use sidestr_core::document::{ChainDocument, Peg};
use sidestr_core::state::{NextBlock, State};
use sidestr_wallet::coins::from_state;
// two agents: the key file text is what an agent keeps (hex, or an nsec)
let alice = AgentKey::parse(&"11".repeat(32)).unwrap();
let bob = AgentKey::parse(&"22".repeat(32)).unwrap();
// a throwaway chain whose genesis pegs alice's script; the producer's key seals blocks
let producer = SecretKey::from_slice(&[7u8; 32]).unwrap();
let json = format!(r#"{{"id":"sidestr:example","name":"example","parent":"tbtc4","challenge":"{}",
"powLimit":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","addressPrefix":"ex",
"genesisTime":1790000000,"signer":"{}","pegs":[]}}"#, challenge_for(&pubkey_of(&producer)).to_hex_string(), pubkey_of(&producer));
let mut doc = ChainDocument::from_json(&json).unwrap();
doc.pegs.push(Peg { txid: "a".repeat(64), vout: 0, amount: 100_000, script: alice.script().to_hex_string(), extra: Default::default() });
let mut chain = State::with_key(doc.clone(), &producer).unwrap();
for i in 1..=100 { chain.produce(&producer, &NextBlock { time: 1790000000 + i, claims: vec![] }, None).unwrap(); }
// alice's did:nostr key is her wallet: the same x-only key, the same script
let me = identity(&alice.pubkey(), "ex").unwrap();
assert_eq!(me.did, format!("did:nostr:{}", alice.pubkey()));
assert_eq!(me.script, format!("5120{}", alice.pubkey()));
// pay bob by his npub; the event carrying the transaction is signed by alice's key too
let to = destination(&identity(&bob.pubkey(), "ex").unwrap().npub).unwrap();
let coins = from_state(&chain, &alice.script());
let p = prepare(&alice, &doc, &coins, chain.height(), Payment::Send, &to, 30_000, None, 1_790_000_200).unwrap();
assert!(p.event.verify().is_ok() && p.event.pubkey == alice.pubkey().to_string());
assert_eq!(p.event.kind, 23500);
chain.submit(p.spend.tx.clone()).unwrap(); // the producer's mempool checkStructs§
- Agent
Key - An agent’s secret key: its Nostr identity, and so its wallet.
Debugprints the public key only. - Chain
View - A chain replayed from a block file and read under the
assetsrule: the UTXO set and what each unspent output carries (SPEC 12). What an agent needs before it moves an issued asset or pays plain sats beside coins that carry one. - Identity
- One key, every name it goes by.
- Pegin
Plan - What a parent wallet pays to peg in.
- Prepared
- A signed transaction and the signed event that carries it.
Enums§
Functions§
- announced_
peg_ address - The parent address of the peg script a chain’s signer announces with
every tip (SPEC 0.0.4, the
pegtag;sidestr_nostr::tip::newest_peg_script): what a level-1 peg-in pays when no--peg-addressis given, as the JS wallet’spegInScriptdoes. The scanner counts only a taproot output paying it, so anything else is refused. - destination
- A destination in the form the wallet takes (a script hex or an address
under any prefix): an
npubor adid:nostr:becomes its key’s5120script; anything else passes through for the wallet to judge — except secret-shaped text (refuse_secret). A bare 64-hex string is refused too: it may be a key file’s secret, and the wallet would otherwise read it as a 32-byte script and publish it in an output. A key is named as annpubor adid:nostr:, a script by its full hex. - fetch_
announced_ peg_ script - The peg script
chain’s signer announces with its newest tip, asked ofrelaysonce (announce.mjs fetchLatestTipthenpegScript, as the JS wallet’spegInScriptdoes): only the chain document’s signer counts, andNonewhen the newest announcement carries none. Featurecli. - identity
- Every name of
keyon a chain whoseaddressPrefixisprefix;Nonefor a prefix bech32 cannot carry. - level1_
peg_ key - The signing key a level-1 document names: its
signer, else the key of a5120‖keychallenge.Nonefor a level-2 document. This is whatPegTarget::Keytakes when the peg holders choose to import a descriptor;pegin_plannever uses it on its own, because at level 1 the peg is whatever the producer’s parent wallet owns. - npub
- The NIP-19
npubof a key. - parent_
explorer_ api - The public explorer API a wallet with no node broadcasts a parent
transaction to, per parent (the JS wallet’s
parentApi): mempool.guide beside a BLAKE2b parent, mempool.space beside stock Bitcoin,/testnet4off mainnet;Nonefor a parent the table does not know. - parse_
pubkey - An x-only key from
npub1…,did:nostr:<hex>or 64 hex characters. - pegin_
plan - Plan a peg-in (SPEC 6) for a parent wallet to pay:
amountsats to the peg output andOP_RETURN pegin:<chain id>:<side script>, in one parent transaction, outputs in any order. Since 0.0.3 the producer takes the peg to be the output its peg wallet owns, wherever it sits. - prepare
- Build and sign a payment with the agent’s key, and the kind-23500 event
that carries it, signed with the same key (pure: no network).
tois a sidechain destination forPayment::Send(seedestination) and a parent address or script forPayment::Burn;created_atis the event’s time. - prepare_
issue - Issue an asset from the agent’s plain coins, its whole supply on one
carrier to
to(the agent itself whenNone), and the kind-23500 event. The asset’s id is the spend’s txid. - prepare_
transfer - An asset transfer signed with the agent’s key, and the kind-23500 event
that carries it:
amountunits ofassettoto(adestination), withmemosas records beside the tally. Plain coins pay the fee; no other asset is touched. - refuse_
secret - Refuse secret-shaped text where a destination or an address is expected
(see
destination); the error names what to use and never echoes it.
Type Aliases§
- Result
- This crate’s result.