N1TS Runtime Interface
The N1TS runtime provides a comprehensive interface for smart contract development. This guide covers the functions and context available to N1TS applications during execution.
Important Note
The functions described here are automatically available within N1TS applications. You don’t need to import them - they are injected into the global scope during execution. This is different from external client interfaces like NAppClient
which are used to interact with N1TS apps from outside applications.
Global Functions Available in N1TS Apps
Data Operations
// Read data from current or external app
await nread(fieldId: string, appId?: string): Promise<any>
// Read data specifically from external app (requires appId)
await nread(fieldId: string, appId: string): Promise<any>
// Write data to storage
nwrite(fieldId: string, value: any, tag?: string, secondary_tag?: string): void
Example Usage:
class DataApp extends NApp {
// This is an internal helper method, NOT a transaction action
protected async readUserData(userId: string): Promise<any> {
// Read from current app
const userData = await nread(`user_${userId}`);
// Read from external app
const externalData = await nread('global_config', 'config_app_id');
return { userData, externalData };
}
saveUserPreferences(userId: string, preferences: any): void {
// Write with tags for organization
nwrite(
`preferences_${userId}`,
JSON.stringify(preferences),
'user_data',
userId
);
}
}
Token Operations
Note: Token functions must be imported from @n1xyz/nts-compiler
// These functions require import:
// import { nmint, nmintEdit, ntransfer } from '@n1xyz/nts-compiler';
// Create a new token mint
nmint(totalSupply: BigInt, admin: string, meta: string): void
// Edit existing token properties (admin only)
nmintEdit(mint: string, freeSupply: BigInt, admin: string, meta: string): void
// Transfer tokens
ntransfer(origin: string, destination: string, mint: string, amount: BigInt): void
Example Usage:
import {
NApp,
createExecutableFunctions,
nmint,
ntransfer
} from '@n1xyz/nts-compiler';
class TokenApp extends NApp {
createGameToken(): void {
nmint(
BigInt('1000000'), // 1M total supply
this.signer(), // Current user as admin
JSON.stringify({ // Metadata
name: 'GameCoin',
symbol: 'GAME'
})
);
this.log('Token creation initiated');
}
sendTokens(recipient: string, mintId: string, amount: string): void {
ntransfer(
this.signer(), // From current user
recipient, // To recipient
mintId, // Token ID
BigInt(amount) // Amount (must be BigInt)
);
this.log('Transfer completed');
}
}
Logging
// Log messages and data for debugging/auditing
log(...args: any[]): void
Example Usage:
class LoggingApp extends NApp {
processTransaction(data: any): void {
this.log('Processing transaction:', data);
try {
// Process logic here
this.log('Transaction successful', { status: 'success' });
} catch (error) {
this.log('Transaction failed:', error.message);
throw error;
}
}
}
Context Access
N1TS applications have access to transaction and app context through the NApp base class:
// Get transaction signer
this.signer(): string
// Get app admin
this.appAdmin(): string
// Get current app ID
this.appId(): string
// Get current timestamp
this.time(): number
Example Usage:
class ContextApp extends NApp {
// This is an internal helper method, NOT a transaction action
protected getTransactionInfo(): any {
return {
signer: this.signer(), // Who sent this transaction
appAdmin: this.appAdmin(), // Who manages this app
appId: this.appId(), // Current app identifier
timestamp: this.time() // When this is executing
};
}
requireAdmin(): void {
if (this.signer() !== this.appAdmin()) {
throw new Error('Admin access required');
}
}
createTimestampedEntry(data: any): void {
const entry = {
...data,
createdAt: this.time(),
createdBy: this.signer()
};
nwrite(`entry_${this.time()}`, JSON.stringify(entry), 'timestamped_data');
}
}
Field Naming Conventions
N1TS uses specific field naming conventions for system data:
Token Data Fields
// 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}`);
Protected Fields
Fields starting with _
are protected and can only be written by:
- System functions (like
nmint
,ntransfer
) - App admin using admin functions
- The field’s designated owner
class ProtectedFieldApp extends NApp {
// ✅ Regular fields - anyone can write (transaction action)
setPublicData(key: string, value: any): void {
nwrite(`public_${key}`, value);
}
// ❌ This will fail - protected fields require admin access (transaction action)
trySetProtectedData(): void {
try {
nwrite('_protected_field', 'value'); // Will throw error
} catch (error) {
this.log('Cannot write to protected field:', error.message);
}
}
}
State Management Patterns
Automatic Persistence
Properties assigned to this
are automatically persisted:
class StateApp extends NApp {
counter: number;
isActive: boolean;
init(): void {
this.counter = 0;
this.isActive = true;
}
increment(): void {
this.counter++; // Automatically saved
this.log('Counter incremented to:', this.counter);
}
}
Typed Collections with NMap
class CollectionApp extends NApp {
users = new NMap<UserProfile>(this, 'users');
scores = new NMap<number>(this, 'scores');
createUser(id: string, profile: UserProfile): void {
this.users.set(id, profile, 'user_created', id);
}
updateScore(userId: string, score: number): void {
this.scores.set(userId, score, 'score_update', userId);
}
// This is an internal helper method, NOT a transaction action
protected getUser(id: string): UserProfile | undefined {
return this.users.get(id);
}
}
Error Handling
N1TS provides comprehensive error handling and validation:
class SafeApp extends NApp {
safeTokenTransfer(
to: string,
mintId: string,
amount: string
): void {
try {
// Validation
if (!to || !mintId || !amount) {
throw new Error('Missing required parameters');
}
const amountBig = BigInt(amount);
if (amountBig <= 0) {
throw new Error('Amount must be positive');
}
// Note: In transaction actions, balance checks are handled by the system
// Execute transfer
ntransfer(this.signer(), to, mintId, amountBig);
this.log('Transfer successful');
} catch (error) {
this.log('Transfer failed:', error.message);
throw error; // Re-throw to fail the transaction
}
}
}
Best Practices
- Always use BigInt for token amounts to avoid precision loss
- Validate inputs before processing
- Use meaningful tags in nwrite calls for data organization
- Log important events for debugging and auditing
- Handle errors gracefully with try-catch blocks
- Check permissions before state modifications
- Use proper field naming conventions
This interface provides everything needed to build sophisticated smart contracts on N1TS while maintaining security and reliability.