Implementing the ID Pointer Pattern
In this tutorial, we will demonstrate how to implement the ID Pointer pattern in the Sui ecosystem. The ID Pointer pattern separates an object's main data and its accessors/capabilities by linking the latter to the original. This pattern can be used in various applications, such as issuing transferable capabilities for shared objects, splitting dynamic data and static data, and avoiding unnecessary type linking in generic applications.
For this example, we will implement a simple Lock
and Key
mechanics on Sui, where a Lock<T>
is a shared object that can contain any object, and a Key
is an owned object required to access the contents of the lock.
Follow these steps to implement the ID Pointer pattern:
- Create the lock_and_key module: Define a new module
examples::lock_and_key
that will implement the ID Pointer pattern.
module examples::lock_and_key {
use sui::object::{Self, ID, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
use std::option::{Self, Option};
}
- Define constants and errors: Define error codes for the lock being empty, the key not matching the lock, and the lock being full.
const ELockIsEmpty: u64 = 0;
const EKeyMismatch: u64 = 1;
const ELockIsFull: u64 = 2;
- Define the Lock and Key structs: Create a
Lock
struct that stores any content inside it and aKey
struct that is linked to itsLock
using anID
field.
struct Lock<T: store + key> has key {
id: UID,
locked: Option<T>
}
struct Key<phantom T: store + key> has key, store {
id: UID,
for: ID,
}
- Create the key_for function: Define a public function
key_for
that takes a reference to aKey<T>
and returns the ID of the associatedLock
.
public fun key_for<T: store + key>(key: &Key<T>): ID {
key.for
}
- Create the create function: Define an entry function
create
that takes an object of typeT
and a mutable transaction context as arguments. It creates a newLock
with the given object and aKey
linked to the lock, then transfers the key to the transaction sender.
public entry fun create<T: store + key>(obj: T, ctx: &mut TxContext) {
let id = object::new(ctx);
let for = object::uid_to_inner(&id);
transfer::share_object(Lock<T> {
id,
locked: option::some(obj),
});
transfer::transfer(Key<T> {
for,
id: object::new(ctx)
}, tx_context::sender(ctx));
}
- Create the lock and unlock functions: Define entry functions
lock
andunlock
that take an object of typeT
, a mutable reference to aLock<T>
, and a reference to aKey<T>
as arguments. Thelock
function locks the object inside the shared lock if it's empty and the key matches the lock. Theunlock
function unlocks the lock and returns its contents if the key matches the lock and it's not empty.
public entry fun lock<T: store + key>(
obj: T,
lock: &mut Lock<T>,
key: &Key<T>,
) {
assert