Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The sithbit campaign CLI (participant beacons & campaigns)

The sithbit campaign command tree is the chain-direct authoring surface for the participant-pool marketplace: a wallet publishes an on-chain participant beacon advertising the tags it is willing to be reached on, and a campaign wallet discovers those participants by tag, prices a bountied send to the matched set, and sends to the prepaid, top-up and first-contact matches the quote keeps — the unmailable, IPFS opt-out and over---max-postage classes are quoted but never sent to.

Two roles use this tree:

  • A participant runs create, update and close to opt in, edit and opt out. Opting in is setting a mailbox price: the advertised participation price is the wallet’s mailbox default_postage, so a beacon can only be published for a wallet that already has a mailbox.
  • A campaign wallet runs search, quote and send to reach the pool. send is direct-signed only — the funded campaign wallet signs every delivery locally; there is no mail-server path for a campaign.
sithbit campaign create  [--tag <TAG>]… [--detail-cid <CID> | --profile-file <PATH>] [--keypair <KEY>]
sithbit campaign update  [--tag <TAG>]… [--detail-cid <CID> | --profile-file <PATH>] [--keypair <KEY>]
sithbit campaign close   [--keypair <KEY>]
sithbit campaign search  --tag <TAG>… [--limit <N>] [--offset <N>]
sithbit campaign quote   --tag <TAG>… --bounty <LAMPORTS> [--sender <ADDRESS>] \
                         [--max-postage <LAMPORTS>] [--limit <N>]
sithbit campaign send    --tag <TAG>… --subject <TEXT> --bounty <LAMPORTS> \
                         [--body-file <PATH>] [--max-postage <LAMPORTS>] [--limit <N>] \
                         [--yes] [--keypair <KEY>]

Tags

Every beacon carries a fixed-vocabulary tag bitmap. A --tag is one name from that vocabulary — an interest.*, skill.*, age.*, region.*, language.* or role.* name, e.g. interest.technology, skill.software, age.25_34, region.apac, language.es, role.founder. Regions cover both the three broad bands (region.americas, region.emea, region.apac) and finer subregions (region.latin_america, region.southeast_asia, …); languages are ISO 639-1 codes (language.en, language.ja); roles are coarse occupation stages (role.student, role.creator). Names are case-insensitive; an unknown name is rejected with the full valid list, so the quickest way to print every tag is to pass a bogus one. --tag is repeatable, and search/quote/send treat multiple tags as a logical AND — a beacon must carry every requested tag to match. The vocabulary is derived mechanically from the mail_model TAG_* constants (TAG_INTEREST_TECHNOLOGYinterest.technology); see the participant-marketplace note for the bitmap layout.

sithbit campaign create

Publishes the signing wallet’s participant beacon, opting it in on the given tags. The wallet’s mailbox must already exist — the advertised price is its default_postage.

sithbit campaign create \
  --tag interest.technology \
  --tag skill.software \
  --tag region.apac

Flags:

  • --tag <TAG> — a tag to advertise on (repeatable).
  • --detail-cid <CID> — attach an existing IPFS CID as the beacon’s off-chain detail profile. Mutually exclusive with --profile-file.
  • --profile-file <PATH> — seal a local profile file under a fresh key, pin it to IPFS, and attach the resulting CID. Needs the CLI’s rand feature. See --ipfs-endpoint / --ipfs-token below.
  • --ipfs-endpoint <URL> — the sithbit-ipfsd endpoint a --profile-file is pinned to (default http://127.0.0.1:8182).
  • --ipfs-token <TOKEN> — bearer token for that daemon, when it requires one.
  • --keypair <KEY_PATH> (short -k) — the wallet publishing the beacon (defaults to the Solana CLI keypair).
  • --skip-preflight (short -s) — skip the transaction pre-flight simulation.

sithbit campaign update

Rewrites the beacon wholesale: the given tags and detail CID replace its current contents in full — this is a replace, not a merge, so omitting --tag clears all tags and omitting a CID clears the attached profile.

sithbit campaign update --tag interest.finance --tag region.emea

Takes the same flags as create.

sithbit campaign close

Closes the signing wallet’s beacon — the opt-out. It drops out of every tag search and its rent is refunded to the wallet.

sithbit campaign close

Flags: --keypair <KEY_PATH> (short -k), --skip-preflight (short -s).

Runs a trustless getProgramAccounts scan of the mail program for beacons carrying every requested tag, and lists each match’s sendable wallet, its tag names, and whether it advertises a detail profile. The wallet is recovered from the beacon’s on-chain owner field (a beacon PDA can’t be inverted to it), so the results are directly mailable.

sithbit campaign search --tag interest.technology --tag skill.software
beacons 1-2 of 2 matching:
  mAiLiLdgjgGdWoCZkpW3cj7JLAC56qb4NErFyQFWNJg  [interest.technology, skill.software]  detail: yes
  CaMUbt4zNKeZb2AUaa4icv33CQEvBeJ8EsWKFsDvKWrT  [interest.technology, skill.software, region.apac]  detail: no

The newest beacons are listed first. Only search pages by default — quote and send act on the whole match set unless their own --limit is passed, so a search view and the quote/send it prices can disagree in size.

On a real terminal, reaching the end of a page (100 matches by default) prompts before continuing: press Enter for the next page or q to stop. Piped or redirected output — a script, or output captured by another program — is not a terminal, so it always prints exactly one page and exits; use --offset to fetch a later page non-interactively.

Flags:

  • --tag <TAG> — a tag a beacon must carry to match (repeatable, required, logical AND).
  • --limit <N> — matches per page (default 100).
  • --offset <N> — skip this many matches before the first page (default 0).

sithbit campaign quote

Prices a campaign against live chain state before any funds are committed. It selects recipients by the same trustless scan search runs, fetches every matched wallet’s real accounts in one batched read, and classifies each recipient by what the campaign will actually pay for them — no flat per-recipient assumption:

  • prepaid — the sender’s frombox toward this recipient has stamps standing: the send just burns one, so the recipient costs only the send leg (message account rent + the escrowed bounty + one signature fee).
  • top-up — the frombox exists but is out of stamps: one stamp is prepaid at its stored required_postage — the price the recipient may have lowered for this sender — plus the send leg.
  • first contact — no frombox yet: the prepay prices at the reputation-scaled first-contact rule (the recipient’s mailbox default_postage, stepped down by the sender’s cumulative stamp-spend tier) and adds a new frombox’s rent, plus the send leg.
  • unmailable — the wallet has no mailbox, so nothing can be delivered: reported, and excluded from the total.
  • ipfs opt-out — the recipient’s mailbox carries the no_ipfs opt-out. A campaign composes its message in the clear and commits the client-computed content address on chain, with no operator store behind the body the way SMTP delivery has, so the send would publish exactly what the owner opted out of: reported, and excluded from the total — whatever the frombox says, including a recipient with stamps already standing.
  • over max-postage — the postage leg exceeds --max-postage: reported, and excluded from the total.

Every prepay leg also carries the per-stamp settlement surcharge, the hybrid stamp-purchase protocol fee on the recipient’s postage, and its own signature fee — an unprepaid recipient pays two signature fees in total, one on the prepay transaction and one on the send. Two more things the quote handles for you: first-contact escrow is folded into the sender’s simulated spend in selection order, so a batch that crosses a reputation spend tier mid-campaign quotes later recipients at the discounted rate; and when the sender has no sender-reputation account yet (this would be their first first-contact prepay), its one-time rent is added once, campaign-wide — never per recipient.

sithbit campaign quote --tag interest.gaming --bounty 1000000 \
  --sender ~/.config/solana/id.json
Campaign quote for 5 recipient(s):
  mAiLiLdgjgGdWoCZkpW3cj7JLAC56qb4NErFyQFWNJg  prepaid (2 stamp(s) standing)
  CaMUbt4zNKeZb2AUaa4icv33CQEvBeJ8EsWKFsDvKWrT  top-up at 250000 lamports postage
  maiLtdkxym8CCmo9TwDuXywqd9DXaK3tB6toKFVeBFR  first contact at 1000000000 lamports postage
  7Np41oeYqPefeNQEHSv1UDhYrehxin3NStELsSKCT4K2  unmailable (no mailbox) — excluded
  7cVfgArCheMR6Cs29HXTFrpMg2XwYFhrCtdz3EgKPfHM  opted out of IPFS body storage — excluded
  prepaid              1 recipient(s)          3169560 lamports
  top-up               1 recipient(s)          3534560 lamports
  first contact        1 recipient(s)       1014158960 lamports
  unmailable           1 recipient(s)                0 lamports
  ipfs opt-out         1 recipient(s)                0 lamports
  over max-postage     0 recipient(s)                0 lamports
Per send (prepaid, top-up and first-contact recipients):
  message rent              2164560 lamports
  bounty                    1000000 lamports
  signature fee                5000 lamports
Per prepay (top-up and first-contact recipients), plus postage:
  stamp surcharge             10000 lamports
  signature fee                5000 lamports
  frombox rent               974400 lamports (first contact only)
  + the stamp-purchase protocol fee on each recipient's postage
  sender reputation account rent (first campaign): 1169280 lamports, charged once
  total                  1022032360 lamports (1.02203236 SOL)

Flags:

  • --tag <TAG> — recipient tag filter (repeatable, required, logical AND).
  • --bounty <LAMPORTS> — the per-recipient bounty being offered.
  • --sender <ADDRESS> — the sending wallet, as an address or a keypair path (only the public key is read; the quote never signs anything). With it the quote prices each recipient against that wallet’s real fromboxes and recorded reputation spend. Without it no frombox is read, so no recipient can classify as prepaid or top-up: each one whose mailbox exists and accepts IPFS bodies prices as a first contact against zero recorded spend — the simulated-spend fold described above still applies, and --max-postage still moves an over-cap one to the excluded class. The quote prints a note saying so, and the note is careful about what that zero means: it is an assumption, not an upper bound. Pricing is not monotone in recorded spend, because the fold and the tier ladder interact — a higher recorded spend earns a cheaper first contact, which folds a smaller escrow into the simulated spend, which can leave a later recipient below a tier boundary the zero-spend run had already crossed. So a sender with real recorded spend can be quoted more than the senderless quote shows, both for individual recipients and for the campaign total. Quote with --sender before committing funds.
  • --max-postage <LAMPORTS> — exclude recipients whose postage leg exceeds this many lamports. This is targeting, not slippage protection: it trims too-expensive recipients out of the campaign, while the purchase-time slippage guard is --max-price at the prepay step.
  • --limit <N> — cap the quote at this many recipients (default: the full match set — unlike search, which pages 100 at a time by default).

sithbit campaign send

Sends one bountied message to each prepaid, top-up and first-contact recipient matching the tag filter. It selects recipients by the same trustless scan, prints the quote — the same real, per-recipient quote, priced with the sending keypair’s wallet so its fromboxes and reputation are the actual ones — gates on a confirmation (unless --yes), then delivers the batch — one direct-signed SendMail per recipient, each escrowing the per-recipient reply bounty with the campaign message as the parent a recipient replies to. The loop continues past a per-recipient failure so one bad address can’t strand the rest of a paid campaign, and prints a sent/failed summary at the end.

Three quote classes are excluded from the batch: unmailable wallets (no mailbox — the send could only fail), recipients whose mailbox carries the no_ipfs opt-out (a campaign publishes a content address for a body no operator holds privately, so the exclusion stands even with a stamp prepaid), and recipients priced over --max-postage. And because SendMail burns a stamp but never buys one, the quote is followed by a warning when kept recipients have no prepaid stamp standing:

WARNING: 2 recipient(s) have no prepaid stamp — those sends will fail until `sithbit frombox stamp` prepays them.

Prepay those fromboxes with sithbit frombox stamp first, then re-run the send. Postage is locked in at that prepay step — the purchase’s --max-price slippage guard is the protection against a price moving between quote and buy. By the time SendMail runs, the stamp is already bought, so the send itself has nothing to slippage-guard.

sithbit campaign send \
  --tag interest.gaming \
  --subject 'Paid gaming survey' \
  --bounty 1000000 \
  --body-file ./invite.txt \
  --yes
Campaign quote for 5 recipient(s):
  …
  sent to mAiLiLdgjgGdWoCZkpW3cj7JLAC56qb4NErFyQFWNJg  https://explorer.solana.com/tx/…
  sent to CaMUbt4zNKeZb2AUaa4icv33CQEvBeJ8EsWKFsDvKWrT  https://explorer.solana.com/tx/…
  sent to maiLtdkxym8CCmo9TwDuXywqd9DXaK3tB6toKFVeBFR  https://explorer.solana.com/tx/…
Campaign complete: 3 sent, 0 failed.

Flags:

  • --tag <TAG> — recipient tag filter (repeatable, required, logical AND).
  • --subject <TEXT> — the message subject line.
  • --body-file <PATH> — the message body; read from stdin when omitted.
  • --bounty <LAMPORTS> — the per-recipient bounty escrowed for each recipient to claim. The claim window is the standard 7-day bounty default.
  • --limit <N> — cap the send at this many recipients (default: the full match set — unlike search, which pages 100 at a time by default).
  • --max-postage <LAMPORTS> — exclude recipients whose postage leg exceeds this many lamports from the quote and the batch (see quote — targeting, not slippage protection).
  • --yes (short -y) — skip the quote-then-confirm prompt and send immediately.
  • --keypair <KEY_PATH> (short -k) — the sending (and funding) wallet.
  • --skip-preflight (short -s) — skip the transaction pre-flight simulation.

Recipients collect a bounty by replying before the window closes; anything unclaimed can be refunded to the campaign wallet after it expires.