Quickstart
NiceTry is a Foundry (Solidity) contracts repository, the reference implementation of the Ephemeral Keys Protocol. This page gets you from a clone to a local build, the test suite, and a deterministic deployment.
Prerequisites
- Foundry (
forge,cast): for building, testing, and deploying the contracts. - Node.js 20.12+: only for the gasless ERC-4337 infrastructure deploy under
deploy-4337/. - Python 3: only for the reference signer scripts under
scripts/. - For live UserOps on a testnet: an RPC endpoint and a bundler/paymaster (e.g. a Pimlico API key).
Clone and build
git clone https://github.com/RivaLabs-Core/NiceTry.git
cd NiceTry
forge install
forge buildThe project targets solc 0.8.30 with the optimizer (200 runs) and via-ir enabled (see foundry.toml). These settings matter: the deterministic addresses below are a function of the exact compiled bytecode, so every chain must be deployed from the same configuration.
Run the tests
forge testThe suite currently holds 93 tests across 7 suites. Coverage includes:
- FORS+C verifier: round-trip sign/recover, tamper tests across every signature field, gas measurements, and a committed reference vector (
test/vectors/fors-reference-0.json) produced byscripts/signing_reference.py. - SimpleAccount: factory address binding (root, backup key, salt), activation with Merkle proofs, rotation, burned-key reuse rejection, multi-device enrollment via
addSigner, SPHINCS dispatch, recovery and bootstrap (with mock verifiers), and the length-dispatch disjointness invariant. - SPHINCS backup verifier: revert guards (wrong length, non-canonical key). The vector-backed happy-path tests activate once
test/vectors/sphincs-reference-0.jsonis generated withscripts/sphincs_reference.py, which drives the external SPHINCS signer. - SphincsAccount: factory key binding, initialisation lock, validation with a mock verifier, garbage rejection on the real verifier, execution surface.
- Standard SPHINCS verifier: layout arithmetic, length disjointness, revert guards and a garbage-returns-false fuzz test. No positive verification is asserted until a reference vector exists.
Deploy
A Forge script lives at script/Deploy.s.sol. Running it deploys, in order:
ForsVerifierSphincsVerifierSimpleAccountFactory, whose constructor deploys the singleSimpleAccountimplementation
forge script script/Deploy.s.sol \
--rpc-url <RPC_URL> \
--broadcastThe script uses the standard CREATE2 deployer at 0x4e59b44847b379578588920cA78FbF26c0B4956C, so all three contracts land at the same addresses on every chain when the same salts, bytecode, and constructor arguments are used. It targets the canonical ERC-4337 EntryPoint v0.7 (0x0000000071727De22E5E9d8BAf0edAc6f37da032), which shares one address across mainnet, Sepolia, and major rollups.
The default salts are keccak256("NiceTry.ForsVerifier.v1"), keccak256("NiceTry.SphincsVerifier.v1") and keccak256("NiceTry.SimpleAccountFactory.v1"). They, and the EntryPoint, can be overridden through the FORS_VERIFIER_SALT, SPHINCS_VERIFIER_SALT, FACTORY_SALT and ENTRYPOINT environment variables, but overriding any of them on one chain drifts every downstream address on that chain. The script is idempotent: contracts that already exist at their predicted address are skipped, and it asserts after deployment that nothing drifted.
Because addresses are deterministic, a user's account address is identical on every supported chain. See Multichain addresses.
The SPHINCS-only family
script/DeploySphincsAccount.s.sol deploys SphincsStandardVerifier and SphincsAccountFactory the same way, under their own salts, without touching the SimpleAccount family:
forge script script/DeploySphincsAccount.s.sol \
--rpc-url <RPC_URL> \
--broadcastSee SPHINCS-only account.
Gasless infrastructure deploy
deploy-4337/ performs the same deployment from a Pimlico-sponsored UserOperation, so the deployer key needs no native gas. It reads bytecode straight from the Forge out/ artifacts and uses the same CREATE2 deployer, salts and EntryPoint, so the addresses match the Foundry path exactly.
cd deploy-4337
npm install
cp .env.example .env # CHAIN_ID, RPC_URL, PRIVATE_KEY, PIMLICO_API_KEY
npm run deploy:dry # print predicted addresses, send nothing
npm run deploy # deploy for real, gas sponsored by PimlicoRe-run per chain by changing CHAIN_ID and RPC_URL. Full details and caveats are in deploy-4337/README.md.
Create and use an account
The steps below are for SimpleAccount, the ephemeral-keys account. For the stateless SphincsAccount the whole procedure collapses to: derive one SPHINCS keypair, call SphincsAccountFactory.getAddress(pkSeed, pkRoot, salt), and sign every UserOp with that key. See SPHINCS-only account.
- Offchain, derive the user's SPHINCS backup keypair (
pkSeed,pkRoot) and keep it cold. It is chain-independent and is never rotated. See SPHINCS backup. - Derive the first FORS+C signer for each supported chain and build the initial-signer Merkle root committing one signer per chain. See Multichain addresses.
- Predict the address with
SimpleAccountFactory.getAddress(initialSignerRoot, backupPkSeed, backupPkRoot, salt), or deploy withcreateAccount(...)(usually via the first UserOp'sinitCode). - Send the activation UserOp on each committed chain: it proves the chain-local first signer against the root and activates the next FORS+C key. On a chain that was not committed in the root, bootstrap with a SPHINCS signature instead. See Recovery & bootstrap.
- Thereafter, every UserOp signs with the current FORS+C key and appends the next key: the account verifies, burns the current key and activates the next one automatically.
The full byte-level procedure is in the Signer spec.
Demos
NiceTry-Wallet: standalone browser-extension wallet with local key management.NiceTry-Metamask: MetaMask integration demo.NiceTry-Multichain: showcase of stable addresses across chains.
Both wallet demos are runnable from nicetry.xyz.