Skip to content

Commit

Permalink
Add Escrow app example to docs (#15812)
Browse files Browse the repository at this point in the history
## Description 

Added the smart contract component to the Esrow app example

---------

Co-authored-by: Daniel Lam <[email protected]>
Co-authored-by: Ronny Roland <[email protected]>
  • Loading branch information
3 people authored Feb 23, 2024
1 parent 60fba35 commit 06749b1
Show file tree
Hide file tree
Showing 9 changed files with 1,659 additions and 9 deletions.
1 change: 1 addition & 0 deletions docs/content/guides/developer/app-examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The ever-growing number of examples in this section showcase packages for the Su

Sui is dedicated to providing a wide range of examples to guide you in proper programming techniques for the Sui blockchain. This list will continue to grow, so check back often.

- [Trading](./app-examples/trading.mdx): This example demonstrates trading objects on the Sui blockchain using a shared object as an escrow account.
- [Blackjack](./app-examples/blackjack.mdx): This example demonstrates the logic behind an on-chain version of the popular casino card game, Blackjack.
- [Coin Flip](./app-examples/coin-flip.mdx): The Coin Flip app demonstrates on-chain randomness.
- [Distributed Counter](./app-examples/e2e-counter.mdx): An end-to-end example that creates a basic decentralized counter that anyone can increment, but only the object owner can reset it. The example includes Move code to create the package and leverages the Sui TypeScript SDK to provide a basic frontend.
Expand Down
6 changes: 0 additions & 6 deletions docs/content/guides/developer/app-examples/escrow.mdx

This file was deleted.

29 changes: 29 additions & 0 deletions docs/content/guides/developer/app-examples/trading.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Trading
hide_table_of_contents: true
---

Escrow refers to a legal concept where a third party holds and regulates payment of the funds required for two parties involved in a given transaction. It helps make transactions more secure by keeping the payment in a secure escrow account that is released only when all of the terms of an agreement are met as overseen by the party acting as the escrow authority.

This guide and example demonstrate an atomic swap on Sui, which is similar to an escrow but does not require a trusted third party. Instead, this example uses a [shared object](../../../concepts/object-ownership/shared.mdx) to act as the escrow between two Sui users wanting to trade. Shared objects are a unique concept to Sui. Any transaction and any signer can modify it, given the changes meet the requirements set forth by the package that defined the type.

:::info

See [Shared versus Owned Objects](../sui-101/shared-owned.mdx) for more information on the differences between object types.

:::

The guide is split into three parts:

1. [Backend](./trading/backend.mdx): The modules that hold the state and perform the swaps.
1. [Indexing and API Service](./trading/indexer-api.mdx): A service that indexes chain state to discover trades, and an API service to read this data.
1. [Frontend](./trading/frontend.mdx): Enables users to list objects for sale and to accept trades.

{@include: ../../../snippets/app-examples-trading-source.mdx}

## Prerequisites

Before getting started, make sure you have:

- [Installed the latest version of Sui](../getting-started/sui-install.mdx).
- [Configured a valid network environment](../../../references/cli/client.mdx#set-current-environment), as you will be deploying the module on Testnet.
160 changes: 160 additions & 0 deletions docs/content/guides/developer/app-examples/trading/backend.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
---
title: Trading Backend
sidebar_label: Backend
---

:::note Multi-Page Guide

This is the first in a [three-part guide](../trading.mdx) on how to build a trustless atomic swap on Sui.

:::

This particular protocol consists of three phases:

1. One party `lock`s their object, obtaining a `Locked` object and its `Key`. This party can `unlock` their object to preserve liveness if the other party stalls before completing the second stage.
2. The other party registers a publicly accessible, shared `Escrow` object. This effectively locks their object at a particular version as well, waiting for the first party to complete the swap. The second party is able to request their object is returned to them, to preserve liveness as well.
3. The first party sends their locked object and its key to the shared `Escrow` object. This completes the swap, as long as all conditions are met: The sender of the swap transaction is the recipient of the `Escrow`, the key of the desired object (`exchange_key`) in the escrow matches the key supplied in the swap, and the key supplied in the swap unlocks the `Locked<U>`.

Let's create a Sui project using the terminal command `sui move new escrow`. Create a file in the sources directory named `shared.move`, and let's go through the code together.

## shared.move

{@include: ../../../../snippets/app-examples-trading-source.mdx}

Let's go through the code line by line:

### Structs

#### `Escrow<T>`

This struct represents an object held in escrow. It contains the following fields:

- `id`: Unique identifier for the escrow object.
- `sender`: Address of the owner of the `escrowed` object.
- `recipient`: Intended recipient of the `escrowed` object.
- `exchange_key`: ID of the key that opens the lock on the object sender wants from the recipient.
- `escrowed`: The actual object held in escrow.

```move
struct Escrow<T: key + store> has key, store {
id: UID,
sender: address,
recipient: address,
exchange_key: ID,
escrowed: T,
}
```

The ID of the key is used as the `exchange_key`, rather than the ID of the object, to ensure that the object is not modified after a trade has been initiated.

### Error codes

Two constants are defined to represent potential errors during the execution of the swap:

- `EMismatchedSenderRecipient`: The `sender` and `recipient` of the two escrowed objects do not match.
- `EMismatchedExchangeObject`: The `exchange_for` fields of the two escrowed objects do not match.

```move
const EMismatchedSenderRecipient: u64 = 0;
const EMismatchedExchangeObject: u64 = 1;
```

### Public functions

#### `create`

This function is used to create a new escrow object. It takes four arguments: the object to be escrowed, the ID of the key that opens the lock on the object the sender wants from the recipient, the intended recipient, and the transaction context.

```move
public fun create<T: key + store>(
escrowed: T,
exchange_key: ID,
recipient: address,
ctx: &mut TxContext
) {
let escrow = Escrow {
id: object::new(ctx),
sender: tx_context::sender(ctx),
recipient,
exchange_key,
escrowed,
};
transfer::public_share_object(escrow);
}
```

#### `swap`

This function is used to perform the swap operation. It takes four arguments: the escrow object, the key, the locked object, and the transaction context.

```move
public fun swap<T: key + store, U: key + store>(
escrow: Escrow<T>,
key: Key,
locked: Locked<U>,
ctx: &TxContext,
): T {
let Escrow {
id,
sender,
recipient,
exchange_key,
escrowed,
} = escrow;
assert!(recipient == tx_context::sender(ctx), EMismatchedSenderRecipient);
assert!(exchange_key == object::id(&key), EMismatchedExchangeObject);
// Do the actual swap
transfer::public_transfer(lock::unlock(locked, key), sender);
object::delete(id);
escrowed
}
```

The `object::delete` function call is used to delete the shared `Escrow` object. Previously, Move supported only the deletion of owned objects, but [shared-object deletion has since been enabled](https://github.com/MystenLabs/sui/pull/16008).

#### `return_to_sender`

This function is used to cancel the escrow and return the escrowed item to the sender. It takes two arguments: the escrow object and the transaction context.

```move
public fun return_to_sender<T: key + store>(
escrow: Escrow<T>,
ctx: &TxContext
): T {
let Escrow {
id,
sender,
recipient: _,
exchange_key: _,
escrowed,
} = escrow;
assert!(sender == tx_context::sender(ctx), EMismatchedSenderRecipient);
object::delete(id);
escrowed
}
```

Once again, the shared `Escrow` object is deleted after the escrowed item is returned to the sender.

### Tests

The code includes several tests to ensure the correct functioning of the atomic swap process. These tests cover successful swaps, mismatches in sender or recipient, mismatches in the exchange object, tampering with the object, and returning the object to the sender.

In conclusion, this code provides a robust and secure way to perform atomic swaps of objects in a decentralized system, without the need for a trusted third party. It uses shared objects and a series of checks to ensure that the swap only occurs if all conditions are met.

## Deployment

{@include: ../../../../snippets/initialize-sui-client-cli.mdx}

{@include: ../../../../snippets/publish-to-devnet-with-coins.mdx}

## Next steps

You have written and deployed the Move package. To turn this into a complete dApp with frontend, you need to create a frontend. For the frontend to be updated, create an indexer that listens to the blockchain as escrows are made and swaps are fulfilled.

For the next step, you [create the indexing service](./indexer-api.mdx).
Loading

0 comments on commit 06749b1

Please sign in to comment.