Skip to content

Commit 5736146

Browse files
authored
chore: migrate rust/icp_transfer to icp-cli (#1412)
1 parent bc34020 commit 5736146

19 files changed

Lines changed: 319 additions & 519 deletions

File tree

‎.github/workflows/icp_transfer.yml‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
pull_request:
77
paths:
88
- motoko/icp_transfer/**
9+
- rust/icp_transfer/**
910
- .github/workflows/icp_transfer.yml
1011

1112
concurrency:
@@ -26,3 +27,17 @@ jobs:
2627
icp network start -d
2728
icp deploy
2829
make test
30+
31+
rust-icp_transfer:
32+
runs-on: ubuntu-24.04
33+
container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1
34+
env:
35+
ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36+
steps:
37+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
38+
- name: Deploy and test
39+
working-directory: rust/icp_transfer
40+
run: |
41+
icp network start -d
42+
icp deploy
43+
make test

‎.github/workflows/rust-icp-transfer-example.yml‎

Lines changed: 0 additions & 43 deletions
This file was deleted.

‎motoko/icp_transfer/Makefile‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,11 @@ test:
4646
echo " PASS"; \
4747
\
4848
echo "=== Test 3: transferToAccountId — same recipient, same deduction ==="; \
49-
account_id_vec="vec { $$(echo "$$cli_hex" | fold -w2 | while read b; do printf '%d : nat8; ' "0x$$b"; done | sed 's/; $$//') }"; \
50-
echo " AccountIdentifier: $$account_id_vec"; \
49+
echo " AccountIdentifier hex: $$cli_hex"; \
5150
backend_before=$$(get_balance "$$backend"); \
5251
recipient_before=$$(get_balance "$$recipient"); \
5352
result=$$(icp canister call backend transferToAccountId \
54-
"(record { e8s = 99_990_000 : nat64 }, $$account_id_vec)"); \
53+
"(record { e8s = 99_990_000 : nat64 }, \"$$cli_hex\")"); \
5554
echo " result: $$result"; \
5655
echo "$$result" | grep -q 'ok' || (echo "FAIL: transfer rejected" && exit 1); \
5756
backend_after=$$(get_balance "$$backend"); \
@@ -60,4 +59,4 @@ test:
6059
echo " recipient: $$recipient_before → $$recipient_after (delta $$((recipient_after - recipient_before)))"; \
6160
[ "$$((backend_after - backend_before))" -eq -100000000 ] || (echo "FAIL: expected backend delta -100000000 e8s" && exit 1); \
6261
[ "$$((recipient_after - recipient_before))" -eq 99990000 ] || (echo "FAIL: expected recipient delta +99990000 e8s" && exit 1); \
63-
echo " PASS — same recipient reached via AccountIdentifier blob as via principal"
62+
echo " PASS — same recipient reached via AccountIdentifier hex as via principal"

‎motoko/icp_transfer/README.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The example exposes three functions to make this concrete:
1010

1111
- **`toAccountIdHex(principal, subaccount)`** — query returning the AccountIdentifier as a 64-char lowercase hex string, the format shown in block explorers and CEX deposit screens.
1212
- **`transferToPrincipal(amount, principal, subaccount)`** — calls `toLedgerAccount` internally. Use this when you have a principal.
13-
- **`transferToAccountId(amount, accountId)`** — accepts the raw blob directly. Use this when an exchange or external service gives you the destination as an AccountIdentifier rather than as a principal.
13+
- **`transferToAccountId(amount, accountIdHex)`** — accepts the AccountIdentifier as a 64-char hex string (the format CEXs and block explorers provide).
1414

1515
> The ICP ledger also supports the [ICRC-1](https://github.com/dfinity/ICRC-1) standard via `icrc1_transfer`. For new token integrations that don't require AccountIdentifier compatibility, ICRC-1 is the recommended interface. A comprehensive ICRC ledger example is planned.
1616
@@ -34,11 +34,11 @@ icp network stop
3434
`make test` funds the backend with 2 ICP, then:
3535
1. Compares `icp identity account-id --format ledger` with `toAccountIdHex` to verify the CLI and the backend compute the same AccountIdentifier.
3636
2. Calls `transferToPrincipal` — transfers 99_990_000 e8s (amount) + 10_000 e8s (fee) = exactly 1 ICP deducted from the backend; confirms both sides via `icp token balance`.
37-
3. Calls `transferToAccountId` with the AccountIdentifier as `vec nat8` — same 1 ICP deduction, confirming both transfer paths reach the same account and the backend balance reaches zero.
37+
3. Calls `transferToAccountId` with the hex string from step 1 — same 1 ICP deduction, confirming both transfer paths reach the same account and the backend balance reaches zero.
3838

3939
## Security considerations and best practices
4040

4141
Refer to the [security best practices](https://docs.internetcomputer.org/guides/security/overview) for information on security and best practices for your ICP app. For this example the following aspects are particularly relevant:
4242

43-
- [Inter-canister calls and rollbacks](https://docs.internetcomputer.org/guides/security/overview): issues around inter-canister calls (here the ledger) can lead to time-of-check time-of-use or double-spending bugs.
44-
- [Certify query responses if they are relevant for security](https://docs.internetcomputer.org/guides/security/overview): essential when displaying financial data that users act on.
43+
- [Securely handle traps in callbacks](https://docs.internetcomputer.org/guides/security/inter-canister-calls/#securely-handle-traps-in-callbacks): issues around inter-canister calls (here the ledger) can lead to time-of-check time-of-use or double-spending bugs.
44+
- [Certified variables](https://docs.internetcomputer.org/guides/security/data-integrity-and-authenticity/#certified-variables): essential when displaying financial data that users act on.

‎motoko/icp_transfer/backend/app.mo‎

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Array "mo:core/Array";
2+
import Blob "mo:core/Blob";
23
import Debug "mo:core/Debug";
34
import Hex "mo:hex";
45
import Result "mo:core/Result";
@@ -82,11 +83,23 @@ actor IcpTransfer {
8283
await doTransfer(amount, toPrincipal.toLedgerAccount(toSubaccount));
8384
};
8485

85-
// Transfer ICP to a recipient identified by an AccountIdentifier blob.
86-
// Use this when an exchange or external service gives you the destination
87-
// as a raw blob rather than as a principal.
88-
public shared func transferToAccountId(amount : Tokens, toAccountId : AccountIdentifier) : async Result.Result<BlockIndex, Text> {
89-
Debug.print("Transferring " # debug_show amount # " to account id " # debug_show toAccountId);
90-
await doTransfer(amount, toAccountId);
86+
// Transfer ICP to a recipient identified by an AccountIdentifier hex string.
87+
// Use this when an exchange or block explorer gives you the destination as a
88+
// 64-char hex string rather than as a principal.
89+
//
90+
// Note: CRC32 checksum validation is not performed here — the ICP ledger
91+
// validates it and returns a clear error on mismatch. A fromHex helper with
92+
// CRC32 validation (equivalent to AccountIdentifier::from_hex in ic-ledger-types)
93+
// would be a valuable addition to a Motoko ICP library.
94+
public shared func transferToAccountId(amount : Tokens, toAccountIdHex : Text) : async Result.Result<BlockIndex, Text> {
95+
switch (Hex.toArray(toAccountIdHex)) {
96+
case (#err(e)) #err("invalid hex: " # e);
97+
case (#ok(bytes)) {
98+
if (bytes.size() != 32) {
99+
return #err("AccountIdentifier must be 32 bytes (64 hex chars), got " # debug_show(bytes.size()));
100+
};
101+
await doTransfer(amount, Blob.fromArray(bytes));
102+
};
103+
};
91104
};
92105
};

‎rust/icp_transfer/.gitignore‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
.DS_Store
88
**/.DS_Store
99

10-
# dfx temporary files
11-
.dfx/
12-
1310
# generated files
1411
**/declarations/
1512

0 commit comments

Comments
 (0)