← Back

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

RoleRequirements
NomineeAny person. No BCH required. App generates a wallet automatically.
SponsorMust 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:

  1. Burns the BQInvite NFT (input 2 — no corresponding token output).
  2. Burns the sponsor's BQRepute NFT (input 3 — reminted with updated stats).
  3. Mints a BQUser immutable NFT for the nominee (platform 0x0a, validity 02 01).
  4. Mints a BQRepute immutable NFT for the nominee (zero stats, bquserBlock = current block).
  5. Sends 0.001 BCH to the BQUserGeneration contract (anti-gaming / prize pool contribution).
  6. Sends 0.001 BCH to the nominee's address (welcome gift).
  7. Remints the sponsor's BQRepute with timesOnboarded incremented 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>
FieldSizeDescription
nameLen1 byteDecimal length of the nominee's chosen username (4–15)
nameHex4–15 bytesUsername as ASCII hex
nomineePKH20 byteshash160 of nominee's compressed public key
codeHash4 bytessha256(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:

UTXOTokenCapabilityPurpose
Minting tokenBQInvite categorymintingAuthorises new BQInvite NFT issuance
Rate-limit ratchetRate-limit categorymutableEnforces one-per-block minimum
Funding reservePlain BCHCovers 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:

IndexSourceContent
0ContractBQInvite minting token
1ContractRate-limit ratchet NFT
2ContractPlain BCH funding UTXO

Outputs:

IndexDestinationContent
0Contract tokenAddressMinting token returned (unchanged)
1Contract tokenAddressRate-limit ratchet updated
2Contract tokenAddressBCH change returned
3Sponsor tokenAddressNew 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:

UTXOTokenCapabilityPurpose
BQUser minting tokenBQUser categorymintingMints nominee's BQUser NFT
BQRepute minting tokenBQRepute categorymintingMints 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:

IndexSourceContent
0ContractBQUser minting token
1ContractBQRepute minting token
2Sponsor walletBQInvite immutable NFT (burned)
3Sponsor walletSponsor's BQRepute immutable NFT (burned)
4+Sponsor walletPlain BCH UTXOs covering 0.002 BCH + fee

Outputs:

IndexDestinationContent
0Contract tokenAddressBQUser minting token returned
1Contract tokenAddressBQRepute minting token returned
2Nominee tokenAddressNew BQUser immutable NFT
3Nominee tokenAddressNew BQRepute immutable NFT (zero stats)
4BQUserGeneration contract0.001 BCH (anti-gaming / prize pool)
5Nominee address0.001 BCH welcome gift
6Sponsor tokenAddressSponsor's BQRepute reminted (timesOnboarded++)
7Sponsor 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:

  1. A valid BQRepute immutable NFT (platform 09 or 0a, any stats).
  2. The BQInvite NFT sent by the nominee (identified by token category and capability=none).
  3. 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

  1. Deploy BQ_Invite contract → note P2SH32 address and token address.
  2. Genesis transaction: send a BQInvite minting token to BQ_Invite.tokenAddress.
  3. Send the rate-limit mutable NFT (RATE_LIMIT category) to BQ_Invite.tokenAddress with initial commitment <genesis_block:4 LE><0x00000000>.
  4. Set NEXT_PUBLIC_BQINVITE_TOKEN_ID in .env.local.
  5. Replace placeholder token IDs in both .cash source files and recompile artifacts.
  6. Deploy BQ_Process_Nominee contract → note P2SH32 address and token address.
  7. Mint a child BQUser minting token to BQ_Process_Nominee.tokenAddress.
  8. Mint a child BQRepute minting token to BQ_Process_Nominee.tokenAddress.
  9. Fund BQ_Invite.tokenAddress with at least 0.001 BCH plain BCH to seed the reserve.
  10. Test the full flow end-to-end on chipnet before mainnet launch.