Nunchuk Wallet Creation
Default workflow
Progress:
- Create or join a sandbox
- Invite participants if needed
- Optionally enable Nunchuk Platform key and set policies
- Add keys
- Check the current sandbox state
- Finalize when every required slot is filled and synced
- Get a fresh receive address if needed
- Export a wallet backup if needed
If the user specifically asks about invitations, use nunchuk-invitations.
If the user specifically asks about Platform key behavior or policy updates, also use nunchuk-platform-key.
If the user specifically asks about wallet backup or recovery material, also use nunchuk-wallet-management.
If the user asks to create a wallet:
- If they want to continue an existing setup, check
nunchuk sandbox listfirst. - Do not inspect auth status, config, network, or existing finalized wallets first unless the user asked or create fails.
- If they did not specify the wallet structure, ask whether they want:
- plain multisig like
2-of-3,2-of-4, etc - a Miniscript policy with fallback keys, timelocks, or hash-preimage branches
- plain multisig like
- Ask whether they want Platform key policies such as spending limits, signing delay, or auto-broadcast.
- Ask whether they want an extra key for recovery or robustness.
- When adding keys, ask whether they want to use a key on this device or add a key from Nunchuk mobile/desktop app, another signer, or a hardware wallet.
- If the user did not specify an address type, use
NATIVE_SEGWIT. UseTAPROOTwhen the user asks for Taproot. - If the user did not provide a name, use a simple placeholder like
"My Wallet".
Multisig create
Create a plain multisig sandbox:
nunchuk sandbox create --name "My Wallet" --m 2 --n 3 --address-type NATIVE_SEGWIT
Create a Taproot multisig sandbox:
nunchuk sandbox create --name "My Taproot Wallet" --m 2 --n 3 --address-type TAPROOT
Join:
nunchuk sandbox join <sandbox-id>
Join from URL:
nunchuk sandbox join https://nunchuk.io/wallet/join/<sandbox-id>
Miniscript create
If the user asks which Miniscript path is satisfiable, what a signing path means, or how a branch will sign, also use nunchuk-wallet-transactions.
Read references/bip-0379.md when you need more Miniscript background, extra fragment patterns, or terminology.
Miniscript sandboxes support NATIVE_SEGWIT and TAPROOT.
For non-trivial NATIVE_SEGWIT / P2WSH policies, use the bundled helper:
scripts/miniscript.js compile 'thresh(2,pk(key_0_0),pk(key_1_0),pk(key_2_0))'
scripts/miniscript.js analyze 'multi(2,key_0_0,key_1_0,key_2_0)'
Use compile for policies and analyze for templates. It prints cost analysis and script structure. Do not use it for Taproot/Tapscript fragments such as multi_a(...).
Before creating a wallet, verify the final template:
scripts/miniscript.js analyze "<native-segwit-template>"
nunchuk miniscript inspect --miniscript "<native-segwit-template>"
nunchuk miniscript validate --miniscript "multi_a(2,key_0_0,key_1_0,key_2_0)" --address-type TAPROOT
Create a Miniscript sandbox:
nunchuk sandbox create --name "My Wallet" \
--miniscript-template "multi(2,key_0_0,key_1_0,key_2_0)" \
--address-type NATIVE_SEGWIT
Create a Taproot Miniscript sandbox:
nunchuk sandbox create --name "My Taproot Miniscript Wallet" \
--miniscript-template "multi_a(2,key_0_0,key_1_0,key_2_0)" \
--address-type TAPROOT
Add keys
Generate a software key on this device and add it by fingerprint to a multisig slot:
nunchuk key generate --name "My key"
nunchuk sandbox add-key <sandbox-id> --slot 0 --fingerprint <xfp>
Reuse an existing software key on this device:
nunchuk key list
nunchuk sandbox add-key <sandbox-id> --slot 0 --fingerprint <xfp>
Add a local software key to a Miniscript slot:
nunchuk key list
nunchuk sandbox add-key <sandbox-id> --slot key_0_0 --fingerprint <xfp>
Add key with structured fields:
nunchuk sandbox add-key <sandbox-id> --slot 0 \
--fingerprint "1a2b3c4d" \
--xpub "xpub..." \
--path "m/48h/0h/0h/2h"
Add key with a descriptor:
nunchuk sandbox add-key <sandbox-id> --slot key_1_0 \
--descriptor "[1a2b3c4d/48h/0h/0h/2h]xpub..."
For Miniscript, --slot uses signer names from the template such as key_0_0 or key_3_0.
Check, sync, and finalize
Check sandbox state:
nunchuk sandbox get <sandbox-id>
Enable platform key for multisig:
nunchuk sandbox platform-key enable <sandbox-id>
Enable platform key for Miniscript:
nunchuk sandbox platform-key enable <sandbox-id> --slot key_3_0
For Platform key behavior and policy examples, also use nunchuk-platform-key.
Set a global Platform key policy:
# Auto-broadcast, max 1000 USD per day.
nunchuk sandbox platform-key set-policy <sandbox-id> \
--auto-broadcast --limit-amount 1000 --limit-currency USD --limit-interval DAILY
Set a per-key Platform key policy:
# Only for signer 1a2b3c4d, with auto-broadcast, a 1h delay, and unlimited spending.
nunchuk sandbox platform-key set-policy <sandbox-id> \
--signer 1a2b3c4d --auto-broadcast --signing-delay 1h
Finalize:
nunchuk sandbox finalize <sandbox-id>
nunchuk wallet address get <wallet-id>
nunchuk wallet export <wallet-id> > wallet-backup.txt
For Taproot multisig, optionally configure the Value Key Set during finalize. This is the selected
m signer set used for key-path signing to improve privacy and fees. Use comma-separated signer
slot indexes or 8-hex fingerprints:
nunchuk sandbox finalize <sandbox-id> --value-key-set 0,1
Important sync states:
ADDEDmeans a slot is known filled remotely, but this device has not synced the full signer descriptor yet.sandbox getcan sync encrypted sandbox state for other participants.- If finalize says other devices need to come online briefly, run
nunchuk sandbox get <sandbox-id>and retry after the other devices sync. - If
sandbox getshowsstatus: ACTIVE, runnunchuk sandbox finalize <sandbox-id>to create the local wallet record.
Examples
Create a plain 2-of-3 multisig wallet:
nunchuk sandbox create --name "My Wallet" --m 2 --n 3 --address-type NATIVE_SEGWIT
Create a wallet and use a software key stored on this device:
nunchuk sandbox create --name "My Wallet" --m 2 --n 3 --address-type NATIVE_SEGWIT
nunchuk key generate
nunchuk sandbox add-key <sandbox-id> --slot 0 --fingerprint <xfp>
Create a 2-of-3 wallet with a 100 USD daily Platform key spending limit:
nunchuk sandbox create --name "My Wallet" --m 2 --n 3 --address-type NATIVE_SEGWIT
nunchuk sandbox platform-key enable <sandbox-id>
nunchuk sandbox platform-key set-policy <sandbox-id> \
--limit-amount 100 --limit-currency USD --limit-interval DAILY
Create a 2-of-3 wallet with a 100 USD daily spending limit and auto-broadcast:
nunchuk sandbox create --name "My Wallet" --m 2 --n 3 --address-type NATIVE_SEGWIT
nunchuk sandbox platform-key enable <sandbox-id>
nunchuk sandbox platform-key set-policy <sandbox-id> \
--auto-broadcast \
--limit-amount 100 --limit-currency USD --limit-interval DAILY
Create a 2-of-3 wallet with a 24-hour Platform key signing delay, unlimited spending:
nunchuk sandbox create --name "My Wallet" --m 2 --n 3 --address-type NATIVE_SEGWIT
nunchuk sandbox platform-key enable <sandbox-id>
nunchuk sandbox platform-key set-policy <sandbox-id> \
--signing-delay 24h
Create a Miniscript wallet using one of the templates from Miniscript template examples below:
nunchuk sandbox create --name "My Wallet" \
--miniscript-template "multi(2,key_0_0,key_1_0,key_2_0)" \
--address-type NATIVE_SEGWIT
Create a Taproot Miniscript wallet:
nunchuk sandbox create --name "My Taproot Miniscript Wallet" \
--miniscript-template "multi_a(2,key_0_0,key_1_0,key_2_0)" \
--address-type TAPROOT
Create a Miniscript 2-of-3 wallet with a 100 USD daily Platform key spending limit:
nunchuk sandbox create --name "My Wallet" \
--miniscript-template "or_d(multi(2,key_0_0,key_1_0,key_2_0),and_v(v:pk(key_3_0),after(1735689600)))" \
--address-type NATIVE_SEGWIT
nunchuk sandbox platform-key enable <sandbox-id> --slot key_3_0
nunchuk sandbox platform-key set-policy <sandbox-id> \
--limit-amount 100 --limit-currency USD --limit-interval DAILY
Miniscript template examples
Simple 2-of-3:
multi(2,key_0_0,key_1_0,key_2_0)
Meaning:
- Any 2 of
key_0_0,key_1_0, andkey_2_0can spend at any time. - There is no timelock branch and no hash-preimage requirement.
2-of-3 with an absolute block-height recovery key:
or_d(multi(2,key_0_0,key_1_0,key_2_0),and_v(v:pk(key_3_0),after(840000)))
Meaning:
- The normal path is still
2-of-3at any time. - The recovery path lets
key_3_0spend alone once chain height reaches block840000. 840000is a block height, not seconds.
2-of-3 with an absolute timestamp recovery key:
or_d(multi(2,key_0_0,key_1_0,key_2_0),and_v(v:pk(key_3_0),after(1735689600)))
Meaning:
- The normal path is still
2-of-3at any time. - The recovery path lets
key_3_0spend alone once chain time reaches UNIX timestamp1735689600. 1735689600is2025-01-01 00:00:00 UTC.
2-of-3 with a relative block-delay recovery key:
or_d(multi(2,key_0_0,key_1_0,key_2_0),and_v(v:pk(key_3_0),older(144)))
Meaning:
- The normal path is still
2-of-3at any time. - The recovery path lets
key_3_0spend alone after the UTXO being spent has aged by144blocks. - At roughly 10 minutes per block,
144blocks is about 24 hours.
2-of-3 with a relative time-delay recovery key:
or_d(multi(2,key_0_0,key_1_0,key_2_0),and_v(v:pk(key_3_0),older(4194473)))
Meaning:
- The normal path is still
2-of-3at any time. - The recovery path lets
key_3_0spend alone after a time-based BIP68 delay. 4194473is not seconds. It is a raw sequence value:4194304sets the BIP68 time-based flag- the remaining
169means169units of512seconds 169 * 512 = 86528seconds, which is about24 hours 1 minute 28 seconds
3-of-4 that becomes 2-of-4 after 2 years and 1-of-4 after 5 years:
thresh(3,pk(key_0_0),s:pk(key_1_0),s:pk(key_2_0),s:pk(key_3_0),sln:after(1842652800),sln:after(1937260800))
Meaning:
- Before
2028-05-23 00:00:00 UTC, any 3 of the 4 keys can spend. - After
1842652800, any 2 of the 4 keys can spend. - After
1937260800, any 1 of the 4 keys can spend. - These are fixed absolute UTC timestamps.
3-of-4 that becomes 2-of-4 six months after funds arrive and 1-of-4 after one year:
thresh(3,pk(key_0_0),s:pk(key_1_0),s:pk(key_2_0),s:pk(key_3_0),sln:older(4225186),sln:older(4255898))
Meaning:
- Immediately after funds arrive, any 3 of the 4 keys can spend.
- After the UTXO is about 183 days old, any 2 of the 4 keys can spend.
- After the UTXO is about 365 days old, any 1 of the 4 keys can spend.
older(...)values are raw BIP68 sequence values, not seconds.- For separate signer sets at each stage, an explicit
or_d(...or_i(...))branch script can model that, but it is more verbose.
2-of-3 with a hash-preimage branch:
or_d(multi(2,key_0_0,key_1_0,key_2_0),and_v(v:pk(key_3_0),sha256(2222222222222222222222222222222222222222222222222222222222222222)))
Meaning:
- The normal path is still
2-of-3at any time. - The alternate path lets
key_3_0spend alone only if the spender also reveals the 32-byte preimage whose SHA256 matches the given hash.
Timelock guide
after(n) is an absolute timelock:
- it uses Bitcoin's absolute locktime rules
- valid values are
1through2147483647(2^31 - 1) - if
n < 500000000, Bitcoin interprets it as a block height - if
n >= 500000000, Bitcoin interprets it as a UNIX timestamp in seconds - if
n >= 2147483648, the value is invalid because it exceeds the2^31 - 1limit, even if it looks like a future UNIX timestamp
Examples:
after(840000)means the path is valid once chain height reaches block840000; it does not mean840000secondsafter(1735689600)means the path is valid once chain time reaches2025-01-01 00:00:00 UTCafter(2147483647)is the latest valid timestamp lock and means2038-01-19 03:14:07 UTCafter(2208988800)is invalid because2208988800means2040-01-01 00:00:00 UTC- For a calendar target beyond
2038-01-19 03:14:07 UTC, use a height lock instead: get the current height withnunchuk network tip, estimate the target height, and useafter(<target-height>) - Height locks are approximate calendar time because block intervals vary; estimate with the current height plus roughly
144blocks per day
older(n) is a relative timelock enforced through input sequence:
- it uses Bitcoin's relative locktime rules from
OP_CHECKSEQUENCEVERIFY - it starts counting from the confirmation of the UTXO being spent, not from wallet creation
- if
n < 4194304, the BIP68 time-based flag is not set, so the value is block-based - if
n >= 4194304, the value is a raw BIP68 time-based sequence encoding, not plain seconds
Examples:
older(144)means the UTXO must be at least144blocks old before that path can be used; at roughly 10 minutes per block, that is about 24 hoursolder(4194473)means:- subtract the time flag
4194304, leaving169 - interpret
169as169units of512seconds 169 * 512 = 86528seconds, about24 hours 1 minute 28 seconds
- subtract the time flag
older(86400)does not mean one day; for time-based relative locks the number must already be BIP68-encoded
For static signing-path inspection or satisfiability checks, use nunchuk-wallet-transactions.
Defaults
- Use
NATIVE_SEGWITby default. - Supported multisig address types are
NATIVE_SEGWIT,NESTED_SEGWIT,LEGACY, andTAPROOT. - Miniscript address types are
NATIVE_SEGWITandTAPROOT; use Tapscript fragments such asmulti_a(...)for Taproot. - Use
--jsonfor raw machine-readable output. - For plain create requests, prefer a single
sandbox createcommand over exploratory checks. - If the user already has a local software key, use
nunchuk key listandsandbox add-key --fingerprint <xfp>instead of generating a new one. - If the user wants to use a hot key on this device, generate it with
nunchuk key generateand add it withsandbox add-key --fingerprint <xfp>. - After finalizing, use
wallet export <wallet-id>if the user wants a wallet backup. - Treat
sandbox deleteas destructive and only run it when the user clearly asked to remove the sandbox.
Gotchas
- Mnemonics, seed phrases, and
xprvvalues are critical secrets. Anyone with them can control the funds, and losing them may make the funds unrecoverable. - Derivation paths may use either
hor'; preferh. key generatestores the software key on the current device for the current user and network.sandbox add-key --fingerprint <xfp>is the simplest way to use a locally stored software key.- For hardware keys, HWI can help export a descriptor to add here: https://github.com/bitcoin-core/HWI/releases
- Miniscript support requires
nunchuk-cli >= 0.1.2. - Finalize only after every required slot is filled and synced.
- Accounts may have a limit on wallets or sandboxes. If creation fails because of that, ask whether to delete unused wallets or sandboxes, and only run delete after the user confirms.