NAV Strikes untuk Dana Pasar Uang di Solana

NAV (Net Asset Value) strikes adalah waktu-waktu tetap selama hari perdagangan ketika saham dana dihargai dan pesanan dieksekusi. Dana tradisional biasanya memiliki satu strike harian saat penutupan pasar, tetapi kecepatan dan biaya rendah Solana memungkinkan beberapa strike intraday, memberikan fleksibilitas lebih bagi investor.

Apa itu NAV?

Dana adalah kumpulan aset yang dimiliki bersama oleh banyak investor. NAV (Net Asset Value) adalah harga satu saham dalam sebuah dana. Bayangkan seperti ini:

NAV = (Total Value of Everything in the Fund - liabilities) ÷ Number of Shares

Misalnya, jika sebuah dana memegang aset senilai $10 juta dan memiliki 10 juta saham beredar, setiap saham bernilai $1,00.

Mengapa NAV penting?

  • Membeli saham (subscription): Anda membayar NAV saat ini. Jika NAV adalah $1,02 dan Anda menginvestasikan $102, Anda mendapatkan 100 saham.
  • Menjual saham (redemption): Anda menerima NAV saat ini. Jika Anda menebus 100 saham pada NAV $1,02, Anda mendapatkan $102.

Dana pasar uang biasanya mempertahankan NAV stabil sekitar $1,00, dengan fluktuasi kecil berdasarkan bunga yang diperoleh.

Permasalahan

Ketika Anda berinvestasi dalam dana pasar uang, secara tradisional dua hal terjadi pada waktu yang berbeda:

  1. Anda mengajukan pesanan → Tunggu penutupan pasar
  2. NAV dihitung → Saham diterbitkan/ditebus pada T+1 atau T+2

Hal ini menimbulkan beberapa masalah:

  • Eksekusi tertunda: Pesanan yang diajukan di pagi hari harus menunggu hingga pukul 16.00
  • Keterlambatan penyelesaian: Uang Anda berada dalam status limbo selama 1-2 hari
  • Fleksibilitas terbatas: Hanya satu kesempatan per hari untuk bertransaksi
  • Harga usang: NAV bisa menjadi usang antara pesanan dan eksekusi

Solusi

NAV Strikes di Solana memungkinkan beberapa jendela penyelesaian harian dengan eksekusi atomik. Pesanan mengantri dan dieksekusi secara instan pada setiap waktu strike dengan NAV saat ini.

┌─────────────────────────────────────────────────────────────┐
│ MULTIPLE DAILY NAV STRIKES │
├─────────────────────────────────────────────────────────────┤
│ │
│ 9:30 AM 12:00 PM 2:30 PM 4:00 PM │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │STRIKE│ │STRIKE│ │STRIKE│ │STRIKE│ │
│ │NAV=$1│ │NAV=$1│ │NAV=$1│ │NAV=$1│ │
│ │.0012 │ │.0015 │ │.0018 │ │.0020 │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ Process Process Process Process │
│ Orders Orders Orders Orders │
│ │
│ ✅ Atomic settlement at each strike │
│ ✅ NAV stored onchain in token metadata │
│ ✅ Less stale prices │
│ │
└─────────────────────────────────────────────────────────────┘

Panduan ini mendemonstrasikan cara mengimplementasikan NAV strikes di Solana menggunakan ekstensi SPL Token 2022 untuk penerbitan saham dana yang sesuai regulasi dan USDC standar untuk penyelesaian, semuanya tanpa menulis program Rust khusus.

Implementasi Referensi Edukasi

Anda dapat menggunakan kode sumber dari implementasi ini untuk mencoba NAV strikes secara lokal.

Panduan ini menyediakan implementasi referensi hanya untuk eksplorasi dan tujuan edukasi. JANGAN gunakan kode ini langsung di produksi tanpa:

  • Audit keamanan yang komprehensif
  • Sistem manajemen kunci yang tepat
  • Tinjauan kepatuhan regulasi
  • Konsultasi hukum
  • Pengujian dan modifikasi yang ekstensif

Gambaran Arsitektur

Sistem NAV Strikes terdiri dari komponen-komponen inti berikut:

  1. Token Saham Dana: SPL Token 2022 dengan metadata yang menyimpan NAV onchain
  2. Mata Uang Penyelesaian: USDC Standar (token SPL yang sudah ada)
  3. Administrator Dana: Mengatur strikes melalui otoritas yang didelegasikan
  4. Sistem Whitelist: Mengontrol alamat mana yang dapat memegang saham dana

Prinsip Desain Utama

  • Tanpa Program Khusus: Hanya menggunakan Token Extensions Program dan USDC standar
  • Penyelesaian Atomik: Satu transaksi memastikan subscription/redemption selesai atau gagal bersama-sama
  • Status Beku Default: Saham memerlukan whitelist eksplisit untuk kepatuhan regulasi
  • Otoritas Terdelegasi: Administrator dana beroperasi tanpa mengambil kustodi
  • NAV Onchain: Harga saat ini tersimpan dalam metadata token, dapat diverifikasi secara publik

Tahap 1: Pengaturan Dana

  1. Buat token Saham Dana dengan ekstensi metadata
  2. Inisialisasi NAV pada $1,00
  3. Tetapkan jadwal strike harian
  4. Tambahkan investor institusional ke whitelist

Tahap 2: Periode Pra-Strike

  1. Investor mengajukan permintaan subscription/redemption
  2. Pesanan mengantri untuk waktu strike berikutnya
  3. Dana menghitung NAV awal
  4. Pemeriksaan risiko dilakukan

Tahap 3: Eksekusi NAV Strike

At Strike Time (e.g., 14:30):
├── Calculate final NAV from underlying assets
├── Update onchain NAV in metadata
├── Process all pending subscriptions atomically
│ └── USDC → Fund Shares at exact NAV
├── Process all pending redemptions atomically
│ └── Fund Shares → USDC at exact NAV
└── Emit strike completion event
✅ STRIKE COMPLETE (all trades at same NAV)

Tahap 4: Pasca-Strike

  1. Hasilkan konfirmasi perdagangan
  2. Perbarui komposisi dana
  3. Persiapkan untuk strike berikutnya
  4. Rekonsiliasi dengan kustodian

Pola Otoritas Terdelegasi

Investor memberikan otorisasi terlebih dahulu kepada administrator dana untuk memindahkan token mereka, lalu administrator mengeksekusi pada waktu strike tanpa memerlukan tanda tangan investor:

┌──────────┐ delegates ┌────────────────────┐ delegates ┌──────────┐
│ Investor │ ───────────────→ │ Fund Administrator │ ←────────────── │ Investor │
│ A │ (USDC) │ (trusted) │ (shares) │ B │
└──────────┘ │ │ └──────────┘
│ executes strike │
│ (atomic txns) │
└────────────────────┘

Alur Subscription (USDC → Saham Dana)

Investor wants to invest $100,000 USDC
Current NAV = $1.000234 per share
────────────────────────────────────
1. Investor approves $100,000 USDC delegation
2. At strike time, atomic transaction:
├── Transfer $100,000 USDC from investor
├── Calculate shares: 100,000 / 1.000234 = 99,976.61
└── Mint 99,976.61 fund shares to investor
3. Settlement complete in <1 second

Alur Redemption (Saham Dana → USDC)

Investor wants to redeem 50,000 shares
Current NAV = $1.000234 per share
────────────────────────────────────
1. Investor approves 50,000 shares delegation
2. At strike time, atomic transaction:
├── Burn 50,000 fund shares from investor
├── Calculate USDC: 50,000 × 1.000234 = $50,011.70
└── Transfer $50,011.70 USDC to investor
3. Settlement complete in <1 second

Mengapa Solana untuk NAV Strikes?

Arsitektur Solana memberikan keunggulan signifikan dibandingkan penyelesaian dana tradisional:

AspekTradisional (T+1)Solana NAV Strikes
Logika PenyelesaianAgen transferPenggabungan transaksi atomik
Waktu Penyelesaian1-2 hari<1 detik
Biaya Transaksi$50-200<$0,01
Strike Per Hari14+ (dapat dikonfigurasi)
Risiko harga usangTinggiLebih rendah
Transparansi NAVLaporan akhir hariOnchain, real-time

Transaksi atomik Solana menghilangkan perantara sekaligus menyediakan penyelesaian yang instan, murah, dan aman dengan transparansi NAV onchain.

Token Dana dengan Token Extensions 2022

SPL Token 2022 menyediakan ekstensi yang powerful yang memungkinkan penerbitan saham dana yang sesuai regulasi tanpa program khusus:

Ekstensi Penting untuk Saham Dana

  1. Ekstensi Default Account State: Menetapkan semua token account baru ke status beku secara default, memerlukan whitelist eksplisit (kepatuhan KYC/AML)
  2. Ekstensi Metadata: Menyimpan NAV, jadwal strike, dan AUM onchain untuk transparansi

Konfigurasi Otoritas

  • Mint Authority: Administrator Dana (mengontrol penerbitan saham)
  • Freeze Authority: Administrator Dana (mengelola whitelist)
  • Update Authority: Administrator Dana (memperbarui metadata NAV)

Status beku default sangat penting untuk kepatuhan regulasi. Hal ini memastikan bahwa hanya alamat yang telah di-whitelist secara eksplisit (terverifikasi KYC) yang dapat menerima dan memegang saham dana.

Bidang Metadata Onchain

{
"name": "Example Money Market Fund",
"symbol": "EX-MMF",
"currentNAV": "1.000234",
"lastStrikeTime": "2024-01-15T14:30:00Z",
"strikeSchedule": "[\"09:30\", \"12:00\", \"14:30\", \"16:00\"]",
"totalAUM": "50000000.00",
"fundType": "Money Market Fund"
}

Implementasi NAV Strikes Lengkap

Menyiapkan NAV Strike Engine

Perhatikan bahwa dalam panduan ini kami menggunakan library @solana/kit untuk membuat NAV Strike Engine. Anda juga dapat menemukan implementasi web3.js dalam kode sumber. Pertama, buat kelas engine inti yang menangani semua operasi dana:

/**
* NAV Strikes Engine - Solana Kit Reference Implementation
*
* This implementation demonstrates NAV strikes for money market funds on Solana using:
* - SPL Token 2022 with Default Account State extension for fund shares
* - Standard USDC for settlement
* - Atomic transactions for subscription/redemption
* - Delegated authority pattern for fund administrator
*
* Built with @solana/kit (web3.js 2.0)
*
* ⚠️ IMPORTANT: This is a reference implementation for educational purposes.
* Do NOT use in production without proper audits and security reviews.
*/
import {
Address,
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
KeyPairSigner,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
Rpc,
RpcSubscriptions,
SolanaRpcApi,
SolanaRpcSubscriptionsApi
} from "@solana/kit";
import { getCreateAccountInstruction } from "@solana-program/system";
import {
TOKEN_PROGRAM_ADDRESS,
getApproveInstruction,
getTransferCheckedInstruction,
getMintToInstruction,
getBurnInstruction,
findAssociatedTokenPda,
getCreateAssociatedTokenInstruction
} from "@solana-program/token";
import {
TOKEN_2022_PROGRAM_ADDRESS,
getInitializeMintInstruction,
getInitializeTokenMetadataInstruction,
getUpdateTokenMetadataFieldInstruction,
tokenMetadataField,
getThawAccountInstruction,
getFreezeAccountInstruction,
AccountState,
getMintSize,
extension,
getPreInitializeInstructionsForMintExtensions,
fetchMaybeToken
} from "@solana-program/token-2022";
import { pack } from "@solana/spl-token-metadata";
import { PublicKey } from "@solana/web3.js";
// TLV (Type-Length-Value) sizes for Token-2022 extensions
const TYPE_SIZE = 2;
const LENGTH_SIZE = 2;
import type {
FundTokenConfig,
FundState,
SubscriptionParams,
RedemptionParams,
SubscriptionResult,
RedemptionResult,
StrikeResult,
StrikeOrder,
SolanaClient
} from "./types";
/**
* Cluster type for explorer links
*/
export type ClusterType = "mainnet-beta" | "devnet" | "testnet" | "localnet";
/**
* Generate Solana Explorer link for a transaction
*/
export function getExplorerLink(
signature: string,
cluster: ClusterType = "localnet"
): string {
const baseUrl = "https://explorer.solana.com/tx";
if (cluster === "localnet") {
return `${baseUrl}/${signature}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899`;
}
return `${baseUrl}/${signature}?cluster=${cluster}`;
}
/**
* Generate Solana Explorer link for an account/address
*/
export function getAddressExplorerLink(
addr: string,
cluster: ClusterType = "localnet"
): string {
const baseUrl = "https://explorer.solana.com/address";
if (cluster === "localnet") {
return `${baseUrl}/${addr}?cluster=custom&customUrl=http%3A%2F%2Flocalhost%3A8899`;
}
return `${baseUrl}/${addr}?cluster=${cluster}`;
}
/**
* Create a Solana client for Kit
*/
export async function createSolanaClient(
rpcUrl: string = "http://127.0.0.1:8899",
wsUrl: string = "ws://127.0.0.1:8900"
): Promise<SolanaClient> {
const rpc = createSolanaRpc(rpcUrl);
const rpcSubscriptions = createSolanaRpcSubscriptions(wsUrl);
const sendAndConfirmTransaction = sendAndConfirmTransactionFactory({
rpc,
rpcSubscriptions
});
return {
rpc,
rpcSubscriptions,
sendAndConfirmTransaction
};
}
/**
* NAV Strike Engine - Solana Kit Version
*
* Manages the lifecycle of a money market fund on Solana:
* - Creates fund tokens with Token 2022 extensions
* - Updates NAV at scheduled strike times
* - Processes subscription and redemption orders atomically
*/
export class NAVStrikeEngine {
private client: SolanaClient;
private fundAdministrator: KeyPairSigner;
private cluster: ClusterType;
// Fund state
private currentNAV: number = 1.0;
private totalAUM: number = 0;
private totalSharesOutstanding: number = 0;
private strikeSchedule: string[] = [];
private lastStrikeTime: Date = new Date();
// Order queue
private pendingOrders: StrikeOrder[] = [];
private orderCounter: number = 0;
constructor(
client: SolanaClient,
fundAdministrator: KeyPairSigner,
cluster: ClusterType = "localnet"
) {
this.client = client;
this.fundAdministrator = fundAdministrator;
this.cluster = cluster;
}
/**
* Helper to send and confirm transactions with correct typing
* Works around stricter types in @solana/kit v5+
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async sendTransaction(
signedTx: any,
commitment: "confirmed" | "finalized" = "confirmed"
): Promise<void> {
await this.client.sendAndConfirmTransaction(signedTx, { commitment });
}
/**
* Get explorer link for a transaction signature
*/
getTxExplorerLink(signature: string): string {
return getExplorerLink(signature, this.cluster);
}
/**
* Get explorer link for an address
*/
getAddressLink(addr: string): string {
return getAddressExplorerLink(addr, this.cluster);
}
/**
* Creates a fund share token using Token-2022 with Default Account State and Metadata extensions
* Fund shares are frozen by default and require whitelisting
* Metadata stores NAV and fund information onchain
*/
async createFundToken(
issuer: KeyPairSigner,
config: FundTokenConfig
): Promise<Address> {
console.log(
"\n╔══════════════════════════════════════════════════════════════╗"
);
console.log(
"║ NAV STRIKE - FUND CREATION (Kit) ║"
);
console.log(
"╚══════════════════════════════════════════════════════════════╝"
);
console.log(`\n🏗️ Creating fund token with Token-2022 + Metadata...`);
console.log(` Name: ${config.name}`);
console.log(` Symbol: ${config.symbol}`);
console.log(` Initial NAV: $${config.initialNAV.toFixed(6)}`);
console.log(` Strike Schedule: ${config.strikeSchedule.join(", ")}`);
// Generate new keypair for the mint
const mint = await generateKeyPairSigner();
const decimals = config.decimals ?? 6;
// Define extensions
const defaultAccountStateExtension = extension("DefaultAccountState", {
state: AccountState.Frozen
});
const metadataPointerExtension = extension("MetadataPointer", {
authority: this.fundAdministrator.address,
metadataAddress: mint.address
});
const extensions = [defaultAccountStateExtension, metadataPointerExtension];
// Calculate mint size without metadata
const baseMintSize = getMintSize(extensions);
// Create metadata object to calculate EXACT size using pack()
// Note: PublicKey is only used here for size calculation, not for transaction building
const metadataForSizing = {
mint: new PublicKey(mint.address),
name: config.name,
symbol: config.symbol,
uri: config.uri || "",
additionalMetadata: [
["icon-uri", "link to icon"], // Max NAV format
["currentNAV", "999999.999999"], // Max NAV format
["lastStrikeTime", "2099-12-31T23:59:59.999Z"], // Max ISO timestamp
["strikeSchedule", JSON.stringify(config.strikeSchedule)],
["totalAUM", "999999999999999.99"], // Max AUM (quadrillions)
["fundType", "Money Market Fund"]
] as [string, string][]
};
// Calculate exact metadata size using pack()
const metadataLen = pack(metadataForSizing).length;
// MetadataExtension TLV overhead: 2 bytes for type, 2 bytes for length
const metadataExtensionOverhead = TYPE_SIZE + LENGTH_SIZE;
// Add safety buffer for any encoding overhead
const totalSpace =
baseMintSize + metadataLen + metadataExtensionOverhead + 100;
// Get rent for total space
const mintRent = await this.client.rpc
.getMinimumBalanceForRentExemption(BigInt(totalSpace))
.send();
// Build create account instruction (with base size, but extra rent for metadata)
const createAccountIx = getCreateAccountInstruction({
payer: issuer,
newAccount: mint,
lamports: lamports(mintRent),
space: baseMintSize,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// Get extension initialization instructions
const preInitIxs = getPreInitializeInstructionsForMintExtensions(
mint.address,
extensions
);
// Initialize mint instruction (Token 2022)
const initMintIx = getInitializeMintInstruction({
mint: mint.address,
decimals,
mintAuthority: this.fundAdministrator.address,
freezeAuthority: this.fundAdministrator.address
});
// Initialize metadata instruction
const initMetadataIx = getInitializeTokenMetadataInstruction({
metadata: mint.address,
updateAuthority: this.fundAdministrator.address,
mint: mint.address,
mintAuthority: this.fundAdministrator,
name: config.name,
symbol: config.symbol,
uri: ""
});
// Build custom metadata field update instructions
const initialMetadata: [string, string][] = [
["currentNAV", config.initialNAV.toFixed(6)],
["lastStrikeTime", new Date().toISOString()],
["strikeSchedule", JSON.stringify(config.strikeSchedule)],
["totalAUM", "0.00"],
["fundType", "Money Market Fund"]
];
const updateFieldIxs = initialMetadata.map(([key, value]) =>
getUpdateTokenMetadataFieldInstruction({
metadata: mint.address,
updateAuthority: this.fundAdministrator,
field: tokenMetadataField("Key", [key]),
value
})
);
// Get latest blockhash
const { value: latestBlockhash } = await this.client.rpc
.getLatestBlockhash()
.send();
// Build transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(issuer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) =>
appendTransactionMessageInstructions(
[
createAccountIx,
...preInitIxs,
initMintIx,
initMetadataIx,
...updateFieldIxs
],
tx
)
);
// Sign and send
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
const signature = getSignatureFromTransaction(signedTransaction);
await this.sendTransaction(signedTransaction);
// Update internal state
this.currentNAV = config.initialNAV;
this.strikeSchedule = config.strikeSchedule;
this.lastStrikeTime = new Date();
console.log(`\n✅ Fund token created: ${mint.address}`);
console.log(` Mint Authority: ${this.fundAdministrator.address}`);
console.log(` Freeze Authority: ${this.fundAdministrator.address}`);
console.log(` Default State: FROZEN (requires whitelisting)`);
console.log(` ✨ Metadata: ON-CHAIN`);
console.log(` - NAV: $${config.initialNAV.toFixed(6)}`);
console.log(` - Schedule: ${config.strikeSchedule.join(", ")}`);
console.log(` 🔗 Token: ${this.getAddressLink(mint.address)}`);
console.log(` 🔗 Tx: ${this.getTxExplorerLink(signature)}`);
return mint.address;
}

Pembaruan Metadata Dinamis

Untuk saham dana, bidang currentNAV, lastStrikeTime, dan totalAUM diperbarui onchain pada setiap NAV strike menggunakan getUpdateTokenMetadataFieldInstruction. Ini memberikan:

  • Visibilitas NAV real-time bagi semua peserta
  • Jejak audit dari semua pembaruan NAV onchain
  • Integrasi dengan protokol DeFi yang dapat membaca metadata onchain
/**
* Updates NAV onchain in token metadata
*/
async updateNAV(fundMint: Address, newNAV: number): Promise<string> {
const previousNAV = this.currentNAV;
this.currentNAV = newNAV;
this.lastStrikeTime = new Date();
// Build update field instructions
const updateNavIx = getUpdateTokenMetadataFieldInstruction({
metadata: fundMint,
updateAuthority: this.fundAdministrator,
field: tokenMetadataField("Key", ["currentNAV"]),
value: newNAV.toFixed(6)
});
const updateTimeIx = getUpdateTokenMetadataFieldInstruction({
metadata: fundMint,
updateAuthority: this.fundAdministrator,
field: tokenMetadataField("Key", ["lastStrikeTime"]),
value: this.lastStrikeTime.toISOString()
});
const updateAumIx = getUpdateTokenMetadataFieldInstruction({
metadata: fundMint,
updateAuthority: this.fundAdministrator,
field: tokenMetadataField("Key", ["totalAUM"]),
value: this.totalAUM.toFixed(2)
});
const { value: latestBlockhash } = await this.client.rpc
.getLatestBlockhash()
.send();
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(this.fundAdministrator, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) =>
appendTransactionMessageInstructions(
[updateNavIx, updateTimeIx, updateAumIx],
tx
)
);
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
const signature = getSignatureFromTransaction(signedTransaction);
await this.sendTransaction(signedTransaction);
const navChange = ((newNAV - previousNAV) / previousNAV) * 100;
const changeSymbol = navChange >= 0 ? "▲" : "▼";
console.log(
` NAV Updated: $${previousNAV.toFixed(6)} → $${newNAV.toFixed(
6
)} (${changeSymbol}${Math.abs(navChange).toFixed(4)}%)`
);
console.log(` 🔗 Explorer: ${this.getTxExplorerLink(signature)}`);
return signature;
}
/**
* Whitelists an investor by creating their fund account and thawing it
*/
async whitelistInvestor(
fundMint: Address,
investor: Address,
payer: KeyPairSigner
): Promise<Address> {
console.log(`\n🔓 Whitelisting investor: ${investor.slice(0, 20)}...`);
// Find the ATA
const [ata] = await findAssociatedTokenPda({
mint: fundMint,
owner: investor,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
const maybeToken = await fetchMaybeToken(this.client.rpc, ata, {
commitment: "confirmed"
});
const { value: latestBlockhash } = await this.client.rpc
.getLatestBlockhash()
.send();
if (!maybeToken.exists) {
// Create ATA
const createAtaIx = getCreateAssociatedTokenInstruction({
payer,
owner: investor,
mint: fundMint,
ata,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
const createAtaMsg = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(payer, tx),
(tx) =>
setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([createAtaIx], tx)
);
const signedCreateAta =
await signTransactionMessageWithSigners(createAtaMsg);
await this.sendTransaction(signedCreateAta);
}
// Thaw the account
const thawIx = getThawAccountInstruction({
account: ata,
mint: fundMint,
owner: this.fundAdministrator
});
const thawMsg = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(payer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([thawIx], tx)
);
const signedThaw = await signTransactionMessageWithSigners(thawMsg);
await this.sendTransaction(signedThaw);
console.log(` Account: ${ata}`);
console.log(`✅ Investor whitelisted and account thawed`);
return ata;
}
/**
* Removes an investor from whitelist by freezing their account
*/
async removeFromWhitelist(
fundMint: Address,
investorFundAccount: Address,
payer: KeyPairSigner
): Promise<void> {
console.log(`\n🔒 Removing from whitelist: ${investorFundAccount}`);
const { value: latestBlockhash } = await this.client.rpc
.getLatestBlockhash()
.send();
const freezeIx = getFreezeAccountInstruction({
account: investorFundAccount,
mint: fundMint,
owner: this.fundAdministrator
});
const freezeMsg = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(payer, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([freezeIx], tx)
);
const signedFreeze = await signTransactionMessageWithSigners(freezeMsg);
await this.sendTransaction(signedFreeze);
console.log(`✅ Account frozen and removed from whitelist`);
}
/**
* Delegates USDC authority to fund administrator for subscription
*/
async delegateUSDCForSubscription(
investor: KeyPairSigner,
usdcMint: Address,
amount: number
): Promise<void> {
const [investorUSDC] = await findAssociatedTokenPda({
mint: usdcMint,
owner: investor.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS
});
console.log(`\n🤝 Delegating USDC for subscription...`);
console.log(` Amount: $${amount.toLocaleString()}`);
const { value: latestBlockhash } = await this.client.rpc
.getLatestBlockhash()
.send();
const approveIx = getApproveInstruction({
source: investorUSDC,
delegate: this.fundAdministrator.address,
owner: investor,
amount: BigInt(amount * 1e6)
});
const approveMsg = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(investor, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([approveIx], tx)
);
const signedApprove = await signTransactionMessageWithSigners(approveMsg);
await this.sendTransaction(signedApprove);
console.log(`✅ USDC delegation approved`);
}
/**
* Delegates fund shares authority to administrator for redemption
*/
async delegateSharesForRedemption(
investor: KeyPairSigner,
fundMint: Address,
shareAmount: number
): Promise<void> {
const [investorShares] = await findAssociatedTokenPda({
mint: fundMint,
owner: investor.address,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
console.log(`\n🤝 Delegating shares for redemption...`);
console.log(` Shares: ${shareAmount.toLocaleString()}`);
const { value: latestBlockhash } = await this.client.rpc
.getLatestBlockhash()
.send();
const approveIx = getApproveInstruction(
{
source: investorShares,
delegate: this.fundAdministrator.address,
owner: investor,
amount: BigInt(Math.floor(shareAmount * 1e6))
},
{ programAddress: TOKEN_2022_PROGRAM_ADDRESS }
);
const approveMsg = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(investor, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([approveIx], tx)
);
const signedApprove = await signTransactionMessageWithSigners(approveMsg);
await this.sendTransaction(signedApprove);
console.log(`✅ Share delegation approved`);
}
/**
* Executes atomic subscription: USDC → Fund Shares at current NAV
*/
async processSubscription(
params: SubscriptionParams
): Promise<SubscriptionResult> {
const { fundMint, usdcMint, investor, usdcAmount } = params;
// Calculate shares at current NAV
const sharesToMint = usdcAmount / this.currentNAV;
const shortAddr = `${investor.slice(0, 4)}...${investor.slice(-4)}`;
console.log(`\n⚡ Processing subscription...`);
console.log(` Investor: ${shortAddr}`);
console.log(` USDC Amount: $${usdcAmount.toLocaleString()}`);
console.log(` NAV: $${this.currentNAV.toFixed(6)}`);
console.log(` Shares to mint: ${sharesToMint.toFixed(6)}`);
// Get token accounts
const [investorUSDC] = await findAssociatedTokenPda({
mint: usdcMint,
owner: investor,
tokenProgram: TOKEN_PROGRAM_ADDRESS
});
const [fundUSDC] = await findAssociatedTokenPda({
mint: usdcMint,
owner: this.fundAdministrator.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS
});
const [investorShares] = await findAssociatedTokenPda({
mint: fundMint,
owner: investor,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
const { value: latestBlockhash } = await this.client.rpc
.getLatestBlockhash()
.send();
// Build atomic transaction with both instructions
// 1. Transfer USDC from investor to fund (using delegated authority)
const transferUSDCIx = getTransferCheckedInstruction({
source: investorUSDC,
mint: usdcMint,
destination: fundUSDC,
authority: this.fundAdministrator,
amount: BigInt(Math.floor(usdcAmount * 1e6)),
decimals: 6
});
// 2. Mint fund shares to investor
const mintSharesIx = getMintToInstruction(
{
mint: fundMint,
token: investorShares,
mintAuthority: this.fundAdministrator,
amount: BigInt(Math.floor(sharesToMint * 1e6))
},
{ programAddress: TOKEN_2022_PROGRAM_ADDRESS }
);
const txMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(this.fundAdministrator, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) =>
appendTransactionMessageInstructions([transferUSDCIx, mintSharesIx], tx) // <- Both happen at the same time
);
const signedTx = await signTransactionMessageWithSigners(txMessage);
const signature = getSignatureFromTransaction(signedTx);
await this.sendTransaction(signedTx);
// Update fund state
this.totalAUM += usdcAmount;
this.totalSharesOutstanding += sharesToMint;
console.log(`✅ SUBSCRIPTION SETTLED ATOMICALLY`);
console.log(` Shares issued: ${sharesToMint.toFixed(6)}`);
console.log(` New AUM: $${this.totalAUM.toLocaleString()}`);
console.log(` 🔗 Explorer: ${this.getTxExplorerLink(signature)}`);
return {
signature,
usdcAmount,
sharesIssued: sharesToMint,
executionNAV: this.currentNAV,
timestamp: new Date()
};
}
/**
* Executes atomic redemption: Fund Shares → USDC at current NAV
*/
async processRedemption(params: RedemptionParams): Promise<RedemptionResult> {
const { fundMint, usdcMint, investor, shareAmount } = params;
// Calculate USDC at current NAV
const usdcToPay = shareAmount * this.currentNAV;
const shortAddr = `${investor.slice(0, 4)}...${investor.slice(-4)}`;
console.log(`\n⚡ Processing redemption...`);
console.log(` Investor: ${shortAddr}`);
console.log(` Shares to redeem: ${shareAmount.toLocaleString()}`);
console.log(` NAV: $${this.currentNAV.toFixed(6)}`);
console.log(` USDC to pay: $${usdcToPay.toFixed(2)}`);
// Get token accounts
const [investorShares] = await findAssociatedTokenPda({
mint: fundMint,
owner: investor,
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
});
const [investorUSDC] = await findAssociatedTokenPda({
mint: usdcMint,
owner: investor,
tokenProgram: TOKEN_PROGRAM_ADDRESS
});
const [fundUSDC] = await findAssociatedTokenPda({
mint: usdcMint,
owner: this.fundAdministrator.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS
});
const { value: latestBlockhash } = await this.client.rpc
.getLatestBlockhash()
.send();
// Build atomic transaction with both instructions
// 1. Burn fund shares from investor (using delegated authority)
const burnSharesIx = getBurnInstruction(
{
account: investorShares,
mint: fundMint,
authority: this.fundAdministrator,
amount: BigInt(Math.floor(shareAmount * 1e6))
},
{ programAddress: TOKEN_2022_PROGRAM_ADDRESS }
);
// 2. Transfer USDC from fund to investor
const transferUSDCIx = getTransferCheckedInstruction({
source: fundUSDC,
mint: usdcMint,
destination: investorUSDC,
authority: this.fundAdministrator,
amount: BigInt(Math.floor(usdcToPay * 1e6)),
decimals: 6
});
const txMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(this.fundAdministrator, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) =>
appendTransactionMessageInstructions([burnSharesIx, transferUSDCIx], tx)
);
const signedTx = await signTransactionMessageWithSigners(txMessage);
const signature = getSignatureFromTransaction(signedTx);
await this.sendTransaction(signedTx);
// Update fund state
this.totalAUM -= usdcToPay;
this.totalSharesOutstanding -= shareAmount;
console.log(`✅ REDEMPTION SETTLED ATOMICALLY`);
console.log(` USDC paid: $${usdcToPay.toFixed(2)}`);
console.log(` New AUM: $${this.totalAUM.toLocaleString()}`);
console.log(` 🔗 Explorer: ${this.getTxExplorerLink(signature)}`);
return {
signature,
sharesRedeemed: shareAmount,
usdcPaid: usdcToPay,
executionNAV: this.currentNAV,
timestamp: new Date()
};
}
/**
* Queue an order for the next NAV strike
*/
queueOrder(
investor: Address,
orderType: "subscribe" | "redeem",
amount: number
): StrikeOrder {
const order: StrikeOrder = {
orderId: `ORD-${++this.orderCounter}`,
investor,
orderType,
amount,
strikeTime: this.getNextStrikeTime(),
status: "pending"
};
this.pendingOrders.push(order);
const shortAddr = `${investor.slice(0, 4)}...${investor.slice(-4)}`;
console.log(`\n📝 Order queued: ${order.orderId}`);
console.log(` Investor: ${shortAddr}`);
console.log(` Type: ${orderType}`);
console.log(
` Amount: ${
orderType === "subscribe"
? `$${amount.toLocaleString()} USDC`
: `${amount.toLocaleString()} shares`
}`
);
return order;
}
/**
* Execute NAV strike - update NAV and process all pending orders
*/
async executeStrike(
fundMint: Address,
usdcMint: Address,
newNAV: number
): Promise<StrikeResult> {
const strikeId = `STRIKE-${Date.now()}`;
const strikeTime = new Date();
console.log(
"\n╔══════════════════════════════════════════════════════════════╗"
);
console.log(
"║ NAV STRIKE EXECUTION ║"
);
console.log(
"╚══════════════════════════════════════════════════════════════╝"
);
console.log(` Strike ID: ${strikeId}`);
console.log(` Strike Time: ${strikeTime.toISOString()}`);
console.log(` New NAV: $${newNAV.toFixed(6)}`);
console.log(` Pending Orders: ${this.pendingOrders.length}`);
console.log(
"────────────────────────────────────────────────────────────────"
);
// 1. Update NAV
await this.updateNAV(fundMint, newNAV);
// 2. Process pending orders
const signatures: string[] = [];
let totalUSDCSubscribed = 0;
let totalSharesMinted = 0;
let totalSharesRedeemed = 0;
let totalUSDCPaid = 0;
let subscriptionsProcessed = 0;
let redemptionsProcessed = 0;
const subscriptions = this.pendingOrders.filter(
(o) => o.orderType === "subscribe" && o.status === "pending"
);
const redemptions = this.pendingOrders.filter(
(o) => o.orderType === "redeem" && o.status === "pending"
);
// Process subscriptions
console.log(`\n📥 Processing ${subscriptions.length} subscriptions...`);
for (const order of subscriptions) {
try {
const result = await this.processSubscription({
fundMint,
usdcMint,
investor: order.investor,
usdcAmount: order.amount
});
order.status = "executed";
signatures.push(result.signature);
totalUSDCSubscribed += result.usdcAmount;
totalSharesMinted += result.sharesIssued;
subscriptionsProcessed++;
} catch (error) {
console.error(` ❌ Order ${order.orderId} failed:`, error);
order.status = "failed";
}
}
// Process redemptions
console.log(`\n📤 Processing ${redemptions.length} redemptions...`);
for (const order of redemptions) {
try {
const result = await this.processRedemption({
fundMint,
usdcMint,
investor: order.investor,
shareAmount: order.amount
});
order.status = "executed";
signatures.push(result.signature);
totalSharesRedeemed += result.sharesRedeemed;
totalUSDCPaid += result.usdcPaid;
redemptionsProcessed++;
} catch (error) {
console.error(` ❌ Order ${order.orderId} failed:`, error);
order.status = "failed";
}
}
// Clear executed orders
this.pendingOrders = this.pendingOrders.filter(
(o) => o.status === "pending"
);
// Print summary
console.log(
"\n════════════════════════════════════════════════════════════════"
);
console.log(
" STRIKE SUMMARY "
);
console.log(
"════════════════════════════════════════════════════════════════"
);
console.log(` Subscriptions: ${subscriptionsProcessed} processed`);
console.log(` Total USDC In: $${totalUSDCSubscribed.toLocaleString()}`);
console.log(` Shares Minted: ${totalSharesMinted.toFixed(2)}`);
console.log(` Redemptions: ${redemptionsProcessed} processed`);
console.log(` Shares Burned: ${totalSharesRedeemed.toFixed(2)}`);
console.log(` Total USDC Out: $${totalUSDCPaid.toLocaleString()}`);
console.log(` New AUM: $${this.totalAUM.toLocaleString()}`);
console.log(
` Shares Outstanding: ${this.totalSharesOutstanding.toFixed(2)}`
);
console.log(
"════════════════════════════════════════════════════════════════\n"
);
return {
strikeId,
strikeTime,
nav: this.currentNAV,
subscriptionsProcessed,
totalUSDCSubscribed,
totalSharesMinted,
redemptionsProcessed,
totalSharesRedeemed,
totalUSDCPaid,
signatures
};
}
/**
* Get current fund state
*/
getFundState(fundMint: Address): FundState {
return {
fundMint,
currentNAV: this.currentNAV,
lastStrikeTime: this.lastStrikeTime,
totalAUM: this.totalAUM,
totalSharesOutstanding: this.totalSharesOutstanding
};
}
/**
* Get current NAV
*/
getCurrentNAV(): number {
return this.currentNAV;
}
/**
* Get pending orders
*/
getPendingOrders(): StrikeOrder[] {
return [...this.pendingOrders];
}
/**
* Calculate next strike time based on schedule
*/
getNextStrikeTime(): Date {
const now = new Date();
const currentTime = `${now.getHours().toString().padStart(2, "0")}:${now
.getMinutes()
.toString()
.padStart(2, "0")}`;
for (const strikeTime of this.strikeSchedule) {
if (strikeTime > currentTime) {
const [hours, minutes] = strikeTime.split(":");
const strikeDate = new Date(now);
strikeDate.setHours(parseInt(hours), parseInt(minutes), 0, 0);
return strikeDate;
}
}
// Next strike is tomorrow's first strike
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
const [hours, minutes] = this.strikeSchedule[0].split(":");
tomorrow.setHours(parseInt(hours), parseInt(minutes), 0, 0);
return tomorrow;
}
/**
* Airdrops SOL for testing
*/
async airdropSol(publicKey: Address, amount: number): Promise<void> {
console.log(
`\n💰 Airdropping ${amount} SOL to ${publicKey.slice(0, 20)}...`
);
const airdrop = airdropFactory({
rpc: this.client.rpc,
rpcSubscriptions: this.client.rpcSubscriptions
});
await airdrop({
recipientAddress: publicKey,
lamports: lamports(BigInt(amount * 1_000_000_000)),
commitment: "confirmed"
});
console.log(`✅ Airdrop complete`);
}
}

Contoh Penggunaan Lengkap

Berikut adalah contoh lengkap yang mendemonstrasikan satu hari penuh operasi NAV strike:

/**
* NAV Strikes Demo - Solana Kit Version
*
* Demonstrates multiple daily NAV strikes for a money market fund on Solana.
* Built with @solana/kit (web3.js 2.0)
*
* Run with: npm run demo:kit
* Requires: solana-test-validator running locally
*
* ⚠️ EDUCATIONAL REFERENCE ONLY - NOT FOR PRODUCTION USE
*/
import {
airdropFactory,
generateKeyPairSigner,
lamports,
KeyPairSigner,
Address
} from "@solana/kit";
import {
NAVStrikeEngine,
createSolanaClient,
getExplorerLink
} from "../nav-strike-engine";
import {
createTestUSDC,
mintTestUSDC,
getUSDCBalance,
getFundShareBalance
} from "../test-usdc";
import type { SolanaClient } from "../types";
/**
* Helper to print section headers
*/
function printHeader(title: string): void {
console.log("\n");
console.log(
"╔══════════════════════════════════════════════════════════════╗"
);
console.log(`║ ${title.padEnd(60)}║`);
console.log(
"╚══════════════════════════════════════════════════════════════╝"
);
}
/**
* Print final balances for all participants
*/
async function printBalances(
client: SolanaClient,
fundMint: Address,
usdcMint: Address,
participants: { name: string; address: Address }[]
): Promise<void> {
console.log(
"\n┌─────────────────────────────────────────────────────────────────┐"
);
console.log(
"│ FINAL BALANCES │"
);
console.log(
"├─────────────────────────────────────────────────────────────────┤"
);
for (const { name, address } of participants) {
const usdcBalance = await getUSDCBalance(client, usdcMint, address);
const shareBalance = await getFundShareBalance(client, fundMint, address);
console.log(
`│ ${name.padEnd(15)} USDC: $${usdcBalance
.toFixed(2)
.padStart(10)} │ Shares: ${shareBalance.toFixed(2).padStart(10)} │`
);
}
console.log(
"└─────────────────────────────────────────────────────────────────┘"
);
// Cost comparison
console.log(
"\n┌─────────────────────────────────────────────────────────────────┐"
);
console.log(
"│ COST COMPARISON │"
);
console.log(
"├─────────────────────────────────────────────────────────────────┤"
);
console.log(
"│ Traditional Solana NAV Strikes │"
);
console.log(
"│ ───────────────────────────────────────────────────────────── │"
);
console.log(
"│ Strikes/Day: 1 (4PM) 4+ (configurable)│"
);
console.log(
"│ Settlement: T+1/T+2 Instant │"
);
console.log(
"│ Pricing: Unknown til 4PM Exact NAV at strike │"
);
console.log(
"│ Compliance: Manual KYC/AML Onchain whitelist │"
);
console.log(
"│ Settlement Risk: High None │"
);
console.log(
"│ Audit Trail: Manual Blockchain │"
);
console.log(
"└─────────────────────────────────────────────────────────────────┘"
);
}
/**
* Main demo function
*/
async function main(): Promise<void> {
console.log(`
╔══════════════════════════════════════════════════════════════════╗
║ ║
║ ███╗ ██╗ █████╗ ██╗ ██╗ ███████╗████████╗██████╗ ║
║ ████╗ ██║██╔══██╗██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗ ║
║ ██╔██╗ ██║███████║██║ ██║ ███████╗ ██║ ██████╔╝ ║
║ ██║╚██╗██║██╔══██║╚██╗ ██╔╝ ╚════██║ ██║ ██╔══██╗ ║
║ ██║ ╚████║██║ ██║ ╚████╔╝ ███████║ ██║ ██║ ██║ ║
║ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ║
║ ║
║ NAV STRIKES - Solana Kit Reference Implementation ║
║ Built with @solana/kit (web3.js 2.0) ║
║ ║
╚══════════════════════════════════════════════════════════════════╝
`);
// ─────────────────────────────────────────────────────────────────
// SETUP: Connect to local validator
// ─────────────────────────────────────────────────────────────────
printHeader("SETUP: Connecting to Local Validator");
const client = await createSolanaClient();
console.log("✅ Connected to local validator (http://127.0.0.1:8899)");
// Create keypairs
const fundAdmin = await generateKeyPairSigner();
const investorA = await generateKeyPairSigner();
const investorB = await generateKeyPairSigner();
console.log(`\n👤 Fund Administrator: ${fundAdmin.address}`);
console.log(`👤 Investor A: ${investorA.address}`);
console.log(`👤 Investor B: ${investorB.address}`);
// Create airdrop function
const airdrop = airdropFactory({
rpc: client.rpc,
rpcSubscriptions: client.rpcSubscriptions
});
// Fund accounts with SOL
console.log("\n💰 Airdropping SOL to accounts...");
await airdrop({
recipientAddress: fundAdmin.address,
lamports: lamports(10_000_000_000n),
commitment: "confirmed"
});
console.log(" ✅ Fund Admin: 10 SOL");
await airdrop({
recipientAddress: investorA.address,
lamports: lamports(2_000_000_000n),
commitment: "confirmed"
});
console.log(" ✅ Investor A: 2 SOL");
await airdrop({
recipientAddress: investorB.address,
lamports: lamports(2_000_000_000n),
commitment: "confirmed"
});
console.log(" ✅ Investor B: 2 SOL");
// ─────────────────────────────────────────────────────────────────
// STEP 1: Create Test USDC
// ─────────────────────────────────────────────────────────────────
printHeader("STEP 1: Creating Test USDC");
const usdcMint = await createTestUSDC(client, fundAdmin, fundAdmin);
// Mint USDC to participants
await mintTestUSDC(
client,
usdcMint,
fundAdmin,
investorA.address,
500,
"Investor A"
);
await mintTestUSDC(
client,
usdcMint,
fundAdmin,
investorB.address,
300,
"Investor B"
);
await mintTestUSDC(
client,
usdcMint,
fundAdmin,
fundAdmin.address,
1000,
"Fund Admin"
);
// ─────────────────────────────────────────────────────────────────
// STEP 2: Create NAV Strike Engine & Fund Token
// ─────────────────────────────────────────────────────────────────
printHeader("STEP 2: Creating Fund & NAV Strike Engine");
const engine = new NAVStrikeEngine(client, fundAdmin, "localnet");
const fundMint = await engine.createFundToken(fundAdmin, {
name: "Example Money Market Fund",
symbol: "EX-MMF",
uri: "Link to of chain meta data",
initialNAV: 1.0,
strikeSchedule: ["09:30", "12:00", "14:30", "16:00"],
decimals: 6
});
// ─────────────────────────────────────────────────────────────────
// STEP 3: Whitelist Investors
// ─────────────────────────────────────────────────────────────────
printHeader("STEP 3: Whitelisting Investors (KYC/AML)");
await engine.whitelistInvestor(fundMint, investorA.address, fundAdmin);
await engine.whitelistInvestor(fundMint, investorB.address, fundAdmin);
// ─────────────────────────────────────────────────────────────────
// STRIKE 1: 9:30 AM - Initial Subscriptions
// ─────────────────────────────────────────────────────────────────
printHeader("STRIKE 1: 9:30 AM - Initial Subscriptions");
// Investors delegate USDC and queue orders
await engine.delegateUSDCForSubscription(investorA, usdcMint, 250);
engine.queueOrder(investorA.address, "subscribe", 250);
await engine.delegateUSDCForSubscription(investorB, usdcMint, 150);
engine.queueOrder(investorB.address, "subscribe", 150);
// Execute strike at $1.00 NAV
await engine.executeStrike(fundMint, usdcMint, 1.0);
// ─────────────────────────────────────────────────────────────────
// STRIKE 2: 12:00 PM - Additional Subscription
// ─────────────────────────────────────────────────────────────────
printHeader("STRIKE 2: 12:00 PM - Additional Subscription");
// Investor A adds more
await engine.delegateUSDCForSubscription(investorA, usdcMint, 100);
engine.queueOrder(investorA.address, "subscribe", 100);
// Execute strike at $1.01 NAV (slight gain from interest)
await engine.executeStrike(fundMint, usdcMint, 1.01);
// ─────────────────────────────────────────────────────────────────
// STRIKE 3: 2:30 PM - Partial Redemption
// ─────────────────────────────────────────────────────────────────
printHeader("STRIKE 3: 2:30 PM - Partial Redemption");
// Investor B redeems 50 shares
await engine.delegateSharesForRedemption(investorB, fundMint, 50);
engine.queueOrder(investorB.address, "redeem", 50);
// Execute strike at $1.02 NAV
await engine.executeStrike(fundMint, usdcMint, 1.02);
// ─────────────────────────────────────────────────────────────────
// STRIKE 4: 4:00 PM - End of Day
// ─────────────────────────────────────────────────────────────────
printHeader("STRIKE 4: 4:00 PM - End of Day");
// No new orders, just NAV update
await engine.executeStrike(fundMint, usdcMint, 1.03);
// ─────────────────────────────────────────────────────────────────
// FINAL: Print Balances
// ─────────────────────────────────────────────────────────────────
printHeader("FINAL RESULTS");
await printBalances(client, fundMint, usdcMint, [
{ name: "Fund Admin", address: fundAdmin.address },
{ name: "Investor A", address: investorA.address },
{ name: "Investor B", address: investorB.address }
]);
// Print fund state
const fundState = engine.getFundState(fundMint);
console.log("\n📊 Fund State:");
console.log(` Current NAV: $${fundState.currentNAV.toFixed(6)}`);
console.log(` Total AUM: $${fundState.totalAUM.toLocaleString()}`);
console.log(
` Shares Outstanding: ${fundState.totalSharesOutstanding.toFixed(2)}`
);
console.log(` Last Strike: ${fundState.lastStrikeTime.toISOString()}`);
console.log(
"\n✅ Demo complete! NAV Strikes with Solana Kit working correctly."
);
console.log(
" View transactions on Solana Explorer (local validator - links shown above)\n"
);
}
// Run the demo
main().catch((error) => {
console.error("❌ Demo failed:", error);
process.exit(1);
});

Pertimbangan Produksi

Sebelum melakukan deployment ke produksi, pastikan Anda menangani hal-hal berikut:

  1. Keamanan: Manajemen kunci profesional, kontrol multi-sig, dan audit keamanan komprehensif terhadap basis kode
  2. Kepatuhan Regulasi: Pendaftaran sekuritas, sistem KYC/AML, pembatasan transfer, dan pelaporan yang diwajibkan
  3. Manajemen Kunci: Solusi kustodi institusional dengan prosedur pencadangan dan pemulihan yang tepat
  4. Pemrosesan Transaksi: Biaya prioritas, logika percobaan ulang, penanganan konfirmasi, dan redundansi RPC
  5. Operasi Dana: Perhitungan NAV dari sumber otoritatif, integrasi kustodian, dan rekonsiliasi
  6. Pemantauan: Pelacakan real-time, peringatan, dan pelaporan regulasi otomatis

Langkah Selanjutnya

Setelah memahami NAV strikes di Solana:

  1. Jelajahi Token Extensions: Pelajari tentang ekstensi Token Extensions lainnya seperti contohnya permanent delegate untuk fitur kepatuhan tambahan

  2. Implementasikan Token ACL: Untuk kepatuhan yang lebih baik, pertimbangkan penggunaan Token ACL untuk mengelola whitelist investor dengan thaw tanpa izin. Ini memungkinkan verifikasi KYC mandiri di mana investor dapat mengaktifkan akun mereka secara otomatis setelah lulus pemeriksaan kepatuhan, mengurangi beban operasional sambil tetap mempertahankan kontrol regulasi.

  3. Pelajari Penyelesaian DvP: Lihat panduan Delivery vs Payment kami untuk pola penyelesaian sekuritas

  4. Arsitektur Produksi: Rancang penjadwalan yang andal, pemantauan, dan sistem pemulihan bencana

Kesimpulan

Model transaksi atomik Solana dan Token Extensions 2022 memberikan fondasi yang kuat untuk mengimplementasikan beberapa NAV strikes harian bagi dana pasar uang. Kombinasi dari:

  • Eksekusi atomik native (tanpa risiko smart contract)
  • Finalitas sub-detik
  • Biaya transaksi mendekati nol
  • Transparansi NAV onchain
  • Fitur kepatuhan bawaan (beku default, metadata)

Menjadikan Solana sebagai platform ideal untuk memodernisasi infrastruktur penyelesaian dana dan memberikan investor kesempatan perdagangan yang lebih sering.

Namun, beralih dari referensi edukasi ini ke produksi memerlukan pekerjaan tambahan yang signifikan di bidang keamanan, kepatuhan, kustodi, dan operasi. Selalu bekerja sama dengan para ahli hukum, regulasi, dan teknis yang berkualifikasi ketika menangani tokenisasi dana.

Is this page helpful?

© 2026 Yayasan Solana. Semua hak dilindungi.