diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 96cf519ac4..4c1fd81563 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,117 +1,14 @@ # Contributing -Thank you for your interest in contributing to example apps for the Internet Computer. -By participating in this project, you agree to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). +Thank you for your interest in the Internet Computer examples. By participating in this project, you agree to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md). -As a member of the community, you are invited and encouraged to contribute by submitting issues, offering suggestions for improvements, adding review comments to existing pull requests, or creating new pull requests to fix issues. +## Current status: not accepting external pull requests -All contributions to DFINITY documentation and the developer community are respected and appreciated. -Your participation is an important factor in the success of the Internet Computer. +This repository is maintained by DFINITY. We are currently restructuring the examples to integrate them more closely with the [developer documentation](https://docs.internetcomputer.org), and are **not accepting external pull requests** — neither new examples nor changes to existing ones — during this period. External pull requests may be closed without review. -## Contents of this repository +## Reporting issues -This repository contains example apps built for the Internet Computer by the DFINITY team. The purpose of these example apps is to showcase useful features and use cases of both the Internet Computer and Motoko through sample code and accompanying documentation. It is our hope that these examples help and inspire you to build your own Internet Computer applications. +Bug reports and suggestions are welcome: -## Before you contribute - -Before contributing, please take a few minutes to review these contributor guidelines. -The contributor guidelines are intended to make the contribution process easy and effective for everyone involved in addressing your issue, assessing changes, and finalizing your pull requests. - -Before contributing, consider the following: - -- If you want to report an issue or request help, click **Issues**. - - You can also post a message to the [community forum](https://forum.dfinity.org/) or submit a [support request](mailto://support@dfinity.org). - -- If you are reporting a bug, provide as much information about the problem -as possible, including the name of the example app and SDK version. - -- If you want to contribute directly to this repository, typical fixes might include any of the following: - - - Bugs and errors when compiling the example apps - - Bugs and errors associated with front-end assets - - Code improvements - - Feature requests - - Note that any contribution to this repository must be submitted in the form of a **pull request**. - -- If you are creating a pull request, be sure that the pull request only implements one fix or suggestion. - -If you are new to working with GitHub repositories and creating pull requests, consider exploring [First Contributions](https://github.com/firstcontributions/first-contributions) or [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). - -# How to make a contribution - -Here's a summary of what you need to do: - -1. Make sure you have a GitHub account, an internet connection, and access to a terminal shell or GitHub Desktop application for running commands. - -1. Navigate to the DFINITY public repository in a web browser. - -1. Click **Fork** to create a copy the repository associated with the issue you want to address under your GitHub account or organization name. - -1. Clone the repository to your local machine. - -1. Create a new branch for your fix by running a command similar to the following: - - ``` - git checkout -b my-branch-name-here - ``` - -1. Open the file you want to fix in a text editor and make the appropriate changes for the issue you are trying to address. - -1. Add the file contents of the changed files to the index `git` uses to manage the state of the project by running a command similar to the following: - - ``` - git add path-to-changed-file - ``` -1. Commit your changes to store the contents you added to the index along with a descriptive message by running a command similar to the following: - - ``` - git commit -m "Description of the fix being committed." - ``` - -1. Push the changes to the remote repository by running a command similar to the following: - - ``` - git push origin my-branch-name-here - ``` - -1. Create a new pull request for the branch you pushed to the upstream GitHub repository. - - Provide a title that includes a short description of the changes made. - -1. Wait for the pull request to be reviewed. - -1. Make changes to the pull request, if requested. - -1. Celebrate your success after your pull request is merged! - -## Tips for contributing to example apps - -Depending on the type of contribution you want to make, you might follow a different workflow. -For example, if you are only interested in reporting an issue, there's no need to clone repository or set up a documentation environment. - -This section describes the most common workflow scenarios: - -- Reporting an issue -- Submitting your own example - -### Reporting an issue - -To open a new issue: - -1. Click **Issues**. - -1. Click **New Issue**. - -1. Click **Open a blank issue**. - -1. Type a title and description, then click **Submit new issue**. - - Be as clear and descriptive as possible. - - For any problem, describe it in detail, including details about the example app, the version of the code you are using, the results you expected, and how the actual results differed from your expectations. - -### Submitting your own example - -See [ADDING_AN_EXAMPLE.md](./../ADDING_AN_EXAMPLE.md) for guidelines on how to structure and submit a new example. +- Open a [GitHub issue](https://github.com/dfinity/examples/issues). For bugs, include the name of the example, your icp-cli version (`icp --version`), the result you expected, and what happened instead. +- Or post on the [community forum](https://forum.dfinity.org/). diff --git a/.github/workflows/pre-download-launcher.sh b/.github/workflows/pre-download-launcher.sh deleted file mode 100644 index d01958c369..0000000000 --- a/.github/workflows/pre-download-launcher.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# Pre-download the icp-cli network launcher to avoid GitHub API rate limits. -# icp-cli v0.1.0 doesn't support ICP_CLI_GITHUB_TOKEN yet, so `icp network start` -# hits the unauthenticated GitHub API rate limit (60 req/hr) when fetching the -# latest launcher version. This script uses the authenticated `gh` CLI to get -# the version, downloads the binary directly, and sets ICP_CLI_NETWORK_LAUNCHER_PATH -# so icp-cli skips the API call entirely. -# -# Requires: GH_TOKEN env var (for authenticated GitHub API access via gh CLI) -# -# This workaround can be removed once icp-cli supports ICP_CLI_GITHUB_TOKEN. -set -ex - -VERSION=$(gh release view --repo dfinity/icp-cli-network-launcher --json tagName -q .tagName) - -ARCH=$(uname -m) -case "$ARCH" in - arm64|aarch64) ARCH="arm64" ;; - x86_64) ARCH="x86_64" ;; - *) echo "Unsupported architecture: $ARCH"; exit 1 ;; -esac - -OS=$(uname -s) -case "$OS" in - Darwin) OS="darwin" ;; - Linux) OS="linux" ;; - *) echo "Unsupported OS: $OS"; exit 1 ;; -esac - -TARBALL="icp-cli-network-launcher-${ARCH}-${OS}-${VERSION}" -URL="https://github.com/dfinity/icp-cli-network-launcher/releases/download/${VERSION}/${TARBALL}.tar.gz" - -LAUNCHER_DIR="$HOME/.icp-cli-launcher" -mkdir -p "$LAUNCHER_DIR" -curl -sL "$URL" | tar xz -C "$LAUNCHER_DIR" - -LAUNCHER_PATH="${LAUNCHER_DIR}/${TARBALL}/icp-cli-network-launcher" -chmod +x "$LAUNCHER_PATH" - -echo "ICP_CLI_NETWORK_LAUNCHER_PATH=${LAUNCHER_PATH}" >> "$GITHUB_ENV" -echo "Network launcher ${VERSION} downloaded to ${LAUNCHER_PATH}" diff --git a/.github/workflows/provision-darwin.sh b/.github/workflows/provision-darwin.sh deleted file mode 100755 index 1e102aef39..0000000000 --- a/.github/workflows/provision-darwin.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -set -ex - -# Enter temporary directory. -pushd /tmp - -# Install Homebrew -curl --location --output install-brew.sh "https://raw.githubusercontent.com/Homebrew/install/master/install.sh" -bash install-brew.sh -rm install-brew.sh - -# Install Node. -version=${NODE_VERSION:=14.21.3} -curl --location --output node.pkg "https://nodejs.org/dist/v$version/node-v$version.pkg" -sudo installer -pkg node.pkg -store -target / -rm node.pkg - -# Install icp-cli -npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm - -# Install ic-mops (for Motoko projects) -npm install -g ic-mops - -# Install DFINITY SDK. -curl --location --output install-dfx.sh "https://raw.githubusercontent.com/dfinity/sdk/master/public/install-dfxvm.sh" -DFX_VERSION=${DFX_VERSION:=0.32.0} DFXVM_INIT_YES=true bash install-dfx.sh -rm install-dfx.sh -echo "$HOME/Library/Application Support/org.dfinity.dfx/bin" >> $GITHUB_PATH -source "$HOME/Library/Application Support/org.dfinity.dfx/env" -dfx cache install - -# check the current ic-commit found in the main branch, check if it differs from the one in this PR branch -# if so, update the dfx cache with the latest ic artifacts -if [ -f "${GITHUB_WORKSPACE}/.ic-commit" ]; then - stable_sha=$(curl https://raw.githubusercontent.com/dfinity/examples/master/.ic-commit) - current_sha=$(sed <"$GITHUB_WORKSPACE/.ic-commit" 's/#.*$//' | sed '/^$/d') - arch="x86_64-darwin" - if [ "$current_sha" != "$stable_sha" ]; then - export current_sha - export arch - sh "$GITHUB_WORKSPACE/.github/workflows/update-dfx-cache.sh" - fi -fi - -# Install ic-repl -version=0.7.7 -curl --location --output ic-repl "https://github.com/dfinity/ic-repl/releases/download/$version/ic-repl-macos" -mv ./ic-repl /usr/local/bin/ic-repl -chmod a+x /usr/local/bin/ic-repl - -# Install rust -curl --location --output install-rustup.sh "https://sh.rustup.rs" -bash install-rustup.sh -y -rustup target add wasm32-unknown-unknown - -# Install matchers -matchers_version=1.2.0 -curl -fsSLO "https://github.com/kritzcreek/motoko-matchers/archive/refs/tags/v${matchers_version}.tar.gz" -tar -xzf "v${matchers_version}.tar.gz" --directory "$(dfx cache show)" -rm "v${matchers_version}.tar.gz" -mv "$(dfx cache show)/motoko-matchers-${matchers_version}" "$(dfx cache show)/motoko-matchers" - -# Install wasmtime -wasmtime_version=0.33.1 -curl -fsSLO "https://github.com/bytecodealliance/wasmtime/releases/download/v${wasmtime_version}/wasmtime-v${wasmtime_version}-x86_64-macos.tar.xz" -mkdir -p "${HOME}/bin" -tar -xf "wasmtime-v${wasmtime_version}-x86_64-macos.tar.xz" --directory "${HOME}/bin/" -mv "${HOME}/bin/wasmtime-v${wasmtime_version}-x86_64-macos/wasmtime" "${HOME}/bin/wasmtime" -rm "wasmtime-v${wasmtime_version}-x86_64-macos.tar.xz" - -# Install wasi2ic -git clone https://github.com/wasm-forge/wasi2ic -cargo install --path wasi2ic --root "${HOME}" --locked - -# Install wasm-opt -version=117 -curl -fsSLO "https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_${version}-x86_64-macos.tar.gz" -tar -xzf "binaryen-version_${version}-x86_64-macos.tar.gz" --directory "${HOME}/" --strip-components 1 -rm "binaryen-version_${version}-x86_64-macos.tar.gz" - -# Exit temporary directory. -popd diff --git a/.github/workflows/provision-linux.sh b/.github/workflows/provision-linux.sh deleted file mode 100755 index a382f35690..0000000000 --- a/.github/workflows/provision-linux.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash - -set -ex - -# Enter temporary directory. -pushd /tmp - -# Install Node. -wget --output-document nodesource_setup.sh "https://deb.nodesource.com/setup_lts.x" -sudo bash nodesource_setup.sh -sudo apt-get install --yes nodejs -rm nodesource_setup.sh - -# Install icp-cli -npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm - -# Install ic-mops (for Motoko projects) -npm install -g ic-mops - -# Install DFINITY SDK. -wget --output-document install-dfx.sh "https://raw.githubusercontent.com/dfinity/sdk/master/public/install-dfxvm.sh" -DFX_VERSION=${DFX_VERSION:=0.32.0} DFXVM_INIT_YES=true bash install-dfx.sh -rm install-dfx.sh -echo "$HOME/.local/share/dfx/bin" >>$GITHUB_PATH -source "$HOME/.local/share/dfx/env" -dfx cache install -# check the current ic-commit found in the main branch, check if it differs from the one in this PR branch -# if so, update the dfx cache with the latest ic artifacts -if [ -f "${GITHUB_WORKSPACE}/.ic-commit" ]; then - stable_sha=$(curl https://raw.githubusercontent.com/dfinity/examples/master/.ic-commit) - current_sha=$(sed <"$GITHUB_WORKSPACE/.ic-commit" 's/#.*$//' | sed '/^$/d') - arch="x86_64-linux" - if [ "$current_sha" != "$stable_sha" ]; then - export current_sha - export arch - sh "$GITHUB_WORKSPACE/.github/workflows/update-dfx-cache.sh" - fi -fi - -# Install ic-repl -version=0.7.7 -curl --location --output ic-repl "https://github.com/dfinity/ic-repl/releases/download/$version/ic-repl-linux64" -mv ./ic-repl /usr/local/bin/ic-repl -chmod a+x /usr/local/bin/ic-repl - -# Install cmake -sudo apt-get install --yes cmake - -# Install rust -wget --output-document install-rustup.sh "https://sh.rustup.rs" -sudo bash install-rustup.sh -y -rustup target add wasm32-unknown-unknown - -# Install matchers -matchers_version=1.2.0 -curl -fsSLO "https://github.com/kritzcreek/motoko-matchers/archive/refs/tags/v${matchers_version}.tar.gz" -tar -xzf "v${matchers_version}.tar.gz" --directory "$(dfx cache show)" -rm "v${matchers_version}.tar.gz" -mv "$(dfx cache show)/motoko-matchers-${matchers_version}" "$(dfx cache show)/motoko-matchers" - -# Install wasmtime -wasmtime_version=0.33.1 -curl -fsSLO "https://github.com/bytecodealliance/wasmtime/releases/download/v${wasmtime_version}/wasmtime-v${wasmtime_version}-x86_64-linux.tar.xz" -mkdir -p "${HOME}/bin" -tar -xf "wasmtime-v${wasmtime_version}-x86_64-linux.tar.xz" --directory "${HOME}/bin/" -mv "${HOME}/bin/wasmtime-v${wasmtime_version}-x86_64-linux/wasmtime" "${HOME}/bin/wasmtime" -rm "wasmtime-v${wasmtime_version}-x86_64-linux.tar.xz" - -# Install wasi2ic -git clone https://github.com/wasm-forge/wasi2ic -cargo install --path wasi2ic --root "${HOME}" --locked - -# Install wasm-opt -version=117 -curl -fsSLO "https://github.com/WebAssembly/binaryen/releases/download/version_117/binaryen-version_${version}-x86_64-linux.tar.gz" -tar -xzf "binaryen-version_${version}-x86_64-linux.tar.gz" --directory "${HOME}/" --strip-components 1 -rm "binaryen-version_${version}-x86_64-linux.tar.gz" - -# Set environment variables. -echo "$HOME/bin" >>$GITHUB_PATH -echo "$HOME/.cargo/bin" >>$GITHUB_PATH - -# Exit temporary directory. -popd diff --git a/.github/workflows/provision-pocket-ic-server.sh b/.github/workflows/provision-pocket-ic-server.sh deleted file mode 100755 index 3d4c10186f..0000000000 --- a/.github/workflows/provision-pocket-ic-server.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -set -ex - -POCKET_IC_SERVER_VERSION=${POCKET_IC_SERVER_VERSION:=6.0.0} -POCKET_IC_SERVER_PATH=${POCKET_IC_SERVER_PATH:="${HOME}/bin/pocket-ic-server"} - -if [[ $OSTYPE == "linux-gnu"* ]] || [[ $RUNNER_OS == "Linux" ]] -then - PLATFORM=linux -elif [[ $OSTYPE == "darwin"* ]] || [[ $RUNNER_OS == "macOS" ]] -then - PLATFORM=darwin -else - echo "OS not supported: ${OSTYPE:-$RUNNER_OS}" - exit 1 -fi - -if [ ! -f "$POCKET_IC_SERVER_PATH" ]; then - echo "Downloading PocketIC." - mkdir -p "$(dirname "${POCKET_IC_SERVER_PATH}")" - curl -sSL "https://github.com/dfinity/pocketic/releases/download/${POCKET_IC_SERVER_VERSION}/pocket-ic-x86_64-${PLATFORM}.gz" -o "${POCKET_IC_SERVER_PATH}".gz - gunzip "${POCKET_IC_SERVER_PATH}.gz" - chmod +x "${POCKET_IC_SERVER_PATH}" -else - echo "PocketIC server already exists, skipping download." -fi - -# Set environment variables. -echo "POCKET_IC_BIN=${POCKET_IC_SERVER_PATH}" >> "$GITHUB_ENV" diff --git a/.github/workflows/update-dfx-cache.sh b/.github/workflows/update-dfx-cache.sh deleted file mode 100755 index 3f487a093a..0000000000 --- a/.github/workflows/update-dfx-cache.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# Download latest ic artifacts -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/replica.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/canister_sandbox.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/compiler_sandbox.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/ic-admin.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/ic-btc-adapter.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/ic-https-outcalls-adapter.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/ic-nns-init.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/ic-starter.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/sandbox_launcher.gz" -curl -O "https://download.dfinity.systems/ic/$current_sha/binaries/$arch/sns.gz" - -# Overwrite artifacts in dfx cache -gzip -d replica.gz && chmod +x replica && mv replica $(dfx cache show) -gzip -d canister_sandbox.gz && chmod +x canister_sandbox && mv canister_sandbox $(dfx cache show) -gzip -d compiler_sandbox.gz && chmod +x compiler_sandbox && mv compiler_sandbox $(dfx cache show) -gzip -d ic-starter.gz && chmod +x ic-starter && mv ic-starter $(dfx cache show) -gzip -d sandbox_launcher.gz && chmod +x sandbox_launcher && mv sandbox_launcher $(dfx cache show) -gzip -d ic-admin.gz && chmod +x ic-admin && mv ic-admin $(dfx cache show) -gzip -d ic-btc-adapter.gz && chmod +x ic-btc-adapter && mv ic-btc-adapter $(dfx cache show) -gzip -d ic-https-outcalls-adapter.gz && chmod +x ic-https-outcalls-adapter && mv ic-https-outcalls-adapter $(dfx cache show) -gzip -d ic-nns-init.gz && chmod +x ic-nns-init && mv ic-nns-init $(dfx cache show) -gzip -d sns.gz && chmod +x sns && mv sns $(dfx cache show) - -echo "dfx cache updated with latest ic artifacts" diff --git a/.github/workflows/update-ic-commit.yml b/.github/workflows/update-ic-commit.yml deleted file mode 100644 index 652c17d864..0000000000 --- a/.github/workflows/update-ic-commit.yml +++ /dev/null @@ -1,86 +0,0 @@ -# A GitHub Actions workflow that regularly creates a pull request to update the IC artifacts -name: IC Artifacts Update - -on: - workflow_dispatch: - schedule: - # create a new pull request every monday - - cron: '0 0 * * MON' - -jobs: - ic-update: - runs-on: ubuntu-latest - steps: - - name: Create GitHub App Token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 - id: app-token - with: - app-id: ${{ vars.PR_AUTOMATION_BOT_PUBLIC_APP_ID }} - private-key: ${{ secrets.PR_AUTOMATION_BOT_PUBLIC_PRIVATE_KEY }} - - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - # First, check if there is a newer version and update the file referencing the version - - name: Check new ic version - id: update - run: | - # Not all ic commits are built and released, so we go through the last commits until we found one - # which has associated artefacts - while read -r sha - do - echo "sha: $sha" - # Send a HEAD to the URL to see if it exists - if curl --fail --head --silent --location \ - "https://download.dfinity.systems/ic/$sha/binaries/x86_64-linux/pocket-ic.gz" - then - echo "$sha appears to have associated binary, using" - latest_sha="$sha" - break - else - echo "$sha does not seem to have associated binary" - fi - done < <(curl \ - -SsL \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/dfinity/ic/commits | jq -cMr '.[] | .sha') - - # If we couldn't find any sha with associated artefacts, abort - if [ -z "${latest_sha:-}" ] - then - echo no sha found - exit 1 - fi - - # Compare the current and latest shas, and potentially update the relevant files - current_sha=$(sed <.ic-commit 's/#.*$//' | sed '/^$/d') - - echo current sha is "$current_sha" - - if [ "$current_sha" != "$latest_sha" ]; then - echo "updating $current_sha to $latest_sha" - sed -i -e \ - "s/$current_sha/$latest_sha/g" \ - ".ic-commit" - echo "updated=1" >> "$GITHUB_OUTPUT" - else - echo "not updating $current_sha" - echo "updated=0" >> "$GITHUB_OUTPUT" - fi - - cat ".ic-commit" - - # If the ic commit was updated, create a PR. - - name: Create Pull Request - if: ${{ steps.update.outputs.updated == '1' }} - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0 - with: - token: ${{ steps.app-token.outputs.token }} - base: master - add-paths: ./.ic-commit - commit-message: Update commit of IC artifacts - committer: GitHub - author: SDK - branch: bot-ic-update - delete-branch: true - title: 'Update commit of IC artifacts' diff --git a/.gitignore b/.gitignore index 65fe9f4a95..ecffc1344c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,35 @@ -.dfx/ +# icp-cli — ignore only the ephemeral build cache; **/.icp/data/ is committed +# (it holds the canister ID mappings) **/.icp/cache/ -build/ + +# Motoko / mops +.mops/ +mops.lock + +# Rust +target/ +Cargo.lock + +# Node.js / frontends node_modules/ package-lock.json -Cargo.lock dist/ -.DS_Store -_MACOSX -.vscode/ -target/ +build/ + +# Generated canister bindings and Candid artifacts +**/src/bindings/ +**/src/declarations/ *.old.did -.idea -.mops -mops.lock + +# Local environment .env -**/src/declarations/ -**/src/bindings/ + +# IDEs and editors +.vscode/ +.idea/ + +# OS +.DS_Store + +# ICP skills — these can be reinstalled with npx skills +.agents/skills diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 3063118f03..0000000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "QR-Code-generator"] - path = archive/c/qr/QR-Code-generator - url = https://github.com/nayuki/QR-Code-generator - branch = fd6917601d0a77b6b6df81599118212d8cdb9a27 diff --git a/.ic-commit b/.ic-commit deleted file mode 100644 index 19af04b1ab..0000000000 --- a/.ic-commit +++ /dev/null @@ -1 +0,0 @@ -b0aef30f10784189a5ce73e58ed1f19359a5a3ae diff --git a/ADDING_AN_EXAMPLE.md b/ADDING_AN_EXAMPLE.md deleted file mode 100644 index 34197c3627..0000000000 --- a/ADDING_AN_EXAMPLE.md +++ /dev/null @@ -1,49 +0,0 @@ -# How to add a new example - -## Purpose and acceptance criteria - -Examples in this repository have an **educational focus**. Each example is intended to demonstrate a specific ICP capability or pattern, and should be suitable for reference from the [ICP developer documentation](https://docs.internetcomputer.org). - -**We do not accept general-purpose or arbitrary examples.** Before opening a PR, confirm that: - -- The example demonstrates a distinct ICP concept not already covered by an existing example. -- There is a clear home for it in the developer documentation (an existing or planned guide, tutorial, or reference page). -- The DFINITY DX team has agreed to maintain it long-term. - -If you are unsure whether your example fits, open an issue first to discuss it with the maintainers before investing time in an implementation. - ---- - -Each example should be available in both Rust and Motoko variations, implementing the same Candid interface (and, ideally, semantics). - -To illustrate the pattern, this repo contains one such example, project `hello_world`: - -``` -motoko/hello_world -rust/hello_world -``` - -When adding a new project, make sure to delete any generated GitHub metadata files (`.gitignore`, `.git` etc). - -Each project should include a language-specific README.md that also links to the corresponding README.md of its counterpart in another language, making it easy for language-curious readers to explore both implementations. - -## CI - -Each project should provide a `Makefile` with a `test` target that runs basic canister tests using `icp canister call`. Each example also needs a GitHub Actions workflow file at `.github/workflows/.yml`. - -Use the workflow template as a starting point: - -``` -.github/workflow-template.yml -``` - -Copy it, replace the placeholders, and add the appropriate container image: - -- Motoko: `ghcr.io/dfinity/icp-dev-env-motoko` -- Rust: `ghcr.io/dfinity/icp-dev-env-rust` - -See `hello_world` and `who_am_i` for reference implementations. Workflows run on Linux only using container images — no provision scripts needed. - -## Notes - -While this structure leads to some duplication (especially shared frontend code) it ensures that Motoko users can focus solely on Motoko-specific content, and likewise for Rust users. It also enables easily finding language-specific examples when a given use case is not easily supported in the other language. diff --git a/AGENTS.md b/AGENTS.md index 536ce30c45..7984899c47 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,24 +2,19 @@ Guidelines for AI agents (Claude, Codex, Cursor, Copilot, etc.) working in this repository. -## Skills — fetch before working +## ICP Skills -ICP skills are live reference documents maintained by DFINITY. Always fetch the relevant skills **before** making changes — do not rely on cached or training-data versions. +ICP skills are tested, frequently-updated instruction files maintained by DFINITY (see https://skills.internetcomputer.org). Consult the relevant skills **before** making changes — do not rely on training-data knowledge of ICP tooling. -**Index:** `https://skills.internetcomputer.org/.well-known/skills/index.json` - -Fetch the skill content at its `url` field. Skills relevant to this repo: - -| Task | Skill to fetch | -|------|---------------| -| Any ICP project work, `icp.yaml`, canister lifecycle | `icp-cli` | -| Migrating an example from dfx to icp-cli | `icp-cli` + its `references/dfx-migration.md` file | -| Motoko canister code | `motoko` | -| `mops.toml`, toolchain pinning, moc flags | `mops-cli` | -| Internet Identity integration | `internet-identity` | -| Frontend asset canister | `asset-canister` | - -Skills take precedence over general knowledge when both cover the same topic. + + +ICP skills are version-locked in this repo (skills-lock.json) and live in your +agent skills directory. Skills are authoritative — prefer them over general +knowledge for all ICP work. Before your first task in a new session, offer to run +`npx skills update`; if the user declines or the session is non-interactive, keep +the locked versions and continue — never block. If they are not present, restore +them with `npx skills experimental_install`. + --- @@ -32,42 +27,53 @@ motoko// rust// ``` -Both implement the same Candid interface so readers can compare language implementations side by side. +Both implement the same Candid interface so readers can compare language implementations side by side. `hosting/` contains frontend-only examples. Reference examples to study first: -- `hello_world` — canonical full-stack example (Motoko backend + Vite frontend); use as the structural template for new examples +- `hello_world` — canonical full-stack example (backend + Vite frontend); use as the structural template for new examples - `who_am_i` — Internet Identity integration; reference for II-authenticated examples ---- +### Adding a new example + +Examples have an **educational focus** — general-purpose or arbitrary examples are not accepted. Before adding one, confirm that: + +- It demonstrates a distinct ICP capability or pattern not already covered by an existing example. +- It has a clear home in the [developer documentation](https://docs.internetcomputer.org) (an existing or planned guide, tutorial, or reference page). +- The DFINITY DX team has agreed to maintain it long-term. -## Toolchain +Ship both language variants (Motoko and Rust) with the same Candid interface. The per-language duplication — including shared frontend code — is deliberate: it keeps each variant self-contained for readers of that language. -- **Always use `icp-cli`** for all ICP operations. Never use `dfx`. -- CLI docs: https://cli.internetcomputer.org -- ICP developer docs: https://docs.internetcomputer.org +**Toolchain**: always use `icp-cli` for all ICP operations — never `dfx`, and never add dfx artifacts (`dfx.json`, `.dfx/`, dfx-generated `.env`) to an example. +CLI docs: https://cli.internetcomputer.org · ICP developer docs: https://docs.internetcomputer.org + +### Versions + +This document deliberately does not pin library, recipe, or toolchain versions — any `vX.Y.Z` in a snippet is a placeholder. When creating or updating an example, take current versions (recipe versions in `icp.yaml`, `moc`/`core` in `mops.toml`, crate versions in `Cargo.toml`, npm packages) from the most recently updated examples in this repo (check `git log`), preferring the newest version already in use. When in doubt, consult the installed ICP skills. + +When asked to bump a specific version — whether a library dependency or an `icp.yaml` recipe (`@dfinity/motoko`, `@dfinity/rust`, `@dfinity/static-site`, ...) — clarify the scope first: all examples, or only the one named? The goal is that every example stays consistent and up to date with the same versions, so prefer repo-wide bumps unless told otherwise. --- ## Canonical example structure -Follow the `hello_world` layout. New examples and migrations should use this pattern: +Follow the `hello_world` layout: ``` // ├── icp.yaml # canister definitions (icp-cli project file) ├── test.sh # executable bash test script ├── README.md -├── package.json # npm workspaces root pointing to frontend/ +├── package.json # npm workspaces root pointing to frontend/ (only if frontend exists) ├── mops.toml # Motoko only ├── Cargo.toml # Rust only (workspace) ├── rust-toolchain.toml # Rust only ├── backend/ │ ├── app.mo or lib.rs # canister entry point -│ └── backend.did # Candid interface (source of truth) +│ └── backend.did # Candid interface (only if a frontend consumes it) └── frontend/ ├── index.html ├── package.json - ├── vite.config.js + ├── vite.config.js # includes @icp-sdk/bindgen vite plugin ├── src/ │ ├── actor.js # icp-sdk actor wiring │ ├── App.jsx @@ -75,24 +81,20 @@ Follow the `hello_world` layout. New examples and migrations should use this pat └── dist/ # build output (gitignored, rebuilt by icp deploy) ``` -> **Note:** `who_am_i` uses `src/backend/` and `src/frontend/` for historical reasons. -> New examples and migrations should use the flat `backend/` / `frontend/` layout above. +> `who_am_i` uses `src/backend/` and `src/frontend/` for historical reasons. New examples use the flat `backend/` / `frontend/` layout above. -### What NOT to include +### What NOT to commit -- `dfx.json` — dfx project file, not used with icp-cli -- `BUILD.md` — ICP Ninja artifact -- `.dfx/` — dfx state directory -- `src/bindings/` — auto-generated by the bindgen Vite plugin, must be gitignored -- Committed `dist/` output — built by `icp deploy`; never commit pre-built assets +- dfx artifacts (`dfx.json`, `.dfx/`) or ICP Ninja artifacts (`BUILD.md`) +- `frontend/src/bindings/` — auto-generated by the bindgen Vite plugin, must be gitignored +- `dist/` output — built by `icp deploy`; never commit pre-built assets +- Per-example `.devcontainer/` — only the repo-root devcontainer exists (see below) ### What NOT to gitignore -- `.icp/data/` — **always commit this**. It holds canister ID mappings (e.g. `local.ids.json`) that map canister names to on-chain principals. Losing this means losing the link between the example's code and its deployed canisters. +- `.icp/data/` — **always commit this**. It holds canister ID mappings (e.g. `local.ids.json`) that map canister names to on-chain principals. -The root `.gitignore` already has `**/.icp/cache/` which correctly ignores only the ephemeral build cache. Do **not** add `.icp/` to per-example `.gitignore` files — it would incorrectly hide `data/` as well. - -When migrating an example, also remove any existing `.dfx/` entries from per-example `.gitignore` files — dfx is no longer used and these entries are dead weight. +The root `.gitignore` already has `**/.icp/cache/`, which correctly ignores only the ephemeral build cache. Do **not** add `.icp/` to per-example `.gitignore` files — it would incorrectly hide `data/` as well. --- @@ -109,11 +111,11 @@ networks: # omit if no Internet Identity needed canisters: - name: backend recipe: - type: "@dfinity/motoko@v5.0.0" + type: "@dfinity/motoko@vX.Y.Z" - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/static-site@vX.Y.Z" configuration: dir: frontend/dist build: @@ -121,20 +123,22 @@ canisters: - npm run build --prefix frontend ``` +The Motoko recipe reads its configuration (`main`, `candid`, `args`) from the `[canisters.]` section of `mops.toml`, so the Motoko canister needs no `configuration:` block here. + ### Rust ```yaml canisters: - name: backend recipe: - type: "@dfinity/rust@v3.2.0" + type: "@dfinity/rust@vX.Y.Z" configuration: package: backend candid: backend/backend.did # omit for backend-only examples (no frontend) - name: frontend recipe: - type: "@dfinity/asset-canister@v2.2.1" + type: "@dfinity/static-site@vX.Y.Z" configuration: dir: frontend/dist build: @@ -142,64 +146,72 @@ canisters: - npm run build --prefix frontend ``` -- With `candid:` specified: the recipe reads the committed `.did` file and embeds it as WASM metadata (no `candid-extractor` needed). -- Without `candid:`: `candid-extractor` extracts the interface directly from the compiled WASM. For backend-only examples, omit `candid:` and do not commit `backend.did`. +- With `candid:`: the recipe reads the committed `.did` file and embeds it as WASM metadata. +- Without `candid:`: `candid-extractor` extracts the interface from the compiled WASM. For backend-only examples, omit `candid:` and do not commit `backend.did`. + +### Canister naming -**Canister names are always `backend` and `frontend`.** Never use names like `_backend`, `internet_identity_app_backend`, etc. +- The standard pair is **`backend`** and **`frontend`**. Never use names like `_backend`. +- Multi-canister examples use short role names instead (e.g. `caller`/`callee`, `publisher`/`subscriber`, `token_a`/`token_b`). -### Per-environment canister configuration +### Environments -When an example interacts with a well-known external canister whose principal differs by environment (e.g. the ICP ledger mainnet vs. TESTICP on staging), use the `environments` block to inject the right value per deployment rather than hardcoding it: +Examples define at most **two** environments, always named exactly: + +- **`local`** — the managed local network, used for development and CI. +- **`ic`** — mainnet. The name `ic` is a contract with ICP Ninja: projects deploy successfully from Ninja only if the mainnet environment carries this exact name. + +Never add a `staging` (or any other) environment by default — developers can extend their own copies, but examples ship with only these two. Self-contained examples that behave identically everywhere need no `environments` block at all. + +Use the block when local and mainnet deployments differ. The common pattern: a companion canister (mock or local instance) is deployed locally, while on mainnet an already-running well-known canister is used instead — the `ic` environment restricts the canister list and injects the principal via an environment variable: ```yaml canisters: - name: backend recipe: - type: "@dfinity/rust@v3.3.0" + type: "@dfinity/rust@vX.Y.Z" + + - name: xrc # mock, deployed locally only + build: + steps: + - type: pre-built + url: https://github.com/dfinity/exchange-rate-canister/releases/download//xrc_mock.wasm.gz environments: + # Local: deploys backend and the mock; icp-cli auto-injects + # PUBLIC_CANISTER_ID:xrc into the backend after deploying xrc. - name: local network: local - settings: - backend: - environment_variables: - ICP_LEDGER_CANISTER_ID: "ryjl3-tyaaa-aaaaa-aaaba-cai" - - - name: staging - network: ic - settings: - backend: - environment_variables: - ICP_LEDGER_CANISTER_ID: "xafvr-biaaa-aaaai-aql5q-cai" # TESTICP - - name: production + # Mainnet: deploys only the backend; the production XRC canister already exists. + - name: ic network: ic + canisters: [backend] settings: backend: environment_variables: - ICP_LEDGER_CANISTER_ID: "ryjl3-tyaaa-aaaaa-aaaba-cai" + "PUBLIC_CANISTER_ID:xrc": "uf6dk-hyaaa-aaaaq-qaaaq-cai" ``` -icp-cli applies these as canister settings at deploy time. Read them at **runtime** via `ic_cdk::api::env_var_value("ICP_LEDGER_CANISTER_ID")` (Rust) or `Runtime.envVar("ICP_LEDGER_CANISTER_ID")` (Motoko) — not via `env!()` or `std::env::var()`. +Per-environment `init_args` follow the same shape (see `basic_bitcoin`: `regtest` locally, `testnet` on `ic`). -**When to apply this pattern**: judge whether staging/production environments make sense for the specific example. Apply it when the example hardcodes an external canister principal that has a test counterpart (TESTICP, testnet URLs, staging governance canisters, etc.). Skip it for self-contained examples that don't call external canisters. +icp-cli applies `environment_variables` as canister settings at deploy time. Read them at **runtime**: -### Motoko naming conventions - -- **Top-level actor:** name it after its logical role, not generically — e.g. `actor TodoList`, `actor CanisterFactory`, not `actor Backend`. -- **Supporting module files:** use PascalCase matching the type they export — e.g. `Counter.mo` exporting `actor class Counter`, `Types.mo` exporting `type X`. -- **Entry point file:** always `backend/app.mo` regardless of actor name. +- Motoko: `Runtime.envVar("PUBLIC_CANISTER_ID:xrc")` +- Rust: `ic_cdk::api::env_var_value("PUBLIC_CANISTER_ID:xrc")` — never `env!()` (compile-time) or `std::env::var()` (no OS environment in WASM) --- -## mops.toml (Motoko) +## Motoko conventions + +### mops.toml ```toml [toolchain] -moc = "1.9.0" +moc = "X.Y.Z" [dependencies] -core = "2.5.0" +core = "X.Y.Z" [moc] # M0236: use context dot notation @@ -212,39 +224,58 @@ main = "backend/app.mo" candid = "backend/backend.did" # omit for backend-only examples (no frontend) ``` -`[canisters.]` replaces the `main`, `candid`, and `args` fields that were previously in `icp.yaml`. The `@dfinity/motoko@v5.0.0` recipe reads this section directly, so the Motoko canister needs no `configuration:` block in `icp.yaml` — the `` must match the canister `name` in `icp.yaml`. +The `[canisters.]` name must match the canister `name` in `icp.yaml`. -After writing or editing Motoko source files, always run: +After writing or editing Motoko source files, always run — both must pass before committing: ```bash mops check # type-check all canister entry points -mops check --fix # auto-fix style warnings (M0236 dot notation, M0237, M0223) +mops check --fix # auto-fix style warnings (M0236, M0237, M0223) ``` -Both commands must pass with no errors before committing. +`--default-persistent-actors` makes the **main actor** persistent by default, so the `persistent` keyword is omitted on the top-level `actor` declaration. `persistent actor class` declarations holding mutable state must still carry the keyword explicitly — the flag does not propagate into actor class sub-WASMs. -`--default-persistent-actors` makes the **main actor** persistent by default, so the `persistent` keyword can be omitted on the top-level `actor` declaration. However, `persistent actor class` declarations that hold mutable state must still carry the `persistent` keyword explicitly — the flag does not propagate into actor class sub-WASMs. +### Naming -### Management canister (Motoko) +- **Top-level actor:** name it after its logical role — e.g. `actor TodoList`, `actor CanisterFactory`, not `actor Backend`. +- **Supporting module files:** PascalCase matching the type they export — e.g. `Counter.mo` exporting `actor class Counter`. +- **Entry point file:** always `backend/app.mo` (or `/app.mo` in multi-canister examples) regardless of actor name. -Use the [`mo:ic`](https://mops.one/ic) mops package instead of `ic:aaaaa-aa` or inline `actor("aaaaa-aa")` definitions: +### Inter-canister calls + +For calls between canisters in the same project (and to well-known external canisters), use a `canister:` import wired via `--actor-env-alias` — do not hand-write actor types or use `actor(...)` casts: + +```motoko +import Callee "canister:callee"; +``` ```toml -[dependencies] -ic = "4.0.0" +[canisters.caller] +main = "caller/app.mo" +# Per-canister args REPLACE the global [moc].args — repeat the shared flags. +args = [ + "--default-persistent-actors", + "-W=M0236,M0237,M0223", + "--actor-env-alias", "callee", "PUBLIC_CANISTER_ID:callee", "callee/callee.did", +] ``` +`icp deploy` injects the `PUBLIC_CANISTER_ID:` environment variable; the import is typed against the committed `.did` file of the target canister. See `parallel_calls` and `pub-sub` for working setups. + +### Management canister + +Use the [`mo:ic`](https://mops.one/ic) mops package instead of `ic:aaaaa-aa` or inline `actor("aaaaa-aa")` definitions: + ```motoko import { ic } "mo:ic"; ``` -**Breaking change in `mo:ic` v4.0.0:** `CanisterSettings` gained two new required fields. Always include `environment_variables = null` and `snapshot_visibility = null` in settings records passed to `ic.create_canister`. - --- -## Cargo.toml (Rust) +## Rust conventions + +Root workspace `Cargo.toml`: -Root workspace: ```toml [workspace] members = ["backend"] @@ -252,6 +283,7 @@ resolver = "2" ``` `backend/Cargo.toml`: + ```toml [package] name = "backend" @@ -260,56 +292,16 @@ edition = "2024" [lib] crate-type = ["cdylib"] - -[dependencies] -candid = "0.10" -ic-cdk = "0.20" -``` - -Always use `ic-cdk = "0.20"` unless a dependency forces a lower version. When using `ic-ledger-types`, use `"0.16"` (requires `ic-cdk = "^0.19"`) — version `0.15` pins `ic-cdk = "^0.18"` which conflicts with `0.20` via an `ic-cdk-executor` `links` constraint. - -**`ic_cdk::export_candid!()` is required** at the end of every Rust canister `lib.rs`. Without it, `candid-extractor` cannot find the `get_candid_pointer` export and the build fails. - -**CI image**: use `ghcr.io/dfinity/icp-dev-env-rust:1.0.1` or later. Earlier images bundle `candid-extractor 0.1.4` which fails with `Error: unknown import: ic0::cost_call` for any WASM compiled with ic-cdk ≥ 0.19. - -### Environment variables in Rust canisters - -Use `ic_cdk::api::env_var_value("VAR_NAME")` to read canister environment variables at **runtime**. These are canister settings applied by icp-cli at deploy time, not WASM metadata. This is the Rust equivalent of `Runtime.envVar` in Motoko: - -```rust -fn ledger_principal() -> Principal { - Principal::from_text(ic_cdk::api::env_var_value("ICP_LEDGER_CANISTER_ID")) - .expect("invalid ICP_LEDGER_CANISTER_ID") -} -``` - -Do **not** use `env!()` (compile-time macro, fails if the var is not set during `cargo build`) or `std::env::var()` (no OS environment in WASM). - -### Management canister (Rust) - -Use the [`ic-cdk-management-canister`](https://crates.io/crates/ic-cdk-management-canister) crate instead of `ic_cdk::api::management_canister` (removed in ic-cdk 0.17+): - -```toml -[dependencies] -ic-cdk-management-canister = "0.1.1" ``` -```rust -use ic_cdk_management_canister::raw_rand; - -#[ic_cdk::update] -async fn get_randomness() -> Vec { - raw_rand().await.expect("raw_rand failed") -} -``` +- **`ic_cdk::export_candid!()` is required** at the end of every canister `lib.rs`. Without it, `candid-extractor` cannot find the `get_candid_pointer` export and the build fails. +- **Management canister:** use the [`ic-cdk-management-canister`](https://crates.io/crates/ic-cdk-management-canister) crate (e.g. `use ic_cdk_management_canister::raw_rand;`) — `ic_cdk::api::management_canister` was removed in ic-cdk 0.17+. --- ## test.sh -Every example must have a `test.sh` bash script that exercises the deployed canister via `icp canister call`. Write a numbered test for every public function. Include state-assertion tests for mutating operations: call the mutating function, then call a read function and assert the stored value changed. - -Using `test.sh` instead of `Makefile` avoids Make-specific syntax pitfalls (`$$`, `\\` continuations, `@` prefix) and works natively in Git Bash on Windows. +Every example has an executable `test.sh` that exercises the deployed canister via `icp canister call`. Write a numbered test for every public function. For mutating operations, also assert state: call the mutating function, then a read function, and check the stored value changed. ```bash #!/usr/bin/env bash @@ -319,135 +311,54 @@ echo "=== Test 1: ===" result=$(icp canister call backend '') echo "$result" echo "$result" | grep -q '' && echo "PASS" || (echo "FAIL" && exit 1) - -echo "=== Test 2: ===" -result=$(icp canister call backend '') -echo "$result" -echo "$result" | grep -q '' && echo "PASS" || (echo "FAIL" && exit 1) - -echo "=== Test 3: ===" -result=$(icp canister call backend '()') -echo "$result" -echo "$result" | grep -q '' && echo "PASS" || (echo "FAIL" && exit 1) -``` - -- Tests must call the `backend` canister by that name. -- Always pass explicit Candid args, including `'()'` for zero-argument calls — omitting args triggers an interactive prompt that blocks CI. -- Use `grep -q` to assert on output content. -- Number each test (`=== Test N: ... ===`) so CI logs are easy to scan. -- For examples that create child canisters, capture the returned principal and call the child directly by ID. -- **Query functions**: always pass `--query` to `icp canister call` for `public query func` and `public composite query func` methods. -- **Balance checks**: use delta-based assertions (record before, act, assert delta) rather than checking absolute values — this keeps tests idempotent across re-runs regardless of prior state: - -```bash -before=$(icp canister call backend get_balance '()' | grep -oE '[0-9_]+' | tr -d '_' | head -1) -icp token transfer 1 "$account_hex" -after=$(icp canister call backend get_balance '()' | grep -oE '[0-9_]+' | tr -d '_' | head -1) -delta=$((after - before)) -[ "$delta" -eq 100000000 ] && echo "PASS" || (echo "FAIL: expected +100000000 e8s" && exit 1) -``` - -- **Async/time-dependent behavior**: if the observable result depends on timers, heartbeats, or polling, use a polling loop: - -```bash -echo "=== Polling for (up to 60s) ===" -secs=0 -while [ "$secs" -lt 60 ]; do - result=$(icp canister call --query backend '()') - echo "$result" - echo "$result" | grep -q '' && echo "PASS" && exit 0 - sleep 3 - secs=$((secs + 3)) -done -echo "FAIL: condition not met within 60s"; exit 1 ``` -- **Canister settings**: if the example requires non-default canister settings (e.g. `wasm_memory_limit`, `wasm_memory_threshold`), apply them at the top of `test.sh` before the tests: - -```bash -icp canister settings update backend -- -f -``` - -- If `icp deploy --cycles 30t` is required (see below), document `icp canister top-up --amount 30t backend` in the README so users can replenish cycles when needed. (`30t` is an example amount — adjust to the example's actual consumption.) - ---- - -## Devcontainer config - -A single root devcontainer at `.devcontainer/devcontainer.json` covers local VS Code usage across all examples. It uses `ghcr.io/dfinity/icp-dev-env-all:` (Motoko + Rust) with `workspaceFolder` set to `/workspaces/examples`. No per-example devcontainer configs exist — do not add them. +- Call canisters by their `icp.yaml` name (`backend`). +- Always pass explicit Candid args, including `'()'` for zero-argument calls — omitting args triggers an interactive prompt that blocks CI. This applies especially when calling dynamically created canisters by principal. +- Always pass `--query` for `public query func` and `public composite query func` methods. +- Use `grep -q` to assert on output and number each test (`=== Test N: ... ===`) so CI logs are easy to scan. +- For examples that create child canisters, capture the returned principal and call the child directly by ID. Deploy with `icp deploy --cycles ` (in CI and README) so the parent can fund children, and document `icp canister top-up --amount backend` in the README. +- **Balance checks:** use delta-based assertions (record before, act, assert the delta) rather than absolute values — keeps tests idempotent across re-runs. +- **Async/time-dependent behavior** (timers, heartbeats): poll in a loop with a timeout instead of a fixed `sleep`. +- **Non-default canister settings** (e.g. `wasm_memory_limit`): apply at the top of `test.sh` via `icp canister settings update backend -- -f`. --- ## CI workflow -Copy `.github/workflow-template.yml` to `.github/workflows/.yml` and fill in the placeholders. A single workflow file covers both language variants: +Copy `.github/workflow-template.yml` to `.github/workflows/.yml` and fill in the placeholders. One workflow file covers all language variants of an example; each job calls the reusable `_run-example.yml` workflow: ```yaml -name: - -on: - push: - branches: [master] - pull_request: - paths: - - motoko//** - - rust//** - - .github/workflows/.yml - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: - motoko-: - runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-motoko:1.0.1 - env: - ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy and test - working-directory: motoko/ - run: | - icp network start -d - icp deploy - bash test.sh - - rust-: - runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1 - env: - ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy and test - working-directory: rust/ - run: | - icp network start -d - icp deploy - bash test.sh + motoko: + uses: ./.github/workflows/_run-example.yml + with: + language: motoko + working-directory: motoko/ + run: | + icp network start -d + icp deploy + bash test.sh ``` -- Linux only, no macOS runners. -- No provision scripts — toolchain comes from the container image. -- Always include the `concurrency` block to cancel superseded runs. -- Pin the `actions/checkout` SHA and annotate it with the version tag. +- `_run-example.yml` is the single source of truth for the dev-env container image version — never pin container images in per-example workflows. +- One job per language variant the example ships; frontend-only `hosting/` examples use a single job with `language: all`. +- Keep the template's `concurrency` block and one `pull_request` path entry per shipped language directory. +- Examples with PocketIC integration tests set `install-pocketic: true`. +- Linux only; the toolchain comes from the container image — no provision scripts. -**When migrating only one language variant**, only include the corresponding job. Do not add a stub for the other language — it will be added when that variant is migrated. Example: migrating `motoko/hello_cycles` → add only `motoko-hello_cycles` job; leave `rust-hello_cycles` for a separate PR. +### Dev-env container images -**Deleting old workflows during migration**: the repo may contain legacy dfx-based workflow files (e.g. `motoko-hello_cycles-example.yaml`). When migrating an example, delete the old workflow file for the language being migrated. Do **not** delete workflow files for the other language: +Images live at `ghcr.io/dfinity/icp-dev-env-{motoko,rust,all}` (source: https://github.com/dfinity/icp-dev-env), pinned with a `v`-prefixed tag in exactly two places, guarded by `dev-env-version-check.yml`: -```bash -# Migrating Motoko — delete only the Motoko legacy workflow: -git rm .github/workflows/motoko--example.yaml # or .yml -# Do NOT touch rust--example.yml -``` +- `.github/workflows/_run-example.yml` (CI) +- `.devcontainer/devcontainer.json` (local VS Code; single root devcontainer for all examples — do not add per-example configs) --- ## README structure -Each example's README should follow this structure: +Each example's README follows this structure: ```markdown # @@ -457,6 +368,7 @@ Each example's README should follow this structure: ## Build and deploy from the command line ### Prerequisites + - Node.js - icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` - ic-mops: `npm install -g ic-mops` @@ -466,85 +378,19 @@ Each example's README should follow this structure: ### Deploy and test -If the example has a frontend with a Vite dev server: `npm run dev` (hot reload during frontend development) - -> If tests fail with an out-of-cycles error, run `icp canister top-up --amount 30t backend` to replenish cycles and retry (`30t` is an example amount). Only relevant for examples that create child canisters. +If the example has a frontend: `npm run dev` (Vite dev server with hot reload) ## Updating the Candid interface - -Motoko: `$(mops toolchain bin moc) --idl -o backend/backend.did backend/app.mo` +Motoko: `mops generate candid backend` Rust: `icp build backend && candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did` ## Security considerations and best practices ``` -- Security best practices URL: `https://docs.internetcomputer.org/guides/security/overview` +- Say **"canister"**, not "smart contract" — in READMEs and code comments alike. - Each README links to its counterpart in the other language. -- **Backend-only examples**: omit the `## Updating the Candid interface` section — there is no frontend consuming the `.did` file, so regeneration instructions add no value. -- **If the original example has a frontend, the migration must include it.** Never drop a frontend that existed in the dfx version — check `dfx.json` for `"type": "assets"` canisters. Port the frontend to Vite + `@icp-sdk/bindgen` following the `hello_world` template. If the original frontend contains important educational logic (e.g. certificate verification, custom cryptography), keep that logic intact and only update the canister interaction layer. -- **Preserve important domain knowledge**: the original README may contain non-obvious details about how the example works (memory limits, fee calculations, network-specific behavior, address type explanations, etc.). Read the original README carefully before migrating and carry forward any content that helps a reader understand *why* the example is structured the way it is — not just *how* to deploy it. Do not silently drop explanatory paragraphs, even if they reference dfx commands that need updating. -- **Broken links**: do not copy anchor links from the original README unless you have verified they resolve on the current docs site. When in doubt, link to the top-level page (e.g. the spec index) rather than a specific anchor. - ---- - -## Pending items (do not resolve prematurely) - -### Container images -Images are published at `ghcr.io/dfinity/icp-dev-env-{motoko,rust,all}`. Current pinned version: **`v1.2.0`**. Releases are tagged with a `v` prefix (e.g. `v1.2.0`); the registry publishes both `v1.2.0` and `1.2.0` tags, and we pin the `v`-prefixed form. When a new release is cut, update the tag in: -- `.devcontainer/devcontainer.json` -- `.github/workflows/_run-example.yml` (the single source of truth for the CI image version) - -`dev-env-version-check.yml` guards that these two stay in sync. - -Source: https://github.com/dfinity/icp-dev-env - ---- - -## dfx → icp-cli migration checklist - -When migrating an existing example: - -**Before you start**: read the original README, Makefile, and any deploy scripts in full. Note any non-obvious configuration (canister settings, special deploy steps, memory limits, key names, etc.) that must be carried forward. - -### Code and configuration -- [ ] Replace `dfx.json` with `icp.yaml` using the canonical structure above -- [ ] Rename canisters to `backend` and `frontend` -- [ ] Rename `src//` to `backend/` (or `src/backend/` if keeping the `src/` layout) -- [ ] Rename `src//` to `frontend/` (or `src/frontend/`) -- [ ] Rename `.did` file to `backend.did` -- [ ] Update `Cargo.toml` package name to `backend` (Rust) -- [ ] Update `Cargo.lock` package name entry (Rust) -- [ ] Update workspace member path in root `Cargo.toml` (Rust) -- [ ] Update `vite.config.js`: canister name, `didFile` path, env var names, remove dfx fallback -- [ ] Update `actor.js`: import path, `PUBLIC_CANISTER_ID:backend`, `CANISTER_ID_BACKEND` -- [ ] Update root `package.json` workspace path to `frontend/` -- [ ] Update `.gitignore` bindings path to `frontend/src/bindings/` -- [ ] Update `mops.toml` to current toolchain versions (Motoko) -- [ ] Run `mops check --fix` in the example directory and commit any auto-fixes (Motoko) -- [ ] If the example uses the management canister: add `ic = "4.0.0"` dependency and replace `ic:aaaaa-aa` / `actor("aaaaa-aa")` with `import { ic } "mo:ic"` (Motoko) -- [ ] If the Rust example uses the management canister: add `ic-cdk-management-canister = "0.1.1"` dependency and replace `ic_cdk::api::management_canister` with the appropriate function from that crate -- [ ] **Judge whether per-environment configuration makes sense**: if the example calls an external canister whose principal differs by environment (e.g. ICP ledger vs. TESTICP), add an `environments` block to `icp.yaml`. Skip for self-contained examples. -- [ ] If the example creates child canisters: use `icp deploy --cycles 30t` in the CI workflow and README -- [ ] If the example requires non-default canister settings (memory limits, freezing threshold, etc.): apply them via `icp canister settings update` at the top of `test.sh` -- [ ] Delete `dfx.json`, `BUILD.md`, `.dfx/`, `.env` (dfx-generated) -- [ ] Delete `.devcontainer/` inside the example folder if one exists (only the repo-root devcontainer is kept) - -### test.sh -- [ ] Add `test.sh` (executable, `#!/usr/bin/env bash`, `set -e`) with numbered tests -- [ ] Use `--query` for all `public query func` and `public composite query func` calls -- [ ] Use a polling loop for any behavior that depends on timers, heartbeats, or async system hooks -- [ ] Use delta-based balance assertions (before/after) rather than absolute values for idempotency -- [ ] For backend-only examples, no `## Updating the Candid interface` section is needed - -### CI workflow -- [ ] Add CI workflow under `.github/workflows/.yml` -- [ ] Include only the job(s) for the language being migrated — do not add stubs for the other language -- [ ] Delete the old dfx-based workflow for the language being migrated (e.g. `motoko--example.yaml`) -- [ ] Do **not** delete workflow files for the other language variant — they will be handled in a separate PR - -### README -- [ ] Update deploy instructions to use `icp-cli` -- [ ] Preserve important domain-specific content from the original README (memory limits, fee behaviour, address type explanations, network-specific notes, etc.) — update the commands but keep the explanations -- [ ] Omit `## Updating the Candid interface` for backend-only examples (no frontend bindings) -- [ ] Verify that all links resolve — do not copy anchors from the old README without checking them +- **Backend-only examples:** omit the `## Updating the Candid interface` section — no frontend consumes the `.did` file. +- **Child-canister examples:** add a note that an out-of-cycles error is fixed with `icp canister top-up --amount backend`. +- **Links:** only add links you have verified resolve on the current docs site; prefer top-level pages over deep anchors when unsure. +- **Docs over product pages:** when the context is learning or integrating a feature, link the developer docs (e.g. https://docs.internetcomputer.org/guides/authentication/internet-identity for Internet Identity); link the product itself (e.g. https://id.ai) only when referring to the live instance an end user interacts with. diff --git a/motoko/ic-pos/.gitignore b/motoko/ic-pos/.gitignore index 9c3d8aaddc..8dde9217ef 100644 --- a/motoko/ic-pos/.gitignore +++ b/motoko/ic-pos/.gitignore @@ -7,9 +7,6 @@ .DS_Store **/.DS_Store -# dfx temporary files -.dfx/ - # rust target/ @@ -38,5 +35,4 @@ yarn-error.log* # misc scratch/ -canister_ids.json diff --git a/rust/basic_bitcoin/.gitignore b/rust/basic_bitcoin/.gitignore index 1ee740a699..d0d9357f40 100644 --- a/rust/basic_bitcoin/.gitignore +++ b/rust/basic_bitcoin/.gitignore @@ -7,9 +7,6 @@ .DS_Store **/.DS_Store -# dfx temporary files -.dfx/ - # rust target/ @@ -19,4 +16,3 @@ bitcoin_data # ord ord-db/ -canister_ids.json diff --git a/rust/low_wasm_memory/.gitignore b/rust/low_wasm_memory/.gitignore index 938bc33a56..6c18f1a20e 100644 --- a/rust/low_wasm_memory/.gitignore +++ b/rust/low_wasm_memory/.gitignore @@ -7,12 +7,6 @@ .DS_Store **/.DS_Store -# dfx temporary files -.dfx/ - -# generated files -src/declarations/ - # rust target/ diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000000..77b0a540ee --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,173 @@ +{ + "version": 1, + "skills": { + "agent-web-identity": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/agent-web-identity/SKILL.md", + "computedHash": "2832d3aa2ffd692fbec25bb7ed196913e19a7aa035aa3a6ae0cfd13ae02493a7" + }, + "asset-canister": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/asset-canister/SKILL.md", + "computedHash": "2b27caf9680ad5226fbf6a758066516e3a4c4434ff6039641bc3fb8081626466" + }, + "autosync-ic-skills": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/autosync-ic-skills/SKILL.md", + "computedHash": "0ccbf3c4c42156bf3c3150efda6a97e4db27068df498e6b9ad781c0aa80eefb8" + }, + "caffeine-app": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/caffeine-app/SKILL.md", + "computedHash": "caa9c9b90c5d23a4c3475331c84fca86b302d58c34ce1a12d1eed1b1161ed515" + }, + "canhelp": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/canhelp/SKILL.md", + "computedHash": "d7418dc6133afbf815501c1c46ce2129c6eb1e9332adbc485e0bdeef9f31505c" + }, + "canister-security": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/canister-security/SKILL.md", + "computedHash": "8367257eb731552d5406e4d7ee49879036b7bcbb9483fdb2664d1953e81738f1" + }, + "certified-variables": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/certified-variables/SKILL.md", + "computedHash": "607178e7294406cb328d1f760bf33f8cf42d386863163e7222c9a31e84252312" + }, + "ckbtc": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/ckbtc/SKILL.md", + "computedHash": "549b31591c740983bde86c660d9574da50890525ecac764b26b440b2503727f3" + }, + "custom-domains": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/custom-domains/SKILL.md", + "computedHash": "a7cfd4178d1d24bebd432d5c723ac12d6a0ffa6323123c7110a645b42657a9ae" + }, + "cycles-management": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/cycles-management/SKILL.md", + "computedHash": "1b07969a39b849ffd73f777ec2f9bc95a4923afea7c40626e821ab29c5c7493d" + }, + "deploy-to-cloud-engine": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/deploy-to-cloud-engine/SKILL.md", + "computedHash": "f2ad8adc8d776548d054d65d02b076a4461a270eab562dda51c521a45a6f9769" + }, + "evm-rpc": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/evm-rpc/SKILL.md", + "computedHash": "9099a405cda7f1ada279acfb2c04a322df90ec7701ddecae5a8f0e671fd3f340" + }, + "https-outcalls": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/https-outcalls/SKILL.md", + "computedHash": "0207638b0c3b0410900d355ad0dc63343f6ac1102b338b0349d430de34265118" + }, + "ic-dashboard": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/ic-dashboard/SKILL.md", + "computedHash": "ea969d231566b38d90c16a59f165d16496dd5f47faf3c7e939a051aeecc6fcb3" + }, + "icp-cli": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/icp-cli/SKILL.md", + "computedHash": "92a38a3d867130d81df8b8d539a47f0f3dbc1a54e49722c544675fcd8a34bdd6" + }, + "icrc-ledger": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/icrc-ledger/SKILL.md", + "computedHash": "0a87182ac37e596cdc8c0c329d18191483ae25a7e5dfd647fee717a08bd92d7e" + }, + "improve-ic-skill": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": ".agents/skills/improve-ic-skill/SKILL.md", + "computedHash": "7e46c64af6dba6b2735630e3575a0c261f02882664a4f423cb2071665b1858b4" + }, + "internet-identity": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/internet-identity/SKILL.md", + "computedHash": "9455fe0380e319b7afe0ccbd60729fa6e454a72409d70d89eea267a6d1d609ed" + }, + "migrating-motoko": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/migrating-motoko/SKILL.md", + "computedHash": "c3bf144fa83262ae3d0a212b8bb45be11641e7f25cc8e059dffb4715bd91f4cd" + }, + "migrating-motoko-enhanced": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/migrating-motoko-enhanced/SKILL.md", + "computedHash": "2b55988526543ec4ec28f028e7025838110daf7be8fa8fce2e64f9ae4871d1e8" + }, + "mops-cli": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/mops-cli/SKILL.md", + "computedHash": "9aaf1975719310944b77b3cb53aed9a308c88ffa7d317a9a4a63771f1b7f4660" + }, + "motoko": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/motoko/SKILL.md", + "computedHash": "95213a75a9c65edb3fede53fcc0bfb42edb6fbec85e91d2ca0fb1095a75d391b" + }, + "multi-canister": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/multi-canister/SKILL.md", + "computedHash": "278aff0df2bf5c20af2ca0c5ae64452c1784d3f770540c78f11481b3da9c443d" + }, + "skill-creator": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": ".agents/skills/skill-creator/SKILL.md", + "computedHash": "d53356a7a117bd9f1de43aff36a0fd16ab816786872588ab36f93f7819a2b245" + }, + "sns-launch": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/sns-launch/SKILL.md", + "computedHash": "7d02bb7addb5504ae160f94d53a97a2b1b9a6d3338efb9bc262903fbeb198056" + }, + "stable-memory": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/stable-memory/SKILL.md", + "computedHash": "bc26d61750c8a22000fed6b7d752d82dfd773fc22889346e904e6cefd88b0078" + }, + "vetkd": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/vetkd/SKILL.md", + "computedHash": "c80cee4c04d2b9e044e5cfd9436e7a8ac973ef895296c4e7824b3adfaafcccdd" + }, + "wallet-integration": { + "source": "dfinity/icskills", + "sourceType": "github", + "skillPath": "skills/wallet-integration/SKILL.md", + "computedHash": "b4ba1162c10886dfead681286676d66e5c9bc0aa104e3ef45a03975e2a8d99a5" + } + } +}