Peera.
Sui.X.Peera.

Earn Your Share of 1000 Sui

Gain Reputation Points & Get Rewards for Helping the Sui Community Grow.

Explore

Connect with communities and discover new ideas.

Communities

Newest

  • harry phan.Peera.
    ForSuiApr 24, 2025

    Soul-Binding Logic & the ReturnReceipt Pattern

    Soul-Binding Logic & the ReturnReceipt Pattern Now for a special design pattern that uses the parent-child mechanism: soul-bound objects. A soul-bound object is intended to be tied to a specific owner and not be permanently transferable. However, you might want to temporarily give someone access to it within a single transaction – for example, to perform some operation on it – but ensure it returns to you by the end of that transaction. This is achieved with what we call the ReturnReceipt pattern, a variant of the “hot potato” pattern. The Hot Potato Pattern in Sui The “hot potato” pattern is named after the children’s game – if you’re handed a hot potato 🌡️🥔, you can’t hold onto it; you must pass it on quickly. In Move terms, a hot potato is usually an object or resource that must be consumed or passed along in the same transaction, otherwise the transaction fails . This is often implemented by giving the object no drop ability (so you can’t just ignore it or drop it) and designing the logic such that the only way to get rid of it is to perform the required action (like returning a loan). A common use case is a flash loan: you borrow some coins in a transaction (get a Loan object, plus a “debt” token). If you don’t repay by the end of the transaction, you can’t finalize because you still hold that debt token which you have no permission to drop – effectively forcing you to return the loan or abort . Soul-Bound Object Example Let’s say we have a SoulBound object that we always want to remain with its original owner’s address. We can enforce that if someone “borrows” it (as a child object under some parent), they must return it in the same transaction. How? By using a ReturnReceipt. Below is a simplified version inspired by Sui’s documentation example of soul-bound objects module demo::soul_bound { use sui::transfer::{Self, Receiving}; use sui::object::UID; use sui::transfer; const EWrongObject: u64 = 0; /// A soul-bound object that cannot be permanently transferred. /// It has only key ability (no store), to tighten transfer/receive rules oai_citation_attribution:40‡docs.sui.io. public struct SoulBound has key { id: UID, data: u64 } /// A receipt that proves a SoulBound object must be returned. /// No abilities: cannot be copied, dropped, or stored (implicitly). public struct ReturnReceipt { /// The object ID of the soul-bound object that must be returned. object_id: UID, /// The address (or object ID) it must be returned to (the original owner). return_to: address } /// Allows the owner of parent to retrieve their SoulBound child. /// Returns the SoulBound object and a ReturnReceipt that compels return. public fun take_soul_bound(parent: &mut UID, sb_ticket: Receiving): (SoulBound, ReturnReceipt) { let sb = transfer::receive(parent, sb_ticket); // receive SoulBound (only this module can do it, since SoulBound has no store) oai_citation_attribution:41‡docs.sui.io let receipt = ReturnReceipt { object_id: sb.id, return_to: parent.to_address() }; (sb, receipt) } /// Return a SoulBound object using the ReturnReceipt. /// This must be called in the same transaction after take_soul_bound. public fun return_soul_bound(sb: SoulBound, receipt: ReturnReceipt) { // Verify the receipt matches this SoulBound object assert!(sb.id == receipt.object_id, EWrongObject); // Send the SoulBound object back to the original owner address transfer::transfer(sb, receipt.return_to); // (ReturnReceipt is consumed/destroyed here as function param) } } In this design: SoulBound is a key-only object. By not giving it store, we prevent use of public_transfer or public_receive on it , meaning the only way to transfer or receive it is through its defining module’s functions (ensuring our custom logic is used). take_soul_bound (analogous to “get_object” in the docs ) is a function the owner would call to take their soul-bound object out of some parent. It calls transfer::receive to get the SoulBound object. Because SoulBound has no store, this call is only allowed here in its module (which is fine). It then creates a ReturnReceipt struct containing the ID of the soul-bound object and the address to return to (the parent’s address). We return both the object and the receipt. ReturnReceipt has no drop, store, or copy abilities (we didn’t declare any, so it’s treated as a resource that cannot be dropped or duplicated). This means the transaction must do something with it; otherwise, it will be a leftover resource and the VM will not let the transaction end successfully. Essentially, the ReturnReceipt is our hot potato token 🔥🥔. The only valid thing to do with a ReturnReceipt is to call return_soul_bound (or a similar designated function) in the same transaction. In return_soul_bound, we verify that the SoulBound object’s ID matches the receipt’s record (to prevent someone trying to return a different object with a wrong receipt) . Then we call transfer::transfer(sb, receipt.return_to), which sends the SoulBound object back to the address in the receipt (the original owner) . This effectively restores the soul-bound object to its rightful place. The ReturnReceipt is consumed as an argument (thus destroyed). If the user tries to finish the transaction without calling return_soul_bound, they would still be holding the ReturnReceipt (from take_soul_bound output). Since ReturnReceipt has no drop, the Move VM will refuse to complete the transaction (in Move, you cannot simply discard a resource; it must be used or stored somewhere). The transaction would abort or be considered invalid, meaning the whole operation rolls back. Outcome: you can’t just run off with the soul-bound object; if you don’t return it, the tx fails and it stays with the parent. From the moment you call take_soul_bound to the moment you call return_soul_bound in the transaction, you do have the SoulBound object value available – presumably to perform some allowed operations on it (maybe read it, or use it as needed). But you must return it before the transaction is over, thanks to the receipt enforcement . This pattern is often described as “soul-bound = can’t leave its owner” but more accurately, it can leave within a single transaction as a child object, with the guarantee it comes back. It’s like checking out a library book – you have to return it before the library closes for the day 😅. Why not just never transfer it at all? There are scenarios where temporarily transferring an object to another object (or context) is useful. One example is a shared object context: perhaps the SoulBound item is held under a shared object during some operation and needs to be taken out by the original owner. Another is to allow controlled composition – you might want to let some module operate on your object by giving it to that module’s object and then get it back. By making SoulBound key-only, we ensured no external public_receive can pluck it without our module’s involvement . By using the receipt, we force adherence to the return policy. The comment in Sui’s example code even notes that the ReturnReceipt prevents swapping – if two soul-bound objects were taken out in one transaction, each receipt carries a specific object ID so you can’t mix them up and return the wrong one to cheat . Generalizing the idea: The ReturnReceipt pattern can be used whenever you want to enforce that an object is given back. Flash loans use a similar concept (loan token and debt token that must be repaid). Anytime you have an invariant “object X must end up back at address Y by end of tx,” you can create a receipt resource to uphold it. Quick Recap: Soul-bound objects**: key-only, retrieved only by their module’s logic. ReturnReceipt**: a dummy resource returned alongside the object to ensure it gets returned. It’s non-drop, so the user must call the return function or fail. Hot potato**: If you hold a ReturnReceipt, you better handle it (return the object) before the “music stops” (transaction ends) . If the object isn’t returned, the transaction will not successfully execute – your changes rollback, effectively enforcing the soul-bound rule. And with that, we’ve covered the major concepts of parent-child relationships in Sui Move! 5. Project Structure & Testing Strategy (GitHub-Ready) To solidify these concepts, you might want to set up a Sui Move project and play with the examples. Here’s a suggested project layout that includes modules for the above examples. You can compile and run unit tests or use the Sui CLI to interact with them. parent_child_demo/ ├── Move.toml ├── sources/ │ ├── toy_box.move (Parent & child in same module example) │ ├── parcel.move (Child module for Parcel) │ ├── warehouse.move (Parent module for Warehouse, uses public_receive) │ └── soul_bound.move (SoulBound and ReturnReceipt module) └── tests/ └── parent_child_test.move (Integration tests for the modules) Move.toml: Make sure to include the Sui framework and any addresses for your package. For example: [package] name = "parent_child_demo" version = "0.0.1" dependencies = [ { name = "Sui", git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } ] [addresses] demo = "0x0" (Use a proper address instead of 0x0 if required by Sui, or a named address that you’ll assign when publishing.) In the sources directory: toy_box.move** could contain the example::toy_box module from Part 2, with Toy and Box definitions and the take_toy function. parcel.move* and *warehouse.move** will contain the modules from Part 3. soul_bound.move** will contain the module from Part 4. Each module should use sui::... as needed for transfer, object, tx_context, etc., as shown in the examples. The tests/parent_child_test.move can then import these modules and simulate scenarios: module demo::parent_child_test { use 0xYourAddr::toy_box; use 0xYourAddr::warehouse; use 0xYourAddr::parcel; use 0xYourAddr::soul_bound; use sui::tx_context::TxContext; #[test] fun test_toy_withdraw() { let ctx = TxContext::new(); // pseudo, context is usually provided let my_box = toy_box::Box { id: object::new(&mut ctx) }; let my_toy = toy_box::Toy { id: object::new(&mut ctx), name: b"Ball".to_vec() }; // Transfer toy into the box transfer::transfer(my_toy, my_box.id); // Now simulate receiving it back let ticket = transfer::Receiving{ /.../ }; // In real test, use transfer::make_receiver or a transaction call let returned_toy = toy_box::take_toy(&mut my_box, ticket); assert!(returned_toy.name == b"Ball".to_vec()); } // Additional tests for warehouse/parcel and soul_bound can be written similarly. } The above is a conceptual illustration. In practice, Sui’s test context might differ – you may need to use transfer::public_transfer in tests when transferring to object (if outside module), and use the Receiving from the transaction context. Sui provides a transfer::make_receiver (test-only function) that can fabricate a Receiving given an ID and version , which is useful in unit tests to simulate a child object input. Running tests: You can run sui move test which will execute the #[test] functions. Or deploy the package to a local Sui network and call the entry functions via CLI or an SDK to observe the behavior: Create a Parcel, create a Warehouse, call transfer::public_transfer via CLI to put the parcel into the warehouse, then call withdraw_parcel. Create a SoulBound object (perhaps by making an entry fun to mint one), transfer it to some parent (or shared object), then in a single transaction call take_soul_bound and omit return_soul_bound to see the transaction fail (expected), versus including the return call to see success. Each part of this project addresses one of the topics: toy_box.move: basic parent-child and receive. parcel.move & warehouse.move: cross-module children and public_receive. soul_bound.move: soul-bound with ReturnReceipt. By experimenting with these, you’ll deepen your understanding of Sui’s object model. The code is structured to be educational and should not be used as-is for production without security reviews, but it provides a solid starting point. Conclusion: Sui Move’s parent-child object functionality is powerful. It lets you create complex data structures on-chain (like inventories, wallets, collections) with fine-grained access control. The combination of transfer::receive/public_receive and Move’s type system ensures that only authorized code can retrieve child objects, and patterns like ReturnReceipt enable enforcing temporal ownership rules (soul-binding, flash loans, etc.). We sprinkled some fun analogies and emojis, but at the end of the day, these are robust tools for builders. Now go forth and build some nested-object magic! 🚀🔥 When an object is transferred to another object instead of an account address, they form a parent-child relationship. You can think of it like this: Parent** = container object Child** = thing placed inside Move doesn’t care if you’re transferring to an account or object ID. It just moves. public struct Parent has key { id: UID, name: String, } public struct Child has key { id: UID, description: String, } Creating Parent & Child Objects The parent must be mutable and must exist. You can’t stuff things into an immutable box!

    0
  • harry phan.Peera.
    ForSuiApr 24, 2025

    Cross-Module Child Management with public_receive

    This is the part 3 of "Parent-Child Objects in Sui Move" series. Sometimes your parent and child types are defined in different modules or even different packages. For example, you might have a generic Warehouse object that can store any kind of Parcel objects. The Warehouse module wants to pull out a Parcel child, but the Parcel type is defined elsewhere. In such cases, we use transfer::public_receive, which is the cross-module cousin of receive. receive vs public_receive As we saw, transfer::receive can only be called in the module that defines T (or a friend) because it doesn’t require T: store . The Move bytecode verifier actually ensures that in any call to receive, the type T is from the current module . This is a safety restriction for key-only objects. transfer::public_receive is a variant that requires T: key + store but allows receiving outside T’s module . In other words, if the object type has the store ability (meaning it’s allowed to exist in global storage freely), then any module (given a &mut UID of the parent) can receive it using public_receive . This is perfect for cases where the parent’s module is different from the child’s module. Why require store? Because store marks that the object can be safely persisted and passed around outside of its defining module. Key-only objects might have custom invariants that the original module wants to enforce on transfer/receive; by excluding those from public_receive, Sui forces developers to handle them in-module (as we’ll see with soul-bound objects). If an object has store, it’s more permissive, and Sui allows generic transfer/receive logic to manage it externally . Example: Separate Parent and Child Modules Let’s illustrate with a simple scenario: a Warehouse that stores Parcel objects. The Parcel type is defined in its own module, and the Warehouse in another. We’ll show how Warehouse can receive a Parcel child using public_receive. module demo::parcel { // Child module use sui::object::{Self, UID}; use sui::tx_context::{Self, TxContext}; /// A parcel object that can be stored in a Warehouse. /// It has both key and store, so it can be transferred across modules. struct Parcel has key, store { id: UID, contents: vector } public entry fun create_parcel(contents: vector, ctx: &mut TxContext): Parcel { Parcel { id: object::new(ctx), contents } } } module demo::warehouse { // Parent module use sui::transfer::{Self, Receiving, public_receive}; use demo::parcel::{Self, Parcel}; use sui::object::{UID}; use sui::tx_context::{Self, TxContext}; struct Warehouse has key { id: UID, location: address } public entry fun create_warehouse(location: address, ctx: &mut TxContext): Warehouse { Warehouse { id: object::new(ctx), location } } /// Receive a Parcel that was sent to this Warehouse. /// Returns the Parcel to the caller (transferred to caller's address). public entry fun withdraw_parcel( warehouse: &mut Warehouse, parcel_ticket: Receiving, ctx: &mut TxContext ): Parcel { // Using public_receive because Parcel is defined in another module and has store let parcel = public_receive(&mut warehouse.id, parcel_ticket) oai_citation_attribution:27‡docs.sui.io oai_citation_attribution:28‡github.com; // Transfer the parcel to the transaction sender (so the caller gets ownership) transfer::transfer(parcel, tx_context::sender(ctx)); // We return nothing because we've transferred the Parcel out to the caller. } } Let’s break down what’s happening in withdraw_parcel: We call public_receive(&mut warehouse.id, parcel_ticket). Because Parcel has the store ability, this call is allowed even though we’re not in the parcel module . Under the hood, this does the same check and extraction as receive, but it’s permitted cross-module since store indicates it’s safe to do so. https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/transfer.move#:~:text=public%20fun%20public_receive,T%3E%29%3A%20T We then immediately transfer the received Parcel to the caller’s address (tx_context::sender(ctx)). This step ensures the parcel leaves the warehouse and goes to the user who initiated the withdrawal. We could also have just returned Parcel from the function, and Sui would treat it as an output to be owned by the caller’s address (since it’s an entry function output). Doing an explicit transfer is more verbose, but makes it clear what’s happening (and allows us to do any checks before releasing the object). Why include store in Parcel? If Parcel lacked the store ability (i.e. was only has key), the public_receive call in demo::warehouse would not compile – Sui enforces that T has store for public_receive . In that case, we’d be forced to retrieve the parcel using receive within the parcel module itself (or use some friend relationship), which complicates cross-module design. By adding store to Parcel, we effectively say “this object can be freely moved and received by external modules,” which is what we want for a generic container pattern. Function call pattern: To use these in a transaction, the flow would be: Deposit (transfer to object): Call transfer::public_transfer(parcel_obj, @warehouse_id) to send a Parcel into a Warehouse. This marks the parcel’s owner as the warehouse. (We use public_transfer here because it’s outside the Parcel module and Parcel has store . Inside the parcel’s module, a plain transfer would work too.) Withdraw (receive back): Later, call withdraw_parcel(warehouse_obj, Receiving(parcel_id, ...)). The Receiving can be obtained by the SDK by referencing the parcel’s ID and latest version. The function will call public_receive and then transfer the parcel to you. After the withdraw_parcel call, the parcel’s owner is back to an address (yours), so it’s a normal address-owned object again. The warehouse no longer owns it. Cross-module considerations: Notice that the Warehouse module needed to know about the Parcel type (we use demo::parcel::Parcel). This is because we explicitly type the Receiving as Receiving. If you wanted a truly generic container that could receive any type of object, you’d have to use generics or a different approach (possibly dynamic fields with type erasure). But for most use-cases, you’ll know what types of children you expect. Why public_receive instead of just calling receive? If we tried transfer::receive(&mut warehouse.id, parcel_ticket) in the warehouse module, the Move verifier would reject it because Parcel is not defined in demo::warehouse. Sui provides the public_receive as the blessed way to do this with an extra ability check (requiring store). Similarly, Sui has transfer vs public_transfer, freeze_object vs public_freeze_object, etc., following the same pattern: the public_ versions are for use outside the defining module and require store . Don’t forget the parent’s permission: Even with public_receive, you still need that &mut warehouse.id. We got it because withdraw_parcel is in Warehouse’s module and accepts &mut Warehouse. Thus, only someone who could call that (the warehouse’s owner) can withdraw the parcel. If the warehouse module didn’t provide such a function publicly, no one could externally call public_receive on its children either. So cross-module doesn’t bypass the parent’s control; it just allows the parent’s code to work with children of types it didn’t define. A note on store ability: Giving an object store makes it more flexible but slightly less restricted – any module with the parent reference can pull it out using public_receive. If you want to restrict how an object is retrieved (for example, enforce custom logic or prevent easy extraction), you might deliberately make it key-only. We’ll see an example of that with soul-bound objects. In those cases, you might implement a custom receive function instead of relying on public_receive. To sum up this part: public_receive is your friend for managing child objects defined in other modules, as long as those objects have the store ability. It allows you to build cross-module systems (like our Warehouse/Parcel) while still respecting ownership and access control. Just remember to include store on child types and use public_transfer when sending them to a parent from outside their module .

    0
  • Rogue.Peera.
    ForSuiApr 23, 2025

    Sui Blockchain: Architecture, Innovations, and Future Outlook

    The Sui blockchain has rapidly emerged as one of the most promising Layer 1 platforms in the decentralized technology landscape. Launched in May 2023 by Mysten Labs—founded by former Meta engineers—Sui was designed to address core industry challenges such as scalability, speed, and transaction costs, while offering a flexible foundation for next-generation decentralized applications (dApps). Key Innovations of Sui Blockchain Object-Centric Data Model: Unlike traditional blockchains that use account-based models, Sui structures data as independent objects. This allows for more granular transaction processing and dynamic asset management, making it ideal for applications in DeFi, NFTs, and gaming. Parallel Transaction Processing: Sui’s architecture supports parallel execution of transactions, drastically reducing bottlenecks and enabling the network to handle up to 297,000–300,000 transactions per second (TPS)—outpacing competitors like Solana and Avalanche. Move Programming Language: Sui utilizes Move, a language originally developed for the Diem project at Meta. Move is designed for secure, efficient smart contracts, with a focus on resource ownership and protection against vulnerabilities such as reentrancy attacks. Delegated Proof-of-Stake (DPoS) Consensus: Sui’s consensus mechanism allows users to stake SUI tokens to support validators, balancing decentralization and throughput. The recent Mysticeti engine upgrade cut consensus latency by 80%, achieving finality in as little as 39 milliseconds at high throughput. Ecosystem Growth and Current Adoption Sui’s ecosystem has seen remarkable expansion since its launch: By September 2024, Sui had over 18 million active accounts and processed more than 4.5 billion transactions. Its ecosystem supports a wide range of dApps, from DeFi and GameFi to NFTs and social platforms, attracting both developers and institutional interest. The SUI token is integral for transaction fees, staking, governance, and utility within various applications. Challenges and Considerations Despite its strengths, Sui faces several challenges: Adoption and Network Effects: Like any emerging blockchain, Sui must continue to attract developers and users to realize its full potential. Widespread adoption is crucial for long-term success. Interoperability: As a multi-chain future becomes more likely, Sui’s ability to integrate seamlessly with other blockchains and legacy systems will be essential. Upcoming cross-chain bridges, such as those to Ethereum, are steps in this direction. Security and Regulation: Vigilance against smart contract vulnerabilities and evolving regulatory requirements remains critical for Sui’s continued growth and acceptance, especially in sensitive sectors like finance and supply chain. Future Outlook: 2025 and Beyond Mainstream Adoption and Ecosystem Expansion 2025 is poised to be a pivotal year for Sui. The network is expected to go mainstream, with its core engine potentially becoming the backbone for finance, gaming, AI-driven applications, and everyday digital interactions. The expansion into cross-chain solutions, DePIN (Decentralized Physical Infrastructure Networks), and AI integrations will further cement Sui’s position as a leading Layer 1 blockchain. Competitive Positioning Sui’s combination of ultra-fast, low-cost transactions and a robust, developer-friendly ecosystem positions it as a strong contender against established blockchains like Ethereum and Solana. Its focus on real-world use cases and institutional partnerships is likely to drive continued growth and innovation. Potential Risks Sui must remain agile in addressing scalability under extreme load, maintaining security, and navigating regulatory landscapes. Continuous development, performance testing, and community engagement will be key to overcoming these hurdles. Conclusion Sui blockchain stands out for its technical innovations, scalability, and rapidly growing ecosystem. As it continues to evolve, Sui is well-positioned to play a central role in the future of decentralized finance, digital assets, and Web3 applications. If current trends continue, 2025 could mark the year Sui transitions from a high-potential platform to a mainstream backbone for the decentralized economy.

    1

Unanswered

  • 0xc348...79f3.Peera.
    ForFunctionlandApr 14, 2025

    Setting up NAS or BAS

    While trying to use my tower as a NAS I followed the instructions, but this is the result: PS C:\WINDOWS\system32> ssh pi@fulatower ssh: Could not resolve hostname fulatower: Host is onbekend. PS C:\WINDOWS\system32> ssh pi@192.168.1.150 ssh: connect to host 192.168.1.150 port 22: Connection timed out

    0
    0
  • 1 Luca.Peera.
    ForSuiApr 09, 2025

    What happens if I don't claim ETH via Sui bridge?

    I've been using the Sui bridge to transfer some ETH but haven't claimed it yet because the fees are quite high. What will happen if I leave it unclaimed?

    0
    0
  • MetaInvestor.Peera.
    ForMoveMar 29, 2025

    Is it easy to learn Move after solidity?

    I know solidity and have made some projects on it,I was wondering if it would be easy for me to learn Move and that to in less time..like less than a month?

    0
    0

Trending

  • Foksa.Peera.
    ForPeera MetaJul 25, 2022

    How to create an account

    To create an account on the Peeranha website you need to log in first. Peeranha supports four wallets you can log in with: Torus, MetaMask, WalletConnect, and Coinbase. Note: If you are new to Web3 we recommend you to use Torus. Log in with Torus Click Log in. Select the checkbox I agree to the Terms and Conditions and Privacy Policy. Choose Torus in the pop-up window. Select any of the suggested ways you want to sign in or enter your email address. You are now logged in and have an account. In your profile section, you can add more detailed information about yourself and track your progress and achievements. To edit your profile, click Profile and select Edit. Enter all the information that you want to be visible in your profile.

    5
  • loth.broke.Peera.
    ForPolygonMar 31, 2025

    Using heimdallcli in a dockerized local network setup?

    I'm trying to use heimdallcli within my local dockerized network that I set up using matic-cli. My main goal is to send a proposal using this command: heimdallcli tx gov submit-proposal param-change {pathToProposalJSONFile} --validator-id {validatorID} --chain-id {chainID}. However, I'm encountering an error that states: 'Config File "heimdall-config" Not Found in "/var/lib/heimdall/config"'. The config directory doesn't seem to exist in the Docker environment, though I can see logs that suggest Heimdall is functioning. How can I resolve this and get the heimdallcli command working?

    5
    4
  • Foksa.Peera.
    ForPeera MetaJul 08, 2022

    How to make a post

    Making posts on Peeranha is easy. Follow the simple steps: Log in to your profile with any available wallets. Click the New post button. Select a community from the dropdown list where you want to make a post. Choose Post Type: Discussion, Expert, Tutorial. Note: Read carefully the requirements for each post type, to be sure that your post is published in the correct section. Enter your question in the Title field. To get a more detailed answer, give more details about what you would like to know in the Body field. In the Preview section, you may check how your post looks before publishing it. Select tags that suit your post most. Note: Tags help other users to find relevant information quickly. Click Post. Your post is published and soon you will get relevant answers from the other users.

    5
We use cookies to ensure you get the best experience on our website.
More info