Verify the $CRANK fair launch from chain state¶
The $CRANK utility token fair launch runs through an on-chain escrow in the
Crank program. On-chain state is the transparency mechanism: the launch
terms, every pledge, the escrow balance and the settlement outcome all live in
Solana accounts that anyone can read. Crank's REST endpoint and the /launch
page only mirror that state for convenience.
This page is the recipe for checking the mirror against the chain yourself.
What lives on chain¶
| Account | Seeds (program 7TjAk4i7EtDYagevPCtTddXBurXxTEADeNwujtzyTbj6) |
Holds |
|---|---|---|
LaunchConfig |
["launch", launch_id as u64 little-endian] |
Terms, status, total_pledged, pledger_count, escrow account address |
Pledge |
["pledge", launch_id u64 LE, pledger pubkey] |
One wallet's cumulative pledge, tokens released, refunded flag |
| Pledge escrow token account | ["escrow_ta", launch_id u64 LE] |
The pledged stablecoin, owned by the LaunchConfig PDA |
| Launch token vault | ["launch_vault", launch_id u64 LE] |
The launch-mint tokens allocated to pledgers |
Both account types use the standard Anchor encoding: an 8-byte discriminator
(sha256("account:<Name>")[0..8]) followed by little-endian Borsh fields.
LaunchConfig (343 bytes):
| Offset | Field | Type |
|---|---|---|
| 10 | status (0 pending, 1 active, 2 succeeded, 3 failed, 4 finalized) |
u8 |
| 11 | launch_id |
u64 |
| 19 / 51 / 83 | authority / launch_mint / pledge_mint |
pubkey |
| 115 | escrow_token_account |
pubkey |
| 147 / 155 | min_raise / max_raise (pledge-mint base units) |
u64 |
| 163 | token_allocation (launch-mint base units) |
u64 |
| 171 | total_pledged |
u64 |
| 179 | pledger_count |
u32 |
| 183 / 191 | start_ts / end_ts |
i64 |
| 199 / 207 / 211 | liquidity release schedule: release_start_ts i64, release_cliff_seconds u32, release_duration_seconds u32 |
|
| 215 | max_pledge_per_wallet (0 = uncapped) |
u64 |
Pledge (139 bytes): launch_id u64 at 10, pledger pubkey at 18,
amount u64 at 50, tokens_released u64 at 58, refunded bool at 66,
pledged_ts i64 at 67.
The settlement rules¶
Settlement is deterministic and the same for every participant:
- When the pledge window closes,
finalize_launchsets the status to succeeded iftotal_pledged >= min_raise, otherwise failed. - Succeeded: every pledger's allocation is
floor(pledge_amount * token_allocation / total_pledged)(pledger_entitlementin the escrow program, computed in 128-bit integers). That is one uniform clearing rate --total_pledged / token_allocationpledge units per launch token -- for everyone. Allocations unlock on the published liquidity release schedule (nothing before the cliff, then linear over the duration), and each pledger claims their own with a transaction they sign. Floor-division dust stays in the vault. - Failed: no launch tokens are allocated. Every pledger can reclaim their
full pledge with
refund_pledge, a transaction they sign themselves. - Finalized: a succeeded launch whose raise has been collected from the escrow; the escrow token account is then empty.
Because these rules use only LaunchConfig and Pledge fields, every figure
the /launch page shows can be reproduced from the accounts above.
Run the verifier¶
verify-ilo-settlement.mjs is a
read-only Node script (no keypair, no signing) that performs every check below
against any RPC node you choose:
npm i @solana/web3.js@1
curl -O https://docs.crank.ing/assets/verify-ilo-settlement.mjs
node verify-ilo-settlement.mjs --launch-id 1 --cluster devnet \
--api https://api.crank.ing
Options: --rpc <url> (your own node), --program-id <id>, --wallet <pubkey>
(show one wallet's allocation or refund), --json (machine-readable report),
--cluster localnet|devnet|mainnet-beta.
It reports each check as OK or FAIL and exits 0 only when all of them
hold:
| Check | Meaning |
|---|---|
sum_of_pledges_equals_total_pledged |
Every Pledge account for the launch adds up to LaunchConfig.total_pledged |
pledge_account_count_equals_pledger_count |
One Pledge account per counted wallet |
total_within_max_raise / every_pledge_within_per_wallet_cap |
The on-chain caps held |
escrow_balance_matches_pledges_held |
The escrow token account holds every un-refunded pledge (zero once finalized) |
settlement_outcome_matches_minimum_raise |
Succeeded if and only if the minimum raise was met |
allocations_within_token_allocation |
The floor-divided allocations never exceed the allocation |
rest_mirror_* (with --api) |
Crank's REST mirror agrees with chain on total, pledger count and status |
Compare with the REST mirror¶
The same state is served, read-only and without authentication, at:
GET https://api.crank.ing/api/v1/ilo/status/ # current launch on this cluster
GET https://api.crank.ing/api/v1/ilo/launches/ # every indexed launch
GET https://api.crank.ing/api/v1/ilo/launches/<launch_id>/?wallet=<pubkey>
Each response carries source.last_indexed_slot (the chain slot the mirror
reflects), explorer links for every account, a settlement block once the
launch has settled, and -- with ?wallet= -- that wallet's allocation or
refund. If the mirror and the chain ever disagree, the chain is right.
The MCP equivalent is get_ilo_details.
Worked example: devnet rehearsal¶
Two rehearsal launches were run on Solana devnet on 1 October 2026 against
the deployed program 7TjAk4i7EtDYagevPCtTddXBurXxTEADeNwujtzyTbj6, using
throwaway test mints (not $CRANK) and throwaway pledger wallets. You can re-run
the verifier against them yourself -- they are permanent devnet accounts.
| Launch id | Outcome | Terms (test-mint units, 6 dp) | Pledges |
|---|---|---|---|
902 |
succeeded | min 100, max 1,000, allocation 1,000,000 | 3 wallets: 150, 100, 50 (total 300) |
903 |
failed, both pledges refunded | min 1,000, max 5,000, allocation 1,000,000 | 2 wallets: 100, 50 (total 150) |
Test mints: pledge 8YLT5Cx5FDzsYYF8v6ZXXrBfL8whqBgHTcYLvRcsuhF2, launch
AHEcRi3xr8CamfdM41PKUU1gLukWWUuMY9R3wGDLNCzE.
$ node verify-ilo-settlement.mjs --launch-id 902 --cluster devnet
launch 902 on devnet -- status succeeded
LaunchConfig BnPizJYZUzLxLeMebZF99dwhnp9uPymJVqEuTwR9DwEH
total pledged 300000000 by 3 wallets; escrow balance 300000000
uniform clearing rate 300000000/1000000000000 pledge base units per launch base unit
[OK ] sum_of_pledges_equals_total_pledged: sum=300000000 total_pledged=300000000
[OK ] pledge_account_count_equals_pledger_count: pledge_accounts=3 pledger_count=3
[OK ] total_within_max_raise: total=300000000 max_raise=1000000000
[OK ] escrow_balance_matches_pledges_held: escrow_balance=300000000 expected=300000000 (status=succeeded)
[OK ] settlement_outcome_matches_minimum_raise: total=300000000 min_raise=100000000 status=succeeded
[OK ] allocations_within_token_allocation: allocated=999999999999 token_allocation=1000000000000
$ echo $?
0
$ node verify-ilo-settlement.mjs --launch-id 903 --cluster devnet
launch 903 on devnet -- status failed
LaunchConfig 8QhwFUkwpzTGVSE79rekch9jdaV8ftsaLLLEQtQmQaiW
total pledged 150000000 by 2 wallets; escrow balance 0
[OK ] sum_of_pledges_equals_total_pledged: sum=150000000 total_pledged=150000000
[OK ] pledge_account_count_equals_pledger_count: pledge_accounts=2 pledger_count=2
[OK ] total_within_max_raise: total=150000000 max_raise=5000000000
[OK ] escrow_balance_matches_pledges_held: escrow_balance=0 expected=0 (status=failed)
[OK ] settlement_outcome_matches_minimum_raise: total=150000000 min_raise=1000000000 status=failed
$ echo $?
0
What the example shows:
- Succeeded (902): 300 pledged against a 1,000,000 allocation gives one
uniform clearing rate. The 150 / 100 / 50 pledgers are entitled to
500,000 / 333,333.333333 / 166,666.666666 launch tokens; the floor-division
dust (one base unit) stays in the vault, which is why
allocatedis one belowtoken_allocation. The escrow still holds the full 300 because the raise has not been collected (finalizedwould show 0). - Failed (903): 150 pledged against a 1,000 minimum, so
finalize_launchset the status to failed and each pledger reclaimed their full pledge withrefund_pledge. The escrow balance is therefore 0, and the verifier expects exactly that because bothPledgeaccounts carryrefunded = true.
The REST comparison (--api) was not part of this rehearsal: the read-only
ILO REST endpoints were not yet serving on any deployed environment at the
time of the run.
Reproduce the rehearsal¶
The rehearsal is scripted in the Crank repository as
scripts/ilo-devnet-rehearsal.mjs. It is devnet-only: it prints a plan and
sends nothing unless given --execute, refuses any mainnet-looking RPC URL,
and checks the RPC's genesis hash is devnet's before signing anything. Pledger
wallets are generated in memory for the run and never written to disk.
cd scripts && npm run install:deps
node ilo-devnet-rehearsal.mjs # plan only
node ilo-devnet-rehearsal.mjs --execute # two fresh launches on devnet
Note on the devnet program binary: the program deployed on devnet at the time
of this rehearsal predates two later source changes -- the per-wallet pledge
cap and the launch-token-vault allocation check in finalize_launch. The
cap field is carved out of reserved space, so the LaunchConfig layout above
is unchanged and decodes it as 0 (uncapped); the rehearsal script detects the
older finalize_launch account list automatically. The settlement rules and
every check above are identical on both versions.
Before any launch exists¶
Until a launch escrow is created, /api/v1/ilo/status/ responds with
"phase": "not_created" and "launch": null, and the verifier reports that no
LaunchConfig exists for the requested id. Participation opens only after the
off-chain gate clears (legal sign-off, the launch prerequisites, and the
adoption milestone) -- the REST launch_gate block shows which leg is pending.
$CRANK is a utility token for use within the Crank rails. A pledge commits funds to a non-custodial escrow; it is not a purchase of any expectation of profit, and nothing here describes or implies a financial outcome.