Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
54 changes: 47 additions & 7 deletions .github/workflows/cut-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,21 @@ jobs:
- name: Install deps
run: npm ci

- name: Test snapshot workflow
run: node --test scripts/cut-versions.test.mjs

- name: Parse release manifest (and merge with manual inputs)
id: manifest
env:
INPUT_VERSION_LABEL: ${{ inputs.version_label }}
INPUT_MIDEN_NODE_REF: ${{ inputs.miden_node_ref }}
INPUT_MIDEN_VM_REF: ${{ inputs.miden_vm_ref }}
INPUT_MIDEN_BASE_REF: ${{ inputs.miden_base_ref }}
INPUT_MIDEN_CLIENT_REF: ${{ inputs.miden_client_ref }}
INPUT_COMPILER_REF: ${{ inputs.compiler_ref }}
INPUT_MIDEN_TUTORIALS_REF: ${{ inputs.miden_tutorials_ref }}
INPUT_GUARDIAN_REF: ${{ inputs.guardian_ref }}
INPUT_NOTE_TRANSPORT_REF: ${{ inputs.note_transport_ref }}
INPUT_BRIDGE_PORTAL_REF: ${{ inputs.bridge_portal_ref }}
run: |
node - <<'NODE'
Expand Down Expand Up @@ -113,22 +125,45 @@ jobs:
process.exit(1);
}

// Emit outputs
for (const [k,v] of Object.entries(out)) {
console.log(`::set-output name=${k}::${v}`);
// A locally generated snapshot may already accompany the manifest push.
// Skip generation only when all three snapshot artifacts are present.
const versions = JSON.parse(read('versions.json') || '[]');
const existing = [
versions.includes(out.version_label),
fs.existsSync(`versioned_docs/version-${out.version_label}`),
fs.existsSync(`versioned_sidebars/version-${out.version_label}-sidebars.json`),
];
if (existing.some(Boolean) && !existing.every(Boolean)) {
console.error(`Incomplete snapshot for ${out.version_label}; refusing to overwrite it.`);
process.exit(1);
}
out.snapshot_exists = String(existing.every(Boolean));

// Emit simple, single-line outputs through the supported Actions file.
const output = Object.entries(out).map(([key, value]) => `${key}=${value}`).join('\n');
fs.appendFileSync(process.env.GITHUB_OUTPUT, `${output}\n`);
NODE

- name: Validate refs exist
env:
GH_TOKEN: ${{ github.token }}
run: |
set -e
validate() {
repo="$1"; ref="$2"
echo "Validating $repo @ $ref"
git ls-remote "https://github.com/$repo" "$ref" | grep -q . || {
echo "ERROR: Ref '$ref' not found in $repo"
exit 1
}
if [[ "$ref" =~ ^[0-9a-f]{40}$ ]]; then
resolved=$(gh api "repos/$repo/git/commits/$ref" --jq '.sha') || exit 1
[[ "$resolved" == "$ref" ]] || {
echo "ERROR: Commit '$ref' did not resolve exactly in $repo"
exit 1
}
else
git ls-remote "https://github.com/$repo" "$ref" | grep -q . || {
echo "ERROR: Ref '$ref' not found in $repo"
exit 1
}
fi
}
validate "${REPO_NODE}" "${{ steps.manifest.outputs.miden_node_ref }}"
validate "${REPO_VM}" "${{ steps.manifest.outputs.miden_vm_ref }}"
Expand All @@ -141,6 +176,7 @@ jobs:
validate "${REPO_BRIDGE_PORTAL}" "${{ steps.manifest.outputs.bridge_portal_ref }}"

- name: Checkout sources at validated refs
if: steps.manifest.outputs.snapshot_exists != 'true'
run: |
set -e
checkout_ref() {
Expand All @@ -167,6 +203,7 @@ jobs:
# This content is cleaned up after versioning (see "Clean up" step below)
# ============================================================
- name: Aggregate docs into v0.4 IA structure
if: steps.manifest.outputs.snapshot_exists != 'true'
run: |
echo "Aggregating vendor docs into v0.4 IA structure..."

Expand Down Expand Up @@ -333,6 +370,7 @@ jobs:
ls -la docs/builder/tutorials/ || true

- name: Cut version snapshot
if: steps.manifest.outputs.snapshot_exists != 'true'
run: |
VERSION="${{ steps.manifest.outputs.version_label }}"
echo "Creating Docusaurus version: $VERSION"
Expand All @@ -346,6 +384,7 @@ jobs:
# docs/ must only contain authored content (builder/, reference/ landing pages)
# All ingested content lives in versioned_docs/ after versioning
- name: Clean up (delete vendor and ingested docs)
if: steps.manifest.outputs.snapshot_exists != 'true'
run: |
rm -rf vendor
# Guardian: remove ingested subdirs (keep authored index.md); note-transport (fully ingested)
Expand Down Expand Up @@ -379,6 +418,7 @@ jobs:
rm -rf docs/builder/tutorials/recipes/img

- name: Commit snapshots
if: steps.manifest.outputs.snapshot_exists != 'true'
run: |
if ! git diff --quiet; then
git config user.name "github-actions[bot]"
Expand Down
23 changes: 12 additions & 11 deletions .release/release-manifest.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
version: "0.15" # the label to snapshot (used if no manual override)
refs: # exact, immutable refs per source repo (tags or release branches)
protocol: "refs/tags/v0.15.3"
node: "refs/tags/v0.15.0"
miden-client: "refs/tags/v0.15.2"
tutorials: "refs/heads/main"
miden-vm: "refs/tags/v0.23.4"
compiler: "refs/tags/v0.8.1"
guardian: "refs/tags/v0.15.0"
note-transport: "refs/tags/v0.4.1"
next_version: "0.16"
version: "0.16"
refs: # Immutable documentation source commits, resolved on 2026-09-14.
protocol: "fb1e40da0f7cec156963e5d815a51a18a8313f25" # v0.16.1
node: "d6ce8b14d4680e0187b877c1de5c1cdeea16e7e2" # v0.16.0
miden-client: "5f36964d2655549e600f55ab6f65caea943d1130" # rust-sdk v0.16.1
tutorials: "965739b626acf3c63a0e547d313859079ee03c18" # v0.16 migration + NFT recipe (#249, #250)
miden-vm: "c1c2ddcb9cabc3d5c2071f8baf68c7930917cad8" # v0.29.2, the protocol v0.16 VM line
compiler: "f2e0b4274f4f13caeceb215645bd3f1f03a3c6f5" # v0.10.1; still pins protocol v0.16.0-rc.4
guardian: "be66822395551c630a164f7bdafbb67771c83442" # v0.17.0, built for Miden v0.16
note-transport: "9736b39d64cfb4d77bb2d777a9ad1a4f2ea3f2a5" # v0.5.0-rc.2, used by rust-sdk v0.16
bridge-portal: "5d8f327a8adea0e0d671b798145b6092f5e23d6a" # Developer docs #117; portal still uses SDK v0.15
next_version: "0.17"
12 changes: 6 additions & 6 deletions docs/builder/get-started/setup/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,13 @@ toolchain for the network you use. `midenup` resolves network names through the
[published channel manifest](https://0xmiden.github.io/midenup/channel-manifest.json).

:::warning Release prerequisite
The published manifest currently maps `testnet` to the v0.15 channel. The
explicit `0.16.0` channel is not a substitute: it still mixes prerelease client
and protocol components. Neither channel is a verified setup for these v0.16
testnet guides, and `devnet` is a different network.
The stable v0.16 toolchain update is ready on midenup's `next` branch, but it
has not yet been promoted to the published manifest. The published `testnet`
channel still points to v0.15, while its explicit `0.16.0` channel is the older
prerelease stack.

Do not continue with the network-dependent guides until `testnet` points to a
coherent v0.16 release channel.
Wait for the updated manifest to be published before using these
network-dependent v0.16 guides against testnet.
:::

After the manifest meets that requirement, install the public testnet toolchain
Expand Down
105 changes: 105 additions & 0 deletions scripts/cut-versions.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { createRequire } from "node:module";
import vm from "node:vm";
import test from "node:test";
import YAML from "yaml";

const require = createRequire(import.meta.url);
const workflow = YAML.parse(readFileSync(new URL("../.github/workflows/cut-versions.yml", import.meta.url), "utf8"));
const steps = workflow.jobs.cut.steps;
const parser = steps.find((step) => step.id === "manifest");
const parseCode = parser.run.match(/node - <<'NODE'\n([\s\S]*?)\nNODE/)[1];

function parse({ inputs = {}, present = [] } = {}) {
const outputs = {};
const env = Object.fromEntries(Object.entries(parser.env || {}).map(([key, value]) => {
const input = value.match(/inputs\.([a-z_]+)/)?.[1];
return [key, inputs[input] || ""];
}));
env.GITHUB_OUTPUT = "__outputs__";
const files = {
".release/release-manifest.yml": "version: '0.16'\nrefs:\n tutorials: pinned-tutorial-commit\n",
"versions.json": JSON.stringify(present.includes("version-list") ? ["0.16", "0.15"] : ["0.15"]),
};
const paths = new Set(Object.keys(files));
if (present.includes("directory")) paths.add("versioned_docs/version-0.16");
if (present.includes("sidebar")) paths.add("versioned_sidebars/version-0.16-sidebars.json");
vm.runInNewContext(parseCode, {
require: (name) => name === "fs" ? {
existsSync: (path) => paths.has(path),
readFileSync: (path) => { assert.ok(path in files, `unexpected read: ${path}`); return files[path]; },
appendFileSync: (path, contents) => {
assert.equal(path, env.GITHUB_OUTPUT);
for (const line of contents.trimEnd().split("\n")) {
const separator = line.indexOf("=");
assert.ok(separator > 0, `invalid output line: ${line}`);
outputs[line.slice(0, separator)] = line.slice(separator + 1);
}
},
} : require(name),
process: { env, exit: (code) => { throw new Error(`exit ${code}`); } },
console: { error() {}, log() {} },
});
return outputs;
}

test("manifest outputs use GITHUB_OUTPUT instead of the deprecated command", () => {
assert.match(parseCode, /GITHUB_OUTPUT/);
assert.doesNotMatch(parseCode, /::set-output/);
});

test("manual version and source overrides reach the parser instead of silently using the manifest", () => {
const outputs = parse({ inputs: {
version_label: "0.99", miden_node_ref: "node-pin", miden_vm_ref: "vm-pin",
miden_base_ref: "protocol-pin", miden_client_ref: "client-pin", compiler_ref: "compiler-pin",
miden_tutorials_ref: "tutorial-pin", guardian_ref: "guardian-pin",
note_transport_ref: "transport-pin", bridge_portal_ref: "bridge-pin",
} });
assert.equal(outputs.version_label, "0.99");
for (const [key, expected] of Object.entries({ miden_node_ref: "node-pin", miden_vm_ref: "vm-pin",
miden_base_ref: "protocol-pin", miden_client_ref: "client-pin", compiler_ref: "compiler-pin",
miden_tutorials_ref: "tutorial-pin", guardian_ref: "guardian-pin", note_transport_ref: "transport-pin",
bridge_portal_ref: "bridge-pin" })) assert.equal(outputs[key], expected);
});

test("absent snapshot is eligible for generation and uses manifest defaults", () => {
const outputs = parse();
assert.equal(outputs.version_label, "0.16");
assert.equal(outputs.miden_tutorials_ref, "pinned-tutorial-commit");
assert.equal(outputs.snapshot_exists, "false");
});

test("complete checked-in snapshot validates refs but skips generation and cleanup", () => {
const outputs = parse({ present: ["version-list", "directory", "sidebar"] });
assert.equal(outputs.snapshot_exists, "true");
const validationStep = steps.find((step) => step.name === "Validate refs exist");
assert.equal(validationStep.if, undefined, "source refs must still be validated");
for (const step of steps.slice(steps.indexOf(validationStep) + 1)) {
const condition = (step.if || "true").replace(/\$\{\{|\}\}/g, "")
.replaceAll("steps.manifest.outputs.snapshot_exists", JSON.stringify(outputs.snapshot_exists));
assert.equal(vm.runInNewContext(condition), false, `${step.name} would run on an existing snapshot`);
}
});

for (const present of [["directory"], ["version-list"], ["sidebar"], ["version-list", "directory"]]) {
test(`partial snapshot is rejected: ${present.join("+")}`, () => assert.throws(() => parse({ present })));
}

const validation = steps.find((step) => step.name === "Validate refs exist").run.split('\nvalidate "${REPO_NODE}"')[0];
const sha = "965739b626acf3c63a0e547d313859079ee03c18";
function validate(ref, apiSha = sha) {
const boundary = `
git() { if [[ "$3" == "refs/tags/v0.16.0" ]]; then echo "${sha} refs/tags/v0.16.0"; fi; }
gh() { [[ "$1" == "api" && "$2" == "repos/0xMiden/tutorials/git/commits/${sha}" ]] || return 1; [[ -n "$API_SHA" ]] || return 1; echo "$API_SHA"; }
`;
return spawnSync("bash", ["-c", `${boundary}\n${validation}\nvalidate 0xMiden/tutorials "$TEST_REF"`], {
env: { ...process.env, TEST_REF: ref, API_SHA: apiSha }, encoding: "utf8",
});
}
test("an existing exact commit pin is accepted", () => assert.equal(validate(sha).status, 0));
test("a missing exact commit is rejected", () => assert.notEqual(validate(sha, "").status, 0));
test("a mismatched commit response is rejected", () => assert.notEqual(validate(sha, "0".repeat(40)).status, 0));
test("normal tag validation still works", () => assert.equal(validate("refs/tags/v0.16.0").status, 0));
test("an unknown tag is rejected", () => assert.notEqual(validate("refs/tags/missing").status, 0));
1 change: 1 addition & 0 deletions sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const sidebars: SidebarsConfig = {
"builder/tutorials/recipes/rust/counter_contract_tutorial",
"builder/tutorials/recipes/rust/create_deploy_tutorial",
"builder/tutorials/recipes/rust/mint_consume_create_tutorial",
"builder/tutorials/recipes/rust/nft_mint_transfer",
"builder/tutorials/recipes/rust/creating_notes_in_masm_tutorial",
"builder/tutorials/recipes/rust/custom_note_how_to",
"builder/tutorials/recipes/rust/unauthenticated_note_how_to",
Expand Down
6 changes: 6 additions & 0 deletions versioned_docs/version-0.16/builder/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"label": "Builder",
"position": 1,
"collapsible": false,
"collapsed": false
}
95 changes: 95 additions & 0 deletions versioned_docs/version-0.16/builder/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# FAQ

## How is privacy implemented in Miden?

Miden leverages zero-knowledge proofs and client side execution and proving to provide security and privacy.

## Does Miden support encrypted notes?

At the moment, Miden does not have support for encrypted notes but it is a planned feature.

## Why does Miden have delegated proving?

Miden leverages delegated proving for a few technical and practical reasons:

1. **Computational:** Generating zero-knowledge proofs is a computationally intensive work. The proving process requires significant processing power and memory, making it impractical for some end-user devices (like smartphones) to generate.
2. **Technical architecture**:
Miden's architecture separates concerns between:
- **Transaction Creation**: End users create and sign transactions
- **Proof Generation**: Specialized provers generate validity proofs
- **Verification**: The network verifies these proofs
3. **Proving efficiency**:
Delegated provers can use optimized hardware that wouldn't be available to end-user devices, specifically designed for the mathematical operations needed in STARK proof generation.

## What is the lifecycle of a transaction?

For a client-executed transaction:

### 1. Transaction Creation

- User creates a transaction specifying the operations to perform (transfers, contract interactions, etc.)
- Client loads the account state and input notes needed for execution

### 2. Transaction Execution

- The Miden VM executes the note and transaction scripts locally
- The resulting state transitions and execution trace are computed
- During authentication, the account pays the protocol fee through a public `TX_FEE` note and authorizes the transaction summary

### 3. Proof Generation

- The client or a delegated prover generates a cryptographic proof attesting to the correctness of the execution

### 4. Transaction Submission

- The proven transaction, sealed inputs, and public state updates are submitted to Miden network nodes
- The node verifies the proof and admission rules before accepting the transaction into the mempool

### 5. Transaction Selection

- Accepted transactions are selected from the mempool
- Transactions are grouped into batches for inclusion in a block

### 6. Block Production

- Transaction batches and their proofs are assembled into a block
- The client synchronizes to observe the transaction's inclusion and updated state

### 7. L1 Submission

- Block proofs are aggregated for settlement
- The resulting proof and state commitments are submitted for L1 verification

### 8. Finalization

- L1 settlement establishes finality for the submitted state
- Inclusion in a Miden block does not by itself establish L1 finality

## Do notes in Miden support recency conditions?

Yes, Miden enables consumption of notes based on time conditions, such as:

- A specific block height being reached
- A timestamp threshold being passed
- An oracle providing specific data
- Another transaction being confirmed

## What does a Miden operator do in Miden?

A Miden operator is an entity that maintains the infrastructure necessary for the functioning of the Miden layer 2. Their roles may involve:

1. Running Sequencer Nodes
2. Operating the Prover Infrastructure
3. Submitting Proofs to L1
4. Maintaining Data Availability
5. Participating in the Consensus Mechanism

## How does bridging work in Miden?

Miden testnet applications can integrate [Agglayer or Epoch](./tools/bridging/).
Agglayer provides canonical bridge semantics for ETH, while Epoch provides a
faster quote-and-solve flow for test USDC. Both integrations are testnet-only.

## What does the gas fee model of Miden look like?

Miden does not meter gas linearly. Its transaction fee grows logarithmically with the transaction's estimated VM cycles. During authentication, the account pays by creating a public `TX_FEE` note that the batch builder can collect. See [Transaction Fees](./smart-contracts/transactions/fees) for the formula, payment assets, and network-account sponsorship fees.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "Get Started",
"position": 1,
"link": {
"type": "doc",
"id": "builder/get-started/index"
}
}
Loading
Loading