Перейти к содержанию

Документы

Документ — это объект, несущий юридически-значимую информацию, необходимую для работы кооператива и подтверждения прав пайщиков или других участников. Взаимодействие с документом включает в себя его подготовку, подписание и публикацию в блокчейне. При этом документ всегда состоит из двух частей:

Публичная часть — хранится в блокчейне в анонимизированном виде, не раскрывает конфиденциальных данных пайщика и включает минимальный набор сведений, достаточный для проверки подлинности документа. При наличии приватных данных, хранящихся вне блокчейна, этот документ может быть полностью восстановлен.

Приватная часть — хранится во внутренней базе данных платформы 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>; // Общее количество страниц
  };
}