Issue: #502
Status: Implemented
Last Updated: 2026-05-28
Beneficiary Conflict Resolution provides automated conflict resolution when multiple beneficiaries claim the same vault or have conflicting claims. This feature enables beneficiaries to file conflict claims and allows administrators to resolve disputes fairly.
- Multiple Claimants: When multiple parties claim to be the rightful beneficiary
- Disputed Inheritance: When beneficiary status is contested
- Claim Documentation: When beneficiaries need to formally document their claim
- Admin Resolution: When administrators need to resolve disputes and approve the rightful beneficiary
Caller: Beneficiary only
Auth: Required
Beneficiary files a conflict claim for a vault.
Parameters:
vault_id: The vault IDreason: Reason for the conflict claim (must not be empty)
Returns: Ok(()) on success, or ContractError on failure
Errors:
InvalidAmount: If reason is emptyNotBeneficiary: If caller is not the beneficiaryVaultNotFound: If vault does not exist
Events: Emits BENEFICIARY_CONFLICT_FILED_TOPIC with vault_id and beneficiary
Example:
client.file_beneficiary_conflict(&vault_id, &String::from_str(&env, "Conflicting claim from another party"))?;resolve_beneficiary_conflict(vault_id: u64, approved_beneficiary: Address) -> Result<(), ContractError>
Caller: Admin only
Auth: Required
Administrator resolves a beneficiary conflict by approving the rightful beneficiary.
Parameters:
vault_id: The vault IDapproved_beneficiary: The address of the approved beneficiary
Returns: Ok(()) on success, or ContractError on failure
Errors:
InvalidBeneficiary: If no conflict exists or conflict already resolvedNotAdmin: If caller is not an administrator
Events: Emits BENEFICIARY_CONFLICT_RESOLVED_TOPIC with vault_id and approved beneficiary
Example:
client.resolve_beneficiary_conflict(&vault_id, &approved_beneficiary)?;Caller: Anyone
Auth: Not required
Retrieves the beneficiary conflict entry if it exists.
Returns:
Some(BeneficiaryConflict)if a conflict existsNoneif no conflict has been filed
Structure:
pub struct BeneficiaryConflict {
pub vault_id: u64,
pub claims: Vec<BeneficiaryConflictClaim>,
pub resolution: ConflictResolution,
pub resolved_at: Option<u64>,
}
pub struct BeneficiaryConflictClaim {
pub claimant: Address,
pub reason: String,
pub filed_at: u64,
}
pub enum ConflictResolution {
Pending,
Approved(Address),
Rejected,
}Example:
if let Some(conflict) = client.get_beneficiary_conflict(&vault_id) {
println!("Claims: {}", conflict.claims.len());
println!("Resolution: {:?}", conflict.resolution);
}- Beneficiary Initiates: Beneficiary calls
file_beneficiary_conflict()with reason - Claim Recorded: Claim is added to vault's conflict record with timestamp
- Event Emitted:
BENEFICIARY_CONFLICT_FILED_TOPICevent is published - Status: Conflict remains in
Pendingstate
- Admin Reviews: Administrator reviews all claims
- Admin Approves: Administrator calls
resolve_beneficiary_conflict()with approved beneficiary - Resolution Recorded: Conflict status changes to
Approved(Address) - Timestamp Set: Resolution timestamp is recorded
- Event Emitted:
BENEFICIARY_CONFLICT_RESOLVED_TOPICevent is published
- Multiple beneficiaries can file claims for the same vault
- All claims are recorded in order with timestamps
- Administrator reviews all claims before resolving
- Only one beneficiary can be approved
Emitted when a beneficiary files a conflict claim.
Data:
(vault_id: u64, beneficiary: Address)
Emitted when a conflict is resolved.
Data:
(vault_id: u64, approved_beneficiary: Address)
| Error | Cause | Resolution |
|---|---|---|
InvalidAmount |
Reason is empty | Provide a non-empty reason |
NotBeneficiary |
Caller is not beneficiary | Call as the beneficiary |
InvalidBeneficiary |
No conflict exists or already resolved | File a new conflict first |
VaultNotFound |
Vault does not exist | Verify vault ID |
- Conflicts do not block release
- Conflicts are informational for audit trail
- Release proceeds normally even with pending conflicts
- Conflicts apply to primary beneficiary
- Multi-beneficiary splits are not affected
- Each beneficiary can file independent claims
- Conflicts are separate from disputes
- Disputes block release; conflicts do not
- Both can exist independently
// Beneficiary files conflict claim
let reason = String::from_str(&env, "Another party claims to be beneficiary");
client.file_beneficiary_conflict(&vault_id, &reason)?;
// Admin reviews and resolves
client.resolve_beneficiary_conflict(&vault_id, &beneficiary)?;
// Check resolution
if let Some(conflict) = client.get_beneficiary_conflict(&vault_id) {
match conflict.resolution {
ConflictResolution::Approved(addr) => println!("Approved: {}", addr),
_ => println!("Not resolved"),
}
}// First beneficiary files claim
client.file_beneficiary_conflict(&vault_id, &reason1)?;
// Second beneficiary files claim
client.file_beneficiary_conflict(&vault_id, &reason2)?;
// Check all claims
if let Some(conflict) = client.get_beneficiary_conflict(&vault_id) {
for claim in conflict.claims.iter() {
println!("Claimant: {}, Reason: {}", claim.claimant, claim.reason);
}
}match client.get_beneficiary_conflict(&vault_id) {
Some(conflict) => {
println!("Conflict exists with {} claims", conflict.claims.len());
match conflict.resolution {
ConflictResolution::Pending => println!("Awaiting resolution"),
ConflictResolution::Approved(addr) => println!("Approved: {}", addr),
ConflictResolution::Rejected => println!("Rejected"),
}
}
None => println!("No conflict"),
}Comprehensive tests are included in contracts/ttl_vault/src/test.rs:
test_file_beneficiary_conflict_beneficiary_only- Validates beneficiary-only accesstest_file_beneficiary_conflict_owner_fails- Ensures owner cannot filetest_file_beneficiary_conflict_empty_reason_fails- Tests reason validationtest_resolve_beneficiary_conflict_admin_only- Validates admin-only accesstest_resolve_beneficiary_conflict_non_admin_fails- Ensures non-admin cannot resolvetest_resolve_beneficiary_conflict_no_conflict_fails- Tests error handlingtest_get_beneficiary_conflict_not_set- Tests query when not settest_file_multiple_beneficiary_conflicts- Tests multiple claimstest_beneficiary_conflict_stores_timestamp- Validates timestamp storagetest_beneficiary_conflict_emits_event- Validates event emission
- Beneficiary-Only Filing: Only beneficiaries can file claims
- Admin-Only Resolution: Only administrators can resolve conflicts
- Immutable Claims: Claims cannot be modified after filing
- Audit Trail: All claims and resolutions are recorded on-chain
- No Blocking: Conflicts don't block vault operations
- Voting-based conflict resolution (multiple admins vote)
- Time-based auto-resolution (if not resolved within X days)
- Beneficiary acceptance of resolution
- Conflict appeal mechanism
- Integration with legal document anchoring