Skip to content

Commit d58e848

Browse files
authored
docs: SNS launching guide (#88)
## Summary - Tokenomics design: developer neurons, treasury, swap allocation, NNS proposal narrative requirements - sns_init.yaml annotated configuration covering all five sections with key constraints - 11-stage launch process: 3 manual stages + 8 automatic stages - CLI commands for adding NNS root as co-controller and submitting the proposal - Motoko and Rust canister preparation snippets for SNS governance-gated admin functions - 7 common pitfalls from field experience ## Sync recommendation `informed by dfinity/icskills — skills/sns-launch/SKILL.md`
1 parent 9c8f113 commit d58e848

1 file changed

Lines changed: 374 additions & 13 deletions

File tree

Lines changed: 374 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,383 @@
11
---
22
title: "Launching an SNS"
3-
description: "Decentralize your dapp with an SNS: token economics, governance setup, and NNS proposal"
3+
description: "Decentralize your dapp with an SNS: token economics, governance setup, and NNS proposal submission"
44
sidebar:
55
order: 1
66
---
77

8-
TODO: Write content for this page.
8+
A Service Nervous System (SNS) is a DAO framework that transfers control of your dapp from your team to a community of token holders. After launch, canister upgrades, treasury spending, and governance parameters all require token holder votes — your team no longer has unilateral control.
99

10-
<!-- Content Brief -->
11-
Decentralize your dapp by launching an SNS (Service Nervous System). Cover token economics configuration (supply, distribution, vesting), governance parameters (voting power, proposal thresholds), integration checklist (what your dapp needs before launch), and the NNS proposal submission process. Include pre-deployment considerations and the SNS checklist.
10+
This guide walks through the complete launch process: from designing your tokenomics and configuring `sns_init.yaml`, to adding NNS root as a co-controller and submitting the NNS proposal that triggers the swap.
1211

13-
<!-- Source Material -->
14-
- Portal: building-apps/governing-apps/launching/ (4 files), tokenomics/ (4 files: index, predeployment-considerations, preparation, sns-checklist)
15-
- icskills: sns-launch
16-
- Examples: sns-adaptor (Rust)
17-
- Learn Hub: [SNS - Service Nervous System](https://learn.internetcomputer.org/hc/en-us/articles/34084394684564), [Framework and Architecture](https://learn.internetcomputer.org/hc/en-us/articles/34140764336788), [Launch](https://learn.internetcomputer.org/hc/en-us/articles/34141180048404), [SNS Tokenomics](https://learn.internetcomputer.org/hc/en-us/articles/34088279488660)
12+
## Before you start
1813

19-
<!-- Cross-Links -->
20-
- concepts/governance -- governance model background
21-
- guides/governance/testing -- test before launch
22-
- guides/governance/managing -- post-launch operations
14+
SNS launch is irreversible. Once the NNS proposal is adopted and the swap succeeds, your dapp canisters are fully controlled by SNS root. Review these prerequisites before proceeding:
15+
16+
- Your dapp canisters are deployed and working on mainnet
17+
- You hold an NNS neuron with sufficient stake to submit proposals (8 ICP minimum stake, plus dissolve delay)
18+
- You have done a security review and open-sourced the dapp code
19+
- Your tokenomics design is finalized and community-vetted
20+
21+
See [concepts/governance.md](../../concepts/governance.md) for background on how SNS DAOs work.
22+
23+
## Pre-launch preparation
24+
25+
### Define your tokenomics
26+
27+
Before writing a single line of configuration, define these parameters clearly:
28+
29+
**Token utility** — explain what the token is used for within your ecosystem. Common utilities include governance voting, access to premium features, and in-app payments.
30+
31+
**Initial token allocation** — decide how tokens are split across these buckets:
32+
- **Developer neurons** — tokens for founders and seed investors, with vesting schedules. Best practice is 12–48 months of vesting.
33+
- **Treasury** — tokens controlled by the DAO governance canister for future spending proposals.
34+
- **Swap** — tokens sold during the decentralization swap in exchange for ICP.
35+
36+
**Swap parameters** — set realistic minimums. If you require 500 participants but only 200 show up, the entire swap fails and all ICP is refunded. Most successful SNS launches use 100–200 minimum participants.
37+
38+
Use the [SNS Tokenomics Analyzer](https://dashboard.internetcomputer.org/sns/tokenomics) to evaluate your configuration and model voting power distribution before committing to parameters.
39+
40+
### Prepare the NNS proposal narrative
41+
42+
The NNS community votes on your proposal. You are pitching your project to thousands of token holders who will decide whether to approve the swap. Your proposal should address:
43+
44+
- Team backgrounds and track record
45+
- Product-market fit evidence (user metrics, traction)
46+
- Token utility and how it accrues value over time
47+
- Tokenomics: distribution, vesting, voting power at genesis
48+
- Funding target and how raised ICP will be spent
49+
- Product roadmap
50+
- Security review status and open-source build instructions
51+
- Dependencies that cannot be managed by the DAO (off-chain services, third-party providers)
52+
53+
Start a discussion thread in the [SNS Launch Proposals](https://forum.dfinity.org/c/community/sns-launch-proposals) forum category at least two weeks before submitting your NNS proposal. Share the draft `sns_init.yaml` file, the whitepaper, and the build/deploy instructions. Community trust built during this period directly affects proposal adoption.
54+
55+
### Technical readiness checklist
56+
57+
Before submitting the NNS proposal:
58+
59+
- [ ] Dapp canisters are deployed and stable on mainnet
60+
- [ ] Source code is open-sourced with reproducible build instructions
61+
- [ ] Security review completed, critical findings addressed
62+
- [ ] SNS launch tested locally using [dfinity/sns-testing](https://github.com/dfinity/sns-testing)
63+
- [ ] SNS testflight deployed on mainnet to verify governance and upgrade flows
64+
- [ ] All dapp operations (canister upgrades, asset updates) tested via SNS proposals
65+
- [ ] Cycles management strategy in place so canisters never run out after launch
66+
- [ ] Frontend SNS integration tested (swap UI, proposal voting)
67+
- [ ] `sns_init.yaml` parameters validated locally
68+
69+
## Configure sns_init.yaml
70+
71+
All launch parameters are defined in a single YAML file. Copy the [template](https://github.com/dfinity/sns-testing/blob/main/example_sns_init.yaml) and fill in every field. The configuration file is included in the NNS proposal, so the NNS community can inspect every parameter before voting.
72+
73+
The file has five sections:
74+
75+
**Project metadata** — name, description, logo, and URL displayed to NNS voters and swap participants.
76+
77+
**Governance parameters** — proposal rejection fees, voting periods, minimum neuron stake, and voting power bonuses for dissolve delay and neuron age.
78+
79+
**Token configuration** — token name, ticker symbol, and ledger transaction fee.
80+
81+
**Token distribution** — developer neuron allocations (with dissolve delays and vesting periods), treasury balance, and swap allocation.
82+
83+
**Swap parameters** — minimum and maximum ICP participation, minimum participant count, swap duration (3–7 days is standard), Neurons' Fund participation, and any geo-restrictions or confirmation text.
84+
85+
Example configuration skeleton:
86+
87+
```yaml
88+
# All numeric values are in e8s (1 token = 100_000_000 e8s). Time values are in seconds.
89+
90+
name: MyProject
91+
description: >
92+
A decentralized application for [purpose].
93+
url: https://myproject.com
94+
logo: logo.png
95+
96+
NnsProposal:
97+
title: "Proposal to create an SNS for MyProject"
98+
url: "https://forum.dfinity.org/t/myproject-sns-proposal/XXXXX"
99+
summary: >
100+
This proposal creates an SNS DAO to govern MyProject.
101+
102+
fallback_controller_principals:
103+
- YOUR_PRINCIPAL_ID_HERE
104+
105+
dapp_canisters:
106+
- BACKEND_CANISTER_ID
107+
- FRONTEND_CANISTER_ID
108+
109+
Token:
110+
name: MyToken
111+
symbol: MYT
112+
transaction_fee: 0.0001 tokens
113+
logo: token_logo.png
114+
115+
Proposals:
116+
rejection_fee: 1 token
117+
initial_voting_period: 4 days
118+
maximum_wait_for_quiet_deadline_extension: 1 day
119+
120+
Neurons:
121+
minimum_creation_stake: 1 token
122+
123+
Voting:
124+
minimum_dissolve_delay: 1 month
125+
MaximumVotingPowerBonuses:
126+
DissolveDelay:
127+
duration: 8 years
128+
bonus: 100%
129+
Age:
130+
duration: 4 years
131+
bonus: 25%
132+
RewardRate:
133+
initial: 2.5%
134+
final: 2.5%
135+
transition_duration: 0 seconds
136+
137+
Distribution:
138+
Neurons:
139+
- principal: DEVELOPER_PRINCIPAL
140+
stake: 2_000_000 tokens
141+
memo: 0
142+
dissolve_delay: 6 months
143+
vesting_period: 24 months
144+
InitialBalances:
145+
treasury: 5_000_000 tokens
146+
swap: 2_500_000 tokens
147+
total: 10_000_000 tokens
148+
149+
Swap:
150+
minimum_participants: 100
151+
minimum_direct_participation_icp: 50_000 tokens
152+
maximum_direct_participation_icp: 500_000 tokens
153+
minimum_participant_icp: 1 token
154+
maximum_participant_icp: 25_000 tokens
155+
duration: 7 days
156+
neurons_fund_participation: true
157+
VestingSchedule:
158+
events: 5
159+
interval: 3 months
160+
confirmation_text: >
161+
I confirm that I am not a resident of a restricted jurisdiction
162+
and I understand the risks of participating in this token swap.
163+
restricted_countries:
164+
- US
165+
- CN
166+
```
167+
168+
Add comments throughout the file explaining your parameter choices — the NNS community will read this file when evaluating the proposal.
169+
170+
**Important constraints:**
171+
172+
- `fallback_controller_principals` must be set. If the swap fails, these principals regain control of the dapp canisters. Without this, your dapp becomes uncontrollable if the swap fails.
173+
- Developer neuron `total` must equal the sum of all neuron stakes, treasury, and swap allocations exactly.
174+
- Only six proposal types are blocked during the swap window: `ManageNervousSystemParameters`, `TransferSnsTreasuryFunds`, `MintSnsTokens`, `UpgradeSnsControlledCanister`, `RegisterDappCanisters`, and `DeregisterDappCanisters`. Do not plan operations requiring these during the swap.
175+
176+
## Launch stages
177+
178+
The SNS launch proceeds through 11 stages. Only the first three require action from your team — the rest are automatic.
179+
180+
### Stage 1: Define parameters (manual)
181+
182+
Finalize `sns_init.yaml` with the parameters you have designed. These parameters become locked into the NNS proposal — you cannot change them after submission.
183+
184+
### Stage 2: Add NNS root as co-controller (manual)
185+
186+
Add the NNS root canister (`r7inp-6aaaa-aaaaa-aaabq-cai`) as a co-controller of each dapp canister. This is required for the automated stages to proceed — it gives NNS the authority to transfer canister control to SNS root after the proposal is adopted.
187+
188+
```bash
189+
icp canister settings update BACKEND_CANISTER_ID \
190+
--add-controller r7inp-6aaaa-aaaaa-aaabq-cai \
191+
-e ic
192+
193+
icp canister settings update FRONTEND_CANISTER_ID \
194+
--add-controller r7inp-6aaaa-aaaaa-aaabq-cai \
195+
-e ic
196+
```
197+
198+
Also revoke any special permissions your team held. For example, if developers had direct commit access to asset canisters, revoke that now — after launch, asset updates must go through SNS proposals:
199+
200+
```bash
201+
# If using the asset canister, revoke direct commit permission from developer principals
202+
icp canister call FRONTEND_CANISTER_ID revoke_permission \
203+
'(record {of_principal = principal "<developer-principal>"; permission = variant { Commit;};})' \
204+
-e ic
205+
```
206+
207+
### Stage 3: Submit NNS proposal (manual)
208+
209+
Anyone with an eligible NNS neuron can submit the proposal, but you should submit it with your own neuron.
210+
211+
:::note[Requires dfx sns extension]
212+
The `dfx sns propose` command requires the `dfx sns` extension. No `icp-cli` equivalent exists yet. Install the extension with: `dfx extension install sns`. See the [dfx SNS documentation](https://github.com/dfinity/dfx-extensions) for details.
213+
:::
214+
215+
<!-- TODO: update when icp-cli sns equivalent lands -->
216+
```bash
217+
dfx sns propose --network ic --neuron $NEURON_ID sns_init.yaml
218+
```
219+
220+
There can only be one SNS creation proposal active in the NNS at a time. If another project's proposal is currently being voted on, you must wait for it to resolve before submitting yours.
221+
222+
After submitting, monitor your proposal's status on the [NNS dapp](https://nns.ic0.app) or by querying NNS governance directly.
223+
224+
### Stages 4–11: Automatic
225+
226+
After the NNS community votes to adopt the proposal, the remaining stages execute automatically:
227+
228+
| Stage | What happens |
229+
|-------|-------------|
230+
| 4 | NNS community votes; if adopted, remaining stages are triggered |
231+
| 5 | SNS-W deploys uninitialized SNS canisters on an SNS subnet |
232+
| 6 | SNS root becomes sole controller of dapp canisters |
233+
| 7 | SNS canisters are initialized in pre-decentralization-swap mode |
234+
| 8 | 24-hour minimum wait before swap opens (timing protocol applied) |
235+
| 9 | Decentralization swap opens; users send ICP and receive SNS neurons |
236+
| 10 | Swap closes (duration expires or maximum ICP reached) |
237+
| 11 | Finalization: exchange rate set, SNS neurons distributed, normal mode activated |
238+
239+
If the swap reaches the minimum participation requirements, it succeeds: SNS governance enters normal mode, token holders become the DAO, and your dapp is fully decentralized. If the swap fails (not enough participants or ICP), everything reverts: your dapp's control returns to the `fallback_controller_principals`, and all ICP contributions are refunded.
240+
241+
## Prepare your canister for SNS governance
242+
243+
Your canister code does not need to change for basic SNS compatibility — SNS governance controls upgrades through the standard canister management API. However, if your canister has admin functions that were previously protected by principal checks, transition them to accept calls from the SNS governance canister:
244+
245+
**Motoko:**
246+
247+
```motoko
248+
import Principal "mo:core/Principal";
249+
import Runtime "mo:core/Runtime";
250+
251+
persistent actor {
252+
var snsGovernanceId : ?Principal = null;
253+
254+
// ⚠ SECURITY: Only canister controllers should call this setter.
255+
// Without the controller check, any caller can front-run you and
256+
// set themselves as governance, permanently locking you out.
257+
public shared ({ caller }) func setSnsGovernance(id : Principal) : async () {
258+
assert (Principal.isController(caller));
259+
switch (snsGovernanceId) {
260+
case (null) { snsGovernanceId := ?id };
261+
case (?_) { Runtime.trap("SNS governance already set") };
262+
};
263+
};
264+
265+
func requireGovernance(caller : Principal) {
266+
switch (snsGovernanceId) {
267+
case (?gov) {
268+
if (caller != gov) { Runtime.trap("Only SNS governance can call this") };
269+
};
270+
case (null) { Runtime.trap("SNS governance not configured") };
271+
};
272+
};
273+
274+
// Admin functions become governance-gated:
275+
public shared ({ caller }) func updateConfig(newFee : Nat) : async () {
276+
requireGovernance(caller);
277+
// ... apply config change
278+
};
279+
};
280+
```
281+
282+
**Rust:**
283+
284+
```rust
285+
use candid::Principal;
286+
use ic_cdk::update;
287+
use std::cell::RefCell;
288+
289+
thread_local! {
290+
// ⚠ STATE LOSS: thread_local! RefCell is heap storage — it is wiped on upgrade.
291+
// Use ic-stable-structures in production to persist across upgrades.
292+
// See: https://docs.rs/ic-stable-structures/latest/ic_stable_structures/ for StableCell.
293+
static SNS_GOVERNANCE: RefCell<Option<Principal>> = RefCell::new(None);
294+
}
295+
296+
fn require_governance(caller: Principal) {
297+
SNS_GOVERNANCE.with(|g| {
298+
match *g.borrow() {
299+
Some(gov) if gov == caller => (),
300+
Some(_) => ic_cdk::trap("Only SNS governance can call this"),
301+
None => ic_cdk::trap("SNS governance not configured"),
302+
}
303+
});
304+
}
305+
306+
// ⚠ SECURITY: Only canister controllers should call this setter.
307+
#[update]
308+
fn set_sns_governance(id: Principal) {
309+
if !ic_cdk::api::is_controller(&ic_cdk::api::msg_caller()) {
310+
ic_cdk::trap("Only canister controllers can set governance");
311+
}
312+
SNS_GOVERNANCE.with(|g| {
313+
let mut governance = g.borrow_mut();
314+
if governance.is_some() {
315+
ic_cdk::trap("SNS governance already set");
316+
}
317+
*governance = Some(id);
318+
});
319+
}
320+
321+
#[update]
322+
fn update_config(new_fee: u64) {
323+
require_governance(ic_cdk::api::msg_caller());
324+
// ... apply config change
325+
}
326+
```
327+
328+
## Verify your configuration before launch
329+
330+
Run the SNS configuration validator locally before submitting the NNS proposal:
331+
332+
:::note[Requires dfx sns extension]
333+
The `dfx sns init-config-file validate` command requires the `dfx sns` extension. No `icp-cli` equivalent exists yet. Install with: `dfx extension install sns`.
334+
:::
335+
336+
<!-- TODO: update when icp-cli sns equivalent lands -->
337+
```bash
338+
# Validate the configuration file for parameter consistency
339+
dfx sns init-config-file validate
340+
```
341+
342+
After a local testflight deployment, verify the SNS canisters are operational:
343+
344+
```bash
345+
# Check governance is functional
346+
icp canister call sns_governance get_nervous_system_parameters '()'
347+
348+
# Verify total token supply matches your configuration
349+
icp canister call sns_ledger icrc1_total_supply '()'
350+
351+
# Confirm dapp canister controller is SNS root (not your principal)
352+
icp canister status BACKEND_CANISTER_ID
353+
```
354+
355+
After mainnet launch, monitor the swap progress:
356+
357+
```bash
358+
# Check swap status, participation count, and ICP raised
359+
icp canister call SNS_SWAP_CANISTER_ID get_state '()' -e ic
360+
```
361+
362+
## Common mistakes
363+
364+
**Setting `min_participants` too high.** If the minimum is not reached, the swap fails and all ICP is refunded. Start conservative — 100–200 is typical for a first launch.
365+
366+
**Forgetting to add NNS root as co-controller.** The launch will fail at stage 6 if NNS root was not added before the proposal was submitted.
367+
368+
**Not doing a testflight first.** The SNS testflight deploys a mock SNS on mainnet without doing a real swap — it lets you test governance flows and canister upgrade proposals before committing to the real launch.
369+
370+
**Developer neurons with no vesting or short dissolve delays.** These are separate but related concerns: a *vesting period* prevents a neuron from being dissolved during the vesting window; a *dissolve delay* sets the cooldown before a stopped neuron becomes liquid. Developer neurons with no vesting period and zero dissolve delay allow the team to immediately sell tokens post-launch. Set both a vesting period and a dissolve delay (12–48 months is standard for each) to demonstrate long-term commitment to the NNS community.
371+
372+
**Unreasonable tokenomics.** The NNS community votes on your proposal. Excessive developer allocation, zero vesting, or swap parameters outside reasonable bounds will lead to rejection. Review past successful SNS launches (OpenChat, Hot or Not, Kinic) for parameter ranges the community accepts.
373+
374+
**Not defining fallback controllers.** Without `fallback_controller_principals`, a failed swap leaves your dapp without any controllers — permanently unupgradeable.
375+
376+
**Swap duration too short.** Less than 24 hours is risky given global time zones. Three to seven days is standard.
377+
378+
## Next steps
379+
380+
- [Testing an SNS](testing.md) — test your SNS configuration locally and with a mainnet testflight before submitting the NNS proposal
381+
- [Managing an SNS](managing.md) — post-launch operations: submitting proposals, managing the treasury, upgrading canisters
382+
383+
<!-- Upstream: informed by dfinity/portal — building-apps/governing-apps/launching/launch-summary-1proposal.mdx, launching/launch-steps-1proposal.mdx, launching/integrating.mdx, tokenomics/index.mdx, tokenomics/predeployment-considerations.mdx, tokenomics/preparation.mdx, tokenomics/sns-checklist.mdx; dfinity/icskills — skills/sns-launch/SKILL.md -->

0 commit comments

Comments
 (0)