Skip to Content
DevelopersNTSNApp class

NApp Class

️⚠️
NTS is in active development and currently unstable. It is open to select early developers for exploration but is not yet suitable for production use. Follow developer updates for the latest changes, as performance is not at production level.

The NApp class is the foundational building block for creating smart contracts on N1 using TypeScript. It provides automatic state management with persistent storage, built-in token operations, transaction context access, and security features. All N1TS apps must inherit from this class.

The NApp class automatically handles:

  • State Persistence: Properties assigned to this are automatically saved to blockchain state
  • Transaction Context: Access to signer, app admin, timestamp, and other context information
  • Method Wrapping: Automatic proxy creation for state management and error handling
  • Security: Protected field access and validation of operations
🚫

Critical Rules for N1TS:

  1. Transaction Actions Cannot Return Values: Methods called by users are for state changes only and cannot return values.

  2. Cannot Initialize Class Properties: You MUST declare properties and initialize them in an init() method.

  3. Use Current Function Names: Use nmint, ntransfer, etc. (not old _mint, _transfer names).

Usage

Creating an Application

To create an App, extend the NApp class:

index.ts
import { NApp } from "@n1xyz/nts-compiler"; class MyApp extends NApp { // Declare properties without initialization counter: number; userAddress: string; // Initialize in init() method init(): void { this.counter = 0; this.userAddress = ''; } }

Creating State

The NMap class is a specialized key-value type for N1 applications that provides persistent storage with automatic state management. It’s designed to work seamlessly with the NApp class to store and manage data.

index.ts
import { NMap } from "@n1xyz/nts-compiler"; class MyApp extends NApp { private messages: NMap<string> = new NMap<string>(this, "messages"); init(): void { // Initialize other properties here if needed } // the rest of your code... }

Creating Actions

Actions are methods/functions that can be called on your App by users. When sending a transaction to the N1 network, the user specifies the action they want to call.

To create an action, you need to expose it using the createExecutableFunctions utility. If you don’t expose the action, it will not be callable by users.

Important: Transaction actions cannot return values!

index.ts
import { createExecutableFunctions, NApp } from "@n1xyz/nts-compiler"; export class MyApp extends NApp { private messages: NMap<string> = new NMap<string>(this, "messages"); init(): void { // Initialize other properties here if needed } setMessage(message: string): void { this.messages.set(this.signer(), message, 'message_set', this.signer()); this.log('Message set for user:', this.signer()); } } export const { setMessage } = createExecutableFunctions(MyApp);

Using Token Functions

N1TS provides token functions that need to be imported. Only basic functions like nread, nwrite, and log are globally available:

index.ts
import { createExecutableFunctions, NApp, nmint, ntransfer, nmintEdit } from "@n1xyz/nts-compiler"; class MyApp extends NApp { transferTokens( recipient: string, mint: string, amount: string | number | bigint ): void { // Imported token functions ntransfer( this.signer(), // From recipient, // To mint, // Token ID BigInt(amount) // Amount ); this.log('Transfer completed'); } createToken( totalSupply: string | number | bigint, admin: string, meta: any ): void { // Transaction actions cannot return values nmint(BigInt(totalSupply), admin, meta); this.log('Token creation initiated'); } editToken( mint: string, freeSupply: string | number | bigint, admin: string, meta: any ): void { nmintEdit(mint, BigInt(freeSupply), admin, meta); this.log('Token edit initiated'); } writeAppData(key: string, value: any): void { // nwrite is globally available nwrite(key, value, 'app_data', this.signer()); this.log('Data written:', key); } } export const { transferTokens, createToken, editToken, writeAppData } = createExecutableFunctions(MyApp);

Context Methods

The NApp class provides several protected methods to access transaction and application context:

Transaction Context

// Get the transaction signer address protected signer(): string // Get the app admin address protected appAdmin(): string // Get the current app ID protected appId(): string // Get current timestamp protected time(): number

Logging

// Log data (appears in transaction logs) protected log(...args: any[]): void // Example usage this.log('Transaction processed', { amount: 100, user: 'alice' }); this.log('Error occurred:', error.message);

State Management

N1TS provides automatic state persistence through property assignment, but properties must be initialized in init():

class MyApp extends NApp { // Declare properties without initialization counter: number; userAddress: string; // Collections using NMap balances: NMap<string> = new NMap<string>(this, 'balances'); init(): void { // Initialize regular properties in init() method this.counter = 0; this.userAddress = ''; } updateCounter(): void { this.counter++; // Automatically saved to blockchain state this.log('Counter updated to:', this.counter); } setBalance(user: string, amount: string): void { this.balances.set(user, amount, 'balance_update', user); this.log('Balance set for user:', user); } }

Available Functions

N1TS provides functions in two categories:

Import Required (from @n1xyz/nts-compiler)

// Token functions that must be imported nmint(totalSupply: BigInt, admin: string, meta: string): void nmintEdit(mint: string, freeSupply: BigInt, admin: string, meta: string): void ntransfer(origin: string, destination: string, mint: string, amount: BigInt): void

Globally Available

// These are automatically available without import nread(fieldId: string, appId?: string): Promise<any> nwrite(fieldId: string, value: any, tag?: string, secondary_tag?: string): void log(...args: any[]): void

Reading Token Data (Internal Use)

For internal app logic, you can read token data using field naming conventions:

class TokenReader extends NApp { protected async getTokenBalance(mintId: string, address: string): Promise<string> { // Internal helper - can return values const balance = await nread(`_balance_${mintId}_${address}`); return balance || '0'; } protected async getTokenAdmin(mintId: string): Promise<string> { return await nread(`_mint_admin_${mintId}`); } protected async getTokenMetadata(mintId: string): Promise<string> { return await nread(`_mint_meta_${mintId}`); } // Example transaction action using internal helpers checkBalance(mintId: string): void { // Note: Cannot return balance in transaction action // For reading data externally, use client SDK this.log('Balance check initiated for mint:', mintId); } }

Reading Data from Client Side

To read app state from outside the app, use the client SDK:

// Client-side code (not in transaction actions) import { NAppClient } from '@n1xyz/nts-sdk'; // Read token balance const balance = await client.readField(`_balance_${mintId}_${address}`); // Read app state const appData = await client.readField('field_name');

Field Naming Conventions

N1TS uses specific field naming patterns:

// Token balances: _balance_{mintId}_{address} const balance = await nread(`_balance_${mintId}_${userAddress}`); // Token admin: _mint_admin_{mintId} const admin = await nread(`_mint_admin_${mintId}`); // Token metadata: _mint_meta_{mintId} const metadata = await nread(`_mint_meta_${mintId}`); // Account nonces: _nonce_{address} const nonce = await nread(`_nonce_${userAddress}`);

Complete Example

complete-example.ts
import { NApp, NMap, createExecutableFunctions, nmint, ntransfer } from '@n1xyz/nts-compiler'; class CompleteApp extends NApp { // Property declarations userCount: number; totalSupply: string; isActive: boolean; // Collections userBalances: NMap<string> = new NMap<string>(this, 'user_balances'); userProfiles: NMap<UserProfile> = new NMap<UserProfile>(this, 'user_profiles'); init(): void { // Initialize regular properties this.userCount = 0; this.totalSupply = '0'; this.isActive = true; } // Transaction actions (no return values) createAppToken(supply: string, metadata: string): void { nmint(BigInt(supply), this.appId(), metadata); this.totalSupply = supply; this.log('App token created with supply:', supply); } registerUser(name: string, email: string): void { const userId = this.signer(); const profile: UserProfile = { name, email, registeredAt: this.time(), active: true }; this.userProfiles.set(userId, profile, 'user_registered', userId); this.userCount++; this.log('User registered:', userId); } transferToUser(recipient: string, mintId: string, amount: string): void { ntransfer(this.signer(), recipient, mintId, BigInt(amount)); this.log('Transfer completed to:', recipient); } // Protected helper methods can return values for internal use protected async getUserBalance(mintId: string, user: string): Promise<string> { return await nread(`_balance_${mintId}_${user}`) || '0'; } } interface UserProfile { name: string; email: string; registeredAt: number; active: boolean; } export const { createAppToken, registerUser, transferToUser } = createExecutableFunctions(CompleteApp);

Key Takeaways

  1. All properties must be declared and initialized in init()
  2. Transaction actions cannot return values - use void
  3. Use global functions: nmint, ntransfer, nwrite, etc.
  4. Reading data externally: Use client SDK, not transaction actions
  5. Internal helpers: Can return values for use within app logic
  6. Always log important events for debugging and auditing
Last updated on