Move.

帖子

分享您的知识。

Jeff.
Jeff406
Aug 24, 2025
专家问答

How do I design multi-step workflows in Move?

If a process needs multiple user interactions (like a game or auction), what’s the best way to structure state and transactions?

  • Move CLI
  • Move Module
  • Move Script
5
6
分享
评论
.

答案

6
SuiLover.
Aug 25 2025, 20:34

You should model multi-step workflows as explicit, small state objects and safe transitions so each user action is one clear, atomic transaction: represent the workflow as a compact state machine object (or a sequence of small objects) that holds only the minimal fields needed to validate the next step, gate transitions with capabilities or ownership checks so only the right actor can advance the state, validate preconditions early and fail-fast to avoid wasting gas, keep large data off-chain and store only references or hashes on-chain, prefer owned per-user objects or dynamic fields over a single shared hotspot to avoid contention, use events for off-chain indexing and UX updates rather than on-chain logs, provide timeouts and reclaim paths for abandoned workflows, and design idempotent steps (include nonces or version numbers) so retries are safe; for example you can mint a BidRequest { id, bidder, amount, nonce } object for each bid and have a single Auction state object that your place_bid entry checks/consumes, or use a typed-state pattern Auction -> Auction -> Auction so invalid transitions are caught by types, and in the frontend use a dry-run (dryRunTransactionBlock) then submit a single PTB combining required moves (e.g., prepare bid object + call place_bid), for a minimal PTB sketch in TypeScript you can build the block like tx.moveCall({ target: ${pkg}::auction::place_bid, arguments: [tx.object(auctionId), tx.object(bidRequestId), tx.pure.u64(amount)] }) and then simulate and sign; read more about Move state patterns and PTBs in the Move book and Sui docs (https://move-book.com and https://docs.sui.io) — this approach makes each step explicit, auditable, retry-safe, and easy to index off-chain.

2
最佳答案
评论
.
Matthardy.
Aug 25 2025, 00:17

You should model multi-step workflows as explicit, small state objects and safe transitions so each user action is one clear, atomic transaction: represent the workflow as a compact state machine object (or a sequence of small objects) that holds only the minimal fields needed to validate the next step, gate transitions with capabilities or ownership checks so only the right actor can advance the state, validate preconditions early and fail-fast to avoid wasting gas, keep large data off-chain and store only references or hashes on-chain, prefer owned per-user objects or dynamic fields over a single shared hotspot to avoid contention, use events for off-chain indexing and UX updates rather than on-chain logs, provide timeouts and reclaim paths for abandoned workflows, and design idempotent steps (include nonces or version numbers) so retries are safe; for example you can mint a BidRequest { id, bidder, amount, nonce } object for each bid and have a single Auction state object that your place_bid entry checks/consumes, or use a typed-state pattern Auction -> Auction -> Auction so invalid transitions are caught by types, and in the frontend use a dry-run (dryRunTransactionBlock) then submit a single PTB combining required moves (e.g., prepare bid object + call place_bid), for a minimal PTB sketch in TypeScript you can build the block like tx.moveCall({ target: ${pkg}::auction::place_bid, arguments: [tx.object(auctionId), tx.object(bidRequestId), tx.pure.u64(amount)] }) and then simulate and sign; read more about Move state patterns and PTBs in the Move book and Sui docs (https://move-book.com and https://docs.sui.io) — this approach makes each step explicit, auditable, retry-safe, and easy to index off-chain.

4
评论
.
290697tz.
Aug 25 2025, 19:38

You should model multi-step workflows as explicit, small state objects and safe transitions so each user action is one clear, atomic transaction: represent the workflow as a compact state machine object (or a sequence of small objects) that holds only the minimal fields needed to validate the next step, gate transitions with capabilities or ownership checks so only the right actor can advance the state, validate preconditions early and fail-fast to avoid wasting gas, keep large data off-chain and store only references or hashes on-chain, prefer owned per-user objects or dynamic fields over a single shared hotspot to avoid contention, use events for off-chain indexing and UX updates rather than on-chain logs, provide timeouts and reclaim paths for abandoned workflows, and design idempotent steps (include nonces or version numbers) so retries are safe; for example you can mint a BidRequest { id, bidder, amount, nonce } object for each bid and have a single Auction state object that your place_bid entry checks/consumes, or use a typed-state pattern Auction -> Auction -> Auction so invalid transitions are caught by types, and in the frontend use a dry-run (dryRunTransactionBlock) then submit a single PTB combining required moves (e.g., prepare bid object + call place_bid), for a minimal PTB sketch in TypeScript you can build the block like tx.moveCall({ target: ${pkg}::auction::place_bid, arguments: [tx.object(auctionId), tx.object(bidRequestId), tx.pure.u64(amount)] }) and then simulate and sign; read more about Move state patterns and PTBs in the Move book and Sui docs (https://move-book.com and https://docs.sui.io) — this approach makes each step explicit, auditable, retry-safe, and easy to index off-chain.

3
评论
.
Tucker.
Aug 25 2025, 19:47

Core Principles for Multi-Step Workflows in Move

  1. Represent Workflow as an Object with State

Use a struct with key ability to persist across transactions.

Add a status field (enum-like using u8 or separate structs) to track the stage.

Example:

struct Auction has key { id: UID, item_id: u64, highest_bid: u64, highest_bidder: address, status: u8 // 0 = Created, 1 = Active, 2 = Ended }


  1. Split Logic into Multiple Entry Functions

Each function handles one step.

Use precondition checks to enforce the correct stage.

Example:

public entry fun start_auction(auction: &mut Auction) { assert!(auction.status == 0, 1); auction.status = 1; }

public entry fun place_bid(auction: &mut Auction, bid: u64, bidder: address) { assert!(auction.status == 1, 2); assert!(bid > auction.highest_bid, 3); auction.highest_bid = bid; auction.highest_bidder = bidder; }

public entry fun close_auction(auction: &mut Auction) { assert!(auction.status == 1, 4); auction.status = 2; }


  1. Use Ownership to Control Who Acts

Pass mutable references (&mut) or transfer ownership to ensure the right actor controls the object.

For auctions, you might:

Keep object shared (anyone can bid).

Or move object to an escrow object controlled by the module.


  1. Enforce Access Control

Use TxContext::sender to check the caller.

Store owner: address or use capabilities (a resource that only the owner holds).


  1. Handle Partial Progress Safely

If step 2 fails, step 1 should remain valid.

Avoid global side effects until the final step (like transferring funds).

Use escrow objects for assets during workflow.


  1. Avoid Global Locks

Each workflow instance should be its own object.

Never put all active workflows in a single vector/map → this kills parallelism.


✅ Example: Multi-Step Auction Workflow in Sui Move

module auction::multi_step { use sui::object::{Self, UID}; use sui::tx_context::{Self, TxContext}; use sui::transfer;

struct Auction has key {
    id: UID,
    owner: address,
    item_id: u64,
    highest_bid: u64,
    highest_bidder: address,
    status: u8 // 0 = Created, 1 = Active, 2 = Ended
}

// Step 1: Create auction
public entry fun create(item_id: u64, ctx: &mut TxContext) {
    let auction = Auction {
        id: object::new(ctx),
        owner: tx_context::sender(ctx),
        item_id,
        highest_bid: 0,
        highest_bidder: tx_context::sender(ctx),
        status: 0
    };
    transfer::transfer(auction, tx_context::sender(ctx));
}

// Step 2: Start auction
public entry fun start(auction: &mut Auction) {
    assert!(auction.status == 0, 1);
    assert!(auction.owner == tx_context::sender(), 2);
    auction.status = 1;
}

// Step 3: Place bid
public entry fun bid(auction: &mut Auction, bid_amount: u64) {
    assert!(auction.status == 1, 3);
    assert!(bid_amount > auction.highest_bid, 4);
    auction.highest_bid = bid_amount;
    auction.highest_bidder = tx_context::sender();
}

// Step 4: Close auction
public entry fun close(auction: &mut Auction) {
    assert!(auction.status == 1, 5);
    assert!(auction.owner == tx_context::sender(), 6);
    auction.status = 2;
    // In real design: transfer item and funds here
}

}


✅ Why This Design Works

Each transaction handles one step only.

The auction object persists across steps with key.

Status enforces workflow order.

Each auction is an independent object → parallel execution possible.

Access control ensures only the right user can perform certain steps.

Alternative Approaches

Capabilities instead of status flags (e.g., ActiveAuction struct vs EndedAuction struct).

Dynamic fields to attach bids without storing all in the auction object.

Do you want me to show a capability-based design (each step creates a new object type for stronger type safety), or a dynamic field approach for storing unlimited bids without large vectors?

3
评论
.
shamueely.
Aug 24 2025, 23:48

If you want to build a workflow in Move that needs several steps or multiple users to interact (like a game round, auction, or multi-signature process), the best approach is to represent the state of the workflow as an on-chain object. Each transaction then consumes and updates that object, ensuring the sequence follows the right order. You normally use a struct with a UID plus fields like phase, participants, or bids, so the program logic enforces which actions are valid at each stage. This way, you don’t rely on off-chain coordination — the object itself carries the workflow state and guarantees correctness through Move’s type system.

Here’s a simple auction-style example:

module auction::multi_step {
    use sui::object::{Self, UID};
    use sui::tx_context::TxContext;
    use sui::transfer;
    use std::option::{Self, Option};

    /// Auction states
    const STATE_OPEN: u8 = 0;
    const STATE_CLOSED: u8 = 1;

    struct Auction has key {
        id: UID,
        item: u64,
        highest_bid: u64,
        winner: Option<address>,
        state: u8,
    }

    /// Create auction in "open" state
    public entry fun create(item: u64, ctx: &mut TxContext) {
        let auction = Auction {
            id: object::new(ctx),
            item,
            highest_bid: 0,
            winner: option::none(),
            state: STATE_OPEN,
        };
        transfer::transfer(auction, tx_context::sender(ctx));
    }

    /// Step 1: Place bid (only valid if state = OPEN)
    public entry fun bid(auction: &mut Auction, bid: u64, sender: address) {
        assert!(auction.state == STATE_OPEN, 100);
        if (bid > auction.highest_bid) {
            auction.highest_bid = bid;
            auction.winner = option::some(sender);
        }
    }

    /// Step 2: Close auction (moves to CLOSED state)
    public entry fun close(auction: &mut Auction) {
        assert!(auction.state == STATE_OPEN, 101);
        auction.state = STATE_CLOSED;
    }
}

In this workflow, you create the auction first, users call bid in step two, and finally someone calls close to lock the state. The key idea is that the workflow object enforces order and access control, so invalid actions fail automatically. Compared to off-chain sequencing, this is safer because the blockchain itself tracks progress.

0
评论
.

你知道答案吗?

请登录并分享。

Move is an executable bytecode language used to implement custom transactions and smart contracts.

242帖子541答案
Sui.X.Peera.

赚取你的 1000 Sui 份额

获取声誉积分,并因帮助 Sui 社区成长而获得奖励。

奖励活动八月