Move.

帖子

分享您的知识。

Satoshi .
Sep 01, 2025
专家问答

How to implement escrow in Move for peer-to-peer trades?

What’s the simplest way to build an escrow smart contract in Move for secure peer-to-peer trades?

  • Move
  • Smart Contract
0
3
分享
评论
.

答案

3
shamueely.
Sep 1 2025, 12:58

You build a simple, safe escrow in Move by modeling the deal as a single Escrow resource (the state machine) and gating sensitive actions with capabilities, so you deposit assets from both parties, release atomically when conditions are met, and allow a refund path on timeout; in practice you define an Escrow object keyed by the two participants, store the coins (or object IDs) inside, emit events on each step for your UI, and accept a ReleaseCap only when both deposits are present—this avoids reentrancy, prevents double-spend, and keeps everything all-or-nothing in one transaction; here’s a compact pattern you can copy:

module p2p::escrow {
    use sui::coin::{Self, Coin};
    use sui::sui::SUI;
    use sui::tx_context::{Self, TxContext};
    use sui::clock;

    struct Escrow has key {
        a: address,
        b: address,
        a_paid: bool,
        b_paid: bool,
        coin_a: option::Option<Coin<SUI>>,
        coin_b: option::Option<Coin<SUI>>,
        deadline_ms: u64,
    }

    struct ReleaseCap has key {} // authority to finalize

    public entry fun open(a: &signer, b: address, deadline_ms: u64, ctx: &mut TxContext) {
        tx_context::transfer(Escrow {
            a: signer::address_of(a), b, a_paid: false, b_paid: false,
            coin_a: option::none(), coin_b: option::none(), deadline_ms
        }, signer::address_of(a));
        tx_context::transfer(ReleaseCap {}, signer::address_of(a));
    }

    public entry fun deposit_for_a(a: &signer, mut e: Escrow, c: Coin<SUI>, ctx: &mut TxContext) {
        assert!(signer::address_of(a) == e.a, 0);
        assert!(!e.a_paid, 1);
        e.coin_a = option::some(c);
        e.a_paid = true;
        tx_context::transfer(e, signer::address_of(a));
    }

    public entry fun deposit_for_b(b: &signer, mut e: Escrow, c: Coin<SUI>, ctx: &mut TxContext) {
        assert!(signer::address_of(b) == e.b, 2);
        assert!(!e.b_paid, 3);
        e.coin_b = option::some(c);
        e.b_paid = true;
        tx_context::transfer(e, signer::address_of(b));
    }

    public entry fun release(mut e: Escrow, _cap: &ReleaseCap, ctx: &mut TxContext) {
        assert!(e.a_paid && e.b_paid, 4);
        let ca = option::extract(&mut e.coin_a);
        let cb = option::extract(&mut e.coin_b);
        coin::transfer(ca, e.b);
        coin::transfer(cb, e.a);
        // e is consumed; atomic swap complete
    }

    public entry fun refund(mut e: Escrow, now: &clock::Clock, ctx: &mut TxContext) {
        assert!(clock::now_millis(now) >= e.deadline_ms, 5);
        if (e.a_paid) coin::transfer(option::extract(&mut e.coin_a), e.a);
        if (e.b_paid) coin::transfer(option::extract(&mut e.coin_b), e.b);
        // e is consumed; both parties refunded after timeout
    }
}
// transaction block: both deposit then release (TypeScript, @mysten/sui.js/transactions)
import { Transaction } from '@mysten/sui.js/transactions';
const tx = new Transaction();
const escrowId = '0x...';            // the Escrow object ID
const capId = '0x...';               // ReleaseCap object ID held by opener
const coinA = tx.splitCoins(tx.gas, [tx.pure.u64(1_000_000)]); // example SUI split
tx.moveCall({ target: 'p2p::escrow::deposit_for_a', arguments: [tx.object(escrowId), coinA] });
const coinB = tx.splitCoins(tx.gas, [tx.pure.u64(1_000_000)]);
tx.moveCall({ target: 'p2p::escrow::deposit_for_b', arguments: [tx.object(escrowId), coinB] });
tx.moveCall({ target: 'p2p::escrow::release', arguments: [tx.object(escrowId), tx.object(capId)] });
// sign + execute: client.signAndExecuteTransaction({ transaction: tx });
0
评论
.
NakaMoto. SUI.
Sep 1 2025, 18:42

Here’s the simplest pattern for building a peer-to-peer escrow in Move on Sui, with resource safety and ownership transfer enforced by the type system:

Core Principles

  1. Funds as Owned Objects: The seller deposits their asset (SUI or custom coin) into an escrow object that is owned by the smart contract until release.

  2. Capability Tokens: Grant the buyer and seller each an owned capability so they can call specific functions (e.g., cancel or complete).

  3. No Global State: Each escrow is its own object to avoid contention and enable parallel execution.

  4. Finalize via Explicit Action: The Move module enforces the release conditions.


Example: Simple Escrow Move Module

module p2p::escrow { use sui::object::{Self, UID}; use sui::transfer; use sui::tx_context::TxContext; use sui::coin::{Coin, SUI};

/// Represents the escrow state
struct Escrow<phantom T> has key {
    id: UID,
    seller: address,
    buyer: address,
    asset: Coin<T>,  // Locked asset
}

/// Capability for buyer to claim
struct BuyerCap has key { id: UID }

/// Capability for seller to cancel
struct SellerCap has key { id: UID }

/// Initialize an escrow with the seller's asset
public entry fun create<T>(
    buyer: address,
    asset: Coin<T>,
    ctx: &mut TxContext
) {
    let escrow = Escrow<T> {
        id: object::new(ctx),
        seller: tx_context::sender(ctx),
        buyer,
        asset,
    };
    let buyer_cap = BuyerCap { id: object::new(ctx) };
    let seller_cap = SellerCap { id: object::new(ctx) };

    transfer::public_transfer(buyer_cap, buyer);
    transfer::public_transfer(seller_cap, tx_context::sender(ctx));
    transfer::share_object(escrow);
}

/// Buyer completes the trade by claiming the asset
public entry fun claim<T>(
    escrow: &mut Escrow<T>,
    _cap: BuyerCap,
    ctx: &mut TxContext
) {
    assert!(tx_context::sender(ctx) == escrow.buyer, 0);
    let asset = escrow.asset;
    escrow.asset = Coin::zero<T>(); // Clear
    transfer::public_transfer(asset, tx_context::sender(ctx));
}

/// Seller cancels the trade if buyer doesn't complete
public entry fun cancel<T>(
    escrow: &mut Escrow<T>,
    _cap: SellerCap,
    ctx: &mut TxContext
) {
    assert!(tx_context::sender(ctx) == escrow.seller, 0);
    let asset = escrow.asset;
    escrow.asset = Coin::zero<T>();
    transfer::public_transfer(asset, tx_context::sender(ctx));
}

}


Key Features

Resource Safety: Assets are locked in Escrow (a resource) and cannot be duplicated or lost.

No Double Spend: Buyer and seller actions require their respective capability.

Parallelizable: Each escrow is an independent object, so multiple escrows run in parallel.

Best Practices

Add timeouts (e.g., block height checks) before cancel is allowed.

Use events to log escrow creation, claim, and cancel for off-chain analytics.

Support arbitrary assets by using a generic type parameter for the coin type.

0
评论
.
290697tz.
Sep 3 2025, 02:05

A simple escrow in Move on Sui works by introducing a neutral object that temporarily holds assets until conditions are met. You define an Escrow struct that contains the seller’s asset, the buyer’s address, and possibly a price or agreed condition. The seller deposits their asset into the escrow object using an entry function. The buyer then calls another entry function to deposit payment, usually in SUI or a custom coin type. Once both sides have deposited, a “release” function transfers the asset to the buyer and the payment to the seller. If the trade fails or times out, you can add a “cancel” function that allows the seller to reclaim their asset. Access control is crucial: only the intended buyer should be able to trigger release, and only the seller should be able to cancel. Events should be emitted at each stage (deposit, release, cancel) so off-chain services can track escrow status. For security, you should avoid keeping large logic in shared state—store minimal data in the escrow object and move assets directly. This design ensures fairness, as neither party can cheat once the escrow contract is the neutral custodian.

0
评论
.

你知道答案吗?

请登录并分享。

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

249帖子556答案
Sui.X.Peera.

赚取你的 1000 Sui 份额

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

奖励活动九月