Поставка проти платежу (DvP) на Solana

Поставка проти платежу (DvP) — це метод розрахунків за цінними паперами, який забезпечує одночасне здійснення передачі цінних паперів і переказу платежу. Це усуває ризик контрагента, гарантуючи атомарне виконання обох частин угоди — або обидві завершуються успішно, або жодна.

Проблема

Коли ви купуєте облігації (наприклад, комерційні папери), традиційно необхідно виконати дві дії:

  1. Ви надсилаєте гроші → Продавцю
  2. Продавець надсилає облігації → Вам

Якщо ці дії виконуються окремо, виникає ризик — що, якщо ви заплатили, але так і не отримали облігації? Або навпаки?

Рішення

(DvP) «Поставка проти платежу» означає, що обидва перекази відбуваються в один і той самий момент, або не відбуваються взагалі. Це схоже на своп, де обидві сторони обмінюються одночасно.

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

Цей посібник демонструє, як реалізувати повний робочий процес DvP на Solana з використанням розширень SPL Token 2022 для відповідного випуску облігацій і стандартного USDC для розрахунків — без написання власних програм на Rust.

Навчальна еталонна реалізація

Ви можете скористатися вихідним кодом цієї реалізації, щоб спробувати реалізацію DvP локально.

Цей посібник надає еталонну реалізацію виключно для дослідження та навчальних цілей. НЕ використовуйте цей код безпосередньо у виробничому середовищі без:

  • Комплексних аудитів безпеки
  • Належних систем управління ключами
  • Перевірки відповідності нормативним вимогам
  • Юридичної консультації
  • Ретельного тестування та модифікацій

Чому Solana для DvP?

Архітектура Solana надає суттєві переваги порівняно з традиційними розрахунками за цінними паперами:

АспектТрадиційний (T+2)Solana
Логіка розрахунківКлірингові палатиАтомарне пакетування транзакцій
Час розрахунків2 дні<1 секунди
Вартість транзакції$50–500<$0.01
Ризик контрагентаВисокий (посередники)Нульовий (атомарне виконання)
ОстаточністьКінець дня~400 мс

Атомарне пакетування транзакцій Solana усуває посередників, забезпечуючи миттєві, дешеві та безпечні розрахунки.

Огляд архітектури

Система DvP складається з таких основних компонентів:

  1. Токен облігації (комерційний папір): SPL Token 2022 з Token Extensions
  2. Валюта розрахунків: Стандартний USDC (наявний SPL-токен)
  3. Розрахунковий агент: Оркеструє атомарні свопи через делеговані повноваження
  4. Система білого списку: Контролює, які адреси можуть зберігати облігації

Ключові принципи проектування

  • Без власних програм: Використовує лише Token Extensions Program та стандартний USDC
  • Атомарні розрахунки: Одна транзакція гарантує, що обидва перекази або завершуються успішно, або відхиляються разом
  • Стан заморозки за замовчуванням: Облігації потребують явного внесення до білого списку для відповідності нормативним вимогам
  • Делеговані повноваження: Розрахунковий агент координує операції, не беручи на зберігання активи
  • Комунікація через стан мережі: Не потребує точкового API-підключення
┌─────────────────────────────────────────────────────┐
│ 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) │
│ │
└─────────────────────────────────────────────────────┘

Довірена сторона, яка оркеструє угоду. Обидві сторони делегують повноваження розрахунковому агенту, який потім виконує атомарний своп.

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

Токен облігації з Token Extensions (Token 2022)

SPL Token 2022 надає потужні розширення, що уможливлюють відповідний випуск цінних паперів без власних програм:

Основні розширення для облігацій

  1. Розширення стану облікового запису за замовчуванням: Встановлює всі нові token account у стан заморозки за замовчуванням, вимагаючи явного внесення до білого списку
  2. Розширення метаданих: Зберігає деталі облігації в ончейні (ISIN, дата погашення, купонна ставка тощо)
  3. Постійний делегат (необов'язково): Дозволяє авторизоване відновлення або примусове вилучення, якщо це вимагається регулятором

Конфігурація повноважень

  • Повноваження карбування: Емітент (контролює створення пропозиції)
  • Повноваження заморозки: Розрахунковий агент (керує білим списком)
  • Повноваження оновлення: Розрахунковий агент (може оновлювати метадані)

Стан заморозки за замовчуванням є критично важливим для відповідності нормативним вимогам. Він гарантує, що лише явно внесені до білого списку адреси можуть отримувати та зберігати цінні папери, відповідаючи вимогам KYC/AML.

Повна реалізація DvP

Налаштування рушія DvP

Спочатку створіть основний клас рушія DvP, який обробляє всі операції:

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`);
}
}

Повний приклад використання

Ось повний приклад, що демонструє весь робочий процес 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);

Додаткові можливості

Оновлення метаданих

Розрахунковий агент (з повноваженнями оновлення) може оновлювати поля метаданих:

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]);
}

Видалення з білого списку

Видаліть можливість інвестора зберігати облігації, заморозивши його обліковий запис за допомогою вбудованого методу removeFromWhitelist:

await dvp.removeFromWhitelist(bondMint, investorBondAccount);

Заморозка проти спалення

Заморозка облікового запису запобігає переказам, але зберігає обліковий запис і баланс. Для повного видалення можна спочатку перевести облігації назад емітенту, а потім заморозити обліковий запис.

Багатостороннє врегулювання

Для складніших сценаріїв за участю кількох сторін можна об'єднати кілька переказів в одну атомарну транзакцію:

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;
}

Обмеження розміру транзакції

Транзакції Solana мають обмеження розміру (~1232 байти). Кожна інструкція переказу додає ~200 байтів. Для дуже великих багатосторонніх розрахунків розгляньте використання таблиць пошуку адрес, щоб стиснути адреси облікових записів і вмістити більше переказів в одну транзакцію.

Міркування щодо виробничого середовища

Перш ніж розгортати у виробничому середовищі, переконайтеся, що ви вирішили такі питання:

  1. Безпека: Професійна інфраструктура управління ключами, контроль через мультипідпис, комплексні аудити кодової бази
  2. Відповідність нормативним вимогам: Реєстрація цінних паперів, системи KYC/AML, обмеження на передачу та правова база
  3. Управління ключами: Професійні рішення зберігання з належними процедурами резервного копіювання та відновлення
  4. Обробка транзакцій: Пріоритетні комісії, логіка повторних спроб, обробка підтверджень та резервування RPC для надійного виконання розрахунків
  5. Операції: Планування розрахунків, обробка невдалих угод, звірка та підтримка клієнтів
  6. Моніторинг: Відстеження в реальному часі, сповіщення та автоматизована нормативна звітність

Обмеження SPL Token

SPL Token account можуть мати лише одного делегата одночасно. Це означає, що для багатосторонніх сценаріїв вам може знадобитися проектувати з урахуванням цього обмеження, використовуючи кілька послідовних делегувань або інші архітектурні підходи.

Перевірка стану облікового запису

У DvPEngine є допоміжний метод для перевірки стану token account. Це корисно для верифікації статусу білого списку, підтвердження завершення розрахунків, налагодження проблем із переказами та аудиту станів облікових записів.

// 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}`);

Наступні кроки

Після ознайомлення з основами DvP на Solana:

  1. Вивчення Token Extensions: Дізнайтеся про інші розширення Token 2022, наприклад Transfer Hooks, для додаткових функцій відповідності

  2. Реалізація Token ACL: Для більш складних потреб у відповідності розгляньте використання Token ACL для реалізації списків дозволів/блокувань із можливістю розморозки без дозволу. Це дозволяє користувачам самостійно пройти верифікацію білого списку, зберігаючи повний контроль відповідності.

  3. Поглиблене вивчення регулювання: Проконсультуйтеся з юридичними експертами щодо законодавства про цінні папери у вашій юрисдикції

  4. Виробнича архітектура: Розробіть надійні системи управління ключами, моніторингу та аварійного відновлення

Висновок

Атомарна модель транзакцій Solana та Token Extensions надають потужну основу для реалізації відповідних розрахунків DvP для цінних паперів. Поєднання:

  • Нативного атомарного виконання (без ризику смарт-контракту)
  • Остаточності менше ніж за секунду
  • Майже нульових витрат на транзакції
  • Вбудованих функцій відповідності (заморозка за замовчуванням, метадані)

Робить Solana ідеальною платформою для модернізації інфраструктури розрахунків за цінними паперами.

Однак перехід від цієї навчальної еталонної реалізації до виробничого середовища потребує значної додаткової роботи в галузі безпеки, відповідності, зберігання та операційної діяльності. Завжди залучайте кваліфікованих юридичних, нормативних і технічних експертів при роботі з токенізацією цінних паперів.

Is this page helpful?