Skip to content

Commit 09445be

Browse files
authored
docs(ledgers): add Transaction deduplication section (#172)
## Summary Adds a dedicated `### Transaction deduplication` section to `docs/guides/digital-assets/ledgers.mdx`, immediately after `### Fee handling`. Previously, deduplication was a single sentence at the end of `### Fee handling`: "Always set `created_at_time` to enable deduplication. Without it, two identical transfers submitted within 24 hours both execute." Fee handling and deduplication are separate concerns, and a developer scanning the page for deduplication guidance would not find it there. The new section covers: - **What it does**: the ledger tracks `(args, created_at_time)` pairs and rejects exact duplicates within 24 hours with `Duplicate { duplicate_of: block_index }`. The `duplicate_of` field lets you confirm the original succeeded without re-submitting. - **How to enable it**: references the existing `sendTokens` (Motoko: `Nat64.fromNat(Int.abs(Time.now()))`) and `send_tokens` (Rust: `ic_cdk::api::time()`) examples already on the page — no redundant code tabs needed. - **Boundary errors**: `TooOld` (timestamp older than 24h, window no longer tracked) and `CreatedInFuture { ledger_time }` (timestamp ahead of ledger time, `ledger_time` field helps diagnose clock drift). - **When `null` is acceptable**: one-off manual CLI calls where double-submission is not a concern. Also updates the `bitcoin.mdx` CLI note to link to `#transaction-deduplication` instead of the broader `#transferring-assets-icrc-1` anchor. ## Related - Surfaced while writing the ckBTC CLI walkthrough in PR #171 - Opened [dfinity/icskills#178](dfinity/icskills#178) to track updating the ckBTC skill's CLI examples ## Sync recommendation Hand-written. Content is derived from the ICRC-1 specification (`created_at_time` deduplication semantics) and verified against the existing Motoko/Rust examples in this file.
1 parent 28bacc8 commit 09445be

2 files changed

Lines changed: 18 additions & 2 deletions

File tree

docs/guides/chain-fusion/bitcoin.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ icp canister call mxzaz-hqaaa-aaaar-qaada-cai icrc1_transfer \
454454
})" -n ic
455455
```
456456

457-
`created_at_time = null` skips deduplication: if you run this command twice, both transfers execute. In production canister code, set this field to the current nanosecond timestamp so that retried calls are rejected as duplicates rather than sending twice. See [Transferring assets (ICRC-1)](../digital-assets/ledgers.md#transferring-assets-icrc-1) for details.
457+
`created_at_time = null` skips deduplication: if you run this command twice, both transfers execute. In production canister code, set this field to the current nanosecond timestamp so that retried calls are rejected as duplicates rather than sending twice. See [Transaction deduplication](../digital-assets/ledgers.md#transaction-deduplication) for details.
458458

459459
### Common mistakes
460460

docs/guides/digital-assets/ledgers.mdx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,23 @@ Always set the `fee` field explicitly. If you pass a fee that does not match the
172172
icp canister call ryjl3-tyaaa-aaaaa-aaaba-cai icrc1_fee '()' -n ic
173173
```
174174

175-
Always set `created_at_time` to enable deduplication. Without it, two identical transfers submitted within 24 hours both execute.
175+
### Transaction deduplication
176+
177+
When `created_at_time` is set to the current nanosecond timestamp, the ledger tracks submitted transactions and rejects exact duplicates within a 24-hour window. A duplicate submission returns `Duplicate { duplicate_of: block_index }` instead of executing again. The `duplicate_of` value is the block index of the original accepted transaction, so you can confirm it succeeded without re-submitting.
178+
179+
Without `created_at_time` (set to `null`), every submission is treated as a new transaction: submitting the same call twice sends the amount twice.
180+
181+
Set `created_at_time` to the current nanosecond timestamp to enable deduplication:
182+
183+
- **Motoko**: `Nat64.fromNat(Int.abs(Time.now()))` (as shown in `sendTokens` above)
184+
- **Rust**: `ic_cdk::api::time()` (as shown in `send_tokens` above)
185+
186+
Two boundary errors to handle alongside the normal transfer errors:
187+
188+
- `TooOld`: the timestamp is more than 24 hours in the past. The ledger no longer tracks that window and rejects the transaction.
189+
- `CreatedInFuture { ledger_time }`: the timestamp is ahead of the ledger's current time, typically due to system clock drift. The `ledger_time` field shows the ledger's view of the current time so you can diagnose the skew.
190+
191+
Always set `created_at_time` in production canister code. `null` is only appropriate for one-off manual CLI calls where double-submission is not a concern.
176192

177193
## Checking balances
178194

0 commit comments

Comments
 (0)