On this page
Reproducible builds
Rebuild the vault or the factory from its recorded commit with the build CI runs, then check that your wasm has the code hash in the record and on chain.
This page rebuilds the vault or the factory contract from its source, in Docker, with the same build CI runs. You then check that your wasm has the code hash recorded in contracts/code-hashes.json (opens in a new tab) and the code hash of the contract deployed on NEAR testnet. A NEAR code hash is the base58 of the sha256 of the wasm, so equal hashes mean the deployed contract is the wasm built from that commit, and the source at that commit is the code that runs.
Note
Rebuilding signs nothing and sends nothing. The last step reads public testnet data through an RPC node, with no key.
What you need#
- Docker, running. The build runs inside the image in the table below, pulled by its digest.
- Linux on x86_64, to follow CI exactly: CI runs on ubuntu, and the archive sha256 below is for its Linux x86_64 release archive of cargo-near.
- git, Node.js 24 or later and pnpm 10, for the clone and its scripts.
- Rust through rustup.
cargo nearis a cargo subcommand, andrust-toolchain.tomlin the clone names the Rust version. - cargo-near, at the version in the table. Step 1 installs it.
The build uses these, each read from the repository when this page is built:
| What | Value | Set in |
|---|---|---|
| cargo-near | 0.22.0 | The release URL in both cargo-near install steps of .github/workflows/ci.yml |
| cargo-near release archive | cargo-near-x86_64-unknown-linux-gnu.tar.gz | The same URL, in the release cargo-near-v0.22.0 |
| Archive sha256 | 9429064e30a427e96523f539cb255f3688d2345e5000d7d4e4873a8247247d1a | CARGO_NEAR_SHA256 in .github/workflows/ci.yml. sha256sum --check --strict stops the job on any other archive |
| Docker image | sourcescan/cargo-near:0.22.0-rust-1.99.1 | image in [package.metadata.near.reproducible_build] of contracts/vault/Cargo.toml and contracts/factory/Cargo.toml |
| Image digest | sha256:c9655eaf7436b90c87ea8b25739f91063032c5c30ba24cfeafe72c142943b5bb | image_digest, in the same place. Docker pulls the image by this digest |
| Environment passed in | None | passed_env, in the same place: the host environment variables the build container receives |
| Build inside the image | cargo near build non-reproducible-wasm --locked | container_build_command, in the same place |
| Rust | 1.99.1 | rust-toolchain.toml, and the toolchain of every Rust job in .github/workflows/ci.yml |
| Build CI runs | bash scripts/build-reproducible.sh vault probarion_vault, bash scripts/build-reproducible.sh factory probarion_factory | One leg per crate in the reproducible job of .github/workflows/ci.yml |
cargo-near runs the build inside the pinned image, with the compiler that image carries, so the compiler on the machine that starts it takes no part. On your machine, Rust only runs cargo near itself.
Rebuild a recorded build#
-
Install cargo-near and check its archive. In a folder outside any clone, download the release archive CI installs and print its sha256:
Shell curl --proto '=https' --tlsv1.2 -fsSL -o cargo-near.tar.gz https://github.com/near/cargo-near/releases/download/cargo-near-v0.22.0/cargo-near-x86_64-unknown-linux-gnu.tar.gz sha256sum cargo-near.tar.gzThe sum must equal the archive sha256. If it does not, stop. Otherwise install the binary where cargo finds it, as CI does:
Shell mkdir -p cargo-near tar -xzf cargo-near.tar.gz --no-same-owner --strip-components 1 -C cargo-near install -m 0755 cargo-near/cargo-near "$HOME/.cargo/bin/cargo-near" cargo near --versionThe version it prints must be the cargo-near version.
-
Clone the repository, and a second copy of its history beside it. Run these lines one at a time:
Shell git clone https://github.com/probarion/probarion cd probarion pnpm install pnpm --filter "{packages/cli}..." build git clone --no-checkout . ../probarion-recordedThe first four lines are the site's usual setup. The last one copies the history into
../probarion-recordedwith no files checked out. CI works the same way: it keeps the current scripts and builds the recorded commit in a separate copy. Run every later command from theprobarionfolder. -
Check out the recorded commit. Pick a row in Recorded builds and copy its commit. This example rebuilds the vault at commit
64ac3c0:Shell git -C ../probarion-recorded checkout --quiet --detach 64ac3c03547d59a1f563b5ddf5767c38ae781413 -
Run the build CI runs.
SRC_DIRtells the script which checkout to build:Shell export SRC_DIR=../probarion-recorded bash scripts/build-reproducible.sh vault probarion_vaultFor a factory row, run
bash scripts/build-reproducible.sh factory probarion_factoryinstead. The script refuses uncommitted changes undercontracts/, runscargo near build reproducible-wasmin the pinned image, and writes the wasm and its build info to../probarion-recorded/contracts/target/reproducible/vault/. Its last line is the wasm's code hash. The first run also pulls the image. -
Compare it with the record. This is the comparison CI makes.
checkreads the commit from the build info, finds the record for the source at that commit, and compares the two hashes:Shell node scripts/code-hash.mjs check probarion-vault vault ../probarion-recorded/contracts/target/reproducible/vault/probarion_vault.wasmFor the vault at
64ac3c0, it prints:Text probarion-vault: code_hash 3cQXYDvfGLRMveCVAHmh1exiuZKDXWqD8cUhqdEs9MF1 matches the recorded build (commit 64ac3c03547d59a1f563b5ddf5767c38ae781413)For a factory row, pass
probarion-factory factoryand the path ofprobarion_factory.wasmunderreproducible/factory/. Any other answer means your wasm is not the recorded build, and the command says why. -
Compare it with the chain. Read the code hash of an account the row's Deployed column names, here the smoke vault the testnet factory created:
Shell node scripts/testnet/chain.mjs code-hash smoke-embedded.vaults.probarion.testnetIt asks a public testnet RPC node for the account and prints its base58 code hash. It must equal the code hash of step 4 and the row's code hash.
To check a vault's code hash and its keys in one run, with no rebuild, use the verifier: its check f accepts a vault whose code is a recorded vault build or, for a vault the canonical factory created, one of that factory's versions.
Recorded builds#
One row for each reproducible build in contracts/code-hashes.json (opens in a new tab), in the order it was recorded, so the newest is last. The site reads the file when it is built.
| Crate | Code hash | Commit | Recorded | Deployed | Built with |
|---|---|---|---|---|---|
probarion-vault 0.1.0 | 6tbERLAXWjaVKbSNzaivRDh7H9kCcjtmV1qGrgz7entR | 3bd8189612a43b1063745c80fd053527ef8e19f9 | 2026-11-01 | Testnet: v0.probarion.testnet | cargo-near 0.22.0 reproducible-wasm, sourcescan/cargo-near:0.22.0-rust-1.99.1 |
probarion-vault 0.1.0 | 54Ne1r9g2Smf2Qtrt9VznmPpEAAHzvK4hbQSJ9voQmuD | ba654353fa5af4cf2b023badf7e56ddf4caff491 | 2026-11-01 | None | cargo-near 0.22.0 reproducible-wasm, sourcescan/cargo-near:0.22.0-rust-1.99.1 |
probarion-factory 0.1.0 | 91MDwdWx56fYh6rN3VWvnuk5fEv2bc58cv9inGEfnevd | ba654353fa5af4cf2b023badf7e56ddf4caff491 | 2026-11-01 | None | cargo-near 0.22.0 reproducible-wasm, sourcescan/cargo-near:0.22.0-rust-1.99.1 |
probarion-factory 0.1.0 | FK6mMqUdieWmYyB9hqyJL23Aew9wWGSPustb6tUDtaQ6 | 3e7bd2d6d6ddc8413220a27d47710f5d47850355 | 2026-11-01 | Testnet: the factory vaults.probarion.testnet | cargo-near 0.22.0 reproducible-wasm, sourcescan/cargo-near:0.22.0-rust-1.99.1 |
probarion-vault 0.1.0 | 3cQXYDvfGLRMveCVAHmh1exiuZKDXWqD8cUhqdEs9MF1 | 64ac3c03547d59a1f563b5ddf5767c38ae781413 | 2026-11-03 | Testnet: version 1 (embedded) of the factory vaults.probarion.testnet, and its smoke vault smoke-embedded.vaults.probarion.testnet; Testnet: the demo vault demo.vaults.probarion.testnet; Mainnet: v0.probarion.near | cargo-near 0.22.0 reproducible-wasm, sourcescan/cargo-near:0.22.0-rust-1.99.1 |
Read the table#
- Code hash is what
view_accountreports ascode_hashfor an account that deploys the build as its own code, and what step 4 prints. An account on a global contract reports it asglobal_contract_hashinstead. - Commit is the commit the wasm was built from and embeds. Check it out to rebuild the row.
- Deployed comes from the committed deployment records,
contracts/deployments/testnet.json(opens in a new tab) andmainnet.json, not from the chain. Step 6 reads the chain. None means no recorded deployment runs that build. - Built with is the cargo-near version and the image the record says built it.
Each entry of the file also holds the git ids of the crate's source tree, of contracts/Cargo.toml and of contracts/Cargo.lock. check uses them to find the row for a build's source, and CI uses them to choose the commit it rebuilds. The first two vault rows share the crate's source tree but not contracts/Cargo.toml or contracts/Cargo.lock, so the record counts them as two sources. Any change to a contract, to contracts/Cargo.toml or to contracts/Cargo.lock leaves that contract without a row for its new source, until the build of the push that made the change is recorded.
The verifier trusts the code hashes of the vault rows, as KNOWN_VAULT_CODE_HASHES in @probarion/core, generated from this file. It never trusts a factory row as a vault. It also trusts each version the canonical factory lists, for a vault that factory created.
What the record does not cover#
- Only the vault and the factory are recorded. The test token and NEAR Intents stub contracts on testnet,
test-ftandintents-stub, are not. - A matching hash shows which source runs, not that the source is right. No third party has audited the contracts: see What works today.
- The npm packages and the hosted app are not in this record.
What CI checks#
CI rebuilds both contracts, the vault and the factory, on every pull request into dev or main, on every push to those two branches and on every v* tag. The reproducible job of .github/workflows/ci.yml (opens in a new tab) runs one leg per contract:
- When the contract's current source is in the record, the leg rebuilds the commit recorded for that source, not the commit the run checked out, and fails when the code hash differs from the recorded one.
- When the source is not in the record yet, as in a pull request that changes the contract, the leg builds the checked-out commit (in a pull request, GitHub's temporary merge commit) and warns that it is not recorded. On a
v*tag it fails instead. - Each leg uploads the wasm and its build info as an artifact,
probarion-vault-wasmorprobarion-factory-wasm, kept for 90 days, and prints the build info in the run summary. The upload comes before the comparison, so a failed comparison still leaves the wasm to inspect.
The verify check, the one check a branch rule requires, passes only when every job succeeded, this one included.
node scripts/code-hash.mjs record adds a build to the record. It refuses a wasm with no build info beside it, or whose build info does not come from a CI push run with a run id. So it never takes the wasm of a pull request run, whose commit is a temporary merge commit, or of a local build, whose build info names no CI event. This guards against mistakes, not against a build info written by hand or a hand edit of contracts/code-hashes.json. What catches a wrong hash is CI: while a contract's source stays the recorded one, every run rebuilds the recorded commit and fails when the hash differs from the record. AGENTS.md (opens in a new tab) has the recording steps.
Common mistakes#
- Building the current commit instead of the recorded one. A reproducible wasm embeds the commit it was built from, as its NEP-330
source_code_snapshot. The same source built at another commit has another code hash, socheckrefuses it and names the commit to rebuild. - Uncommitted changes under
contracts/. The script stops withcommit contracts/ first: the reproducible build uses the committed source. Keep../probarion-recordedclean. - A shallow clone.
git clone --depth 1leaves out the older commits, so the checkout of a recorded commit finds nothing. CI fetches the whole history for the same reason. - A local build.
pnpm contracts:buildrunscargo near build non-reproducible-wasmoutside Docker, and adds--no-abiin a Windows shell such as Git Bash. It is for tests and testnet runs. Its hash matches no row, and the repository never records one. - Comparing hex with base58.
sha256sumprints the sha256 in hex. NEAR reports the same sha256 in base58.node scripts/code-hash.mjs hash <wasm>prints the base58 form, and the build info next to the wasm holds both, ascode_hashandsha256. - A vault on a global contract. Such an account has no code of its own:
view_accountreports the all-zero hash,11111111111111111111111111111111, as itscode_hash, and the code's hash asglobal_contract_hash.chain.mjs code-hashprintscode_hashonly. The SDK, and so the verifier, readsglobal_contract_hashfirst. - Looking on mainnet. One vault is deployed there,
v0.probarion.near. Its code hash is3cQXYDvfGLRMveCVAHmh1exiuZKDXWqD8cUhqdEs9MF1, the same recorded build the testnet factory's vaults run. Runchain.mjs code-hashwithRPC=https://rpc.mainnet.fastnear.comto read it. No factory is deployed on mainnet. Testnet and mainnet has the status.
Related pages#
- Verify a vault yourself: run the verifier, whose check f compares a vault's code hash with this record.
- Verifier checks: what each check proves, and what it leaves to trust.
- Security model: what the vault enforces, and the known gaps.
- What works today: the status of reproducible builds, and why
v0.probarion.testnetruns an older build. - Contract reference: the methods of the code you rebuilt.
Built from 4ab41ca · reviewed
.github/workflows/ci.yml(opens in a new tab)contracts/code-hashes.json(opens in a new tab)packages/core/src/code-hashes.ts(opens in a new tab)packages/core/scripts/sync-code-hashes.ts(opens in a new tab)scripts/build-reproducible.sh(opens in a new tab)scripts/code-hash.mjs(opens in a new tab)scripts/build-contracts.sh(opens in a new tab)scripts/testnet/chain.mjs(opens in a new tab)contracts/vault/Cargo.toml(opens in a new tab)contracts/factory/Cargo.toml(opens in a new tab)rust-toolchain.toml(opens in a new tab)contracts/deployments/testnet.json(opens in a new tab)contracts/deployments/mainnet.json(opens in a new tab)packages/core/src/verify.ts(opens in a new tab)packages/sdk/src/rpc.ts(opens in a new tab)AGENTS.md(opens in a new tab)README.md(opens in a new tab)SECURITY.md(opens in a new tab)docs/runbooks/factory.md(opens in a new tab)docs/runbooks/mainnet-v0.md(opens in a new tab)apps/web/src/lib/proof/cli.ts(opens in a new tab)apps/web/content/docs/trust/verify-a-vault.mdx(opens in a new tab).gitignore(opens in a new tab)package.json(opens in a new tab)docs/evidence/mainnet-v0/README.md(opens in a new tab)