Документы
Документ — это объект, несущий юридически-значимую информацию, необходимую для работы кооператива и подтверждения прав пайщиков или других участников. Взаимодействие с документом включает в себя его подготовку, подписание и публикацию в блокчейне. При этом документ всегда состоит из двух частей:
Публичная часть
— хранится в блокчейне в анонимизированном виде, не раскрывает конфиденциальных данных пайщика и включает минимальный набор сведений, достаточный для проверки подлинности документа. При наличии приватных данных, хранящихся вне блокчейна, этот документ может быть полностью восстановлен.
Приватная часть
— хранится во внутренней базе данных платформы MONO. Эта часть содержит личные или иные конфиденциальные данные пайщика, информацию о содержании документа, подписи, дополнительные детали и всё то, что не должно публиковаться публично. Из неё же берётся необходимая информация для генерации, регенерации и сверки документа с его публичной частью, которая была зафиксирована в блокчейне.
При этом все документы, публикуемые в блокчейне, анонимизированно фиксируются через соответствующие смарт-контракты, что обеспечивает прозрачный и неизменяемый учёт без раскрытия приватных сведений.
flowchart TD
A("Цифровой Документ")
B("Обезличенная часть")
C("Приватная часть")
A -->|зафиксировано в блокчейне| B
A -->|хранится в MONO| C
Фиксация документов¶
Каждый раз, когда пользователь (пайщик) формирует документ, он генерируется платформой MONO на основании данных, которые уже есть во внутренней базе кооператива. При генерации создаётся как PDF-версия (бинарное представление), так и HTML-версия (для удобного отображения в интерфейсе). Параллельно формируется hash PDF-файла и метаданные, необходимые для будущей регенерации.
Когда пользователь подписывает сформированный документ (PDF-хэш), подпись вместе с публичным ключом и мета-данными отправляется обратно на платформу MONO, и уже MONO публикует эту информацию в блокчейн через вызов соответствующих мутаций смарт-контрактов.
Таким образом, пайщик напрямую не отправляет документ в блокчейн: за него это делает MONO, храня всю детальную информацию у себя, но публикуя в блокчейне лишь зашифрованные или анонимизированные данные, достаточные для удостоверения подлинности документа.
Структура данных в блокчейне¶
Смарт-контракты в блокчейне фиксируют документ в обезличенном виде. Вызов мутации, отвечающей за публикацию документа, сохраняет в блокчейне следующую структуру:
interface ISignedDocument {
hash: <string> // Хэш-сумма подписанного PDF-документа
signature: <string> // Подпись хэш-суммы
public_key: <string> // Публичный ключ, которым подписан документ
meta: <string> // Строка с JSON-объектом мета-информации о документе
}
Где hash
- позволяет однозначно идентифицировать документ, signature
и public_key
обеспечивают криптографическую верификацию подлинности подписи, а meta
содержит дополнительную информацию (в формате JSON), позволяющую в будущем восстановить содержание документа (используя данные из внутренней базы MONO).
Таким образом, вся конфиденциальная часть документа (тексты, личные данные, детали договора и т.д.) не вводятся в публичный реестр блокчейна. Блокчейн хранит лишь контрольные суммы и ограниченный набор открытых сведений, необходимые для проверки и восстановления документа.
Процесс генерации и подписания документа¶
sequenceDiagram
participant U as Пайщик (Пользователь)
participant M as MONO
participant BC as Блокчейн (смарт-контракт)
U->>M: 1) Запрос на генерацию документа (Generate...)
M->>U: Возвращает IGeneratedDocument <br/>(PDF/HTML, хэш, метаданные)
U->>U: 2) Подписывает документ<br/>(hash) приватным ключом
U->>M: Передаёт подписанный документ (signature, public_key)
M->>BC: 3) Публикует ISignedDocument<br/>(hash, signature, public_key, meta)
BC-->>M: Извлекает и сохраняет публичные данные (хэш и пр.)
1. Инициирующее действие (заявление)
Пользователь инициирует процесс, вызывая мутацию с префиксом Generate.
В ответ MONO
формирует документ:
interface IGeneratedDocument {
binary: <string> // base64-кодированное содержимое PDF
full_title: <string> // Полное название документа
hash: <string> // Хэш-сумма PDF-файла
html: <string> // HTML-версия документа
meta: <MetaDocument> // Объект метаданных
}
2. Подпись документа
На полученный hash
документ пользователь накладывает цифровую подпись с помощью своего приватного ключа:
import { Wallet } from '@coopenomics/sdk'
const wallet = new Wallet({
chain_url: <string>, // конечная точка блокчейна
chain_id: <string>, // идентификатор блокчейна
})
wallet.setWif(<username>, <wif>) //установить имя пользователя и приватный ключ для подписи
//подписать документ
const signedDocument = wallet.signDocument(generatedDocument)
3. Публикация в блокчейне
Пользователь вызывает «публикующую» мутацию MONO
и передаёт подписанный документ в качестве аргумента. MONO
производит необходимые проверки, после чего, публикует документ в блокчейне.
В результате, публичные данные документа (хэш, подпись, публичный ключ и мета-данные) фиксируются в блокчейне, а кооперативный смарт-контракт, который связан с опубликованным документом, продолжает своё исполнение по ходу своей бизнес-логики.
Пакет документов¶
Пакет документов представляет собой набор всех документов, относящихся к определённому процессу внутри кооператива (например, вступление в кооператив, взнос или возврат имущества имущества, подписание соглашений и т.д.).
Минимальный пакет всегда содержит хотя бы одно заявление (StatementDetail), опубликованное в блокчейне. В зависимости от бизнес-логики смарт-контракта, к заявлению могут добавляться и другие документы:
-
Протокол Решения (DecisionDetail), содержащий данные о том, как совет кооператива принял решение по заявлению.
-
Акты (ActDetail), завершающие юридически значимые действия, например, передача имущества.
-
Связанные документы (GeneratedDocument), которые могут прикладываться к заявлению и подписываться вместе с ним (соглашения об использовании платформы, политике конфиденциальности и т.д.).
MONO
собирает документы, связанные с исходным заявлением, в единый пакет. В этом пакете сохраняются как публичные данные, извлечённые из блокчейна (подписи, хэши, статусы), так и приватные данные (конкретное содержимое документов), которые могут быть показаны только тем, кто имеет право их видеть (пайщик, члены совета, председатель и т.д.).
Если после публикации документа в блокчейне с помощью соответствующей мутации, логика смарт-контракта подразумевает, что необходимо собрать совет кооператива для голосования по вопросу в заявлении, то автоматически создаётся объект будущего протокола решения (DecisionDetail), в котором фиксируется процесс голосования и утверждения.
Структурно пакет может выглядеть так, как показано в диаграмме (см. «Структурная диаграмма пакета документов»).
graph LR
A["📂 Пакет документов (DocumentPackage)"] -->|Содержит| B["📄 Заявление (StatementDetail)"]
A -->|Может содержать| C["📜 Протокол Решения (DecisionDetail)"]
A -->|Может содержать| D["📜 Акты (ActDetail)"]
A -->|Может содержать| E["🔗 Связанные документы (GeneratedDocument)"]
B -->|Основано на| F["⚡ Расширенное действие в блокчейне (ExtendedBlockchainAction)"]
B -->|Содержит| G["📑 Документ заявления (GeneratedDocument)"]
C -->|Основано на| H["⚡ Расширенное действие в блокчейне (ExtendedBlockchainAction)"]
C -->|Содержит| I["📑 Документ протокола решения (GeneratedDocument)"]
C -->|Голоса| J["✅ Голоса 'за' (ExtendedBlockchainAction)"] & K["❌ Голоса 'против' (ExtendedBlockchainAction)"]
D -->|Может содержать| L["⚡ Расширенное действие в блокчейне (ExtendedBlockchainAction)"]
D -->|Связан с| M["📑 Документ акта (GeneratedDocument)"]
F -->|Содержит| Z["👤 Информацию о заявителе"]
H -->|Содержит| X["👤 Информацию о председателе"]
J & K -->|Содержит| O["👤 Информацию о члене совета"]
L -->|Содержит| MM["👤 Информацию о председателе (КУ)"]
L -->|Содержит| NN["👤 Информацию о пайщике"]
Статусы документов¶
При публикации в блокчейне каждый документ, в зависимости от контекста и бизнес-логики, получает статус:
-
submitted
(входящий) — документ ещё не завершён, ему требуется решение совета или иное дальнейшее действие. -
resolved
(принятый) — документ получил финальную юридическую силу. Это означает, что все необходимые действия выполнены, голоса совета собраны (если были нужны), акт подписан (если требовалось), и смарт-контракт отметил его как завершённый. -
rejected
(отклонённый) - документ отклонён. Это означает, что изначально входящий документ отклонён в процессе рассмотрения советом.
flowchart LR
A("submitted (входящий)") --> B("rejected (отклоненный)")
A --> C("resolved (принятый)")
При этом все статусы навсегда сохраняются в блокчейне для каждого документа. Даже если документ, находящийся в статусе submitted
, в итоге отклонён советом, он всё равно останется в этом статусе (и дополнительно переходит в rejected
), так как данные в публичном реестре не редактируются и не удаляются задним числом.
Соответственно:
-
Входящий (
submitted
) документ отражает факт, что не все требования закрыты и решение по нему ещё может быть не принято либо принято отрицательно (но мы увидим это не по статусу, а по факту отсутствия закрывающего действия). -
Принятый (
resolved
) документ свидетельствует о завершённом процессе и имеет всю юридическую силу в реестре кооператива. -
Отклоненный (
rejected
) документ свидетельствует о прерванном процессе и не имеет юридической силы.
Регенерация документов и хранение в MONO¶
Внутренняя база MONO хранит всё документы, их приватные части, исторические данные, а также привязку к хэшам, которые были опубликованы в блокчейне. Когда пользователь или член совета запрашивает пакет документов, MONO
:
-
Извлекает публичные данные по документам из блокчейна (через смарт-контракты).
-
Находит приватные данные в своей БД, сопоставляя их по
hash
. -
Собирает итоговый пакет, в котором каждому опубликованному действию (ExtendedBlockchainAction) соответствует документ с нужными вложениями (PDF, HTML, метаданные).
Благодаря этому подходу, платформа может регенерировать любой документ в том же виде, в котором он был подписан и отправлен в блокчейн, и сверить заново сгенерированный документ с тем, который был опубликован в блокчейне, тем самым, верифицировав то, что документ создан корректно, а цифровая подпись на нём - валидна.
Связанные документы¶
Часто к заявлению или другому документу требуется приложить дополнительные договоры, согласия, соглашения и прочие документы, которые, в свою очередь, могут быть подписаны и закреплены криптографически. Это делается путём добавления в поле meta
списка (массива) хэш-сумм связанных документов. Подписывая один документ, пайщик тем самым подтверждает связь с другими документами и своё согласие с ними именно в той версии, с которой он ознакомлен.
Пример: когда пользователь вступает в кооператив, он одновременно потверждает согласие с политикой конфиденциальности, условиями целевой программы «Цифрового Кошелька», а также правила использования простой электронной подписи и пользовательское соглашение. Хэши всех этих документов указываются в поле meta
инициирующего заявления на вступление в кооператив, а подробное содержимое каждого документа хранится в базе MONO
.
В итоге, вся последовательность от первого инициирующего заявления до заключительного акта, если он предусмотрен бизнес-логикой, превращается в связный пакет документов:
flowchart LR
Z("Инициирующее заявление<br/>(submitted)") -->|Если требуется решение совета| A("Собрание совета и голосование")
Z -->|Если решение совета не требуется| B("Перевод<br/>в resolved")
A -->|Решение принято| D("Подпись актов (при необходимости)")
A -->|Решение не принято| C("Отклонение -> rejected")
D -->|Подписи получены| B("resolved")
-
Заявление (StatementDetail) и его подпись в блокчейне инициирует цепочку принятия решений или сразу переводит документ в статус
resolved
(принятое), если решение по вопросу не требуется; -
Протокол решения (DecisionDetail) собирает голоса членов совета и ожидает утверждения подписью председателя, если это предусмотрено смарт-контрактом как реакция на инициирующее заявление;
-
Акты (ActDetail) подписывается пайщиком и председателем (кооперативного участка), если такое требование есть у смарт-контракта как реакция на инициирующее заявление. Крайняя подпись на акте переводит документ в статус
resolved
и фиксирует его в реестре; -
Прикреплённые (связанные) документы, позволяющие зафиксировать другие важные файлы в рамках этого же пакета;
Такой пакет становится частью списка всех «принятых» решений и документов кооператива, который в любой момент может быть выгружен для проведения аудита, юридических проверок или отчетности перед надзорными органами.
Получить список пакетов документов¶
🛠️ SDK: Queries.Documents.GetDocuments | 🔗 GraphQL API: Query.getDocuments
import { Queries } from '@coopenomics/sdk';
const variables = {
data: {
filter: {
additionalFilters?: <any>;
receiver: <string>;
};
limit?: <null | number>;
page?: <null | number>;
type?: <null | string>;
};
};
const { [Queries.Documents.GetDocuments.name]: result } = await client.Query(
Queries.Documents.GetDocuments.query,
{ variables }
);
Результат исполнения:
interface IOutput {
getDocuments: {
currentPage: <number>; // Текущая страница
items: <{
acts: <{
action?: {
account: <string>;
account_ram_deltas: <{
account: <string>;
delta: <number>;
}[]>;
action_ordinal: <number>;
authorization: <{
actor: <string>;
permission: <string>;
}[]>;
block_id: <string>;
block_num: <number>;
chain_id: <string>;
console: <string>;
context_free: <boolean>;
creator_action_ordinal: <number>;
data: <unknown>; // Данные действия в формате JSON
elapsed: <number>;
global_sequence: <string>;
name: <string>;
receipt: {
abi_sequence: <number>;
act_digest: <string>;
auth_sequence: <{
account: <string>;
sequence: <string>;
}[]>;
code_sequence: <number>;
global_sequence: <string>;
receiver: <string>;
recv_sequence: <string>;
};
receiver: <string>;
transaction_id: <string>;
user?: <({ username: string; email: string; birthdate: string; first_name: string; full_address: string; last_name: string; middle_name: string; phone: string; passport?: { number: number; code: string; issued_at: string; issued_by: string; series: number; } | undefined; } | { ...; } | { ...; }) & {}>; // Доп. данные о пользователе (физ/ИП/организация)
};
document?: {
binary: <string>; // Бинарное содержимое документа (base64)
full_title: <string>; // Полное название документа
hash: <string>; // Хэш документа
html: <string>; // HTML содержимое документа
meta: {
block_num: <number>; // Номер блока, на котором был создан документ
coopname: <string>; // Название кооператива, связанное с документом
created_at: <string>; // Дата и время создания документа
generator: <string>; // Имя генератора, использованного для создания документа
lang: <ru>; // Язык документа
links: <string[]>; // Ссылки, связанные с документом
registry_id: <number>; // ID документа в реестре
timezone: <string>; // Часовой пояс, в котором был создан документ
title: <string>; // Название документа
username: <string>; // Имя пользователя, создавшего документ
version: <string>; // Версия генератора, использованного для создания документа
};
};
}[]>; // Массив объект(ов) актов, относящихся к заявлению
decision: {
action: {
account: <string>;
account_ram_deltas: <{
account: <string>;
delta: <number>;
}[]>;
action_ordinal: <number>;
authorization: <{
actor: <string>;
permission: <string>;
}[]>;
block_id: <string>;
block_num: <number>;
chain_id: <string>;
console: <string>;
context_free: <boolean>;
creator_action_ordinal: <number>;
data: <unknown>; // Данные действия в формате JSON
elapsed: <number>;
global_sequence: <string>;
name: <string>;
receipt: {
abi_sequence: <number>;
act_digest: <string>;
auth_sequence: <{
account: <string>;
sequence: <string>;
}[]>;
code_sequence: <number>;
global_sequence: <string>;
receiver: <string>;
recv_sequence: <string>;
};
receiver: <string>;
transaction_id: <string>;
user?: <({ username: string; email: string; birthdate: string; first_name: string; full_address: string; last_name: string; middle_name: string; phone: string; passport?: { number: number; code: string; issued_at: string; issued_by: string; series: number; } | undefined; } | { ...; } | { ...; }) & {}>; // Доп. данные о пользователе (физ/ИП/организация)
};
document: <unknown>;
votes_against: <{
account: <string>;
account_ram_deltas: <{
account: <string>;
delta: <number>;
}[]>;
action_ordinal: <number>;
authorization: <{
actor: <string>;
permission: <string>;
}[]>;
block_id: <string>;
block_num: <number>;
chain_id: <string>;
console: <string>;
context_free: <boolean>;
creator_action_ordinal: <number>;
data: <unknown>; // Данные действия в формате JSON
elapsed: <number>;
global_sequence: <string>;
name: <string>;
receipt: {
abi_sequence: <number>;
act_digest: <string>;
auth_sequence: <{
account: <string>;
sequence: <string>;
}[]>;
code_sequence: <number>;
global_sequence: <string>;
receiver: <string>;
recv_sequence: <string>;
};
receiver: <string>;
transaction_id: <string>;
user?: <({ username: string; email: string; birthdate: string; first_name: string; full_address: string; last_name: string; middle_name: string; phone: string; passport?: { number: number; code: string; issued_at: string; issued_by: string; series: number; } | undefined; } | { ...; } | { ...; }) & {}>; // Доп. данные о пользователе (физ/ИП/организация)
}[]>;
votes_for: <{
account: <string>;
account_ram_deltas: <{
account: <string>;
delta: <number>;
}[]>;
action_ordinal: <number>;
authorization: <{
actor: <string>;
permission: <string>;
}[]>;
block_id: <string>;
block_num: <number>;
chain_id: <string>;
console: <string>;
context_free: <boolean>;
creator_action_ordinal: <number>;
data: <unknown>; // Данные действия в формате JSON
elapsed: <number>;
global_sequence: <string>;
name: <string>;
receipt: {
abi_sequence: <number>;
act_digest: <string>;
auth_sequence: <{
account: <string>;
sequence: <string>;
}[]>;
code_sequence: <number>;
global_sequence: <string>;
receiver: <string>;
recv_sequence: <string>;
};
receiver: <string>;
transaction_id: <string>;
user?: <({ username: string; email: string; birthdate: string; first_name: string; full_address: string; last_name: string; middle_name: string; phone: string; passport?: { number: number; code: string; issued_at: string; issued_by: string; series: number; } | undefined; } | { ...; } | { ...; }) & {}>; // Доп. данные о пользователе (физ/ИП/организация)
}[]>;
};
links: <{
binary: <string>; // Бинарное содержимое документа (base64)
full_title: <string>; // Полное название документа
hash: <string>; // Хэш документа
html: <string>; // HTML содержимое документа
meta: {
block_num: <number>; // Номер блока, на котором был создан документ
coopname: <string>; // Название кооператива, связанное с документом
created_at: <string>; // Дата и время создания документа
generator: <string>; // Имя генератора, использованного для создания документа
lang: <ru>; // Язык документа
links: <string[]>; // Ссылки, связанные с документом
registry_id: <number>; // ID документа в реестре
timezone: <string>; // Часовой пояс, в котором был создан документ
title: <string>; // Название документа
username: <string>; // Имя пользователя, создавшего документ
version: <string>; // Версия генератора, использованного для создания документа
};
}[]>; // Массив связанных документов, извлечённых из мета-данных
statement: {
action: {
account: <string>;
account_ram_deltas: <{
account: <string>;
delta: <number>;
}[]>;
action_ordinal: <number>;
authorization: <{
actor: <string>;
permission: <string>;
}[]>;
block_id: <string>;
block_num: <number>;
chain_id: <string>;
console: <string>;
context_free: <boolean>;
creator_action_ordinal: <number>;
data: <unknown>; // Данные действия в формате JSON
elapsed: <number>;
global_sequence: <string>;
name: <string>;
receipt: {
abi_sequence: <number>;
act_digest: <string>;
auth_sequence: <{
account: <string>;
sequence: <string>;
}[]>;
code_sequence: <number>;
global_sequence: <string>;
receiver: <string>;
recv_sequence: <string>;
};
receiver: <string>;
transaction_id: <string>;
user?: <({ username: string; email: string; birthdate: string; first_name: string; full_address: string; last_name: string; middle_name: string; phone: string; passport?: { number: number; code: string; issued_at: string; issued_by: string; series: number; } | undefined; } | { ...; } | { ...; }) & {}>; // Доп. данные о пользователе (физ/ИП/организация)
};
document: <unknown>;
};
}[]>; // Элементы текущей страницы
totalCount: <number>; // Общее количество элементов
totalPages: <number>; // Общее количество страниц
};
}