From 68bab5d6d7e133a005e8ffd3864abfb9cfce0dec Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 16 Jun 2026 19:58:45 +0200 Subject: [PATCH 1/2] chore: migrate rust/icp_transfer to icp-cli Replaces dfx.json with icp.yaml (@dfinity/rust@v3.3.0), moves the canister source from src/icp_transfer_backend/ to backend/, renames the Cargo package to `backend`, adds a Makefile that deploys the ICP ledger locally at ryjl3-tyaaa-aaaaa-aaaba-cai and runs end-to-end transfer tests, and adds a CI workflow using icp-dev-env-rust:1.0.0. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/icp_transfer.yml | 28 ++ .../workflows/rust-icp-transfer-example.yml | 43 --- rust/icp_transfer/.gitignore | 3 - rust/icp_transfer/Cargo.lock | 2 +- rust/icp_transfer/Cargo.toml | 4 +- rust/icp_transfer/Makefile | 73 +++++ rust/icp_transfer/README.md | 303 ++---------------- rust/icp_transfer/backend/Cargo.toml | 13 + .../src/lib.rs | 0 rust/icp_transfer/demo.sh | 43 --- rust/icp_transfer/dfx.json | 28 -- rust/icp_transfer/icp.yaml | 8 + rust/icp_transfer/rust-toolchain.toml | 2 + .../src/icp_transfer_backend/Cargo.toml | 15 - .../icp_transfer_backend.did | 8 - 15 files changed, 154 insertions(+), 419 deletions(-) create mode 100644 .github/workflows/icp_transfer.yml delete mode 100644 .github/workflows/rust-icp-transfer-example.yml create mode 100644 rust/icp_transfer/Makefile create mode 100644 rust/icp_transfer/backend/Cargo.toml rename rust/icp_transfer/{src/icp_transfer_backend => backend}/src/lib.rs (100%) delete mode 100755 rust/icp_transfer/demo.sh delete mode 100644 rust/icp_transfer/dfx.json create mode 100644 rust/icp_transfer/icp.yaml create mode 100644 rust/icp_transfer/rust-toolchain.toml delete mode 100644 rust/icp_transfer/src/icp_transfer_backend/Cargo.toml delete mode 100644 rust/icp_transfer/src/icp_transfer_backend/icp_transfer_backend.did diff --git a/.github/workflows/icp_transfer.yml b/.github/workflows/icp_transfer.yml new file mode 100644 index 0000000000..71571a6a05 --- /dev/null +++ b/.github/workflows/icp_transfer.yml @@ -0,0 +1,28 @@ +name: icp_transfer + +on: + push: + branches: [master] + pull_request: + paths: + - rust/icp_transfer/** + - .github/workflows/icp_transfer.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + rust-icp_transfer: + runs-on: ubuntu-24.04 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.0 + env: + ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Deploy and test + working-directory: rust/icp_transfer + run: | + icp network start -d + icp deploy + make test diff --git a/.github/workflows/rust-icp-transfer-example.yml b/.github/workflows/rust-icp-transfer-example.yml deleted file mode 100644 index ee6228a0bb..0000000000 --- a/.github/workflows/rust-icp-transfer-example.yml +++ /dev/null @@ -1,43 +0,0 @@ -# Known failure: https://dfinity.atlassian.net/browse/EM-5 -name: rust-icp_transfer -on: - push: - branches: - - master - pull_request: - paths: - - rust/icp_transfer/** - - .github/workflows/provision-darwin.sh - - .github/workflows/provision-linux.sh - - .github/workflows/rust-icp-transfer-example.yml - - .ic-commit -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -jobs: - rust-icp_transfer-darwin: - runs-on: macos-15 - steps: - - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0 - with: - submodules: recursive - - name: Provision Darwin - run: bash .github/workflows/provision-darwin.sh - - name: Rust Tokens Transfer Darwin - run: | - pushd rust/icp_transfer - bash ./demo.sh - popd - rust-icp_transfer-linux: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0 - with: - submodules: recursive - - name: Provision Linux - run: bash .github/workflows/provision-linux.sh - - name: Rust Tokens Transfer Linux - run: | - pushd rust/icp_transfer - bash ./demo.sh - popd diff --git a/rust/icp_transfer/.gitignore b/rust/icp_transfer/.gitignore index 49c89a1c95..0c42efdeaa 100644 --- a/rust/icp_transfer/.gitignore +++ b/rust/icp_transfer/.gitignore @@ -7,9 +7,6 @@ .DS_Store **/.DS_Store -# dfx temporary files -.dfx/ - # generated files **/declarations/ diff --git a/rust/icp_transfer/Cargo.lock b/rust/icp_transfer/Cargo.lock index d1de36fe51..71a1d03b64 100644 --- a/rust/icp_transfer/Cargo.lock +++ b/rust/icp_transfer/Cargo.lock @@ -315,7 +315,7 @@ dependencies = [ ] [[package]] -name = "icp_transfer_backend" +name = "backend" version = "0.1.0" dependencies = [ "candid", diff --git a/rust/icp_transfer/Cargo.toml b/rust/icp_transfer/Cargo.toml index 7330ee5fd8..d1e49e317a 100644 --- a/rust/icp_transfer/Cargo.toml +++ b/rust/icp_transfer/Cargo.toml @@ -1,5 +1,3 @@ [workspace] -members = [ - "src/icp_transfer_backend" -] +members = ["backend"] resolver = "2" diff --git a/rust/icp_transfer/Makefile b/rust/icp_transfer/Makefile new file mode 100644 index 0000000000..ba3f96214f --- /dev/null +++ b/rust/icp_transfer/Makefile @@ -0,0 +1,73 @@ +IC_REVISION = d87954601e4b22972899e9957e800406a0a6b929 +LEDGER_WASM_URL = https://download.dfinity.systems/ic/$(IC_REVISION)/canisters/ledger-canister.wasm.gz +LEDGER_PRINCIPAL = ryjl3-tyaaa-aaaaa-aaaba-cai + +.PHONY: deploy-ledger test + +# Download and deploy the ICP ledger canister at the well-known mainnet principal. +# On the local network, provisional_create_canister_with_cycles allows specifying +# an exact canister ID so that the backend (which hardcodes MAINNET_LEDGER_CANISTER_ID) +# finds the ledger at the expected address. +deploy-ledger: + @echo "=== Downloading ICP ledger WASM ===" + curl -sL $(LEDGER_WASM_URL) -o /tmp/ledger-canister.wasm.gz + gunzip -f /tmp/ledger-canister.wasm.gz + @echo "=== Creating ledger canister at $(LEDGER_PRINCIPAL) ===" + icp canister call aaaaa-aa provisional_create_canister_with_cycles \ + '(record { amount = 10_000_000_000_000 : nat; settings = null; specified_id = opt principal "$(LEDGER_PRINCIPAL)" })' \ + -n http://localhost:8000 + @echo "=== Installing ICP ledger ===" + MINTER_ACCOUNT_ID=$$(icp identity account-id --identity anonymous) && \ + DEFAULT_ACCOUNT_ID=$$(icp identity account-id) && \ + icp canister install $(LEDGER_PRINCIPAL) \ + --wasm /tmp/ledger-canister.wasm \ + --args "(variant { Init = record { \ + minting_account = \"$$MINTER_ACCOUNT_ID\"; \ + initial_values = vec { record { \"$$DEFAULT_ACCOUNT_ID\"; record { e8s = 10_000_000_000 : nat64; }; }; }; \ + send_whitelist = vec {}; \ + transfer_fee = opt record { e8s = 10_000 : nat64; }; \ + token_symbol = opt \"LICP\"; \ + token_name = opt \"Local ICP\"; \ + } })" \ + -n http://localhost:8000 -y + +test: deploy-ledger + @echo "=== Test 1: verify default account has initial ICP balance ===" + @DEFAULT_ACCOUNT_ID=$$(icp identity account-id) && \ + ACCOUNT_BYTES=$$(python3 -c "b=bytes.fromhex('$$DEFAULT_ACCOUNT_ID'); print('vec{' + ';'.join(str(x) for x in b) + '}')") && \ + result=$$(icp canister call $(LEDGER_PRINCIPAL) account_balance \ + "(record { account = $$ACCOUNT_BYTES })" \ + -n http://localhost:8000) && \ + echo "$$result" && \ + echo "$$result" | grep -q '10_000_000_000' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 2: fund the backend canister with 1 ICP ===" + @BACKEND_PRINCIPAL=$$(icp canister status backend -i) && \ + BACKEND_ACCOUNT_ID=$$(icp identity account-id --of-principal $$BACKEND_PRINCIPAL) && \ + BACKEND_ACCOUNT_BYTES=$$(python3 -c "b=bytes.fromhex('$$BACKEND_ACCOUNT_ID'); print('vec{' + ';'.join(str(x) for x in b) + '}')") && \ + result=$$(icp canister call $(LEDGER_PRINCIPAL) transfer \ + "(record { to = $$BACKEND_ACCOUNT_BYTES; memo = 0 : nat64; amount = record { e8s = 100_000_000 : nat64 }; fee = record { e8s = 10_000 : nat64 }; })" \ + -n http://localhost:8000) && \ + echo "$$result" && \ + echo "$$result" | grep -q 'Ok' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 3: transfer ICP from backend canister to default identity ===" + @CALLER_PRINCIPAL=$$(icp identity principal) && \ + result=$$(icp canister call backend transfer \ + "(record { amount = record { e8s = 50_000_000 : nat64 }; to_principal = principal \"$$CALLER_PRINCIPAL\"; to_subaccount = null })") && \ + echo "$$result" && \ + echo "$$result" | grep -q 'Ok' && \ + echo "PASS" || (echo "FAIL" && exit 1) + + @echo "=== Test 4: verify backend ledger balance decreased after transfer ===" + @BACKEND_PRINCIPAL=$$(icp canister status backend -i) && \ + BACKEND_ACCOUNT_ID=$$(icp identity account-id --of-principal $$BACKEND_PRINCIPAL) && \ + BACKEND_ACCOUNT_BYTES=$$(python3 -c "b=bytes.fromhex('$$BACKEND_ACCOUNT_ID'); print('vec{' + ';'.join(str(x) for x in b) + '}')") && \ + result=$$(icp canister call $(LEDGER_PRINCIPAL) account_balance \ + "(record { account = $$BACKEND_ACCOUNT_BYTES })" \ + -n http://localhost:8000) && \ + echo "$$result" && \ + echo "$$result" | grep -q 'e8s' && \ + echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/icp_transfer/README.md b/rust/icp_transfer/README.md index fab53c11ed..8f1aedb59c 100644 --- a/rust/icp_transfer/README.md +++ b/rust/icp_transfer/README.md @@ -1,302 +1,55 @@ # ICP transfer -ICP transfer is a canister that can transfer ICP from its account to other accounts. It is an example of a canister that uses the ledger canister. Sample code is available in [Motoko](https://github.com/dfinity/examples/tree/master/motoko/icp_transfer) and [Rust](https://github.com/dfinity/examples/tree/master/rust/icp_transfer). +ICP transfer is a canister that can transfer ICP from its account to other accounts. It is an example of a canister that uses the ICP ledger. Sample code is available in [Motoko](https://github.com/dfinity/examples/tree/master/motoko/icp_transfer) and [Rust](https://github.com/dfinity/examples/tree/master/rust/icp_transfer). -:::info - -The ICP ledger supports the ICRC1 standard, which is the recommended standard for token transfers. You can [read more about the differences](https://internetcomputer.org/docs/current/developer-docs/defi/overview) and find an example of how to transfer ICRC1 tokens from a canister in [Motoko](https://github.com/dfinity/examples/tree/master/motoko/token_transfer) and [Rust](https://github.com/dfinity/examples/tree/master/rust/token_transfer). -::: +> **Note:** The ICP ledger also supports the ICRC-1 standard, which is the recommended standard for new token integrations. You can [read more about the differences](https://internetcomputer.org/docs/current/developer-docs/defi/overview) and find examples of how to transfer ICRC-1 tokens from a canister in [Motoko](https://github.com/dfinity/examples/tree/master/motoko/token_transfer) and [Rust](https://github.com/dfinity/examples/tree/master/rust/token_transfer). ## Architecture -The sample code revolves around one core transfer function which takes as input the amount of ICP to transfer, the account (and optionally the subaccount) to which to transfer ICP and returns either success or an error in case e.g. the ICP transfer canister doesn’t have enough ICP to do the transfer. In case of success, a unique identifier of the transaction is returned. This identifier will be stored in the memo of the transaction in the ledger. - -This sample will use the Rust variant. - -## Prerequisites - -- [x] Install the [IC - SDK](https://internetcomputer.org/docs/current/developer-docs/getting-started/install). For local testing, `dfx >= 0.22.0` is required. - -## Step 1: Setup project environment - -Start a local instance of the replica and create a new project with the commands: - -``` -dfx start --background -dfx new --type=rust icp_transfer --no-frontend -cd icp_transfer -``` - -### Step 2: Determine ledger file locations - -> [!TIP] -> You can read more about how to [setup the ICP ledger locally](https://internetcomputer.org/docs/current/developer-docs/defi/icp-tokens/ledger-local-setup). - -Go to the [releases overview](https://dashboard.internetcomputer.org/releases) and copy the latest replica binary revision. - -The URL for the ledger Wasm module is `https://download.dfinity.systems/ic//canisters/ledger-canister.wasm.gz`. - -The URL for the ledger.did file is `https://raw.githubusercontent.com/dfinity/ic//rs/rosetta-api/icp_ledger/ledger.did`. - -[OPTIONAL] -If you want to make sure you have the latest ICP ledger files, you can run the following script. Please ensure that you have [`jq`](https://jqlang.github.io/jq/) installed as the script relies on it. - -```sh -curl -o download_latest_icp_ledger.sh "https://raw.githubusercontent.com/dfinity/ic/1f1d8dd8c294d19a5551a022e3f00f25da7dc944/rs/rosetta-api/scripts/download_latest_icp_ledger.sh" -chmod +x download_latest_icp_ledger.sh -./download_latest_icp_ledger.sh -``` - -## Step 3: Configure the `dfx.json` file to use the ledger - -Replace its contents with this but adapt the URLs to be the ones you determined in step 2: - -```json -{ - "canisters": { - "icp_transfer_backend": { - "candid": "src/icp_transfer_backend/icp_transfer_backend.did", - "package": "icp_transfer_backend", - "type": "rust" - }, - "icp_ledger_canister": { - "type": "custom", - "candid": "https://raw.githubusercontent.com/dfinity/ic//rs/rosetta-api/icp_ledger/ledger.did", - "wasm": "https://download.dfinity.systems/ic//canisters/ledger-canister.wasm.gz", - "remote": { - "id": { - "ic": "ryjl3-tyaaa-aaaaa-aaaba-cai" - } - } - } - }, - "defaults": { - "build": { - "args": "", - "packtool": "" - } - }, - "output_env_file": ".env", - "version": 1 -} -``` - -## Step 4: Create a new identity that will work as a minting account - -```bash -dfx identity new minter --storage-mode plaintext -dfx identity use minter -export MINTER_ACCOUNT_ID=$(dfx ledger account-id) -``` - -> [!IMPORTANT] -> Transfers from the minting account will create Mint transactions. Transfers to the minting account will create Burn transactions. - -## Step 5: Switch back to your default identity and record its ledger account identifier - -```bash -dfx identity use default -export DEFAULT_ACCOUNT_ID=$(dfx ledger account-id) -``` +The sample code revolves around one core `transfer` function which takes as input the amount of ICP to transfer, the destination account (and optionally a subaccount), and returns either a unique block index on success or an error string on failure. The block index is stored in the transaction memo in the ledger, making every transfer auditable. -## Step 6: Deploy the ledger canister to your network +The canister uses `MAINNET_LEDGER_CANISTER_ID` from `ic-ledger-types`, which is the well-known principal `ryjl3-tyaaa-aaaaa-aaaba-cai`. In production this resolves to the real ICP ledger; in local testing the ledger is deployed at the same principal ID using `provisional_create_canister_with_cycles`. -Take a moment to read the details of the call made below. Not only are you deploying the ICP ledger canister, you are also: +> **Important:** Transfers from the minting account create Mint transactions. Transfers to the minting account create Burn transactions. -- Deploying the canister to the same canister ID as the mainnet ledger canister. This is to make it easier to switch between local and mainnet deployments and set in `dfx.json` using `specified_id`. -- Setting the minting account to the account identifier you saved in a previous step (MINTER_ACCOUNT_ID). -- Minting 100 ICP tokens to the DEFAULT_ACCOUNT_ID (1 ICP is equal to 10^8 e8s, hence the name). -- Setting the transfer fee to 0.0001 ICP. -- Naming the token Local ICP / LICP +## Build and deploy from the command line -```bash -dfx deploy icp_ledger_canister --argument " - (variant { - Init = record { - minting_account = \"$MINTER_ACCOUNT_ID\"; - initial_values = vec { - record { - \"$DEFAULT_ACCOUNT_ID\"; - record { - e8s = 10_000_000_000 : nat64; - }; - }; - }; - send_whitelist = vec {}; - transfer_fee = opt record { - e8s = 10_000 : nat64; - }; - token_symbol = opt \"LICP\"; - token_name = opt \"Local ICP\"; - } - }) -" -``` - -If successful, the output should be: +### Prerequisites -```bash -Deployed canisters. -URLs: - Backend canister via Candid interface: - icp_ledger_canister: http://127.0.0.1:4943/?canisterId=bnz7o-iuaaa-aaaaa-qaaaa-cai&id=ryjl3-tyaaa-aaaaa-aaaba-cai -``` +- Node.js +- icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- Python 3 (for computing ledger account identifiers in `make test`) -## Step 7: Verify that the ledger canister is healthy and working as expected +### Install ```bash -dfx canister call icp_ledger_canister account_balance '(record { account = '$(python3 -c 'print("vec{" + ";".join([str(b) for b in bytes.fromhex("'$DEFAULT_ACCOUNT_ID'")]) + "}")')'})' +git clone https://github.com/dfinity/examples +cd examples/rust/icp_transfer ``` -The output should be: +### Deploy and test ```bash -(record { e8s = 10_000_000_000 : nat64 }) +icp network start -d +icp deploy +make test +icp network stop ``` -## Step 8: Prepare the token transfer canister - -Replace the contents of the `src/icp_transfer_backend/Cargo.toml` file with the following: - -```toml -[package] -name = "icp_transfer_backend" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib"] - -[dependencies] -candid = "0.10.4" -ic-cdk = "0.12.1" -ic-cdk-macros = "0.8.4" -ic-ledger-types = "0.9.0" -serde = "1.0.197" -serde_derive = "1.0.197" +`make test` performs the full end-to-end workflow: -``` - -Replace the contents of the `src/icp_transfer_backend/src/lib.rs` file with the following: - -```rust -use candid::{CandidType, Principal}; - -use ic_cdk_macros::*; -use ic_ledger_types::{ - AccountIdentifier, BlockIndex, Memo, Subaccount, Tokens, DEFAULT_SUBACCOUNT, - MAINNET_LEDGER_CANISTER_ID, -}; -use serde::{Deserialize, Serialize}; - -#[derive(CandidType, Serialize, Deserialize, Clone, Debug)] -pub struct TransferArgs { - amount: Tokens, - to_principal: Principal, - to_subaccount: Option, -} - -#[update] -async fn transfer(args: TransferArgs) -> Result { - ic_cdk::println!( - "Transferring {} tokens to principal {} subaccount {:?}", - &args.amount, - &args.to_principal, - &args.to_subaccount - ); - let to_subaccount = args.to_subaccount.unwrap_or(DEFAULT_SUBACCOUNT); - let transfer_args = ic_ledger_types::TransferArgs { - memo: Memo(0), - amount: args.amount, - fee: Tokens::from_e8s(10_000), - // The subaccount of the account identifier that will be used to withdraw tokens and send them - // to another account identifier. If set to None then the default subaccount will be used. - // See the [Ledger doc](https://internetcomputer.org/docs/current/developer-docs/integrations/ledger/#accounts). - from_subaccount: None, - to: AccountIdentifier::new(&args.to_principal, &to_subaccount), - created_at_time: None, - }; - ic_ledger_types::transfer(MAINNET_LEDGER_CANISTER_ID, transfer_args) - .await - .map_err(|e| format!("failed to call ledger: {:?}", e))? - .map_err(|e| format!("ledger transfer error {:?}", e)) -} - -// Enable Candid export (see https://internetcomputer.org/docs/current/developer-docs/backend/rust/generating-candid) -ic_cdk::export_candid!(); - -``` - -Replace the contents of the `src/icp_transfer_backend/icp_transfer_backend.did` file with the following: - -:::info - -The `icp_transfer_backend.did` file is a Candid file that describes the service interface of the canister. It was generated from the Rust code using the `candid-extractor` tool. You can read more about the [necessary steps](https://internetcomputer.org/docs/current/developer-docs/backend/rust/generating-candid). - -::: - -```did -type Result = variant { Ok : nat64; Err : text }; -type Tokens = record { e8s : nat64 }; -type TransferArgs = record { - to_principal : principal; - to_subaccount : opt vec nat8; - amount : Tokens; -}; -service : { - canister_account : () -> (vec nat8) query; - transfer : (TransferArgs) -> (Result); -} - -``` - -## Step 9: Deploy the token transfer canister - -```bash -dfx deploy icp_transfer_backend -``` - -## Step 10: Determine out the address of your canister - -```bash -TOKENS_TRANSFER_ACCOUNT_ID="$(dfx ledger account-id --of-canister icp_transfer_backend)" -TOKENS_TRANSFER_ACCOUNT_ID_BYTES="$(python3 -c 'print("vec{" + ";".join([str(b) for b in bytes.fromhex("'$TOKENS_TRANSFER_ACCOUNT_ID'")]) + "}")')" -``` - -## Step 11: Transfer funds to your canister - -> [!TIP] -> Make sure that you are using the default `dfx` account that we minted tokens to in step 6 for the following steps. - -Make the following call to transfer funds to the canister: - -```bash -dfx canister call icp_ledger_canister transfer "(record { to = ${TOKENS_TRANSFER_ACCOUNT_ID_BYTES}; memo = 1; amount = record { e8s = 2_00_000_000 }; fee = record { e8s = 10_000 }; })" -``` - -If successful, the output should be: - -```bash -(variant { Ok = 1 : nat64 }) -``` - -## Step 12: Transfer funds from the canister - -Now that the canister owns ICP on the ledger, you can transfer funds from the canister to another account, in this case back to the default account: - -```bash -dfx canister call icp_transfer_backend transfer "(record { amount = record { e8s = 1_00_000_000 }; to_principal = principal \"$(dfx identity get-principal)\"})" -``` +1. Downloads the ICP ledger WASM and deploys it locally at `ryjl3-tyaaa-aaaaa-aaaba-cai` +2. Seeds the default identity with 100 ICP (1 ICP = 10^8 e8s) +3. Funds the backend canister with 1 ICP via the ledger +4. Calls `backend.transfer` to send 0.5 ICP back to the default identity +5. Verifies each step produces the expected result ## Security considerations and best practices -If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://internetcomputer.org/docs/current/references/security/) for developing on the Internet Computer. This example may not implement all the best practices. +If you base your application on this example, we recommend you familiarize yourself with and adhere to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for developing on the Internet Computer. This example may not implement all the best practices. For example, the following aspects are particularly relevant for this app: -- [Inter-canister calls and rollbacks](https://internetcomputer.org/docs/current/developer-docs/security/security-best-practices/overview), since issues around inter-canister calls (here the ledger) can e.g. lead to time-of-check time-of-use or double spending security bugs. -- [Certify query responses if they are relevant for security](https://internetcomputer.org/docs/current/references/security/general-security-best-practices#certify-query-responses-if-they-are-relevant-for-security), since this is essential when e.g. displaying important financial data in the frontend that may be used by users to decide on future transactions. In this example, this is e.g. relevant for the call to `canisterBalance`. -- [Use a decentralized governance system like SNS to make a canister have a decentralized controller](https://internetcomputer.org/docs/current/developer-docs/security/security-best-practices/overview), since decentralizing control is a fundamental aspect of decentralized finance applications. +- **Inter-canister calls and rollbacks**: issues around inter-canister calls (here the ledger) can lead to time-of-check time-of-use or double spending security bugs. +- **Certify query responses if they are relevant for security**: this is essential when displaying important financial data in a frontend that may be used to inform future transactions. +- **Use a decentralized governance system like SNS to make a canister have a decentralized controller**: decentralizing control is a fundamental aspect of decentralized finance applications. diff --git a/rust/icp_transfer/backend/Cargo.toml b/rust/icp_transfer/backend/Cargo.toml new file mode 100644 index 0000000000..9536949116 --- /dev/null +++ b/rust/icp_transfer/backend/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "backend" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +candid = "0.10" +ic-cdk = "0.20" +ic-ledger-types = "0.15.0" +serde = "1.0" diff --git a/rust/icp_transfer/src/icp_transfer_backend/src/lib.rs b/rust/icp_transfer/backend/src/lib.rs similarity index 100% rename from rust/icp_transfer/src/icp_transfer_backend/src/lib.rs rename to rust/icp_transfer/backend/src/lib.rs diff --git a/rust/icp_transfer/demo.sh b/rust/icp_transfer/demo.sh deleted file mode 100755 index 038c286946..0000000000 --- a/rust/icp_transfer/demo.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -dfx stop -set -e -trap 'dfx stop' EXIT - -echo "===========SETUP=========" -dfx start --background --clean -dfx identity new alice_icp_transfer --storage-mode plaintext --force -export MINTER_ACCOUNT_ID=$(dfx --identity anonymous ledger account-id) -export DEFAULT_ACCOUNT_ID=$(dfx ledger account-id) -dfx deploy icp_ledger_canister --argument " - (variant { - Init = record { - minting_account = \"$MINTER_ACCOUNT_ID\"; - initial_values = vec { - record { - \"$DEFAULT_ACCOUNT_ID\"; - record { - e8s = 10_000_000_000 : nat64; - }; - }; - }; - send_whitelist = vec {}; - transfer_fee = opt record { - e8s = 10_000 : nat64; - }; - token_symbol = opt \"LICP\"; - token_name = opt \"Local ICP\"; - } - }) -" -dfx canister call icp_ledger_canister account_balance '(record { account = '$(python3 -c 'print("vec{" + ";".join([str(b) for b in bytes.fromhex("'$DEFAULT_ACCOUNT_ID'")]) + "}")')'})' -echo "===========SETUP DONE=========" - -dfx deploy icp_transfer_backend - -TOKENS_TRANSFER_ACCOUNT_ID="$(dfx ledger account-id --of-canister icp_transfer_backend)" -TOKENS_TRANSFER_ACCOUNT_ID_BYTES="$(python3 -c 'print("vec{" + ";".join([str(b) for b in bytes.fromhex("'$TOKENS_TRANSFER_ACCOUNT_ID'")]) + "}")')" -dfx canister call icp_ledger_canister transfer "(record { to=${TOKENS_TRANSFER_ACCOUNT_ID_BYTES}; amount=record { e8s=100_000 }; fee=record { e8s=10_000 }; memo=0:nat64; }, )" - -dfx canister call icp_transfer_backend transfer "(record { amount=record { e8s=5 }; to_principal=principal \"$(dfx identity get-principal)\" },)" - -echo "DONE" \ No newline at end of file diff --git a/rust/icp_transfer/dfx.json b/rust/icp_transfer/dfx.json deleted file mode 100644 index d7fb777a9c..0000000000 --- a/rust/icp_transfer/dfx.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "canisters": { - "icp_transfer_backend": { - "candid": "src/icp_transfer_backend/icp_transfer_backend.did", - "package": "icp_transfer_backend", - "type": "rust" - }, - "icp_ledger_canister": { - "type": "custom", - "candid": "https://raw.githubusercontent.com/dfinity/ic/d87954601e4b22972899e9957e800406a0a6b929/rs/rosetta-api/icp_ledger/ledger.did", - "wasm": "https://download.dfinity.systems/ic/d87954601e4b22972899e9957e800406a0a6b929/canisters/ledger-canister.wasm.gz", - "remote": { - "id": { - "ic": "ryjl3-tyaaa-aaaaa-aaaba-cai" - } - }, - "specified_id": "ryjl3-tyaaa-aaaaa-aaaba-cai" - } - }, - "defaults": { - "build": { - "args": "", - "packtool": "" - } - }, - "output_env_file": ".env", - "version": 1 -} \ No newline at end of file diff --git a/rust/icp_transfer/icp.yaml b/rust/icp_transfer/icp.yaml new file mode 100644 index 0000000000..df5c7edaaf --- /dev/null +++ b/rust/icp_transfer/icp.yaml @@ -0,0 +1,8 @@ +networks: + - name: local + mode: managed + +canisters: + - name: backend + recipe: + type: "@dfinity/rust@v3.3.0" diff --git a/rust/icp_transfer/rust-toolchain.toml b/rust/icp_transfer/rust-toolchain.toml new file mode 100644 index 0000000000..990104f055 --- /dev/null +++ b/rust/icp_transfer/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +targets = ["wasm32-unknown-unknown"] diff --git a/rust/icp_transfer/src/icp_transfer_backend/Cargo.toml b/rust/icp_transfer/src/icp_transfer_backend/Cargo.toml deleted file mode 100644 index fa7c36fa60..0000000000 --- a/rust/icp_transfer/src/icp_transfer_backend/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "icp_transfer_backend" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib"] - -[dependencies] -candid = "0.10.14" -ic-cdk = "0.18.3" -ic-ledger-types = "0.15.0" -serde = "1.0.219" \ No newline at end of file diff --git a/rust/icp_transfer/src/icp_transfer_backend/icp_transfer_backend.did b/rust/icp_transfer/src/icp_transfer_backend/icp_transfer_backend.did deleted file mode 100644 index 6064f15755..0000000000 --- a/rust/icp_transfer/src/icp_transfer_backend/icp_transfer_backend.did +++ /dev/null @@ -1,8 +0,0 @@ -type Result = variant { Ok : nat64; Err : text }; -type Tokens = record { e8s : nat64 }; -type TransferArgs = record { - to_principal : principal; - to_subaccount : opt vec nat8; - amount : Tokens; -}; -service : { transfer : (TransferArgs) -> (Result) } From 89cc3ba7148e42b8cb33895028f958f6207c3674 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 16 Jun 2026 19:59:16 +0200 Subject: [PATCH 2/2] chore: pin ic-cdk to 0.18 to match existing Cargo.lock Co-Authored-By: Claude Sonnet 4.6 --- rust/icp_transfer/backend/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/icp_transfer/backend/Cargo.toml b/rust/icp_transfer/backend/Cargo.toml index 9536949116..c063b51031 100644 --- a/rust/icp_transfer/backend/Cargo.toml +++ b/rust/icp_transfer/backend/Cargo.toml @@ -8,6 +8,6 @@ crate-type = ["cdylib"] [dependencies] candid = "0.10" -ic-cdk = "0.20" +ic-cdk = "0.18" ic-ledger-types = "0.15.0" serde = "1.0"