From 9cff7c9f18deaae91dd3ca948f829dee8ea0163e Mon Sep 17 00:00:00 2001 From: Sam-Rytech Date: Wed, 22 Jul 2026 10:15:24 +0100 Subject: [PATCH 1/2] fix(splitter): fallback to claimable balance on failed transfer --- contracts/splitter/src/lib.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/contracts/splitter/src/lib.rs b/contracts/splitter/src/lib.rs index ed56786..e90b0a1 100644 --- a/contracts/splitter/src/lib.rs +++ b/contracts/splitter/src/lib.rs @@ -115,6 +115,7 @@ enum DataKey { Created(Address), HeldTokens(u64), PendingController(u64), + AccountBalance(Address, Address), } #[contractevent] @@ -644,6 +645,19 @@ impl Splitter { .persistent() .get(&DataKey::PendingController(id)) } + + /// Claims any fallback balances credited to this account due to failed payouts. + pub fn claim(env: Env, account: Address, token: Address) -> Result<(), Error> { + let key = DataKey::AccountBalance(account.clone(), token.clone()); + let held: i128 = env.storage().persistent().get(&key).unwrap_or(0); + if held == 0 { + return Err(Error::InvalidAmount); + } + env.storage().persistent().remove(&key); + let client = token::Client::new(&env, &token); + client.transfer(&env.current_contract_address(), &account, &held); + Ok(()) + } } fn load_created(env: &Env, creator: &Address) -> Vec { @@ -730,7 +744,17 @@ fn payout(env: &Env, split: &Split, from: &Address, token: &Address, amount: i12 continue; } match split.recipients.get_unchecked(i) { - Recipient::Account(addr) => client.transfer(from, &addr, &part), + Recipient::Account(addr) => { + match client.try_transfer(from, &addr, &part) { + Ok(Ok(())) => {} + _ => { + if from != &vault { + client.transfer(from, &vault, &part); + } + credit_account(env, &addr, token, part); + } + } + } Recipient::Split(child) => { if from != &vault { client.transfer(from, &vault, &part); @@ -741,6 +765,13 @@ fn payout(env: &Env, split: &Split, from: &Address, token: &Address, amount: i12 } } +fn credit_account(env: &Env, account: &Address, token: &Address, amount: i128) { + let key = DataKey::AccountBalance(account.clone(), token.clone()); + let held: i128 = env.storage().persistent().get(&key).unwrap_or(0); + env.storage().persistent().set(&key, &(held + amount)); + env.storage().persistent().extend_ttl(&key, TTL_THRESHOLD, TTL_EXTEND_TO); +} + fn credit(env: &Env, id: u64, token: &Address, amount: i128) { let key = DataKey::Balance(id, token.clone()); let held: i128 = env.storage().persistent().get(&key).unwrap_or(0); From 76b4ab7bd2c15231a11eb06b6c91dbf7460dc0d1 Mon Sep 17 00:00:00 2001 From: Sam-Rytech Date: Wed, 22 Jul 2026 10:21:03 +0100 Subject: [PATCH 2/2] fix(splitter): add claimable fallback for failed payouts (#260) --- contracts/splitter/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/splitter/src/lib.rs b/contracts/splitter/src/lib.rs index e90b0a1..b2973ad 100644 --- a/contracts/splitter/src/lib.rs +++ b/contracts/splitter/src/lib.rs @@ -744,17 +744,15 @@ fn payout(env: &Env, split: &Split, from: &Address, token: &Address, amount: i12 continue; } match split.recipients.get_unchecked(i) { - Recipient::Account(addr) => { - match client.try_transfer(from, &addr, &part) { - Ok(Ok(())) => {} - _ => { - if from != &vault { - client.transfer(from, &vault, &part); - } - credit_account(env, &addr, token, part); + Recipient::Account(addr) => match client.try_transfer(from, &addr, &part) { + Ok(Ok(())) => {} + _ => { + if from != &vault { + client.transfer(from, &vault, &part); } + credit_account(env, &addr, token, part); } - } + }, Recipient::Split(child) => { if from != &vault { client.transfer(from, &vault, &part); @@ -769,7 +767,9 @@ fn credit_account(env: &Env, account: &Address, token: &Address, amount: i128) { let key = DataKey::AccountBalance(account.clone(), token.clone()); let held: i128 = env.storage().persistent().get(&key).unwrap_or(0); env.storage().persistent().set(&key, &(held + amount)); - env.storage().persistent().extend_ttl(&key, TTL_THRESHOLD, TTL_EXTEND_TO); + env.storage() + .persistent() + .extend_ttl(&key, TTL_THRESHOLD, TTL_EXTEND_TO); } fn credit(env: &Env, id: u64, token: &Address, amount: i128) {