Delivery vs Payment (DvP) na Solanie

Delivery vs Payment (DvP) to metoda rozliczania papierów wartościowych, która zapewnia, że przekazanie papierów wartościowych następuje jednocześnie z przekazaniem płatności. Eliminuje to ryzyko kontrahenta, gwarantując, że obie nogi transakcji wykonują się atomowo — albo obie się kończą, albo żadna z nich.

Problem

Kiedy kupujesz obligacje (np. papiery komercyjne), tradycyjnie muszą nastąpić dwie rzeczy:

  1. Wysyłasz pieniądze → Sprzedający
  2. Sprzedający wysyła obligacje → Tobie

Jeśli te zdarzenia zachodzą oddzielnie, pojawia się ryzyko — co jeśli zapłacisz, ale nigdy nie otrzymasz obligacji? Albo odwrotnie?

Rozwiązanie

(DvP) „Delivery vs Payment“ oznacza, że oba transfery następują w dokładnie tym samym momencie albo żaden z nich nie następuje. To jak swap, w którym obie strony wymieniają się jednocześnie.

┌─────────────────────────────────────────────────────┐
│ ONE ATOMIC TRANSACTION │
├─────────────────────────────────────────────────────┤
│ │
│ Investor ──── $95,000 USDC ────→ Issuer │
│ │
│ Issuer ─────── 100 Bonds ──────→ Investor │
│ │
│ ✅ Both happen together, or neither happens │
│ │
└─────────────────────────────────────────────────────┘

Ten przewodnik pokazuje, jak zaimplementować kompletny przepływ pracy DvP na Solanie, używając rozszerzeń SPL Token 2022 do emisji zgodnych z przepisami obligacji oraz standardowego USDC do rozliczeń — bez pisania własnych programów w Rust.

Referencyjna implementacja edukacyjna

Możesz skorzystać z kodu źródłowego tej implementacji, aby wypróbować DvP lokalnie.

Ten przewodnik dostarcza referencyjnej implementacji wyłącznie do celów eksploracyjnych i edukacyjnych. NIE używaj tego kodu bezpośrednio w środowisku produkcyjnym bez:

  • Kompleksowych audytów bezpieczeństwa
  • Właściwych systemów zarządzania kluczami
  • Przeglądu zgodności z przepisami
  • Konsultacji prawnych
  • Rozległych testów i modyfikacji

Dlaczego Solana do DvP?

Architektura Solany zapewnia znaczące przewagi nad tradycyjnym rozliczaniem papierów wartościowych:

AspektTradycyjne (T+2)Solana
Logika rozliczaniaIzby rozliczenioweAtomowe grupowanie transakcji
Czas rozliczania2 dni<1 sekunda
Koszt transakcji50–500 USD<0,01 USD
Ryzyko kontrahentaWysokie (pośrednicy)Zerowe (atomowa egzekucja)
FinalizacjaKoniec dnia~400 ms

Atomowe grupowanie transakcji Solany eliminuje pośredników, zapewniając natychmiastowe, tanie i bezpieczne rozliczenia.

Przegląd architektury

System DvP składa się z następujących kluczowych komponentów:

  1. Token obligacji (papier komercyjny): SPL Token 2022 z Token Extensions
  2. Waluta rozliczeniowa: Standardowy USDC (istniejący token SPL)
  3. Agent rozliczeniowy: Organizuje atomowe swapy poprzez delegowane uprawnienia
  4. System białej listy: Kontroluje, które adresy mogą posiadać obligacje

Kluczowe zasady projektowania

  • Brak własnych programów: Używa wyłącznie Token Extensions SPL Token 2022 i standardowego USDC
  • Atomowe rozliczenie: Pojedyncza transakcja gwarantuje, że oba transfery zakończą się sukcesem lub niepowodzeniem razem
  • Domyślny stan zamrożenia: Obligacje wymagają jawnego wpisania na białą listę w celu zapewnienia zgodności z przepisami
  • Delegowane uprawnienia: Agent rozliczeniowy koordynuje działania bez przejmowania pieczy nad aktywami
  • Komunikacja stanu sieci: Nie jest wymagana łączność API punkt-punkt
┌─────────────────────────────────────────────────────┐
│ DvP Settlement Flow │
├─────────────────────────────────────────────────────┤
│ │
│ 1. Bond Creation (Token-2022 + Extensions) │
│ └─> Default State: FROZEN │
│ └─> Freeze Authority: Settlement Agent/Issuer │
│ └─> Token metadata: Bond information │
│ │
│ 2. Whitelist Participants │
│ └─> Whitelist issuer, mint bonds │
│ └─> Whitelist investor for trading │
│ │
│ 3. Delegate Authority to settlement agent │
│ ├─> Issuer delegates bonds │
│ └─> Investor delegates USDC │
│ │
│ 4. Atomic Settlement │
│ ├─> Transfer bonds: Issuer → Investor │
│ └─> Transfer USDC: Investor → Issuer │
│ (Both or neither - atomic) │
│ │
└─────────────────────────────────────────────────────┘

Zaufana strona, która organizuje transakcję. Obie strony delegują uprawnienia agentowi rozliczeniowemu, który następnie wykonuje atomowy swap.

┌─────────┐ delegates ┌──────────────────┐ delegates ┌──────────┐
│ Issuer │ ─────────────────→ │ Settlement Agent │ ←──────────────── │ Investor │
└─────────┘ (bonds) │ (trusted) │ (USDC) └──────────┘
│ │
│ executes atomic │
│ transaction │
└──────────────────┘

Token obligacji z Token Extensions Token 2022

SPL Token 2022 dostarcza zaawansowane rozszerzenia umożliwiające emisję zgodnych z przepisami papierów wartościowych bez własnych programów:

Niezbędne rozszerzenia dla obligacji

  1. Rozszerzenie domyślnego stanu konta: Ustawia wszystkie nowe token account na domyślnie zamrożone, wymagając jawnego wpisania na białą listę
  2. Rozszerzenie metadanych: Przechowuje szczegóły obligacji onchain (ISIN, data zapadalności, oprocentowanie kuponu itp.)
  3. Stały delegat (opcjonalnie): Umożliwia autoryzowane odzyskanie lub clawback, jeśli wymaga tego regulacja

Konfiguracja uprawnień

  • Uprawnienie do mintowania: Emitent (kontroluje tworzenie podaży)
  • Uprawnienie do zamrażania: Agent rozliczeniowy (zarządza białą listą)
  • Uprawnienie do aktualizacji: Agent rozliczeniowy (może aktualizować metadane)

Domyślny stan zamrożenia jest kluczowy dla zgodności z przepisami. Zapewnia, że tylko jawnie umieszczone na białej liście adresy mogą odbierać i posiadać papiery wartościowe, spełniając wymogi KYC/AML.

Kompletna implementacja DvP

Konfiguracja silnika DvP

Najpierw utwórz klasę głównego silnika DvP obsługującą wszystkie operacje:

import {
Connection,
Keypair,
PublicKey,
Transaction,
SystemProgram,
sendAndConfirmTransaction,
LAMPORTS_PER_SOL
} from "@solana/web3.js";
import {
approve,
thawAccount,
freezeAccount,
getAccount,
getAssociatedTokenAddress,
getOrCreateAssociatedTokenAccount,
createTransferCheckedInstruction,
TOKEN_2022_PROGRAM_ID,
TOKEN_PROGRAM_ID,
ExtensionType,
getMintLen,
createInitializeMintInstruction,
createInitializeDefaultAccountStateInstruction,
createInitializeMetadataPointerInstruction,
AccountState,
LENGTH_SIZE,
TYPE_SIZE
} from "@solana/spl-token";
import {
pack,
createInitializeInstruction,
createUpdateFieldInstruction,
type TokenMetadata
} from "@solana/spl-token-metadata";
interface BondTokenConfig {
name: string;
symbol: string;
decimals: number;
maturityDate: Date;
couponRate: number;
isin?: string;
description?: string;
}
interface DvPParams {
bondMint: PublicKey;
usdcMint: PublicKey;
bondAmount: number;
usdcAmount: number;
issuer: PublicKey;
investor: PublicKey;
}
interface DvPResult {
signature: string;
bondAmount: number;
usdcAmount: number;
timestamp: Date;
bondsSent: boolean;
usdcReceived: boolean;
}
/**
* DvP Engine - Reference Implementation
*
* This implementation demonstrates Delivery vs Payment (DvP) on Solana using:
* - SPL Token 2022 with Default Account State extension for bonds
* - Standard USDC for settlement
* - Atomic transactions for settlement
* - Delegated authority pattern for settlement agent
*
* ⚠️ IMPORTANT: This is a reference implementation for educational purposes.
* Do NOT use in production without proper audits and security reviews.
*/
export class DvPEngine {
private connection: Connection;
private settlementAgent: Keypair;
constructor(connection: Connection, settlementAgent: Keypair) {
this.connection = connection;
this.settlementAgent = settlementAgent;
}
/**
* Creates a bond token using Token-2022 with Default Account State and Metadata extensions
* Bonds are frozen by default and require whitelisting
* Metadata is stored onchain using the TokenMetadata extension
*/
async createBondToken(
issuer: Keypair,
config: BondTokenConfig
): Promise<PublicKey> {
console.log("\n🏗️ Creating bond token with Token-2022 + Metadata...");
console.log(` Name: ${config.name}`);
console.log(` Symbol: ${config.symbol}`);
console.log(` Coupon Rate: ${config.couponRate}%`);
console.log(
` Maturity: ${config.maturityDate.toISOString().split("T")[0]}`
);
// Generate new keypair for the mint
const mintKeypair = Keypair.generate();
// Create the metadata object to get EXACT size
const metadata: TokenMetadata = {
mint: mintKeypair.publicKey,
name: config.name,
symbol: config.symbol,
uri: config.description || "",
additionalMetadata: [
["couponRate", config.couponRate.toString()],
["maturityDate", config.maturityDate.toISOString()],
["isin", config.isin || ""]
]
};
// Size of metadata using pack() - this gives us the EXACT size
const metadataLen = pack(metadata).length;
// Size of MetadataExtension: 2 bytes for type, 2 bytes for length
const metadataExtension = TYPE_SIZE + LENGTH_SIZE;
// Calculate space for mint with extensions (without metadata)
const extensions = [
ExtensionType.DefaultAccountState,
ExtensionType.MetadataPointer
];
const spaceWithoutMetadataExtension = getMintLen(extensions);
// Calculate rent for FULL space (mint + metadata + TLV overhead)
const lamports = await this.connection.getMinimumBalanceForRentExemption(
spaceWithoutMetadataExtension + metadataLen + metadataExtension
);
// Build transaction following the official docs pattern
const transaction = new Transaction().add(
// 1. Create account with just base space, but rent for full space
SystemProgram.createAccount({
fromPubkey: issuer.publicKey,
newAccountPubkey: mintKeypair.publicKey,
space: spaceWithoutMetadataExtension, // Just base space
lamports, // But rent for full space (includes metadata + TLV)
programId: TOKEN_2022_PROGRAM_ID
}),
// 2. Initialize metadata pointer (before mint!)
createInitializeMetadataPointerInstruction(
mintKeypair.publicKey,
issuer.publicKey, // authority
mintKeypair.publicKey, // metadata address (self)
TOKEN_2022_PROGRAM_ID
),
// 3. Initialize default account state (frozen)
createInitializeDefaultAccountStateInstruction(
mintKeypair.publicKey,
AccountState.Frozen,
TOKEN_2022_PROGRAM_ID
),
// 4. Initialize mint
createInitializeMintInstruction(
mintKeypair.publicKey,
config.decimals,
issuer.publicKey, // mint authority
this.settlementAgent.publicKey, // freeze authority
TOKEN_2022_PROGRAM_ID
),
// 5. Initialize metadata
createInitializeInstruction({
programId: TOKEN_2022_PROGRAM_ID,
mint: mintKeypair.publicKey,
metadata: mintKeypair.publicKey,
name: config.name,
symbol: config.symbol,
uri: config.description || "",
mintAuthority: issuer.publicKey,
updateAuthority: this.settlementAgent.publicKey
})
);
// 6. Add custom metadata fields
for (const [field, value] of metadata.additionalMetadata) {
if (value) {
transaction.add(
createUpdateFieldInstruction({
programId: TOKEN_2022_PROGRAM_ID,
metadata: mintKeypair.publicKey,
updateAuthority: this.settlementAgent.publicKey,
field: field,
value: value
})
);
}
}
// Send transaction
await sendAndConfirmTransaction(
this.connection,
transaction,
[issuer, mintKeypair, this.settlementAgent],
{ commitment: "confirmed" }
);
console.log(`✅ Bond token created: ${mintKeypair.publicKey.toBase58()}`);
console.log(` Mint Authority: ${issuer.publicKey.toBase58()}`);
console.log(
` Freeze Authority: ${this.settlementAgent.publicKey.toBase58()}`
);
console.log(
` Update Authority: ${this.settlementAgent.publicKey.toBase58()}`
);
console.log(` Default State: FROZEN (requires whitelisting)`);
console.log(` ✨ Metadata: ON-CHAIN`);
return mintKeypair.publicKey;
}
/**
* Whitelists a participant by creating their bond account and thawing it
*/
async whitelist(
bondMint: PublicKey,
participant: PublicKey,
payer: Keypair
): Promise<PublicKey> {
console.log(`\n🔓 Whitelisting participant: ${participant.toBase58()}`);
// Get or create token account (will be frozen by default if new)
const bondAccount = await getOrCreateAssociatedTokenAccount(
this.connection,
payer,
bondMint,
participant,
false,
"confirmed",
{ commitment: "confirmed" },
TOKEN_2022_PROGRAM_ID
);
console.log(` Account: ${bondAccount.address.toBase58()}`);
// Only thaw if the account is frozen
if (bondAccount.isFrozen) {
await thawAccount(
this.connection,
this.settlementAgent,
bondAccount.address,
bondMint,
this.settlementAgent,
[],
{ commitment: "confirmed" },
TOKEN_2022_PROGRAM_ID
);
console.log(`✅ Participant whitelisted and account thawed`);
} else {
console.log(
`✅ Participant already whitelisted (account was not frozen)`
);
}
return bondAccount.address;
}
/**
* Removes an investor from whitelist by freezing their account
*/
async removeFromWhitelist(
bondMint: PublicKey,
investorBondAccount: PublicKey
): Promise<void> {
console.log(
`\n🔒 Removing from whitelist: ${investorBondAccount.toBase58()}`
);
await freezeAccount(
this.connection,
this.settlementAgent,
investorBondAccount,
bondMint,
this.settlementAgent,
[],
{ commitment: "confirmed" },
TOKEN_2022_PROGRAM_ID
);
console.log(`✅ Account frozen and removed from whitelist`);
}
/**
* Delegates authority to settlement agent for a token account
*/
async delegateAuthority(
owner: Keypair,
tokenAccount: PublicKey,
amount: number,
decimals: number,
programId: PublicKey
): Promise<void> {
const amountWithDecimals = amount * Math.pow(10, decimals);
console.log(`\n🤝 Delegating authority...`);
console.log(` Account: ${tokenAccount.toBase58()}`);
console.log(` Amount: ${amount}`);
console.log(` Delegate: ${this.settlementAgent.publicKey.toBase58()}`);
await approve(
this.connection,
owner,
tokenAccount,
this.settlementAgent.publicKey,
owner.publicKey,
amountWithDecimals,
[],
{ commitment: "confirmed" },
programId
);
console.log(`✅ Authority delegated`);
}
/**
* Executes atomic DvP settlement
* Both bond and USDC transfers happen in a single transaction
*/
async executeDvP(params: DvPParams): Promise<DvPResult> {
console.log(`\n⚡ Executing atomic DvP settlement...`);
console.log(` Bonds: ${params.bondAmount}`);
console.log(` USDC: ${params.usdcAmount}`);
console.log(` Issuer: ${params.issuer.toBase58()}`);
console.log(` Investor: ${params.investor.toBase58()}`);
// Get all associated token account addresses (deterministically)
const issuerBondAccount = await getAssociatedTokenAddress(
params.bondMint,
params.issuer,
false, // allowOwnerOffCurve
TOKEN_2022_PROGRAM_ID
);
const investorBondAccount = await getAssociatedTokenAddress(
params.bondMint,
params.investor,
false,
TOKEN_2022_PROGRAM_ID
);
const investorUSDCAccount = await getAssociatedTokenAddress(
params.usdcMint,
params.investor,
false,
TOKEN_PROGRAM_ID
);
const issuerUSDCAccount = await getAssociatedTokenAddress(
params.usdcMint,
params.issuer,
false,
TOKEN_PROGRAM_ID
);
// Build atomic transaction
const transaction = new Transaction();
// Add bond transfer instruction (Issuer → Investor)
transaction.add(
this.createTransferCheckedIx(
issuerBondAccount,
params.bondMint,
investorBondAccount,
params.bondAmount,
0, // bonds have 0 decimals
TOKEN_2022_PROGRAM_ID
)
);
// Add USDC transfer instruction (Investor → Issuer)
transaction.add(
this.createTransferCheckedIx(
investorUSDCAccount,
params.usdcMint,
issuerUSDCAccount,
params.usdcAmount * 1e6, // USDC has 6 decimals
6,
TOKEN_PROGRAM_ID
)
);
// Send atomic transaction
console.log(`\n📡 Sending atomic transaction...`);
const signature = await sendAndConfirmTransaction(
this.connection,
transaction,
[this.settlementAgent],
{ commitment: "confirmed" }
);
console.log(`✅ DvP SETTLED ATOMICALLY`);
console.log(` Signature: ${signature}`);
console.log(` Bonds transferred: ${params.bondAmount}`);
console.log(` USDC transferred: ${params.usdcAmount}`);
return {
signature,
bondAmount: params.bondAmount,
usdcAmount: params.usdcAmount,
timestamp: new Date(),
bondsSent: true,
usdcReceived: true
};
}
/**
* Helper to create a transferChecked instruction using delegated authority
*/
private createTransferCheckedIx(
source: PublicKey,
mint: PublicKey,
destination: PublicKey,
amount: number,
decimals: number,
programId: PublicKey
) {
return createTransferCheckedInstruction(
source,
mint,
destination,
this.settlementAgent.publicKey, // Settlement agent acts via delegation
amount,
decimals,
[],
programId
);
}
/**
* Gets account information for inspection
*/
async getAccountInfo(tokenAccount: PublicKey, programId: PublicKey) {
const account = await getAccount(
this.connection,
tokenAccount,
"confirmed",
programId
);
return {
address: tokenAccount,
mint: account.mint,
owner: account.owner,
amount: account.amount,
isFrozen: account.isFrozen
};
}
/**
* Airdrops SOL for testing (devnet/testnet only)
*/
async airdropSol(publicKey: PublicKey, amount: number): Promise<void> {
console.log(`\n💰 Airdropping ${amount} SOL to ${publicKey.toBase58()}`);
const signature = await this.connection.requestAirdrop(
publicKey,
amount * LAMPORTS_PER_SOL
);
await this.connection.confirmTransaction(signature, "confirmed");
console.log(`✅ Airdrop complete`);
}
}

Kompletny przykład użycia

Oto kompletny przykład demonstrujący cały przepływ pracy DvP:

import { Connection, Keypair, clusterApiUrl, PublicKey } from "@solana/web3.js";
import {
getOrCreateAssociatedTokenAccount,
mintTo,
TOKEN_2022_PROGRAM_ID,
TOKEN_PROGRAM_ID
} from "@solana/spl-token";
// Standard USDC mint address on mainnet
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
async function runDvPExample() {
// 1. Initialize connection and keypairs
const connection = new Connection(clusterApiUrl("devnet"));
const settlementAgent = Keypair.generate();
const issuer = Keypair.generate();
const investor = Keypair.generate();
console.log("🚀 Starting DvP Workflow\n");
// 2. Initialize DvP engine
const dvp = new DvPEngine(connection, settlementAgent);
// 3. Airdrop SOL for transaction fees (devnet only)
await dvp.airdropSol(settlementAgent.publicKey, 2);
await dvp.airdropSol(issuer.publicKey, 2);
await dvp.airdropSol(investor.publicKey, 2);
// 4. Create commercial paper (bond) token with metadata
const bondMint = await dvp.createBondToken(issuer, {
name: "ACME Commercial Paper Series A",
symbol: "ACME-CP-A",
decimals: 0, // Bonds are whole units
maturityDate: new Date("2026-12-31"),
couponRate: 4.5, // 4.5% annual coupon
isin: "US0000000001",
description: "https://acme.com/bonds/series-a"
});
// 5. Whitelist issuer and mint bonds
console.log("\n🏦 Whitelisting issuer for bond holding...");
const issuerBondAccount = await dvp.whitelist(
bondMint,
issuer.publicKey,
settlementAgent
);
console.log("\n💰 Minting 100 bonds to issuer...");
await mintTo(
connection,
issuer,
bondMint,
issuerBondAccount,
issuer,
100, // 100 bonds
[],
{ commitment: "confirmed" },
TOKEN_2022_PROGRAM_ID
);
// 6. Whitelist investor (KYC/AML approved)
await dvp.whitelist(bondMint, investor.publicKey, settlementAgent);
// 7. Setup USDC for investor
console.log("\n💵 Setting up USDC for investor...");
const investorUSDCAccount = await getOrCreateAssociatedTokenAccount(
connection,
investor,
USDC_MINT,
investor.publicKey,
false,
"confirmed",
{ commitment: "confirmed" },
TOKEN_PROGRAM_ID
);
// In production, investor would acquire USDC from exchange/market
// For this example, assume they have 95,000 USDC
// 8. Delegate authority to settlement agent
console.log("\n🔐 Delegating authority to settlement agent...");
// Issuer delegates bonds
await dvp.delegateAuthority(
issuer,
issuerBondAccount,
100, // 100 bonds
0, // 0 decimals
TOKEN_2022_PROGRAM_ID
);
// Investor delegates USDC
await dvp.delegateAuthority(
investor,
investorUSDCAccount.address,
95000, // $95,000
6, // USDC decimals
TOKEN_PROGRAM_ID
);
// 9. Execute atomic DvP settlement
console.log("\n⚡ Executing atomic DvP settlement...");
console.log(" Terms: 100 bonds @ $950 each = $95,000\n");
const result = await dvp.executeDvP({
bondMint,
usdcMint: USDC_MINT,
bondAmount: 100,
usdcAmount: 95000,
issuer: issuer.publicKey,
investor: investor.publicKey
});
console.log("\n✨ Settlement complete!");
console.log(` Transaction: ${result.signature}`);
console.log(` Timestamp: ${result.timestamp.toISOString()}`);
console.log(
` View on explorer: https://explorer.solana.com/tx/${result.signature}?cluster=devnet`
);
// 10. Verify balances
console.log("\n🔍 Verifying final balances...");
const issuerBondInfo = await dvp.getAccountInfo(
issuerBondAccount,
TOKEN_2022_PROGRAM_ID
);
console.log(` Issuer bonds: ${issuerBondInfo.amount}`);
const investorBondInfo = await dvp.getAccountInfo(
await getAssociatedTokenAddress(
bondMint,
investor.publicKey,
false,
TOKEN_2022_PROGRAM_ID
),
TOKEN_2022_PROGRAM_ID
);
console.log(` Investor bonds: ${investorBondInfo.amount}`);
console.log(` Investor frozen: ${investorBondInfo.isFrozen}`);
}
// Run the example
runDvPExample().catch(console.error);

Dodatkowe funkcje

Aktualizowanie metadanych

Agent rozliczeniowy (posiadający uprawnienie do aktualizacji) może aktualizować pola metadanych:

import { createUpdateFieldInstruction } from "@solana/spl-token-metadata";
async function updateBondMetadata(
connection: Connection,
settlementAgent: Keypair,
bondMint: PublicKey,
field: string,
value: string
): Promise<void> {
const transaction = new Transaction().add(
createUpdateFieldInstruction({
programId: TOKEN_2022_PROGRAM_ID,
metadata: bondMint,
updateAuthority: settlementAgent.publicKey,
field: field,
value: value
})
);
await sendAndConfirmTransaction(connection, transaction, [settlementAgent]);
}

Usuwanie z białej listy

Usuń możliwość posiadania obligacji przez inwestora, zamrażając jego konto za pomocą wbudowanej metody removeFromWhitelist:

await dvp.removeFromWhitelist(bondMint, investorBondAccount);

Zamrażanie a spalanie

Zamrożenie konta uniemożliwia transfery, ale zachowuje konto i saldo. W celu całkowitego usunięcia możesz najpierw przenieść obligacje z powrotem do emitenta, a następnie zamrozić konto.

Rozliczenie wielostronne

W przypadku bardziej złożonych scenariuszy obejmujących wiele stron możesz łączyć wiele transferów w jedną atomową transakcję:

import { sendAndConfirmTransaction } from "@solana/web3.js";
import { createTransferCheckedInstruction } from "@solana/spl-token";
interface TransferLeg {
from: PublicKey;
to: PublicKey;
mint: PublicKey;
amount: number;
decimals: number;
programId: PublicKey;
}
async function executeMultiPartyDvP(
connection: Connection,
settlementAgent: Keypair,
legs: TransferLeg[]
): Promise<string> {
const transaction = new Transaction();
// Add all transfer legs to single transaction
for (const leg of legs) {
const fromAccount = await getAssociatedTokenAddress(
leg.mint,
leg.from,
false,
leg.programId
);
const toAccount = await getAssociatedTokenAddress(
leg.mint,
leg.to,
false,
leg.programId
);
// Add transfer instruction using delegated authority
transaction.add(
createTransferCheckedInstruction(
fromAccount,
leg.mint,
toAccount,
settlementAgent.publicKey, // Uses delegated authority
leg.amount * Math.pow(10, leg.decimals),
leg.decimals,
[],
leg.programId
)
);
}
// All legs settle atomically - either all succeed or all fail
const signature = await sendAndConfirmTransaction(
connection,
transaction,
[settlementAgent],
{ commitment: "confirmed" }
);
console.log(`✅ Multi-party DvP settled: ${legs.length} legs`);
return signature;
}

Limity rozmiaru transakcji

Transakcje Solany mają limit rozmiaru (~1232 bajty). Każda instrukcja transferu dodaje ~200 bajtów. W przypadku bardzo dużych rozliczeń wielostronnych rozważ użycie Address Lookup Tables, aby skompresować adresy kont i zmieścić więcej transferów w jednej transakcji.

Uwagi dotyczące środowiska produkcyjnego

Przed wdrożeniem do środowiska produkcyjnego upewnij się, że uwzględniasz:

  1. Bezpieczeństwo: Profesjonalna infrastruktura zarządzania kluczami, kontrole multi-sig, kompleksowe audyty kodu źródłowego
  2. Zgodność z przepisami: Rejestracja papierów wartościowych, systemy KYC/AML, ograniczenia transferów i ramy prawne
  3. Zarządzanie kluczami: Profesjonalne rozwiązania custody z odpowiednimi procedurami tworzenia kopii zapasowych i odzyskiwania
  4. Przetwarzanie transakcji: Opłaty priorytetowe, logika ponownych prób, obsługa potwierdzeń i redundancja RPC dla niezawodnej egzekucji rozliczeń
  5. Operacje: Harmonogramowanie rozliczeń, obsługa nieudanych transakcji, uzgadnianie i obsługa klienta
  6. Monitorowanie: Śledzenie w czasie rzeczywistym, alerty i automatyczne raportowanie do organów regulacyjnych

Ograniczenie SPL Token

Konta SPL Token mogą mieć tylko jednego delegata w danym momencie. Oznacza to, że w scenariuszach wielostronnych może być konieczne zaprojektowanie rozwiązania uwzględniającego to ograniczenie poprzez stosowanie wielu kolejnych delegacji lub różnych wzorców architektonicznych.

Inspekcja stanu konta

DvPEngine zawiera funkcję pomocniczą do inspekcji stanu token account. Jest ona przydatna do weryfikacji statusu białej listy, potwierdzania zakończenia rozliczenia, debugowania problemów z transferami oraz audytowania stanów kont.

// Get detailed account information
const accountInfo = await dvp.getAccountInfo(
investorBondAccount,
TOKEN_2022_PROGRAM_ID
);
console.log("Account Information:");
console.log(` Address: ${accountInfo.address.toBase58()}`);
console.log(` Mint: ${accountInfo.mint.toBase58()}`);
console.log(` Owner: ${accountInfo.owner.toBase58()}`);
console.log(` Balance: ${accountInfo.amount}`);
console.log(` Frozen: ${accountInfo.isFrozen}`);

Kolejne kroki

Po zapoznaniu się z podstawami DvP na Solanie:

  1. Poznaj Token Extensions: Dowiedz się więcej o innych rozszerzeniach Token 2022, takich jak Transfer Hooks, umożliwiających dodatkowe funkcje zgodności z przepisami

  2. Zaimplementuj Token ACL: W przypadku bardziej zaawansowanych potrzeb dotyczących zgodności rozważ użycie Token ACL do implementacji list zezwoleń/blokad z możliwością odblokowania bez wymaganych uprawnień. Pozwala to użytkownikom na samodzielną weryfikację białej listy przy zachowaniu pełnej kontroli zgodności.

  3. Szczegółowa analiza regulacyjna: Skonsultuj się z ekspertami prawnymi w zakresie przepisów dotyczących papierów wartościowych w swojej jurysdykcji

  4. Architektura produkcyjna: Zaprojektuj solidne systemy zarządzania kluczami, monitorowania i odtwarzania po awarii

Podsumowanie

Atomowy model transakcji Solany oraz Token Extensions Token 2022 zapewniają solidne podstawy do implementacji zgodnych z przepisami rozliczeń DvP dla papierów wartościowych. Połączenie:

  • Natywnej atomowej egzekucji (bez ryzyka smart kontraktów)
  • Finalizacji poniżej sekundy
  • Niemal zerowych kosztów transakcji
  • Wbudowanych funkcji zgodności z przepisami (domyślne zamrożenie, metadane)

Sprawia, że Solana jest idealną platformą do modernizacji infrastruktury rozliczania papierów wartościowych.

Jednak przejście od tej edukacyjnej implementacji referencyjnej do środowiska produkcyjnego wymaga znacznej dodatkowej pracy w zakresie bezpieczeństwa, zgodności z przepisami, custody i operacji. Zawsze współpracuj z wykwalifikowanymi ekspertami prawnymi, regulacyjnymi i technicznymi w przypadku tokenizacji papierów wartościowych.

Is this page helpful?