NApp Class
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:
-
Transaction Actions Cannot Return Values: Methods called by users are for state changes only and cannot return values.
-
Cannot Initialize Class Properties: You MUST declare properties and initialize them in an
init()
method. -
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:
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.
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!
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:
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
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
- All properties must be declared and initialized in
init()
- Transaction actions cannot return values - use
void
- Use global functions:
nmint
,ntransfer
,nwrite
, etc. - Reading data externally: Use client SDK, not transaction actions
- Internal helpers: Can return values for use within app logic
- Always log important events for debugging and auditing