Cycle

The Scriptorium

Smart Assembly code templates and tools for on-chain development in Eve Frontier.

TypeScript PTBs for EVE Frontier
AdvancedChapter 1 of 410 min read

SDK Setup

Before you can build Programmable Transaction Blocks (PTBs) in TypeScript, you need the SUI SDK and a client connection. This chapter covers installation, client creation, and the import patterns used throughout the AncientStorage dApp.

Installing the SUI SDK

The primary package is @mysten/sui, which includes everything you need:

bash
npm install @mysten/sui

For React-based dApps that need wallet integration, you also need the DAppKit packages:

bash
npm install @mysten/dapp-kit-core @mysten/dapp-kit-react

Creating a SUI Client

The SuiGrpcClient connects to a SUI fullnode. Here is how the AncientStorage dApp sets it up:

typescript
import { SuiGrpcClient } from '@mysten/sui/grpc';

const client = new SuiGrpcClient({
  network: 'testnet',
  baseUrl: 'https://fullnode.testnet.sui.io:443',
});

For different networks:

typescript
const FULLNODE_URLS = {
  mainnet: 'https://fullnode.mainnet.sui.io:443',
  testnet: 'https://fullnode.testnet.sui.io:443',
};

The Transaction Import

Transactions are built using the Transaction class from @mysten/sui/transactions:

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

const tx = new Transaction();

In some codebases, you may also need Inputs for advanced object references:

typescript
import { Transaction, Inputs } from '@mysten/sui/transactions';

Package Structure

A typical EVE Frontier dApp has several important constants:

typescript
// Your published Move package ID
const TRIBE_STORAGE_PACKAGE_ID = '0x789...';

// The shared ExtensionConfig object ID
const TRIBE_STORAGE_CONFIG_ID = '0xdef456...';

// The EVE Frontier world package ID (same for all extensions)
const EVE_FRONTIER_PACKAGE_ID = '0x28b497559d65ab320d9da4613bf2498d5946b2c0ae3597ccfda3072ce127448c';

These constants come from:

  • Your package ID: Output of sui client publish
  • Config ID: Created during init() when your package was published
  • EVE Frontier package ID: Provided by the game developers, same for all extensions on the same environment

In practice, these are stored as environment variables:

typescript
const TRIBE_STORAGE_PACKAGE_ID = process.env.NEXT_PUBLIC_TRIBE_STORAGE_PKG ?? '';
const TRIBE_STORAGE_CONFIG_ID = process.env.NEXT_PUBLIC_TRIBE_STORAGE_CONFIG ?? '';
const EVE_FRONTIER_PACKAGE_ID = process.env.NEXT_PUBLIC_EVE_FRONTIER_PKG ?? '';

Reading Object Data via JSON-RPC

Before building transactions, you often need to fetch object data. The SUI JSON-RPC API provides this:

typescript
async function fetchObjectRef(objectId: string): Promise<{
  version: string;
  digest: string;
  typeName: string | null;
  owner: string | null;
} | null> {
  const res = await fetch('https://fullnode.testnet.sui.io:443', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'sui_getObject',
      params: [objectId, { showType: true, showOwner: true, showContent: true }],
    }),
  });
  const json = await res.json();
  const data = json.result?.data;
  if (!data?.digest) return null;
  return {
    version: data.version ?? '0',
    digest: data.digest,
    typeName: data.type ?? null,
    owner: data.owner?.AddressOwner ?? null,
  };
}

This is especially important for the ReceivingRef pattern used when borrowing OwnerCap objects -- you need the exact version and digest of the object at the time of the transaction.

GraphQL Alternative

SUI also provides a GraphQL endpoint for more complex queries. The AncientStorage dApp uses it for character lookups:

typescript
const GRAPHQL_ENDPOINTS = {
  testnet: 'https://sui-testnet.mystenlabs.com/graphql',
  mainnet: 'https://sui-mainnet.mystenlabs.com/graphql',
};

async function suiGraphQL<T>(
  query: string,
  variables: Record<string, unknown>,
): Promise<T | null> {
  const res = await fetch(GRAPHQL_ENDPOINTS.testnet, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables }),
  });
  const result = await res.json();
  return result.data ?? null;
}

GraphQL is better for:

  • Querying objects owned by an address
  • Filtering by object type
  • Navigating object relationships (e.g., PlayerProfile to Character)

JSON-RPC is better for:

  • Fetching a single object by ID
  • Getting object version/digest for transactions
  • Simple lookups

Key Takeaways

  • Install @mysten/sui for the core SDK and @mysten/dapp-kit-react for wallet integration.
  • Create a SuiGrpcClient with the target network and fullnode URL.
  • Import Transaction from @mysten/sui/transactions to build PTBs.
  • Store package IDs and object IDs as environment variables.
  • Use JSON-RPC (sui_getObject) for single object lookups and GraphQL for complex queries.

Sign in to track your progress.