Skip to main content

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:

  1. 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};
}
  1. 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;
  1. Define the Lock and Key structs: Create a Lock struct that stores any content inside it and a Key struct that is linked to its Lock using an ID 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,
}
  1. Create the key_for function: Define a public function key_for that takes a reference to a Key<T> and returns the ID of the associated Lock.
public fun key_for<T: store + key>(key: &Key<T>): ID {
key.for
}
  1. Create the create function: Define an entry function create that takes an object of type T and a mutable transaction context as arguments. It creates a new Lock with the given object and a Key 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));
}
  1. Create the lock and unlock functions: Define entry functions lock and unlock that take an object of type T, a mutable reference to a Lock<T>, and a reference to a Key<T> as arguments. The lock function locks the object inside the shared lock if it's empty and the key matches the lock. The unlock 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