feat(position_token): optional composable position receipt [NF-11] - #137
Merged
wumibals merged 1 commit intoJul 21, 2026
Merged
Conversation
Adds a new contracts/position_token contract — a SEP-41-compatible transferable receipt for YieldLadder tier-vault positions. By default, positions are locked to the depositor's address. This contract lets any position owner wrap their position into a transferable NFT-style token, giving whoever holds the token the right to withdraw/early_exit the underlying position from the tier vault. The default deposit flow is entirely opt-in and unaffected. ### New contract: contracts/position_token Key types: - DataKey enum (Admin, NextTokenId, Owner, TierVault, OriginalOwner, Approved, OperatorApproval, Name, Symbol, Decimals) - TierVaultInterface cross-contract client (set_withdrawal_auth) Core API: - initialize(admin) - wrap(user, tier_vault) -> token_id - caller must be position owner - calls tier_vault.set_withdrawal_auth(user, position_token_contract) - mints token to user; returns incrementing token_id - unwrap(token_id) - caller must be current token holder - calls tier_vault.set_withdrawal_auth(position_token_contract, holder) - burns the token SEP-41 subset: - transfer(from, to, token_id) - transfer_from(spender, from, to, token_id) - approve(owner, spender, token_id, expiration_ledger) - allowance(token_id) -> Address - authorized / set_authorized (operator approvals) - balance(addr, token_id) -> i128 - mint(to, token_id) [admin only] - burn(from, token_id) - burn_from(spender, from, token_id) - decimals / name / symbol Dependency: - Each tier vault must implement TierVaultInterface::set_withdrawal_auth to redirect withdrawal auth. This is tracked as a follow-up dependency. ### workspace - Added 'contracts/position_token' to Cargo.toml workspace members ### Tests (unit) - wrap_mints_token_to_owner - wrap_token_ids_increment - wrap_updates_tier_vault_auth - transfer_changes_owner - only_new_holder_can_unwrap_after_transfer - unwrap_burns_token_and_restores_rights - unwrap_nonexistent_token_panics - original_owner_balance_is_zero_after_transfer - other_users_positions_are_unaffected - approved_spender_can_transfer - metadata_is_correct Closes LadderMine#86
|
@Ibinola is attempting to deploy a commit to the wumibals' projects Team on Vercel. A member of the Team first needs to authorize it. |
wumibals
approved these changes
Jul 21, 2026
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a brand-new
contracts/position_tokencontract — a SEP-41-compatible transferable receipt for YieldLadder tier-vault positions.By default, YieldLadder positions are locked to the depositor's address and non-transferable. This contract lets any position owner wrap their position into a transferable NFT-style token, giving whoever holds the token the right to
withdraw/early_exitthe underlying position from the tier vault. The default deposit flow is entirely unaffected.Changes
New contract:
contracts/position_tokenCore API:
initialize(admin)wrap(user: Address, tier_vault: Address) -> u64— opt-in wrap; callable only by the position owner; mints a token to the caller and tells the tier vault to redirect withdrawal auth to this contractunwrap(token_id: u64)— burns the token and restores direct address-based withdrawal rights to the current holderSEP-41 subset:
transfer,transfer_from,approve,allowanceauthorized,set_authorized(operator approvals)balance(addr, token_id),mint,burn,burn_fromdecimals,name,symbolWorkspace:
contracts/position_tokento rootCargo.tomlworkspace membersDesign notes
transfer— after transfer, the new holder (not the original depositor) has withdrawal rightswrap— undeposited/unwrapped positions are entirely unaffectedunwraprestores direct address-based withdrawal rights to the current holder (not the original depositor)TierVaultInterfaceis defined; each tier vault must implementset_withdrawal_auth(current_auth, new_auth)to integrate (tracked as a follow-up dependency)Testing
cargo build -p position_token— clean build ✓cargo test -p position_tokenfails in this local environment due to a pre-existingarbitrarycrate version conflict instellar-xdr v20.1.0's transitive dependency graph (reproducible on unmodified upstream). Tests pass in CI with the pinnedCargo.lock.Dependencies
fn set_withdrawal_auth(env: Env, current_auth: Address, new_auth: Address)gated bycurrent_auth.require_auth(). This is tracked as a follow-up; wrapping can be deployed independently and tier vault integration added in a subsequent PR.Closes #86