Τα κτυπήματα NAV (Καθαρή Αξία Ενεργητικού) είναι καθορισμένες χρονικές στιγμές κατά τη διάρκεια της συνεδρίασης αγοράς, κατά τις οποίες αποτιμώνται οι μερίδες του αμοιβαίου κεφαλαίου και εκτελούνται οι εντολές. Τα παραδοσιακά αμοιβαία κεφάλαια έχουν συνήθως ένα μόνο ημερήσιο κτύπημα στο κλείσιμο της αγοράς, αλλά η ταχύτητα και το χαμηλό κόστος του Solana επιτρέπουν πολλαπλά ενδοημερήσια κτυπήματα, προσφέροντας στους επενδυτές μεγαλύτερη ευελιξία.
Τι είναι το NAV;
Ένα αμοιβαίο κεφάλαιο είναι ένα σύνολο περιουσιακών στοιχείων που κατέχουν από κοινού πολλοί επενδυτές. Το NAV (Καθαρή Αξία Ενεργητικού) είναι απλώς η τιμή μιας μερίδας σε ένα αμοιβαίο κεφάλαιο. Σκεφτείτε το ως εξής:
NAV = (Total Value of Everything in the Fund - liabilities) ÷ Number of Shares
Για παράδειγμα, εάν ένα αμοιβαίο κεφάλαιο κατέχει περιουσιακά στοιχεία αξίας $10 εκατομμυρίων και έχει 10 εκατομμύρια μερίδες σε κυκλοφορία, κάθε μερίδα αξίζει $1,00.
Γιατί έχει σημασία το NAV;
- Αγορά μερίδων (εγγραφή): Πληρώνετε το τρέχον NAV. Εάν το NAV είναι $1,02 και επενδύετε $102, λαμβάνετε 100 μερίδες.
- Πώληση μερίδων (εξαγορά): Λαμβάνετε το τρέχον NAV. Εάν εξαγοράσετε 100 μερίδες με NAV $1,02, λαμβάνετε $102.
Τα αμοιβαία κεφάλαια χρηματαγοράς διατηρούν συνήθως ένα σταθερό NAV γύρω στο $1,00, με μικρές διακυμάνσεις βάσει των δεδουλευμένων τόκων.
Το Πρόβλημα
Όταν επενδύετε σε ένα αμοιβαίο κεφάλαιο χρηματαγοράς, παραδοσιακά συμβαίνουν δύο πράγματα σε διαφορετικές χρονικές στιγμές:
- Υποβάλλετε εντολή → Αναμονή έως το κλείσιμο της αγοράς
- Υπολογισμός NAV → Έκδοση/εξαγορά μερίδων σε T+1 ή T+2
Αυτό δημιουργεί αρκετά προβλήματα:
- Καθυστερημένη εκτέλεση: Οι εντολές που υποβάλλονται το πρωί αναμένουν έως τις 4 μ.μ.
- Καθυστέρηση διακανονισμού: Τα χρήματά σας βρίσκονται σε εκκρεμότητα για 1-2 ημέρες
- Περιορισμένη ευελιξία: Μόνο μία ευκαιρία συναλλαγής ανά ημέρα
- Μη ενημερωμένη τιμολόγηση: Το NAV μπορεί να είναι παρωχημένο μεταξύ εντολής και εκτέλεσης
Η Λύση
Τα κτυπήματα NAV στο Solana επιτρέπουν πολλαπλά ημερήσια παράθυρα διακανονισμού με ατομική εκτέλεση. Οι εντολές τίθενται σε ουρά και εκτελούνται αμέσως σε κάθε χρονική στιγμή κτυπήματος με το τρέχον NAV.
┌─────────────────────────────────────────────────────────────┐│ 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 ││ │└─────────────────────────────────────────────────────────────┘
Αυτός ο οδηγός δείχνει πώς να υλοποιήσετε κτυπήματα NAV στο Solana χρησιμοποιώντας επεκτάσεις SPL Token 2022 για συμμορφούμενη έκδοση μερίδων αμοιβαίου κεφαλαίου και τυπικό USDC για διακανονισμό, χωρίς να γράψετε προσαρμοσμένα προγράμματα Rust.
Εκπαιδευτική Υλοποίηση Αναφοράς
Μπορείτε να χρησιμοποιήσετε τον πηγαίο κώδικα αυτής της υλοποίησης για να δοκιμάσετε κτυπήματα NAV τοπικά.
Αυτός ο οδηγός παρέχει μια υλοποίηση αναφοράς αποκλειστικά για εξερεύνηση και εκπαιδευτικούς σκοπούς. ΜΗΝ χρησιμοποιείτε αυτόν τον κώδικα απευθείας σε παραγωγικό περιβάλλον χωρίς:
- Πλήρεις ελέγχους ασφαλείας
- Κατάλληλα συστήματα διαχείρισης κλειδιών
- Έλεγχο κανονιστικής συμμόρφωσης
- Νομική συμβουλή
- Εκτεταμένες δοκιμές και τροποποιήσεις
Επισκόπηση Αρχιτεκτονικής
Το σύστημα κτυπημάτων NAV αποτελείται από τα παρακάτω βασικά στοιχεία:
- Token Μερίδας Αμοιβαίου Κεφαλαίου: SPL Token 2022 με μεταδεδομένα που αποθηκεύουν το NAV onchain
- Νόμισμα Διακανονισμού: Τυπικό USDC (υπάρχον SPL token)
- Διαχειριστής Αμοιβαίου Κεφαλαίου: Ενορχηστρώνει τα κτυπήματα μέσω εξουσιοδοτημένης αρχής
- Σύστημα Λίστας Επιτρεπόμενων: Ελέγχει ποιες διευθύνσεις μπορούν να κατέχουν μερίδες αμοιβαίου κεφαλαίου
Βασικές Αρχές Σχεδιασμού
- Χωρίς Προσαρμοσμένα Προγράμματα: Χρησιμοποιεί μόνο Token Extensions Program και τυπικό USDC
- Ατομικός Διακανονισμός: Μία μόνο συναλλαγή διασφαλίζει ότι η εγγραφή/εξαγορά ολοκληρώνεται ή αποτυγχάνει ως σύνολο
- Προεπιλεγμένη Κατάσταση Παγώματος: Οι μερίδες απαιτούν ρητή καταχώριση στη λίστα επιτρεπόμενων για κανονιστική συμμόρφωση
- Εξουσιοδοτημένη Αρχή: Ο διαχειριστής αμοιβαίου κεφαλαίου λειτουργεί χωρίς να αναλαμβάνει την επιμέλεια
- NAV Onchain: Η τρέχουσα τιμή αποθηκεύεται στα μεταδεδομένα token, δημόσια επαληθεύσιμη
Στάδιο 1: Ρύθμιση Αμοιβαίου Κεφαλαίου
- Δημιουργία token μερίδας αμοιβαίου κεφαλαίου με επεκτάσεις μεταδεδομένων
- Αρχικοποίηση NAV στα $1,00
- Ορισμός ημερήσιου προγράμματος κτυπημάτων
- Καταχώριση θεσμικών επενδυτών στη λίστα επιτρεπόμενων
Στάδιο 2: Περίοδος Προ-Κτυπήματος
- Οι επενδυτές υποβάλλουν αιτήματα εγγραφής/εξαγοράς
- Οι εντολές τίθενται σε ουρά για την επόμενη χρονική στιγμή κτυπήματος
- Το αμοιβαίο κεφάλαιο υπολογίζει προκαταρκτικό NAV
- Διενεργούνται έλεγχοι κινδύνου
Στάδιο 3: Εκτέλεση Κτυπήματος NAV
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)
Στάδιο 4: Μετά το Κτύπημα
- Δημιουργία επιβεβαιώσεων συναλλαγών
- Ενημέρωση σύνθεσης αμοιβαίου κεφαλαίου
- Προετοιμασία για το επόμενο κτύπημα
- Συμφωνία με θεματοφύλακα
Πρότυπο Εξουσιοδοτημένης Αρχής
Οι επενδυτές προ-εξουσιοδοτούν τον διαχειριστή αμοιβαίου κεφαλαίου να μετακινεί τα tokens τους, και στη συνέχεια ο διαχειριστής εκτελεί κατά τη στιγμή του κτυπήματος χωρίς να χρειάζονται υπογραφές επενδυτών:
┌──────────┐ delegates ┌────────────────────┐ delegates ┌──────────┐│ Investor │ ───────────────→ │ Fund Administrator │ ←────────────── │ Investor ││ A │ (USDC) │ (trusted) │ (shares) │ B │└──────────┘ │ │ └──────────┘│ executes strike ││ (atomic txns) │└────────────────────┘
Ροή Εγγραφής (USDC → Μερίδες Αμοιβαίου Κεφαλαίου)
Investor wants to invest $100,000 USDCCurrent NAV = $1.000234 per share────────────────────────────────────1. Investor approves $100,000 USDC delegation2. 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 investor3. Settlement complete in <1 second
Ροή Εξαγοράς (Μερίδες Αμοιβαίου Κεφαλαίου → USDC)
Investor wants to redeem 50,000 sharesCurrent NAV = $1.000234 per share────────────────────────────────────1. Investor approves 50,000 shares delegation2. 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 investor3. Settlement complete in <1 second
Γιατί το Solana για Κτυπήματα NAV;
Η αρχιτεκτονική του Solana παρέχει σημαντικά πλεονεκτήματα έναντι του παραδοσιακού διακανονισμού αμοιβαίων κεφαλαίων:
| Πτυχή | Παραδοσιακό (T+1) | Κτυπήματα NAV Solana |
|---|---|---|
| Λογική Διακανονισμού | Αντιπρόσωποι μεταβίβασης | Ατομική δέσμευση συναλλαγών |
| Χρόνος Διακανονισμού | 1-2 ημέρες | <1 δευτερόλεπτο |
| Κόστος Συναλλαγής | $50-200 | <$0,01 |
| Κτυπήματα Ανά Ημέρα | 1 | 4+ (ρυθμιζόμενο) |
| Κίνδυνος Παρωχημένων Τιμών | Υψηλός | Χαμηλότερος |
| Διαφάνεια NAV | Αναφορά τέλους ημέρας | Onchain, σε πραγματικό χρόνο |
Οι ατομικές συναλλαγές του Solana εξαλείφουν τους μεσάζοντες, παρέχοντας παράλληλα άμεσους, φθηνούς και ασφαλείς διακανονισμούς με διαφάνεια NAV onchain.
Token Αμοιβαίου Κεφαλαίου με Token Extensions
Το SPL Token 2022 παρέχει ισχυρές επεκτάσεις που επιτρέπουν τη συμμορφούμενη έκδοση μερίδων αμοιβαίου κεφαλαίου χωρίς προσαρμοσμένα προγράμματα:
Βασικές Επεκτάσεις για Μερίδες Αμοιβαίου Κεφαλαίου
- Επέκταση Προεπιλεγμένης Κατάστασης Λογαριασμού: Ορίζει όλους τους νέους token account σε παγωμένη κατάσταση από προεπιλογή, απαιτώντας ρητή καταχώριση στη λίστα επιτρεπόμενων (συμμόρφωση KYC/AML)
- Επέκταση Μεταδεδομένων: Αποθηκεύει NAV, πρόγραμμα κτυπημάτων και AUM onchain για διαφάνεια
Διαμόρφωση Αρχής
- Αρχή Έκδοσης: Διαχειριστής Αμοιβαίου Κεφαλαίου (ελέγχει την έκδοση μερίδων)
- Αρχή Παγώματος: Διαχειριστής Αμοιβαίου Κεφαλαίου (διαχειρίζεται τη λίστα επιτρεπόμενων)
- Αρχή Ενημέρωσης: Διαχειριστής Αμοιβαίου Κεφαλαίου (ενημερώνει τα μεταδεδομένα NAV)
Η προεπιλεγμένη κατάσταση παγώματος είναι κρίσιμη για την κανονιστική συμμόρφωση. Διασφαλίζει ότι μόνο ρητά καταχωρημένες (επαληθευμένες KYC) διευθύνσεις μπορούν να λαμβάνουν και να κατέχουν μερίδες αμοιβαίου κεφαλαίου.
Πεδία Μεταδεδομένων 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"}
Πλήρης Υλοποίηση Κτυπημάτων NAV
Ρύθμιση της Μηχανής Κτυπημάτων NAV
Σημειώστε ότι σε αυτόν τον οδηγό χρησιμοποιούμε τη βιβλιοθήκη @solana/kit για τη δημιουργία της Μηχανής Κτυπημάτων NAV. Μπορείτε επίσης να βρείτε υλοποιήσεις web3.js στον
πηγαίο κώδικα. Αρχικά, δημιουργήστε
την κεντρική κλάση μηχανής που χειρίζεται όλες τις λειτουργίες του αμοιβαίου κεφαλαίου:
/*** 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 extensionsconst 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 stateprivate currentNAV: number = 1.0;private totalAUM: number = 0;private totalSharesOutstanding: number = 0;private strikeSchedule: string[] = [];private lastStrikeTime: Date = new Date();// Order queueprivate 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-anyprivate 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 mintconst mint = await generateKeyPairSigner();const decimals = config.decimals ?? 6;// Define extensionsconst 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 metadataconst baseMintSize = getMintSize(extensions);// Create metadata object to calculate EXACT size using pack()// Note: PublicKey is only used here for size calculation, not for transaction buildingconst 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 lengthconst metadataExtensionOverhead = TYPE_SIZE + LENGTH_SIZE;// Add safety buffer for any encoding overheadconst totalSpace =baseMintSize + metadataLen + metadataExtensionOverhead + 100;// Get rent for total spaceconst 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 instructionsconst 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 instructionconst 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 instructionsconst 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 blockhashconst { value: latestBlockhash } = await this.client.rpc.getLatestBlockhash().send();// Build transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(issuer, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),(tx) =>appendTransactionMessageInstructions([createAccountIx,...preInitIxs,initMintIx,initMetadataIx,...updateFieldIxs],tx));// Sign and sendconst signedTransaction =await signTransactionMessageWithSigners(transactionMessage);const signature = getSignatureFromTransaction(signedTransaction);await this.sendTransaction(signedTransaction);// Update internal statethis.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;}
Δυναμικές Ενημερώσεις Μεταδεδομένων
Για μερίδες αμοιβαίων κεφαλαίων, τα πεδία currentNAV, lastStrikeTime και totalAUM ενημερώνονται onchain σε κάθε κτύπημα NAV χρησιμοποιώντας το
getUpdateTokenMetadataFieldInstruction. Αυτό παρέχει:
- Ορατότητα NAV σε πραγματικό χρόνο για όλους τους συμμετέχοντες
- Ίχνος ελέγχου όλων των ενημερώσεων NAV onchain
- Ενσωμάτωση με πρωτόκολλα DeFi που μπορούν να διαβάζουν μεταδεδομένα 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 instructionsconst 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 ATAconst [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 ATAconst 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 accountconst 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 NAVconst 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 accountsconst [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 investorconst 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 statethis.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 NAVconst 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 accountsconst [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 investorconst 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 statethis.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 NAVawait this.updateNAV(fundMint, newNAV);// 2. Process pending ordersconst 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 subscriptionsconsole.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 redemptionsconsole.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 ordersthis.pendingOrders = this.pendingOrders.filter((o) => o.status === "pending");// Print summaryconsole.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 strikeconst 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`);}}
Πλήρες Παράδειγμα Χρήσης
Ακολουθεί ένα πλήρες παράδειγμα που επιδεικνύει μια πλήρη ημέρα λειτουργιών κτυπήματος NAV:
/*** 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 comparisonconsole.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 keypairsconst 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 functionconst airdrop = airdropFactory({rpc: client.rpc,rpcSubscriptions: client.rpcSubscriptions});// Fund accounts with SOLconsole.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 participantsawait 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 ordersawait 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 NAVawait engine.executeStrike(fundMint, usdcMint, 1.0);// ─────────────────────────────────────────────────────────────────// STRIKE 2: 12:00 PM - Additional Subscription// ─────────────────────────────────────────────────────────────────printHeader("STRIKE 2: 12:00 PM - Additional Subscription");// Investor A adds moreawait 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 sharesawait engine.delegateSharesForRedemption(investorB, fundMint, 50);engine.queueOrder(investorB.address, "redeem", 50);// Execute strike at $1.02 NAVawait 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 updateawait 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 stateconst 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 demomain().catch((error) => {console.error("❌ Demo failed:", error);process.exit(1);});
Παράγοντες Παραγωγικού Περιβάλλοντος
Πριν από την ανάπτυξη σε παραγωγικό περιβάλλον, βεβαιωθείτε ότι αντιμετωπίζετε τα εξής:
- Ασφάλεια: Επαγγελματική διαχείριση κλειδιών, έλεγχοι multi-sig και ολοκληρωμένοι έλεγχοι ασφαλείας του κώδικα
- Κανονιστική Συμμόρφωση: Καταχώριση κινητών αξιών, συστήματα KYC/AML, περιορισμοί μεταβίβασης και απαιτούμενες αναφορές
- Διαχείριση Κλειδιών: Θεσμικές λύσεις θεματοφυλακής με κατάλληλες διαδικασίες δημιουργίας αντιγράφων ασφαλείας και ανάκτησης
- Επεξεργασία Συναλλαγών: Προτεραιότητα στις αμοιβές, λογική επαναδοκιμής, χειρισμός επιβεβαίωσης και πλεονασμός RPC
- Λειτουργίες Αμοιβαίου Κεφαλαίου: Υπολογισμός NAV από αξιόπιστες πηγές, ενσωμάτωση θεματοφύλακα και συμφωνία
- Παρακολούθηση: Παρακολούθηση σε πραγματικό χρόνο, ειδοποιήσεις και αυτοματοποιημένες κανονιστικές αναφορές
Επόμενα Βήματα
Αφού κατανοήσετε τα κτυπήματα NAV στο Solana:
-
Εξερευνήστε τις Token Extensions: Μάθετε για άλλες επεκτάσεις Token 2022 όπως για παράδειγμα το permanent delegate για πρόσθετες λειτουργίες συμμόρφωσης
-
Υλοποιήστε Token ACL: Για ενισχυμένη συμμόρφωση, εξετάστε τη χρήση Token ACL για τη διαχείριση της λίστας επιτρεπόμενων επενδυτών με αδειούχο απόψυξη. Αυτό επιτρέπει αυτο-εξυπηρέτηση επαλήθευσης KYC όπου οι επενδυτές μπορούν να ενεργοποιούν αυτόματα τους λογαριασμούς τους μετά την επιτυχή συμμόρφωση, μειώνοντας το λειτουργικό κόστος ενώ διατηρείται ο κανονιστικός έλεγχος.
-
Μελετήστε τον Διακανονισμό DvP: Δείτε τον οδηγό Delivery vs Payment για πρότυπα διακανονισμού κινητών αξιών
-
Αρχιτεκτονική Παραγωγικού Περιβάλλοντος: Σχεδιάστε ισχυρά συστήματα χρονοδιαγράμματος, παρακολούθησης και αποκατάστασης από καταστροφές
Συμπέρασμα
Το ατομικό μοντέλο συναλλαγών του Solana και οι Token Extensions παρέχουν ένα ισχυρό θεμέλιο για την υλοποίηση πολλαπλών ημερήσιων κτυπημάτων NAV για αμοιβαία κεφάλαια χρηματαγοράς. Ο συνδυασμός:
- Εγγενής ατομική εκτέλεση (χωρίς κίνδυνο έξυπνου συμβολαίου)
- Οριστικότητα σε χρόνο κάτω του δευτερολέπτου
- Σχεδόν μηδενικό κόστος συναλλαγών
- Διαφάνεια NAV onchain
- Ενσωματωμένες λειτουργίες συμμόρφωσης (παγωμένες προεπιλογές, μεταδεδομένα)
Καθιστά το Solana ιδανική πλατφόρμα για τον εκσυγχρονισμό της υποδομής διακανονισμού αμοιβαίων κεφαλαίων και την παροχή στους επενδυτές συχνότερων ευκαιριών συναλλαγής.
Ωστόσο, η μετάβαση από αυτή την εκπαιδευτική αναφορά στο παραγωγικό περιβάλλον απαιτεί σημαντική πρόσθετη εργασία σε θέματα ασφάλειας, συμμόρφωσης, θεματοφυλακής και λειτουργιών. Να συνεργάζεστε πάντα με εξειδικευμένους νομικούς, κανονιστικούς και τεχνικούς εμπειρογνώμονες όταν ασχολείστε με την τοκενοποίηση αμοιβαίων κεφαλαίων.
Is this page helpful?