# Why Build on EVE Frontier?

<figure><img src="https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2FCcrQJliZ4OPw3WXFPHV3%2Fbanner_gate.png?alt=media&amp;token=d010ecbe-6c44-404f-a266-1c93b9efe17e" alt=""><figcaption></figcaption></figure>

In **EVE Frontier**, survival is only the starting point. What truly defines this next generation sandbox is the unprecedented agency it gives to Builders, creators, and developers. Frontier expands on decades of EVE design philosophy, player-driven economies, emergent gameplay, and systemic depth, and pushes it into a new era of openness, modularity, and community-driven evolution.

## A Sandbox Where Builders Shape the Universe

At the core of Frontier are **Smart Assemblies**, physical and programmable structures anchored in space. These are not just buildings, they are the foundation of a living infrastructure layer that players can design, modify, automate, and optimize. Turrets, markets, storage facilities, refineries, logistics hubs, and defensive networks become active components of your strategy. Your constructions do not just exist in the world, they act within it.

Frontier is designed as a true open environment. The world evolves not only through player actions, but also through server-side, real-time modding by the community. This is a game where builders can directly influence the systems that govern it.

## Construct to Expand, Program to Dominate

Building in Frontier is not cosmetic, it is strategic.

### Build Infrastructure to Expand Your Influence

Every structure you place expands your agency in the Frontier. Bases become footholds, networks become territory, and entire playstyles emerge from how you design your industrial, logistical, or defensive footprint.

### Program Functionality to Master the Sandbox

Through the programmable layer of Smart Assemblies, you could for example:

* automate defenses
* create dynamic trade systems
* build custom logistics pipelines
* design specialized utility structures
* script unique gameplay interactions

The more you build, and the smarter your assemblies are, the more control you gain over your environment.

## A Fully Open Economic Sandbox

Frontier embraces a **community-driven, open in-game economy**. Builders can create:

* custom currencies
* decentralized markets
* trading networks
* service economies
* reputation-based systems

Every tool exists for players to shape the economic landscape, not just participate in it.

## Powered by Eve Frontier World Smart Contracts

The **World Contracts** extend Frontier's programmability far beyond structures. Any in-game entity can be represented on-chain and edited by player-written code, including:

* characters
* assemblies
* items
* killmails
* and more

This creates a composable and extensible universe where emergent gameplay is not just allowed, it is inevitable.

## Building on Blockchain: A Foundation for Player Ownership

* **True ownership of your creations** — your assets are verifiably yours, not stored on a centralized server that can be shut down.
* **A persistent universe** — your contributions endure on-chain, creating a living history that outlasts any single play session, update, or game cycle.
* **An open, composable economy** — player-built systems, markets, and tools can interact with each other, enabling a co-created world rather than a developer-dictated one.

## Why Sui: Built for a Universe of This Scale

Frontier's integration with [**Sui**](https://www.sui.io/intro-to-sui) is not a cosmetic technical choice — it directly enables the kind of world Frontier is designed to be.

* **Objects, not accounts** — Sui treats every asset as an individual object with its own identity, history, and ownership record.
* **Safe player modding at scale** — Smart Assemblies let anyone deploy code into a shared universe. Sui's Move language makes entire categories of exploits structurally impossible, so player creativity doesn't come at the cost of world stability.
* **Systems that talk to each other** — player-built structures, contracts, and tools can natively compose and extend one another, meaning what you build can plug into what others build.
* **No wallet, no gas, no friction** — Sui's zkLogin lets you sign in with your account credentials, and sponsored transactions mean you never need to buy tokens just to play. The blockchain is there, but you don't have to think about it.
* **Speed that matches gameplay** — sub-second transaction finality and parallel execution mean on-chain actions feel instant, not like waiting for a confirmation screen.

## Build the Future of the EVE Universe

By stepping into the Frontier ecosystem, you join a community empowered to shape the world at every level, from infrastructure to economics to systemic gameplay. Whether you are programming advanced Smart Assemblies, designing tools for other players, or building vast industrial complexes, Frontier gives you both the freedom and the framework to bring your ideas to life.

**EVE Frontier is open, collaborative, and powered by the creativity of its builders.**\
Together, we can define what it truly means to build in the EVE universe.

To start building refer [Modding](/smart-assemblies/introduction)


# Smart Infrastructure

<figure><img src="https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2FvvqKYsOxokKiKv3IWvnq%2Fbanner5.png?alt=media&amp;token=f6ef4e91-2b67-414d-9dbc-0bedac4d7981" alt=""><figcaption></figcaption></figure>

**Smart Infrastructure** in EVE Frontier transforms the game world into a truly programmable sandbox. As a builder, you’re empowered to create, extend, and customize persistent in-game structures (Smart Assemblies) using modern on-chain technology—centered now on the Sui blockchain and its language, Move.

## What Are Smart Assemblies?

Smart Assemblies are special in-game infrastructure: programmable, player-built structures anchored to specific locations within the EVE Frontier universe. Each assembly can serve a unique mechanical purpose and exposes configurable interfaces for builders to innovate far beyond default gameplay features.

Key examples include:

* **Smart Storage Unit**: Storage and dispensing of items.
* **Smart Turret**: Automated defense of zones and assets.
* **Smart Gate**: Transportation or access control.
* ...and many more to be designed, upgraded, and customized as the game continues to evolve.

## Builder Agency and Expression

Smart Assemblies are at the heart of EVE Frontier’s open-world philosophy. Every base, turret, shop, or gate can be programmed with new logic, interfaces, and behaviors. As a builder, you can evolve a storage unit into a real-time marketplace, a quest dispensing kiosk, or a mini-game terminal. The underlying rules are transparent, verifiable, and persistent—once deployed, your innovations become part of the simulation and its history.

What can you enable?

* **New game mechanics** (trading, missions, rewards)
* **Advanced asset management** (locking, dispensing, tokenization)
* **Player-driven economies** and custom rulesets
* **Automation** and real-time reactions to world events
* **Composable systems that extend other builders' creations** — your marketplace can use someone else's currency, your gate can reference another player's access list
* **External dApps and tools** that interact with the game world from outside the client
* ...and much, much more.

## The Technology: Sui and Move

Smart Assemblies are built and managed on the Sui blockchain, leveraging its unique features for developer access, composability, and security. Programming is done in [Move](https://move-language.github.io/move/), a language designed for safe, flexible on-chain interactions.

> **Note:** You do not need to be a Move expert to get started. Many tools, templates, and builder guides simplify the process, letting you focus on gameplay, not boilerplate.

## Ready to Begin?

Check out the available Smart Assemblies for inspiration, or dive into tutorials on creating your own.

Explore:

* [Smart Storage Unit](/smart-assemblies/storage-unit) — store, automate, trade
* [Smart Turret](/smart-assemblies/turret) — defend, alert, customize
* [Smart Gate](/smart-assemblies/gate) — transport, restrict, innovate


# Sui & Move Fundamentals

EVE Frontier runs on the [Sui](https://sui.io/) blockchain, leveraging the power and security of the [Move](https://docs.sui.io/concepts/sui-move-concepts) programming language. Understanding Sui’s architecture and Move’s unique features will unlock the full potential of developing Smart Assemblies and interactive systems for players.

***

## What is Sui?

**Sui** is a high-performance, Layer 1 blockchain designed for secure, scalable, and low-latency digital asset ownership and transfer. It is optimized for real-time, dynamic applications — like games and social platforms — where transaction throughput and parallelism matter.

**Key Sui Features:**

* **Low Latency:** Extremely fast transaction finality, suitable for real-time interactions.
* **High Throughput:** Scales horizontally by processing transactions in parallel, not just one at a time.
* **Object-Centric Model:** Every asset is a unique on-chain object with its own identity, ownership, and history.
* **Native Support for Games:** Built with composability and upgradability in mind.

Learn more: [Sui Documentation](https://docs.sui.io/concepts) — [Understanding Sui's architecture](https://docs.sui.io/concepts/architecture)

***

## What is Move?

**Move** is a smart contract language originally built for blockchain applications and now integral to the Sui ecosystem. Move prioritizes security, resource management, and flexibility, making it ideal for complex interactions in games.

**Key Move Features:**

* **Resource-Oriented Programming:** Assets are treated as first-class resources. They can’t be duplicated or accidentally destroyed, preventing common bugs in digital asset management.
* **Modules and Scripts:** Logic is organized into reusable modules, while scripts interact with those modules to perform actions.
* **Strong Typing:** Move enforces strict type and ownership checks at compile-time and runtime.
* **Upgradeable and Composable:** Contracts can be designed for modular upgrades and extensibility.

New to Move? Start with the [Hello World example](https://docs.sui.io/guides/developer/getting-started/hello-world)

***

## Getting Started

* [Learn Sui basics](https://docs.sui.io/concepts)
* [Try Move tutorials](https://docs.sui.io/guides/developer/getting-started/)
* [Explore Sui Dev Tools](https://docs.sui.io/references/sui-sdks)


# Environment Setup

To start building on EVE Frontier, follow the steps below to set up your local development tools. If you already have the tools, make sure they are the correct version to avoid difficulties building.

## Community tool: efctl (one-command setup)

The community CLI [**efctl**](https://frontier.scetrov.live/links/efctl/) automates the full local setup.

***

## Recommended: Docker (any OS)

The fastest way to get a Sui local development environment is with Docker. This works on Windows, Linux, and macOS with a single prerequisite.

**Prerequisite:** [Docker](https://docs.docker.com/get-docker/) installed and running.

Use the EVE Frontier builder-scaffold localnet setup:

1. Clone the builder-scaffold repo and navigate to the Docker setup:

```bash
mkdir -p workspace && cd workspace
git clone https://github.com/evefrontier/builder-scaffold.git
cd builder-scaffold/docker
```

2. Follow the instructions in the [localnet-setup/docker](https://github.com/evefrontier/builder-scaffold/blob/main/docker/readme.md) directory.

This gives you a pre-configured Sui localnet and development environment without installing Sui CLI, WSL, or platform-specific tools.

***

## Manual setup by OS

If you prefer to install tools directly on your system, follow the steps for your OS below.

{% tabs %}
{% tab title="Windows" %}
This guide is for **Windows** users. [suiup](https://github.com/MystenLabs/suiup) is a native Windows executable. Run commands in PowerShell, Command Prompt, or **Git Bash** (recommended if you prefer using the bash install script).

### Step 1: Install Git

Install [Git for Windows](https://git-scm.com/download/win) if you haven't already. Git Bash (included with the install) lets you run the suiup bash install script directly without needing to download the binary manually:

```bash
curl -sSfL https://raw.githubusercontent.com/MystenLabs/suiup/main/install.sh | sh
```

Then continue from Step 3 below. If you prefer the manual binary install, follow Steps 2–3 instead.

### Step 2: Install suiup

suiup is the recommended way to install and manage Sui CLI. It is a cross-compiled native executable for Windows.

1. **Download** the latest Windows release from [suiup releases](https://github.com/MystenLabs/suiup/releases). Choose the archive for Windows x86\_64 (e.g. `suiup-vX.X.X-windows-x86_64.zip`).
2. **Unzip** the archive. You'll get a folder containing the `suiup.exe` executable.
3. **Add suiup to your PATH** — choose one:

   **Option A — Use a dedicated bin folder (recommended)**

   * Create `%LOCALAPPDATA%\bin` if it doesn't exist (on most systems this is `C:\Users\<YourUsername>\AppData\Local\bin`).
   * Move `suiup.exe` into that folder.
   * Add this folder to your user PATH in Environment Variables:

   **Option B — Use the extracted folder**

   * Leave `suiup.exe` in the folder where you unzipped it.
   * Add that folder to your user PATH using the same steps as Option A (Environment Variables → Path → New).
4. **Restart** your terminal (PowerShell or Command Prompt) so the updated PATH is picked up. Test with `suiup --version`.

### Step 3: Install Sui CLI

```powershell
suiup install sui@testnet
```

Verify:

```powershell
sui --version
```

suiup installs binaries to `%LOCALAPPDATA%\bin`. Ensure this folder is on your `PATH`.

### Step 4 (Optional): Node.js and PNPM

For interaction using the TypeScript SDK, install [Node.js](https://nodejs.org/) (LTS) and then:

```powershell
npm install -g pnpm
```

{% endtab %}

{% tab title="Linux" %}
This guide is for **Linux** users. Supported: Ubuntu 22.04 or newer.

### Step 1: Install Git

```bash
sudo apt-get install git curl
```

### Step 2: Install Sui CLI via suiup

[suiup](https://github.com/MystenLabs/suiup) is the recommended installer for Sui.

```bash
curl -sSfL https://raw.githubusercontent.com/MystenLabs/suiup/main/install.sh | sh
```

Restart your shell, then install Sui for testnet:

```bash
suiup install sui@testnet
```

Verify:

```bash
sui --version
```

### Step 3 (Optional): Node.js and PNPM

For interaction using typescript sdk:

```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash && . ~/.bashrc && nvm install 24
npm install -g pnpm
```

{% endtab %}

{% tab title="macOS" %}
This guide is for **macOS** users. Supported: macOS Tahoe or newer.

### Step 1: Install Homebrew (if needed)

```sh
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

Add brew to your path:

```sh
export PATH="/opt/homebrew/bin:$PATH"
```

### Step 2: Install Git

```bash
brew install git
```

### Step 3: Install Sui CLI

**Option A — suiup (recommended):**

```bash
curl -sSfL https://raw.githubusercontent.com/MystenLabs/suiup/main/install.sh | sh
```

Restart your shell, then:

```bash
suiup install sui@testnet
```

**Option B — Homebrew:**

```bash
brew install sui
```

Verify:

```bash
sui --version
```

### Step 4 (Optional): Node.js and PNPM

For interaction using typescript sdk:

```sh
brew install node@24
npm install -g pnpm
```

{% endtab %}
{% endtabs %}

## Configure Sui Client

Installing Sui does not configure the client. To use `sui` commands:

1. [Configure the Sui client](https://docs.sui.io/guides/developer/getting-started/configure-sui-client) to create an address and connect to a network.
2. [Get SUI from the faucet](https://docs.sui.io/guides/developer/getting-started/get-coins) for testnet.

## Next Steps

You are ready to build on EVE Frontier. If you learn by doing, head to [Smart Assemblies](/smart-assemblies/storage-unit). To understand concepts first, see [Introduction to Smart Contracts](/smart-contracts/introduction-to-smart-contracts).


# Introduction to Smart Contracts

## What are Smart Contracts?

Smart contracts are programs that execute on the blockchain. They enforce persistent rules, manage assets, automate actions, and run shared logic in a secure, deterministic, and verifiable way. In EVE Frontier, smart contracts are the foundation for Smart Assembly functionality and on-chain business logic. Contracts are written in [Move](https://docs.sui.io/concepts/sui-move-concepts) and deployed to [Sui](https://sui.io/).

Sui validators compile and verify Move modules, execute transactions on the Move VM, and persist the resulting state on-chain. This ensures that all business logic runs deterministically and that any participant can verify correctness.

## Next Steps

* [Move Documentation](https://move-book.com/concepts/) — Move concepts.
* [Move Patterns in Frontier](/smart-contracts/move-patterns-in-frontier) — Key Move patterns used in the world contracts.
* [EVE Frontier World Explainer](/smart-contracts/eve-frontier-world-explainer) — Overview of the world architecture.
* [World Contracts](https://github.com/evefrontier/world-contracts) — Source code and examples.


# EVE Frontier World Explainer

<figure><img src="https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fa2qYo3yTUUDAE3FYkeb8%2Fbanner4.png?alt=media&amp;token=0b3b7825-bba3-4ec6-945c-00f6a55fc4ce" alt=""><figcaption></figcaption></figure>

The EVE Frontier world runs on [Sui](https://sui.io/) as Move smart contracts that define in-game structures, physics, and rules. The design stresses **composition over inheritance**: small building blocks combine into assemblies, which players can extend with custom logic.

***

## The Three-Layer Architecture

The world contracts use a three-layer architecture:

{% @mermaid/diagram content="flowchart TB
subgraph L3 \[Layer 3: Player Extensions]
direction LR
Ext\[Third-Party Smart Contracts]
end

```
subgraph L2 [Layer 2: Smart Assemblies]
    direction LR
    SU[storage_unit.move] --- Gate[gate.move] --- Turret[turret.move] --- Network_Node[network_node.move]
end

subgraph L1 [Layer 1: Primitives]
    direction LR
    status[status.move] --- loc[location.move] --- inv[inventory.move] --- fuel[fuel.move] --- energy[energy.move]
end

L3 --> L2
L2 --> L1" %}
```

**Layer 1: Primitives** — Low-level Move modules that implement the "digital physics" of the game. They are small, focused, and designed for reusability without circular dependencies. Examples: `location.move`, `inventory.move`, `fuel.move`, `status.move`, `network_node.move`.

**Layer 2: Assemblies** — Composed structures that players interact with (Storage Unit, Gate, Turret). Each assembly is a Sui shared object, enabling concurrent access by the game server and players. Assemblies combine primitives and expose public functions protected by capabilities or witnesses.

**Layer 3: Player Extensions** — Custom smart contracts built by players that extend assembly behavior. Extensions register with assemblies via the typed authentication witness pattern and are authorized by type identity.

Assemblies compose primitives. Player extensions authenticate via the [typed witness pattern](https://move-book.com/programmability/witness-pattern) when calling Layer 2.

***

## Layer 1: Primitives

Primitives are small, focused Move modules that implement basic mechanics:

* **`location.move`** — Spatial positioning and hashed location storage (for privacy).
* **`inventory.move`** — Item storage and transfers.
* **`fuel.move`** — Energy and resource consumption mechanics.
* **`status.move`** — Lifecycle of assembly (Anchored, Online, Offline).
* **`energy.move`** — Power generation and reservation.

Primitives expose `public(package)` functions, so only modules in the same package can mutate them. Players do not call primitives directly; assemblies use them internally. Primitive access is restricted to Frontier-designed assemblies.

**How assemblies use primitives** (example: Storage Unit):

{% @mermaid/diagram content="flowchart TD
status --> SU\[Storage Unit]
inventory --> SU
location --> SU
fuel --> SU
network\_node --> SU" %}

Other assemblies compose subsets of these primitives (e.g., Gate = status + location + fuel + network\_node).

***

## Layer 2: Smart Assemblies

Assemblies are in-game structures (storage units, gates and turrets) that players deploy and interact with. Each assembly is a **Sui shared object**, allowing concurrent access by the game and multiple players.

**Storage Unit** — Programmable on-chain storage. Supports extensions (via typed witness) and direct owner access.

**Gate (Stargate)** — Handles traversal between gates. Supports extensions (via typed witness).

**Turret** — Programmable structure for defense and targeting. Supports extensions (via typed witness).

Assemblies orchestrate primitives, enforce "digital physics" (e.g., proximity checks before withdraw), and expose public functions.

***

## Layer 3: Player Extensions (Moddability)

Players extend assembly behavior by deploying custom Move packages that register with assemblies through a **typed authentication witness** pattern. The assembly keeps an allowlist of registered extension `TypeName`s. A builder registers their witness type; only that module can create instances of it, so only that extension can call the assembly's authenticated entry points.

**Flow:**

1. Owner registers a witness type (e.g., `Builder::Auth`) from the builder's package.
2. Assembly adds the `TypeName` to its allowlist.
3. Builder's module calls assembly functions by passing its witness; the assembly verifies the type is registered.

```move
// World: assembly maintains allowlist of registered extension types
module world::assembly {
    public struct Assembly has key {
        id: UID,
        allowed_extensions: Option<TypeName>,
        // ... other fields
    }

    public fun perform_op<Auth: drop>(assembly: &mut Assembly, _auth: Auth) {
        // Verify Auth type is registered in allowed_extensions
        // ... business logic
    }

    public fun register_extension<Auth: drop>(assembly: &mut Assembly, owner_cap: &OwnerCap) {
        // Add extension_type to allowed_extensions
    }
}

// Builder: custom extension defines a witness type
module builder::custom_extension {
    public struct Auth has drop {} // Witness type — only this module can create it

    public entry fun swap_items(assembly: &mut world::assembly::Assembly) {
        world::assembly::perform_op(assembly, Auth {})
    }
}

// Owner registers the extension type (requires owner_cap)
assembly::register_extension<builder::custom_extension::Auth>(assembly, owner_cap);

// Players can then call the custom extension's entry points
builder::custom_extension::swap_items(assembly);
```

**Benefits:** Type-based authorization; dynamic registration without redeploying assemblies; builders add custom logic while assemblies enforce authorization by type identity.

***

## Privacy: Location Obfuscation

To support mechanics that require information asymmetry (e.g., hidden bases):

* **Hashed locations** — On-chain locations are stored as cryptographic hashes, not cleartext coordinates.
* **Proximity verification** — Interactions require proof that entities are at the same or adjacent locations. Current implementation uses signatures from a trusted game server; future implementations may use zero-knowledge proofs.

***

## Security Model

* **AdminACL operations** — Require the transaction to be sponsored by a authorised server.
* **Owner operations** — Require ownership certificates (e.g., `OwnerCap`) for assembly-specific changes.
* **Extension operations** — Use the witness type's `TypeName` to ensure calls come from registered third-party modules.

***

## Next Steps

**Learn more (Smart Contracts)** :

* [Object Model](/smart-contracts/object-model) - how objects are derived and shared.
* [Ownership Model](/smart-contracts/ownership-model) — understand access control.
* [Move Patterns in Frontier](/smart-contracts/move-patterns-in-frontier) — Key patterns used in the world contracts.

**Start Building** :

* Assembly-specific build guides: [Storage Unit](/smart-assemblies/storage-unit), [Gate](/smart-assemblies/gate), [Turret](/smart-assemblies/turret)
* [Interfacing with the EVE Frontier World](/tools/interfacing-with-the-eve-frontier-world) — How to read and write on-chain state.

**Reference**

* [World Contracts](https://github.com/evefrontier/world-contracts)
* [ADR](https://github.com/evefrontier/world-contracts/blob/main/docs/architechture.md)


# Object Model

All game resources exist both in-game and on-chain. This section explains how in-game items are deterministically mapped to Sui objects.

## Items

Every in-game resource is represented as an item with two key identifiers:

* **`item_id`** — unique identifier for the item in-game
* **`type_id`** — identifier for the item type (e.g., fuel type, assembly type)

A **singleton** item is uniquely identified by `item_id + tenant`. A **non-singleton** item is uniquely identified by `type_id + tenant`.

> `tenant` distinguishes different game server instances (e.g., production, testnet, development).

## Deterministic ID Derivation

On-chain object IDs are derived deterministically from the in-game identifiers using Sui's [derived objects](https://docs.sui.io/guides/developer/objects/derived-objects). This ensures a 1:1 mapping between in-game items and on-chain objects.

The derivation key is a `TenantItemId`:

```move
public struct TenantItemId has copy, drop, store {
    item_id: u64,
    tenant: String,
}
```

A single shared `ObjectRegistry` is used to derive all object IDs. Every game asset : characters, assemblies, network nodes derives its object ID from this registry using the same `TenantItemId` key. This guarantees that each in-game item ID can only be used once across all object types.

```move
public struct ObjectRegistry has key {
    id: UID,
}
```

When creating an object (e.g., a Character), the ID is derived from the registry:

```move
let character_key = in_game_id::create_key(game_character_id as u64, tenant);
let character_uid = derived_object::claim(registry.borrow_registry_id(), character_key);
```

Since the derivation is deterministic, the object ID can be **pre-computed** off-chain using the registry's object ID and the `TenantItemId`.

## Shared Objects

Most on-chain objects (characters, assemblies, network nodes) are [shared objects](https://docs.sui.io/concepts/object-ownership/shared). This allows both the game server (admin) and the character owner to mutate the same object without ownership transfers.

**Reference:**

* [`object_registry.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/registry/object_registry.move)
* [`in_game_id.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/primitives/in_game_id.move)


# Ownership Model

EVE Frontier uses a **capability-based** access control system. Instead of relying on wallet addresses for ownership, transferable capability objects (`OwnerCap`) grant access to on-chain objects.

## Access Hierarchy

```
GovernorCap  (deployer — top-level authority)
    └── AdminACL  (shared object — authorized sponsor addresses)
            └── OwnerCap<T>  (player — mutates a specific object)
```

* **`GovernorCap`** — created at deploy time. Can add/remove sponsors in `AdminACL`.
* **`AdminACL`** — a shared object containing a list of authorized sponsor addresses. Functions protected by `AdminACL` call `verify_sponsor(ctx)`, which checks the transaction sponsor.
* **`OwnerCap<T>`** — a typed "keycard" that authorizes mutation of **one specific object**.

```move
public struct OwnerCap<phantom T> has key {
    id: UID,
    authorized_object_id: ID,
}
```

## Character as a Keychain

All `OwnerCap`s are stored inside the player's `Character` object (not in their wallet directly). This is inspired by a real-world **keychain pattern** — a single master key (the character) grants access to many capabilities.

```
User Wallet
    └── Character (shared object, mapped to user address)
            ├── OwnerCap<NetworkNode>
            ├── OwnerCap<Gate>
            ├── OwnerCap<StorageUnit>
            └── ...
```

When a Smart Assembly is created, its `OwnerCap` is minted and [transferred to the `Character` object](https://docs.sui.io/guides/developer/objects/transfers/transfer-to-object) (transfer to object). If the user has access to the character, they have access to all its capabilities. To discover a character from a wallet address, see [Smart Character — Discovering character from wallet address](/smart-assemblies/smart-character#discovering-character-from-wallet-address).

## Borrow-Use-Return Pattern

To use an `OwnerCap`, the player **borrows** it from the character, uses it, and **returns** it all within a single transaction. This uses Sui's [`Receiving`](https://docs.sui.io/guides/developer/objects/transfers/transfer-to-object) pattern.

```move
// 1. Borrow the OwnerCap from the character
public fun borrow_owner_cap<T: key>(
    character: &mut Character,
    owner_cap_ticket: Receiving<OwnerCap<T>>,
    ctx: &TxContext,
): (OwnerCap<T>, ReturnOwnerCapReceipt)

// 2. Use it (e.g., bring a storage unit online)
public fun online(
    storage_unit: &mut StorageUnit,
    network_node: &mut NetworkNode,
    energy_config: &EnergyConfig,
    owner_cap: &OwnerCap<StorageUnit>,
)

// 3. Return it to the character
public fun return_owner_cap<T: key>(
    character: &Character,
    owner_cap: OwnerCap<T>,
    receipt: ReturnOwnerCapReceipt,
)
```

A [`ReturnOwnerCapReceipt`](https://github.com/evefrontier/world-contracts/blob/3cc9ffad767cb499bfb31b3c9abe28f653ed5d69/contracts/world/sources/access/access_control.move#L30) (hot potato) ensures the cap is always returned or explicitly transferred, it cannot be silently dropped.

### TypeScript Example

A typical owner-authenticated call (e.g., bringing a network node online):

```typescript
// 1. Borrow OwnerCap from character
const [ownerCap, receipt] = tx.moveCall({
  target: `${packageId}::character::borrow_owner_cap`,
  typeArguments: [`${packageId}::network_node::NetworkNode`],
  arguments: [tx.object(characterId), tx.object(ownerCapId)],
});

// 2. Use OwnerCap to bring network node online
tx.moveCall({
  target: `${packageId}::network_node::online`,
  arguments: [tx.object(nwnId), ownerCap, tx.object(CLOCK_OBJECT_ID)],
});

// 3. Return OwnerCap to character
tx.moveCall({
  target: `${packageId}::character::return_owner_cap`,
  arguments: [tx.object(characterId), ownerCap, receipt],
});
```

## Transferring OwnerCap

You can transfer an `OwnerCap` to another address instead of returning it to the character (e.g. another player or an object managed by a tribe/corporation) using functions defined in [`access_control.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/access/access_control.move).

* **Transfer while borrowed** — If you borrowed the cap and have a `ReturnOwnerCapReceipt`, use [`transfer_owner_cap_with_receipt`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/access/access_control.move#L129) to send the cap to a new owner in the same transaction. The receipt is consumed; the cap is not returned to the original character.
* **Direct transfer** — [`transfer_owner_cap<T>(owner_cap, owner)`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/access/access_control.move#L100) sends the cap to a given address or [object ID](https://docs.sui.io/guides/developer/objects/transfers/transfer-to-object) (e.g. another Character). Only the current owner can call this; the Sui runtime enforces ownership.

So you can hand off an assembly access to another player, or to an address that represents a tribe/corporation. The contracts currently transfer to a single address; multi-party access (e.g. a capability shared by a tribe) is planned as a future extension via a capability registry.

## Benefits

* **Centralized ownership** — manage all capabilities from a single character object
* **Granular access** — each `OwnerCap<T>` only authorizes one specific object
* **Delegatable** — transfer an `OwnerCap` without moving the underlying assembly
* **Composable** — the borrow-use-return pattern works within programmable transactions

**Reference:**

* [Transfer to Object (Sui docs)](https://docs.sui.io/guides/developer/objects/transfers/transfer-to-object)
* [`access_control.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/access/access_control.move)
* [`character.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/character/character.move)


# Move Patterns in Frontier

Key Move patterns used in the EVE Frontier world contracts:

***

## Derived object ID

On-chain object IDs are **derived deterministically** from in-game identifiers (`item_id` + `tenant`) using Sui’s [derived objects](https://docs.sui.io/guides/developer/objects/derived-objects). A single `ObjectRegistry` ensures one in-game item maps to one on-chain object across characters, assemblies, and network nodes.

**Example:** IDs are claimed from the registry when creating assemblies (e.g. [`assembly::anchor`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/assembly.move#L178)).\
**More:** [Object model](/smart-contracts/object-model) — items, `TenantItemId`, and derivation.

***

## Shared object model

Most world objects (characters, assemblies, network nodes) are **shared objects**. The game server (admin) and the object owner can both mutate the same object without transferring ownership. Access is enforced via capabilities, not object ownership.

**More:** [Object model](/smart-contracts/object-model).

***

## Hot potato

A **hot potato** is a struct with no `drop` ability. It must be consumed in the same transaction (e.g. by calling a function that takes it and destroys it). Frontier uses this to enforce multi-step flows.

**Examples in world contracts:**

* **ReturnOwnerCapReceipt** — Returned when borrowing an OwnerCap from a Character; it must be used to either return or transfer the cap, so the cap cannot be dropped or lost.
* **OfflineAssemblies** — [`network_node::offline`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/network_node/network_node.move#L133) returns a hot potato; the caller must bring all connected assemblies offline and then call `destroy_offline_assemblies` in the same transaction.

***

## Witness pattern (typed extension)

A **witness** is a one-time type that proves “this call comes from the module that defines this type.” Frontier uses it across **assemblies** so that only a specific extension module can authorize itself on an assembly. Once authorized, that custom contract can change the assembly's behaviour.

**Example:** [`gate::authorize_extension`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/gate.move#L167) — the gate calls `type_name::with_defining_ids()` and stores that type as its extension. Only the module that defines that type could have produced the witness, so only that module’s logic can be registered. Other assemblies use the same pattern to allow custom contracts to extend their behaviour.

***

## Receiving as object (transfer to object)

OwnerCaps are **borrowed** from the Character object for a single transaction using Sui’s [transfer to object](https://docs.sui.io/guides/developer/objects/transfers/transfer-to-object) (e.g. `Receiving<OwnerCap<T>>`). The Character has a `Receiving` slot; the client passes a ticket to materialize the OwnerCap, uses it, then returns it (or transfers it) in the same transaction.

**In world contracts:** [character.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/character/character.move) — [`borrow_owner_cap`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/character/character.move#L191), [`return_owner_cap`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/character/character.move#L207).\
**More:** [Move book: Transfer to object](https://move-book.com/storage/transfer-to-object); [Ownership model](/smart-contracts/ownership-model) — borrow-use-return pattern.

***

## Dynamic fields

**Dynamic fields** let you attach key–value data to an object without changing the object’s type. Frontier uses them for ephemeral (e.g. temporary inventories) that can be added and removed at runtime.

**More:** [Move book: Dynamic fields](https://move-book.com/programmability/dynamic-fields).

***

## See also

* [Object model](/smart-contracts/object-model)
* [Ownership model](/smart-contracts/ownership-model)
* [EVE Frontier World Explainer](/smart-contracts/eve-frontier-world-explainer)
* [World contracts (GitHub)](https://github.com/evefrontier/world-contracts)


# Introduction to Modding Smart Assemblies

Some EVE Frontier smart assemblies are **programmable** — you can customize their in-game behavior by deploying custom Move contracts.

## Getting Started

Prerequisites to customize a smart assembly:

1. **Create a Character** — your on-chain identity that owns all your assemblies. See [Smart Character](/smart-assemblies/smart-character).
2. **Build a Network Node** — anchor a network node at a Lagrange point. This is the power source for your base. See [Network Node](/smart-assemblies/network-node).
3. **Deposit Fuel & Go Online** — deposit fuel into the network node and bring it online to start generating energy.
4. **Anchor a Smart Assembly** — create a smart assembly (e.g., Storage Unit, Gate) in your base. It automatically connects to the network node for energy.
5. **Bring the Assembly Online** — the assembly reserves energy from the network node and becomes operational.

> For local development and testing, refer to [builder-scaffold](https://github.com/evefrontier/builder-scaffold) so you have everything you need to directly write custom logic for your smart assembly.

> For a one-command automated setup, see the community tool [efctl](https://frontier.scetrov.live/links/efctl/) ([docs](/tools/efctl)).

## Programmable Assemblies

Each assembly type has its own extension pattern. Each section has a **concept overview** (how the assembly works and its API) and a **build guide** (step-by-step instructions to write, publish, and test a custom extension):

* [Smart Gate](/smart-assemblies/gate) — custom rules for space travel (e.g., toll gates, access lists) · [Build](/smart-assemblies/gate/build)
* [Smart Storage Unit](/smart-assemblies/storage-unit) — custom rules for item deposits and withdrawals (e.g., vending machines, trade hubs) · [Build](/smart-assemblies/storage-unit/build)
* [Smart Turret](/smart-assemblies/turret) — custom targeting logic · [Build](/smart-assemblies/turret/build)

To read and write world state from code (SDK, GraphQL, gRPC), see [Interfacing with the EVE Frontier World](/tools/interfacing-with-the-eve-frontier-world).


# Smart Character

A Smart Character is the on-chain representation of an in-game character. It serves as the player's **identity on-chain** and the **owner of all assemblies** they create.

## Overview

When a player creates a character in-game, a corresponding `Character` object is created on-chain with the same character ID. The character is associated with a **tribe** and mapped to the player's **wallet address**.

## Character as Capability Holder

The character object acts as a **keychain** — it holds the `OwnerCap` for every object the player owns (network nodes, gates, storage units, etc.). See [Ownership Model](/smart-contracts/ownership-model) for details on the borrow-use-return pattern.

## Creation

Characters are created by the game server (admin) with a deterministic object ID derived from the in-game character ID.

## Discovering character from wallet address

The character object is a shared object; you need its object ID to interact with it. To get a character from a **wallet address** (e.g. when a player connects their wallet to your dApp), the game creates a **PlayerProfile** at character creation and [transfers it to the wallet](https://github.com/evefrontier/world-contracts/pull/119). It contains only `character_id`. Query objects owned by the wallet with type `PlayerProfile` to obtain the character ID, then fetch the `Character` object.

For a full GraphQL query and variables, see [Query character by wallet address](/tools/interfacing-with-the-eve-frontier-world#query-character-by-wallet-address).

## Access Control

Only the wallet address stored in `character_address` can borrow `OwnerCap`s from the character. This is enforced in the `borrow_owner_cap` function:

```move
public fun borrow_owner_cap<T: key>(
    character: &mut Character,
    owner_cap_ticket: Receiving<OwnerCap<T>>,
    ctx: &TxContext,
): (OwnerCap<T>, ReturnOwnerCapReceipt) {
    assert!(character.character_address == ctx.sender(), ESenderCannotAccessCharacter);
    // ...
}
```

**Reference:**

* [`character.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/character/character.move)


# Network Node

The Network Node is the **power source** for a player's base. It burns fuel to produce energy (measured in GJ) which is consumed by all connected smart assemblies.

## Overview

A player anchors a Network Node at a Lagrange point to establish a base. All smart assemblies built in this base connect to the network node to draw energy for their operations.

## Fuel System

Fuel is deposited into the network node and burned over time. Different fuel types have different **efficiencies** that affect how long they last.

### Fuel Efficiency

Each fuel type has a configurable efficiency (10–100%). The **actual consumption rate** depends on the burn rate and fuel efficiency:

```
actualConsumptionRate = burnRateInMs × (fuelEfficiency / 100)
```

| Burn Rate       | Fuel Efficiency | Actual Duration |
| --------------- | --------------- | --------------- |
| 3600s (1 hour)  | 100%            | 60 minutes      |
| 3600s (1 hour)  | 80%             | 48 minutes      |
| 3600s (1 hour)  | 50%             | 30 minutes      |
| 7200s (2 hours) | 70%             | 84 minutes      |

In-game fuel efficiency is represented by fuel type:

| Fuel type   | Efficiency |
| ----------- | ---------- |
| EU-90 Fuel  | 90%        |
| SOF-80 Fuel | 80%        |
| EU-40 Fuel  | 40%        |
| D2 FUEL     | 15%        |

### Fuel Lifecycle

1. **Deposit** — add fuel to the network node
2. **Start burning** — when the network node goes online, fuel burning begins and produces energy
3. **Stop burning** — saves remaining elapsed time for the next cycle
4. **Depletion** — when fuel runs out, the network node goes offline and all connected assemblies are forced offline

## Energy System

When the network node is online and burning fuel, it produces a fixed amount of energy (GJ). Assemblies **reserve** energy when they come online and **release** it when they go offline.

Each assembly type requires a fixed amount of energy to operate (configured in `EnergyConfig`):

```move
public struct EnergyConfig has key {
    id: UID,
    assembly_energy: Table<u64, u64>, // assembly_type_id -> energy required (GJ)
}
```

### Example

| Step | Action                                                   | Available Energy |
| ---- | -------------------------------------------------------- | ---------------- |
| 1    | Network node online, producing 100 GJ                    | 100 GJ           |
| 2    | Storage Unit A connects and goes online (requires 50 GJ) | 50 GJ            |
| 3    | Storage Unit B connects and goes online (requires 50 GJ) | 0 GJ             |
| 4    | Cannot online another assembly — no energy left          | 0 GJ             |
| 5    | Storage Unit A goes offline, releases 50 GJ              | 50 GJ            |
| 6    | Gate connects and goes online (requires 1 GJ)            | 49 GJ            |

If the network node runs out of fuel, it goes **offline** — all connected assemblies are automatically brought offline.

## Assembly Connection

Assemblies connect to a network node when they are anchored. The network node powers all connected assemblies:

### Hot Potato Pattern

Critical operations use **hot potato structs** to enforce atomicity within a single transaction:

* **`OfflineAssemblies`** — returned when a network node goes offline. Every connected assembly must be offlined in the same transaction.
* **`UpdateEnergySources`** — returned when assemblies are connected. Each assembly's energy source must be updated in the same transaction.
* **`HandleOrphanedAssemblies`** — returned when a network node is unanchored. All connected assemblies must be handled.

**Reference:**

* [`network_node.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/network_node/network_node.move)
* [`fuel.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/primitives/fuel.move)
* [`energy.move`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/primitives/energy.move)


# Storage Unit

<figure><img src="https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2FrWAXtAMfdVvGcZnQDh5o%2FSSU.png?alt=media&amp;token=2950caf6-4371-4e34-ab9c-977a7c2819c4" alt=""><figcaption></figcaption></figure>

## Introduction

The Smart Storage Unit (SSU) is a **programmable, on-chain storage structure**. Players can store, withdraw, and manage items and the owner can define custom rules through extension contracts.

## Design

A storage unit has two types of inventories:

* **Primary inventory** — owned by the storage unit owner, accessed via the storage unit's `OwnerCap`. This is the main inventory for the owner's items.
* **Ephemeral inventories** — temporary, per-character inventories for players other than the owner. Used for trading, deposits, etc. Ephemeral inventories have a smaller capacity than the primary inventory to prevent abuse of the owner's storage unit.

Ephemeral inventories are created on-demand and stored as dynamic fields keyed by the character's `OwnerCap` ID. Each ephemeral inventory is accessed by the interacting character's own `OwnerCap`, like biometric authentication for temporary access. This avoids minting separate `OwnerCap`s just for ephemeral inventory access.

Items in inventories are on-chain representations of in-game resources.

## Interacting with a Storage Unit

### 1. Bridging Items (Game to Chain)

For any on-chain interaction, items must first be available on-chain. Players move items from in-game into the chain, and can move them back out to the game.

* **Game → Chain** — items are minted on-chain from in-game data.
* **Chain → Game** — on-chain items are burned and returned to the game.

### 2. Owner Deposit & Withdraw

Once items are on-chain, the owner can deposit and withdraw directly using their owner capability. The owner signs the transaction and proves access with their `OwnerCap`; They can perform these actions from a dApp.

### 3. Custom Logic via Extensions

The owner can deploy custom rules (a [Move contract](https://github.com/evefrontier/world-contracts)) and register it as the storage unit’s extension. After that, the extension controls how items are deposited or withdrawn — for example:

* **Vending machine** — players pay (e.g. in tokens) and receive an item from the unit.
* **Trade hub** — custom rules for listing, buying, and exchanging items.
* **Gated access** — only characters that meet certain conditions can deposit or withdraw.
* **Extension-to-owned** — the extension can deposit items into a specific player’s owned inventory (e.g. async delivery, guild hangars, rewards); the recipient does not need to be the transaction sender.

The extension uses the same pattern as the [Gate](/smart-assemblies/gate): the owner authorizes a contract type on the storage unit, and that contract’s logic runs when players interact. For API details and build steps, see the [Storage Unit Build Guide](/smart-assemblies/storage-unit/build).

## Next Steps

Build a custom storage unit extension: [Build Guide](/smart-assemblies/storage-unit/build)

**Reference:**

* [world-contracts](https://github.com/evefrontier/world-contracts) — EVE Frontier Sui Move contracts
* [storage\_unit.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/storage_unit.move) — core storage unit module
* [inventory.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/primitives/inventory.move) — inventory primitives
* [contracts/world](https://github.com/evefrontier/world-contracts/tree/main/contracts/world) — world contract package
* [world-contracts releases](https://github.com/evefrontier/world-contracts/releases)


# Build

Step-by-step instructions for building a custom Storage Unit extension. For concepts and design, see the [Storage Unit README](/smart-assemblies/storage-unit).

## Prerequisites

* Follow [environment-setup](/quickstart/environment-setup)
* For typed-witness and publish flow, see the [Gate build guide](/smart-assemblies/gate/build) and [builder-scaffold](https://github.com/evefrontier/builder-scaffold) — use them as the source of truth for setup and build steps.

## High-level steps

1. **Define a witness struct** (e.g. `public struct VendingAuth has drop {}`) in your Move package.
2. **Implement your logic** so it calls the world storage unit module ([`deposit_item`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/storage_unit.move#L254), [`withdraw_item`](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/storage_unit.move#L282)) to deposit or withdraw items using your `Auth` witness (e.g. after checking payment, tribe, or other rules).
3. **Publish** the package and **authorize** it on the storage unit: borrow the storage unit’s `OwnerCap` and call `storage_unit::authorize_extension<YourAuth>`.

After authorization, only your extension’s logic can perform deposits/withdrawals for non-owner characters; the world module checks the `Auth` type.

## Storage Unit API

Custom contracts use the **typed witness pattern**: define a witness struct (`Auth`) and register it on the storage unit.

**Authorize an extension:**

```move
public fun authorize_extension<Auth: drop>(
    storage_unit: &mut StorageUnit,
    owner_cap: &OwnerCap<StorageUnit>,
)
```

**Extension: deposit / withdraw** (main inventory; your extension calls these with your `Auth` witness):

```move
public fun deposit_item<Auth: drop>(
    storage_unit: &mut StorageUnit,
    character: &Character,
    item: Item,
    _: Auth,
    _: &mut TxContext,
)

public fun withdraw_item<Auth: drop>(
    storage_unit: &mut StorageUnit,
    character: &Character,
    _: Auth,
    type_id: u64,
    quantity: u32,
    ctx: &mut TxContext,
): Item
```

**Extension: deposit into a player’s owned inventory** Lets the extension push items into a specific character’s owned inventory; the recipient does not need to be the transaction sender. Use for async delivery, guild hangars, rewards:

```move
public fun deposit_to_owned<Auth: drop>(
    storage_unit: &mut StorageUnit,
    character: &Character,
    item: Item,
    _: Auth,
    _: &mut TxContext,
)
```

**Owner deposit / withdraw** (no extension; owner uses `OwnerCap`; sender must be the character).

```move
public fun deposit_by_owner<T: key>(
    storage_unit: &mut StorageUnit,
    item: Item,
    character: &Character,
    owner_cap: &OwnerCap<T>,
    ctx: &mut TxContext,
)

public fun withdraw_by_owner<T: key>(
    storage_unit: &mut StorageUnit,
    character: &Character,
    owner_cap: &OwnerCap<T>,
    type_id: u64,
    quantity: u32,
    ctx: &mut TxContext,
): Item
```

Example use cases: vending machine (pay then withdraw), trade hub, gated access. See [builder-scaffold storage unit extension](https://github.com/evefrontier/builder-scaffold/tree/main/move-contracts/storage_unit_extension) for a working example.

## Reference

* [world-contracts](https://github.com/evefrontier/world-contracts) — EVE Frontier Sui Move contracts
* [storage\_unit.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/storage_unit.move) — core storage unit module
* [inventory.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/primitives/inventory.move) — inventory primitives
* [builder-scaffold storage\_unit\_extension](https://github.com/evefrontier/builder-scaffold/tree/main/move-contracts/storage_unit_extension) — example extension
* [contracts/world](https://github.com/evefrontier/world-contracts/tree/main/contracts/world) — world contract package


# Turret

<figure><img src="https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2FjkklcTyuUKAuWtxjdoy7%2FTurret.png?alt=media&amp;token=92b6e722-18c2-468c-8253-63570e054efb" alt=""><figcaption></figcaption></figure>

## Introduction

A Turret is a **programmable structure** in space that projects offensive or defensive power over a fixed location. It is anchored on a base (grid), draws power from the network node, and automatically engages targets within its proximity. The game continuously evaluates nearby entities and asks *which* target to shoot and in what order—either using built-in default logic or your custom extension.

### Default Behavior

By default (no extension configured), the game uses the built-in logic in the [world turret contract](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/turret.move) to decide which targets to shoot. Default rules:

* **Exclude** — same tribe as owner and not an aggressor; or target **stopped** attacking
* **Prioritize** — **started attacking** adds a large weight increment; **entered** proximity adds a smaller weight if different tribe or aggressor
* The game shoots the target with the highest priority weight; ties use list order.

### Custom Behavior (Extension)

When the owner configures an extension, the game uses that extension’s logic instead of the default to decide which targets to shoot and in what order. The extension receives the same target information the game uses and returns a custom priority list.

1. The owner deploys custom targeting logic (a [Move contract](https://github.com/evefrontier/world-contracts/blob/main/contracts/extension_examples/sources/turret.move)) and registers it as the turret’s extension.
2. Whenever the game evaluates targets, it calls that logic and uses the returned list to choose what to shoot.

## Behaviours

The game calls your extension **whenever target behaviour changes** (e.g. a ship enters range, starts or stops attacking). It sends **exactly one behaviour change per candidate**; if both ENTERED and STARTED\_ATTACK apply, the game sends STARTED\_ATTACK (the higher-priority). Your extension returns which targets to shoot and at what priority weight; the game shoots the highest-weight target, and uses list order to break ties.

**Behaviour reasons:**

* **ENTERED** — target entered the turret’s proximity
* **STARTED\_ATTACK** — target started attacking the base (or someone on grid)
* **STOPPED\_ATTACK** — target stopped attacking

**Target candidate and return structs** (from the [world turret module](https://github.com/evefrontier/world-contracts/blob/3c35d6dce5f107bb8dd286e589330929ac401423/contracts/world/sources/assemblies/turret.move#L82)):

```move
public struct TargetCandidate has copy, drop, store {
    item_id: u64,
    type_id: u64,
    group_id: u64,
    character_id: u32,
    character_tribe: u32,
    hp_ratio: u64,
    shield_ratio: u64,
    armor_ratio: u64,
    is_aggressor: bool,
    priority_weight: u64,
    behaviour_change: BehaviourChangeReason,
}

public struct ReturnTargetPriorityList has copy, drop, store {
    target_item_id: u64,
    priority_weight: u64,
}
```

**TargetCandidate field meanings:**

| Field                                     | Meaning                                                                                        |
| ----------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `item_id`                                 | Unique id for the target (ship\_id / npc\_id).                                                 |
| `type_id`                                 | Target type (ship or NPC).                                                                     |
| `group_id`                                | Ship group; helps prioritize by ship class. 0 for NPCs.                                        |
| `character_id`                            | Pilot character id; 0 for NPCs.                                                                |
| `character_tribe`                         | Character tribe; 0 for NPCs.                                                                   |
| `hp_ratio`, `shield_ratio`, `armor_ratio` | Remaining structure/shield/armor as percentage (0–100).                                        |
| `is_aggressor`                            | True if this target is attacking anyone on grid (structure or player).                         |
| `priority_weight`                         | Default weight from game; your extension can override via the return list.                     |
| `behaviour_change`                        | The single reason sent for this candidate (e.g. STARTED\_ATTACK over ENTERED when both apply). |

**Return list:** the game shoots the target with the highest `priority_weight` in your returned list; if equal, it uses the order in the list.

### Ship groups and turret specialization

Turret types in the game are specialized against certain ship groups. You can use `group_id` to prefer targets your turret is good against:

| Ship class           | group\_id |
| -------------------- | --------- |
| Shuttle              | 31        |
| Corvette             | 237       |
| Frigate              | 25        |
| Destroyer            | 420       |
| Cruiser              | 26        |
| Combat Battlecruiser | 419       |

| Turret type | type\_id | Specialized against (group\_ids)         |
| ----------- | -------- | ---------------------------------------- |
| Autocannon  | 92402    | Shuttle (31), Corvette (237)             |
| Plasma      | 92403    | Frigate (25), Destroyer (420)            |
| Howitzer    | 92484    | Cruiser (26), Combat Battlecruiser (419) |

## Next Steps

Build and test a custom turret extension: [Build Guide](/smart-assemblies/turret/build)

**Reference:**

* [world-contracts](https://github.com/evefrontier/world-contracts) — EVE Frontier Sui Move contracts
* [turret.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/turret.move) — core turret module
* [extension\_examples/turret.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/extension_examples/sources/turret.move) — example custom turret extension
* [contracts/world](https://github.com/evefrontier/world-contracts/tree/main/contracts/world) — world contract package


# Build

Step-by-step instructions for building a custom Turret extension that controls target priority. For concepts and behaviour, see the [Turret README](/smart-assemblies/turret).

## Prerequisites

* Follow [environment-setup](/quickstart/environment-setup)
* For the same typed-witness and publish flow (authorize extension, game calls into your package), see the [Gate build guide](/smart-assemblies/gate/build) and [builder-scaffold](https://github.com/evefrontier/builder-scaffold), use them as the source of truth for setup and build steps.

## High-level steps

1. **Define a witness struct** (e.g. `public struct TurretAuth has drop {}`) in your Move package.
2. **Implement `get_target_priority_list`** with the same signature as the [world turret module](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/turret.move#L315):
   * Parameters: `turret`, `owner_character`, BCS target list, `OnlineReceipt`
   * Return: BCS `vector<ReturnTargetPriorityList>`
   * Use `turret::unpack_candidate_list` and `turret::new_return_target_priority_list`; apply your priority rules (e.g. aggressors only, tribe/group filters, behaviour-based weights).
3. **Publish** the package and **authorize** it on the turret: borrow the turret's `OwnerCap` from the character and call `turret::authorize_extension<YourAuth>`.

After authorization, the game **resolves the package ID** from the authorised type and calls `get_target_priority_list` in your extension package whenever it evaluates targets for that turret.

## Turret API

Custom contracts use the **typed witness pattern**: define a witness struct (`Auth`) and register it on the turret.

**Authorize an extension:**

```move
public fun authorize_extension<Auth: drop>(
    turret: &mut Turret,
    owner_cap: &OwnerCap<Turret>,
)
```

**Extension entry point (game calls this):**\
Expose a function with the same signature as the [world turret module](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/turret.move#L315).

* The game deserialises `target_candidate_list` as BCS `vector<TargetCandidate>` and expects BCS `vector<ReturnTargetPriorityList>` back.
* Use `turret::unpack_candidate_list` and `turret::new_return_target_priority_list` / `turret::unpack_return_priority_list` as helpers.

If the function’s parameter types or return type do not match exactly, the game may not be able to read the values and the extension will not work correctly.

```move
public fun get_target_priority_list(
    turret: &Turret,
    owner_character: &Character,
    target_candidate_list: vector<u8>,
    receipt: OnlineReceipt,
): vector<u8>
```

**Verify turret is online (world module):**

```move
public fun verify_online(turret: &Turret): OnlineReceipt
```

The game obtains an `OnlineReceipt` before calling `get_target_priority_list`. The receipt is a hot potato and must be consumed: extensions that call the world turret module should use `destroy_online_receipt<Auth>(receipt, auth)` before returning.

For ship group IDs and turret specialization by type, see the [Turret README](/smart-assemblies/turret#ship-groups-and-turret-specialization).

## Reference

* [world-contracts](https://github.com/evefrontier/world-contracts) — EVE Frontier Sui Move contracts
* [turret.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/turret.move) — core turret module (API, structs, helpers)
* [extension\_examples/turret.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/extension_examples/sources/turret.move) — example custom turret extension
* [contracts/world](https://github.com/evefrontier/world-contracts/tree/main/contracts/world) — world contract package


# Gate

<figure><img src="https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2FgIwSDY7OiCH7cZhQig33%2FGate.png?alt=media&amp;token=39ff7010-34db-49d6-8d26-031282a61590" alt=""><figcaption></figcaption></figure>

## Introduction

A Gate is a structure in space that enables travel between locations. Two gates link together to create a transport route. Gates are **programmable** — the owner can deploy custom extension contracts to control who can jump.

### Default Behavior

By default (no extension configured), **anyone can jump** through the gate without restrictions.

### Custom Behavior (Extension)

When the owner configures an extension, the gate switches to a **permit-based** model:

1. The owner deploys custom jump rules (a [Move contract](https://github.com/evefrontier/world-contracts)) and registers it as the gate’s extension.
2. Players must obtain a **jump permit** from that logic (e.g. by meeting a tribe check, paying a toll, or completing a bounty) before they can travel.
3. The game validates the permit and allows the jump. Permits are tied to a route and an expiry time; a permit for Gate A ↔ Gate B works in both directions.

For a full working example, see [builder-scaffold smart\_gate](https://github.com/evefrontier/builder-scaffold/tree/main/move-contracts/smart_gate).

## Linking Gates

Two gates must be **linked** before anyone can jump between them. Requirements:

* Both gates must be owned by the same character.
* Gates must be at least 20 km apart (verified with a server-signed distance proof).
* Linking requires an authorized transaction (the game validates this).

## Next Steps

Build and test a custom smart gate end-to-end: [Build Guide](/smart-assemblies/gate/build)

**Reference:**

* [world-contracts](https://github.com/evefrontier/world-contracts) — EVE Frontier Sui Move contracts
* [gate.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/gate.move) — core gate module
* [builder-scaffold smart\_gate](https://github.com/evefrontier/builder-scaffold/tree/main/move-contracts/smart_gate) — example gate extension
* [contracts/world](https://github.com/evefrontier/world-contracts/tree/main/contracts/world) — world contract package


# Build

End-to-end build guide: write a gate extension, publish it, and test the complete jump flow. For concepts and behaviour, see the [Gate README](/smart-assemblies/gate).

## Prerequisites

* Follow [environment-setup](/quickstart/environment-setup)
* Complete step-by-step instructions: [builder-scaffold builder-flow](https://github.com/evefrontier/builder-scaffold/blob/main/docs/builder-flow.md)

## Gate API

Custom contracts use the **typed witness pattern**: define a witness struct (`Auth`) and register it on the gate. The [world gate module](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/gate.move#L932) verifies the type at runtime.

**Authorize an extension:**

```move
public fun authorize_extension<Auth: drop>(
    gate: &mut Gate,
    owner_cap: &OwnerCap<Gate>,
)
```

**Issue a jump permit (called by your extension):**

```move
public fun issue_jump_permit<Auth: drop>(
    source_gate: &Gate,
    destination_gate: &Gate,
    character: &Character,
    _: Auth,
    expires_at_timestamp_ms: u64,
    ctx: &mut TxContext,
)
```

**Jump with a permit:**

```move
public fun jump_with_permit(
    source_gate: &Gate,
    destination_gate: &Gate,
    character: &Character,
    jump_permit: JumpPermit,
    admin_acl: &AdminACL,
    clock: &Clock,
    ctx: &mut TxContext,
)
```

**JumpPermit** (from the world module): contains `character_id`, `route_hash`, and `expires_at_timestamp_ms`. The `route_hash` is direction-agnostic — a permit for Gate A → Gate B also works for Gate B → Gate A.

***

Below is the high-level understanding:

## 1. Understand the example contract

The scaffold includes a working gate extension at `move-contracts/smart_gate/`. It has three modules:

**`config.move`** — shared configuration object and the witness type:

```move
public struct XAuth has drop {}

public struct ExtensionConfig has key { id: UID }

public struct AdminCap has key, store { id: UID }
```

`XAuth` is the witness type registered on the gate. `ExtensionConfig` is a shared object where extension modules store their rules as dynamic fields. `AdminCap` controls who can update those rules.

**`tribe_permit.move`** — issues jump permits only to characters in a configured tribe:

```move
public fun issue_jump_permit(
    extension_config: &ExtensionConfig,
    source_gate: &Gate,
    destination_gate: &Gate,
    character: &Character,
    clock: &Clock,
    ctx: &mut TxContext,
) {
    // Check character's tribe matches the configured tribe
    assert!(character.tribe() == tribe_cfg.tribe, ENotStarterTribe);

    // Issue a time-limited permit via the world gate module
    gate::issue_jump_permit<XAuth>(
        source_gate, destination_gate, character,
        config::x_auth(),
        expires_at_timestamp_ms,
        ctx,
    );
}
```

The key pattern: your module calls `gate::issue_jump_permit<XAuth>` with your witness type. The gate verifies `XAuth` matches the type registered via `authorize_extension`.

**`corpse_gate_bounty.move`** — a more advanced example that combines a storage unit interaction with a gate permit. Players deposit a bounty item and receive a jump permit in return.

## 2. Build and publish

```bash
cd move-contracts/smart_gate
sui client publish --build-env testnet
```

> For local network, provide the world package path:
>
> ```bash
> sui client publish --build-env testnet --pubfile-path ../../../world-contracts/contracts/world/Pub.localnet.toml
> ```

From the publish output, note the **package ID** and the **ExtensionConfig object ID**. Set both in your `.env`:

```
BUILDER_PACKAGE_ID=0x...
EXTENSION_CONFIG_ID=0x...
```

## 3. Configure extension rules

Set the tribe and expiry parameters on the shared `ExtensionConfig`:

```bash
pnpm configure-rules
```

This calls `tribe_permit::set_tribe_config` with a tribe ID and permit expiry duration. You can modify `ts-scripts/smart_gate/configure-rules.ts` to change the values.

## 4. Authorize the extension on both gates

Register your `XAuth` type on each gate so it switches from default (open) to permit-based:

```bash
pnpm authorise-gate
```

Under the hood this builds a transaction that:

1. Borrows the gate's `OwnerCap` from the character
2. Calls `gate::authorize_extension<XAuth>` on the gate
3. Returns the `OwnerCap` to the character

After this, the default `jump` function is disabled — players must use `jump_with_permit`.

## 5. Issue a jump permit

Issue a permit to a player character:

```bash
pnpm issue-tribe-jump-permit
```

This calls `tribe_permit::issue_jump_permit`, which checks the character's tribe and mints a `JumpPermit` object owned by the character.

Players can collect/buy permit from a builder dapp.

## 6. Jump with permit

The player uses their permit to jump through the gate:

```bash
pnpm jump-with-permit
```

A successful jump emits a `JumpEvent` and consumes the permit. Ideally this flow happens in the game.

## Writing your own extension

To create a gate extension from scratch:

1. Define a **witness struct** (e.g., `public struct MyAuth has drop {}`)
2. Write a function that enforces your rules and calls `gate::issue_jump_permit<MyAuth>`
3. Publish the contract and authorize it on your gate with `gate::authorize_extension<MyAuth>`

Here's a minimal toll gate that requires payment before issuing a permit:

```move
module custom::toll_gate;

public struct TollGateAuth has drop {}

public fun buy_pass<T>(
    source_gate: &Gate,
    destination_gate: &Gate,
    character: &Character,
    payment: Coin<T>,
    clock: &Clock,
    ctx: &mut TxContext,
) {
    // Custom logic: verify payment amount, token type, allowlist, etc.
    // ...

    gate::issue_jump_permit(
        source_gate,
        destination_gate,
        character,
        TollGateAuth {},
        clock.timestamp_ms() + 3600_000, // 1 hour expiry
        ctx,
    );
}
```

Other ideas:

* **Allowlist** — check a stored list of approved characters
* **Bounty gate** — require depositing an item to earn passage (see `corpse_gate_bounty.move` in the scaffold)

See the [Gate API](#gate-api) above for the full function signatures.

## Reference

* [world-contracts](https://github.com/evefrontier/world-contracts) — EVE Frontier Sui Move contracts
* [gate.move](https://github.com/evefrontier/world-contracts/blob/main/contracts/world/sources/assemblies/gate.move) — core gate module
* [builder-scaffold smart\_gate](https://github.com/evefrontier/builder-scaffold/tree/main/move-contracts/smart_gate) — example gate extension
* [contracts/world](https://github.com/evefrontier/world-contracts/tree/main/contracts/world) — world contract package


# dApps Quick Start


# Connecting from an External Browser

The dApp uses the [Sui Wallet Standard](https://docs.sui.io/standards/wallet-standard) to enable discoverability with Sui wallets. The only wallet currently supported with the EVE Frontier dApps is [Eve Vault](https://github.com/evefrontier/builder-documentation/blob/main/EveVault/README.md).

Open a browser and connect to [https://dapps.evefrontier.com/?tenant=stillness](https://test.dapps.evefrontier.com/?tenant=stillness) (Stillness) or <https://uat.dapps.evefrontier.com/?tenant=utopia> (Utopia).

![Eve dApp Connect View](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-ed626ad2530520b7e6e80c5ecf77ffadb6ccacd9%2Fconnect-evevault.png?alt=media)

Click on Connect with EVE Vault. This will prompt your EVE Vault extension to connect. If EVE Vault is currently locked, you will see a pop-up with a prompt to enter your PIN.

![Eve Vault Unlock Popup](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-a847b14751fe700f38e9c3120a4e781044501e5e%2Fdapp-evevault-unlock.png?alt=media)

### Connected tenant and itemId

The selected assembly is determined by using query parameters from the URL. `?tenant` and `?itemId` must be defined.

ItemId is the in-game game item ID, e.g. `1000000012345`.

Your completed URL should look something like `https://dapps.evefrontier.com/?tenant=stillness&itemId=12345...`


# Connecting In-Game

Every Assembly has a Base dApp. This base dApp lets you configure properties of smart assemblies that you own or interact with, and view aspects of others' Assemblies.

1. In the EVE Frontier game client, navigate to a built Assembly, such as a Network Node.
2. Press **F** to interact. The in-game browser opens, showing the configuration dApp page for Smart Assembly you selected.

![Network Node](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-9dc6d15e714e99fc238394904815773c3339edea%2Fnetwork-node.png?alt=media)

The dApp displays primitive, view-only information for every Smart Assembly. This includes publicly viewable information about the owner and state.

### dApp Actions

The owner of an Assembly can use the Smart Assembly Base dApp to set a custom name, description, and URL. These changes are on-chain actions, but are submitted as [sponsored transactions](https://sui-docs.evefrontier.com/functions/useSponsoredTransaction.html) so they have no GAS cost to the player.

To change a unit's name, description or URL:

1. Click the "Edit unit" button.
2. Make changes accordingly under dApp URL, Unit name or Unit description.
3. Click **Save**. This submits the change to a sponsored transaction endpoint, retrieving the transaction bytes, signing them, and returning them for submission to the chain.

**Note**: You must be a unit's owner to set its URL, name or description.

![Edit name and description](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-02d6f359f5f0f369b48dbe0e56853ac007cbdade%2Fdapp-update-metadata.png?alt=media)

### Custom dApps

Some units may have an associated external URL for website built and maintained by the unit's owner. These associated URLs allow builders to create UIs from which they can configure smart hooks easily. Only unit owners may add or edit URLs.

Users can view these custom dApps in-game, but should be aware that these dApps are entirely independent of the CCP safe zone.

Any functions called by external dApps are sent as [gas-consuming transactions](https://sdk.mystenlabs.com/dapp-kit/react/hooks/use-dapp-kit#transaction-execution). Users must have SUI in their wallet in order to successfully call any builder functions. A Sui faucet is [available here](https://faucet.sui.io/) if required.

![Custom dapp](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-5d77ade4885bd1e068d0c53c8ba1ecc76327b055%2Fdapp-customdapp.png?alt=media)

### Modules

Assemblies are tagged as a Smart Storage Unit, Smart Turret or Smart Gate. These tags are detected in the Smart Assembly dApp, and their respective installed modules are then rendered.

#### Inventory Module

An inventory module grants storage capacity and lets players store inventory items in a **Storage Unit**.

To interact with on-chain inventory items, users must link them to the chain through depositing them in the *Ephemeral Inventory* holding area.

Once inventory items are deposited to the *Ephemeral Inventory*, they are accessible for on-chain transactions and other interactions. Items in a player's inventory are viewable in the Inventory Module UI of the dApp.

![Smart Storage Unit Inventory](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-4d18d6dfbc484be83245501906b0965c91df9585%2Fdapp-ssu-inventory.png?alt=media)


# Verifying a Sign-In Server-Side

Verifying the signature is how your server establishes a **trusted player identity**: proof that the user actually controls the wallet they claim, rather than just supplying a name or address. You need this anywhere your server attributes something to a player — for example, bookmarks tied to a player, chat messages shown as coming from a particular character, or leaderboards built from parsed logs. Without this check, a client can claim to be any wallet.

Verifying a sign-in works by having the user sign a personal message with EVE Vault (via the wallet's `signPersonalMessage`) and verifying that signature on your server. This page shows the flow end to end: issue a nonce, have the wallet sign a message built from it, and verify the personal message signature server-side.

## Prerequisites

* [`@mysten/sui`](https://sdk.mystenlabs.com/typescript) installed on your server.
* A `SuiClient` for the network your users are on.
* Somewhere to store a pending nonce per session (cache, DB, signed cookie).

EVE Vault addresses are [zkLogin](https://docs.sui.io/concepts/cryptography/zklogin) addresses, so the verifier needs a `client` to check the zkLogin proof against the current epoch. An offline verify will not work.

## Steps

1. **Issue a nonce.** Generate a random, single-use, short-lived nonce. Store it against the session and send it to the client.
2. **Have the client sign it as a personal message.** The dApp builds the agreed message from the nonce and signs it with the wallet's `signPersonalMessage`, then posts back the resulting `signature` and the `address` it claims to be.
3. **Rebuild the message from the nonce you stored — do not use any message bytes from the request.** `signPersonalMessage` returns `{ bytes, signature }`; verifying against those returned `bytes` lets any message the wallet has ever signed be replayed with a fresh nonce.
4. **Verify** the signature against the rebuilt message, passing the claimed `address` so the check also confirms who signed it.
5. **Consume the nonce** once verification succeeds, so the same challenge cannot be used twice.

## Full example

```ts
import { verifyPersonalMessageSignature } from '@mysten/sui/verify'
import { SuiClient, getFullnodeUrl } from '@mysten/sui/client'

const client = new SuiClient({ url: getFullnodeUrl('mainnet') })

// Agree this format with your client and build it the SAME way on both sides.
function buildLoginMessage(nonce: string): Uint8Array {
  return new TextEncoder().encode(`Sign in to Example dApp\nnonce: ${nonce}`)
}

async function verifySignIn(req: { signature: string; address: string }) {
  // The nonce we issued and stored for this session — NOT anything from req.
  const storedNonce = await loadNonceForSession()
  if (!storedNonce) throw new Error('No pending sign-in')

  // Rebuild the message ourselves. We never touch req.bytes.
  const message = buildLoginMessage(storedNonce)

  try {
    // Throws if the signature is invalid for `message`, or if it wasn't
    // produced by `address`. `client` is required to check the zkLogin proof.
    await verifyPersonalMessageSignature(message, req.signature, {
      client,
      address: req.address,
    })
  } catch {
    throw new Error('Invalid sign-in')
  }

  await consumeNonce(storedNonce) // single-use: prevent replay
  return { address: req.address } // authenticated
}
```

The `address` option makes the verifier reject a signature that is valid but was produced by a different wallet, so a signature over your challenge from one account cannot be claimed by another.

## Summary

* The nonce is random, single-use, and expires.
* The message is rebuilt on the server from the stored nonce.
* Client-supplied message bytes are never passed to the verifier.
* A `client` is passed so the zkLogin proof is verified against the current epoch.
* The signer is bound to the claimed address (via the `address` option or an explicit compare).
* The nonce is consumed once verification succeeds.


# Wallets & Identity

## Introduction

Getting started in EVE Frontier begins with securing your wallet and establishing your in-game identity. With the move to Sui, the foundation for your presence, assets, and interactions—inside the game and across third-party dApps—is built around the EVE Vault wallet and browser extension.

***

## EVE Vault: Your Wallet and Identity

**EVE Vault** is the official wallet and inventory manager for all EVE Frontier experiences on Sui. It's available as a web app and as a Chrome browser extension. EVE Vault does more than hold your tokens and assets—it serves as your digital passport, letting you connect securely to both the main game and the wider EVE Frontier dApp ecosystem.

* **Wallet:** Secure storage for your Sui-based assets—including currencies, NFTs, and game items.
* **Identity:** The EVE Vault extension functions as your authentication hub for dApps, making it easy (and private) to prove who you are without exposing sensitive keys or details.
* **Single Sign-On:** Once the extension is installed and authenticated, connecting to dApps or new services is as simple as approving the prompt—no extra accounts or passwords needed.

***

## Seamless Onboarding with zkLogin

EVE Vault leverages **zkLogin**—a zero-knowledge login protocol native to Sui—to give you effortless, privacy-preserving authentication:

* **No Seed Phrases Needed:** Create your EVE Vault wallet using your EVE Frontier SSO account.
* **Zero-Knowledge Proof:** zkLogin lets you prove ownership of your account to dApps and the EVE Vault, without the OAuth provider or anyone else learning your blockchain address or secrets.
* **Fast & Private:** You get a secure Sui wallet, instantly ready for game and dApp use, with complete privacy and full control.

***

## Your Identity Inside and Outside the Client

* **In-Game:** Log in with EVE Vault to link your character, assets, and progression on-chain. Tokens, and other on chain property are visible and accessible through the wallet.
* **Out-of-Game dApps:** The EVE Vault extension allows you to authenticate and interact with external EVE Frontier dApps, marketplaces, community tools, and integrations. Each connection is permissioned, using your wallet as the anchor of your identity, never re-using or exposing your login elsewhere.

***

## High-Level Flow

1. **Install EVE Vault extension** from [EVE Vault Releases](https://github.com/evefrontier/evevault/releases)
2. **Create your wallet** with a single sign on login — no secret phrases or manual backups.
3. **Authorize dApps** and the EVE Frontier game client to interact with your wallet and identity as needed.
4. **Transact, play, and participate** using EVE Vault as your only identity, both in and out of game.

***

## Summary

* **EVE Vault** is your one wallet and identity for EVE Frontier on Sui.
* **zkLogin** provides secure, private onboarding—no seed phrase required.
* The **browser extension** acts as your decentralized ID for all dApps and integrations.
* Your assets, character, and reputation travel with you, wherever you explore in the EVE Frontier ecosystem.

***


# Introduction to EVE Vault

**EVE Vault** is the official wallet and inventory manager for EVE Frontier on Sui, available as a [web app and extensions for Chrome and Firefox](https://github.com/evefrontier/evevault). For what it is, how to install it, and how wallet and identity work (including zkLogin), see [Wallets & Identity](/eve-vault/wallets-and-identity).

**Features:**

* Sui Wallet Standard compliance for fast, safe, cross-dApp integration.
* FusionAuth OAuth support for linking your EVE Frontier account and in-game character to your wallet.
* Permissioned dApp access: connect to player-made dApps and tools while protecting your identity — even if your clone is destroyed.

***

## The Economy and Currencies

The EVE Frontier economy spans an off-chain, in-game currency and an on-chain Sui token, connected so value can move between in-game and on-chain contexts.

* **LUX**: The off-chain, in-game currency used for most in-game transactions, purchases, trades, and services. LUX is tracked in-game and is not a Sui coin.
* **EVE Token**: The native on-chain token of EVE Frontier (a `Coin<EVEToken>` on Sui). It powers the open, composable EVE Frontier game and the builder-driven economy.

LUX and EVE Token are linked by an exchange service that converts between the off-chain LUX currency and the on-chain EVE Token in both directions, letting value move between the game and the Sui blockchain.

EVE Vault enables:

* Trading and storage of EVE Token and other Sui-based assets.
* Secure management and transfer of player-generated and alliance-specific currencies.
* Integration with markets, trading systems, and configurable infrastructure rewards.

**Core advantages:**

* Trade assets, currencies, services, and reputation with robust permissions and privacy.
* Create, exchange, and use custom currencies—including alliance tokens and specialized mission rewards.
* Seamless experience for builders, traders, and players operating across the decentralized EVE Frontier world.

***


# Browser Extension

Install the Eve Vault browser extension to use the vault from Chrome.

## Download

Get the latest extension from the release:

[**Eve Vault Extension**](https://github.com/evefrontier/evevault/releases)

Download the zip asset from the release page.

## Installation (Chrome)

1. **Unzip** the downloaded file to a folder on your computer (e.g. `evevault-extension`).
2. Open Chrome and go to **chrome://extensions/**.
3. Turn on **Developer mode** (toggle in the top-right).
4. Click **Load unpacked**.
5. Select the folder where you unzipped the extension.
6. The Eve Vault extension will appear in your extensions list and in the Chrome toolbar.

## Sign in

After installing, open the extension and sign in with your credentials for the Utopia server. Follow these steps:

### 1. Create a 6-digit PIN

When you open the extension for the first time, you’ll be asked to create a **6-digit PIN**. This PIN is used to unlock the vault on this device.

![Create PIN](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-eace4579cea165ad6cf4bac37cf3dff1ac8f5636%2Fcreate-pin.png?alt=media)

### 2. Open the sign-in flow

On the sign-in screen, select which server you are signing into (Stillness for most, Utopia for hackathon). Click **Login**. This opens the Eve Frontier sign-in flow.

![Sign in](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-479cefce12ca36c5e243b20271763b3d40116135%2Fsign-in.png?alt=media)

### 3. Sign in with your Frontier credentials

Use your **Frontier credentials** (for your selected server) to sign in. Enter your email and password when prompted.

### 4. Use the dashboard

After signing in successfully, you’ll see the **Eve Vault dashboard**, where you can manage your wallet and transactions.

![Dashboard](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-aa44971a7d69c08830d0874a00c44aaf202c3817%2Fdashboard.png?alt=media)

***

For source code and more releases, see the [evevault repository](https://github.com/evefrontier/evevault).


# Using Your Wallet

Once you've [installed EVE Vault and signed in](/eve-vault/browser-extension), the dashboard is where you view balances, send tokens, track custom assets, review activity, and lock the wallet. This page covers those everyday actions.

> Before you can sign any transaction, EVE Vault requires you to register a personal access key as an address alias. If your first **transfer** opens a recovery-key setup screen, see [Address Aliases & Recovery](/eve-vault/address-aliases-and-recovery).

***

## The dashboard and token list

![Vault dashboard](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-aa44971a7d69c08830d0874a00c44aaf202c3817%2Fdashboard.png?alt=media)

The dashboard lists the tokens tracked for the current network in three columns: **TOKEN**, **ADDRESS**, and **BALANCE**.

* Your **wallet address** appears at the top with a **Copy** button — click it to copy the full address.
* Each row shows the token name, a shortened coin type (e.g. `0x1234•••ABCD`) with a copy icon, and your balance.
* If no tokens are tracked yet, you'll see **No tokens added yet**.

### Refreshing balances

The **BALANCE** column header doubles as a **Refresh balances** button. Clicking it updates your balances and recent activity. If a refresh fails, you'll see **Failed to refresh balances**.

### Selecting a token

Click a token row to select it. When a row is selected, a **Transfer** button appears on that row, and the **Remove token** action at the bottom becomes available. Selecting the same row again deselects it. Use the copy icon on a row to copy that token's full coin type.

***

## Send tokens

![Send token screen](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-54938a296bfb6cd27de18a187257e3502df36dde%2Fsend-token.png?alt=media)

Start a transfer with the **Transfer** button on a selected token row (or the send action for that token). On the send screen:

1. Enter the **Recipient Address** (a Sui address).
2. Enter the **Amount**. The field accepts numbers only. Your available balance is shown on the right as **Wallet balance: {balance} {symbol}**.
3. Review the fee. Every transfer pays a network fee (gas) in **SUI**, and an estimate appears as **Estimated fee: \~{x} SUI** once the form is valid.
4. Click **transfer**. To cancel, click **cancel**.

### Validation and warnings

EVE Vault checks the form as you type:

* **Invalid Sui address format** — the recipient isn't a valid Sui address.
* **Invalid amount or exceeds balance** — the amount is empty, zero, or larger than your balance.
* System notices also appear for **No network selected**, **Not authenticated**, **Wallet not ready** (locked), **Insufficient balance**, and **No SUI for gas (required for transaction fees)**.

Because gas is paid in SUI, you need a SUI balance even when sending a different token. If your SUI balance is zero on a test network, a **Faucet test SUI** shortcut appears so you can top up (see [Developer & Localnet](/eve-vault/developer-and-localnet)).

The **transfer** button stays disabled until the network is ready, you're authenticated and unlocked, you have both a token balance and SUI for gas, and the recipient and amount are valid.

### First transfer: recovery-key setup

The **first time** you send (or sign) a transaction, EVE Vault requires a personal access key to be registered as an address alias. Clicking **transfer** opens the recovery-key setup flow; once you complete it, the transfer proceeds. This is a one-time step per account — see [Address Aliases & Recovery](/eve-vault/address-aliases-and-recovery).

### After sending

On success you'll see a confirmation screen showing the **Amount sent**, **Recipient address**, and **Transaction** digest, with a **View on Suiscan** button to open the transaction in the block explorer, and a **close** button to return to the dashboard.

***

## Add a custom token

![Add custom token](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-9d36e68e6dc426673de8aece8ee60316776cc347%2Fadd-token.png?alt=media)

To track a token that isn't listed by default, use **Add token** at the bottom of the dashboard. On the **Add custom token** screen:

1. Enter the full coin type in the **Token identifier** field, in the form `0x...::module::COIN`.
2. Click **Add Token** (or **Cancel** to go back).

EVE Vault validates the format and rejects malformed identifiers (**Invalid coin type format. Expected: 0x...::module::COIN or 0x2::Coin<...>**). Adding a token only tracks and displays its balance locally — it performs no on-chain action.

> **Only add tokens you trust.** Invalid or malicious tokens can cause problems. Double-check the coin type before adding it.

To stop tracking a token, select its row on the dashboard and click **Remove token**.

***

## Transaction history

![Transaction history](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-1731389e0be24d4124273a20681eac868b543666%2Ftransactions.png?alt=media)

Open **Transaction History** from the account menu to see past activity for your address, in columns **Date**, **Sender / Recipient**, and **Amount**.

* Sent transactions show a minus sign (`−`) before the amount; received transactions don't.
* The counterparty is shown as a shortened address, or **System** when there isn't one.
* Click a row to expand it and see the per-token **balance changes**, the full **Transaction** digest, and a **View on Suiscan** button. Only one row is expanded at a time.
* History loads 50 transactions at a time; click **Load more** to fetch older entries.

Status messages appear when relevant: **Loading transactions...**, **No transactions found**, or **Failed to load transactions**.

***

## Locking, auto-lock, and reset

EVE Vault is protected by a **6-digit PIN** that you create the first time you open it and enter to unlock it afterward.

* **Lock manually** — choose **Lock Wallet** from the account menu.
* **Auto-lock** — the vault locks itself automatically after about 10 minutes. You'll need to re-enter your PIN to continue. (In dev mode, an **Unlock expires in {m:ss}** countdown is shown; see [Developer & Localnet](/eve-vault/developer-and-localnet).)

### Forgot PIN / reset on this device

If you've forgotten your PIN, use **Forgot PIN** on the lock screen. This opens a confirmation:

> **Reset EVE Vault on this device?** This will reset your PIN and remove all EVE Vault data from this device. Your wallet will be available after you recreate your PIN and sign back in.

Choosing **Reset** wipes device-local data only — your PIN, cached keys and sessions, stored logins, and any custom tokens you added. It does **not** destroy your wallet: because your address is derived from your zkLogin identity, you recover the same wallet by creating a new PIN and signing back in.

> **Note:** Custom tokens you added are cleared by a reset and will need to be re-added.

***

For install and sign-in steps, see [Browser Extension](/eve-vault/browser-extension). For the concepts behind the wallet and zkLogin, see [Wallets & Identity](/eve-vault/wallets-and-identity).


# Address Aliases & Recovery

EVE Vault uses **address aliases** to provide account recovery. Before you can send tokens or sign transactions, you must register a **personal access key** as an address alias. This page explains what an alias is, why the personal access key matters, and how to set it up and manage it.

> **Read this carefully.** An address alias is a **co-owner** of your account with full, unilateral control. Anyone who controls an alias can move all of your coins and assets. Treat alias changes with extreme caution.

***

## What an address alias is

An address alias is an on-chain authorization attached to your Sui address. It designates **which keys are allowed to sign transactions for that address**. Each alias is effectively a **co-owner**: it has complete, unilateral control and can take all of the account's coins, balances, and other resources.

* Aliasing must be **enabled** once (this creates an on-chain aliases object owned by your account).
* Your account can have up to **8** aliases.
* Your own address always appears in the list, labeled **(This address)**, and cannot be removed from EVE Vault.

Aliases are not an address book: aliases are authorization keys, not saved recipients.

***

## Why the personal access key exists

EVE Vault accounts are **zkLogin** accounts — the signing key is derived from your EVE Frontier login on the device you're using. That's what makes onboarding seedless and private, but it also means that losing your device or login could mean losing access to the account.

A **personal access key** solves this. You generate a standalone keypair (a 24-word recovery phrase and a private key), register its address as an alias (co-owner) of your account, and store the secret **offline**. If you ever lose access to your zkLogin device or login, that personal access key can still sign for the account.

Because a personal access alias hands full control to a separate key, EVE Vault requires one before it will let you transact. This guarantees every account has a recovery path.

***

## Set up your personal access key

The first time you try to send tokens or sign a transaction, EVE Vault opens the personal access key setup flow if you haven't completed it yet. You can also reach it from the alias management screen.

![Personal access key setup](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-960f92b779a90d84eb8bc214589f1435415dbc5f%2Fkey-setup.png?alt=media)

1. On **Set up your personal access key**, click **Generate personal access key**. EVE Vault creates a fresh keypair **on your device only** — it is never sent to or stored by the app.
2. On the reveal screen, save all three values shown, each with a **Copy** button:
   * **Recovery phrase** (24 words)
   * **Private key**
   * **Alias address**
3. Store them somewhere safe and private (offline). **They are shown only once.**
4. Check **I've saved my recovery phrase and private key somewhere safe**. The **Register personal access alias** button stays disabled until you do.
5. Click **Register personal access alias**, then confirm in the **Register personal access alias?** dialog.

EVE Vault then registers the personal access address as an alias. If aliasing wasn't enabled yet, this runs two on-chain transactions in sequence (enable, then add), so you may approve more than one. On success you'll see **Recovery alias registered**; click **Continue** to return to what you were doing — your original send or signing action can now proceed.

> **Anyone with this recovery phrase or private key has complete control of your account.** Never share it, never enter it into untrusted software, and keep it offline.

### How the requirement is enforced

The check happens when you send or sign. EVE Vault verifies that your account has at least one alias **other than your own address**. Simply enabling aliasing, or having only your own address listed, does **not** satisfy the requirement — you need a registered recovery (or other non-self) alias.

***

## Manage address aliases

Open **Manage Address Aliases** from the account menu to review and change who can sign for your account.

* If aliasing isn't set up yet, click **Enable address aliasing** first.
* The list shows **Current aliases (N/8)**. Your own address is marked **(This address)** and can't be removed; every other alias has a **Remove** button.
* To add one, enter a Sui address in the **0x… address alias** field and click **Add alias**. **Add alias** is disabled once you reach the maximum of 8.

Validation prevents common mistakes — for example an empty or malformed address, an address that's already an alias, adding beyond the limit of 8, or removing an address that isn't currently an alias. Every add or remove is an on-chain transaction you'll approve, after which the list refreshes.

> Adding an alias grants that address **full control** of your account. Only add addresses you fully trust and control.

***

For what happens when you approve these transactions, see [Signing Transactions](/eve-vault/signing-transactions). For everyday actions like sending tokens, see [Using Your Wallet](/eve-vault/using-your-wallet).


# Signing Transactions

When a dApp asks EVE Vault to sign a message or transaction, the wallet opens a dedicated approval popup. The popup shows who is asking, what they're asking for, a preview of the outcome, and any risks — so you can review before approving. This page explains what you'll see and the different request types.

***

## The signing popup

![Signing popup](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-2fffb1476ff16b773a622d0ab14e7ad714280eb0%2Fsign-request.png?alt=media)

Every request must pass an auth gate first:

* If the vault is **locked**, you'll be asked for your PIN.
* If you're **not logged in**, you'll see **You need to log in before signing** with **Log In** and **Cancel** buttons.

Once unlocked and authenticated, the request appears with a context panel showing up to four fields:

* **SITE** — the dApp's origin (hover for the full URL).
* **ACCOUNT** — the account that would sign.
* **REQUEST** — the kind of request (see below).
* **CONNECTED** — when the dApp connected, if known.

At the bottom are **Approve** and **Reject**, plus a network selector showing the target chain. When a request carries risk (see below), a checkbox appears that you must tick before **Approve** is enabled.

***

## Outcome, Warnings, and Payload tabs

Transaction requests include a compact set of tabs so you can inspect exactly what you're signing before approving.

### Outcome (simulation)

![Transaction simulation](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-f5cf55f2bdef5e4d27dde01fcdcb9c4fe1c759ed%2Ftx-simulation.png?alt=media)

EVE Vault simulates the transaction (a dry run) and shows the expected result:

* **Expected to succeed** or **Expected to fail** (with the failure reason).
* The transaction **Digest** (when available) and the **Gas fee** in SUI.
* **Balance changes** — signed amounts per token (debits in red, credits in green), or **None**.
* **Changed objects** — each object's kind, id, type, and ownership change (your address is shown as **you**).
* **Events** emitted by the transaction.

If the simulation can't run, you'll see **Could not simulate this transaction. Approve with caution.** A simulated on-chain failure also forces you to acknowledge before approving.

### Warnings (risk review)

EVE Vault statically inspects the transaction and flags notable actions. Higher-severity findings (shown in red) require you to tick the acknowledgement checkbox before approving:

| Severity | Flag                              | What it means                                                                                              |
| -------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| High     | **Publishes Move code**           | Can add new on-chain package code from your account.                                                       |
| High     | **Upgrades Move code**            | Can change package behavior controlled by your account.                                                    |
| High     | **Transfers objects**             | Can move owned objects or tokens out of your account.                                                      |
| High     | **Modifies address aliases**      | Can add or remove [address aliases](/eve-vault/address-aliases-and-recovery) (co-owners) for your account. |
| High     | **Unverified transaction format** | The payload couldn't be decoded for review — a fail-safe against blind-signing.                            |
| Warning  | **Calls Move code**               | Review the package, module, and function before approving.                                                 |
| Warning  | **Builds object vectors**         | Can pass multiple objects into a Move call.                                                                |
| Warning  | **Uses shared objects**           | Shared-object calls can change state used by other accounts.                                               |

### Payload

The **Payload** tab shows the decoded transaction as JSON, so you can inspect the raw contents.

***

## Request types

### Personal message

**Request: Personal message.** A signature over a plain message (no transaction, no gas). The popup shows the decoded message text in a **Message** box (or a note that it's binary). There's no simulation or risk review because nothing is executed. Approve to sign, or reject.

### Sign transaction

**Request: Sign Transaction.** EVE Vault signs the transaction and returns the signature to the dApp **without** submitting it. Includes the Outcome / Warnings / Payload tabs. The [recovery-alias requirement](/eve-vault/address-aliases-and-recovery) applies before signing.

### Sign & execute transaction

**Request: Sign and Execute Transaction.** Same review UI as above, but after signing EVE Vault also **submits** the transaction to the network and returns the result (digest and effects). Your balances and activity refresh afterward.

### Sponsored transaction

**Request: Sponsored transaction.** Someone else pays the gas. The popup shows a **Sponsored request** box with details such as the action, type, and dApp name/URL when provided, and the Outcome and Payload tabs let you review exactly what you're signing. Sponsored transactions aren't available on localnet, and you must be signed in with an unlocked wallet.

***

For the aliases referenced above, see [Address Aliases & Recovery](/eve-vault/address-aliases-and-recovery). For everyday wallet actions, see [Using Your Wallet](/eve-vault/using-your-wallet).


# Developer & Localnet

EVE Vault includes a **dev mode** with tools aimed at builders: pointing the wallet at a local Sui network, loading a raw private key for local testing, and a few utilities for signing test transactions and rotating keys. These are off by default and intended for development only.

***

## Enabling dev mode

Dev mode is a toggle. Turning it on reveals the developer menu items and the localnet settings route.

* **Signed out** — a small floating eye button in the bottom-right corner of the sign-in screen toggles dev mode.
* **Signed in** — open the account menu and flip the **Dev mode** switch at the top.

When dev mode is off, the developer items are hidden and the localnet settings page redirects away.

***

## Localnet settings

![Localnet settings](https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2Fgit-blob-7a63afbdb754652c53bf84cf3e94bd32ddc797ed%2Flocalnet-settings.png?alt=media)

With dev mode on, open **Localnet Settings** from the account menu. (Selecting the localnet network without a keypair loaded also brings you here.) The page has three sections.

### RPC URL

Enter your local node's URL — the default is `http://127.0.0.1:9000` — and click **Save URL**. EVE Vault validates the URL and checks connectivity by calling the node (`suix_getLatestSuiSystemState`). The status line shows **Connecting…**, then **Connected** or **Connection failed**.

### Private key

For local testing you can load a raw private key directly:

1. Make sure the vault is unlocked (a key can only be loaded into an unlocked vault).
2. Paste a key in the **Private key** field. It must be in `suiprivkey1...` format.
3. Click **Load Key**. On success you'll see **Key loaded**.

EVE Vault derives a keypair from the key and shows the resulting address in the **SUI ADDRESS** section.

> **Localnet development only.** Never load a private key that controls real assets. This feature exists for testing against a local network — do not use it with mainnet or testnet keys.

### SUI address

This shows the address derived from the loaded key, or **No keypair loaded** if none is set.

***

## Developer utilities

With dev mode on, the account menu gains a few extra actions:

* **Sign and submit test** — builds and submits an empty transaction from your address, to confirm signing works end-to-end. You'll get a **Transaction submitted!** toast and a link to the explorer.
* **Rotate eph key** — rotates the ephemeral signing key (useful when debugging zkLogin session state); details are logged to the console.
* **Faucet test SUI** — opens the current network's faucet in a new tab so you can fund your address with test SUI. Available on networks that have a faucet (e.g. devnet/testnet), not on mainnet.
* **Unlock expires in {m:ss}** — a read-only countdown to the vault's auto-lock.
* **v{version}** — the installed app version.

***

For network switching and everyday actions, see [Using Your Wallet](/eve-vault/using-your-wallet). For install and sign-in, see [Browser Extension](/eve-vault/browser-extension).


# Wallet Game Setup

//TODO


# Interfacing with the EVE Frontier World

## Overview

You interact with the EVE Frontier world in two ways:

* **Write path** — Submit transactions to Move public functions to mutate on-chain state (create assemblies, bring them online, deposit items, etc.).
* **Read path** — Query on-chain state and events via [GraphQL](https://docs.sui.io/guides/developer/accessing-data/query-with-graphql), [gRPC](https://docs.sui.io/guides/developer/accessing-data/grpc-overview), or custom indexers.

***

## Writing to the World Contracts

Write operations use the [Sui TypeScript SDK](https://sdk.mystenlabs.com/typescript) to build and submit transactions. The [world-contracts ts-scripts](https://github.com/evefrontier/world-contracts/tree/main/ts-scripts) provide examples on how to interact with the EVE Frontier world. You can also use other SDKs (e.g., [Rust](https://docs.sui.io/references/rust-sdk) or [community Go SDK](https://docs.sui.io/references/sui-sdks)) based on your tech stack.

### Example: Bring Assembly Online

This script borrows `OwnerCap` from a character, calls the assembly `online` function, then returns the cap:

```typescript
import { Transaction } from "@mysten/sui/transactions";

// 1. Borrow OwnerCap from character
const [ownerCap] = tx.moveCall({
  target: `${config.packageId}::character::borrow_owner_cap`,
  typeArguments: [`${config.packageId}::assembly::Assembly`],
  arguments: [tx.object(characterId), tx.object(ownerCapId)],
});

// 2. Bring assembly online
tx.moveCall({
  target: `${config.packageId}::assembly::online`,
  arguments: [
    tx.object(assemblyId),
    tx.object(networkNodeId),
    tx.object(config.energyConfig),
    ownerCap,
  ],
});

// 3. Return OwnerCap to character
tx.moveCall({
  target: `${config.packageId}::character::return_owner_cap`,
  typeArguments: [`${config.packageId}::assembly::Assembly`],
  arguments: [tx.object(characterId), ownerCap],
});
```

See [ts-scripts/assembly/online.ts](https://github.com/evefrontier/world-contracts/blob/main/ts-scripts/assembly/online.ts) for the full script.

### Example: Sponsored Transactions

Many world operations require server-side validation (e.g., proximity checks, sponsor checks). These use **sponsored transactions** — the player signs the intent, and an authorized sponsor (e.g., EVE Frontier) pays gas and submits:

```typescript
tx.setSender(playerAddress);
tx.setGasOwner(adminAddress);  // Sponsor pays gas

// ... moveCall to game_item_to_chain_inventory, etc.

const result = await executeSponsoredTransaction(
  tx, client, playerKeypair, adminKeypair,
  playerAddress, adminAddress,
  { showEvents: true }
);
```

See [ts-scripts/storage-unit/deposit-to-ephemeral-inventory.ts](https://github.com/evefrontier/world-contracts/blob/main/ts-scripts/storage-unit/deposit-to-ephemeral-inventory.ts) for a full example.

***

## Reading from the World Contracts

**SuiClient** is the main entry point for read operations in the [TypeScript SDK](https://sdk.mystenlabs.com/typescript). It connects to a Sui full node and exposes methods for querying objects, events, and transactions without submitting any transaction. Use it when you need to read state programmatically — for example, fetching an assembly's current config or checking ownership before building a transaction.

### GraphQL

Use Sui's [GraphQL](https://docs.sui.io/guides/developer/accessing-data/query-with-graphql) to query objects by type, owner, or filters.

**Example: Get objects by type**

```graphql
query GetObjectsByType($type: String!, $first: Int) {
  objects(filter: { type: $type }, first: $first) {
    pageInfo {
      hasNextPage
      endCursor
    }
    nodes {
      address
      asMoveObject {
        contents {
          json
        }
      }
    }
  }
}
```

Try it: [GraphQL Testnet IDE](https://graphql.testnet.sui.io/graphql). Pass variables in the IDE's Variables panel, e.g.:

```json
{
  "type": "0x2ff3e06b96eb830bdcffbc6cae9b8fe43f005c3b94cef05d9ec23057df16f107::network_node::NetworkNode",
  "first": 10
}
```

### Query character by wallet address

A [PlayerProfile](https://github.com/evefrontier/world-contracts/blob/8edd7e441daec68afab0558e80574efd6a241ce8/contracts/world/sources/character/character.move#L50) is created at character creation and transferred to the player’s wallet (`character_address`). Query objects owned by the wallet with type `PlayerProfile` to get `character_id`, then fetch the full `Character` if needed.

Use your network’s GraphQL endpoint and the world package ID for that network. Set `address` to the wallet (Sui address) and `profileType` to `0x<WORLD_PACKAGE_ID>::character::PlayerProfile` (package ID is network-specific; see your deployment or [world-contracts](https://github.com/evefrontier/world-contracts)).

```graphql
query GetCharacterDetails($address: SuiAddress!, $profileType: String!) {
  address(address: $address) {
    objects(last: 10, filter: { type: $profileType }) {
      nodes {
        contents {
          ... on MoveObject {
            contents {
              type { repr }
              json
            }
          }
        }
      }
    }
  }
}
```

Variables:

```json
{
  "address": "0x...",
  "profileType": "0x<WORLD_PACKAGE_ID>::character::PlayerProfile"
}
```

Each node’s `json` includes `character_id`. Use that ID to load the full `Character` (e.g. query `objects` by that ID).

### gRPC

For higher throughput and streaming (e.g., checkpoints), use [gRPC](https://docs.sui.io/guides/developer/accessing-data/grpc-overview)—Sui's preferred read path. Requires a gRPC-enabled Sui full node.

```bash
# List objects owned by an address
grpcurl -d '{ "owner": "<Sui_address>" }' <full_node_url>:443 sui.rpc.v2.StateService/ListOwnedObjects
```

### Events

State changes emit events on transactions. Use [suix\_queryEvents](https://docs.sui.io/guides/developer/accessing-data/using-events) to filter by module, type, or sender:

```bash
curl -X POST https://fullnode.mainnet.sui.io:443 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "suix_queryEvents",
    "params": [{
      "MoveEventType": "0x...::smartgate::JumpEvent"
    }, null, 10, false]
  }'
```

World events include `JumpEvent` (gate traversal), inventory updates, and deployment changes. Store and subscribe to events off-chain for dashboards, analytics, or game services.

***

## Reading from the World

* **TypeScript** — [SuiClient](https://sdk.mystenlabs.com/typescript) for objects, events, and transactions.
* **GraphQL** — [Query by type, owner, or filters](https://docs.sui.io/guides/developer/accessing-data/query-with-graphql); [Testnet IDE](https://graphql.testnet.sui.io/graphql).
* **gRPC** — [Higher throughput and streaming](https://docs.sui.io/guides/developer/accessing-data/grpc-overview) (requires gRPC-enabled full node).
* **Events** — [suix\_queryEvents](https://docs.sui.io/guides/developer/accessing-data/using-events) for Move events (e.g. gate jumps, inventory updates).


# World upgrades

[EVE Frontier world-contracts](https://github.com/evefrontier/world-contracts) ships as versioned releases. Each **upgrade** creates a **new package object** (new address); older versions stay on-chain and stay callable.

***

## How package upgrades work on Sui

Read these in order:

1. [**Object and package versioning**](https://docs.sui.io/guides/developer/objects/versioning) — User packages get a **new package ID per publish/upgrade**; the family is tied by **original ID** and **UpgradeCap**.
2. [**Upgrading packages**](https://docs.sui.io/build/package-upgrades) — Compatibility rules, `UpgradeCap` / tickets, `sui client upgrade`, old packages remain on-chain.
3. [**Move package management**](https://docs.sui.io/guides/developer/sui-101/move-package-management) — `published-at`, dependencies, upgraded dependencies.

***

## Using MVR (recommended for clients)

The [Move Registry (MVR)](https://docs.suins.io/move-registry) maps a **stable application name** to the **correct package address** for the network your client uses. After we publish an upgrade, you keep the same MVR name in config and tooling; resolution picks up the **latest** registered package for that name on **testnet** or **mainnet**, instead of chasing two hex IDs.

`@evefrontier/world` : <https://www.moveregistry.com/package/@evefrontier/world?tab=versions>

Today this name resolves to our **testnet** deployment for Stillness server. When Stillness ships on mainnet and the registry is updated, the **same name** will resolve to the production package on mainnet and to the sandbox/testnet package when clients target testnet.

**Integrate in TypeScript:** [Transaction plugin for Sui TypeScript SDK](https://docs.suins.io/move-registry/tooling/typescript-sdk) (`namedPackagesPlugin`, or `@mysten/sui` client resolution). Use `@evefrontier/world::…` in `moveCall` `target` strings and in type references the same way you would a package ID.

### Why this replaces the old “two package IDs” mental model (for apps)

Before MVR, builders often tracked **two** hex addresses from our `Published.toml`:

* **`published-at`** — latest package ID; needed for **new** entrypoints and bytecode that only exist on the upgraded package.
* **`original-id`** — first package in the family; objects and type strings created before an upgrade often still reference this id in **on-chain type tags** and filters.

For **calling** the world package from a dApp, you had to update `packageId` in config when we shipped a new `published-at`. With MVR, **one name** (`@evefrontier/world`) is enough for those call targets on a given network, as long as your client resolves names against the right chain.

You still need to understand **original vs latest** when **reading** existing objects or filtering by type string; see [Which package ID for what?](#which-package-id-for-what) below.

***

## Where the IDs live (Move / extensions / debugging)

After a world upgrade, **`published-at`** (per environment) remains the **latest** package ID in the repo and in `Published.toml`. Extension authors and anyone editing `Move.toml` still care about **`original-id`** vs **`published-at`** for dependency pins and upgrades.

In [world-contracts](https://github.com/evefrontier/world-contracts), each environment is recorded in `contracts/world/Published.toml`:

* **`original-id`** — First package in the upgrade family (the original publish address).
* **`published-at`** — **Latest** package ID; used for Move dependency `published-at`, extension upgrades, and **manual** hex-based tooling.

Bump your Move dependency to the release tag you need (e.g. `v0.0.21`), refresh `Move.lock`, then republish or **upgrade** your extension with your `UpgradeCap` if the world API you use changed.

### TypeScript / dApps

Prefer **`@evefrontier/world`** (MVR) for configurable `moveCall` targets when your stack supports it. If you are not using MVR yet, use a configurable `packageId` set from **`published-at`** for that network.

***

## Migration checklist

If you are still pinned to an **old** world package ID:

| Audience              | What to do                                                                                                                                                                                                                                                                                                              |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **dApps**             | Prefer **`@evefrontier/world`** (MVR) for call targets on testnet; otherwise point config at **`published-at`** when you need new functions or fixes.                                                                                                                                                                   |
| **Custom extensions** | Bump world-contracts, test, upgrade your package if needed.                                                                                                                                                                                                                                                             |
| **Indexers**          | Do not assume a single type prefix across upgrades, expand filters or use canonical type resolution ([GraphQL `type`](https://docs.sui.io/references/sui-api/sui-graphql/beta/reference/operations/queries/type)); see also [MystenLabs/sui#12853](https://github.com/MystenLabs/sui/pull/12853) for event edge cases). |

***

## Which package ID for what?

| Situation                                                      | What to use                                                                                                                                                                    |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`moveCall` targets from an app (recommended)**               | **`@evefrontier/world`** — resolves to the latest registered package on the network your RPC targets (Stillness / testnet today).                                              |
| **New functions** (only in upgraded bytecode), **without** MVR | **Latest** (`published-at`) in `moveCall` targets.                                                                                                                             |
| **Existing flows** (unchanged public APIs), **without** MVR    | **Original** or **latest**—both work.                                                                                                                                          |
| **Object lookups / type filters** (e.g. `getOwnedObjects`)     | Use the **package ID in the object’s type string** (often **original** for objects from before the upgrade, e.g. `originalId::gate::JumpPermit`). Types are not auto-retagged. |

**GraphQL:** `[type](https://docs.sui.io/references/sui-api/sui-graphql/beta/reference/operations/queries/type)` accepts a type string with any package address in the lineage **at or after** the **defining** (first) package for that type and returns the canonical form—useful when comparing types across upgrades.


# efctl (community)

[**efctl**](https://frontier.scetrov.live/links/efctl/) is a community-built CLI that automates the full builder-scaffold setup: bringing up the local EVE Frontier environment, deploying world contracts and smart gates, and tearing it down. Use it when you want a single-command workflow instead of following the manual Docker or Host flow.

efctl is built and maintained by a community member; see [efctl](https://frontier.scetrov.live/links/efctl/) for source and docs.

**Docs:** [Unofficial EVE Frontier Development Notes](https://frontier.scetrov.live/getting-started/)

## For better understanding of the building

* [Sui CLI Cookbook](https://frontier.scetrov.live/devsecops/sui-cli-cookbook/) — Sui CLI basics and common commands
* [World contracts](https://frontier.scetrov.live/develop/world-contracts/) — Design and architecture of EVE Frontier world contracts
* [Builder scaffold overview](https://frontier.scetrov.live/develop/builder-scaffold/) — How the scaffold works and common gotchas


# dApp kit

> React SDK for building EVE Frontier dApps on the Sui blockchain

**Full API documentation (TypeDoc):** [**http://sui-docs.evefrontier.com/**](http://sui-docs.evefrontier.com/)

## Features

* 🔌 **Wallet Connection** - Easy integration with EVE Vault and Sui wallets
* 📦 **Smart Object Data** - Fetch and transform assembly data via GraphQL
* ⚡ **Sponsored Transactions** - Gas-free transactions via EVE Frontier backend
* 🔄 **Auto-Polling** - Real-time updates with automatic data refresh
* 🎨 **TypeScript First** - Full type safety for all assembly types

## Installation

```bash
npm install @evefrontier/dapp-kit
# or
pnpm add @evefrontier/dapp-kit
```

### Peer Dependencies

```bash
npm install @tanstack/react-query react
```

> Keep your app’s versions of **react**, **@mysten/dapp-kit-react**, and **@mysten/sui** in sync with the versions used by this package to avoid type or runtime mismatches. Check `package.json` for the current ranges.

## Quick Start

### 1. Set up the Provider

Wrap your app with `EveFrontierProvider`:

```tsx
import { EveFrontierProvider } from "@evefrontier/dapp-kit";
import { QueryClient } from "@tanstack/react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <EveFrontierProvider queryClient={queryClient}>
      <MyDapp />
    </EveFrontierProvider>
  );
}
```

### 2. Configure Assembly ID

Set the assembly ID via URL parameter:

`https://yourdapp.com/?tenant=utopia&itemId=...`

### 3. Use the Hooks

```tsx
import { EveFrontierProvider, useConnection, useSmartObject } from "@evefrontier/dapp-kit";
import { QueryClient } from "@tanstack/react-query";

const queryClient = new QueryClient();

function App() {
  return (
    <EveFrontierProvider queryClient={queryClient}>
      <MyDapp />
    </EveFrontierProvider>
  );
}

function MyDapp() {
  const { isConnected, handleConnect } = useConnection();
  const { assembly, loading } = useSmartObject();
  if (!isConnected) return <button onClick={handleConnect}>Connect</button>;
  if (loading) return <div>Loading...</div>;
  return <div>{assembly?.name}</div>;
}
```

## Core Concepts

### Hooks

| Hook                                  | Description                                      |
| ------------------------------------- | ------------------------------------------------ |
| `useConnection()`                     | Wallet connection state and methods              |
| `useSmartObject()`                    | Current assembly data with auto-polling          |
| `useNotification()`                   | Display user notifications                       |
| `dAppKit.signTransaction()`           | Sign a transaction (from @mysten/dapp-kit-react) |
| `dAppKit.signAndExecuteTransaction()` | Sign and execute a transaction                   |

### Standard Transactions (via dAppKit)

For normal transactions, use the `dAppKit` object which wraps Mysten's `@mysten/dapp-kit-react`:

```tsx
import { useDAppKit } from "@mysten/dapp-kit-react";
import { Transaction } from "@mysten/sui/transactions";

function CustomTransactionButton() {
  const dAppKit = useDAppKit();

  const handleCustomTx = async () => {
    // Build your transaction
    const tx = new Transaction();
    tx.moveCall({
      target: "0xpackage::module::function",
      arguments: [tx.pure.string("hello")],
    });

    // Sign and execute
    const result = await dAppKit.signAndExecute({
      transaction: tx,
    });

    console.log("Transaction digest:", result.digest);
  };

  return (
    <button onClick={handleCustomTx}>
      Execute Tx
    </button>
  );
}
```

### Assembly Types

The SDK supports all EVE Frontier assembly types:

```typescript
import { Assemblies, AssemblyType } from "@evefrontier/dapp-kit";

// Available types
Assemblies.SmartStorageUnit
Assemblies.SmartTurret
Assemblies.SmartGate
Assemblies.NetworkNode
Assemblies.Manufacturing
Assemblies.Refinery
```

## GraphQL API

For custom data fetching:

```typescript
import { 
  getAssemblyWithOwner,
  getObjectWithJson,
  executeGraphQLQuery 
} from "@evefrontier/dapp-kit";

// Fetch assembly with owner
const { moveObject, character } = await getAssemblyWithOwner("0x123...");

// Fetch any object
const result = await getObjectWithJson("0x456...");

// Custom query
const data = await executeGraphQLQuery(myQuery, { address: "0x789..." });
```

## Utilities

```typescript
import {
  abbreviateAddress,
  isOwner,
  formatM3,
  formatDuration,
  getTxUrl,
  getDatahubGameInfo,
} from "@evefrontier/dapp-kit";

// Shorten address for display
abbreviateAddress("0x1234567890abcdef"); // "0x123...cdef"

// Fetch item metadata
const info = await getDatahubGameInfo(83463);
console.log(info.name, info.iconUrl);
```

## API Reference

**Full API documentation (TypeDoc):** [**http://sui-docs.evefrontier.com/**](http://sui-docs.evefrontier.com/)


# Resources

## World API

These are the current addresses for the World API endpoints for both Stillness (Live)

| Environment | Address                                                                                        |
| ----------- | ---------------------------------------------------------------------------------------------- |
| Stillness   | [World API (Stillness)](https://world-api-stillness.live.tech.evefrontier.com/docs/index.html) |

## Package ID's

Here are the current Package ID's for Stillness (Live)

## Stillness

| Object                  | Address                                                            |
| ----------------------- | ------------------------------------------------------------------ |
| World Package           | 0x8b8a46ed766fa1358ce7c5c51f6a164b13d627a63e45343f69ed0ba0446c1aa1 |
| Object Registry         | 0xf6aed9361acc0d7021672b653ebe9dae45d88e11fecef01cc5434c8f60ae764f |
| Killmail Registry       | 0x74fee5f66951f4e6319ff6f5f450ce7f64c31b0943d224d0b755cc638b0860c9 |
| Server Address Registry | 0xdb5f40fd5659e4f21d6b07ed3cedcd532a21a6054815a7b3fe3817631ed6dbd2 |
| Location Registry       | 0x0b3f4f78d7db3c7dee8d624298d45d53f45da69bb5df7b6165f3259258a6d723 |
| Energy Config           | 0x885d13b06bd9199d037aa358ba37e6692aca92d7bf6c1b5a5210da7d83501b09 |
| Fuel Config             | 0x190645fbcf66b9322dbc8f3ee5f883e46e1e6ab562daa978ffd78cb88404f7cf |
| Gate Config             | 0x940975151ac5fa80e3ab1a993dc95172ea792167b6bcfb7882205004795270d3 |
| AdminACL                | 0x725fe75e302c7f4b1baaa9c624a949f5a53a4fac355e206ba4b54f96470b1b14 |

## EVE Vault

You can find the latest EVE Vault release at [EVE Vault releases](https://github.com/evefrontier/evevault/releases/)


# Builder

Below are some of the common gotchas during building

1.


# Player


# Wallet

Below are some of the common gotchas in using the wallet


# Sandbox Access

{% hint style="info" %}
Note that this server is only available during specific events and is not generally accessible.
{% endhint %}

#### Download the Launcher

* Visit <https://evefrontier.com/en/download> and select either the Windows or Mac launcher
* Download and install

#### Add the Utopia Server to the launcher

{% tabs %}
{% tab title="Windows" %}
**Modify an application shortcut**

* Right-click on the desktop shortcut to the EVE Frontier Launcher
* Choose Properties
* On the "shortcut" tab add the following to the end of the Target field: `--frontier-test-servers=Utopia`
* Click "Okay"
  {% endtab %}

{% tab title="Mac OS" %}

#### Run the launcher from Terminal

* Open Terminal in the folder where the application resides
* Run the following command: cd /Applications; open 'EVE Frontier.app/' --args --frontier-test-servers=Utopia
  {% endtab %}
  {% endtabs %}

#### Run the Launcher and Select "Utopia"

You should then see Utopia on the drop down list of servers in the bottom right of the launcher

* Choose Utopia
* Click Register
* Fill in your details
* You will see a prompt to enter a verification code which you will receive by email
* You can then download the Utopia Client

{% hint style="info" %}
After this point you will need to wait for your access to be granted by Fenris, until then you will see a "Founder Access Required" Error
{% endhint %}

### Slash Commands

To speed up moving around the universe and gathering materials we have enabled a number of commands you can use on the sandbox To use these commands simply enter them in the chat window

* `/moveme` This will display a list of star systems which you can move to instantly
* `/giveitem <itemid> <quantity>` This will allow you to spawn the specified item in your current ships' cargo hold
* `/giveitem "<item name>" <quantity>` This works the same way as the above but lets you specify an item by name rather than ItemID

{% hint style="info" %}
Note it is possible to overload your ships cargo this way which will prevent warping, you would first need to move the excess to a storage unit or jettison it
{% endhint %}

Some common items you may want to spawn include the below, more ItemID's can be found via the WorldAPI (<https://world-api-utopia.uat.pub.evefrontier.com/docs/index.html>):

| Name                    | ItemID |
| ----------------------- | ------ |
| Carbon Weave            | 84210  |
| Thermal Composites      | 88561  |
| Printed Circuits        | 84180  |
| Reinforced Alloys       | 84182  |
| Feldspar Crystals       | 77800  |
| Hydrated Sulfide Matrix | 77811  |
| Building Foam           | 89089  |


# A Work In Progress

As we transition EVE Frontier to the Sui Blockchain we are also re-writing all of our documentation to reflect this change and expanding the scope of this site to better serve those new to building in EVE Frontier.&#x20;

This will be a gradual process and you will notice //todo's on a number of pages, these pages are still being actively worked on and will be filled out over the coming days and weeks.&#x20;

That's not the end however; and this new documentation site has been designed for community contribution going forward, so that together, we can build a better experience for builders at all skill levels.&#x20;

<figure><img src="https://3422455832-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjououNqaED1JlIqLVkpm%2Fuploads%2FbK3yWTpoaJpolr2gGMsB%2Fbanner7.png?alt=media&amp;token=1ee18ee1-3e7d-42f9-b9fe-b90cbb24c472" alt=""><figcaption></figcaption></figure>


# How to Contribute

{% hint style="info" %}
This site is synced to GitHub. You can propose changes from any page. Use **Edit on GitHub** to open a pull request.
{% endhint %}

### What you can contribute

* Fix typos and broken links.
* Clarify steps that are confusing or missing.
* Add examples, screenshots, or diagrams.
* Update docs when behavior changes.
* Add troubleshooting notes and known pitfalls.

{% hint style="info" %}
If you are unsure, submit a small PR first. We can iterate from there.
{% endhint %}

### Quick workflow (recommended)

{% stepper %}
{% step %}
**Open the page you want to change**

Navigate to the exact page in the docs. Scroll to the section you want to improve.
{% endstep %}

{% step %}
**Click Edit on GitHub**

Open the page menu (top-right dropdown). Select **Edit on GitHub**. Sign in to GitHub if prompted. GitHub will open the source file for that page.
{% endstep %}

{% step %}
**Edit in GitHub**

Click the pencil icon to edit the file. If you do not have access, GitHub will fork first. Make your changes in the editor. Use the preview tab to sanity check formatting.
{% endstep %}

{% step %}
**Propose the change**

Scroll to the bottom of the editor. Write a clear commit message. Keep it short and specific. Select the option to create a new branch. Use a descriptive branch name. Click **Propose changes**.
{% endstep %}

{% step %}
**Open a pull request**

GitHub will suggest creating a PR. Fill in:

* **Title**: what changed.
* **Description**: why it changed.
* **Scope**: which pages or sections.
* **Verification**: how you checked accuracy.

Then click **Create pull request**.
{% endstep %}

{% step %}
**Respond to review**

You may get comments or requested edits. Update the same branch. GitHub will update the PR automatically.
{% endstep %}

{% step %}
**Merge and publish**

After approval, a maintainer will merge the PR. This site will sync and publish shortly after. If you do not see updates, wait a few minutes.
{% endstep %}
{% endstepper %}

### Best practices for strong PRs

#### Keep changes small

Prefer one topic per PR. Avoid drive-by refactors. Large rewrites are hard to review.

#### Write for the reader

State prerequisites early. Use concrete values and examples. Prefer copy-pasteable snippets. Call out footguns and failure modes.

#### Match existing style

Keep sentences short. Use headings to break up long pages. Use consistent terms across pages.

#### Be precise and verifiable

Do not guess. If something is unconfirmed, label it clearly. Add a link to a source when possible.

#### Don’t leak secrets

Never commit API keys, tokens, or internal URLs. Redact logs that include credentials.

### Courtesy guidelines

* Assume good intent.
* Be kind and direct in review comments.
* Ask before making broad structural changes.
* Don’t “win” arguments by rewriting someone’s work.
* Prefer suggestions over mandates.
* Be patient with response times.

{% hint style="warning" %}
If your change impacts behavior, call it out. Surprise changes cause real user pain.
{% endhint %}

### Common reasons PRs get delayed

* Mixed, unrelated edits in one PR.
* Vague commit message or PR description.
* Claims without a way to verify.
* Breaking links or inconsistent terminology.
* Big rewrites without prior discussion.

### Need help?

If GitHub is blocking your edit, copy the error message. Include it in your PR or message to the maintainers. If you are unsure where to edit, link the page.


# Moving into the Future

The meat of this development blog is covered in the [main news article](https://evefrontier.com/en/news/moving-into-the-future-upgrading-to-python-3). But if you’re still here you may be one who appreciates reading changelogs and delving deep into codebases. If so then oh boy do we have some bonus content for you.

Python has changed quite a bit since 2.7, we thought it would be good to compile a quick overview of the changes that are worth highlighting:

First of all, we’d just like to point you to \[the official Python documentation]\(<https://docs.python.org/3/>). It’s actually a really good resource for all things related to the language syntax, the standard library, release notes, etc. Once this is complete the World Deployer container will go offline.

## Core Additions

### Print is a function

The cowboy-debugger's best friend. The print statement has bit the dust and been replaced by the `print()` function. The print function works mostly the same as before, with some new options such as separator and suffix customization. The function signature looks like this:

```python
def print(*args, sep=' ', end='\n', file=None):
```

### Iterators and views

A handful of things that used to return a list now return an iterator. For example `map()`, `filter()`, `range()` and `zip()` all return an iterator. The `iterkeys()`, `itervalues()` and `iteritems()` methods on dict have been removed and the corresponding `keys()`, `values()` and `items()` methods now return a view instead (practically the same as an iterator).

This means that you don't have to use utilities such as `xrange()` or `itertools.izip` anymore. However, this also means that if you need the returned values to be in a list then you have to create it yourself, i.e. `list(d.values())`.

### Text vs. Data

The Python documentation has a rather dramatic quote regarding this change:

> *Everything you thought you knew about binary data and Unicode has changed.*

<br>

However, we can be a bit less dramatic and just say:

> *Don't worry, in practice this doesn't really change anything for you, you're good.*

<br>

In short we now have the concepts of text and data. All text, represented with the `str` type, is Unicode. Data, represented with the `bytes` type, is just a sequence of bytes. You can not directly mix these two together. If you need to represent some text as data (i.e. for serialization) then it must be encoded as bytes with the `str.encode()` method. If you need to interpret some data as text, then it must first be decoded from bytes to text with the `bytes.decode()` method. That's pretty much it.

Basic string literals are now treated as Unicode text by default (no need to use `u""` anymore), and `b""` is used for bytes literals.

You can read a much more detailed breakdown here: [What’s New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html#text-vs-data-instead-of-unicode-vs-8-bit)

## New Toys

### Type Hinting

You can now annotate functions and variables with type information.

```python
age: int = 1

def stringify(num: int) -> str:
    return str(num)
```

These annotations are not enforced at runtime, but they are used by tools such as PyCharm and [mypy](https://github.com/python/mypy) to give you insights and warnings about your code.

This is a huge feature that we highly encourage developers to explore and use. Coding against a well type-annotated API makes everything so much easier for everyone.

There’s no need to go into more detail about this vast topic here, but a good starting point would be to read about it in the mypy documentation: [Type hints cheat sheet - mypy 1.15.0 documentation](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html)

### Data Classes

Sometimes we create classes that exist mostly to group together a handful of data. It's fairly easy to slap together such a class by just defining an `__init__()` method that assigns its arguments to `self`, or you could use a `collections.namedtuple`[<sup>1</sup>](#footnote1). However, sometimes we also need to be able to compare instances of the class, and hash them so they can be used as dictionary keys, and print them nicely for debugging, and order them in a priority queue, etc. In that case you need to implement magic-methods for your class.

Imagine for example that you have a `User` class:

```python
class User:
    firstname: str
    lastname: str

    def __init__(self, firstname: str, lastname: str) -> None:
        self.firstname = firstname
        self.lastname = lastname

    def __repr__(self) -> str:
        clsname = self.__class__.__name__
        firstname = self.firstname
        lastname = self.lastname
        return f'{clsname}({firstname=}, {lastname=})'

    def __eq__(self, other) -> bool:
        return (
            self.__class__ is other.__class__ and
            self.firstname == other.firstname and
            self.lastname == other.lastname
        )

    def __hash__(self) -> int:
        return hash((self.firstname, self.lastname))
```

If you wanted to add a `age` field to this class you'd have to make at least seven changes in the above code. However, the same class written using the new `@dataclass` decorator would only require that you make a single change:

```python
from dataclasses import dataclass

@dataclass
class User:
    firstname: str
    lastname: str
    age: int = 0  # just add this line and you're done
```

The `@dataclass` decorator automatically generates `__init__`, `__hash__`, `__repr__`, etc. You can somewhat control which methods are generated via arguments to the decorator, which you can read more about here: [dataclasses — Data Classes](https://docs.python.org/3/library/dataclasses.html)

\<br/ >**\[1]**: *Please, don’t use namedtuple for data classes. It’s a tool that’s misunderstood and misused for problems that @dataclass handles far better.*

### Enum

An `Enum` is a set of symbolic names bound to unique values. They are similar to global variables, but they offer a more useful `repr()`, grouping, type-safety, and a few other features.

```python
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
```

We already had a back-ported version of the `enum` module in our codebase, but now it's in the standard library as well.

More info here: [enum - Support for enumerations](https://docs.python.org/3/library/enum.html)

### F-strings

You're probably used to using either %-formatting or the `str.format()` method to format strings. However, there's a new kid on the block that allows you to embed expressions inside string literals, using a minimal syntax. Say hello to the f-string:

```python
>>> name = "Bob"
>>> f"His name is {name}."
"His name is Bob."
```

For the cowboy-debuggers out there you can add '=' to quickly format the given variable name along with its value:

```python
>>> cowboy = True
>>> guns = 2
>>> print(f"{cowboy=}, {guns=}")
cowboy=True, guns=2
```

See also [Lexical analysis](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)

## Niche but Notable Changes

### Pattern matching

If you're familiar with C / Java / JavaScript's `switch` or even Rust's `match` then this should be pretty familiar.

```python
def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the internet"
```

"But isn't this just a glorified if-else statement?" you might ask. Yes, in the case above it certainly is. However, the rabbit hole does go quite a bit deeper:

```python
class Point:
    __match_args__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

match point:
	case Point(x=0, y=0):
		print("Origin")
	case Point(x=0, y=y):
		print(f"Y={y}")
	case Point(x=x, y=0):
		print(f"X={x}")
	case Point():
		print("Somewhere else")
	case _:
		print("Not a point")

match points:
    case []:
        print("No points")
    case [Point(0, 0)]:
        print("The origin")
    case [Point(x, y)]:
        print(f"Single point {x}, {y}")
    case [Point(0, y1), Point(0, y2)]:
        print(f"Two on the Y axis at {y1}, {y2}")
    case _:
        print("Something else")
```

Be careful with the match statement. It’s a powerful tool that comes with some tradeoffs. It increases the indentation one more level compared to an if-else statement, and it's easy to craft some complex code with it, so only use it when it markedly improves code readability vs. if-else statements.

You can read more about match statements here: [More Control Flow Tools](https://docs.python.org/3/tutorial/controlflow.html#tut-match)

### Division

In Python 2 the result type of a division depended on the input types, so f.ex. if you divided a float by an integer the result would be an integer.

In Python 3 division is always performed with floats, regardless of the input types. If you want a truncated result you can use the `//` operator instead, or use `round()`.

### Ordering comparisons are more strict

Python 3.0 has simplified the rules for ordering comparisons. The ordering comparison operators (`<`, `<=`, `>=`, `>`) now raise a TypeError exception when the operands don’t have a meaningful natural ordering. I.e. `1 < ''`, `0 > None` or `len <= len` are no longer valid. Additionally, the `cmp` argument of `sorted()` and `list.sort()` has been removed along with the `cmp()` function and the associated `__cmp__()` method.

### Dictionaries retain the insertion order

Do you remember `collections.OrderedDict`? Well, that's effectively the default dictionary type in Python 3.

`dict()` now remembers the insertion order of its keys, and iterating over the keys/values follows that same insertion order. Likewise, `**kwargs` and class member order is preserved since both are essentially `dict()s` under the hood.

### Nested `with` statements

You can now use multiple context managers in a single `with` statement, like this:

```python
with open("a.txt"), open("b.txt") as a, b:
    ...
```

### The Walrus Operator `:=`

Honestly, this is a strange one that some people wish hadn’t been added to the language, but it's here to stay, so we need to get familiar with it. Here's an example sighting of this wild beast:

```python
while node := node.parent is not None:
    process(node)
```

The walrus operator allows you to use variable assignments as expressions, and thus nest them within other expressions. There are niche cases where this can increase readability given that you know about this operator. However, as is stated in the official documentation (a good indicator to some that this was a bad decision in the first place):

> Try to limit use of the walrus operator to clean cases that reduce complexity and improve readability.

\<br/ >

Read more about it here: [What's New In Python 3.8](https://docs.python.org/3/whatsnew/3.8.html#assignment-expressions)

### Underscores as Thousand Separators

This one is just a nice little code-readability feature that can be summarized with this example:

```python
amount = 10_000_000.0
addr = 0xCAFE_F00D
flags = 0b_0011_1111_0100_1110
```

See also [PEP 515 - Underscores in Numeric Literals](https://peps.python.org/pep-0515/)

## Advanced Topics

### Metaclasses

The syntax for declaring metaclasses and the semantics for how classes with metaclasses are constructed has been changed drastically. You can read the specifics here: [PEP 3115 - Metaclasses in Python 3000](https://peps.python.org/pep-3115/)

### Positional-only parameters

There is a new function parameter syntax `/` to indicate that some function parameters must be specified positionally and cannot be used as keyword arguments. Additionally, any parameters defined after the `*` variable parameter must be specified as keyword arguments.

In the following example, parameters *a* and *b* are positional-only, while *c* or *d* can be positional or keyword, and *e* or *f* are required to be keywords:

```python
def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)
```

More details here: [What's New In Python 3.8](https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters)

### Simpler customization of class creation

It is now possible to customize subclass creation without using a metaclass. The new `__init_subclass__` classmethod is called on the base class whenever a new subclass is created.

```python
class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass
```

More details here: [What's New In Python 3.6](https://docs.python.org/3/whatsnew/3.6.html#pep-487-simpler-customization-of-class-creation)

### Extended iterable unpacking

The iterable unpacking syntax has been slightly changed to allow specifying a “catch-all” name which will be assigned a list of all items not assigned to a “regular” name:

```python
>>> a, *b, c = range(5)
>>> a
0
>>> c
4
>>> b
[1, 2, 3]
```

More details here: [PEP 3132 - Extended Iterable Unpacking](https://peps.python.org/pep-3132/)

### No more implicit relative imports

In Python 2 you were allowed to [implicitly import modules with a path relative to the importing module](https://docs.python.org/2.7/tutorial/modules.html?highlight=implicit%20relative#intra-package-references). In Python 3 all import paths must be explicit, meaning that a path starting with a `.` is relative and anything else is absolute. If you don't understand what any of that means, don't worry, it was just an old foot-gun that's now been removed.

### Exception context

The inspection and reporting capabilities of exception objects has been significantly improved. Exception instances support new `__context__`, `__cause__` and `__traceback__` properties that help give more information about exceptions that occur during the handling of other exceptions.

During the handling of one exception (exception A), it is possible that another exception (exception B) may occur. In Python 2, if this happened, exception B was propagated outward and exception A was lost. In order to debug the problem, it would be useful to know about both exceptions. The exception’s `__context__` attribute retains this information automatically.

Additionally, it can sometimes be useful for an exception handler to intentionally re-raise an exception, either to provide extra information or to translate an exception to another type. The `__cause__` attribute provides an explicit way to record the direct cause of an exception.

We’ve got some fancy new syntax to set the `__cause__` property:

```python
raise EXCEPTION from CAUSE
```

which is equivalent to:

```python
exc = EXCEPTION
exc.__cause__ = CAUSE
raise exc
```

And finally the `__traceback__` property simply stores the traceback object whereas in Python 2 you had to retrieve the traceback from `sys.exc_traceback`.

You can read more about exceptions here: [Built-in Exceptions](https://docs.python.org/3/library/exceptions.html)


# Introduction


# Smart Infrastructure


# Smart Assemblies


# Smart Storage Unit


# Smart Turret


# Smart Gate


# Setting Up Your Tools


# Setting Up The World


# Smart Storage Unit


# configure smart storage unit


# deploy smart storage unit contracts


# Smart Turret


# configure smart turret


# deploy smart turret contracts


# Smart Gate


# configure smart gate


# deploy smart gate contracts


# Swagger World Api


# Stillness Blockchain Address


# Decentralized Applications


# dApp Quick Start


# Folder Structure


# Navigating the dApp (In-Game)


# Connecting From An External Browser


# Building


# Customizing External dApps


# Building with the dApp Scaffold


# World API


# Connecting In-Game


# Smart Contracts


# Development in the EVE Frontier World


# MUD Explainer


# Smart Object Framework Explainer


# EVE Frontier World Explainer


# Interfacing with the EVE Frontier World


# Building Pragmatics


# Introduction to PODs


# Creating and Verifying PODs


# Zero Knowledge Proofs


# EVE Vault


# Wallet Game Setup


# Browser Extension


# GAS Faucet


# Known Issues


# Troubleshooting


# Builder


# Player


# Wallet


# Contributing


