Руководство по Vault (XLS-65d)
Руководство по использованию XRPL Single Asset Vault с SDK XrplCSharp. Vault позволяет объединять активы (XRP, IOU-токены или MPT) и выпускать доли (shares) для вкладчиков пропорционально их вкладу.
Примечание: Vault требует поправку Single Asset Vault (XLS-65d). Функция находится в статусе draft и может измениться.
Содержание
- Обзор
- Ключевые концепции
- Типы транзакций
- Пошаговое руководство
- Формат поля Data
- Ledger-объект: Vault
- Частые ошибки
- Лучшие практики
Обзор
Vault — это ledger-структура, которая хранит один тип актива и выпускает доли (в виде MPT-токенов) для вкладчиков. При внесении активов вкладчик получает доли пропорционально вкладу. Доли можно обменять обратно на базовые активы.
Вкладчик A Vault (псевдо-аккаунт)
┌──────────────────┐ ┌──────────────────────────┐
│ Депозит 100 XRP │ ──────────► │ Asset: XRP │
│ Получает доли │ ◄────────── │ AssetsTotal: 300 XRP │
└──────────────────┘ │ Shares (MPT): выпущены │
│ Owner: rBroker... │
Вкладчик B │ ShareMPTID: 000ABC... │
┌──────────────────┐ └──────────────────────────┘
│ Депозит 200 XRP │ ──────────►
│ Получает доли │ ◄──────────
└──────────────────┘
При создании vault создаются:
- Псевдо-аккаунт, который хранит объединённые активы
- MPTokenIssuance для долей vault
Ключевые концепции
Доли Vault (MPT)
При внесении активов vault выпускает доли в виде MPT-токенов. Количество долей зависит от поля Scale и текущего курса обмена.
Расчёт долей: Активы умножаются на 10^Scale для преобразования дробных значений в целые числа. Например, при Scale = 6 внесение 20.3 единиц создаёт 20 300 000 долей.
- XRP и MPT vault: фиксированный Scale = 0 (1 единица актива = 1 доля)
- Trust line token vault: Scale от 0 до 18 (по умолчанию 6)
Политика вывода (WithdrawalPolicy)
Определяет правила вывода:
0x0001(vaultStrategyFirstComeFirstServe) — вкладчики могут выкупить любое количество активов при наличии достаточного числа долей
Флаги Vault
Устанавливаются только при создании через VaultCreate:
- tfVaultPrivate (
0x00010000) — ограничивает доступ аккаунтами с учётными данными в указанном Permissioned Domain - tfVaultShareNonTransferable (
0x00020000) — делает доли непередаваемыми между аккаунтами
Нереализованные убытки (LossUnrealized)
Поле LossUnrealized отслеживает потенциальные убытки (например, от clawback). При наличии нереализованных убытков стоимость погашения каждой доли пропорционально уменьшается.
Типы транзакций
| Транзакция | Назначение | Кто отправляет |
|---|---|---|
VaultCreate |
Создание нового vault | Владелец vault |
VaultDeposit |
Внесение активов в vault | Любой аккаунт |
VaultWithdraw |
Обмен долей на активы | Любой держатель долей |
VaultSet |
Обновление метаданных/настроек | Владелец vault |
VaultDelete |
Удаление пустого vault | Владелец vault |
VaultClawback |
Clawback активов у держателя | Эмитент актива |
Пошаговое руководство
1. Создание Vault
using Xrpl.Models.Transactions;
using Xrpl.Models.Common;
using Xrpl.Sugar;
using static Xrpl.Models.Common.Common;
// Создание XRP vault
VaultCreate vaultTx = new VaultCreate
{
Account = wallet.ClassicAddress,
Asset = new IssuedCurrency { Currency = "XRP" },
};
vaultTx = await client.Autofill(vaultTx);
TransactionSummary result = await client.SubmitAndWait(vaultTx, wallet, true);
// Извлечение VaultID из метаданных
string vaultId = GetCreatedObjectId(result, LedgerEntryType.Vault);
2. Создание Vault с дополнительными параметрами
VaultCreate vaultTx = new VaultCreate
{
Account = wallet.ClassicAddress,
Asset = new IssuedCurrency { Currency = "XRP" },
AssetsMaximum = "1000000000", // макс. 1000 XRP (в drops)
MPTokenMetadata = "48656C6C6F", // hex-метаданные для долей
Data = "7B226E223A225465737420566175"
+ "6C74222C2277223A226578616D70"
+ "6C652E636F6D227D", // hex-JSON метаданные vault
WithdrawalPolicy = 1, // FirstComeFirstServe
Scale = 6, // точность (только trust line токены)
Flags = (uint)VaultCreateFlags.tfVaultShareNonTransferable,
};
vaultTx = await client.Autofill(vaultTx);
TransactionSummary result = await client.SubmitAndWait(vaultTx, wallet, true);
3. Внесение активов
Любой аккаунт может внести активы:
VaultDeposit depositTx = new VaultDeposit
{
Account = depositor.ClassicAddress,
VaultID = vaultId,
Amount = new Currency { Value = "50000000", CurrencyCode = "XRP" }, // 50 XRP
};
depositTx = await client.Autofill(depositTx);
await client.SubmitAndWait(depositTx, depositor, true);
4. Вывод активов
Любой держатель долей может обменять их на активы:
Вывод по количеству активов:
VaultWithdraw withdrawTx = new VaultWithdraw
{
Account = depositor.ClassicAddress,
VaultID = vaultId,
Amount = new Currency { Value = "25000000", CurrencyCode = "XRP" }, // 25 XRP
};
withdrawTx = await client.Autofill(withdrawTx);
await client.SubmitAndWait(withdrawTx, depositor, true);
Вывод на другой аккаунт:
VaultWithdraw withdrawTx = new VaultWithdraw
{
Account = depositor.ClassicAddress,
VaultID = vaultId,
Amount = new Currency { Value = "25000000", CurrencyCode = "XRP" },
Destination = "rRecipient...",
DestinationTag = 42,
};
Примечание: Комиссия за перевод (transfer fee) не применяется к VaultWithdraw.
5. Обновление настроек Vault
Владелец может обновить изменяемые поля:
VaultSet setTx = new VaultSet
{
Account = wallet.ClassicAddress,
VaultID = vaultId,
AssetsMaximum = "2000000000", // увеличить лимит до 2000 XRP
Data = "7B226E223A2255706461746564227D", // новые метаданные
};
setTx = await client.Autofill(setTx);
await client.SubmitAndWait(setTx, wallet, true);
Ограничение: VaultSet может изменять только
Data,AssetsMaximumиDomainID. Статус vault (публичный/приватный) является постоянным.
6. Удаление пустого Vault
Vault должен иметь нулевой баланс и не иметь выпущенных долей:
VaultDelete deleteTx = new VaultDelete
{
Account = wallet.ClassicAddress,
VaultID = vaultId,
};
deleteTx = await client.Autofill(deleteTx);
await client.SubmitAndWait(deleteTx, wallet, true);
7. Clawback активов (только эмитент)
Эмитент актива может выполнить clawback у держателя долей vault. Clawback не применим к нативному XRP.
VaultClawback clawbackTx = new VaultClawback
{
Account = issuer.ClassicAddress,
VaultID = vaultId,
Holder = "rShareHolder...",
Amount = new Currency
{
Value = "100",
CurrencyCode = "USD",
Issuer = issuer.ClassicAddress,
},
};
clawbackTx = await client.Autofill(clawbackTx);
await client.SubmitAndWait(clawbackTx, issuer, true);
При
Amount = 0эмитент забирает все средства, вплоть до общего количества долей держателя.
Формат поля Data
Поле Data хранит произвольные метаданные в виде hex-строки (макс. 256 байт). SDK предоставляет класс VaultDataFormat для рекомендуемой JSON-структуры:
using Xrpl.Models.Ledger;
// Создание структурированных данных
VaultDataFormat data = new VaultDataFormat
{
Name = "My Investment Vault",
Website = "https://example.com",
};
// Конвертация в hex для VaultCreate/VaultSet
string hex = VaultDataFormat.ToHex(data);
// Парсинг hex из ledger-объекта Vault
LOVault vault = ...; // получен через ledger_entry
VaultDataFormat parsed = vault.DataParsed; // [JsonIgnore] свойство-помощник
string rawUtf8 = vault.DataRaw; // [JsonIgnore] сырая UTF-8 строка
JSON-структура VaultDataFormat: {"n":"name","w":"website"} — без пробелов, hex-кодированная.
Ledger-объект: Vault
Получение Vault ledger entry через ledger_entry:
using Xrpl.Models.Methods;
using Xrpl.Models.Ledger;
LedgerEntryRequest request = new LedgerEntryRequest { Index = vaultId };
LedgerEntryResponse response = await client.LedgerEntry(request);
LOVault vault = (LOVault)response.Node;
Console.WriteLine($"Owner: {vault.Owner}");
Console.WriteLine($"Asset: {vault.Asset}");
Console.WriteLine($"AssetsTotal: {vault.AssetsTotal}");
Console.WriteLine($"AssetsAvailable: {vault.AssetsAvailable}");
Console.WriteLine($"ShareMPTID: {vault.ShareMPTID}");
Console.WriteLine($"Scale: {vault.Scale}");
Console.WriteLine($"Metadata: {vault.DataParsed?.Name}");
Ключевые поля LOVault
| Поле | Тип | Описание |
|---|---|---|
Account |
string | Адрес псевдо-аккаунта vault |
Owner |
string | Адрес владельца vault |
Asset |
IssuedCurrency | Хранимый актив |
AssetsTotal |
string | Общая стоимость vault (тип Number) |
AssetsAvailable |
string | Доступные активы (тип Number) |
AssetsMaximum |
string | Лимит хранения, 0 = без ограничений (тип Number) |
LossUnrealized |
string | Нереализованные убытки (тип Number) |
ShareMPTID |
string | ID MPTokenIssuance для долей |
WithdrawalPolicy |
uint? | Стратегия вывода |
Scale |
uint? | Точность при расчёте долей |
Data |
string | Hex-метаданные (макс. 256 байт) |
Sequence |
uint? | Sequence транзакции создания |
Флаги Vault (LOVault)
[Flags]
public enum VaultLedgerFlags : uint
{
lsfVaultPrivate = 0x00010000,
}
Частые ошибки
| Код ошибки | Причина |
|---|---|
tecHAS_OBLIGATIONS |
Невозможно удалить vault с ненулевыми долями или активами |
tecNO_PERMISSION |
Не-владелец пытается выполнить VaultSet/VaultDelete, или не-эмитент — VaultClawback |
tecUNFUNDED |
Недостаточно средств для VaultDeposit |
tecINSUFFICIENT_FUNDS |
Недостаточно долей для VaultWithdraw |
tecFROZEN |
Trust line между псевдо-аккаунтом vault и эмитентом заморожена |
temMALFORMED |
Data превышает 256 байт или недопустимые значения полей |
tecOBJECT_NOT_FOUND |
VaultID не существует |
Лучшие практики
- Всегда задавайте AssetsMaximum — ограничивает риски и предотвращает неконтролируемые депозиты
- Используйте VaultDataFormat — обеспечивает стандартную структуру метаданных, читаемую другими приложениями
- Проверяйте LossUnrealized — перед выводом убедитесь, что vault не имеет нереализованных убытков
- Приватные vault для регулируемых активов — используйте
tfVaultPrivateс Permissioned Domain для KYC-gated vault - Непередаваемые доли — используйте
tfVaultShareNonTransferable, когда доли не должны торговаться на вторичном рынке - Scale для trust line токенов — выбирайте Scale тщательно при создании (нельзя изменить позже). Больший Scale = точнее гранулярность, но больше числа долей
- Учитывайте clawback — только эмитент актива может выполнить clawback, и только для не-XRP активов. Clawback создаёт нереализованные убытки для других вкладчиков