Skip to main content

Crate sidestr_agent

Crate sidestr_agent 

Source
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.

itemwhat
AgentKeythe secret: a key file as 64 hex characters or an nsec (NIP-19)
parse_pubkey, npub, Identitynpub / hex / did:nostr: ↔ x-only key ↔ script ↔ chain address
destination, refuse_secreta pay-to: an npub, a did:nostr:, a chain address or a script hex; never secret-shaped text
preparea spend or peg-out burn, signed by the key, and its kind-23500 event, signed by the same key
ChainViewa mirror’s block file replayed, with the SPEC 12 assets view: coins, plain coins, asset balances
prepare_transfer, prepare_issuemove or issue an asset (with memo records such as tip:nostr:<event id>), and the event
pegin_planwhat 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 check

Structs§

AgentKey
An agent’s secret key: its Nostr identity, and so its wallet. Debug prints the public key only.
ChainView
A chain replayed from a block file and read under the assets rule: 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.
PeginPlan
What a parent wallet pays to peg in.
Prepared
A signed transaction and the signed event that carries it.

Enums§

Error
What can go wrong.
Payment
What a payment is.
PegTarget
Whose output the peg is.

Functions§

announced_peg_address
The parent address of the peg script a chain’s signer announces with every tip (SPEC 0.0.4, the peg tag; sidestr_nostr::tip::newest_peg_script): what a level-1 peg-in pays when no --peg-address is given, as the JS wallet’s pegInScript does. 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 npub or a did:nostr: becomes its key’s 5120 script; 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 an npub or a did:nostr:, a script by its full hex.
fetch_announced_peg_script
The peg script chain’s signer announces with its newest tip, asked of relays once (announce.mjs fetchLatestTip then pegScript, as the JS wallet’s pegInScript does): only the chain document’s signer counts, and None when the newest announcement carries none. Feature cli.
identity
Every name of key on a chain whose addressPrefix is prefix; None for a prefix bech32 cannot carry.
level1_peg_key
The signing key a level-1 document names: its signer, else the key of a 5120‖key challenge. None for a level-2 document. This is what PegTarget::Key takes when the peg holders choose to import a descriptor; pegin_plan never uses it on its own, because at level 1 the peg is whatever the producer’s parent wallet owns.
npub
The NIP-19 npub of 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, /testnet4 off mainnet; None for 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: amount sats to the peg output and OP_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). to is a sidechain destination for Payment::Send (see destination) and a parent address or script for Payment::Burn; created_at is 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 when None), 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: amount units of asset to to (a destination), with memos as 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.