Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
fc1e131
add fee collector operation
maciejdfinity Oct 24, 2025
9e7c58d
index build fix
maciejdfinity Oct 24, 2025
8914b52
remove unused conversion
maciejdfinity Oct 27, 2025
440545a
convert back from flattened
maciejdfinity Oct 27, 2025
5ed8d30
clippy
maciejdfinity Oct 28, 2025
b1a7a40
panic for fee col blocks
maciejdfinity Oct 28, 2025
372c508
build fix
maciejdfinity Oct 29, 2025
1bdf8a3
Merge branch 'master' into maciej-feecol-block
maciejdfinity Oct 29, 2025
3dcf6a6
build fix
maciejdfinity Oct 29, 2025
6a30c76
Merge branch 'master' into maciej-feecol-block
maciejdfinity Oct 29, 2025
d010fbd
revert unintentional change
maciejdfinity Oct 29, 2025
42072ed
build fix
maciejdfinity Oct 30, 2025
0398011
update did files
maciejdfinity Oct 30, 2025
cfbbc26
Merge branch 'master' into maciej-feecol-block
maciejdfinity Oct 31, 2025
a5b886c
handle new fee collector in index
maciejdfinity Oct 31, 2025
2fe87b4
add fee collector to approve
maciejdfinity Oct 31, 2025
5d5e118
clippy
maciejdfinity Oct 31, 2025
8102274
refactor get_fee_collector
maciejdfinity Nov 11, 2025
3f83e5f
Merge branch 'master' into maciej-feecol-block
maciejdfinity Nov 11, 2025
cbe5156
clippy
maciejdfinity Nov 11, 2025
b5530ec
change op name to 107set_fee_collector
maciejdfinity Nov 12, 2025
fd4dab5
add fee collector block generation to the test block builder
maciejdfinity Nov 12, 2025
08aba67
fix block conversion, caller doesn't work still
maciejdfinity Nov 13, 2025
eba90cd
change caller to principal
maciejdfinity Nov 13, 2025
8125a18
build fix
maciejdfinity Nov 13, 2025
a782c66
update did files
maciejdfinity Nov 13, 2025
a201faf
create a proptest
maciejdfinity Nov 13, 2025
5f2c5df
test u64 and u256 blocks
maciejdfinity Nov 13, 2025
f28d46b
change created_at_time to ts
maciejdfinity Nov 13, 2025
eeca831
test index handling of the 107 fee collector
maciejdfinity Nov 13, 2025
c9a3a32
change panic message
maciejdfinity Nov 13, 2025
61d4e0b
build fix
maciejdfinity Nov 13, 2025
975cdc9
clippy
maciejdfinity Nov 13, 2025
50e90ae
test approve fees
maciejdfinity Nov 13, 2025
bfb852e
small refactor
maciejdfinity Nov 14, 2025
17cb2b2
Update rs/ledger_suite/icrc1/index-ng/src/main.rs
maciejdfinity Nov 14, 2025
dc2eb00
Update rs/ledger_suite/icrc1/test_utils/src/icrc3.rs
maciejdfinity Nov 14, 2025
3a858f5
Update rs/ledger_suite/icrc1/index-ng/src/main.rs
maciejdfinity Nov 14, 2025
26b1efc
clippy
maciejdfinity Nov 14, 2025
a08a50f
Merge branch 'master' into maciej-feecol-block
maciejdfinity Nov 14, 2025
de06078
rustdoc for fee collector 107
maciejdfinity Nov 14, 2025
f3c2280
Update rs/ledger_suite/icrc1/index-ng/src/main.rs
maciejdfinity Nov 18, 2025
fec306f
Merge branch 'master' into maciej-feecol-block
maciejdfinity Nov 18, 2025
3c8283f
update changelog
maciejdfinity Nov 19, 2025
bf53fb8
rename fee_collector to set_fee_collector
maciejdfinity Nov 24, 2025
8f8f140
Merge branch 'master' into maciej-feecol-block
maciejdfinity Nov 24, 2025
b41b566
update canbench results
maciejdfinity Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/icrc-ledger-types/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add ICRC-107 fee collector transaction type.

## 0.1.12

### Added
Expand Down
15 changes: 15 additions & 0 deletions packages/icrc-ledger-types/src/icrc/generic_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,21 @@ impl From<Account> for Value {
}
}

impl TryFrom<Value> for Principal {
type Error = String;

fn try_from(value: Value) -> Result<Self, Self::Error> {
Principal::try_from_slice(value.as_blob()?.as_slice())
.map_err(|err| format!("Unable to decode the principal, error {err}"))
}
}

impl From<Principal> for Value {
fn from(principal: Principal) -> Self {
Self::blob(principal.as_slice())
}
}

impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down
26 changes: 25 additions & 1 deletion packages/icrc-ledger-types/src/icrc3/transactions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use candid::{CandidType, Deserialize, Nat};
use candid::{CandidType, Deserialize, Nat, Principal};
use serde::Serialize;

use crate::{
Expand Down Expand Up @@ -58,6 +58,13 @@ pub struct Approve {
pub created_at_time: Option<u64>,
}

#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct FeeCollector {
pub fee_collector: Option<Account>,
pub caller: Option<Principal>,
pub ts: Option<u64>,
}

// Representation of a Transaction which supports the Icrc1 Standard functionalities
#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Transaction {
Expand All @@ -66,6 +73,7 @@ pub struct Transaction {
pub burn: Option<Burn>,
pub transfer: Option<Transfer>,
pub approve: Option<Approve>,
pub fee_collector: Option<FeeCollector>,
pub timestamp: u64,
}

Expand All @@ -78,6 +86,7 @@ impl Transaction {
burn: Some(burn),
transfer: None,
approve: None,
fee_collector: None,
}
}

Expand All @@ -89,6 +98,7 @@ impl Transaction {
burn: None,
transfer: None,
approve: None,
fee_collector: None,
}
}

Expand All @@ -100,6 +110,7 @@ impl Transaction {
burn: None,
transfer: Some(transfer),
approve: None,
fee_collector: None,
}
}

Expand All @@ -111,6 +122,19 @@ impl Transaction {
burn: None,
transfer: None,
approve: Some(approve),
fee_collector: None,
}
}

pub fn set_fee_collector(fee_collector: FeeCollector, timestamp: u64) -> Self {
Self {
kind: "107set_fee_collector".into(),
timestamp,
mint: None,
burn: None,
transfer: None,
approve: None,
fee_collector: Some(fee_collector),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions rs/ledger_suite/icrc1/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ rust_test(
"//packages/ic-ledger-hash-of:ic_ledger_hash_of",
"//rs/ledger_suite/icrc1/tokens_u256",
"//rs/ledger_suite/icrc1/tokens_u64",
"//rs/types/base_types",
] + DEV_DEPENDENCIES,
)

Expand Down
7 changes: 7 additions & 0 deletions rs/ledger_suite/icrc1/archive/archive.did
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type Transaction = record {
kind : text;
mint : opt Mint;
approve : opt Approve;
fee_collector : opt FeeCollector;
timestamp : nat64;
transfer : opt Transfer
};
Expand All @@ -21,6 +22,12 @@ type Approve = record {
spender : Account
};

type FeeCollector = record {
caller : opt principal;
fee_collector : opt Account;
ts : opt nat64
};

type Burn = record {
from : Account;
memo : opt vec nat8;
Expand Down
2 changes: 2 additions & 0 deletions rs/ledger_suite/icrc1/archive/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fn test_icrc3_get_blocks() {
created_at_time: None,
memo: None,
},
btype: None,
}
};

Expand Down Expand Up @@ -265,6 +266,7 @@ fn test_icrc3_get_blocks_number_of_blocks_limit() {
created_at_time: None,
memo: None,
},
btype: None,
}
.encode()
}
Expand Down
7 changes: 7 additions & 0 deletions rs/ledger_suite/icrc1/index-ng/index-ng.did
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,17 @@ type Transaction = record {
kind : text;
mint : opt Mint;
approve : opt Approve;
fee_collector : opt FeeCollector;
timestamp : nat64;
transfer : opt Transfer
};

type FeeCollector = record {
caller : opt principal;
fee_collector : opt Account;
ts : opt nat64
};

type Approve = record {
fee : opt Tokens;
from : Account;
Expand Down
64 changes: 55 additions & 9 deletions rs/ledger_suite/icrc1/index-ng/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ struct State {
/// index. Lower values will result in a more responsive UI, but higher costs due to increased
/// cycle burn for the index, ledger and archive(s).
retrieve_blocks_from_ledger_interval: Option<Duration>,

/// The ICRC-107 fee collector. Example values:
/// - `None` - legacy fee collector is used.
/// - `Some(None)` - 107 fee collector is enabled but fees are burned.
/// - `Some(Some(account1))` - 107 fee collector is enabled, `account1` collects the fees.
fee_collector_107: Option<Option<Account>>,
}

impl State {
Expand All @@ -161,6 +167,7 @@ impl Default for State {
fee_collectors: Default::default(),
last_fee: None,
retrieve_blocks_from_ledger_interval: None,
fee_collector_107: None,
}
}
}
Expand Down Expand Up @@ -783,8 +790,11 @@ fn append_block(block_index: BlockIndex64, block: GenericBlock) -> Result<(), Sy
}
});

// change the fee collector if block is a 107 block
process_fee_collector_block(&decoded_block);

// add the block to the fee_collector if one is set
index_fee_collector(block_index, &decoded_block);
index_fee_collector(block_index);

// change the balance of the involved accounts
process_balance_changes(block_index, &decoded_block);
Expand Down Expand Up @@ -828,8 +838,18 @@ fn append_icrc3_blocks(new_blocks: Vec<BlockWithId>) -> Result<(), SyncError> {
Ok(())
}

fn index_fee_collector(block_index: BlockIndex64, block: &Block<Tokens>) {
if let Some(fee_collector) = get_fee_collector(block_index, block) {
fn process_fee_collector_block(block: &Block<Tokens>) {
if let Operation::FeeCollector {
fee_collector,
caller: _,
} = block.transaction.operation
{
mutate_state(|s| s.fee_collector_107 = Some(fee_collector));
}
}

fn index_fee_collector(block_index: BlockIndex64) {
if let Some(fee_collector) = get_fee_collector() {
mutate_state(|s| {
s.fee_collectors
.entry(fee_collector)
Expand Down Expand Up @@ -875,7 +895,7 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block<Tokens>) {
))
});
mutate_state(|s| s.last_fee = Some(fee));
if let Some(fee_collector) = get_fee_collector(block_index, block) {
if let Some(fee_collector) = get_fee_collector() {
credit(block_index, fee_collector, fee);
}
}
Expand All @@ -891,7 +911,7 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block<Tokens>) {
))
});
mutate_state(|s| s.last_fee = Some(fee));
if let Some(fee_collector) = get_fee_collector(block_index, block) {
if let Some(fee_collector) = get_fee_collector() {
credit(block_index, fee_collector, fee);
}
}
Expand Down Expand Up @@ -920,7 +940,7 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block<Tokens>) {
}),
);
credit(block_index, to, amount);
if let Some(fee_collector) = get_fee_collector(block_index, block) {
if let Some(fee_collector) = get_fee_collector() {
credit(block_index, fee_collector, fee);
}
}
Expand Down Expand Up @@ -954,6 +974,16 @@ fn process_balance_changes(block_index: BlockIndex64, block: &Block<Tokens>) {
change_balance(spender, |balance| balance);

debit(block_index, from, fee);

if let Some(fee_collector_107) = get_fee_collector_107().flatten() {
credit(block_index, fee_collector_107, fee);
}
}
Operation::FeeCollector {
fee_collector: _,
caller: _,
} => {
// Does not affect the balance
}
},
);
Expand Down Expand Up @@ -989,19 +1019,35 @@ fn get_accounts(block: &Block<Tokens>) -> Vec<Account> {
Operation::Mint { to, .. } => vec![to],
Operation::Transfer { from, to, .. } => vec![from, to],
Operation::Approve { from, .. } => vec![from],
Operation::FeeCollector { .. } => vec![],
}
}

fn get_fee_collector(block_index: BlockIndex64, block: &Block<Tokens>) -> Option<Account> {
fn get_fee_collector() -> Option<Account> {
get_fee_collector_107().unwrap_or_else(get_legacy_fee_collector)
}

fn get_fee_collector_107() -> Option<Option<Account>> {
with_state(|s| s.fee_collector_107)
}

fn get_legacy_fee_collector() -> Option<Account> {
let chain_length = with_blocks(|blocks| blocks.len());
if chain_length == 0 {
return None;
}
let last_block_index = chain_length - 1;
let block = get_decoded_block(last_block_index)
.expect("chain_length is positive, should have at least one block");
if block.fee_collector.is_some() {
block.fee_collector
} else if let Some(fee_collector_block_index) = block.fee_collector_block_index {
let block = get_decoded_block(fee_collector_block_index)
.unwrap_or_else(||
ic_cdk::trap(format!("Block at index {block_index} has fee_collector_block_index {fee_collector_block_index} but there is no block at that index")));
ic_cdk::trap(format!("Block at index {last_block_index} has fee_collector_block_index {fee_collector_block_index} but there is no block at that index")));
if block.fee_collector.is_none() {
ic_cdk::trap(format!(
"Block at index {block_index} has fee_collector_block_index {fee_collector_block_index} but that block has no fee_collector set"
"Block at index {last_block_index} has fee_collector_block_index {fee_collector_block_index} but that block has no fee_collector set"
))
} else {
block.fee_collector
Expand Down
Loading
Loading