Module (nplex=0x0)::fractional
NPLEX Fractional - Fractionalize LTC1 Tokens into real Coins
Allows investors to split their LTC1Token balance into real fungible Coin
- Struct
FractionalVault - Constants
- Function
fractionalize - Function
redeem - Function
merge_back - Function
destroy_empty_vault - Function
vault_package_id - Function
vault_total_supply - Function
vault_total_fractionalized
use (iota_identity=0x0)::controller;
use (iota_identity=0x0)::permissions;
use (iota_notarization=0x0)::method;
use (iota_notarization=0x0)::notarization;
use (iota_notarization=0x0)::timelock;
use (nplex=0x0)::events;
use (nplex=0x0)::ltc1;
use (nplex=0x0)::registry;
use iota::address;
use iota::bag;
use iota::balance;
use iota::borrow;
use iota::clock;
use iota::coin;
use iota::config;
use iota::deny_list;
use iota::display;
use iota::dynamic_field;
use iota::dynamic_object_field;
use iota::event;
use iota::hex;
use iota::object;
use iota::package;
use iota::table;
use iota::transfer;
use iota::tx_context;
use iota::types;
use iota::url;
use iota::vec_map;
use std::address;
use std::ascii;
use std::bcs;
use std::option;
use std::string;
use std::type_name;
use std::vector;
Struct FractionalVault
Shared vault holding the TreasuryCap for a fractionalized LTC1Token. Anyone can redeem Coin
public struct FractionalVault<phantom F> has key
Fields
-
id: iota::object::UID -
package_id: iota::object::ID - Which LTC1Package the fractionalized token belongs to
-
treasury_cap: iota::coin::TreasuryCap<F> - The locked TreasuryCap — only this contract can mint/burn
-
total_claimed_snapshot: u64 - Snapshot of claimed_revenue at fractionalization time (for proportional accounting)
-
total_fractionalized: u64 - Total balance that was fractionalized (= total coins minted)
Constants
const E_TREASURY_NOT_FRESH: u64 = 2001;
const E_PACKAGE_MISMATCH: u64 = 2002;
const E_ZERO_AMOUNT: u64 = 2003;
Function fractionalize
Fractionalize: convert amount of an LTC1Token’s balance into real Coin
Security: asserts TreasuryCap total_supply == 0 (no pre-minting allowed). The TreasuryCap is locked in a shared FractionalVault.
Caller must own the LTC1Token and pass a fresh TreasuryCap
public entry fun fractionalize<F>(token: &mut (nplex=0x0)::ltc1::LTC1Token, treasury_cap: iota::coin::TreasuryCap<F>, amount: u64, ctx: &mut iota::tx_context::TxContext)
Implementation
public entry fun fractionalize<F>(
token: &mut LTC1Token,
treasury_cap: TreasuryCap<F>,
amount: u64,
ctx: &mut TxContext
) {
// 1. Security: no coins must have been minted before
assert!(coin::total_supply(&treasury_cap) == 0, E_TREASURY_NOT_FRESH);
assert!(amount > 0, E_ZERO_AMOUNT);
// 2. Subtract balance from token (handles validation + proportional claimed split)
let (_balance_removed, claimed_split) = ltc1::subtract_balance(token, amount);
// 3. Mint coins
let mut cap = treasury_cap;
let coins = coin::mint(&mut cap, amount, ctx);
// 4. Create shared vault with the TreasuryCap locked inside
let vault = FractionalVault<F> {
id: object::new(ctx),
package_id: ltc1::package_id(token),
treasury_cap: cap,
total_claimed_snapshot: claimed_split,
total_fractionalized: amount,
};
// 5. Emit Event
// 5. Emit Event
events::emit_vault_created(
object::id(&vault),
ltc1::package_id(token),
type_name::get<F>().into_string(),
amount,
ctx.sender(),
);
// 6. Share the vault so anyone can redeem
iota::transfer::share_object(vault);
// 7. Send coins to the caller
iota::transfer::public_transfer(coins, ctx.sender());
}
Function redeem
Redeem: burn Coin
Anyone holding Coin
public entry fun redeem<F, P>(vault: &mut (nplex=0x0)::fractional::FractionalVault<F>, coins: iota::coin::Coin<F>, package: &(nplex=0x0)::ltc1::LTC1Package<P>, ctx: &mut iota::tx_context::TxContext)
Implementation
public entry fun redeem<F, P>(
vault: &mut FractionalVault<F>,
coins: Coin<F>,
package: <C1Package<P>,
ctx: &mut TxContext
) {
let burn_amount = coin::value(&coins);
assert!(burn_amount > 0, E_ZERO_AMOUNT);
// 1. Verify the vault belongs to this package
assert!(vault.package_id == object::id(package), E_PACKAGE_MISMATCH);
// 2. Burn the coins
coin::burn(&mut vault.treasury_cap, coins);
// 3. Calculate proportional claimed_revenue
let claimed = if (vault.total_claimed_snapshot == 0) {
0
} else {
(((vault.total_claimed_snapshot as u256) * (burn_amount as u256)) / (vault.total_fractionalized as u256) as u64)
};
// 4. Create a new LTC1Token with the redeemed balance
let token = ltc1::create_token_from_fraction(
vault.package_id,
burn_amount,
claimed,
ctx
);
// 5. Transfer the new token to the caller
ltc1::send_token_to(token, ctx.sender());
// 5.5 Emit redeem event
events::emit_fraction_redeemed(
object::id(vault),
burn_amount,
ctx.sender(),
);
// 6. Check if empty and emit event (but do NOT destroy automatically)
if (coin::total_supply(&vault.treasury_cap) == 0) {
events::emit_vault_empty(
object::id(vault),
type_name::get<F>().into_string(),
);
};
}
Function merge_back
Merge back: burn Coin
public entry fun merge_back<F>(token: &mut (nplex=0x0)::ltc1::LTC1Token, vault: &mut (nplex=0x0)::fractional::FractionalVault<F>, coins: iota::coin::Coin<F>, _ctx: &mut iota::tx_context::TxContext)
Implementation
public entry fun merge_back<F>(
token: &mut LTC1Token,
vault: &mut FractionalVault<F>,
coins: Coin<F>,
_ctx: &mut TxContext
) {
let burn_amount = coin::value(&coins);
assert!(burn_amount > 0, E_ZERO_AMOUNT);
// 1. Verify same package
assert!(vault.package_id == ltc1::package_id(token), E_PACKAGE_MISMATCH);
// 2. Burn the coins
coin::burn(&mut vault.treasury_cap, coins);
// 3. Calculate proportional claimed_revenue
let claimed = if (vault.total_claimed_snapshot == 0) {
0
} else {
(((vault.total_claimed_snapshot as u256) * (burn_amount as u256)) / (vault.total_fractionalized as u256) as u64)
};
// 4. Add back to the existing token
ltc1::add_fraction_balance(token, burn_amount, claimed);
// 4.5 Emit merge event
events::emit_fraction_merged_back(
object::id(vault),
object::id(token),
burn_amount,
);
// 5. Check if empty and emit event
if (coin::total_supply(&vault.treasury_cap) == 0) {
events::emit_vault_empty(
object::id(vault),
type_name::get<F>().into_string(),
);
};
}
Function destroy_empty_vault
Manually destroy an empty vault and return the TreasuryCap. Anyone can call this to clean up the state (permissionless).
public entry fun destroy_empty_vault<F>(vault: (nplex=0x0)::fractional::FractionalVault<F>, _ctx: &mut iota::tx_context::TxContext)
Implementation
public entry fun destroy_empty_vault<F>(
vault: FractionalVault<F>,
_ctx: &mut TxContext
) {
assert!(coin::total_supply(&vault.treasury_cap) == 0, E_ZERO_AMOUNT); // Reusing error code for checking non-zero supply
let FractionalVault {
id,
package_id: _,
treasury_cap,
total_claimed_snapshot: _,
total_fractionalized: _,
} = vault;
events::emit_vault_destroyed(object::uid_to_inner(&id));
object::delete(id);
iota::transfer::public_freeze_object(treasury_cap);
}
Function vault_package_id
Returns the package ID of the fractionalized token. Retrieves FractionalVault.package_id.
public fun vault_package_id<F>(vault: &(nplex=0x0)::fractional::FractionalVault<F>): iota::object::ID
Implementation
public fun vault_package_id<F>(vault: &FractionalVault<F>): ID {
vault.package_id
}
Function vault_total_supply
Returns the total supply of the fractionalized token. Calculates standard coin::total_supply using FractionalVault.treasury_cap.
public fun vault_total_supply<F>(vault: &(nplex=0x0)::fractional::FractionalVault<F>): u64
Implementation
public fun vault_total_supply<F>(vault: &FractionalVault<F>): u64 {
coin::total_supply(&vault.treasury_cap)
}
Function vault_total_fractionalized
Returns the total amount of coins that were fractionalized. Retrieves FractionalVault.total_fractionalized.
public fun vault_total_fractionalized<F>(vault: &(nplex=0x0)::fractional::FractionalVault<F>): u64
Implementation
public fun vault_total_fractionalized<F>(vault: &FractionalVault<F>): u64 {
vault.total_fractionalized
}