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,updateandcloseto opt in, edit and opt out. Opting in is setting a mailbox price: the advertised participation price is the wallet’s mailboxdefault_postage, so a beacon can only be published for a wallet that already has a mailbox. - A campaign wallet runs
search,quoteandsendto reach the pool.sendis 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_TECHNOLOGY → interest.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’srandfeature. See--ipfs-endpoint/--ipfs-tokenbelow.--ipfs-endpoint <URL>— thesithbit-ipfsdendpoint a--profile-fileis pinned to (defaulthttp://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).
sithbit campaign search
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_ipfsopt-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-postagestill 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--senderbefore 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-priceat the prepay step.--limit <N>— cap the quote at this many recipients (default: the full match set — unlikesearch, 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 — unlikesearch, 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 (seequote— 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.