Skip to content

Commit 1382f67

Browse files
marc0oloclaude
andcommitted
refine(rust/icp_transfer): accept hex string in transfer_to_account_id
Passing a 64-char hex string (the format CEXs and block explorers provide) is cleaner than a raw blob: AccountIdentifier::from_hex validates the CRC32 in one call with no byte-manipulation, the Makefile test passes the hex string directly, and the Candid type text avoids vec nat8 complexity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 629ac9c commit 1382f67

3 files changed

Lines changed: 10 additions & 13 deletions

File tree

‎rust/icp_transfer/Makefile‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,11 @@ test:
4646
echo " PASS"; \
4747
\
4848
echo "=== Test 3: transfer_to_account_id — 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 transfer_to_account_id \
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"); \

‎rust/icp_transfer/README.md‎

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

1111
- **`to_account_id_hex(principal, subaccount)`** — query returning the AccountIdentifier as a 64-char lowercase hex string, the format shown in block explorers and CEX deposit screens.
1212
- **`transfer_to_principal(amount, principal, subaccount)`** — calls `AccountIdentifier::new` internally. Use this when you have a principal.
13-
- **`transfer_to_account_id(amount, account_id)`** — accepts the raw 32-byte blob directly. Use this when an exchange or external service gives you the destination as an AccountIdentifier rather than as a principal.
13+
- **`transfer_to_account_id(amount, account_id_hex)`** — accepts the AccountIdentifier as a 64-char hex string (the format CEXs and block explorers provide). Validates the embedded CRC32 checksum before transferring.
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
@@ -33,7 +33,7 @@ icp network stop
3333
`make test` funds the backend with 2 ICP, then:
3434
1. Compares `icp identity account-id --format ledger` with `to_account_id_hex` to verify CLI and backend compute the same AccountIdentifier.
3535
2. Calls `transfer_to_principal` — transfers 99_990_000 e8s (amount) + 10_000 e8s (fee) = exactly 1 ICP deducted from the backend; confirms both sides via `icp token balance`.
36-
3. Calls `transfer_to_account_id` with the AccountIdentifier as `vec { N : nat8; ... }` — same 1 ICP deduction, confirming both transfer paths reach the same account and the backend balance reaches zero.
36+
3. Calls `transfer_to_account_id` 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.
3737

3838
## Security considerations and best practices
3939

‎rust/icp_transfer/backend/src/lib.rs‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,16 @@ async fn transfer_to_principal(
2626
do_transfer(amount, AccountIdentifier::new(&to_principal, &sub)).await
2727
}
2828

29-
// Transfer ICP to a recipient identified by a raw AccountIdentifier (32 bytes).
30-
// Use this when an exchange or external service gives you the destination as an
31-
// AccountIdentifier blob rather than as a principal.
32-
//
33-
// The 32-byte AccountIdentifier format is: 4-byte CRC32 checksum + 28-byte SHA224 hash.
29+
// Transfer ICP to a recipient identified by an AccountIdentifier hex string.
30+
// Use this when an exchange or block explorer gives you the destination as a
31+
// 64-char hex string rather than as a principal. The CRC32 checksum embedded
32+
// in the hex is validated before the transfer is attempted.
3433
#[update]
3534
async fn transfer_to_account_id(
3635
amount: Tokens,
37-
to_account_id: Vec<u8>,
36+
to_account_id_hex: String,
3837
) -> Result<BlockIndex, String> {
39-
let hex: String = to_account_id.iter().map(|b| format!("{:02x}", b)).collect();
40-
let account_id = AccountIdentifier::from_hex(&hex)
38+
let account_id = AccountIdentifier::from_hex(&to_account_id_hex)
4139
.map_err(|e| format!("invalid AccountIdentifier: {}", e))?;
4240
do_transfer(amount, account_id).await
4341
}

0 commit comments

Comments
 (0)