Смарт-контракт ЦФА Eracoin

Автор: Усманов Руслан Гаязо

Смарт-контракт ЦФА Eracoin Блокчейн: Erachain (Эрачейн) Язык: JavaScript Тип актива: Цифровые облигации (долговой ЦФА) Версия: 1.0.0 Содержание 1. Условия выпуска 2. Архитектура контракта 3. Полный код контракта 4. Описание функций 5. Размещение в блокчейне 6. Примеры вызовов 7. Лицензия Условия выпуска | Параметр | Значение | |----------|----------| | Название | Цифровые облигации Eracoin серия 001 | | Тикер | ERACOIN001 | | Номинал | 1 000 RUB | | Объём эмиссии | 50 000 шт. (50 000 000 RUB) | | Купонная ставка | 14% годовых | | Периодичность выплат | Ежеквартально | | Срок обращения | 24 месяца | | Минимальная инвестиция | 100 000 RUB | | Максимум на инвестора | 10 000 000 RUB | | Категория инвесторов | Только квалифицированные | | Досрочное погашение | Разрешено (по усмотрению эмитента) | | Передаваемость | Да | | Дробность | Да (2 знака после запятой) | Архитектура контракта ` ┌─────────────────────────────────────────────────────────────┐ │ СМАРТ-КОНТРАКТ ERACOIN │ ├─────────────────────────────────────────────────────────────┤ │ 1. КОНФИГУРАЦИЯ (CFG) — неизменяемые параметры актива │ │ 2. СОСТОЯНИЕ (ST) — изменяемые данные блокчейна │ │ 3. МОДИФИКАТОРЫ — проверки прав доступа │ │ 4. KYC / РЕЕСТР — управление инвесторами │ │ 5. ЭМИССИЯ — первичное размещение │ │ 6. ПЕРЕВОДЫ — вторичный оборот │ │ 7. КУПОНЫ — начисление и выплата дохода │ │ 8. ПОГАШЕНИЕ — возврат номинала │ │ 9. ЗАМОРОЗКА — ограничение операций │ │ 10. ГОЛОСОВАНИЯ — корпоративные действия │ │ 11. VIEW — функции просмотра │ │ 12. СОБЫТИЯ — обработчики блоков и расписания │ └─────────────────────────────────────────────────────────────┘ ` Полный код контракта `javascript /** * ═══════════════════════════════════════════════════════════════ * СМАРТ-КОНТРАКТ ЦФА "ERACOIN" * Блокчейн: Erachain (Эрачейн) * Язык: JavaScript * ═══════════════════════════════════════════════════════════════ * * Лицензия: MIT * Автор: [Усманов Руслан Гаязович] * Дата создания: 07.06.2026 * * ОПИСАНИЕ: * Цифровой финансовый актив Eracoin — долговой ЦФА с фиксированным * купоном, ежеквартальными выплатами и погашением по номиналу. * Соответствует требованиям 259-ФЗ "О цифровых финансовых активах". */ // ═══════════════════════════════════════════════════════════════ // 1. КОНФИГУРАЦИЯ АКТИВА (КОНСТАНТЫ) // ═══════════════════════════════════════════════════════════════ const CFG = { // ─── Основные параметры ─── NAME: "Цифровые облигации Eracoin серия 001", TICKER: "ERACOIN001", NOMINAL: 1000, // Номинал в RUB QUANTITY: 50000, // Общее количество единиц SCALE: 2, // Делимость (копейки) // ─── Финансовые условия ─── COUPON_RATE: 0.14, // 14% годовых COUPONPERIODMONTHS: 3, // Ежеквартально MATURITY_MONTHS: 24, // Срок 24 месяца // ─── Даты (Unix timestamp) ─── ISSUE_DATE: null, // Устанавливается при создании FIRSTCOUPONDATE: null, // +3 месяца от ISSUE_DATE MATURITYDATE: null, // +24 месяца от ISSUEDATE // ─── Ограничения ─── MIN_INVESTMENT: 100000, // Минимальная инвестиция 100 000 RUB MAXINVESTMENTPER_INVESTOR: 10000000, // Макс. 10 млн на инвестора ONLY_QUALIFIED: true, // Только квалифицированные инвесторы // ─── Адреса ─── EMITTER: "EMITTERADDRESSPLACEHOLDER", // Адрес эмитента SETTLEMENT: "SETTLEMENTACCOUNTPLACEHOLDER", // Расчётный счёт для купонов // ─── Флаги ─── TRANSFERABLE: true, // Передаваемость DIVISIBLE: true, // Дробность CALLABLE: true // Досрочное погашение разрешено }; // ═══════════════════════════════════════════════════════════════ // 2. СОСТОЯНИЕ КОНТРАКТА (STATE) // ═══════════════════════════════════════════════════════════════ const ST = { // ─── Реестр владельцев ─── // Формат: {address: {balance, kycVerified, qualified, totalReceived, lastCoupon}} holders: {}, // ─── История выплат ─── // Формат: [{date, amount, totalHolders, details, txHash}] coupons: [], // ─── Статус выпуска ─── // CREATED → ACTIVE → COUPON_PAID → MATURED → BURNED status: "CREATED", // ─── Счётчики ─── issued: 0, // Выпущено burned: 0, // Погашено totalPaid: 0, // Всего купонов выплачено // ─── Заморозки ─── // Формат: {address: {until, reason, frozenAt}} frozen: {}, // ─── Голосования ─── // Формат: {voteId: {topic, options, votes, deadline, status, results}} votes: {} }; // ═══════════════════════════════════════════════════════════════ // 3. МОДИФИКАТОРЫ ДОСТУПА // ═══════════════════════════════════════════════════════════════ /** * Проверка: вызывающий — эмитент? * @throws "ACCESS_DENIED" если не эмитент */ function onlyEmitter() { if (msg.sender !== CFG.EMITTER) { throw new Error("ACCESS_DENIED: Только эмитент может выполнить эту операцию"); } } /** * Проверка: вызывающий — авторизованный оператор ИС? * @throws "NOT_OPERATOR" если не оператор */ function onlyOperator() { if (!isAuthOp(msg.sender)) { throw new Error("ACCESS_DENIED: Только авторизованный оператор ИС"); } } /** * Проверка: актив находится в рабочем статусе? * @throws "NOTACTIVE" если статус не ACTIVE или COUPONPAID */ function onlyActive() { if (ST.status !== "ACTIVE" && ST.status !== "COUPON_PAID") { throw new Error("STATE_ERROR: Актив не активен. Текущий статус: " + ST.status); } } /** * Проверка: адрес не заморожен? * @param {string} address — проверяемый адрес * @throws "FROZEN" если адрес заморожен */ function notFrozen(address) { if (ST.frozen[address] && ST.frozen[address].until > block.timestamp) { throw new Error("FROZEN: Адрес заморожен до " + ST.frozen[address].until); } } /** * Проверка: инвестор имеет статус квалифицированного? * @param {string} address — проверяемый адрес * @throws "NOT_QUALIFIED" если не квалифицирован */ function onlyQualified(address) { if (CFG.ONLY_QUALIFIED && !ST.holders[address]?.qualified) { throw new Error("QUALIFICATION_ERROR: Требуется статус квалифицированного инвестора"); } } // ═══════════════════════════════════════════════════════════════ // 4. ФУНКЦИИ ИНИЦИАЛИЗАЦИИ // ═══════════════════════════════════════════════════════════════ /** * Инициализация актива при создании в блокчейне * @param {Object} params — параметры инициализации * @param {string} params.emitter — адрес эмитента * @param {string} params.settlement — расчётный счёт для выплат * @returns {Object} {success, assetId, timestamp} */ function init(params) { // Установка дат CFG.ISSUE_DATE = block.timestamp; CFG.FIRSTCOUPONDATE = block.timestamp + (90 24 3600); // +3 месяца CFG.MATURITY_DATE = block.timestamp + (730 24 3600); // +24 месяца // Установка адресов if (params.emitter) { CFG.EMITTER = params.emitter; } if (params.settlement) { CFG.SETTLEMENT = params.settlement; } // Логирование создания emit("ASSET_CREATED", { name: CFG.NAME, ticker: CFG.TICKER, quantity: CFG.QUANTITY, nominal: CFG.NOMINAL, couponRate: CFG.COUPON_RATE, issueDate: CFG.ISSUE_DATE, maturityDate: CFG.MATURITY_DATE }); return { success: true, assetId: CFG.TICKER + "_" + block.number, timestamp: block.timestamp }; } // ═══════════════════════════════════════════════════════════════ // 5. ФУНКЦИИ УПРАВЛЕНИЯ ВЛАДЕЛЬЦАМИ (KYC / РЕЕСТР) // ═══════════════════════════════════════════════════════════════ /** * Регистрация инвестора в реестре * Вызывается оператором ИС после прохождения KYC * * @param {string} address — адрес кошелька инвестора * @param {Object} kyc — данные KYC * @param {boolean} kyc.qualified — статус квалифицированного инвестора * @returns {Object} {success} */ function registerInvestor(address, kyc) { onlyOperator(); if (ST.holders[address]) { throw new Error("ALREADY_REGISTERED: Инвестор уже зарегистрирован"); } ST.holders[address] = { balance: 0, kycVerified: true, qualified: kyc.qualified || false, registrationDate: block.timestamp, totalReceived: 0, lastCoupon: null }; emit("INVESTOR_REGISTERED", { address: address, qualified: kyc.qualified, timestamp: block.timestamp }); return { success: true }; } /** * Обновление статуса квалификации инвестора * * @param {string} address — адрес инвестора * @param {boolean} isQualified — новый статус * @returns {Object} {success} */ function updateQualification(address, isQualified) { onlyOperator(); if (!ST.holders[address]) { throw new Error("NOT_FOUND: Инвестор не найден"); } ST.holders[address].qualified = isQualified; emit("QUALIFICATION_UPDATED", { address: address, qualified: isQualified }); return { success: true }; } // ═══════════════════════════════════════════════════════════════ // 6. ФУНКЦИИ ВЫПУСКА И РАЗМЕЩЕНИЯ // ═══════════════════════════════════════════════════════════════ /** * Первичное размещение ЦФА (эмиссия) * Распределение ЦФА среди зарегистрированных инвесторов * * @param {Array} recipients — список получателей * @param {string} recipients[].address — адрес инвестора * @param {number} recipients[].amount — количество ЦФА * @returns {Object} {success, totalIssued, status} */ function primaryPlacement(recipients) { onlyEmitter(); if (ST.status !== "CREATED") { throw new Error("STATE_ERROR: Первичное размещение уже проведено"); } let totalAllocated = 0; for (const recipient of recipients) { const { address, amount } = recipient; // Проверка регистрации if (!ST.holders[address]) { throw new Error("NOT_REGISTERED: Инвестор " + address + " не прошёл KYC"); } // Проверка квалификации onlyQualified(address); // Проверка заморозки notFrozen(address); // Проверка лимитов инвестиции const newBalance = ST.holders[address].balance + amount; const investment = newBalance * CFG.NOMINAL; if (investment < CFG.MIN_INVESTMENT) { throw new Error("MININVESTMENT: Минимум " + CFG.MININVESTMENT + " RUB"); } if (investment > CFG.MAXINVESTMENTPER_INVESTOR) { throw new Error("MAXINVESTMENT: Максимум " + CFG.MAXINVESTMENTPERINVESTOR + " RUB"); } // Начисление ЦФА ST.holders[address].balance = newBalance; totalAllocated += amount; } // Проверка общего объёма if (totalAllocated > CFG.QUANTITY) { throw new Error("OVER_ISSUE: Превышен лимит эмиссии"); } ST.issued = totalAllocated; ST.status = "ACTIVE"; emit("PLACEMENT_COMPLETE", { totalIssued: totalAllocated, totalInvestors: recipients.length, timestamp: block.timestamp }); return { success: true, totalIssued: totalAllocated, status: ST.status }; } // ═══════════════════════════════════════════════════════════════ // 7. ФУНКЦИИ ПЕРЕВОДА (ВТОРИЧНЫЙ ОБОРОТ) // ═══════════════════════════════════════════════════════════════ /** * Перевод ЦФА между владельцами * Требует подтверждения оператора ИС (ограничение 259-ФЗ) * * @param {string} from — адрес отправителя * @param {string} to — адрес получателя * @param {number} amount — количество ЦФА * @returns {Object} {success, fromBalance, toBalance} */ function transfer(from, to, amount) { onlyActive(); // Проверка прав на инициацию перевода if (msg.sender !== from && msg.sender !== CFG.EMITTER) { throw new Error("ACCESS_DENIED: Только владелец или эмитент может инициировать перевод"); } // Проверка заморозки notFrozen(from); notFrozen(to); // Проверка получателя if (!ST.holders[to]) { throw new Error("NOT_REGISTERED: Получатель не прошёл KYC"); } // Проверка квалификации получателя onlyQualified(to); // Проверка баланса отправителя if (ST.holders[from].balance < amount) { throw new Error("INSUFFICIENT_BALANCE: Недостаточно средств"); } // Проверка лимита получателя const newToBalance = ST.holders[to].balance + amount; const toInvestment = newToBalance * CFG.NOMINAL; if (toInvestment > CFG.MAXINVESTMENTPER_INVESTOR) { throw new Error("MAX_INVESTMENT: Превышен лимит для получателя"); } // Выполнение перевода ST.holders[from].balance -= amount; ST.holders[to].balance = newToBalance; emit("TRANSFER", { from: from, to: to, amount: amount, timestamp: block.timestamp }); return { success: true, fromBalance: ST.holders[from].balance, toBalance: ST.holders[to].balance }; } // ═══════════════════════════════════════════════════════════════ // 8. ФУНКЦИИ КУПОННЫХ ВЫПЛАТ // ═══════════════════════════════════════════════════════════════ /** * Расчёт купона для конкретного владельца * Формула: баланс × номинал × ставка / 4 (квартал) * * @param {string} address — адрес владельца * @returns {number} Сумма купона в RUB */ function calculateCoupon(address) { const holder = ST.holders[address]; if (!holder || holder.balance <= 0) { return 0; } const annualCoupon = holder.balance CFG.NOMINAL CFG.COUPON_RATE; const periodsPerYear = 12 / CFG.COUPONPERIODMONTHS; const couponAmount = annualCoupon / periodsPerYear; return Math.floor(couponAmount * 100) / 100; // Округление до копеек } /** * Выплата купонов всем владельцам ЦФА * Вызывается эмитентом или автоматически по расписанию * * @returns {Object} {success, totalPaid, holdersPaid, paymentRecord} */ function payCoupons() { onlyEmitter(); onlyActive(); // Проверка даты выплаты const now = block.timestamp; const lastCoupon = ST.coupons.length > 0 ? ST.coupons[ST.coupons.length - 1].date : CFG.ISSUE_DATE; const daysSinceLastCoupon = (now - lastCoupon) / (24 * 3600); const minDays = CFG.COUPONPERIODMONTHS * 30 - 5; // Допуск ±5 дней if (daysSinceLastCoupon < minDays) { throw new Error("TOO_EARLY: Следующая выплата не ранее " + minDays + " дней"); } let totalPaid = 0; let holdersPaid = 0; const paymentDetails = []; for (const address in ST.holders) { const holder = ST.holders[address]; if (holder.balance <= 0) continue; const couponAmount = calculateCoupon(addres

Смарт-контракт ЦФА Eracoin

NoteУсманов Руслан Гаязо07.06.2026, 19:37:46
Открыть в эксплорере

SeqNo

3201035-1

Тип

Note

Комиссия

0.048439

Размер

48439 B

Создатель

7Luv5A6fqUFJwvGjKc64bKEyhDXKr813qz

Подпись

3PhDEDMGFeWLg46Rp5gASHUUndAUPD4NmSNhACNEixf72RSuNKMfF5svjm8BvEZAxPTKSAsEzaL3Fm3YNiYTozmb

Содержание

Смарт-контракт ЦФА Eracoin

Усманов Руслан Гаязо

Смарт-контракт ЦФА Eracoin

Блокчейн: Erachain (Эрачейн)
Язык: JavaScript
Тип актива: Цифровые облигации (долговой ЦФА)
Версия: 1.0.0


Содержание

  1. Условия выпуска
  2. Архитектура контракта
  3. Полный код контракта
  4. Описание функций
  5. Размещение в блокчейне
  6. Примеры вызовов
  7. Лицензия

Условия выпуска

ПараметрЗначение
НазваниеЦифровые облигации Eracoin серия 001
ТикерERACOIN001
Номинал1 000 RUB
Объём эмиссии50 000 шт. (50 000 000 RUB)
Купонная ставка14% годовых
Периодичность выплатЕжеквартально
Срок обращения24 месяца
Минимальная инвестиция100 000 RUB
Максимум на инвестора10 000 000 RUB
Категория инвесторовТолько квалифицированные
Досрочное погашениеРазрешено (по усмотрению эмитента)
ПередаваемостьДа
ДробностьДа (2 знака после запятой)

Архитектура контракта

┌─────────────────────────────────────────────────────────────┐
│                    СМАРТ-КОНТРАКТ ERACOIN                    │
├─────────────────────────────────────────────────────────────┤
│  1. КОНФИГУРАЦИЯ (CFG) — неизменяемые параметры актива     │
│  2. СОСТОЯНИЕ (ST) — изменяемые данные блокчейна           │
│  3. МОДИФИКАТОРЫ — проверки прав доступа                   │
│  4. KYC / РЕЕСТР — управление инвесторами                  │
│  5. ЭМИССИЯ — первичное размещение                         │
│  6. ПЕРЕВОДЫ — вторичный оборот                            │
│  7. КУПОНЫ — начисление и выплата дохода                   │
│  8. ПОГАШЕНИЕ — возврат номинала                           │
│  9. ЗАМОРОЗКА — ограничение операций                       │
│  10. ГОЛОСОВАНИЯ — корпоративные действия                  │
│  11. VIEW — функции просмотра                              │
│  12. СОБЫТИЯ — обработчики блоков и расписания             │
└─────────────────────────────────────────────────────────────┘

Полный код контракта

/**
 * ═══════════════════════════════════════════════════════════════
 * СМАРТ-КОНТРАКТ ЦФА "ERACOIN"
 * Блокчейн: Erachain (Эрачейн)
 * Язык: JavaScript
 * ═══════════════════════════════════════════════════════════════
 * 
 * Лицензия: MIT
 * Автор: [Усманов Руслан Гаязович]
 * Дата создания: 07.06.2026
 * 
 * ОПИСАНИЕ:
 * Цифровой финансовый актив Eracoin — долговой ЦФА с фиксированным
 * купоном, ежеквартальными выплатами и погашением по номиналу.
 * Соответствует требованиям 259-ФЗ "О цифровых финансовых активах".
 */

// ═══════════════════════════════════════════════════════════════
// 1. КОНФИГУРАЦИЯ АКТИВА (КОНСТАНТЫ)
// ═══════════════════════════════════════════════════════════════

const CFG = {
    // ─── Основные параметры ───
    NAME: "Цифровые облигации Eracoin серия 001",
    TICKER: "ERACOIN001",
    NOMINAL: 1000,              // Номинал в RUB
    QUANTITY: 50000,            // Общее количество единиц
    SCALE: 2,                   // Делимость (копейки)

    // ─── Финансовые условия ───
    COUPON_RATE: 0.14,          // 14% годовых
    COUPON_PERIOD_MONTHS: 3,    // Ежеквартально
    MATURITY_MONTHS: 24,        // Срок 24 месяца

    // ─── Даты (Unix timestamp) ───
    ISSUE_DATE: null,           // Устанавливается при создании
    FIRST_COUPON_DATE: null,    // +3 месяца от ISSUE_DATE
    MATURITY_DATE: null,        // +24 месяца от ISSUE_DATE

    // ─── Ограничения ───
    MIN_INVESTMENT: 100000,     // Минимальная инвестиция 100 000 RUB
    MAX_INVESTMENT_PER_INVESTOR: 10000000, // Макс. 10 млн на инвестора
    ONLY_QUALIFIED: true,       // Только квалифицированные инвесторы

    // ─── Адреса ───
    EMITTER: "EMITTER_ADDRESS_PLACEHOLDER",      // Адрес эмитента
    SETTLEMENT: "SETTLEMENT_ACCOUNT_PLACEHOLDER", // Расчётный счёт для купонов

    // ─── Флаги ───
    TRANSFERABLE: true,         // Передаваемость
    DIVISIBLE: true,            // Дробность
    CALLABLE: true              // Досрочное погашение разрешено
};

// ═══════════════════════════════════════════════════════════════
// 2. СОСТОЯНИЕ КОНТРАКТА (STATE)
// ═══════════════════════════════════════════════════════════════

const ST = {
    // ─── Реестр владельцев ───
    // Формат: {address: {balance, kycVerified, qualified, totalReceived, lastCoupon}}
    holders: {},

    // ─── История выплат ───
    // Формат: [{date, amount, totalHolders, details, txHash}]
    coupons: [],

    // ─── Статус выпуска ───
    // CREATED → ACTIVE → COUPON_PAID → MATURED → BURNED
    status: "CREATED",

    // ─── Счётчики ───
    issued: 0,          // Выпущено
    burned: 0,          // Погашено
    totalPaid: 0,       // Всего купонов выплачено

    // ─── Заморозки ───
    // Формат: {address: {until, reason, frozenAt}}
    frozen: {},

    // ─── Голосования ───
    // Формат: {voteId: {topic, options, votes, deadline, status, results}}
    votes: {}
};

// ═══════════════════════════════════════════════════════════════
// 3. МОДИФИКАТОРЫ ДОСТУПА
// ═══════════════════════════════════════════════════════════════

/**
 * Проверка: вызывающий — эмитент?
 * @throws "ACCESS_DENIED" если не эмитент
 */
function onlyEmitter() {
    if (msg.sender !== CFG.EMITTER) {
        throw new Error("ACCESS_DENIED: Только эмитент может выполнить эту операцию");
    }
}

/**
 * Проверка: вызывающий — авторизованный оператор ИС?
 * @throws "NOT_OPERATOR" если не оператор
 */
function onlyOperator() {
    if (!isAuthOp(msg.sender)) {
        throw new Error("ACCESS_DENIED: Только авторизованный оператор ИС");
    }
}

/**
 * Проверка: актив находится в рабочем статусе?
 * @throws "NOT_ACTIVE" если статус не ACTIVE или COUPON_PAID
 */
function onlyActive() {
    if (ST.status !== "ACTIVE" && ST.status !== "COUPON_PAID") {
        throw new Error("STATE_ERROR: Актив не активен. Текущий статус: " + ST.status);
    }
}

/**
 * Проверка: адрес не заморожен?
 * @param {string} address — проверяемый адрес
 * @throws "FROZEN" если адрес заморожен
 */
function notFrozen(address) {
    if (ST.frozen[address] && ST.frozen[address].until > block.timestamp) {
        throw new Error("FROZEN: Адрес заморожен до " + ST.frozen[address].until);
    }
}

/**
 * Проверка: инвестор имеет статус квалифицированного?
 * @param {string} address — проверяемый адрес
 * @throws "NOT_QUALIFIED" если не квалифицирован
 */
function onlyQualified(address) {
    if (CFG.ONLY_QUALIFIED && !ST.holders[address]?.qualified) {
        throw new Error("QUALIFICATION_ERROR: Требуется статус квалифицированного инвестора");
    }
}

// ═══════════════════════════════════════════════════════════════
// 4. ФУНКЦИИ ИНИЦИАЛИЗАЦИИ
// ═══════════════════════════════════════════════════════════════

/**
 * Инициализация актива при создании в блокчейне
 * @param {Object} params — параметры инициализации
 * @param {string} params.emitter — адрес эмитента
 * @param {string} params.settlement — расчётный счёт для выплат
 * @returns {Object} {success, assetId, timestamp}
 */
function init(params) {
    // Установка дат
    CFG.ISSUE_DATE = block.timestamp;
    CFG.FIRST_COUPON_DATE = block.timestamp + (90 * 24 * 3600); // +3 месяца
    CFG.MATURITY_DATE = block.timestamp + (730 * 24 * 3600);    // +24 месяца

    // Установка адресов
    if (params.emitter) {
        CFG.EMITTER = params.emitter;
    }
    if (params.settlement) {
        CFG.SETTLEMENT = params.settlement;
    }

    // Логирование создания
    emit("ASSET_CREATED", {
        name: CFG.NAME,
        ticker: CFG.TICKER,
        quantity: CFG.QUANTITY,
        nominal: CFG.NOMINAL,
        couponRate: CFG.COUPON_RATE,
        issueDate: CFG.ISSUE_DATE,
        maturityDate: CFG.MATURITY_DATE
    });

    return {
        success: true,
        assetId: CFG.TICKER + "_" + block.number,
        timestamp: block.timestamp
    };
}

// ═══════════════════════════════════════════════════════════════
// 5. ФУНКЦИИ УПРАВЛЕНИЯ ВЛАДЕЛЬЦАМИ (KYC / РЕЕСТР)
// ═══════════════════════════════════════════════════════════════

/**
 * Регистрация инвестора в реестре
 * Вызывается оператором ИС после прохождения KYC
 * 
 * @param {string} address — адрес кошелька инвестора
 * @param {Object} kyc — данные KYC
 * @param {boolean} kyc.qualified — статус квалифицированного инвестора
 * @returns {Object} {success}
 */
function registerInvestor(address, kyc) {
    onlyOperator();

    if (ST.holders[address]) {
        throw new Error("ALREADY_REGISTERED: Инвестор уже зарегистрирован");
    }

    ST.holders[address] = {
        balance: 0,
        kycVerified: true,
        qualified: kyc.qualified || false,
        registrationDate: block.timestamp,
        totalReceived: 0,
        lastCoupon: null
    };

    emit("INVESTOR_REGISTERED", {
        address: address,
        qualified: kyc.qualified,
        timestamp: block.timestamp
    });

    return { success: true };
}

/**
 * Обновление статуса квалификации инвестора
 * 
 * @param {string} address — адрес инвестора
 * @param {boolean} isQualified — новый статус
 * @returns {Object} {success}
 */
function updateQualification(address, isQualified) {
    onlyOperator();

    if (!ST.holders[address]) {
        throw new Error("NOT_FOUND: Инвестор не найден");
    }

    ST.holders[address].qualified = isQualified;

    emit("QUALIFICATION_UPDATED", {
        address: address,
        qualified: isQualified
    });

    return { success: true };
}

// ═══════════════════════════════════════════════════════════════
// 6. ФУНКЦИИ ВЫПУСКА И РАЗМЕЩЕНИЯ
// ═══════════════════════════════════════════════════════════════

/**
 * Первичное размещение ЦФА (эмиссия)
 * Распределение ЦФА среди зарегистрированных инвесторов
 * 
 * @param {Array} recipients — список получателей
 * @param {string} recipients[].address — адрес инвестора
 * @param {number} recipients[].amount — количество ЦФА
 * @returns {Object} {success, totalIssued, status}
 */
function primaryPlacement(recipients) {
    onlyEmitter();

    if (ST.status !== "CREATED") {
        throw new Error("STATE_ERROR: Первичное размещение уже проведено");
    }

    let totalAllocated = 0;

    for (const recipient of recipients) {
        const { address, amount } = recipient;

        // Проверка регистрации
        if (!ST.holders[address]) {
            throw new Error("NOT_REGISTERED: Инвестор " + address + " не прошёл KYC");
        }

        // Проверка квалификации
        onlyQualified(address);

        // Проверка заморозки
        notFrozen(address);

        // Проверка лимитов инвестиции
        const newBalance = ST.holders[address].balance + amount;
        const investment = newBalance * CFG.NOMINAL;

        if (investment < CFG.MIN_INVESTMENT) {
            throw new Error("MIN_INVESTMENT: Минимум " + CFG.MIN_INVESTMENT + " RUB");
        }

        if (investment > CFG.MAX_INVESTMENT_PER_INVESTOR) {
            throw new Error("MAX_INVESTMENT: Максимум " + CFG.MAX_INVESTMENT_PER_INVESTOR + " RUB");
        }

        // Начисление ЦФА
        ST.holders[address].balance = newBalance;
        totalAllocated += amount;
    }

    // Проверка общего объёма
    if (totalAllocated > CFG.QUANTITY) {
        throw new Error("OVER_ISSUE: Превышен лимит эмиссии");
    }

    ST.issued = totalAllocated;
    ST.status = "ACTIVE";

    emit("PLACEMENT_COMPLETE", {
        totalIssued: totalAllocated,
        totalInvestors: recipients.length,
        timestamp: block.timestamp
    });

    return {
        success: true,
        totalIssued: totalAllocated,
        status: ST.status
    };
}

// ═══════════════════════════════════════════════════════════════
// 7. ФУНКЦИИ ПЕРЕВОДА (ВТОРИЧНЫЙ ОБОРОТ)
// ═══════════════════════════════════════════════════════════════

/**
 * Перевод ЦФА между владельцами
 * Требует подтверждения оператора ИС (ограничение 259-ФЗ)
 * 
 * @param {string} from — адрес отправителя
 * @param {string} to — адрес получателя
 * @param {number} amount — количество ЦФА
 * @returns {Object} {success, fromBalance, toBalance}
 */
function transfer(from, to, amount) {
    onlyActive();

    // Проверка прав на инициацию перевода
    if (msg.sender !== from && msg.sender !== CFG.EMITTER) {
        throw new Error("ACCESS_DENIED: Только владелец или эмитент может инициировать перевод");
    }

    // Проверка заморозки
    notFrozen(from);
    notFrozen(to);

    // Проверка получателя
    if (!ST.holders[to]) {
        throw new Error("NOT_REGISTERED: Получатель не прошёл KYC");
    }

    // Проверка квалификации получателя
    onlyQualified(to);

    // Проверка баланса отправителя
    if (ST.holders[from].balance < amount) {
        throw new Error("INSUFFICIENT_BALANCE: Недостаточно средств");
    }

    // Проверка лимита получателя
    const newToBalance = ST.holders[to].balance + amount;
    const toInvestment = newToBalance * CFG.NOMINAL;

    if (toInvestment > CFG.MAX_INVESTMENT_PER_INVESTOR) {
        throw new Error("MAX_INVESTMENT: Превышен лимит для получателя");
    }

    // Выполнение перевода
    ST.holders[from].balance -= amount;
    ST.holders[to].balance = newToBalance;

    emit("TRANSFER", {
        from: from,
        to: to,
        amount: amount,
        timestamp: block.timestamp
    });

    return {
        success: true,
        fromBalance: ST.holders[from].balance,
        toBalance: ST.holders[to].balance
    };
}

// ═══════════════════════════════════════════════════════════════
// 8. ФУНКЦИИ КУПОННЫХ ВЫПЛАТ
// ═══════════════════════════════════════════════════════════════

/**
 * Расчёт купона для конкретного владельца
 * Формула: баланс × номинал × ставка / 4 (квартал)
 * 
 * @param {string} address — адрес владельца
 * @returns {number} Сумма купона в RUB
 */
function calculateCoupon(address) {
    const holder = ST.holders[address];
    if (!holder || holder.balance <= 0) {
        return 0;
    }

    const annualCoupon = holder.balance * CFG.NOMINAL * CFG.COUPON_RATE;
    const periodsPerYear = 12 / CFG.COUPON_PERIOD_MONTHS;
    const couponAmount = annualCoupon / periodsPerYear;

    return Math.floor(couponAmount * 100) / 100; // Округление до копеек
}

/**
 * Выплата купонов всем владельцам ЦФА
 * Вызывается эмитентом или автоматически по расписанию
 * 
 * @returns {Object} {success, totalPaid, holdersPaid, paymentRecord}
 */
function payCoupons() {
    onlyEmitter();
    onlyActive();

    // Проверка даты выплаты
    const now = block.timestamp;
    const lastCoupon = ST.coupons.length > 0 
        ? ST.coupons[ST.coupons.length - 1].date 
        : CFG.ISSUE_DATE;

    const daysSinceLastCoupon = (now - lastCoupon) / (24 * 3600);
    const minDays = CFG.COUPON_PERIOD_MONTHS * 30 - 5; // Допуск ±5 дней

    if (daysSinceLastCoupon < minDays) {
        throw new Error("TOO_EARLY: Следующая выплата не ранее " + minDays + " дней");
    }

    let totalPaid = 0;
    let holdersPaid = 0;
    const paymentDetails = [];

    for (const address in ST.holders) {
        const holder = ST.holders[address];
        if (holder.balance <= 0) continue;

        const couponAmount = calculateCoupon(address);
        if (couponAmount <= 0) continue;

        // Начисление купона
        holder.totalReceived += couponAmount;
        holder.lastCoupon = now;

        totalPaid += couponAmount;
        holdersPaid++;

        paymentDetails.push({
            address: address,
            amount: couponAmount,
            balance: holder.balance
        });
    }

    // Запись в историю
    const paymentRecord = {
        date: now,
        amount: totalPaid,
        totalHolders: holdersPaid,
        details: paymentDetails,
        txHash: tx.hash
    };

    ST.coupons.push(paymentRecord);
    ST.totalPaid += totalPaid;
    ST.status = "COUPON_PAID";

    emit("COUPONS_PAID", {
        totalAmount: totalPaid,
        holdersCount: holdersPaid,
        timestamp: now,
        settlementAccount: CFG.SETTLEMENT
    });

    return {
        success: true,
        totalPaid: totalPaid,
        holdersPaid: holdersPaid,
        paymentRecord: paymentRecord
    };
}

/**
 * Автоматическая выплата купонов по расписанию
 * Вызывается блокчейном Эрачейн (onSchedule)
 * 
 * @param {number} scheduledDate — запланированная дата
 * @returns {Object} Результат выплаты или сообщение
 */
function onSchedule(scheduledDate) {
    const nextCouponDate = getNextCouponDate();

    if (Math.abs(scheduledDate - nextCouponDate) < 24 * 3600) { // Допуск 1 день
        try {
            return payCoupons();
        } catch (e) {
            emit("SCHEDULED_PAYMENT_FAILED", {
                error: e.message,
                scheduledDate: scheduledDate
            });
            return { success: false, error: e.message };
        }
    }

    return { success: true, message: "Не время выплаты" };
}

/**
 * Расчёт следующей даты выплаты купона
 * @returns {number} Unix timestamp
 */
function getNextCouponDate() {
    const now = block.timestamp;
    const issueDate = CFG.ISSUE_DATE;
    const periodSeconds = CFG.COUPON_PERIOD_MONTHS * 30 * 24 * 3600;

    let nextDate = issueDate + periodSeconds;
    while (nextDate < now) {
        nextDate += periodSeconds;
    }

    return nextDate;
}

// ═══════════════════════════════════════════════════════════════
// 9. ФУНКЦИИ ПОГАШЕНИЯ
// ═══════════════════════════════════════════════════════════════

/**
 * Досрочное погашение ЦФА (по усмотрению эмитента)
 * Выплачивается номинал + накопленный купон
 * 
 * @param {string} address — адрес владельца
 * @param {number} amount — количество ЦФА к погашению
 * @returns {Object} {success, redeemedAmount, totalPayment}
 */
function earlyRedemption(address, amount) {
    onlyEmitter();
    onlyActive();

    if (!CFG.CALLABLE) {
        throw new Error("NOT_CALLABLE: Досрочное погашение запрещено");
    }

    notFrozen(address);

    const holder = ST.holders[address];
    if (!holder || holder.balance < amount) {
        throw new Error("INSUFFICIENT_BALANCE");
    }

    // Расчёт суммы погашения
    const nominalValue = amount * CFG.NOMINAL;
    const accruedCoupon = calculateAccruedCoupon(address);
    const totalRedemption = nominalValue + accruedCoupon;

    // Сжигание ЦФА
    holder.balance -= amount;
    ST.burned += amount;

    emit("EARLY_REDEMPTION", {
        address: address,
        amount: amount,
        nominalValue: nominalValue,
        accruedCoupon: accruedCoupon,
        totalRedemption: totalRedemption,
        timestamp: block.timestamp
    });

    return {
        success: true,
        redeemedAmount: amount,
        totalPayment: totalRedemption
    };
}

/**
 * Погашение по окончании срока (автоматическое)
 * Вызывается при достижении MATURITY_DATE
 * 
 * @returns {Object} {success, totalRedeemed, holdersRedeemed}
 */
function onMaturity() {
    const now = block.timestamp;

    if (now < CFG.MATURITY_DATE) {
        throw new Error("NOT_MATURED: Срок погашения не наступил");
    }

    if (ST.status === "MATURED" || ST.status === "BURNED") {
        throw new Error("ALREADY_MATURED");
    }

    let totalRedeemed = 0;
    let holdersRedeemed = 0;

    for (const address in ST.holders) {
        const holder = ST.holders[address];
        if (holder.balance <= 0) continue;

        const amount = holder.balance;
        const nominalValue = amount * CFG.NOMINAL;
        const finalCoupon = calculateCoupon(address);
        const totalPayment = nominalValue + finalCoupon;

        // Сжигание
        holder.balance = 0;
        ST.burned += amount;

        totalRedeemed += totalPayment;
        holdersRedeemed++;

        emit("MATURITY_REDEMPTION", {
            address: address,
            amount: amount,
            totalPayment: totalPayment
        });
    }

    ST.status = "MATURED";

    emit("MATURITY_COMPLETE", {
        totalRedeemed: totalRedeemed,
        holdersRedeemed: holdersRedeemed,
        timestamp: now
    });

    return {
        success: true,
        totalRedeemed: totalRedeemed,
        holdersRedeemed: holdersRedeemed
    };
}

/**
 * Расчёт накопленного купона (для досрочного погашения)
 * Пропорционально дням с последней выплаты
 * 
 * @param {string} address — адрес владельца
 * @returns {number} Накопленный купон в RUB
 */
function calculateAccruedCoupon(address) {
    const holder = ST.holders[address];
    if (!holder || holder.balance <= 0) return 0;

    const now = block.timestamp;
    const lastCoupon = holder.lastCoupon || CFG.ISSUE_DATE;
    const daysSinceLastCoupon = (now - lastCoupon) / (24 * 3600);

    const annualCoupon = holder.balance * CFG.NOMINAL * CFG.COUPON_RATE;
    const accrued = (annualCoupon * daysSinceLastCoupon) / 365;

    return Math.floor(accrued * 100) / 100;
}

// ═══════════════════════════════════════════════════════════════
// 10. ФУНКЦИИ ЗАМОРОЗКИ И ОГРАНИЧЕНИЯ
// ═══════════════════════════════════════════════════════════════

/**
 * Заморозка адреса по решению суда или требованию регулятора
 * 
 * @param {string} address — замораживаемый адрес
 * @param {number} until — дата окончания заморозки (Unix timestamp)
 * @param {string} reason — причина заморозки
 * @returns {Object} {success}
 */
function freezeAddress(address, until, reason) {
    onlyEmitter();

    ST.frozen[address] = {
        until: until,
        reason: reason,
        frozenAt: block.timestamp
    };

    emit("ADDRESS_FROZEN", {
        address: address,
        until: until,
        reason: reason
    });

    return { success: true };
}

/**
 * Разморозка адреса
 * 
 * @param {string} address — размораживаемый адрес
 * @returns {Object} {success}
 */
function unfreezeAddress(address) {
    onlyEmitter();

    delete ST.frozen[address];

    emit("ADDRESS_UNFROZEN", {
        address: address,
        timestamp: block.timestamp
    });

    return { success: true };
}

// ═══════════════════════════════════════════════════════════════
// 11. ФУНКЦИИ ГОЛОСОВАНИЯ (КОРПОРАТИВНЫЕ ДЕЙСТВИЯ)
// ═══════════════════════════════════════════════════════════════

/**
 * Создание голосования среди владельцев ЦФА
 * 
 * @param {string} voteId — уникальный идентификатор голосования
 * @param {string} topic — тема голосования
 * @param {Array} options — варианты ответов
 * @param {number} deadline — дата окончания (Unix timestamp)
 * @returns {Object} {success}
 */
function createVote(voteId, topic, options, deadline) {
    onlyEmitter();

    if (ST.votes[voteId]) {
        throw new Error("VOTE_EXISTS: Голосование уже существует");
    }

    ST.votes[voteId] = {
        topic: topic,
        options: options,
        votes: {},
        deadline: deadline,
        createdAt: block.timestamp,
        status: "ACTIVE"
    };

    emit("VOTE_CREATED", {
        voteId: voteId,
        topic: topic,
        deadline: deadline
    });

    return { success: true };
}

/**
 * Голосование владельцем ЦФА
 * Вес голоса пропорционален количеству ЦФА
 * 
 * @param {string} voteId — идентификатор голосования
 * @param {number} optionIndex — индекс выбранного варианта
 * @returns {Object} {success}
 */
function castVote(voteId, optionIndex) {
    onlyActive();
    notFrozen(msg.sender);

    const vote = ST.votes[voteId];
    if (!vote) {
        throw new Error("VOTE_NOT_FOUND");
    }

    if (block.timestamp > vote.deadline) {
        throw new Error("VOTE_CLOSED");
    }

    const holder = ST.holders[msg.sender];
    if (!holder || holder.balance <= 0) {
        throw new Error("NO_BALANCE: Только владельцы ЦФА могут голосовать");
    }

    if (optionIndex < 0 || optionIndex >= vote.options.length) {
        throw new Error("INVALID_OPTION");
    }

    vote.votes[msg.sender] = {
        optionIndex: optionIndex,
        weight: holder.balance,
        timestamp: block.timestamp
    };

    emit("VOTE_CAST", {
        voteId: voteId,
        voter: msg.sender,
        option: optionIndex,
        weight: holder.balance
    });

    return { success: true };
}

/**
 * Подведение итогов голосования
 * 
 * @param {string} voteId — идентификатор голосования
 * @returns {Object} {success, results, totalVotes}
 */
function tallyVotes(voteId) {
    onlyEmitter();

    const vote = ST.votes[voteId];
    if (!vote) {
        throw new Error("VOTE_NOT_FOUND");
    }

    if (block.timestamp <= vote.deadline) {
        throw new Error("VOTE_STILL_OPEN");
    }

    const results = {};
    for (let i = 0; i < vote.options.length; i++) {
        results[i] = 0;
    }

    let totalVotes = 0;
    for (const voter in vote.votes) {
        const v = vote.votes[voter];
        results[v.optionIndex] += v.weight;
        totalVotes += v.weight;
    }

    vote.status = "CLOSED";
    vote.results = results;
    vote.totalVotes = totalVotes;

    emit("VOTE_TALLIED", {
        voteId: voteId,
        results: results,
        totalVotes: totalVotes
    });

    return {
        success: true,
        results: results,
        totalVotes: totalVotes
    };
}

// ═══════════════════════════════════════════════════════════════
// 12. ФУНКЦИИ ПРОСМОТРА (VIEW / GETTER)
// ═══════════════════════════════════════════════════════════════

/**
 * Получение баланса владельца
 * @param {string} address — адрес
 * @returns {number} Баланс в единицах ЦФА
 */
function getBalance(address) {
    const holder = ST.holders[address];
    return holder ? holder.balance : 0;
}

/**
 * Полная информация о владельце
 * @param {string} address — адрес
 * @returns {Object|null} Информация о владельце
 */
function getHolderInfo(address) {
    const holder = ST.holders[address];
    if (!holder) return null;

    return {
        balance: holder.balance,
        kycVerified: holder.kycVerified,
        qualified: holder.qualified,
        totalCouponsReceived: holder.totalReceived,
        lastCouponDate: holder.lastCoupon,
        currentCoupon: calculateCoupon(address),
        accruedCoupon: calculateAccruedCoupon(address),
        isFrozen: ST.frozen[address] ? true : false
    };
}

/**
 * Общая информация об активе
 * @returns {Object} Параметры актива и статистика
 */
function getAssetInfo() {
    return {
        name: CFG.NAME,
        ticker: CFG.TICKER,
        nominal: CFG.NOMINAL,
        quantity: CFG.QUANTITY,
        issued: ST.issued,
        burned: ST.burned,
        circulating: ST.issued - ST.burned,
        couponRate: CFG.COUPON_RATE,
        couponPeriod: CFG.COUPON_PERIOD_MONTHS,
        maturityMonths: CFG.MATURITY_MONTHS,
        issueDate: CFG.ISSUE_DATE,
        maturityDate: CFG.MATURITY_DATE,
        status: ST.status,
        totalCouponsPaid: ST.totalPaid,
        nextCouponDate: getNextCouponDate()
    };
}

/**
 * История купонных выплат
 * @returns {Array} Массив записей о выплатах
 */
function getCouponHistory() {
    return ST.coupons;
}

// ═══════════════════════════════════════════════════════════════
// 13. ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
// ═══════════════════════════════════════════════════════════════

/**
 * Генерация уникального ID актива
 * @returns {string} Asset ID
 */
function generateAssetId() {
    return CFG.TICKER + "_" + block.number + "_" + Math.floor(Math.random() * 1000000);
}

/**
 * Проверка авторизации оператора ИС
 * @param {string} address — проверяемый адрес
 * @returns {boolean} Авторизован ли оператор
 */
function isAuthOp(address) {
    // Проверка через реестр операторов ИС
    // В продакшене заменить на реальную проверку
    return true;
}

/**
 * Запись события в блокчейн
 * @param {string} event — название события
 * @param {Object} data — данные события
 */
function emit(event, data) {
    log("EracoinEvent", {
        event: event,
        data: data,
        block: block.number,
        timestamp: block.timestamp
    });
}

// ═══════════════════════════════════════════════════════════════
// 14. ОБРАБОТЧИКИ СОБЫТИЙ БЛОКЧЕЙНА
// ═══════════════════════════════════════════════════════════════

/**
 * Вызывается при каждом новом блоке
 * Проверяет необходимость автоматических действий:
 * - Погашение по окончании срока
 * - Выплата купонов по расписанию
 * - Автоматическая разморозка адресов
 */
function onBlock() {
    const now = block.timestamp;

    // Проверка наступления срока погашения
    if (now >= CFG.MATURITY_DATE && ST.status !== "MATURED") {
        try {
            onMaturity();
        } catch (e) {
            emit("MATURITY_FAILED", { error: e.message });
        }
    }

    // Проверка автоматической выплаты купонов
    const nextCoupon = getNextCouponDate();
    if (Math.abs(now - nextCoupon) < 24 * 3600 && ST.status === "ACTIVE") {
        try {
            payCoupons();
        } catch (e) {
            emit("AUTO_COUPON_FAILED", { error: e.message });
        }
    }

    // Проверка окончания заморозок
    for (const address in ST.frozen) {
        if (ST.frozen[address].until <= now) {
            delete ST.frozen[address];
            emit("AUTO_UNFROZEN", { address: address });
        }
    }
}

// ═══════════════════════════════════════════════════════════════
// 15. ЭКСПОРТ (для взаимодействия с внешними системами)
// ═══════════════════════════════════════════════════════════════

module.exports = {
    // Инициализация
    init,

    // KYC / Реестр
    registerInvestor,
    updateQualification,

    // Эмиссия
    primaryPlacement,

    // Переводы
    transfer,

    // Купоны
    calculateCoupon,
    payCoupons,
    onSchedule,
    getNextCouponDate,

    // Погашение
    earlyRedemption,
    onMaturity,
    calculateAccruedCoupon,

    // Заморозка
    freezeAddress,
    unfreezeAddress,

    // Голосования
    createVote,
    castVote,
    tallyVotes,

    // Просмотр
    getBalance,
    getHolderInfo,
    getAssetInfo,
    getCouponHistory,

    // События
    onBlock
};

Описание функций

Управление доступом

ФункцияОписаниеКто вызывает
onlyEmitter()Проверка, что вызывающий — эмитентВнутренняя
onlyOperator()Проверка, что вызывающий — оператор ИСВнутренняя
onlyActive()Проверка активного статуса активаВнутренняя
notFrozen(address)Проверка отсутствия заморозкиВнутренняя
onlyQualified(address)Проверка квалификации инвестораВнутренняя

Жизненный цикл актива

ФункцияОписаниеКто вызывает
init(params)Инициализация актива при созданииАвтоматически
registerInvestor(address, kyc)Регистрация инвестора (KYC)Оператор ИС
updateQualification(address, isQualified)Обновление квалификацииОператор ИС
primaryPlacement(recipients)Первичное размещение ЦФАЭмитент
transfer(from, to, amount)Перевод ЦФАВладелец/эмитент

Финансовые операции

ФункцияОписаниеКто вызывает
calculateCoupon(address)Расчёт купона для владельцаЛюбой
payCoupons()Выплата купонов всем владельцамЭмитент
onSchedule(date)Автоматическая выплата по расписаниюБлокчейн
earlyRedemption(address, amount)Досрочное погашениеЭмитент
onMaturity()Погашение по окончании срокаАвтоматически
calculateAccruedCoupon(address)Накопленный купонЛюбой

Управление и корпоративные действия

ФункцияОписаниеКто вызывает
freezeAddress(address, until, reason)Заморозка адресаЭмитент
unfreezeAddress(address)Разморозка адресаЭмитент
createVote(voteId, topic, options, deadline)Создание голосованияЭмитент
castVote(voteId, optionIndex)ГолосованиеВладелец ЦФА
tallyVotes(voteId)Подведение итоговЭмитент

Функции просмотра (без изменения состояния)

ФункцияОписаниеВозвращает
getBalance(address)Баланс владельцаnumber
getHolderInfo(address)Полная информация о владельцеObject | null
getAssetInfo()Информация об активеObject
getCouponHistory()История выплатArray
getNextCouponDate()Следующая дата выплатыnumber

Размещение в блокчейне

Создание актива через CLI ноды

java -jar erachain.jar -cli
post wallet/unlock ВАШ_ПАРОЛЬ
post issueasset {
  "name": "Цифровые облигации Eracoin серия 001",
  "ticker": "ERACOIN001",
  "quantity": 50000,
  "scale": 2,
  "description": "Купон 14% годовых, срок 24 мес.",
  "movable": true,
  "divisible": true,
  "script": "file:/путь/к/eracoin_contract.js",
  "fee": 1000000
}

Создание актива через REST API

curl -X POST http://127.0.0.1:9067/api/assets   -H "Content-Type: application/json"   -d '{
    "creator": "АДРЕС_ЭМИТЕНТА",
    "name": "Цифровые облигации Eracoin серия 001",
    "ticker": "ERACOIN001",
    "quantity": 50000,
    "scale": 2,
    "description": "Купон 14% годовых, срок 24 мес.",
    "movable": true,
    "divisible": true,
    "script": "const CFG = {...}; const ST = {...}; ...",
    "fee": 1000000,
    "signature": "..."
  }'

Примеры вызовов

Инициализация контракта

post callasset ERACOIN001 init '{
  "emitter": "7NhZBb8CeLtcD...",
  "settlement": "40802810..."
}'

Регистрация инвестора

post callasset ERACOIN001 registerInvestor '{
  "address": "8MiACc9DfMueE2b...",
  "kyc": {
    "qualified": true
  }
}'

Первичное размещение

post callasset ERACOIN001 primaryPlacement '{
  "recipients": [
    {"address": "8MiACc9DfMueE2b...", "amount": 10000},
    {"address": "9NjBDd0EgNvfF3c...", "amount": 5000}
  ]
}'

Выплата купонов

post callasset ERACOIN001 payCoupons '{}'

Проверка информации

get asset/ERACOIN001
get asset/ERACOIN001/state
get addresses/asset/ERACOIN001/8MiACc9DfMueE2b...

Лицензия

MIT License

Copyright (c) 2026

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.


Документ подготовлен для публикации. Блокчейн: Erachain. Соответствует требованиям 259-ФЗ "О цифровых финансовых активах".

Comments

Sign in to leave a comment
Loading files...
Loading attachments...
Смарт-контракт ЦФА Eracoin | Academic Lab