Articles
Educational materials & tutorials about Sui
Posts
14- ArticleApr 13, 2025
Object Access Control & receive Mechanics
This is the part 2 of "Parent-Child Objects in Sui Move" series. You can read part 1 here Object Access Control transfer::receive Mechanics So you’ve put an object X inside a parent P (by transferring X to P’s ID) – how do you get it back out or use it? 🤔 That’s where Sui’s special receive mechanism comes in. When an object is transferred to a parent, it doesn’t automatically pop out. It sits there, owned by P. To use or remove that child object in a transaction, you must receive it. Sui provides a structured way to do this using a Receiving ticket and the transfer::receive function . The Receivingticket Think of Receiving as a claim ticket for a child object of type T. In a transaction, instead of directly passing the child object (which you don’t own yet) to a function, you pass a Receiving – essentially a reference to “the object with ID Y that is owned by parent X”. This Receiving is a special Move struct that carries the ID, version, and digest of the object to be received . It has only the drop ability, meaning it can exist ephemerally but can’t be stored persistently  . In other words, it’s a one-time use ticket inside a transaction. How do you get a Receiving? Usually, by performing the transfer. In a programmable transaction block (PTB), one step can transfer object C to parent P, which yields a Receiving ticket that the next step can use . If the child was already sitting in the parent from an earlier transaction, you can supply a Receiving as an input to a new transaction (the Sui SDK/CLI allows you to specify a child object by its ID and parent to generate the ticket). Important: A Receiving is not the object itself – it doesn’t give you ownership yet. It just signals that “object of type T with this ID is owned by that parent, and I intend to take it.” To actually grab the object, you must call transfer::receive: module example::toy_box { use sui::transfer::{Self, Receiving}; struct Toy has key { id: UID, name: vector } struct Box has key { id: UID } /// Remove a Toy child from a Box, returning the Toy to the caller. public entry fun take_toy(box: &mut Box, toy_ticket: Receiving): Toy { // Use the parent's UID and the Receiving ticket to get the Toy let toy = transfer::receive(&mut box.id, toy_ticket); // Now toy is an owned value we can return (or transfer to someone). toy } } In take_toy, the toy_ticket: Receiving represents a Toy that belongs to the box. By calling transfer::receive(&mut box.id, toy_ticket), we invoke Sui’s native logic to actually retrieve the Toy object out from the box . This does a few critical things: It verifies at runtime that the toy_ticket indeed references an object currently owned by the box (using the parent’s UID) . If something doesn’t match (wrong parent, or object not actually there), it will abort. It then returns the actual Toy object as an owned value in the transaction, meaning now the Toy is in our function’s control. Notice we had to pass &mut box.id. Sui enforces that we have a mutable reference to the parent’s UID to call receive . This is a clever access control: only the module that can produce a &mut UID of the parent can allow receiving. Typically, the parent type’s defining module will expose a function like take_toy that internally calls receive. If the parent module doesn’t expose any function that gives out a &mut UID (directly or indirectly), then no external code can snatch its children. This way, the parent’s module controls how and when children can be accessed . In the example, Box has an id: UID field. Since take_toy is in the same module, it can borrow &mut box.id. External modules or transactions can call take_toy(box_obj, ticket) but they cannot themselves call transfer::receive on Box because box.id is private. This pattern ensures only authorized code can retrieve children. ✅ What about nested objects? If Toy was literally a field inside Box (say Box { id, toy: Toy }), we wouldn’t need any receive – the toy would be accessible whenever you have &mut Box. But that also means removing it or treating it separately is harder (it’s like part of the box). With child objects, we decouple storage: the toy is separate and must be explicitly taken out. This explicitness is why Sui requires the Receiving ticket and receive call – it makes child retrieval an authorized action. Mutable reference requirement: You might wonder, why &mut UID and not just &UID? Mutable ensures the parent is locked during the receive operation, preventing concurrent modification and ensuring the caller actually has the right to modify that parent (usually implying they are the owner). It’s part of Sui’s dynamic ownership checks – by taking a mutable borrow of the parent’s UID, Sui guarantees no other transaction or parallel action can interfere while you’re pulling out the child. It’s a bit like locking the parent’s door before removing the contents. Example usage in a transaction block: Suppose a Box object is owned by Alice, and she has a Toy object she wants to put into the box and then perhap s take it out later. Here’s how it could loo k: Attach toy to box:** Alice calls transfer::public_transfer(toy, @); in a PTB. This transfers the toy under the box (we use public_transfer here because it’s a transaction context – more on that soon). Now the toy is a child of the box. In the same or another transaction, retrieve toy:** Alice invokes example::toy_box::take_toy(box_obj, >) where that Receiving refers to the toy. Under the hood, take_toy calls receive, verifies the relationship, and returns the Toy. Now the Toy would be an output of the transaction, typically ending up back under Alice’s address (since we returned it from an entry function). Key Takeaways: Child objects aren’t accessible by default; you need a Receiving ticket and an appropriate function to get them out. The parent’s module decides how you can retrieve (by providing a function with &mut UID). transfer::receive is used within the parent’s defining module for objects of that module or its friends. If the child’s type is defined elsewhere, you’ll need a different approach (enter public_receive…). Before moving on, one more detail: if an object type T has only the key ability (no store), Sui treats it as a bit more restricted. Such objects cannot be received by generic code outside their module . In practice, this means if T is key-only and becomes a child, you’ll need to handle its retrieval in its own module or the parent’s module with a custom rule. If T also has store, we have more flexibility via public_receive. Let’s explore that next.
- Sui
2 - ArticleApr 12, 2025
✏️Parent-Child Objects in Sui Move
In Sui Move, objects can own other objects just like accounts own objects. This opens up new design patterns (and a few gotchas) for developers. In this guide, I’ll break down parent-child object concepts in Sui Move into four parts: Intro to Parent-Child Concepts in Sui Move Object Access Control & receive Mechanics Cross-Module Child Management with public_receive Soul-Binding Logic & ReturnReceipt Pattern By the end, you’ll understand how to nest objects, retrieve child objects via transfer::receive, manage children across modules with public_receive, and even create soul-bound objects that boomerang back to their owner. Intro to Parent-Child Concepts in Sui Move What Are Parent and Child Objects? In Sui, every object has a unique ID and an owner. Usually the owner is an address (like a user’s account), but the owner can also be another object. If object A owns object B, we call A the parent and B the child. The child is object-owned rather than address-owned. Transferring to objects: Sui doesn’t actually distinguish between an address and an object ID under the hood – both are just 32-byte identifiers This means you can transfer an object to another object’s ID in the same way you’d transfer to a user’s address. When you do this, you’re effectively nesting an object inside a parent object. Sui’s runtime sets the child’s owner field to the parent’s ID (instead of an address) So why is this cool? Because the child keeps its own unique ID and exists independently in storage, but now it’s tied to the parent. It’s like giving your friend a unique collectible to hold in their locker – the collectible keeps its ID and can be tracked, but your friend’s locker is now listed as its owner. It’s important to distinguish Unique vs. nested vs. child objects: Unique Object (Address-Owned):** A regular object owned by an address (e.g. a user’s account). This is the default case – think NFTs, coins, etc., living directly in a user’s wallet. Each has a unique ID and is top-level in storage. Nested (Wrapped) Object:* An object that is stored *inside another object’s data (for example, as a field in a struct). In this case, the inner object is wrapped and not a separate top-level entity. It doesn’t show up by its ID in global storage because it’s part of the parent’s byte content. In Sui, if you put an object as a field in another without special handling, it becomes wrapped. Child Object (Object-Owned):* An object that is owned by another object *but not directly wrapped in its fields. The child remains a separate top-level object in storage (with its own ID and data), and the parent’s ID is recorded as the owner in the child’s metadata . This means you can query or access the child by its ID (with the right permissions). It’s not physically embedded in the parent’s contents, just logically owned. Using our analogy, it’s like you gave your friend your collectible to hold – it’s still individually tagged and trackable, just stored in their locker. The benefit of making a child object (via transfer) instead of wrapping it is that the child’s ID remains accessible externally. For example, explorers or wallets can list a child object by ID, whereas a wrapped object is invisible outside its parent. Child objects also maintain stable IDs even as they move between owners or get nested in different parents . This is great for things like on-chain inventory or “wallet inside a wallet” patterns, where you want a container object to hold many items that others can still reference individually Ownership and access: If an object is owned by another object, only the owner of the parent can typically access or use the child This is a form of dynamic authorization. For instance, if Alice owns a parent object P, and P owns child C, then only Alice (or transactions Alice signs) can manipulate C. Sui enforces this so that owning a parent is like holding the keys to all its children 🔑. Maybe you know: Sui’s transfer-to-object feature essentially gives us tree-like object ownership. A parent can have many children (and those children can have their own children, forming a hierarchy). This is done by treating object IDs like addresses for transfers. We have: Address-owned objects** (normal unique objects), Object-owned objects** (child objects, still top-level but tied to a parent), Wrapped objects** (nested inside another object’s data, not top-level). In the next articles, we’ll see how to actually retrieve or interact with child objects (since they aren’t directly accessible like address-owned ones) and how to enforce rules around them.
- Sui
- Move
3 - ArticleApr 10, 2025
Building a Next-Gen NFT Lottery DApp with Sui Move & a Modern UI
🧩 Building a Next-Gen NFT Lottery DApp with Sui Move & a Modern UI This is your ultimate guide to building a gamified, NFT-powered lottery DApp using Sui Move, with multi-round support, referral systems, DAO governance, and a design system Gen Z will love. From contract architecture to UI flow—let's go all in. 📦 Phase Breakdown Phase 1 – Core Lottery Multi-round gameplay NFT ticketing Referral reward system Basic DAO voting Phase 2 – Marketplace & Gamification NFT marketplace integration Boosters (increase win chance) Jackpot system Hidden airdrops Phase 3 – DAO & Multichain Cross-chain compatibility DAO with advanced proposals Dynamic pricing On-chain analytics 🧠 Smart Contract Deep Dive on Sui Move Contract Structure module nft_lottery_x::nft_lottery_x { use sui::object; use sui::balance::{Balance, zero}; use sui::coin::{Self, Coin}; use sui::clock::Clock; use sui::random::Random; use sui::event::emit; use sui::transfer; use sui::tx_context::TxContext; use std::option; use std::signer; const EGameNotStarted: u64 = 1000; const EGameAlreadyFinished: u64 = 1001; const EInvalidPayment: u64 = 1002; const ENoTickets: u64 = 1003; const EWinnerAlreadyChosen: u64 = 1004; const ENotWinner: u64 = 1005; public struct Game has key { id: UID, ticket_price: u64, start_time: u64, end_time: u64, total_tickets: u32, round: u32, winner: Option, balance: Balance, referral_bonus: u64, } public struct Ticket has key { id: UID, game_id: ID, ticket_number: u32, buyer: address, referrer: Option, } public struct GameCreated has copy, drop { game_id: ID, start_time: u64, end_time: u64, ticket_price: u64, } public struct TicketBought has copy, drop { game_id: ID, ticket_number: u32, buyer: address, referrer: Option, } public struct WinnerAnnounced has copy, drop { game_id: ID, winner_ticket: u32, round: u32, } public struct RewardClaimed has copy, drop { game_id: ID, ticket_number: u32, amount: u64, } public fun create_game( start_time: u64, end_time: u64, ticket_price: u64, referral_bonus: u64, ctx: &mut TxContext ) { let game = Game { id: object::new(ctx), ticket_price, start_time, end_time, total_tickets: 0, round: 1, winner: option::none(), balance: zero(), referral_bonus, }; emit(GameCreated { game_id: object::id(&game), start_time, end_time, ticket_price, }); transfer::share_object(game); } public fun buy_ticket( game: &mut Game, coin: Coin, clock: &Clock, referrer: Option, ctx: &mut TxContext ): Ticket { assert!(clock.timestamp_ms() >= game.start_time, EGameNotStarted); assert!(clock.timestamp_ms() (TicketBought { game_id: object::id(game), ticket_number: ticket.ticket_number, buyer: ticket.buyer, referrer: ticket.referrer, }); ticket } public entry fun determine_winner( game: &mut Game, rand: &Random, clock: &Clock, ctx: &mut TxContext ) { assert!(clock.timestamp_ms() >= game.end_time, EGameNotStarted); assert!(game.winner.is_none(), EWinnerAlreadyChosen); assert!(game.total_tickets > 0, ENoTickets); let mut generator = rand.new_generator(ctx); let winning_ticket = generator.generate_u32_in_range(1, game.total_tickets); game.winner = option::some(winning_ticket); emit(WinnerAnnounced { game_id: object::id(game), winner_ticket: winning_ticket, round: game.round, }); } public fun claim_reward( ticket: Ticket, game: Game, ctx: &mut TxContext ): Coin { assert!(object::id(&game) == ticket.game_id, EInvalidPayment); let ticket_num = ticket.ticket_number; assert!(game.winner.contains(&ticket_num), ENotWinner); let amount = game.balance.value(); let reward = game.balance.into_coin(ctx); emit(RewardClaimed { game_id: object::id(&game), ticket_number: ticket.ticket_number, amount, }); object::delete(object::id(&game)); reward } } Key Takeaways: ✅ Balance ensures type-safety and proper coin handling ✅ Option clearly signals if a winner has been chosen ✅ Events offer traceability for frontends and explorers 🛠 Sui CLI Commands sui client call --package --module nft_lottery_x --function create_game --args --gas-budget 10000000 To buy ticket, determine winner, or claim reward, follow similar CLI flows. 🔮 Future Additions Auto-reset logic for next round in claim_reward Emit more events like ReferralRewardDistributed Refactor jackpots and referrals into submodules Let me know if you want a part 2 to build UI and integrate on Sui testnet!
- Sui
3 - ArticleApr 09, 2025
Guide to Sui Transactions: From Setup to Execution and Verification
Guide to Sui Transactions: From Setup to Execution and Verification If you’re curious about the nuts and bolts of executing transactions on the Sui blockchain and want an in-depth, practical guide that walks you through every step. In this article, we’ll explore the entire journey—from setting up your client environment, checking your wallet objects, calculating gas fees, to signing and executing a transaction, and finally verifying its details. Let’s break it down step by step: What Makes Sui So Special? 🔥 Sui offers a highly optimized platform for decentralized applications (dApps) and smart contracts. Its elegant design in managing gas fees and transaction logic makes it an exciting playground for developers looking to push the boundaries of Web3 technology. 2. Getting Started: Environment Setup and Wallet Configuration ⚙️ 2.1. Configuring Your Sui Client Environment Before diving into transactions, ensure your Sui client is properly set up. Sui supports multiple networks (devnet, mainnet, testnet), and you can check which one is active with the command below: ➜ sui client envs ╭─────────┬─────────────────────────────────────┬────────╮ │ alias │ url │ active │ ├─────────┼─────────────────────────────────────┼────────┤ │ devnet │ https://fullnode.devnet.sui.io:443 │ │ │ mainnet │ https://fullnode.mainnet.sui.io:443 │ │ │ testnet │ https://fullnode.testnet.sui.io:443 │ * │ ╰─────────┴─────────────────────────────────────┴────────╯ This confirms that you’re connected to the testnet. Being on the right network is the first step toward a successful transaction. 2.2. Checking Your Active Wallet Next, verify your active wallet address. This is crucial because every transaction is tied to your wallet identity: ➜ sui client active-address 0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73 2.3. Querying Owned Objects Using the suix_getOwnedObjects API, you can fetch details about the objects (like coins) you own on the blockchain. This command helps you check your account balance and the assets available for transactions: { "jsonrpc": "2.0", "id": 1, "method": "suix_getOwnedObjects", "params": [ "0x35370841d2e69b495b1e2f944a3087e4242f314e503691a00b054e0ee2a45a73", { "filter": { "MatchAll": [ { "StructType": "0x2::coin::Coin" } ] }, "options": { "showType": true, "showOwner": true, "showPreviousTransaction": true } } ] } This step is vital for verifying that your wallet has the necessary coins (in this case, SUI coins) before you attempt any transactions. 3. Gas Calculation: Budgeting for Transaction Costs 💸 Gas is the fuel that powers blockchain transactions. It’s essential to understand both the gas price and gas budget to avoid transaction failures. 3.1. Fetching the Gas Price The current gas price can be retrieved using the suix_getReferenceGasPrice API call: { "jsonrpc": "2.0", "id": 1, "method": "suix_getReferenceGasPrice", "params": [] } If the API returns "1000", it means that each unit of gas costs 1000 MIST. Remember, 1 SUI equals 10^9 MIST, so even small numbers in MIST can add up when budgeting. 3.2. Setting the Gas Budget Your gas budget is the maximum amount of gas (in MIST) you’re willing to spend. For our example, let’s say your gas budget is 4964000 MIST. The total cost of a transaction is typically computed as: Total Cost = Computation Cost + Storage Cost – Storage Rebate For instance: • Computation Cost: 1,000,000 MIST • Storage Cost: 2,964,000 MIST • Storage Rebate: 978,120 MIST So, the net cost becomes 1,000,000 + 2,964,000 − 978,120 = 2,985,880 MIST. Accurately setting your gas budget ensures your transaction has sufficient funds to be executed successfully. 4. Crafting the Transaction: A Dry Run for Confidence 🔧 Before sending a live transaction, it’s best to run a “dry run” to catch any potential issues. This allows you to validate the transaction logic without spending any gas. 4.1. Building a Dry-Run Transaction Here’s a sample TypeScript function that demonstrates how to prepare and execute a dry-run transaction. This code outlines how to split coins and prepare transfer operations: export const signSuiDryRunTransaction = async (requestParams: SignDryRequestParams): Promise => { const { gasPrice, privateKey, coinRefs, network, recipients } = requestParams; const keypair = Ed25519Keypair.fromSecretKey(privateKey); const tx = newTransaction(); // Configure gas payment, price, and sender tx.setGasPayment(coinRefs); tx.setGasPrice(gasPrice); tx.setSender(keypair.toSuiAddress()); // Split coins based on each recipient's amount const coins = tx.splitCoins(tx.gas, recipients.map((transfer) => transfer.amount)); recipients.forEach((transfer, index) => { tx.transferObjects([coins[index]], transfer.to); }); // Build and sign the transaction with the client const client = newSuiClient({ url: getFullnodeUrl(network) }); const bytes = await tx.build({ client }); const { signature } = await keypair.signTransaction(bytes); await verifyTransactionSignature(bytes, signature, { address: keypair.getPublicKey().toSuiAddress() }); return JSON.stringify([toBase64(bytes), signature]); }; This dry-run step is crucial to ensure that every detail is correct before you commit real funds. 5. Signing & Executing the Transaction: Putting It All Together ✍️ After a successful dry run, the next step is to sign and send your transaction on the blockchain. 5.1. Signing the Transaction Below is a refined example function that signs the transaction with the specified gas budget: const signSuiTransaction = async (requestParams: SignRequestParams): Promise => { const { gasBudget, gasPrice, privateKey, coinRefs, network, recipients } = requestParams; const keypair = Ed25519Keypair.fromSecretKey(privateKey); const tx = newTransaction(); // Set up gas parameters, including the gas budget tx.setGasPayment(coinRefs); tx.setGasPrice(gasPrice); tx.setGasBudget(gasBudget); tx.setSender(keypair.toSuiAddress()); // Split coins for each recipient const coins = tx.splitCoins(tx.gas, recipients.map((transfer) => transfer.amount)); recipients.forEach((transfer, index) => { tx.transferObjects([coins[index]], transfer.to); }); // Build the transaction and sign it const client = newSuiClient({ url: getFullnodeUrl(network) }); const bytes = await tx.build({ client }); const { signature } = await keypair.signTransaction(bytes); await verifyTransactionSignature(bytes, signature, { address: keypair.getPublicKey().toSuiAddress() }); return JSON.stringify([toBase64(bytes), signature]); }; This function integrates all necessary parameters—including gas details and recipients—ensuring that your transaction is securely signed and ready for execution. 5.2. Executing the Transaction Once signed, the transaction is sent to the blockchain using the sui_executeTransactionBlock API endpoint: curl --location 'https://fullnode.testnet.sui.io:443' \ --header 'Content-Type: application/json' \ --data '{ "jsonrpc": "2.0", "id": 1, "method": "sui_executeTransactionBlock", "params": [ "", [""], { "showInput": true, "showRawInput": true, "showEffects": true, "showEvents": true, "showObjectChanges": true, "showBalanceChanges": true }, "WaitForLocalExecution" ] }' This call returns a detailed JSON response with information such as the transaction digest, gas consumption, object modifications, and balance updates. 6. Verifying Your Transaction: Cross-Check Everything 🔍 After executing the transaction, it’s essential to verify that everything executed as expected. 6.1. Browser Verification You can check your transaction on a blockchain explorer like Suivision Testnet Explorer. The explorer displays all transaction details in an intuitive visual format, making it easier to spot any issues. 6.2. Command Line Verification For a more detailed audit, use the command line: sui client tx-block -- 3FopuDy5qzKm1kLRFZCdi8Lynadym9j15NaVxzUH6nYD This command provides a comprehensive breakdown of the transaction, including sender details, gas payment, object changes, and execution status. 7. Analyzing the JSON Response: Understanding the Layers of a Transaction Let’s unpack the JSON response you receive after executing your transaction: 7.1. Transaction Overview jsonrpc & id: Standard fields for the JSON-RPC protocol. digest: The unique transaction hash (e.g., "3FopuDy5qzKm1kLRFZCdi8Lynadym9j15NaVxzUH6nYD"), which is used for tracking. timestampMs & checkpoint: Provide context on when the transaction was executed and the blockchain checkpoint at that moment. 7.2. Transaction Content Sender & Gas Data: Includes the sender’s address and all gas-related configurations (payment, price, budget). Operations (Transactions): The transaction logic includes operations such as: SplitCoins: Breaking a gas coin into smaller parts. TransferObjects: Moving coin segments to the specified recipient addresses. Signatures: Cryptographic signatures (Base64 encoded) ensure the transaction’s authenticity. 7.3. Execution Effects Status: A “success” status confirms that the transaction was processed without errors. Gas Usage: Details the computational and storage costs along with any applicable rebates. Object Changes: Outlines which objects were modified, created, or updated as a result of the transaction. Dependencies: Lists related transaction hashes that this transaction depends on. This granular breakdown is essential for debugging and improving your dApp’s performance. ⸻ 8. Practical Developer Insights: Tips and Takeaways Understanding each step of this process equips you with the skills to build secure, efficient Web3 applications on Sui. These insights not only help you troubleshoot issues but also empower you to innovate confidently within the Sui ecosystem. ⸻
- Sui
- SDKs and Developer Tools
- Transaction Processing
3 - Article0xduckmove135Apr 08, 2025
👀 SEAL- I Think Web3 Data Privacy Is About to Change
👀SEAL is Live on Sui Testnet – I Think Web3 Data Privacy Is About to Change In the Web3, it’s common to hear phrases like “users own their data” or “decentralized by design”. But when you look closely, many applications still rely on centralized infrastructures to handle sensitive data — using services like AWS or Google Cloud for key management. This introduces a contradiction: decentralization on the surface, centralization underneath. But what if there was a way to manage secrets securely, without giving up decentralization?Introducing SEAL – Decentralized Secrets Management (DSM), now live on the Sui Testnet. SEAL aims to fix one of Web3’s biggest hypocrisies: shouting decentralization while secretly using AWS You maybe ask me: What is SEAL? SEAL is a protocol that lets you manage sensitive data securely and decentrally – built specifically for the Web3 world. Think of it as a privacy-first access control layer that plugs into your dApp. You can think of SEAL as a kind of programmable lock for your data. You don’t just lock and unlock things manually — you write policies directly into your smart contracts, using Move on Sui. Let’s say you’re building a dApp where: Only NFT holders can unlock a premium tutorial Or maybe a DAO has to vote before sensitive files are revealed Or you want metadata to be time-locked and only accessible after a specific date SEAL makes all of that possible. The access control lives onchain, fully automated, no need for an admin to manage it. Just logic, baked right into the blockchain. SEAL makes all of that possible. The access control lives onchain, fully automated, no need for an admin to manage it. Just logic, baked right into the blockchain. Another interesting piece is how SEAL handles encryption. It uses something called threshold encryption, which means: no single node can decrypt the data. It takes a group of servers to work together — kinda like multi-sig, but for unlocking secrets. This distributes trust and avoids the usual single-point-of-failure problem. And to keep things truly private, SEAL encrypts and decrypts everything on the client side. Your data is never visible to any backend. It stays in your hands — literally — on your device. and SEAL doesn’t care where you store your data. Whether it’s IPFS, Arweave, Walrus, or some other platform, SEAL doesn’t try to control that part. It just focuses on who’s allowed to see what, not where things are stored. So yeah, it’s not just a library or API — it’s an onchain-first, access-controlled, privacy-by-default layer for your dApp. SEAL fills a pretty critical gap. Let’s break that down a bit more. If you’re building a dApp that deals with any form of sensitive data — gated content, user documents, encrypted messages, even time-locked NFT metadata — you’ll run into the same problem: ➡️ How do you manage access securely, without relying on a centralized service? Without something like SEAL, most teams either: Use centralized tools like AWS KMS or Firebase, which clearly goes against decentralization Or try to patch together half-baked encryption logic themselves, which usually ends up brittle and hard to audit https://x.com/EmanAbio/status/1908240279720841425?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1908240279720841425%7Ctwgr%5E697f93dc65359d0c8c7d64ddede66c0c4adeadf1%7Ctwcon%5Es1_&ref_url=https%3A%2F%2Fwww.notion.so%2Fharryph%2FSEAL-Launches-on-Sui-Testnet-1cc4f8e09bb380969c0dcc627b96cc22 Neither of those scales well. Especially not when you’re trying to build trustless apps across multiple chains or communities. SEAL makes that entire process modular and programmable. You define your access rules in Move smart contracts, and SEAL handles the rest — key generation, decryption approvals, and access enforcement — all without anyone manually issuing keys or running backend checks. Even better, those rules are auditable and immutable — once they’re onchain, they follow the contract, not a human admin. So instead of asking “who should manage access to this data?” you just ask: “What logic should define access?” …and let the chain handle it. Clean and scalable. That’s what makes SEAL relevant for more than just “security tools” — it’s a base layer for any dApp that cares about privacy, compliance, or dynamic access logic. It’s a small shift — but it changes a lot about how we think of data in Web3. Instead of encrypting after deployment, or relying on external services, you start with privacy built-in — and access handled entirely by smart contract logic. And that’s exactly what Web3 needs right now. How Does SEAL Actually Work? We’ve covered what SEAL is and why Web3 needs it, let’s take a look at how it’s actually built under the hood. This part is where things get more technical — but in a good way. The architecture is elegant once you see how all the pieces fit together. At a high level, SEAL works by combining onchain access logic with offchain key management, using a technique called Identity-Based Encryption (IBE). This allows devs to encrypt data to an identity, and then rely on smart contracts to define who is allowed to decrypt it. Step 1: Access Rules in Smart Contracts (on Sui) Everything starts with the smart contract. When you’re using SEAL, you define a function called seal_approve in your Move contract — this is where you write your conditions for decryption. For example, here’s a simple time-lock rule written in Move: entry fun seal_approve(id: vector, c: &clock::Clock) { let mut prepared: BCS = bcs::new(id); let t = prepared.peel_u64(); let leftovers = prepared.into_remainder_bytes(); assert!((leftovers.length() == 0) && (c.timestamp_ms() >= t), ENoAccess); } Once deployed, this contract acts as the gatekeeper. Whenever someone wants to decrypt data, their request will get checked against this logic. If it passes, the key gets released. If not, they’re blocked. No one has to intervene. Step 2: Identity-Based Encryption (IBE) Here’s where the magic happens. Instead of encrypting data for a specific wallet address (like with PGP or RSA), SEAL uses identity strings — meaning you encrypt to something like: 0xwalletaddress dao_voted:proposal_xyz PkgId_2025_05_01 (a timestamp-based rule) or even game_user_nft_holder When the data is encrypted, it looks like this: Encrypt(mpk, identity, message) mpk = master public key (known to everyone) identity = the logic-defined recipient message = the actual data Later, if someone wants to decrypt, the key server checks if they match the policy (via the seal_approve call onchain). If it’s approved, it returns a derived private key for that identity. Derive(msk, identity) → sk Decrypt(sk, encrypted_data) The user can then decrypt the content locally. So encryption is done without needing to know who will decrypt ahead of time. You just define the conditions, and SEAL figures out the rest later. It’s dynamic. Step 3: The Key Server – Offchain, But Not Centralized You might wonder: who’s holding these master keys? This is where SEAL’s Key Server comes in. Think of it as a backend that: Holds the master secret key (msk) Watches onchain contracts (like your seal_approve logic) Only issues derived keys if the conditions are satisfied But — and this is key — SEAL doesn’t rely on just one key server. You can run it in threshold mode, where multiple independent servers need to agree before a decryption key is issued. For example: 3-of-5 key servers must approve the request. This avoids central points of failure and allows decentralization at the key management layer too. Even better, in the future SEAL will support MPC (multi-party computation) and enclave-based setups (like TEE) — so you can get even stronger guarantees without compromising usability. Step 4: Client-Side Decryption Once the key is returned to the user, the actual decryption happens on their device. This means: The server never sees your data The backend never stores decrypted content Only the user can access the final message It’s a solid privacy model. Even if someone compromises the storage layer (IPFS, Arweave, etc.), they still can’t read the data without passing the access logic. Here’s the quick mental model: This structure makes it easy to build dApps where access rules aren’t hardcoded — they’re dynamic, auditable, and fully integrated into your chain logic. The Team Behind SEAL SEAL is led by Samczsun, a well-known figure in the blockchain security community. Formerly a Research Partner at Paradigm, he has audited and saved multiple ecosystems from major exploits. Now, he’s focused full-time on building SEAL into a core piece of Web3’s privacy infrastructure. With his background and credibility, SEAL is not just another experimental tool — it’s a serious attempt at making decentralized data privacy both practical and scalable. As SEAL goes live on the Sui Testnet, it brings a new standard for how Web3 applications can manage secrets. By combining onchain access control, threshold encryption, and client-side privacy, SEAL offers a more trustworthy foundation for decentralized data handling. Whether you’re building dApps, DAOs, or decentralized games — SEAL provides a powerful toolkit to enforce access control and protect user data without compromising on decentralization. If Web3 is going to move forward, secure infrastructure like SEAL is not optional — it’s essential
- Sui
- Architecture
- SDKs and Developer Tools
4 - ArticleLokie13Mar 16, 2025
Step by step guide to create a Suiet wallet
Step by step guide to create a Suiet wallet: Download suiet extension: https://suiet.app/ After successful installation, click on the Suiet icon in your browser and select "Create New". Set a strong password to protect your wallet. Write down and save your recovery phrase in a safe place. This phrase is needed to recover your wallet if needed Done
- Sui
1 - ArticleBahador85Mar 15, 2025
Journey as a SUI Move smart contract developer
In today's article, I wanna dive into a roadmap suggestion for those who like to enter into the SUI move development way. 1. Understand Blockchain Fundamentals Core Concepts:* Familiarize yourself with key blockchain concepts such as decentralization, consensus mechanisms, cryptographic primitives, and smart contracts. SUI Blockchain Overview:* Learn about what makes SUI unique—its object-centric data model, performance goals, and how it handles state management. 2. Learn the Move Language Language Basics:* Start with the fundamentals of the Move programming language. Focus on: Resource Types:* How resources work to ensure safety and ownership. Modules and Structs:* How to define modules and data structures. Entry Functions:* How transactions are executed through designated entry points. Recommended Resources: Utilize official Move language tutorials, documentation, and sample code repositories. 3. Set Up Your Development Environment Tools and CLI:* Install the SUI CLI and set up the Move toolchain on your local machine. Local Test Environment:* Configure a local SUI development network or use available testnets. This helps you experiment with smart contract deployment and testing before going live. IDE & Debugging:* Choose an Integrated Development Environment (IDE) that supports Move (for example, VSCode with Move extensions) and get familiar with debugging and testing your contracts. 4. Build Your First Simple Contract Hands-on Tutorials:* Start with a simple project such as a token contract. This will allow you to apply the basic Move constructs. Explore SUI-Specific Patterns:* Work with SUI’s object model and learn how transactions are processed within the SUI ecosystem. Documentation & Examples:* Leverage SUI’s developer documentation and sample projects to understand best practices. 5. Deep Dive into SUI-Specific Features Object-Centric Model:* Understand how SUI treats objects differently from account-based blockchains like Ethereum. Gas & Transaction Model:* Study how gas fees are calculated and how transaction execution is managed in SUI. State Management:* Learn about SUI’s approach to state storage, modular updates, and object lifecycle management. 6. Testing, Debugging, and Deployment Unit and Integration Testing:* Write tests to verify the logic and safety of your smart contracts. Make sure you cover edge cases and potential vulnerabilities. Local and Testnet Deployment:* Deploy your contracts in a controlled environment to see how they perform in real conditions. Tooling:* Utilize SUI’s debugging tools and logging features to iterate and improve your code. 7. Security Best Practices and Code Audits Understand Common Pitfalls:* Study common security vulnerabilities in smart contracts (e.g., reentrancy, improper access controls) and how Move’s design mitigates them. Code Reviews:* Get involved in community code reviews or collaborate with peers to audit and improve your code. Formal Verification:* Explore any available formal verification tools for Move to mathematically prove the correctness of your contracts. 8. Join the SUI Developer Community Community Channels:* Engage with other developers via forums, Discord channels, or community calls. Sharing experiences and challenges is invaluable. Open Source Contributions:* Contribute to open source projects or developer repositories related to SUI and Move. Stay Updated:* Follow SUI and Move blogs, GitHub repositories, and social media channels to keep track of new developments, updates, and best practices. 9. Explore Advanced Topics Complex Applications:* As you become more comfortable, experiment with more advanced smart contract designs such as decentralized finance (DeFi) protocols, NFTs, or multi-signature wallets. Interoperability and Integration:* Learn how to interact with other smart contracts and integrate SUI Move modules with off-chain systems. Performance and Scalability:* Explore techniques to optimize your contracts for speed and cost-efficiency on the SUI blockchain. 10. Build a Portfolio and Keep Practicing Showcase Projects:* Develop and document a series of projects that demonstrate your understanding and expertise. Continuous Learning:* Blockchain and Move are evolving rapidly—make continuous learning a habit by revisiting documentation, attending workshops, and participating in hackathons. Feedback Loop:* Use community feedback to refine your skills and stay ahead of the curve in smart contract development. Although the above Items are suggestions and are not the only way to turn into an SUI developer, I hope it is helpful for you guys. Happy coding. Happy SUIng!
- Sui
- SDKs and Developer Tools
- Move
2 - Article0xduckmove135Mar 12, 2025
Setting Up a Project to Test the SUI Name Service (SuiNS)
The SUI Name Service (SuiNS) is a decentralized naming system on the SUI blockchain that allows users to map human-readable names (e.g., "0xduck.sui") to blockchain addresses or other data, enhancing usability and accessibility. For developers, testing the SuiNS SDK is an essential step to ensure applications can resolve these names correctly. In this article, we’ll walk you through the process of setting up a project to test the SuiNS SDK, from preparing your development environment to querying a name record like "0xduck.sui" and understanding the results. Introduction to SUI Name Service The SUI Name Service (SuiNS) simplifies blockchain interactions by allowing users to register memorable names instead of using complex cryptographic addresses. For example, instead of sending tokens to "0x1234...abcd", you could send them to "0xduck.sui". Testing the SuiNS SDK ensures that your application can correctly interact with this system, retrieving and interpreting name records as needed. In this project, we’ll set up a Node.js environment, connect to the SUI mainnet, and write a script to query the name record for "0xduck.sui". By the end, you’ll have a working setup to explore SuiNS further. Prerequisites Before starting, ensure you have the following: Node.js (version 14 or higher) and npm (version 6 or higher) installed. Download them from nodejs.org if needed. Basic understanding of JavaScript, particularly asynchronous programming (e.g., async/await). A code editor like Visual Studio Code (VS Code) for writing and running scripts. An internet connection to install packages and connect to the SUI. Setting Up the Development Environment Let’s create a new project directory and initialize it as a Node.js project. This will provide a clean workspace for our test. Step-by-Step Instructions: Open your terminal (e.g., Command Prompt, PowerShell, or a Unix shell). Create a new directory: mkdir suins-test-project cd suins-test-project Initialize a Node.js project: Run this command to create a package.json file with default settings: npm init -y Your project is now set up with a basic structure. Installing Dependencies Run the following in your terminal: npm install @mysten/suins @mysten/sui Verify the Installation: Check that the packages are added to package.json under dependencies. You can also confirm the installed versions by running: npm list @mysten/suins @mysten/sui This should output something like: suins-test-project@1.0.0 ├── @mysten/sui@x.x.x └── @mysten/suins@y.y.y Configuring the SUI Client The SUI client connects your project to the SUI blockchain. For testing, we’ll use the mainnet. Create a Script File: In your project directory, create a file named test-suins.js: touch test-suins.js # On Unix/macOS echo. > test-suins.js # On Windows Open test-suins.js in your editor and add the following: import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; // Create a SUI client connected to the testnet const suiClient = new SuiClient({ url: getFullnodeUrl('testnet') }); Note: If you encounter an error about ES modules (e.g., "Cannot use import statement outside a module"), add "type": "module" to your package.json: { "name": "suins-test-project", "version": "1.0.0", "type": "module", ... } Testing the Name Service Now, let’s write a script to query the name record for "0xduck.sui" and log the result. import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { SuinsClient } from '@mysten/suins'; // Create a SUI client connected to the testnet // const suiClient = new SuiClient({ url: getFullnodeUrl('mainnet') }); const client = new SuiClient({ url: getFullnodeUrl('mainnet') }); // Create a SuiNS client using the SUI client const suinsClient = new SuinsClient({ client, network: 'mainnet', }); // Function to test the name service async function testNameService() { try { // Query the name record for 'demo.sui' const nameRecord = await suinsClient.getNameRecord('0xduck.sui'); // Log the result console.log('Name Record for "0xduck.sui":', nameRecord); } catch (error) { console.error('Error fetching name record:', error.message); } } testNameService(); In your terminal, execute: node test-suins.js
- SDKs and Developer Tools
3 - ArticleBahador85Mar 11, 2025
Suibase, a great tool to experience the SUI
As I was checking some resources for the SUI development, I faced a tool named, suibase. In my exploration on that, I found it very helpful, especially when using localnet which a local explorer will be set up on our local system. I loved it so much. as there is stated in the official website of Suibase: Suibase makes it easy to create "workdirs", each defining a distinct development environment targeting a network. It seems like virtual env in python programming. As far as I found this tool, there are many usefull functionalities which we can take benefit of: Key functionalities of Suibase include: Workdir Management: Suibase enables the creation of isolated workdirs for each network, ensuring that configurations and dependencies are maintained separately. This isolation facilitates organized and efficient development workflows. Suibase Simplified Command-Line Interface:* The tool provides scripts such as lsui, dsui, tsui, and msui, which serve as frontends to the Mysten Labs sui binaries for localnet, devnet, testnet, and mainnet, respectively. These scripts eliminate the need to manually switch environments, as they automatically execute the appropriate sui client and keystore for the targeted network. Localnet Operations:* Suibase offers commands like localnet start, localnet stop, and localnet status to manage the local network. Additionally, the localnet regen command allows developers to reset the network to its initial state, complete with predefined addresses and aliases, which is particularly useful for testing purposes. Faucet Functionality:* The localnet faucet command enables the distribution of Sui coins to specified addresses or all addresses within the localnet, facilitating testing and development activities. Independent Installation:* Suibase operates independently of other Mysten Labs installations and keystores, ensuring that it does not interfere with existing setups. This design allows Suibase to coexist safely with standard installations on the same system. By providing these features, Suibase enhances the development experience on the Sui network, offering a structured and efficient environment for building and testing applications. I recommend testing it!
- Sui
- SDKs and Developer Tools
3 - ArticleBahador85Mar 11, 2025
How to fix the SUI installation error?
When I try to install and build the SUI binary on my local system with this command: cargo install --git https://github.com/MystenLabs/sui.git --bin sui --branch devnet I face this error: Please specify a package, e.g. cargo install --git https://github.com/MystenLabs/sui.git anemo-benchmark. After some digging, I found the solution and was able to install and build it error-free and completely using some modification in the above command: For Devnet: cargo install --locked --git https://github.com/MystenLabs/sui.git sui --branch devnet For Testnet: cargo install --locked --git https://github.com/MystenLabs/sui.git sui --branch devnet This way, you can install and build SUI on your local machine and start on your way! Best of luck.
- Sui
- Move
3
- Sui
- SDKs and Developer Tools
- Architecture
- Move
- NFT Ecosystem
- Transaction Processing
- Security Protocols