Quick Reference
Quick reference for common N1TS patterns, functions, and best practices.
Basic App Structure
import {
NApp,
NMap,
createExecutableFunctions,
nmint,
ntransfer,
nmintEdit
} from '@n1xyz/nts-compiler';
class MyApp extends NApp {
// Property declarations (cannot initialize here!)
counter: number;
isActive: boolean;
// Typed collections (initialized directly)
users: NMap<UserProfile> = new NMap<UserProfile>(this, 'users');
balances: NMap<string> = new NMap<string>(this, 'balances');
// Initialize regular properties in init() method
init(): void {
this.counter = 0;
this.isActive = true;
}
// Public methods (exported as transaction actions)
doSomething(param: string): void {
// Your logic here
this.log('Action performed:', param);
// Note: Transaction actions cannot return values
}
// Private methods (not exported)
private validateInput(input: string): void {
if (!input) throw new Error('Input required');
}
}
// Export public methods
export const { doSomething } = createExecutableFunctions(MyApp);
Context Access
Transaction Context
// Get transaction signer
const user = this.signer();
// Get app admin
const admin = this.appAdmin();
// Get current app ID
const appId = this.appId();
Token Operations Cheat Sheet
Critical Issues with N1TS:
-
Transaction Actions Cannot Return Values: When using functions as transaction actions, they cannot have return values and should not use
await
. -
Cannot Initialize Class Properties Directly: You MUST declare properties and initialize them in an
init()
method.
// ❌ Wrong
class MyApp extends NApp {
counter: number = 0; // This doesn't work!
}
// ✅ Correct
class MyApp extends NApp {
counter: number;
init(): void {
this.counter = 0;
}
}
Available Functions
// Import required (from @n1xyz/nts-compiler):
// - nmint(totalSupply: BigInt, admin: string, meta: string): void
// - ntransfer(origin: string, destination: string, mint: string, amount: BigInt): void
// - nmintEdit(mint: string, freeSupply: BigInt, admin: string, meta: string): void
// Globally available (no import needed):
// - nread(fieldId: string, appId?: string): Promise<any> - INTERNAL USE ONLY
// - nwrite(fieldId: string, value: any, tag?: string, secondary_tag?: string): void
// - log(...args: any[]): void
Token Creation (Transaction Action)
// ✅ Correct: Transaction action usage
createToken(): void {
nmint(
BigInt('1000000'), // Total supply (BigInt required)
this.signer(), // Admin address
'Token Name' // Metadata (string or JSON)
);
this.log('Token creation initiated');
}
Token Transfers (Transaction Actions)
// ✅ Correct: Basic transfer action
transferTokens(recipient: string, mintId: string, amount: string): void {
ntransfer(
this.signer(), // From
recipient, // To
mintId, // Token ID
BigInt(amount) // Amount (BigInt required)
);
this.log('Transfer initiated');
}
Token Administration
// Edit token properties (admin only)
nmintEdit(
'mint_id', // Token ID
BigInt('500000'), // New free supply (BigInt required)
'new_admin_addr', // New admin address
'Updated metadata' // New metadata
);
Reading Token Data
// Get token balance (using helper function format)
const balance = await nread(`_balance_${mintId}_${address}`);
// Get token admin
const admin = await nread(`_mint_admin_${mintId}`);
// Get token metadata
const metadata = await nread(`_mint_meta_${mintId}`);
// Read from external app
const externalData = await nread('fieldId', 'external_app_id');
Data Operations
Writing Data
// Basic write
nwrite('my_field', 'my_value');
// Write with tags
nwrite('user_score', 100, 'game_data', this.signer());
// Write complex data
nwrite('user_profile', JSON.stringify({
name: 'Alice',
level: 5,
items: ['sword', 'shield']
}), 'profile', this.signer());
Reading Data
// Read from current app
const value = await nread('my_field');
// Read from specific app
const appData = await nread('field_name', 'specific_app_id');
// Read from external app (requires app ID)
const externalValue = await nread('field_name', 'external_app_id');
NMap Operations
class MyApp extends NApp {
users: NMap<UserProfile> = new NMap<UserProfile>(this, 'users');
init(): void {
// Initialize other properties here if needed
}
someMethod(): void {
// Set data with tag
this.users.set('user123', profileData, 'user_created', 'user123');
// Get data
const profile = this.users.get('user123');
// Check existence
if (this.users.has && this.users.has('user123')) {
// User exists (if has method is available)
}
// Remove data
this.users.delete('user123', 'user_deleted', 'user123');
}
}
Access Control Patterns
Admin Only
requireAdmin(): void {
if (this.signer() !== this.appAdmin()) {
throw new Error('Admin access required');
}
}
adminFunction(): void {
this.requireAdmin();
// Admin logic here
}
Owner Only
// This is an internal helper method, NOT a transaction action
protected async requireOwner(resourceId: string): Promise<void> {
const owner = await nread(`owner_${resourceId}`);
if (this.signer() !== owner) {
throw new Error('Owner access required');
}
}
Role-Based
class RoleBasedApp extends NApp {
roles: NMap<string> = new NMap<string>(this, 'user_roles');
init(): void {
// Initialize other properties here if needed
}
// This is an internal helper method, NOT a transaction action
protected requireRole(role: string): void {
const userRole = this.roles.get(this.signer());
if (userRole !== role && userRole !== 'admin') {
throw new Error(`${role} access required`);
}
}
}
Error Handling Patterns
Input Validation
validateInput(value: string, name: string): void {
if (!value || value.trim().length === 0) {
throw new Error(`${name} is required`);
}
if (value.length > 100) {
throw new Error(`${name} too long (max 100 chars)`);
}
}
validateAmount(amount: string): BigInt {
let amountBig: BigInt;
try {
amountBig = BigInt(amount);
} catch {
throw new Error('Invalid amount format');
}
if (amountBig <= 0) {
throw new Error('Amount must be positive');
}
return amountBig;
}
Try-Catch Patterns
safeOperation(): void {
try {
// Risky operation
this.performOperation();
this.log('Operation completed successfully');
} catch (error) {
this.log('Operation failed:', error.message);
throw error; // Re-throw to fail the transaction
}
}
State Management Patterns
Counters and Flags
class CounterApp extends NApp {
count: number;
isEnabled: boolean;
lastUser: string;
init(): void {
this.count = 0;
this.isEnabled = true;
this.lastUser = '';
}
increment(): void {
if (!this.isEnabled) {
throw new Error('Counter disabled');
}
this.count++;
this.lastUser = this.signer();
this.log('Counter incremented to:', this.count);
}
}
Collections
class CollectionsApp extends NApp {
// User management
users: NMap<UserProfile> = new NMap<UserProfile>(this, 'users');
userEmails: NMap<string> = new NMap<string>(this, 'emails');
// Financial data
balances: NMap<string> = new NMap<string>(this, 'balances');
transactions: NMap<TransactionData> = new NMap<TransactionData>(this, 'transactions');
// Configuration
settings: NMap<ConfigValue> = new NMap<ConfigValue>(this, 'settings');
permissions: NMap<boolean> = new NMap<boolean>(this, 'permissions');
init(): void {
// Initialize other properties here if needed
}
}
Timestamps and History
class AuditableApp extends NApp {
lastModified: number;
history: NMap<HistoryEntry> = new NMap<HistoryEntry>(this, 'history');
data: NMap<any> = new NMap<any>(this, 'data');
init(): void {
this.lastModified = 0;
}
updateWithHistory(key: string, value: any): void {
// Save history
const historyEntry: HistoryEntry = {
timestamp: this.time(),
user: this.signer(),
action: 'update',
key,
oldValue: this.data.get(key),
newValue: value
};
this.history.set(`${key}_${this.time()}`, historyEntry, 'history', key);
// Update data
this.data.set(key, value, 'data_update', key);
this.lastModified = this.time();
}
}
Common Utility Functions
Amount Formatting
// Convert token amount with decimals
parseTokenAmount(amount: string, decimals: number): BigInt {
const parts = amount.split('.');
const whole = parts[0] || '0';
const fractional = (parts[1] || '').padEnd(decimals, '0').slice(0, decimals);
return BigInt(whole + fractional);
}
// Format amount for display
formatTokenAmount(amount: string, decimals: number): string {
const amountBig = BigInt(amount);
const divisor = BigInt(10 ** decimals);
const whole = amountBig / divisor;
const fractional = amountBig % divisor;
return `${whole}.${fractional.toString().padStart(decimals, '0')}`;
}
Address Validation
validateAddress(address: string): void {
if (!address || address.length === 0) {
throw new Error('Address required');
}
// Add more specific validation based on your address format
if (address.length < 10) {
throw new Error('Invalid address format');
}
}
Rate Limiting
class RateLimitedApp extends NApp {
lastAction: NMap<number> = new NMap<number>(this, 'last_action');
init(): void {
// Initialize other properties here if needed
}
checkRateLimit(user: string, cooldownSeconds: number): void {
const lastTime = this.lastAction.get(user) || 0;
const timeSince = this.time() - lastTime;
if (timeSince < cooldownSeconds) {
const timeLeft = cooldownSeconds - timeSince;
throw new Error(`Please wait ${timeLeft} seconds`);
}
this.lastAction.set(user, this.time(), 'rate_limit', user);
}
}
Testing Patterns
Basic Test Setup
// tests/app.test.ts
import { compileCodeAndIdl } from '@n1xyz/nts-compiler';
describe('App Tests', () => {
const appCode = `
import { NApp, createExecutableFunctions } from '@n1xyz/nts-compiler';
class TestApp extends NApp {
count: number;
init(): void {
this.count = 0;
}
increment(): void {
this.count++;
this.log('Count is now:', this.count);
}
}
export const { increment } = createExecutableFunctions(TestApp);
`;
test('compiles successfully', () => {
const result = compileCodeAndIdl(appCode);
expect(result.code).toBeDefined();
expect(result.idl).toBeDefined();
});
});
Best Practices Checklist
Quick Checklist for N1TS Development
✅ Use BigInt for all token amounts
✅ Validate all user inputs
✅ Check permissions before state changes
✅ Use meaningful tags for NMap operations
✅ Log important events and errors
✅ Handle errors gracefully with try-catch
✅ Test your code before deployment
✅ Use descriptive variable and method names
✅ Keep methods focused and single-purpose
✅ Document complex business logic
Available Global Functions
// All these functions are globally available in N1TS apps:
// Data operations
await nread(fieldId: string, appId?: string): Promise<any>
await nread(fieldId: string, appId: string): Promise<any>
nwrite(fieldId: string, value: any, tag?: string, secondary_tag?: string): void
// Token operations (transaction actions - no return values)
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, args?: string): void
ntransferToCluster(origin: string, clusterId: string, mint: string, amount: BigInt, args?: string): void
// Logging
log(...args: any[]): void
// Context methods (available on NApp instances)
this.signer(): string
this.appAdmin(): string
this.appId(): string
this.time(): number
Data Types Reference
// Common interfaces
interface UserProfile {
name: string;
email: string;
createdAt: number;
active: boolean;
}
interface TransactionData {
from: string;
to: string;
amount: string;
timestamp: number;
memo?: string;
}
interface ConfigValue {
value: string | number | boolean;
updatedAt: number;
updatedBy: string;
}
interface HistoryEntry {
timestamp: number;
user: string;
action: string;
key: string;
oldValue: any;
newValue: any;
}
This quick reference covers the most commonly used patterns in N1TS development. Keep this handy while building your smart contracts!