Social Onboarding Protocol
Social Onboarding Specification
1. Overview
The BQuest social onboarding system allows an existing BQuest identity holder (the sponsor) to register a new user (the nominee) without requiring the nominee to hold any BCH or have a pre-funded wallet.
The protocol spans two contracts:
- BQ_Invite — rate-limited minting contract that issues a BQInvite NFT to the sponsor's wallet as proof of a pending nomination.
- BQ_Process_Nominee — finalisation contract called by the sponsor to complete the nominee's registration in a single atomic transaction.
The flow uses an out-of-band code exchange to bind the nominee's on-chain identity to a real-world relationship, providing lightweight Sybil resistance without requiring identity documents or a centralised issuer.
2. Roles
| Role | Requirements |
|---|---|
| Nominee | Any person. No BCH required. App generates a wallet automatically. |
| Sponsor | Must hold a valid BQUser + BQRepute pair and ~0.002 BCH free (not locked in token UTXOs). |
3. Protocol Flow
Step 1 — Sponsor shares referral link
The sponsor's referral link takes the form:
https://id.bquest.cash?sponsor=<username>+9
where <username> is the sponsor's BQUser name and 9 is the BQuest platform ID.
Step 2 — Nominee arrives and chooses a username
The nominee opens the referral link. The app resolves the sponsor's username to their
PKH via Chaingraph. The nominee chooses a username (4–15 characters, [a-z0-9_]) and
clicks Request Invite.
Step 3 — BQInvite NFT minted (BQ_Invite contract)
The app calls mintBQInvite(). This transaction:
- Is signed by the nominee's key (proving they control the address the NFT will land at).
- Is funded entirely by the BQ_Invite contract's plain-BCH reserve — the nominee needs zero on-chain balance.
- Mints a BQInvite immutable NFT and delivers it to the sponsor's token address.
- Simultaneously generates a random 6-digit numeric code client-side and embeds a 4-byte hash of it in the NFT commitment.
The nominee's wallet address (PKH) is embedded in the NFT; the code hash is embedded alongside it. Neither can be forged without the nominee's private key.
Step 4 — Out-of-band code exchange
The app displays the 6-digit code to the nominee. The nominee DMs it to the sponsor via any channel (Telegram, WhatsApp, Signal, etc.). This step is deliberately off-chain and cannot be automated — it ties the on-chain nomination to a real human relationship.
Step 5 — Sponsor verifies the code
The sponsor opens the Earn → Invite & Onboard panel. Pending BQInvite tokens appear here. The sponsor enters the nominee's code, which is verified client-side:
sha256(code_bytes ++ nomineePKH)[0:4] == codeHash
If it matches, the invite is confirmed. The sponsor reviews the nominee's username and address, then clicks Confirm & Onboard.
Step 6 — Nominee registered (BQ_Process_Nominee contract)
The sponsor calls processNominee(). This single atomic transaction:
- Burns the BQInvite NFT (input 2 — no corresponding token output).
- Burns the sponsor's BQRepute NFT (input 3 — reminted with updated stats).
- Mints a BQUser immutable NFT for the nominee (platform
0x0a, validity02 01). - Mints a BQRepute immutable NFT for the nominee (zero stats,
bquserBlock= current block). - Sends 0.001 BCH to the BQUserGeneration contract (anti-gaming / prize pool contribution).
- Sends 0.001 BCH to the nominee's address (welcome gift).
- Remints the sponsor's BQRepute with
timesOnboardedincremented by 1.
All of this happens in one transaction. There is no intermediate state.
4. BQInvite Token
Token Category
Set via NEXT_PUBLIC_BQINVITE_TOKEN_ID (big-endian). On-chain LE form:
BQINVITE_TOKEN_REV = 39ae73b98cd04c5235f776f3d9e9b16fd22313a3e2ba911e5d799b1b83459373
NFT Commitment Format
<nameLen:1><nameHex:N><nomineePKH:20><codeHash:4>
| Field | Size | Description |
|---|---|---|
| nameLen | 1 byte | Decimal length of the nominee's chosen username (4–15) |
| nameHex | 4–15 bytes | Username as ASCII hex |
| nomineePKH | 20 bytes | hash160 of nominee's compressed public key |
| codeHash | 4 bytes | sha256(code_bytes ++ nomineePKH)[0:4] |
Maximum size: 1 + 15 + 20 + 4 = 40 bytes (exactly at the BCH NFT commitment limit).
Code Hash Construction
code_bytes = UTF-8 bytes of the 6-digit numeric string (e.g. "482951" → 6 bytes)
codeHash = sha256(code_bytes ++ nomineePKH)[0:4]
Verification by the sponsor:
verifyInviteCode(enteredCode, invite) →
sha256(utf8(enteredCode) ++ hexToBin(invite.nomineePKH))[0:4] == hexToBin(invite.codeHash)
5. BQ_Invite Contract
Contract UTXOs
The BQ_Invite contract holds three UTXOs at its token address:
| UTXO | Token | Capability | Purpose |
|---|---|---|---|
| Minting token | BQInvite category | minting | Authorises new BQInvite NFT issuance |
| Rate-limit ratchet | Rate-limit category | mutable | Enforces one-per-block minimum |
| Funding reserve | Plain BCH | — | Covers sponsor dust + miner fee per invite |
The funding reserve must be topped up by the admin as it is consumed. Each invite costs approximately 800 sat dust (NFT output to sponsor) plus the miner fee.
Rate-Limit Ratchet
The rate-limit mutable NFT commitment is 8 bytes:
<current_block:4 LE><previous_block:4 LE>
Invariant: current > previous in both input and output states. The declared
currentBlock parameter must strictly exceed the stored current value. CLTV ensures
the transaction mines at or after the declared block height.
This enforces a minimum of one block between successive invites. It does not cap the total number of invites, only the rate.
Transaction Structure
Inputs:
| Index | Source | Content |
|---|---|---|
| 0 | Contract | BQInvite minting token |
| 1 | Contract | Rate-limit ratchet NFT |
| 2 | Contract | Plain BCH funding UTXO |
Outputs:
| Index | Destination | Content |
|---|---|---|
| 0 | Contract tokenAddress | Minting token returned (unchanged) |
| 1 | Contract tokenAddress | Rate-limit ratchet updated |
| 2 | Contract tokenAddress | BCH change returned |
| 3 | Sponsor tokenAddress | New BQInvite immutable NFT |
6. Nominee's BQUser Token — Platform ID
Nominees registered via social onboarding receive a BQUser token with platform ID
0x0a (Invited), distinguishing them from self-registered users (platform 0x09,
BQuest).
BQUser commitment for a sponsored nominee:
<nameLen><nameHex><0a><02><01>
Initial validity is identical to self-registration: positive 02, negative 01.
The platform byte records the registration path; it does not affect validity rules,
dispute costs, or Block Bounty eligibility.
7. BQ_Process_Nominee Contract
Contract UTXOs
The BQ_Process_Nominee contract holds two UTXOs at its token address:
| UTXO | Token | Capability | Purpose |
|---|---|---|---|
| BQUser minting token | BQUser category | minting | Mints nominee's BQUser NFT |
| BQRepute minting token | BQRepute category | minting | Mints nominee's BQRepute NFT |
These are child minting tokens derived from the admin's external minting tokens for each category. The contract holds no BCH reserve; all BCH flows from the sponsor's inputs.
Transaction Structure
Inputs:
| Index | Source | Content |
|---|---|---|
| 0 | Contract | BQUser minting token |
| 1 | Contract | BQRepute minting token |
| 2 | Sponsor wallet | BQInvite immutable NFT (burned) |
| 3 | Sponsor wallet | Sponsor's BQRepute immutable NFT (burned) |
| 4+ | Sponsor wallet | Plain BCH UTXOs covering 0.002 BCH + fee |
Outputs:
| Index | Destination | Content |
|---|---|---|
| 0 | Contract tokenAddress | BQUser minting token returned |
| 1 | Contract tokenAddress | BQRepute minting token returned |
| 2 | Nominee tokenAddress | New BQUser immutable NFT |
| 3 | Nominee tokenAddress | New BQRepute immutable NFT (zero stats) |
| 4 | BQUserGeneration contract | 0.001 BCH (anti-gaming / prize pool) |
| 5 | Nominee address | 0.001 BCH welcome gift |
| 6 | Sponsor tokenAddress | Sponsor's BQRepute reminted (timesOnboarded++) |
| 7 | Sponsor address | (optional) BCH change |
Nominee's Initial BQRepute Commitment
credentialBytes = <nameLen:1><nameHex:N><0x0a:1><bquserBlock:4 LE>
stats = 0x000000000000000000000000 (12 zero bytes)
commitment = credentialBytes ++ stats
bquserBlock is the expected mining block height, passed by the caller. It is embedded
as the canonical genesis anchor for disambiguation.
Sponsor BQRepute Update
The contract parses the sponsor's existing BQRepute commitment and increments
timesOnboarded (bytes 8–9 of the stats block, little-endian unsigned 16-bit integer):
newStats = prefixStats[0:8] ++ bytes2(timesOnboarded + 1) ++ tailStats[10:12]
All other fields (gamesWon, totalSatsWon, timesSent, timesRecovered) are
carried over unchanged. The credentialBytes portion is always preserved unmodified.
8. Sponsor Requirements
To call processNominee() the sponsor must have in their wallet:
- A valid BQRepute immutable NFT (platform
09or0a, any stats). - The BQInvite NFT sent by the nominee (identified by token category and
capability=none). - At least ~0.002 BCH in free (non-token-bearing) UTXOs to cover the 0.001 BCH prize pool contribution, 0.001 BCH welcome gift, and miner fee.
BCH locked inside token-bearing UTXOs cannot be used to fund this transaction. The app displays a warning if free BCH falls below 0.00220000 BCH (220,000 sat).
9. Sponsor Dismissal — Burning a BQInvite Token
If a sponsor wishes to decline or discard a pending BQInvite NFT, they burn it:
- The BQInvite immutable NFT is spent as a P2PKH input.
- No token output is included — the CashTokens protocol destroys the token.
- The BCH dust (800 sat) is recovered to the sponsor's wallet minus the miner fee.
This is irreversible. The nominee's username remains available and the nominee would need to re-request an invite (possibly from a different sponsor) if they still wish to register.
10. Deployment Order
- Deploy BQ_Invite contract → note P2SH32 address and token address.
- Genesis transaction: send a BQInvite minting token to
BQ_Invite.tokenAddress. - Send the rate-limit mutable NFT (RATE_LIMIT category) to
BQ_Invite.tokenAddresswith initial commitment<genesis_block:4 LE><0x00000000>. - Set
NEXT_PUBLIC_BQINVITE_TOKEN_IDin.env.local. - Replace placeholder token IDs in both
.cashsource files and recompile artifacts. - Deploy BQ_Process_Nominee contract → note P2SH32 address and token address.
- Mint a child BQUser minting token to
BQ_Process_Nominee.tokenAddress. - Mint a child BQRepute minting token to
BQ_Process_Nominee.tokenAddress. - Fund
BQ_Invite.tokenAddresswith at least 0.001 BCH plain BCH to seed the reserve. - Test the full flow end-to-end on chipnet before mainnet launch.