Compare commits
392 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b922b00c1 | |||
| 2a4eabcda5 | |||
| 280d98c9c4 | |||
| 227c5c80c8 | |||
| edd00a6ab5 | |||
| d57bb17fc7 | |||
| 098da5c868 | |||
| dfc17bcc26 | |||
| 7a5eab104e | |||
| e0bf815377 | |||
| 2cbc6f2890 | |||
| 5859b2c08b | |||
| 44eaa9dc8b | |||
| 49cd7a41dd | |||
| 53de5ece21 | |||
| 90dffa5fcc | |||
| ac6cd0ca15 | |||
| 0e970128ab | |||
| 9fc373384d | |||
| a76bd2ea55 | |||
| 3c610b74d2 | |||
| 0623005ff4 | |||
| 8b032eba9a | |||
| e3229b85fd | |||
| 1178431b8f | |||
| 9d92c4a2b1 | |||
| 0bda8707ce | |||
| 9cb7d3a0fd | |||
| f1204f948a | |||
| 455b8dafb0 | |||
| fe5a32d95b | |||
| b34f6f1690 | |||
| a89bc35c6f | |||
| e551b6c202 | |||
| c6ed1afc51 | |||
| 4bedebb74d | |||
| 741fc7eb05 | |||
| 000da1197c | |||
| dd8be29d54 | |||
| 099c2c7ff4 | |||
| 874232a152 | |||
| d21f046e1f | |||
| ef0e25878e | |||
| cd37619af1 | |||
| 209ee18943 | |||
| aff7733a5b | |||
| 4bb65921e4 | |||
| 26216a6a8c | |||
| 60c3f11d7e | |||
| fcab08bfab | |||
| e69ba466e1 | |||
| 675e85ee09 | |||
| 36ec13b2c4 | |||
| 0b9c36e3ce | |||
| 07cf895447 | |||
| 028f21f0cf | |||
| 77724e363d | |||
| 15863d8a5a | |||
| b836af58f0 | |||
| ef5d880f14 | |||
| 15fd03b4c0 | |||
| cac06f3e17 | |||
| 6d81c62cdf | |||
| 60e319589d | |||
| 7723a5785d | |||
| dd982fe6e2 | |||
| c02dfeaa97 | |||
| a3d513afb0 | |||
| 40fa99a1f9 | |||
| f26c996095 | |||
| 012c4dfb9f | |||
| 9337ca2616 | |||
| 5dcec4bcfd | |||
| ed2e5e4d09 | |||
| 2249f52c32 | |||
| f4ef5d2b53 | |||
| a92c5d6ce0 | |||
| b61ceaead6 | |||
| 0672cfc8f3 | |||
| b5859a43cc | |||
| de1cd7c737 | |||
| f588312ad3 | |||
| 643ef1fa45 | |||
| ec38a4ed33 | |||
| defbd80ce0 | |||
| f11b91e7fa | |||
| 58bb4d5a7f | |||
| d92af60d9b | |||
| bca1e043bd | |||
| 7719175e85 | |||
| 046146c4d8 | |||
| 0e2f66b35a | |||
| 56b9ff1dc9 | |||
| dab8e78cd5 | |||
| 67dd27b9a1 | |||
| 4605985a47 | |||
| 02ac27342a | |||
| cbc59a4a1c | |||
| 02758d96c7 | |||
| 12752134fd | |||
| 504e70441b | |||
| 85cd16e793 | |||
| aa2277f05e | |||
| 27eb96722a | |||
| 0215096c9e | |||
| 90d8c51402 | |||
| ba0bbfefe7 | |||
| dd65673c1e | |||
| 2f3f8de682 | |||
| 3ce6fcad3a | |||
| 4302e7426e | |||
| 4e246c35c8 | |||
| 0cbcae9624 | |||
| 313aed606f | |||
| aade735c79 | |||
| 1e5786a142 | |||
| 3e5e5eeafa | |||
| 8a48ee8e14 | |||
| 6a89981c77 | |||
| 03c014fdda | |||
| ae683cd3c9 | |||
| a0dcae0882 | |||
| 6e839b210c | |||
| c7f4f62e93 | |||
| 214aba8f01 | |||
| 153c9d944f | |||
| 99a9d15871 | |||
| f6fb905ef8 | |||
| a5456452f6 | |||
| 48cd9af739 | |||
| 01fac428d5 | |||
| 3acce100f6 | |||
| ba2dfe6598 | |||
| 46600c38a8 | |||
| b9ae66e557 | |||
| bed9010918 | |||
| d13ccbde71 | |||
| 2e2aac0413 | |||
| 5f18cff344 | |||
| 830300ea81 | |||
| 9da2fcfbca | |||
| ece68df495 | |||
| 6a43ddbee9 | |||
| ee58596ed0 | |||
| 5bce6e77ae | |||
| 5dec45c0c8 | |||
| b7ba6500e4 | |||
| 2a263d749b | |||
| 3a21af48d1 | |||
| cb3bfe99ce | |||
| 328d51622f | |||
| 38ead74697 | |||
| ffd4d72850 | |||
| d23d90a794 | |||
| c74036ad26 | |||
| a146a9f424 | |||
| ddc0c82cee | |||
| bab09ac0d7 | |||
| 8a6eb912c5 | |||
| b5fbd40ea3 | |||
| 46b1c085cd | |||
| e5642cd752 | |||
| be933e9085 | |||
| 26769675c7 | |||
| 672d6d67aa | |||
| b3c6967ed9 | |||
| 9a528b17e8 | |||
| 4532932749 | |||
| 639a8e2f35 | |||
| 56c2ab38c6 | |||
| 9769cc0ed4 | |||
| 96d66b2205 | |||
| fea73109ca | |||
| e39c15c16f | |||
| 2a13f60bbd | |||
| d8e9738d71 | |||
| f38b78f821 | |||
| 9c21630295 | |||
| ea502727ba | |||
| 72f4999162 | |||
| adf1dbe635 | |||
| af2cdb1ca1 | |||
| e499c0e990 | |||
| 206ab6793f | |||
| e207c90fd4 | |||
| c9864008a3 | |||
| 5c89b27a87 | |||
| 3c482ba017 | |||
| 2d684411d4 | |||
| 408ee36a8b | |||
| d9efbad80d | |||
| 92e5c9f71a | |||
| baef88be50 | |||
| 7eff6ac504 | |||
| a3ea4d4e18 | |||
| f12047e36f | |||
| c77f37cc43 | |||
| 8bc463b6d6 | |||
| 953429f0da | |||
| 8598050931 | |||
| 81566674a8 | |||
| aba9f88352 | |||
| 0093c6ddf7 | |||
| cd5a0c2fbc | |||
| b1c803d2f1 | |||
| 3883b68669 | |||
| 5111bb12cf | |||
| cc2518a346 | |||
| 6e98f4c953 | |||
| 8990b25970 | |||
| c0cdde8685 | |||
| 7a7e20e4c2 | |||
| 743ccd27b6 | |||
| 155d9110c5 | |||
| 651e0b3445 | |||
| 82f438cbe4 | |||
| c3c30b4e20 | |||
| 8b4889ce83 | |||
| 7ccaa0390d | |||
| 5f0ed73d0d | |||
| 54518b63d0 | |||
| 1c38f2e5fd | |||
| 7ba12707e1 | |||
| 01992b26f4 | |||
| 8670ba08d5 | |||
| 67698757cf | |||
| 833ef3f5d3 | |||
| b104f14899 | |||
| 60c86b1db8 | |||
| b6d2965962 | |||
| 1548552af3 | |||
| 002cf8025b | |||
| dfd7e4b62c | |||
| e54380cd81 | |||
| 09b3532793 | |||
| b36e288958 | |||
| bcd3c2739a | |||
| 695e653461 | |||
| b068ff810c | |||
| 1a57f7a047 | |||
| 90f955e48e | |||
| 14fbce5a6b | |||
| 5cff407379 | |||
| 79b7ff21f5 | |||
| 65996a40e9 | |||
| cf3566255a | |||
| 582c6fa36d | |||
| aefba8b4df | |||
| e43674fd29 | |||
| 3856826e4d | |||
| 00ed0b7691 | |||
| aa456f6cff | |||
| 3a9dfa643e | |||
| 28580114da | |||
| 8d47f118dd | |||
| d7b45157ff | |||
| db8a48284f | |||
| b00b8d6ca3 | |||
| 22b3d8f691 | |||
| 432c1cd476 | |||
| ed8e4a059a | |||
| 571c1e31ac | |||
| b294d0eea0 | |||
| 0b50fbc55d | |||
| f5d5534290 | |||
| 777eb7e398 | |||
| 9b1be55a9e | |||
| ef4a7d1fa1 | |||
| e6a7380aa5 | |||
| 2f41591339 | |||
| 8916ef0207 | |||
| c3288f765e | |||
| 11d631bee3 | |||
| e3be78edb0 | |||
| 59f01a8426 | |||
| 76b7c8ef3b | |||
| fbaae9ce76 | |||
| e70b58a3df | |||
| ec551c9de8 | |||
| 7a8cb5835a | |||
| 3b8ccf173b | |||
| 172ce29665 | |||
| e7d8a8d2c0 | |||
| cc7b43f4ba | |||
| 71425977f5 | |||
| 4549727d0b | |||
| e030d97059 | |||
| 13c1ca4f79 | |||
| 637d7c0db1 | |||
| 7513a4b6d6 | |||
| 76100ef07d | |||
| ab139209db | |||
| c6a31baaab | |||
| 7c9d4629b6 | |||
| 986858c20d | |||
| a0551095dc | |||
| d0804c886a | |||
| 1befca7e28 | |||
| 3d4482157a | |||
| 3cd71f1cd4 | |||
| eebf443c1d | |||
| 7aaf0b9375 | |||
| 5ff9675e60 | |||
| 646390edff | |||
| b1958c5442 | |||
| 01b9590e81 | |||
| 985a288bf3 | |||
| 01dcfb88e7 | |||
| 76415a8dec | |||
| 586b7c14f8 | |||
| 0ee4bf2b25 | |||
| 5673e8abe8 | |||
| c446e32a8a | |||
| eee9392fb4 | |||
| 82a3961608 | |||
| f8689fc409 | |||
| 7de93e1780 | |||
| 19e742b7d1 | |||
| e3f0250fd2 | |||
| e4b39b89f3 | |||
| d2888b49e8 | |||
| b97ed49a60 | |||
| 65936dc85b | |||
| b1d02ee302 | |||
| cca47abb9c | |||
| c810ba62b4 | |||
| dcce7ffacc | |||
| e9fb137146 | |||
| 7100c9c300 | |||
| e9b5496387 | |||
| 87ecde390d | |||
| 8b33c66e22 | |||
| b3bbb6624e | |||
| 2ddac45612 | |||
| ddd7d47516 | |||
| a770072809 | |||
| 5b38ce0b37 | |||
| 804708076a | |||
| a0ad71a524 | |||
| 9d03694ea7 | |||
| 7a619e0e9e | |||
| 6bd5eb7930 | |||
| 744d1859d5 | |||
| 8033e3fc8e | |||
| abb0b629f3 | |||
| 34a61f49f6 | |||
| 08c31171a0 | |||
| 40addbf595 | |||
| abd6da0a37 | |||
| 794d1c5019 | |||
| b8d048bf3c | |||
| bb7b650ebc | |||
| 5253de9279 | |||
| 094945e350 | |||
| eb6b58e6db | |||
| 7bcb6e30a7 | |||
| 7ed4024555 | |||
| 6fa45c5958 | |||
| a883f140d4 | |||
| 1bec52efcb | |||
| 674e58ec3f | |||
| 373d705387 | |||
| 46fb693020 | |||
| f1fe7d1545 | |||
| 44ab386835 | |||
| 51d736eba4 | |||
| dcc4f6bd03 | |||
| eef2482423 | |||
| f46a1fb9b5 | |||
| bae45af414 | |||
| aff3835f50 | |||
| 5f249f6032 | |||
| a185d7e9e8 | |||
| 3449caf0e2 | |||
| cc458cc916 | |||
| 2b6b56ace9 | |||
| 4d72c920c6 | |||
| b0186a1baa | |||
| bc3836b316 | |||
| 0a444efe55 | |||
| fba3988834 | |||
| b99e1abb03 | |||
| ab44237bb9 | |||
| 6d98dc073f | |||
| 9709ef4333 | |||
| cb60f6f735 | |||
| 5d0ab2e2db | |||
| 4e3b0b52c8 | |||
| 973590281c | |||
| 4f7defb85c | |||
| 59f4d8dc26 | |||
| be736e105a |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"agentCanUpdateSnapshot": true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
ВАЖНО!!! Мы работаем в МОНО-репозитории pnpm, любая установка пакетов производится ЧЕРЕЗ ФИЛЬТР компонента в корне: pnpm add glob --filter notificator2.
|
||||
|
||||
ВАЖНО!!! НИКОГДА НЕ ЗАПУСКАЙ ПРИЛОЖЕНИЕ ИЛИ ЕГО СБОРКУ!!! Просто отчитайся что всё сделал.
|
||||
@@ -0,0 +1,151 @@
|
||||
---
|
||||
description:
|
||||
globs: components/controller/**
|
||||
alwaysApply: false
|
||||
---
|
||||
# NestJS Controller и Чистая архитектура
|
||||
|
||||
## Основные принципы
|
||||
1. Чистая архитектура - главный архитектурный подход. Пиши код с комментариями.
|
||||
2. Типы IName, IChecksum256, ITimePointSec - это просто строки.
|
||||
3. Домен должен быть полностью изолирован от инфраструктурных деталей.
|
||||
4. Направление зависимостей - внутрь (к ядру, домену).
|
||||
5. Домен, инфраструктура и приложение связаны через App.module, НЕ НУЖНО импортировать их друг в друга, а достаточно просто импортировать домен на уровне приложения чтобы использовать все экспорты домена.
|
||||
|
||||
## Структура проекта
|
||||
- `domain/` - доменный слой (бизнес-логика, независимая от инфраструктуры)
|
||||
- `infrastructure/` - инфраструктурный слой (адаптеры к внешним системам)
|
||||
- `modules/` - слой приложения (DTO, резолверы, сервисы)
|
||||
|
||||
## Слои и их взаимодействие
|
||||
1. **Домен**:
|
||||
- Содержит чистые доменные интерфейсы и реализованные на них сущности
|
||||
- НЕ должен зависеть от `cooptypes` (инфраструктурный контракт)
|
||||
- Использует собственные доменные типы (например, Date вместо строковых timestamp)
|
||||
- Интерфейсы портов описывают взаимодействие с внешними системами
|
||||
|
||||
2. **Инфраструктура**:
|
||||
- Содержит адаптеры к внешним системам (блокчейн, БД и т.д.)
|
||||
- Осуществляет преобразование между доменными и инфраструктурными типами
|
||||
- Общая логика преобразования выносится в утилитарные классы (например, `DomainToBlockchainUtils`)
|
||||
|
||||
3. **Модули (приложение)**:
|
||||
- DTO имплементируют доменные интерфейсы напрямую
|
||||
- Резолвер вызывает сервис, который принимает DTO
|
||||
- Сервис передает объекты DTO в интерактор домена
|
||||
|
||||
## Поток данных
|
||||
1. Резолвер принимает входные данные и передает их в сервис
|
||||
2. Сервис вызывает интерактор домена, передавая ему объекты DTO (имплементирующие доменный интерфейс)
|
||||
3. Интерактор выполняет бизнес-логику и взаимодействует с портами домена
|
||||
4. Адаптеры (инфраструктурный слой) преобразуют доменные объекты в формат внешних систем (JSON для хранения мета-данных, ISO для дат и т.д.)
|
||||
5. Адаптеры возвращают результаты в домен, домен возвращает данные в сервис, который возвращает их в резолвер
|
||||
|
||||
## Правила преобразования данных
|
||||
1. Преобразование DTO → доменный объект: не требуется, если DTO имплементирует доменный интерфейс
|
||||
2. Преобразование доменный объект → инфраструктурный тип: происходит в адаптерах
|
||||
3. Преобразование документов, дат и других сложных объектов: используются утилитарные классы уровня инфраструктуры
|
||||
|
||||
## Работа с типами данных
|
||||
1. **Даты**:
|
||||
- В доменных интерфейсах используется тип `Date`
|
||||
- В DTO используется тип `Date` с декораторами `@IsDate()` и `@Type(() => Date)`
|
||||
- В инфраструктурном слое происходит преобразование `Date` → `string` (ISO формат)
|
||||
```typescript
|
||||
// Доменный интерфейс
|
||||
interface MeetDomainInterface {
|
||||
open_at: Date;
|
||||
}
|
||||
|
||||
// DTO
|
||||
@Field(() => Date)
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
open_at!: Date;
|
||||
|
||||
// Адаптер
|
||||
const blockchainData = {
|
||||
open_at: domainToBlockchainUtils.convertDateToBlockchainFormat(data.open_at)
|
||||
};
|
||||
```
|
||||
|
||||
2. **Документы**:
|
||||
- В доменных интерфейсах используется собственный тип `SignedDocumentDomainInterface<T>`
|
||||
- В DTO используется соответствующий DTO-класс (например, `SignedDigitalDocumentInputDTO`)
|
||||
- В инфраструктурном слое происходит преобразование между форматами (meta-поля в JSON и т.д.)
|
||||
|
||||
## Типовые ошибки
|
||||
1. **Нарушение изоляции домена**: домен должен быть изолирован от инфраструктуры. Не используйте импорты из `cooptypes` в доменных интерфейсах.
|
||||
```typescript
|
||||
// Неправильно
|
||||
import { MeetContract } from 'cooptypes';
|
||||
export type VoteDomainInterface = MeetContract.Actions.Vote.IInput;
|
||||
|
||||
// Правильно
|
||||
export interface VoteDomainInterface {
|
||||
coopname: string;
|
||||
hash: string;
|
||||
// ... доменные поля
|
||||
}
|
||||
```
|
||||
|
||||
2. **Преобразование в неправильном слое**: преобразование доменных объектов в инфраструктурные типы должно происходить в адаптерах, а не в доменном слое или сервисе.
|
||||
```typescript
|
||||
// Неправильно (в доменном интеракторе)
|
||||
async vote(data: VoteDomainInterface) {
|
||||
const blockchainData = { ...data, meta: JSON.stringify(data.meta) };
|
||||
// ...
|
||||
}
|
||||
|
||||
// Правильно (в адаптере)
|
||||
async vote(data: VoteDomainInterface) {
|
||||
const blockchainData = this.convertToBlockchainFormat(data);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
3. **Дублирование логики преобразования**: общая логика преобразования должна быть вынесена в утилитарные классы.
|
||||
```typescript
|
||||
// Неправильно (дублирование в разных адаптерах)
|
||||
// В MeetAdapter
|
||||
private convertDoc(doc) { return { ...doc, meta: JSON.stringify(doc.meta) }; }
|
||||
// В BranchAdapter
|
||||
private convertDoc(doc) { return { ...doc, meta: JSON.stringify(doc.meta) }; }
|
||||
|
||||
// Правильно (общий утилитарный класс)
|
||||
// DomainToBlockchainUtils
|
||||
convertSignedDocumentToBlockchainFormat(doc) {
|
||||
return { ...doc, meta: JSON.stringify(doc.meta) };
|
||||
}
|
||||
```
|
||||
|
||||
## Примеры
|
||||
```typescript
|
||||
// Доменный интерфейс - чистый, без зависимостей от инфраструктуры
|
||||
export interface VoteOnAnnualGeneralMeetInputDomainInterface {
|
||||
coopname: string;
|
||||
hash: string;
|
||||
member: string;
|
||||
ballot: VoteItemInputDomainInterface[];
|
||||
}
|
||||
|
||||
// DTO имплементирует доменный интерфейс напрямую
|
||||
@InputType('VoteOnAnnualGeneralMeetInput')
|
||||
export class VoteOnAnnualGeneralMeetInputDTO implements VoteOnAnnualGeneralMeetInputDomainInterface {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
coopname!: string;
|
||||
// ... остальные поля
|
||||
}
|
||||
|
||||
// Адаптер преобразует доменный объект в инфраструктурный тип
|
||||
async vote(data: VoteOnAnnualGeneralMeetInputDomainInterface): Promise<TransactionResult> {
|
||||
// Преобразуем доменный объект в инфраструктурный тип
|
||||
const blockchainData: MeetContract.Actions.Vote.IInput = {
|
||||
coopname: data.coopname,
|
||||
hash: data.hash,
|
||||
// ... преобразование других полей
|
||||
};
|
||||
// ... отправка данных во внешнюю систему
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
description: SDK мутации и запросы для подключения на рабочем столе
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
Бэкенд реализован здесь components/controller, sdk здесь components/sdk.
|
||||
|
||||
Пример создания Query к API через SDK:
|
||||
|
||||
``` ts
|
||||
async function loadBranches(data: IGetBranchesInput): Promise<IBranch[]> {
|
||||
const { [Queries.Branches.GetBranches.name]: output } = await client.Query(
|
||||
Queries.Branches.GetBranches.query,
|
||||
{
|
||||
variables: {
|
||||
data
|
||||
}
|
||||
}
|
||||
);
|
||||
return output;
|
||||
}
|
||||
```
|
||||
|
||||
Такие запросы обычно хранятся в папке Entities и вызываются из его store.
|
||||
|
||||
Пример мутации:
|
||||
|
||||
``` ts
|
||||
async function selectBranch(data: ISelectBranchInput): Promise<boolean>{
|
||||
const {[Mutations.Branches.SelectBranch.name]: result} = await client.Mutation(Mutations.Branches.SelectBranch.mutation, {variables: {
|
||||
data
|
||||
}})
|
||||
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
Мутации обычно храним в папке features.
|
||||
|
||||
Как можешь обратить внимание, все мутации и запросы строятся по одному шаблону. Вся необходимая информация уже есть в SDK.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
# DESKTOP
|
||||
|
||||
- Пишем код фронтенда в архитектуре Feature Sliced Design (FSD).
|
||||
- Стиль шаблонов PUG на composite API.
|
||||
- Всегда создаём index.ts файлы для vue компонент.
|
||||
- Создавай индексные файлы для vue через export {default as NAME} from './where', а на уровне выше, если это необходимо, делай export * from './ui'
|
||||
- Всегда удаляем неиспользуемые импорты.
|
||||
- Не используем emit избыточно. Вместо них используем композабл функции моделей фич, если необходимо. Emit только в крайнем случае.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
Файл [main.py](mdc:monocoop/monocoop/monocoop/components/docs/main.py) автоматически генерит ссылки на документацию SDK и GraphQL, которые формируются и публикуются автоматически. При создании документации к методам всегда применяй ссылки на SDK и GraphQL по форме:
|
||||
{{ get_sdk_doc("Mutations", "Accounts", "RegisterAccount") }} | {{ get_graphql_doc("Mutation.registerAccount") }}
|
||||
|
||||
### 🎯 Основные паттерны использования
|
||||
|
||||
**1. Стандартная структура для методов API:**
|
||||
```markdown
|
||||
## Название действия
|
||||
{{ get_sdk_doc("Namespace", "Module", "Method") }} | {{ get_graphql_doc("Type.methodName") }}
|
||||
|
||||
{{ get_typedoc_input("Namespace.Module.Method") }}
|
||||
|
||||
Результат:
|
||||
{{ get_typedoc_definition("Namespace.Module.Method", "IOutput") }}
|
||||
```
|
||||
|
||||
**2. Описание + пример (для сложных методов):**
|
||||
```markdown
|
||||
{{ get_typedoc_desc("Namespace.Module.Method") }}
|
||||
|
||||
{{ get_typedoc_input("Namespace.Module.Method") }}
|
||||
```
|
||||
|
||||
**3. Ссылки на типы данных:**
|
||||
```markdown
|
||||
У каждого аккаунта есть объект {{ get_graphql_definition("Account") }} в GraphQL-API
|
||||
```
|
||||
|
||||
### 🔧 Макросы и их применение
|
||||
|
||||
| Макрос | Назначение | Пример использования |
|
||||
|--------|------------|---------------------|
|
||||
| `get_sdk_doc` | Ссылка на SDK документацию | `{{ get_sdk_doc("Mutations", "Payments", "CreateDeposit") }}` |
|
||||
| `get_graphql_doc` | Ссылка на GraphQL операцию | `{{ get_graphql_doc("Query.getAccount") }}` |
|
||||
| `get_graphql_definition` | Ссылка на GraphQL тип | `{{ get_graphql_definition("Account") }}` |
|
||||
| `get_class_doc` | Ссылка на класс/метод SDK | `{{ get_class_doc("Document", "sign") }}` |
|
||||
| `get_typedoc_input` | **Полный пример** TypeScript вызова | `{{ get_typedoc_input("Mutations.Auth.Login") }}` |
|
||||
| `get_typedoc_definition` | **Структура интерфейса** | `{{ get_typedoc_definition("Mutations.Auth.Login", "IOutput") }}` |
|
||||
| `get_typedoc_desc` | Описание + примеры | `{{ get_typedoc_desc("Mutations.Auth.Login") }}` |
|
||||
| `get_typedoc_value` | Значение константы | `{{ get_typedoc_value("Constants.API_VERSION") }}` |
|
||||
|
||||
### 📝 Правила оформления
|
||||
|
||||
**ОБЯЗАТЕЛЬНО:**
|
||||
- Всегда используй `get_typedoc_input` для демонстрации **КАК вызывать** метод
|
||||
- Всегда используй `get_typedoc_definition` для показа **ЧТО возвращается**
|
||||
- Комбинируй SDK и GraphQL ссылки через ` | `: `{{ get_sdk_doc(...) }} | {{ get_graphql_doc(...) }}`
|
||||
|
||||
**ЖЕЛАТЕЛЬНО:**
|
||||
- Для сложных методов добавляй `get_typedoc_desc` в начало
|
||||
- Используй `get_graphql_definition` для ссылок на типы данных в тексте
|
||||
- Группируй связанные операции в одном разделе
|
||||
|
||||
**ФОРМАТ ССЫЛОК:**
|
||||
- SDK: `"Namespace", "Module", "Method"` (3 аргумента)
|
||||
- GraphQL: `"Type.methodName"` (точка между типом и методом)
|
||||
- Определения: просто имя типа `"TypeName"`
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
description:
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Структура файлов шаблонов документов и алгоритм правки
|
||||
|
||||
## Основные директории и файлы
|
||||
|
||||
### 1. Определение типов и интерфейсов (cooptypes)
|
||||
**Путь:** `/cooptypes/src/cooperative/registry/[ID.DocumentName]/index.ts`
|
||||
|
||||
В этих файлах определяются:
|
||||
- Интерфейсы данных (`IAgendaMeet`, etc.)
|
||||
- Основная модель данных документа (`Model`)
|
||||
- HTML шаблон документа (`context`)
|
||||
- Переводы строк (`translations`)
|
||||
- Пример данных для тестирования (`exampleData`)
|
||||
|
||||
**Для изменения шаблона нужно править:**
|
||||
- Интерфейсы модели данных при изменении структуры
|
||||
- HTML в переменной `context` для изменения внешнего вида
|
||||
- Строки переводов в `translations.ru`
|
||||
|
||||
### 2. Схемы валидации (factory)
|
||||
**Путь:** `/factory/src/Schema/[SchemaName].ts`
|
||||
|
||||
Содержат:
|
||||
- JSON схемы для валидации данных
|
||||
- Определение обязательных полей
|
||||
|
||||
**При изменении структуры данных:**
|
||||
- Обновите соответствующую схему валидации
|
||||
- Обновите список обязательных полей в `required`
|
||||
|
||||
### 3. Фабрики документов (factory)
|
||||
**Путь:** `/factory/src/Actions/[ID.DocumentName].ts`
|
||||
|
||||
Обрабатывают:
|
||||
- Получение данных из разных источников
|
||||
- Сборку модели для шаблонизатора
|
||||
- Валидацию данных по схеме
|
||||
- Генерацию PDF
|
||||
|
||||
**При изменении логики сборки документа:**
|
||||
- Обновите метод `generateDocument()`
|
||||
- Добавьте новые источники данных
|
||||
|
||||
### 4. Шаблоны (factory)
|
||||
**Путь:** `/factory/src/Templates/[ID.DocumentName].ts`
|
||||
|
||||
Связывают:
|
||||
- Шаблон из cooptypes
|
||||
- Схему валидации
|
||||
- Метаданные документа
|
||||
|
||||
**После изменения в cooptypes:**
|
||||
- Убедитесь, что схема в Templates соответствует новой модели
|
||||
|
||||
### 5. Тесты (factory)
|
||||
**Путь:** `/factory/test/[category].test.ts`
|
||||
|
||||
Содержат:
|
||||
- Тестовые данные для генерации документов
|
||||
- Вызовы функции `testDocumentGeneration`
|
||||
|
||||
**После внесения изменений:**
|
||||
- Обновите тестовые данные в соответствии с новой структурой
|
||||
- Запустите тесты командой `pnpm test`
|
||||
|
||||
## Алгоритм внесения изменений
|
||||
|
||||
1. **Модификация типов**
|
||||
- Изменить файл в `/cooptypes/src/cooperative/registry/[ID.DocumentName]/index.ts`
|
||||
- Обновить интерфейсы, шаблон HTML и переводы
|
||||
|
||||
2. **Обновление схемы валидации**
|
||||
- Изменить соответствующую схему в `/factory/src/Schema/`
|
||||
- Убедиться, что обязательные поля совпадают с интерфейсом
|
||||
|
||||
3. **Компиляция библиотеки типов**
|
||||
- Выполнить `cd /cooptypes && pnpm build`
|
||||
|
||||
4. **Обновление тестовых данных**
|
||||
- Привести тестовые данные в соответствие с новой структурой
|
||||
|
||||
5. **Запуск тестов**
|
||||
- Выполнить `cd /factory && pnpm test [filename].test.ts`
|
||||
|
||||
## Особенности шаблонизации
|
||||
|
||||
- Используется Nunjucks для шаблонов
|
||||
- Поддерживаются конструкции: `{% if %}`, `{% for %}`, `{% trans %}`
|
||||
- Переменные вставляются через `{{ variable }}`
|
||||
- Переводы через `{% trans 'KEY' %}`
|
||||
@@ -0,0 +1,225 @@
|
||||
# Архитектура расширений в MonoCoop
|
||||
|
||||
## Основные принципы
|
||||
1. Расширения построены на модулях NestJS с использованием шаблона "Порты и адаптеры"
|
||||
2. Каждое расширение наследуется от `BaseExtModule` и реализует интерфейс `OnModuleInit`
|
||||
3. Расширения могут взаимодействовать с блокчейном через соответствующие порты
|
||||
4. Конфигурации расширений хранятся в БД и описываются с помощью Zod-схем
|
||||
5. Расширения регистрируются в глобальном реестре `AppRegistry`
|
||||
|
||||
## Структура расширения
|
||||
Минимальная структура расширения включает:
|
||||
- `XXX-extension.module.ts` - основной модуль расширения
|
||||
- `package.json` - информация о пакете
|
||||
- `README.md` - документация
|
||||
- `INSTALL.md` - инструкции по установке
|
||||
- `CHANGELOG.md` - история изменений
|
||||
|
||||
## Создание нового расширения
|
||||
1. Создайте директорию для расширения в `components/controller/src/extensions/`
|
||||
2. Создайте основной класс расширения, наследующийся от `BaseExtModule`
|
||||
3. Определите Zod-схему для конфигурации
|
||||
4. Реализуйте метод `initialize()`
|
||||
5. Зарегистрируйте расширение в `extensions.registry.ts`
|
||||
6. Добавьте расширение в список дефолтных приложений в `extension-domain.service.ts`
|
||||
|
||||
## Пример структуры модуля расширения
|
||||
```typescript
|
||||
// XXX-extension.module.ts
|
||||
export class XXXPlugin extends BaseExtModule {
|
||||
constructor(...) {
|
||||
super();
|
||||
}
|
||||
|
||||
name = 'xxx';
|
||||
plugin!: ExtensionDomainEntity<IConfig>;
|
||||
public configSchemas = Schema;
|
||||
|
||||
async initialize() {
|
||||
// Инициализация расширения
|
||||
// Настройка cron-задач
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [XXXPlugin],
|
||||
})
|
||||
export class XXXPluginModule {
|
||||
constructor(private readonly xxxPlugin: XXXPlugin) {}
|
||||
|
||||
async initialize() {
|
||||
await this.xxxPlugin.initialize();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Управление отображением конфигурации
|
||||
Zod-схемы используются для автоматического отображения формы настроек в интерфейсе пользователя.
|
||||
Через интерфейс `DeserializedDescriptionOfExtension` из `components/controller/src/types/shared/extension.types.ts`
|
||||
можно управлять отображением полей формы:
|
||||
|
||||
```typescript
|
||||
export const Schema = z.object({
|
||||
// Базовое поле с меткой
|
||||
simpleField: z.string().describe(
|
||||
describeField({
|
||||
label: 'Название поля',
|
||||
note: 'Подсказка под полем'
|
||||
})
|
||||
),
|
||||
|
||||
// Скрытое поле для служебного использования
|
||||
hiddenField: z.string().describe(
|
||||
describeField({
|
||||
label: 'Скрытое поле',
|
||||
visible: false
|
||||
})
|
||||
),
|
||||
|
||||
// Поле с проверкой значения
|
||||
validatedField: z.number().describe(
|
||||
describeField({
|
||||
label: 'Поле с валидацией',
|
||||
rules: ['val >= 5', 'val <= 100'],
|
||||
})
|
||||
),
|
||||
|
||||
// Форматированное поле с префиксом и суффиксом
|
||||
formattedField: z.number().describe(
|
||||
describeField({
|
||||
label: 'Форматированное поле',
|
||||
prepend: '$',
|
||||
append: 'USD',
|
||||
})
|
||||
),
|
||||
|
||||
// Многострочное текстовое поле
|
||||
multilineField: z.string().describe(
|
||||
describeField({
|
||||
label: 'Многострочное поле',
|
||||
maxRows: 5,
|
||||
minLength: 10,
|
||||
maxLength: 1000
|
||||
})
|
||||
),
|
||||
});
|
||||
```
|
||||
|
||||
Доступные поля для управления отображением:
|
||||
- `label` - название поля (обязательное)
|
||||
- `note` - пояснение или подсказка
|
||||
- `visible` - видимость поля (по умолчанию true)
|
||||
- `rules` - правила валидации в виде строковых выражений
|
||||
- `mask` - маска для ввода
|
||||
- `fillMask` - автозаполнение маски
|
||||
- `minLength` / `maxLength` - ограничения длины для текстовых полей
|
||||
- `maxRows` - количество строк для многострочного ввода
|
||||
- `append` / `prepend` - текст до/после значения поля
|
||||
|
||||
## Взаимодействие с блокчейном
|
||||
Для взаимодействия с блокчейном:
|
||||
1. Определите порт в доменном слое (например, `SovietBlockchainPort`)
|
||||
2. Инжектируйте порт в конструкторе расширения через DI
|
||||
3. Используйте методы порта для взаимодействия с блокчейном
|
||||
|
||||
```typescript
|
||||
@Inject(SOVIET_BLOCKCHAIN_PORT) private readonly sovietBlockchainPort: SovietBlockchainPort
|
||||
// ...
|
||||
const decisions = await this.sovietBlockchainPort.getDecisions(coopname);
|
||||
```
|
||||
|
||||
## Настройка планировщика задач
|
||||
Расширения могут использовать cron-задачи для периодического выполнения операций:
|
||||
|
||||
```typescript
|
||||
import cron from 'node-cron';
|
||||
|
||||
// Регистрация cron-задачи (каждые N минут)
|
||||
const cronExpression = `*/${this.plugin.config.checkInterval} * * * *`;
|
||||
cron.schedule(cronExpression, () => {
|
||||
this.logger.info('Запуск запланированной задачи');
|
||||
this.runTask();
|
||||
});
|
||||
```
|
||||
|
||||
## Работа с конфигурацией
|
||||
1. Определите Zod-схему для конфигурации
|
||||
2. Используйте `describeField` для добавления UI-метаданных к полям
|
||||
3. Получайте и обновляйте конфигурацию через репозиторий `extensionRepository`
|
||||
|
||||
```typescript
|
||||
export const Schema = z.object({
|
||||
checkInterval: z.number().describe(
|
||||
describeField({
|
||||
label: 'Интервал проверки (в минутах)',
|
||||
note: 'Минимум: 5 минут',
|
||||
rules: ['val >= 5'],
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
// Обновление конфигурации
|
||||
this.plugin.config.lastCheckDate = new Date().toISOString();
|
||||
await this.extensionRepository.update(this.plugin);
|
||||
```
|
||||
|
||||
## Логирование действий
|
||||
Расширения должны логировать свои действия:
|
||||
1. Используйте `WinstonLoggerService` для системного логирования
|
||||
2. Используйте `LogExtensionDomainRepository` для хранения логов в БД
|
||||
|
||||
```typescript
|
||||
// Системное логирование
|
||||
this.logger.info(`Выполнение операции для ${id}`);
|
||||
|
||||
// Сохранение лога в БД
|
||||
await this.logExtensionRepository.push(this.name, {
|
||||
type: 'operation',
|
||||
timestamp: new Date().toISOString(),
|
||||
data: { ... },
|
||||
});
|
||||
```
|
||||
|
||||
## Регистрация расширения
|
||||
После создания расширения, добавьте его в `extensions.registry.ts`:
|
||||
|
||||
```typescript
|
||||
export const AppRegistry: INamedExtension = {
|
||||
myExtension: {
|
||||
is_builtin: false,
|
||||
is_internal: true,
|
||||
is_available: true,
|
||||
is_desktop: false,
|
||||
title: 'Моё расширение',
|
||||
description: 'Описание функциональности.',
|
||||
image: 'https://example.com/image.png',
|
||||
class: MyExtensionPluginModule,
|
||||
schema: MyExtensionSchema,
|
||||
tags: ['тег1', 'тег2'],
|
||||
readme: getReadmeContent('./myExtension'),
|
||||
instructions: getInstructionsContent('./myExtension'),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Дефолтные настройки
|
||||
Добавьте расширение в список дефолтных приложений в `extension-domain.service.ts`:
|
||||
|
||||
```typescript
|
||||
getDefaultApps(): Partial<ExtensionDomainEntity>[] {
|
||||
return [
|
||||
// ...
|
||||
{
|
||||
name: 'myExtension',
|
||||
enabled: true,
|
||||
config: {
|
||||
// Дефолтные значения конфигурации
|
||||
parameter1: 'value1',
|
||||
parameter2: 42,
|
||||
},
|
||||
},
|
||||
// ...
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
description:
|
||||
globs: **/controller/src/extensions/**
|
||||
alwaysApply: false
|
||||
---
|
||||
# Архитектура расширений в MonoCoop
|
||||
|
||||
## Основные принципы
|
||||
1. Расширения построены на модулях NestJS с использованием шаблона "Порты и адаптеры"
|
||||
2. Каждое расширение наследуется от `BaseExtModule` и реализует интерфейс `OnModuleInit`
|
||||
3. Расширения могут взаимодействовать с блокчейном через соответствующие порты
|
||||
4. Конфигурации расширений хранятся в БД и описываются с помощью Zod-схем
|
||||
5. Расширения регистрируются в глобальном реестре `AppRegistry`
|
||||
|
||||
## Структура расширения
|
||||
Минимальная структура расширения включает:
|
||||
- `XXX-extension.module.ts` - основной модуль расширения
|
||||
- `package.json` - информация о пакете
|
||||
- `README.md` - документация
|
||||
- `INSTALL.md` - инструкции по установке
|
||||
- `CHANGELOG.md` - история изменений
|
||||
|
||||
## Создание нового расширения
|
||||
1. Создайте директорию для расширения в `components/controller/src/extensions/`
|
||||
2. Создайте основной класс расширения, наследующийся от `BaseExtModule`
|
||||
3. Определите Zod-схему для конфигурации
|
||||
4. Реализуйте метод `initialize()`
|
||||
5. Зарегистрируйте расширение в `extensions.registry.ts`
|
||||
6. Добавьте расширение в список дефолтных приложений в `extension-domain.service.ts`
|
||||
|
||||
## Пример структуры модуля расширения
|
||||
```typescript
|
||||
// XXX-extension.module.ts
|
||||
export class XXXPlugin extends BaseExtModule {
|
||||
constructor(...) {
|
||||
super();
|
||||
}
|
||||
|
||||
name = 'xxx';
|
||||
plugin!: ExtensionDomainEntity<IConfig>;
|
||||
public configSchemas = Schema;
|
||||
|
||||
async initialize() {
|
||||
// Инициализация расширения
|
||||
// Настройка cron-задач
|
||||
}
|
||||
|
||||
// Дополнительные методы расширения
|
||||
}
|
||||
|
||||
@Module({
|
||||
providers: [XXXPlugin],
|
||||
})
|
||||
export class XXXPluginModule {
|
||||
constructor(private readonly xxxPlugin: XXXPlugin) {}
|
||||
|
||||
async initialize() {
|
||||
await this.xxxPlugin.initialize();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Взаимодействие с блокчейном
|
||||
Для взаимодействия с блокчейном:
|
||||
1. Определите порт в доменном слое (например, `SovietBlockchainPort`)
|
||||
2. Инжектируйте порт в конструкторе расширения через DI
|
||||
3. Используйте методы порта для взаимодействия с блокчейном
|
||||
|
||||
```typescript
|
||||
@Inject(SOVIET_BLOCKCHAIN_PORT) private readonly sovietBlockchainPort: SovietBlockchainPort
|
||||
// ...
|
||||
const decisions = await this.sovietBlockchainPort.getDecisions(coopname);
|
||||
```
|
||||
|
||||
## Настройка планировщика задач
|
||||
Расширения могут использовать cron-задачи для периодического выполнения операций:
|
||||
|
||||
```typescript
|
||||
import cron from 'node-cron';
|
||||
|
||||
// Регистрация cron-задачи (каждые N минут)
|
||||
const cronExpression = `*/${this.plugin.config.checkInterval} * * * *`;
|
||||
cron.schedule(cronExpression, () => {
|
||||
this.logger.info('Запуск запланированной задачи');
|
||||
this.runTask();
|
||||
});
|
||||
```
|
||||
|
||||
## Работа с конфигурацией
|
||||
1. Определите Zod-схему для конфигурации
|
||||
2. Используйте `describeField` для добавления UI-метаданных к полям
|
||||
3. Получайте и обновляйте конфигурацию через репозиторий `extensionRepository`
|
||||
|
||||
```typescript
|
||||
export const Schema = z.object({
|
||||
checkInterval: z.number().describe(
|
||||
describeField({
|
||||
label: 'Интервал проверки (в минутах)',
|
||||
note: 'Минимум: 5 минут',
|
||||
rules: ['val >= 5'],
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
// Обновление конфигурации
|
||||
this.plugin.config.lastCheckDate = new Date().toISOString();
|
||||
await this.extensionRepository.update(this.plugin);
|
||||
```
|
||||
|
||||
## Логирование действий
|
||||
Расширения должны логировать свои действия:
|
||||
1. Используйте `WinstonLoggerService` для системного логирования
|
||||
2. Используйте `LogExtensionDomainRepository` для хранения логов в БД
|
||||
|
||||
```typescript
|
||||
// Системное логирование
|
||||
this.logger.info(`Выполнение операции для ${id}`);
|
||||
|
||||
// Сохранение лога в БД
|
||||
await this.logExtensionRepository.push(this.name, {
|
||||
type: 'operation',
|
||||
timestamp: new Date().toISOString(),
|
||||
data: { ... },
|
||||
});
|
||||
```
|
||||
|
||||
## Регистрация расширения
|
||||
После создания расширения, добавьте его в `extensions.registry.ts`:
|
||||
|
||||
```typescript
|
||||
export const AppRegistry: INamedExtension = {
|
||||
myExtension: {
|
||||
is_builtin: false,
|
||||
is_internal: true,
|
||||
is_available: true,
|
||||
is_desktop: false,
|
||||
title: 'Моё расширение',
|
||||
description: 'Описание функциональности.',
|
||||
image: 'https://example.com/image.png',
|
||||
class: MyExtensionPluginModule,
|
||||
schema: MyExtensionSchema,
|
||||
tags: ['тег1', 'тег2'],
|
||||
readme: getReadmeContent('./myExtension'),
|
||||
instructions: getInstructionsContent('./myExtension'),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Дефолтные настройки
|
||||
Добавьте расширение в список дефолтных приложений в `extension-domain.service.ts`:
|
||||
|
||||
```typescript
|
||||
getDefaultApps(): Partial<ExtensionDomainEntity>[] {
|
||||
return [
|
||||
// ...
|
||||
{
|
||||
name: 'myExtension',
|
||||
enabled: true,
|
||||
config: {
|
||||
// Дефолтные значения конфигурации
|
||||
parameter1: 'value1',
|
||||
parameter2: 42,
|
||||
},
|
||||
},
|
||||
// ...
|
||||
];
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,261 @@
|
||||
---
|
||||
description:
|
||||
globs: src/**/*.ts
|
||||
alwaysApply: false
|
||||
---
|
||||
# Фабрика Документов Кооперативов
|
||||
|
||||
## Общая Архитектура
|
||||
|
||||
Фабрика документов — это система генерации PDF документов для кооперативов, построенная на TypeScript с использованием MongoDB для хранения данных. Система состоит из трех основных частей:
|
||||
|
||||
1. **registry/** — JSON-шаблоны документов (статические данные)
|
||||
2. **factory/** — основная фабрика с логикой генерации
|
||||
3. **cooptypes/** — типы данных и интерфейсы
|
||||
|
||||
## Структура Registry
|
||||
|
||||
В корневой папке `registry/` находятся JSON файлы с номерными названиями, представляющие шаблоны документов:
|
||||
|
||||
### Основные документы:
|
||||
- `1.walletProgramAgreement.json` — соглашение о кошельке
|
||||
- `2.regulationElectronicSignature.json` — регламент электронной подписи
|
||||
- `3.privacyPolicy.json` — политика конфиденциальности
|
||||
- `4.userAgreement.json` — пользовательское соглашение
|
||||
- `50.CoopenomicsAgreement.json` — соглашение с партнерами
|
||||
- `100.participantApplication.json` — заявление участника
|
||||
- `101.selectBranchStatement.json` — заявление о выборе филиала
|
||||
|
||||
### Документы общих собраний (300-304):
|
||||
- `300.annualGeneralMeetingAgenda.json` — предложение повестки дня
|
||||
- `301.annualGeneralMeetingSovietDecision.json` — протокол заседания совета
|
||||
- `302.annualGeneralMeetingNotification.json` — уведомление о собрании
|
||||
- `303.annualGeneralMeetingVotingBallot.json` — бюллетень для голосования
|
||||
- `304.annualGeneralMeetingDecision.json` — протокол общего собрания
|
||||
|
||||
### Инвестиционные документы (1000+):
|
||||
- `1000.investAgreement.json` — инвестиционное соглашение
|
||||
- `1001.investByResultStatement.json` — заявление о зачете по результатам
|
||||
- `1002.investByResultAct.json` — акт зачета по результатам
|
||||
- `1005.investByMoneyStatement.json` — заявление о зачете денежных средств
|
||||
|
||||
### Структура JSON-шаблона:
|
||||
```json
|
||||
{
|
||||
"context": "<div>...HTML шаблон с переменными...</div>",
|
||||
"model": {...данные для примера...},
|
||||
"translation": {...переводы ключей...},
|
||||
"object_model": {...схема объектной модели...}
|
||||
}
|
||||
```
|
||||
|
||||
## Фабрика (factory/)
|
||||
|
||||
### Основные компоненты:
|
||||
|
||||
#### src/index.ts — Главный класс Generator
|
||||
```typescript
|
||||
export class Generator implements IGenerator {
|
||||
// Хранилище фабрик для каждого типа документа
|
||||
factories: { [K in Numbers]: DocFactory<IGenerate> }
|
||||
|
||||
// MongoDB коннектор
|
||||
public storage: MongoDBConnector
|
||||
|
||||
// Основной метод генерации
|
||||
async generate(data: IGenerate, options?: IGenerationOptions): Promise<IGeneratedDocument>
|
||||
}
|
||||
```
|
||||
|
||||
#### Архитектура Factory Pattern:
|
||||
- Базовый класс `DocFactory<T>` в `src/Factory/index.ts`
|
||||
- Каждый документ имеет свою фабрику в `src/Actions/`
|
||||
- Фабрики наследуются от `DocFactory` и реализуют метод `generateDocument()`
|
||||
|
||||
### Сервисы:
|
||||
|
||||
#### Services/Generator/ — PDF генерация
|
||||
- `PDFService` — конвертирует HTML в PDF через WeasyPrint
|
||||
- Использует шрифт Arial (base64)
|
||||
- Добавляет метаданные в PDF
|
||||
- Вычисляет SHA-256 хеш документа
|
||||
|
||||
#### Services/Templator/ — Шаблонизация
|
||||
- Основан на Nunjucks
|
||||
- Поддерживает кастомное расширение `{% trans %}` для переводов
|
||||
- Рендерит HTML из шаблона с подстановкой переменных
|
||||
|
||||
#### Services/Validator/ — Валидация
|
||||
- Использует AJV для JSON Schema валидации
|
||||
- Поддерживает кастомные форматы (телефон)
|
||||
- Локализация ошибок на русском языке
|
||||
|
||||
#### Services/Databazor/ — База данных
|
||||
- `MongoDBConnector` — работа с MongoDB
|
||||
- `DataService` — абстракция над данными
|
||||
- Коллекции: `deltas`, `actions`, `documents`, и другие
|
||||
|
||||
### Модели данных (src/Models/):
|
||||
|
||||
#### Основные типы пользователей:
|
||||
- `Individual` — физические лица (ФИО, паспорт, адрес)
|
||||
- `Organization` — организации (ИНН, ОГРН, представитель)
|
||||
- `Entrepreneur` — ИП (ФИО + ИНН/ОГРН)
|
||||
|
||||
#### Кооперативные данные:
|
||||
- `Cooperative` — данные кооператива
|
||||
- `PaymentMethod` — платежные методы
|
||||
- `Vars` — переменные кооператива
|
||||
- `Project` — проекты
|
||||
|
||||
### Система Action-ов:
|
||||
|
||||
Каждый документ имеет Action класс в `src/Actions/` с методом `generateDocument()`:
|
||||
|
||||
1. **Получение шаблона** — из локального Registry или MongoDB
|
||||
2. **Сбор данных** — пользователь, кооператив, переменные, специфичные данные
|
||||
3. **Валидация** — проверка по JSON схеме
|
||||
4. **Рендеринг** — HTML из шаблона + данные
|
||||
5. **PDF генерация** — HTML → PDF с метаданными
|
||||
6. **Сохранение** — в MongoDB (если не skip_save)
|
||||
|
||||
## Система типов (cooptypes/)
|
||||
|
||||
### cooperative/registry/ — Типы документов
|
||||
Каждый документ имеет папку с интерфейсами:
|
||||
- `Action` — входные данные для генерации
|
||||
- `Model` — модель данных для шаблона
|
||||
- `Template` — структура шаблона
|
||||
|
||||
### contracts/ — Блокчейн контракты
|
||||
- `registrator/` — регистрация кооперативов
|
||||
- `soviet/` — управление советом
|
||||
- `meet/` — общие собрания
|
||||
- `wallet/`, `capital/`, `fund/` — финансовые операции
|
||||
|
||||
## База данных MongoDB
|
||||
|
||||
### Основные коллекции:
|
||||
|
||||
#### deltas — Состояние блокчейна
|
||||
```javascript
|
||||
{
|
||||
block_num: number,
|
||||
present: boolean,
|
||||
code: string, // название контракта
|
||||
scope: string, // область действия
|
||||
table: string, // имя таблицы
|
||||
primary_key: string,
|
||||
value: {...} // данные записи
|
||||
}
|
||||
```
|
||||
|
||||
#### actions — Действия блокчейна
|
||||
```javascript
|
||||
{
|
||||
block_num: number,
|
||||
account: string,
|
||||
name: string, // имя действия
|
||||
receiver: string,
|
||||
data: {...} // данные действия
|
||||
}
|
||||
```
|
||||
|
||||
#### documents — Сгенерированные документы
|
||||
```javascript
|
||||
{
|
||||
hash: string, // SHA-256 хеш
|
||||
binary: Uint8Array, // PDF данные
|
||||
html: string, // HTML исходник
|
||||
meta: { // метаданные
|
||||
title: string,
|
||||
created_at: string,
|
||||
lang: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Таблицы данных:
|
||||
- `coops` — кооперативы
|
||||
- `meets` — собрания
|
||||
- `questions` — вопросы собраний
|
||||
- `decisions` — решения совета
|
||||
- `individuals` — физические лица
|
||||
- `organizations` — организации
|
||||
- `entrepreneurs` — ИП
|
||||
- `paymentMethods` — платежные методы
|
||||
- `vars` — переменные кооперативов
|
||||
|
||||
## Генерация документов
|
||||
|
||||
### Процесс генерации:
|
||||
1. **Вызов** `generator.generate(action, options)`
|
||||
2. **Поиск фабрики** по `registry_id`
|
||||
3. **Загрузка шаблона** (локально или из БД)
|
||||
4. **Сбор данных** из MongoDB по `coopname`, `username`, `block_num`
|
||||
5. **Создание модели** — объединение всех данных
|
||||
6. **Валидация** модели по JSON схеме
|
||||
7. **Рендеринг HTML** через Nunjucks
|
||||
8. **Генерация PDF** через WeasyPrint
|
||||
9. **Добавление метаданных** в PDF
|
||||
10. **Вычисление хеша** SHA-256
|
||||
11. **Сохранение** в MongoDB
|
||||
|
||||
### Пример использования:
|
||||
```typescript
|
||||
const generator = new Generator()
|
||||
await generator.connect(mongoUri)
|
||||
|
||||
const document = await generator.generate({
|
||||
registry_id: '300',
|
||||
coopname: 'voskhod',
|
||||
username: 'ant',
|
||||
block_num: 0,
|
||||
meet: {...},
|
||||
questions: [...]
|
||||
})
|
||||
```
|
||||
|
||||
## Особенности реализации
|
||||
|
||||
### Шаблонизация:
|
||||
- HTML шаблоны с CSS стилями
|
||||
- Переменные в формате `{{variable.field}}`
|
||||
- Условная логика `{% if condition %}`
|
||||
- Циклы `{% for item in array %}`
|
||||
- Переводы `{% trans 'KEY', var1, var2 %}`
|
||||
|
||||
### Подписи:
|
||||
- Цифровые подписи вместо физических
|
||||
- Текст "Подписано электронной подписью"
|
||||
- Убраны подчеркивания для подписей
|
||||
|
||||
### Типы собраний:
|
||||
- `regular` — очередное
|
||||
- `extraordinary` — внеочередное
|
||||
- Условная логика в шаблонах
|
||||
|
||||
### Филиалы:
|
||||
- `coop.is_branched` — проверка на наличие филиалов
|
||||
- "пайщиков" vs "уполномоченных" в зависимости от типа
|
||||
|
||||
### Форматирование дат:
|
||||
- Формат: "г. Москва, 15 декабря 2024 г."
|
||||
- Без кавычек вокруг дат
|
||||
- Запятая после города
|
||||
|
||||
## Тестирование
|
||||
|
||||
### test/utils/index.ts — Тестовые утилиты:
|
||||
- `preLoading()` — инициализация тестовых данных
|
||||
- Создание кооператива, пользователей, платежных методов
|
||||
- Настройка данных собраний и решений
|
||||
- Очистка временных файлов
|
||||
|
||||
### Тестовые данные:
|
||||
- Кооператив "ВОСХОД"
|
||||
- Пользователи: ant, individual, entrepreneur
|
||||
- Организации: voskhod, branch, exampleorg
|
||||
- Собрания с вопросами и решениями
|
||||
|
||||
Фабрика поддерживает полный цикл создания документов кооператива от заявлений до протоколов собраний с возможностью кастомизации под разные типы кооперативов и требования.
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
description:
|
||||
globs: components/sdk/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Руководство по работе с GraphQL Zeus в SDK
|
||||
|
||||
## Основной процесс
|
||||
|
||||
1. **Анализ DTO бэкенда**:
|
||||
- Изучите структуру DTO классов (`@ObjectType`) в бэкенде
|
||||
- Обратите внимание на имена типов в декораторах `@ObjectType('ИмяТипа')`
|
||||
- Отметьте поля, связи и вложенные объекты
|
||||
|
||||
2. **Создание селекторов**:
|
||||
- Для каждого DTO создайте соответствующий селектор с именем `raw<ИмяТипа>Selector`
|
||||
- Селектор - это объект, где ключи соответствуют полям DTO, а значения - `true`
|
||||
- Для вложенных объектов используйте вложенные селекторы: `field: nestedSelector`
|
||||
- Для списков используйте один селектор без массива: `items: itemSelector`
|
||||
|
||||
3. **Валидация селекторов**:
|
||||
- Для каждого селектора создайте проверку типа:
|
||||
```typescript
|
||||
const _validate: MakeAllFieldsRequired<ValueTypes['ТочноеИмяГрафКьЭлТипа']> = rawSelector
|
||||
```
|
||||
- Имя типа должно точно совпадать с именем в декораторе `@ObjectType`
|
||||
|
||||
4. **Экспорт селекторов**:
|
||||
- Создайте финальный селектор с помощью функции `Selector`:
|
||||
```typescript
|
||||
export const typeSelector = Selector('ТочноеИмяГрафКьЭлТипа')(rawTypeSelector)
|
||||
```
|
||||
- Экспортируйте сырой селектор для переиспользования
|
||||
- Экспортируйте тип модели: `export type modelType = ModelTypes['ТочноеИмяГрафКьЭлТипа']`
|
||||
|
||||
5. **Создание запросов/мутаций**:
|
||||
- Используйте селекторы в запросах и мутациях:
|
||||
```typescript
|
||||
export const query = Selector('Query')({
|
||||
queryName: [{ data: $('data', 'ТочноеИмяВходногоТипа!') }, exportedSelector]
|
||||
})
|
||||
```
|
||||
- Для параметров используйте оператор `$` с точным именем входного типа
|
||||
- Создайте интерфейс входных данных:
|
||||
```typescript
|
||||
export interface IInput {
|
||||
data: ModelTypes['ТочноеИмяВходногоТипа']
|
||||
}
|
||||
```
|
||||
|
||||
## Особенности работы
|
||||
|
||||
- **Документы**: всегда сохраняйте структуру `{ hash, signatures, rawDocument }`
|
||||
- **Сложные DTO**: разбивайте на атомарные селекторы и комбинируйте их
|
||||
- **Типы в Zeus**: часто отличаются от имен классов в бэкенде, всегда проверяйте в `schema.gql`
|
||||
- **Массивы**: Zeus автоматически обрабатывает массивы, не используйте `[selector]`
|
||||
|
||||
Это руководство поможет правильно структурировать работу с SDK и избежать типичных ошибок при работе с Zeus.
|
||||
@@ -0,0 +1,122 @@
|
||||
name: Build Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- testnet
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Debug info
|
||||
run: |
|
||||
echo "Текущая ветка:"
|
||||
git branch --show-current
|
||||
echo "Последние коммиты:"
|
||||
git log -n 3 --oneline
|
||||
echo "Проверяем файлы в директории components/desktop/src-ssr:"
|
||||
ls -la components/desktop/src-ssr/ || echo "Директория не найдена!"
|
||||
echo "Проверяем файлы в middlewares:"
|
||||
ls -la components/desktop/src-ssr/middlewares/ || echo "Директория middlewares не найдена!"
|
||||
|
||||
- name: Set docker tag
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
echo "DOCKER_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "DOCKER_TAG=latest" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
# Сначала собираем базовый образ с runtime
|
||||
- name: Build base image
|
||||
run: |
|
||||
docker build --target runtime -t dicoop/mono-base:${{ env.DOCKER_TAG }} .
|
||||
docker push dicoop/mono-base:${{ env.DOCKER_TAG }}
|
||||
|
||||
# Создаем сервисные образы на основе базового
|
||||
- name: Build desktop image
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.desktop
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/desktop\", \"run\", \"start\"]" >> Dockerfile.desktop
|
||||
docker build -t dicoop/desktop:${{ env.DOCKER_TAG }} -f Dockerfile.desktop .
|
||||
docker push dicoop/desktop:${{ env.DOCKER_TAG }}
|
||||
|
||||
- name: Build controller image
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.coopback
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/controller\", \"run\", \"start\"]" >> Dockerfile.coopback
|
||||
docker build -t dicoop/coopback:${{ env.DOCKER_TAG }} -f Dockerfile.coopback .
|
||||
docker push dicoop/coopback:${{ env.DOCKER_TAG }}
|
||||
|
||||
- name: Build parser image
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.cooparser
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/parser\", \"run\", \"start\"]" >> Dockerfile.cooparser
|
||||
docker build -t dicoop/cooparser:${{ env.DOCKER_TAG }} -f Dockerfile.cooparser .
|
||||
docker push dicoop/cooparser:${{ env.DOCKER_TAG }}
|
||||
|
||||
- name: Build notificator image
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.notificator
|
||||
echo "CMD [\"pnpm\", \"-F\", \"coop-notificator\", \"run\", \"start\"]" >> Dockerfile.notificator
|
||||
docker build -t dicoop/notificator:${{ env.DOCKER_TAG }} -f Dockerfile.notificator .
|
||||
docker push dicoop/notificator:${{ env.DOCKER_TAG }}
|
||||
|
||||
- name: Build notifications image
|
||||
run: |
|
||||
echo "FROM dicoop/mono-base:${{ env.DOCKER_TAG }}" > Dockerfile.notifications
|
||||
echo "CMD [\"pnpm\", \"-F\", \"@coopenomics/notifications\", \"run\", \"sync\"]" >> Dockerfile.notifications
|
||||
docker build -t dicoop/notifications:${{ env.DOCKER_TAG }} -f Dockerfile.notifications .
|
||||
docker push dicoop/notifications:${{ env.DOCKER_TAG }}
|
||||
|
||||
# Отправка хука для деплоя
|
||||
- name: Trigger deployment webhook
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
if [[ $GITHUB_REF == refs/tags/latest ]]; then
|
||||
# Хук для тестнета
|
||||
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
elif [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
# Хук для продакшена
|
||||
curl -X POST ${{ vars.PRODUCTION_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
else
|
||||
# Хук для тестнета (обычная ветка)
|
||||
curl -X POST ${{ vars.TESTNET_WEBHOOK_URL }} \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '${{ env.DOCKER_TAG }}'
|
||||
fi
|
||||
|
||||
# Уведомление в Telegram об успехе
|
||||
- name: Telegram notify success
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
|
||||
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
|
||||
-d text="✅ [GITHUB MONO] Успешная сборка контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]"
|
||||
|
||||
# Уведомление в Telegram об ошибке
|
||||
- name: Telegram notify failure
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
curl -s -X POST https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \
|
||||
-d chat_id=${{ secrets.TELEGRAM_CHAT_ID }} \
|
||||
-d text="❌ [GITHUB MONO] Ошибка при сборке контейнеров: $GITHUB_REPOSITORY ($GITHUB_REF) [${{ env.DOCKER_TAG }}]"
|
||||
@@ -0,0 +1,18 @@
|
||||
# .github/workflows/trigger-coopenomics.yml
|
||||
name: Trigger Contracts Docs Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, testnet, main, capital] # или когда нужно триггерить
|
||||
|
||||
jobs:
|
||||
trigger-coopenomics:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Coopenomics deployment
|
||||
uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.COOPENOMICS_PAT }}
|
||||
repository: coopenomics/coopenomics # укажи правильный owner/repo
|
||||
event-type: deploy_from_mono
|
||||
client-payload: '{"repository": "${{ github.repository }}", "sha": "${{ github.sha }}", "ref": "${{ github.ref }}", "actor": "${{ github.actor }}"}'
|
||||
@@ -0,0 +1,93 @@
|
||||
name: Publish Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
build-and-publish-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install Python requirements
|
||||
run: |
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install mkdocs-material mkdocs-macros-plugin mkdocs-section-index pymdown-extensions
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: pnpm install
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Patch spectaql-config.yml for CI
|
||||
run: |
|
||||
sed -i.bak "0,/url:.*/s|url:.*|url: 'https://testnet.coopenomics.world/backend/v1/graphql'|" spectaql-config.yml
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Show patched spectaql-config.yml
|
||||
run: cat spectaql-config.yml
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Build cooptypes
|
||||
run: pnpm run build
|
||||
working-directory: ./components/cooptypes
|
||||
|
||||
- name: Generate controller docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/controller
|
||||
|
||||
- name: Copy controller docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/graphql
|
||||
cp -r ./components/controller/docs/* ./components/docs/docs/graphql/
|
||||
|
||||
- name: Generate sdk docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/sdk
|
||||
|
||||
- name: Copy sdk docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/sdk
|
||||
cp -r ./components/sdk/docs/* ./components/docs/docs/sdk/
|
||||
|
||||
- name: Generate cooptypes docs
|
||||
run: pnpm run docs
|
||||
working-directory: ./components/cooptypes
|
||||
|
||||
- name: Copy cooptypes docs
|
||||
run: |
|
||||
mkdir -p ./components/docs/docs/cooptypes
|
||||
cp -r ./components/cooptypes/docs/* ./components/docs/docs/cooptypes/
|
||||
|
||||
- name: Build docs (mkdocs)
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
mkdocs build
|
||||
working-directory: ./components/docs
|
||||
|
||||
- name: Publish to GitHub Pages
|
||||
run: npx gh-pages --nojekyll -d site --repo https://x-access-token:${GITHUB_TOKEN}@github.com/coopenomics/mono.git
|
||||
working-directory: ./components/docs
|
||||
env:
|
||||
GIT_AUTHOR_NAME: github-actions
|
||||
GIT_AUTHOR_EMAIL: github-actions@github.com
|
||||
GIT_COMMITTER_NAME: github-actions
|
||||
GIT_COMMITTER_EMAIL: github-actions@github.com
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Publish Packages
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm install -g pnpm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build all packages
|
||||
run: pnpm lerna run build
|
||||
|
||||
- name: Publish to npm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: pnpm lerna publish from-package --yes --no-verify-access
|
||||
@@ -1,3 +1,11 @@
|
||||
node_modules/
|
||||
lerna-debug.log
|
||||
components/controller/graph.png
|
||||
blockchain-data/
|
||||
scripts/changelog-prompt.md
|
||||
scripts/changelog-release.md
|
||||
scripts/release-info.md
|
||||
components/docs/docs/sdk
|
||||
dist/
|
||||
.env
|
||||
.DS_Store
|
||||
|
||||
Vendored
+48
-3
@@ -1,14 +1,59 @@
|
||||
{
|
||||
"editor.bracketPairColorization.enabled": true,
|
||||
"editor.guides.bracketPairs": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.tabCompletion": "onlySnippets",
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
|
||||
"eslint.run": "onType",
|
||||
"eslint.validate": ["javascript", "typescript", "vue"],
|
||||
// "typescript.tsdk": "node_modules/typescript/lib",
|
||||
"i18n-ally.localesPaths": ["src/i18n"],
|
||||
"typescript.format.enable": true,
|
||||
"typescript.format.indentSwitchCase": false,
|
||||
"typescript.validate.enable": true,
|
||||
"notebook.defaultFormatter": "Vue.volar",
|
||||
|
||||
// Оптимизация для монорепозитория
|
||||
"typescript.preferences.useAliasesForRenames": false,
|
||||
"typescript.preferences.include": false,
|
||||
"typescript.disableAutomaticTypeAcquisition": true,
|
||||
"typescript.preferences.includePackageJsonAutoImports": "off",
|
||||
"typescript.suggest.autoImports": true,
|
||||
"typescript.suggest.paths": true,
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"typescript.workspaceSymbols.scope": "currentProject",
|
||||
|
||||
// Настройки для снижения нагрузки
|
||||
"files.watcherExclude": {
|
||||
"**/node_modules/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/*/**": true,
|
||||
"**/.cache/**": true,
|
||||
"**/.quasar/**": true,
|
||||
"**/*.tsbuildinfo": true
|
||||
},
|
||||
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/dist": true,
|
||||
"**/.cache": true,
|
||||
"**/.quasar": true,
|
||||
"**/*.tsbuildinfo": true
|
||||
},
|
||||
|
||||
"files.exclude": {
|
||||
"**/.cache": true,
|
||||
"**/*.tsbuildinfo": true,
|
||||
"**/node_modules/.cache": true
|
||||
},
|
||||
|
||||
// TypeScript server настройки
|
||||
"typescript.tsserver.maxTsServerMemory": 8192,
|
||||
"typescript.tsserver.watchOptions": {
|
||||
"excludeFiles": [
|
||||
"**/node_modules/**/*",
|
||||
"**/dist/**/*",
|
||||
"**/.cache/**/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+175
@@ -1,8 +1,183 @@
|
||||
# v2025.9.1
|
||||
|
||||
В системе Кооперативной Экономики развернут смарт-контракт CAPITAL v0.2 для генерации и капитализации результатов интеллектуальной деятельности. Контракт описывает и обеспечивает:
|
||||
- бизнес-процесс производства результатов интеллектуальной деятельности в кооперативах создателями, авторами, инвесторами, координаторами, мастерами и собственниками имущества в кооперации на проектах;
|
||||
- приём результатов интеллектуальной деятельности в качестве паевых взносов в кооператив;
|
||||
- распределение потока членских взносов среди вкладчиков;
|
||||
- капитализацию результатов интеллектуальной деятельности новыми результатами по модели золотого сечения;
|
||||
- оценку вкладов авторов и создателей по методу Водянова;
|
||||
|
||||
В основе математической модели контракта лежит принцип распределения справедливой выгоды между вкладчиками согласно концепции "Общественно-полезного времени", разработанной в рамках теорий Кузнецова и академика Глушкова при работе над проектом общегосударственной автоматизированной системы учета и обработки информации (ОГАС).
|
||||
|
||||
Подробнее в документации: https://coopenomics.world/contracts/group__public__capital.html
|
||||
|
||||
✨ Новые функции
|
||||
- [#301](https://github.com/coopenomics/mono/issues/301): Реализация контракта "Капитализация" v0.2: регистрация вкладчиков, создание и управление проектами, поддержка инвестиций, ссуд, членских взносов, проведение голосований, расчет капитализации, интеграция с внешними контрактами, поддержка импорта данных.
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.7.1-1
|
||||
|
||||
В этом релизе реализованы механизмы возврата паевого взноса пайщика и инструменты управления этим процессом для членов совета. Также внесены визуальные доработки интерфейсов для улучшения восприятия информации.
|
||||
|
||||
✨ Новые функции
|
||||
- [#276](https://github.com/coopenomics/mono/issues/276): Реализовать путь возврата паевого взноса из кошелька пайщика
|
||||
- [#281](https://github.com/coopenomics/mono/issues/281): Генерация заявления на возврат паевого взноса
|
||||
- [#282](https://github.com/coopenomics/mono/issues/282): Генерация решения совета на возврат паевого взноса
|
||||
- [#278](https://github.com/coopenomics/mono/issues/278): Введение методов управления возвратами паевых взносов в контроллере
|
||||
- [#279](https://github.com/coopenomics/mono/issues/279): Отобразить исходящие платежи с кнопками управления в реестре платежей для совета
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#286](https://github.com/coopenomics/mono/issues/286): Поправить ошибки склонений времени
|
||||
- [#262](https://github.com/coopenomics/mono/issues/262): Баг: рабочий стол совета включается, однако, страница всегда открывается со стола пайщика
|
||||
- [#261](https://github.com/coopenomics/mono/issues/261): Баг: первая загрузка переадресует на главную страницу всегда - прямой переход на собрание становится недоступен.
|
||||
|
||||
🔧 Улучшения
|
||||
- [#285](https://github.com/coopenomics/mono/issues/285): Корректировка дизайна кошелька, профиля, повестки совета, реестра документов, реестра платежей
|
||||
- [#284](https://github.com/coopenomics/mono/issues/284): Разместить кошелек на главную вместо профиля
|
||||
- [#283](https://github.com/coopenomics/mono/issues/283): Ввести лоадер на переходе между рабочими столами
|
||||
- [#280](https://github.com/coopenomics/mono/issues/280): Мигрировать имеющиеся данные о входящих платежах в новую модель
|
||||
- [#277](https://github.com/coopenomics/mono/issues/277): Рефакторинг модуля платежей: переход на унифицированную модель входящего и исходящего платежа
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.6.14
|
||||
|
||||
Разработан модуль для проведения очередных общих собраний пайщиков. Исправлены баги, внесены улучшения пользовательского интерфейса.
|
||||
|
||||
✨ Новые функции
|
||||
- [#264](https://github.com/coopenomics/mono/issues/264): Разработан смарт-контракт общего собрания пайщиков (meet)
|
||||
- [#263](https://github.com/coopenomics/mono/issues/263): Реализован модуль оповещений на электронные почты по жизненному циклу общего собрания пайщиков
|
||||
- [#273](https://github.com/coopenomics/mono/issues/273): Встроены реальные шаблоны документов общего собрания
|
||||
- [#268](https://github.com/coopenomics/mono/issues/268): Добавлена страница результатов общего собрания
|
||||
- [#267](https://github.com/coopenomics/mono/issues/267): Добавлена страница просмотра и скачивания бюллетеней и уведомлений по собранию
|
||||
- [#270](https://github.com/coopenomics/mono/issues/270): Ссылка в оповещении ведет на страницу собрания с документом-уведомлением для подписи
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#262](https://github.com/coopenomics/mono/issues/262): Исправлено некорректное открытие рабочего стола совета
|
||||
- [#261](https://github.com/coopenomics/mono/issues/261): Исправлена ошибка с редиректом при первой загрузке и прямом переходе на собрание
|
||||
- [#259](https://github.com/coopenomics/mono/issues/259): Исправлены отступы в мобильной карточке пайщика
|
||||
- [#258](https://github.com/coopenomics/mono/issues/258): Убран hover-эффект на документе и пайщике в таблице
|
||||
|
||||
🔧 Улучшения
|
||||
- [#274](https://github.com/coopenomics/mono/issues/274): Настроена рассылка оповещений на почту при получении решения о проведении собрания
|
||||
- [#272](https://github.com/coopenomics/mono/issues/272): Подписанные уведомления сохраняются и извлекаются из реестра по Graph-QL
|
||||
- [#271](https://github.com/coopenomics/mono/issues/271): Ведется подсчет количества пайщиков в каждом кооперативе при добавлении и удалении
|
||||
- [#260](https://github.com/coopenomics/mono/issues/260): Введен счетчик количества пайщиков в кооперативе на контракте регистратора
|
||||
- [#256](https://github.com/coopenomics/mono/issues/256): Отображается статус членства каждого пайщика
|
||||
- [#255](https://github.com/coopenomics/mono/issues/255): В разделе Платежи отображается ФИО/Наименование плательщика
|
||||
- [#269](https://github.com/coopenomics/mono/issues/269): Реализована рассылка уведомлений перед началом собрания
|
||||
- [#265](https://github.com/coopenomics/mono/issues/265): Проведена отладка и тестирование процесса общего собрания пайщиков
|
||||
- [#266](https://github.com/coopenomics/mono/issues/266): Протестированы все процессы общего собрания
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.5.14
|
||||
|
||||
В этом релизе реализован новый стандарт передачи и хранения документов по блокчейну с поддержкой неограниченного количества подписей и их валидацией. Также внесены улучшения в интерфейс и исправлены ошибки.
|
||||
|
||||
✨ Новые функции
|
||||
- [#252](https://github.com/coopenomics/mono/issues/252): Внедрение обновленного стандарта хранения и передачи документов по блокчейну
|
||||
- [#251](https://github.com/coopenomics/mono/issues/251): Реализация версионированного мигратора данных для контроллера кооператива
|
||||
- [#244](https://github.com/coopenomics/mono/issues/244): Обновление стандарта сборки документов и переход на хэш-идентификаторы
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#249](https://github.com/coopenomics/mono/issues/249): Исправлена спутанная маршрутизация между рабочими столами кооперативов
|
||||
- [#253](https://github.com/coopenomics/mono/issues/253): Исправлена избыточная точность суммы оплаты в заявлении на вступление
|
||||
|
||||
🔧 Улучшения
|
||||
- [#259](https://github.com/coopenomics/mono/issues/259): Исправлены отступы в мобильной карточке пайщика на странице пайщиков
|
||||
- [#258](https://github.com/coopenomics/mono/issues/258): Удалён hover-эффект для документов и пайщиков в таблице
|
||||
- [#257](https://github.com/coopenomics/mono/issues/257): Добавлено сохранение светлой/тёмной темы в localStorage и восстановление при загрузке страницы
|
||||
- [#256](https://github.com/coopenomics/mono/issues/256): Отображение статуса членства каждого пайщика в разделе "Пайщики"
|
||||
- [#255](https://github.com/coopenomics/mono/issues/255): Отображение ФИО/Наименования плательщика в разделе "Платежи"
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# v2025.5.2
|
||||
|
||||
В этом релизе рабочие столы переведены на серверный рендеринг, что улучшает стабильность развертывания и упрощает автоматизацию поставки ПО. Данный релиз является подготовительным к переходу на новый стандарт цифровых документов на платформе.
|
||||
|
||||
✨ Новые функции
|
||||
- [#245](https://github.com/coopenomics/mono/issues/245): Перевод десктопа на серверный рендеринг с поддержкой динамических переменных окружения
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#247](https://github.com/coopenomics/mono/issues/247): Исправлен баг с повторной поставкой ПО при обрывах соединения между серверами
|
||||
|
||||
🔧 Улучшения
|
||||
- [#246](https://github.com/coopenomics/mono/issues/246): Перевод CI/CD на сборку и поставку предсобранных докер-контейнеров
|
||||
|
||||
#releases
|
||||
|
||||
---
|
||||
|
||||
# MONO v2025.4.29
|
||||
|
||||
В этом релизе реализована новая архитектура рабочих столов с установкой через маркетплейс. Добавлены стол пайщика и стол совета, переработаны разделы документов и подписей, улучшено разделение кошелька и профиля для повышения удобства пользователей.
|
||||
|
||||
✨ Новые функции
|
||||
- [#234](https://github.com/coopenomics/mono/issues/234): Маркетплейс рабочих столов с поддержкой разных ролей и микросервисной архитектурой
|
||||
- [#238](https://github.com/coopenomics/mono/issues/238): Контроллер общего собрания пайщиков с поддержкой документооборота и подписей
|
||||
- [#233](https://github.com/coopenomics/mono/issues/233): Минимальный смарт-контракт общих собраний пайщиков
|
||||
- [#232](https://github.com/coopenomics/mono/issues/232): Бэкенд полного обозревателя блоков
|
||||
|
||||
🐛 Исправления ошибок
|
||||
- [#231](https://github.com/coopenomics/mono/issues/231): Исправления ошибок регистрации, выхода из системы, отображения платежей и редактирования организации
|
||||
|
||||
🔧 Улучшения
|
||||
- [#243](https://github.com/coopenomics/mono/issues/243): Контроль прав доступа на получении документов пайщика
|
||||
- [#242](https://github.com/coopenomics/mono/issues/242): Бесконечный скролл на документах пайщика и кооператива
|
||||
- [#241](https://github.com/coopenomics/mono/issues/241): Раздел "Документы" для пайщика
|
||||
- [#240](https://github.com/coopenomics/mono/issues/240): Множественные подписи на одном документе
|
||||
- [#239](https://github.com/coopenomics/mono/issues/239): Последовательные методы подписи протокола общего собрания
|
||||
- [#237](https://github.com/coopenomics/mono/issues/237): Мобильная вёрстка на страницах стола совета
|
||||
- [#236](https://github.com/coopenomics/mono/issues/236): Пересобран лендинг для MONO
|
||||
- [#235](https://github.com/coopenomics/mono/issues/235): Настроен флоу гитхаб-релизов с описаниями
|
||||
- [#226](https://github.com/coopenomics/mono/issues/226): Встроено редактирование пайщиков
|
||||
- [#224](https://github.com/coopenomics/mono/issues/224): Добавлено ТЗ по контракту капитализации
|
||||
|
||||
---
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.10](https://github.com/coopenomics/mono/compare/v2.2.9...v2.2.10) (2025-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add Zeus type ([7bcb6e3](https://github.com/coopenomics/mono/commit/7bcb6e30a77b0ab89c5293188b58f08f19c8761e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.9](https://github.com/coopenomics/mono/compare/v2.2.8...v2.2.9) (2025-03-12)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.8](https://github.com/coopenomics/monocoop/compare/v2.2.7...v2.2.8) (2025-02-10)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.7](https://github.com/coopenomics/monocoop/compare/v2.2.6...v2.2.7) (2025-02-07)
|
||||
|
||||
**Note:** Version bump only for package monocoop
|
||||
|
||||
+34
-1
@@ -1,10 +1,43 @@
|
||||
FROM node:18-alpine
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Сразу копируем все файлы
|
||||
COPY . .
|
||||
|
||||
# Устанавливаем инструменты
|
||||
RUN npm install -g pnpm lerna
|
||||
|
||||
# Установка зависимостей
|
||||
# Используем версию pnpm, совместимую с существующим lock-файлом
|
||||
RUN pnpm install
|
||||
|
||||
# Установка системных зависимостей для WeasyPrint
|
||||
RUN apk add --no-cache \
|
||||
python3 \
|
||||
py3-pip \
|
||||
gcc \
|
||||
musl-dev \
|
||||
python3-dev \
|
||||
pango \
|
||||
zlib-dev \
|
||||
jpeg-dev \
|
||||
openjpeg-dev \
|
||||
g++ \
|
||||
libffi-dev \
|
||||
harfbuzz-subset \
|
||||
&& python3 -m venv /venv \
|
||||
&& /venv/bin/pip install WeasyPrint==62.3 \
|
||||
&& rm -rf /var/cache/*
|
||||
|
||||
# Сборка всех компонентов
|
||||
RUN lerna run build
|
||||
|
||||
# Финальный образ
|
||||
FROM builder AS runtime
|
||||
|
||||
# Настройка переменных окружения
|
||||
ENV PATH="/venv/bin:$PATH"
|
||||
|
||||
# Проверка WeasyPrint
|
||||
RUN weasyprint --version
|
||||
@@ -1,18 +1,135 @@
|
||||
# MONOCOOP
|
||||
# MONO
|
||||
|
||||
Моно-репозиторий компонент клиента системы электронного документооборота для потребительских и производственных кооперативов.
|
||||
Система управления взаимоотношениями в кооперативе. Включает в себя полный комплект программного обеспечения для подключения к платформе "Кооперативная Экономика" и управления взаимоотношениями с пайщиками в кооперативе и самим кооперативом на основе смарт-контрактов и простой электронной подписи.
|
||||
|
||||
## Инициализация
|
||||
## Ветки
|
||||
|
||||
- Для разработки используется ветка `dev`.
|
||||
- Для стейджа используется ветка `testnet`.
|
||||
- Для продакшена используется ветка `main`.
|
||||
|
||||
## Установка
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
curl -X POST http://127.0.0.1:2998/v1/system/init \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "server-secret: SECRET" \
|
||||
-d @init-cooperative1.json
|
||||
|
||||
Также для работы потребуется установленная фабрика документов `weasyprint` для вашей операционной системы: <https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#installation>
|
||||
|
||||
## Конфигурация
|
||||
|
||||
Для конфигурации потребуетcя установленный `docker` и `docker-compose`. Выполните команду и следуйте инструкциям:
|
||||
|
||||
```sh
|
||||
pnpm run setup
|
||||
```
|
||||
|
||||
|
||||
## Запуск
|
||||
|
||||
### Инфраструктура
|
||||
|
||||
Для работы системы необходимы базы `mongo`, `postgres` и `redis`.
|
||||
|
||||
Команда запуска баз данных в контейнерах для работы в фоновом режиме:
|
||||
|
||||
```sh
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Блокчейн и контракты
|
||||
|
||||
```sh
|
||||
pnpm boot
|
||||
```
|
||||
|
||||
Команда запустит блокчейн и установит все смарт-контракты, подготовив систему к локальной разработке или применению.
|
||||
|
||||
### Бэкенд
|
||||
|
||||
```sh
|
||||
pnpm run dev:backend
|
||||
```
|
||||
|
||||
Команда запустит набор сервисов бэкенда и точку доступа GraphQL-API на 2998 порту.
|
||||
|
||||
### Фронтенд
|
||||
|
||||
Команда запустит рабочий стол кооператива на 3005 порту:
|
||||
|
||||
```sh
|
||||
pnpm run dev:desktop
|
||||
```
|
||||
|
||||
### Очистка базы
|
||||
|
||||
Команда очистит базу mongodb и начнет производство блоков с начала:
|
||||
|
||||
```sh
|
||||
pnpm run clear
|
||||
```
|
||||
|
||||
Правильная последовательность команд при переустановке проекта "с нуля":
|
||||
|
||||
```sh
|
||||
git pull
|
||||
pnpm install
|
||||
pnpm run setup
|
||||
pnpm run clear
|
||||
pnpm run boot
|
||||
pnpm run dev:backend
|
||||
pnpm run dev:desktop
|
||||
```
|
||||
|
||||
### Кошелёк
|
||||
Для работы с кошельком блокчейна используется утилита cleos, которая позволяет извлекать таблицы смарт-контрактов и отправлять транзакции в блокчейн если это требуется.
|
||||
|
||||
Для работы с командным кошельком `cleos` локально необходимо чтобы инфраструктура была поднята:
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Запускаем контейнер с кошельком и входим в него командой:
|
||||
|
||||
```
|
||||
pnpm run cleos
|
||||
```
|
||||
|
||||
В кошельке уже находится дефолтный ключ для работы с локальной средой. Этот ключ зашифрован паролем. Пароль хранится в файле `password` и используется автоматически при разблокировке. Для разблокировки кошелька, находясь в контейнере выполняем:
|
||||
|
||||
```
|
||||
./unlock.sh
|
||||
```
|
||||
|
||||
Для использования кошелька выполняем:
|
||||
```
|
||||
./cleos.sh <command>
|
||||
```
|
||||
|
||||
Для получения полного списка команд вызываем кошелек без команд:
|
||||
```
|
||||
./cleos.sh
|
||||
```
|
||||
|
||||
Например, для получения информации о текущем состоянии блокчейна:
|
||||
```
|
||||
./cleos.sh get info
|
||||
```
|
||||
|
||||
### Возможные проблемы
|
||||
В случае, если при выполнении ./unlock.sh возникает проблема:
|
||||
`Unable to connect to keosd, if keosd is running please kill the process and try again`, то выполните команду:
|
||||
|
||||
```
|
||||
./reset.sh
|
||||
```
|
||||
|
||||
Данная команда удалит созданный ранее сокет кошелька, после чего, выполните ./unlock.sh повторно.
|
||||
|
||||
## Лицензия
|
||||
Продукт Потребительского Кооператива "ВОСХОД" распространяется по лицензии BY-NC-SA 4.0.
|
||||
|
||||
Продукт Потребительского Кооператива "ВОСХОД" распространяется по лицензии BY-NC-SA 4.0.
|
||||
|
||||
Разрешено делиться, копировать и распространять материал на любом носителе и форме, адаптировать, делать ремиксы, видоизменять и создавать новое, опираясь на этот материал. При использовании, Вы должны обеспечить указание авторства, предоставить ссылку, и обозначить изменения, если таковые были сделаны. Если вы перерабатываете, преобразовываете материал или берёте его за основу для производного произведения, вы должны распространять переделанные вами части материала на условиях той же лицензии , в соответствии с которой распространяется оригинал. Запрещено коммерческое использование материала. Использование в коммерческих целях – это использование, в первую очередь направленное на получение коммерческого преимущества или денежного вознаграждения.
|
||||
|
||||
Юридический текст лицензии: https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru
|
||||
Юридический текст лицензии: <https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.ru>
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
# 📋 Резюме создания компонентов Notifications
|
||||
|
||||
## ✅ Что создано
|
||||
|
||||
### 1. 📚 Библиотека `@monocoop/notifications`
|
||||
**Расположение:** `components/notifications/`
|
||||
|
||||
**Функции:**
|
||||
- ✅ Типизированные интерфейсы для workflow с Zod
|
||||
- ✅ Builder паттерн для создания workflow
|
||||
- ✅ Базовые шаблоны для email, in-app, push, SMS
|
||||
- ✅ Автоматическая конвертация Zod схем в JSON Schema для Novu
|
||||
- ✅ Экспорт всех workflow для использования в других пакетах
|
||||
|
||||
**Структура:**
|
||||
```
|
||||
src/
|
||||
├── types/ # Базовые типы и интерфейсы
|
||||
├── base/ # Утилиты и настройки по умолчанию
|
||||
├── workflows/ # Папки с workflow
|
||||
│ └── welcome/ # Пример приветственного workflow
|
||||
└── index.ts # Главный экспорт
|
||||
```
|
||||
|
||||
### 2. 🚀 NestJS приложение `@monocoop/notificator2`
|
||||
**Расположение:** `components/notificator2/`
|
||||
|
||||
**Функции:**
|
||||
- ✅ Автоматический upsert всех workflow в Novu при запуске
|
||||
- ✅ RESTful API для триггера уведомлений
|
||||
- ✅ Типизированная валидация payload
|
||||
- ✅ Health check endpoints
|
||||
- ✅ Использует библиотеку notifications для типов
|
||||
|
||||
**API Endpoints:**
|
||||
- `GET /api/notifications/health` - Health check
|
||||
- `GET /api/notifications/workflows` - Список workflow
|
||||
- `POST /api/notifications/trigger` - Универсальный триггер
|
||||
- `POST /api/notifications/trigger/welcome` - Триггер welcome workflow
|
||||
- `POST /api/notifications/workflows/upsert-all` - Принудительный upsert
|
||||
|
||||
## 🔧 Как использовать
|
||||
|
||||
### 1. Настройка библиотеки notifications
|
||||
```bash
|
||||
cd components/notifications
|
||||
pnpm install # или npm install
|
||||
pnpm build # для компиляции TypeScript
|
||||
```
|
||||
|
||||
### 2. Настройка notificator2
|
||||
```bash
|
||||
cd components/notificator2
|
||||
pnpm install # или npm install
|
||||
|
||||
# Настройка окружения
|
||||
cp .env.example .env
|
||||
# Добавить NOVU_API_KEY в .env
|
||||
|
||||
# Запуск
|
||||
pnpm start:dev
|
||||
```
|
||||
|
||||
### 3. Добавление нового workflow
|
||||
|
||||
**В библиотеке notifications:**
|
||||
```typescript
|
||||
// components/notifications/src/workflows/order/order-workflow.ts
|
||||
export const orderWorkflow = WorkflowBuilder
|
||||
.create<OrderPayload>()
|
||||
.name('Order Confirmation')
|
||||
.workflowId('order-confirmation')
|
||||
.payloadSchema(orderPayloadSchema)
|
||||
.addSteps([...])
|
||||
.build();
|
||||
```
|
||||
|
||||
**Регистрация в index.ts:**
|
||||
```typescript
|
||||
// components/notifications/src/workflows/index.ts
|
||||
import { orderWorkflow } from './order';
|
||||
|
||||
export const allWorkflows = [
|
||||
welcomeWorkflow,
|
||||
orderWorkflow, // ← добавить новый workflow
|
||||
];
|
||||
```
|
||||
|
||||
### 4. Использование API
|
||||
|
||||
**Триггер welcome workflow:**
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/notifications/trigger/welcome \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"subscriberId": "user-123",
|
||||
"email": "user@example.com",
|
||||
"payload": {
|
||||
"userName": "Иван Иванов",
|
||||
"userEmail": "user@example.com",
|
||||
"age": 25
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## 🎯 Преимущества архитектуры
|
||||
|
||||
1. **Типобезопасность** - Zod схемы обеспечивают валидацию на уровне TypeScript и runtime
|
||||
2. **Разделение ответственности** - Библиотека содержит только типы, сервер - логику
|
||||
3. **Расширяемость** - Легко добавлять новые workflow
|
||||
4. **Автоматизация** - Workflow автоматически синхронизируются с Novu
|
||||
5. **Переиспользование** - Типы можно использовать в других частях системы
|
||||
|
||||
## 🔄 Workflow при запуске
|
||||
|
||||
1. **Запуск notificator2** →
|
||||
2. **Чтение всех workflow из библиотеки** →
|
||||
3. **Проверка существования в Novu** →
|
||||
4. **Создание/обновление workflow** →
|
||||
5. **Готов к приёму запросов на триггеры**
|
||||
|
||||
## 📁 Структура как в testFramework2.ts
|
||||
|
||||
Вся логика из `testFramework2.ts` была перенесена в structured архитектуру:
|
||||
- ✅ `buildWorkflowData` → `WorkflowBuilder`
|
||||
- ✅ `baseSteps` → `createEmailStep`, `createInAppStep`, etc.
|
||||
- ✅ `upsertWorkflow` → `NovuService.upsertWorkflow`
|
||||
- ✅ `triggerWorkflow` → `NovuService.triggerWorkflow`
|
||||
- ✅ Типизация payload → Zod схемы + TypeScript типы
|
||||
File diff suppressed because one or more lines are too long
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
EOSIO_PUB_KEY=EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
|
||||
EOSIO_PRV_KEY=5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
|
||||
DATADIR=blockchain-data
|
||||
BPACCOUNT=eosio
|
||||
PASSWORD=PW5JGe4WsTPGrjnMzkGd4wfVdzoHCEySgbyq2WBWGAxSfevbXqAG4
|
||||
SERVER_SECRET=SECRET
|
||||
SERVER_URL=http://127.0.0.1:2998
|
||||
MONGO_URI=mongodb://127.0.0.1:27017/cooperative-x
|
||||
SIMPLE_EXPLORER_API=http://localhost:4000
|
||||
SKIP_BLOCK_FETCH=TRUE
|
||||
@@ -0,0 +1,14 @@
|
||||
.cache
|
||||
.DS_Store
|
||||
.idea
|
||||
*.log
|
||||
*.tgz
|
||||
coverage
|
||||
dist
|
||||
lib-cov
|
||||
logs
|
||||
node_modules
|
||||
temp
|
||||
.env
|
||||
blockchain-data/*
|
||||
wallet-data/*
|
||||
@@ -0,0 +1,2 @@
|
||||
ignore-workspace-root-check=true
|
||||
shell-emulator=true
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/lib/**",
|
||||
"${workspaceFolder}/lib/eosiolib/contracts/**",
|
||||
"${workspaceFolder}/lib/eosiolib/core/**"
|
||||
],
|
||||
"defines": [
|
||||
"_DEBUG",
|
||||
"UNICODE",
|
||||
"_UNICODE",
|
||||
"int128_t=__int128_t",
|
||||
"uint128_t=__uint128_t"
|
||||
],
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "c++20"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
// Enable the ESlint flat config support
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": true,
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
// "source.fixAll": "explicit",
|
||||
// "source.organizeImports": "never"
|
||||
},
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{
|
||||
"rule": "style/*",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-indent",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spacing",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spaces",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-order",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-dangle",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-newline",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*quotes",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*semi",
|
||||
"severity": "off"
|
||||
}
|
||||
],
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml"
|
||||
],
|
||||
// "[cpp]": {
|
||||
// "editor.defaultFormatter": "ms-vscode.cpptools",
|
||||
// },
|
||||
// "C_Cpp.vcFormat.indent.withinParentheses": "indent",
|
||||
"C_Cpp.errorSquiggles": "disabled"
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.2.10](https://github.com/coopenomics/mono/compare/v2.2.9...v2.2.10) (2025-03-27)
|
||||
|
||||
**Note:** Version bump only for package @coopenomics/boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.9](https://github.com/coopenomics/mono/compare/v2.2.8...v2.2.9) (2025-03-12)
|
||||
|
||||
**Note:** Version bump only for package @coopenomics/boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.8](https://github.com/coopenomics/contracts/compare/boot@0.1.7...boot@0.1.8) (2024-12-24)
|
||||
|
||||
**Note:** Version bump only for package boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.7](https://github.com/coopenomics/contracts/compare/boot@0.1.7-alpha.0...boot@0.1.7) (2024-10-16)
|
||||
|
||||
**Note:** Version bump only for package boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.6](https://github.com/coopenomics/contracts/compare/boot@0.1.5...boot@0.1.6) (2024-10-13)
|
||||
|
||||
**Note:** Version bump only for package boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.5](https://github.com/coopenomics/contracts/compare/boot@0.1.5-alpha.0...boot@0.1.5) (2024-09-26)
|
||||
|
||||
**Note:** Version bump only for package boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.4](https://github.com/coopenomics/contracts/compare/boot@0.1.4-alpha.0...boot@0.1.4) (2024-08-13)
|
||||
|
||||
**Note:** Version bump only for package boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.3](https://github.com/coopenomics/contracts/compare/boot@0.1.3-alpha.0...boot@0.1.3) (2024-08-13)
|
||||
|
||||
**Note:** Version bump only for package boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.2](https://github.com/coopenomics/contracts/compare/boot@0.1.2-alpha.0...boot@0.1.2) (2024-08-10)
|
||||
|
||||
**Note:** Version bump only for package boot
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.1](https://github.com/coopenomics/contracts/compare/boot@0.1.1-alpha.1...boot@0.1.1) (2024-08-09)
|
||||
|
||||
**Note:** Version bump only for package boot
|
||||
@@ -0,0 +1 @@
|
||||
# Загрузчик.
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineBuildConfig } from 'unbuild'
|
||||
|
||||
export default defineBuildConfig({
|
||||
entries: [
|
||||
'src/index',
|
||||
],
|
||||
declaration: true,
|
||||
clean: true,
|
||||
rollup: {
|
||||
emitCJS: true,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
// @ts-check
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default antfu(
|
||||
{
|
||||
ignores: [
|
||||
// eslint ignore globs here
|
||||
],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
'no-console': 'off',
|
||||
'unicorn/prefer-number-properties': 'off',
|
||||
// overrides
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@coopenomics/boot",
|
||||
"type": "module",
|
||||
"version": "2025.9.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.0.6",
|
||||
"description": "",
|
||||
"author": "Alex Ant <dacom.dark.sun@gmail.com>",
|
||||
"license": "",
|
||||
"keywords": [],
|
||||
"scripts": {
|
||||
"enter": "./scripts/enter.sh",
|
||||
"cli": "esno src/index.ts",
|
||||
"deploy": "esno src/index.ts deploy",
|
||||
"cleos": "esno src/index.ts cleos",
|
||||
"unlock": "esno src/index.ts unlock",
|
||||
"boot": "esno src/index.ts boot",
|
||||
"clear": "esno src/index.ts clear",
|
||||
"start": "./scripts/restart.sh",
|
||||
"stop": "./scripts/stop.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coopenomics/factory": "workspace:*",
|
||||
"axios": "^1.6.8",
|
||||
"chai": "^5.1.2",
|
||||
"commander": "^12.1.0",
|
||||
"cooptypes": "workspace:*",
|
||||
"dockerode": "^4.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"eosjs": "^22.1.0",
|
||||
"eosjs-api": "^7.0.4",
|
||||
"eosjs-ecc": "^4.0.7",
|
||||
"execa": "^9.5.2",
|
||||
"mocha": "^10.7.3",
|
||||
"mongoose": "^8.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.18.0",
|
||||
"@antfu/ni": "^0.21.12",
|
||||
"@antfu/utils": "^0.7.8",
|
||||
"@types/dockerode": "^3.3.31",
|
||||
"@types/node": "^20.12.12",
|
||||
"bumpp": "^9.4.1",
|
||||
"eslint": "^9.2.0",
|
||||
"esno": "^4.7.0",
|
||||
"pnpm": "^9.1.1",
|
||||
"rimraf": "^5.0.7",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"typescript": "^5.4.5",
|
||||
"unbuild": "^2.0.0",
|
||||
"vite": "^5.2.11",
|
||||
"vitest": "^1.6.0"
|
||||
}
|
||||
}
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/bin/zsh
|
||||
|
||||
# Определение ассоциативных массивов
|
||||
typeset -A contract_params_test contract_params_prod
|
||||
contract_params_test=(
|
||||
[ano]="ano"
|
||||
[gateway]="gateway"
|
||||
[draft]="draft"
|
||||
[marketplace]="marketplace"
|
||||
[soviet]="soviet"
|
||||
[registrator]="registrator"
|
||||
[system]="eosio"
|
||||
[fund]="fund"
|
||||
)
|
||||
contract_params_prod=(
|
||||
[ano]="ano"
|
||||
[gateway]="gateway"
|
||||
[draft]="draft"
|
||||
[marketplace]="marketplace"
|
||||
[soviet]="soviet"
|
||||
[registrator]="registrator"
|
||||
[system]="eosio"
|
||||
[fund]="fund"
|
||||
[template]="template"
|
||||
)
|
||||
|
||||
# Выбор массива параметров в зависимости от входящего аргумента
|
||||
if [ "$2" = "prod" ]; then
|
||||
contract_params=("${(@kv)contract_params_prod}")
|
||||
elif [ "$2" = "test" ]; then
|
||||
contract_params=("${(@kv)contract_params_test}")
|
||||
else
|
||||
contract_params=("${(@kv)contract_params_test}")
|
||||
fi
|
||||
|
||||
# Форматирование строки для переменной окружения
|
||||
contract_params_str=""
|
||||
for key value in "${(@kv)contract_params}"; do
|
||||
contract_params_str+="${key}=${value},"
|
||||
done
|
||||
|
||||
# Проверяем, задан ли аргумент для контрактов
|
||||
if [ -n "$1" ]; then
|
||||
contracts=$1
|
||||
else
|
||||
contracts="*"
|
||||
fi
|
||||
|
||||
# Запуск контейнера Docker
|
||||
docker run --rm --name cdt_v4.1.0 \
|
||||
--volume $(pwd):/project \
|
||||
-w /project \
|
||||
--env CONTRACT_PARAMS="$contract_params_str" \
|
||||
dicoop/cdt_v4.1.0 /bin/bash -c "
|
||||
|
||||
cd ../cpp
|
||||
|
||||
# Перебираем все папки в директории
|
||||
for folder in $contracts ; do
|
||||
# Убираем слэш из названия папки, чтобы получить имя контракта
|
||||
contract=\${folder%/}
|
||||
contract=\${contract##*/}
|
||||
|
||||
echo \"Сборка контракта \$contract в \$folder...\"
|
||||
|
||||
# Переходим в директорию контракта
|
||||
cd \$folder
|
||||
|
||||
# Извлекаем параметр для -contract из переменной окружения
|
||||
contract_param=\$(echo \$CONTRACT_PARAMS | grep -o \"\$contract=[^,]*\" | cut -d'=' -f2)
|
||||
|
||||
# Компилируем с использованием параметра -contract
|
||||
cdt-cpp -abigen -I include -R include -contract \$contract_param -o \$contract.wasm \$contract.cpp
|
||||
|
||||
# Возвращаемся назад
|
||||
cd ..
|
||||
|
||||
echo \"Сборка контракта \$contract завершена!\"
|
||||
done
|
||||
"
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
cd ../system/build && cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -Dcdt_DIR="/cdt/build/lib/cmake/cdt" -Dleap_DIR="/blockchain/build/lib/cmake/leap" .. && make -j 2
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Передача аргументов cleos
|
||||
docker exec -it node /usr/local/bin/cleos "$@"
|
||||
|
||||
Executable
+108
@@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Импорт параметров сетей
|
||||
source ./networks.sh
|
||||
|
||||
# Устанавливаем значения по умолчанию
|
||||
network_param=$local
|
||||
account_name_param=""
|
||||
network=""
|
||||
|
||||
# Получаем первый аргумент, который должен быть именем контракта
|
||||
contract_name=$1
|
||||
|
||||
rm /Users/darksun/testnet/wallet/keosd.sock
|
||||
./kill.sh
|
||||
|
||||
# Проверяем наличие параметра -n и устанавливаем соответствующую сеть
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-n)
|
||||
network=$2
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-t)
|
||||
account_name_param=$2
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Устанавливаем значение сети из networks.sh
|
||||
case $network in
|
||||
prod)
|
||||
network_param=$prod
|
||||
;;
|
||||
test)
|
||||
network_param=$test
|
||||
;;
|
||||
local)
|
||||
network_param=$local
|
||||
;;
|
||||
esac
|
||||
|
||||
# Разблокировка кошелька
|
||||
./unlock.sh
|
||||
|
||||
# Функция для установки контракта
|
||||
deploy_contract() {
|
||||
local contract=$1
|
||||
local dir=$2
|
||||
local account_name=$3
|
||||
echo "Устанавливаем контракт $contract на аккаунт $account_name..."
|
||||
if [ -z "$account_name_param" ]; then
|
||||
echo ./cleos.sh $network_param set contract $account_name $dir -p $account_name
|
||||
./cleos.sh $network_param set contract $account_name $dir -p $account_name
|
||||
else
|
||||
echo ./cleos.sh $network_param set contract $account_name_param $dir -p $account_name_param
|
||||
./cleos.sh $network_param set contract $account_name_param $dir -p $account_name_param
|
||||
fi
|
||||
}
|
||||
|
||||
# Если контракт указан, деплоим только его
|
||||
if [ ! -z "$contract_name" ]; then
|
||||
ls ./
|
||||
echo "Проверка наличия папки для контракта $contract_name..."
|
||||
if [ -d "./$contract_name" ]; then
|
||||
echo "Папка контракта найдена. Содержимое папки:"
|
||||
deploy_contract $contract_name "/contracts/cpp/$contract_name" $contract_name
|
||||
elif [[ "$contract_name" == "eosio.msig" || "$contract_name" == "eosio.token" || "$contract_name" == "eosio.wrap" ]]; then
|
||||
echo "Папка системного контракта найдена. Содержимое папки:"
|
||||
ls "/contracts/cpp/system/build/contracts/$contract_name"
|
||||
deploy_contract $contract_name "/contracts/cpp/system/build/contracts/$contract_name" $contract_name
|
||||
elif [ "$contract_name" == "eosio.system" ]; then
|
||||
echo "Папка системного контракта найдена. Содержимое папки:"
|
||||
ls "/contracts/cpp/system/build/contracts/$contract_name"
|
||||
deploy_contract $contract_name "/contracts/cpp/system/build/contracts/$contract_name" "eosio"
|
||||
elif [ "$contract_name" == "eosio.boot" ]; then
|
||||
echo "Папка системного контракта найдена. Содержимое папки:"
|
||||
ls "/contracts/cpp/system/build/contracts/$contract_name"
|
||||
deploy_contract $contract_name "/contracts/cpp/system/build/contracts/$contract_name" "eosio"
|
||||
else
|
||||
echo "Контракт $contract_name не найден"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Перебираем все папки с контрактами на верхнем уровне, исключая 'system'
|
||||
for dir in $(find /contracts/cpp/* -maxdepth 0 -type d ! -name 'system'); do
|
||||
contract=$(basename $dir)
|
||||
echo "Найдена папка $dir для контракта $contract. Содержимое папки:"
|
||||
ls $dir
|
||||
deploy_contract $contract $dir $contract
|
||||
done
|
||||
|
||||
# Исключение для контрактов в папке system
|
||||
special_system_contracts=("eosio.msig" "eosio.token" "eosio.wrap")
|
||||
for contract in "${special_system_contracts[@]}"; do
|
||||
echo "Найдена папка системного контракта для $contract. Содержимое папки:"
|
||||
ls "/contracts/cpp/system/build/contracts/$contract"
|
||||
deploy_contract $contract "/contracts/cpp/system/build/contracts/$contract" $contract
|
||||
done
|
||||
|
||||
deploy_contract "eosio.system" "/contracts/cpp/system/build/contracts/eosio.system" "eosio"
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
docker run --rm -it --name cdt \
|
||||
--volume $(pwd)/:/project \
|
||||
-w /project \
|
||||
dicoop/blockchain_v5.1.1:dev /bin/bash
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
cd ../
|
||||
doxygen
|
||||
cp -r ../docs/html/* ../../doctrine/site/contracts
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
# убиваем keosd на всякий случай
|
||||
docker exec -it node pkill keosd
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Получаем текущую директорию
|
||||
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Проверяем, существуют ли директории
|
||||
if [ ! -d ~/testnet ]; then
|
||||
echo "Директория ~/testnet не найдена. Создание..."
|
||||
mkdir -p ~/testnet
|
||||
fi
|
||||
|
||||
if [ ! -d ~/testnet/config ]; then
|
||||
echo "Директория ~/testnet/config не найдена. Создание..."
|
||||
mkdir -p ~/testnet/config
|
||||
fi
|
||||
|
||||
# Проверяем, существует ли файл
|
||||
if [ ! -f ~/testnet/config/config.ini ]; then
|
||||
echo "Файл config.ini не найден. Копирование..."
|
||||
cp $dir/configs/master/config.ini ~/testnet/config/config.ini
|
||||
fi
|
||||
|
||||
# Запускаем контейнер Docker с нужными параметрами
|
||||
docker run --name node -d -p 8888:8888 -p 9876:9876 -p 8080:8080 \
|
||||
-v ~/testnet/data:/mnt/dev/data \
|
||||
-v ~/testnet/config:/mnt/dev/config \
|
||||
-v ~/testnet/wallet:/root/eosio-wallet \
|
||||
-v $dir/../../contracts:/contracts \
|
||||
dicoop/blockchain:v5.1.0-dev \
|
||||
/bin/bash -c '/usr/local/bin/nodeos -d /mnt/dev/data -p eosio --config-dir /mnt/dev/config --genesis-json /mnt/dev/config/genesis.json'
|
||||
|
||||
docker network connect hyperion node
|
||||
|
||||
echo "Контейнер node установлен - блокчейн запущен."
|
||||
sleep 10
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
local="-u http://localhost:8888"
|
||||
test="-u https://testnet.coopenomics.world/api"
|
||||
prod="-u https://voskhod.coopenomics.world/api"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"net": {
|
||||
"assumed_stake_weight": 10000000,
|
||||
"current_weight_ratio": 500000000000000,
|
||||
"decay_secs": 86400,
|
||||
"exponent": 1,
|
||||
"max_price": "1000.0000 AXON",
|
||||
"min_price": "1000.0000 AXON",
|
||||
"target_weight_ratio": 500000000000000
|
||||
},
|
||||
"cpu": {
|
||||
"assumed_stake_weight": 10000000,
|
||||
"current_weight_ratio": 500000000000000,
|
||||
"decay_secs": 86400,
|
||||
"exponent": 1,
|
||||
"max_price": "1000.0000 AXON",
|
||||
"min_price": "1000.0000 AXON",
|
||||
"target_weight_ratio": 500000000000000
|
||||
},
|
||||
"min_powerup_fee": "0.0001 AXON",
|
||||
"powerup_days": 1
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
cleos push action eosio cfgpowerup "[`cat ./powerup.json`]" -p eosio
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Run unlock script before activating features
|
||||
./unlock.sh
|
||||
|
||||
# Activate PREACTIVATE_FEATURE before installing eosio.boot
|
||||
#./cleos.sh push action eosio activate '["0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd"]' -p eosio@active
|
||||
|
||||
curl --noproxy -X POST "http://localhost:8888/v1/producer/schedule_protocol_feature_activations" -d '{"protocol_features_to_activate": ["0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd"]}'
|
||||
|
||||
# Install eosio.boot which supports the native actions and activate
|
||||
# action that allows activating desired protocol features prior to
|
||||
# deploying a system contract with more features such as eosio.bios
|
||||
# or eosio.system
|
||||
./deploy.sh eosio.boot
|
||||
|
||||
# Activate remaining features
|
||||
./cleos.sh push action eosio activate '["c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["d528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["5443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["2652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d67"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["4fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b"]' -p eosio@active
|
||||
./cleos.sh push action eosio activate '["6bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc"]' -p eosio@active
|
||||
|
||||
# Install the eosio.system contract
|
||||
./deploy.sh eosio.system
|
||||
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Останавливаем и удаляем контейнер, если он существует
|
||||
docker stop node > /dev/null 2>&1 && docker rm node > /dev/null 2>&1
|
||||
|
||||
# Очищаем папку с данными блокчейна
|
||||
rm -rf ~/testnet/data/*
|
||||
|
||||
echo "Очистка завершена."
|
||||
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
cd ../cpp/system/build/tests && ctest -j $(nproc) --rerun-failed --output-on-failure
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ ! -d $DATADIR ]; then
|
||||
mkdir -p $DATADIR;
|
||||
fi
|
||||
|
||||
ARCH=`uname -m`
|
||||
|
||||
NODEOS='/usr/local/bin/nodeos'
|
||||
|
||||
if [ "${ARCH}" = "x86_64" ]; then
|
||||
EOSVM=eos-vm-jit
|
||||
else
|
||||
EOSVM=eos-vm
|
||||
fi
|
||||
|
||||
$NODEOS \
|
||||
--signature-provider $EOSIO_PUB_KEY=KEY:$EOSIO_PRV_KEY \
|
||||
--plugin eosio::net_plugin \
|
||||
--plugin eosio::net_api_plugin \
|
||||
--plugin eosio::producer_plugin \
|
||||
--plugin eosio::producer_api_plugin \
|
||||
--plugin eosio::chain_plugin \
|
||||
--plugin eosio::chain_api_plugin \
|
||||
--plugin eosio::http_plugin \
|
||||
--data-dir $DATADIR"/data" \
|
||||
--blocks-dir $DATADIR"/blocks" \
|
||||
--config-dir $DATADIR"/config" \
|
||||
--producer-name $BPACCOUNT \
|
||||
--http-server-address 0.0.0.0:8888 \
|
||||
--p2p-listen-endpoint 0.0.0.0:9010 \
|
||||
--access-control-allow-origin=* \
|
||||
--contracts-console \
|
||||
--http-validate-host=false \
|
||||
--verbose-http-errors \
|
||||
--enable-stale-production \
|
||||
--trace-history \
|
||||
--chain-state-history \
|
||||
--max-transaction-time=2000 \
|
||||
--abi-serializer-max-time-ms=60000 \
|
||||
--http-max-response-time-ms=8000 \
|
||||
--chain-state-db-size-mb 8192 \
|
||||
--chain-state-db-guard-size-mb 1024 \
|
||||
--wasm-runtime=$EOSVM \
|
||||
>> $DATADIR"/coopos.log" 2>&1 & \
|
||||
echo $! > $DATADIR"/coopos.pid"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERBOSE=""
|
||||
|
||||
if [[ $1 == "--verbose" ]]; then
|
||||
VERBOSE="--verbose"
|
||||
fi
|
||||
|
||||
cd ../cpp/system/build/tests && ctest -j $(nproc) --output-on-failure $VERBOSE
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
source .env
|
||||
|
||||
if [ -f $DATADIR"/eosd.pid" ]; then
|
||||
pid=`cat $DATADIR"/eosd.pid"`
|
||||
echo $pid
|
||||
kill $pid
|
||||
rm -r $DATADIR"/eosd.pid"
|
||||
echo -ne "Stoping Node"
|
||||
while true; do
|
||||
[ ! -d "/proc/$pid/fd" ] && break
|
||||
echo -ne "."
|
||||
sleep 1
|
||||
done
|
||||
echo -ne "\rNode Stopped. \n"
|
||||
fi
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Переходим в директорию скрипта
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Проверяем наличие папки для хранения данных о кошельке
|
||||
if [ ! -d "$HOME/testnet/wallet" ]; then
|
||||
mkdir -p $HOME/testnet/wallet
|
||||
fi
|
||||
|
||||
# Проверяем наличие файла с паролем
|
||||
if [ ! -f "$HOME/testnet/wallet/password" ]; then
|
||||
echo "Создание нового кошелька..."
|
||||
# Создаем новый кошелек и сохраняем пароль в файл внутри контейнера
|
||||
docker exec -it node /usr/local/bin/cleos wallet create --file password.txt
|
||||
if [ $? -eq 0 ]; then
|
||||
# Копируем пароль из файла контейнера в локальный файл
|
||||
docker cp node:/workdir/password.txt $HOME/testnet/wallet/password
|
||||
# Удаляем временный файл из контейнера
|
||||
docker exec -it node rm /workdir/password.txt
|
||||
echo "Пароль от кошелька сохранён в файле $HOME/testnet/wallet/password"
|
||||
# Импортируем ключ в кошелек
|
||||
docker exec -it node /usr/local/bin/cleos wallet import --private-key 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
|
||||
else
|
||||
echo "Не удалось создать кошелек"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Использование существующего кошелька..."
|
||||
# Разблокировка кошелька
|
||||
docker exec -it node /usr/local/bin/cleos wallet unlock --password $(cat $HOME/testnet/wallet/password) > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,532 @@
|
||||
plugin = eosio::chain_plugin
|
||||
plugin = eosio::producer_plugin
|
||||
plugin = eosio::chain_api_plugin
|
||||
plugin = eosio::http_plugin
|
||||
plugin = eosio::state_history_plugin
|
||||
plugin = eosio::producer_api_plugin
|
||||
plugin = eosio::resource_monitor_plugin
|
||||
|
||||
enable-stale-production = true
|
||||
read-only-read-window-time-us = 120000
|
||||
|
||||
net-threads = 2
|
||||
max-transaction-time=2000
|
||||
|
||||
http-server-address = 0.0.0.0:8888
|
||||
p2p-listen-endpoint = 0.0.0.0:9876
|
||||
access-control-allow-origin = *
|
||||
access-control-allow-credentials = false
|
||||
http-validate-host = false
|
||||
producer-name=eosio
|
||||
producer-name=core
|
||||
signature-provider = EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV=KEY:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
|
||||
|
||||
http-max-response-time-ms = 30000
|
||||
max-body-size = 10485760
|
||||
abi-serializer-max-time-ms = 200000
|
||||
contracts-console = true
|
||||
|
||||
max-block-cpu-usage-threshold-us = 5000
|
||||
max-block-net-usage-threshold-bytes = 1024
|
||||
|
||||
verbose-http-errors = true
|
||||
chain-state-history = true
|
||||
trace-history = true
|
||||
state-history-endpoint = 0.0.0.0:8070
|
||||
resource-monitor-space-threshold = 99
|
||||
resource-monitor-not-shutdown-on-threshold-exceeded = true
|
||||
wasm-runtime = eos-vm
|
||||
|
||||
# Percentage of cpu block production time used to produce block. Whole number percentages, e.g. 80 for 80% (eosio::producer_plugin)
|
||||
# cpu-effort-percent = 80
|
||||
|
||||
# Percentage of cpu block production time used to produce last block. Whole number percentages, e.g. 80 for 80% (eosio::producer_plugin)
|
||||
# last-block-cpu-effort-percent = 80
|
||||
|
||||
|
||||
# the location of the blocks directory (absolute path or relative to application data dir) (eosio::chain_plugin)
|
||||
# blocks-dir = "blocks"
|
||||
|
||||
# split the block log file when the head block number is the multiple of the stride
|
||||
# When the stride is reached, the current block log and index will be renamed '<blocks-retained-dir>/blocks-<start num>-<end num>.log/index'
|
||||
# and a new current block log and index will be created with the most recent block. All files following
|
||||
# this format will be used to construct an extended block log. (eosio::chain_plugin)
|
||||
# blocks-log-stride =
|
||||
|
||||
# the maximum number of blocks files to retain so that the blocks in those files can be queried.
|
||||
# When the number is reached, the oldest block file would be moved to archive dir or deleted if the archive dir is empty.
|
||||
# The retained block log files should not be manipulated by users. (eosio::chain_plugin)
|
||||
# max-retained-block-files =
|
||||
|
||||
# the location of the blocks retained directory (absolute path or relative to blocks dir).
|
||||
# If the value is empty, it is set to the value of blocks dir. (eosio::chain_plugin)
|
||||
# blocks-retained-dir =
|
||||
|
||||
# the location of the blocks archive directory (absolute path or relative to blocks dir).
|
||||
# If the value is empty, blocks files beyond the retained limit will be deleted.
|
||||
# All files in the archive directory are completely under user's control, i.e. they won't be accessed by nodeos anymore. (eosio::chain_plugin)
|
||||
# blocks-archive-dir =
|
||||
|
||||
# the location of the state directory (absolute path or relative to application data dir) (eosio::chain_plugin)
|
||||
# state-dir = "state"
|
||||
|
||||
# the location of the protocol_features directory (absolute path or relative to application config dir) (eosio::chain_plugin)
|
||||
# protocol-features-dir = "protocol_features"
|
||||
|
||||
# Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints. (eosio::chain_plugin)
|
||||
# checkpoint =
|
||||
|
||||
# Override default WASM runtime ( "eos-vm-jit", "eos-vm")
|
||||
# "eos-vm-jit" : A WebAssembly runtime that compiles WebAssembly code to native x86 code prior to execution.
|
||||
# "eos-vm" : A WebAssembly interpreter.
|
||||
# (eosio::chain_plugin)
|
||||
# wasm-runtime = eos-vm-jit
|
||||
|
||||
# The name of an account whose code will be profiled (eosio::chain_plugin)
|
||||
# profile-account =
|
||||
|
||||
# Override default maximum ABI serialization time allowed in ms (eosio::chain_plugin)
|
||||
# abi-serializer-max-time-ms = 15
|
||||
|
||||
# Maximum size (in MiB) of the chain state database (eosio::chain_plugin)
|
||||
# chain-state-db-size-mb = 1024
|
||||
|
||||
# Safely shut down node when free space remaining in the chain state database drops below this size (in MiB). (eosio::chain_plugin)
|
||||
# chain-state-db-guard-size-mb = 128
|
||||
|
||||
# Percentage of actual signature recovery cpu to bill. Whole number percentages, e.g. 50 for 50% (eosio::chain_plugin)
|
||||
# signature-cpu-billable-pct = 50
|
||||
|
||||
# Number of worker threads in controller thread pool (eosio::chain_plugin)
|
||||
# chain-threads = 2
|
||||
|
||||
# print contract's output to console (eosio::chain_plugin)
|
||||
# contracts-console = false
|
||||
|
||||
# print deeper information about chain operations (eosio::chain_plugin)
|
||||
deep-mind = false
|
||||
|
||||
# Account added to actor whitelist (may specify multiple times) (eosio::chain_plugin)
|
||||
# actor-whitelist =
|
||||
|
||||
# Account added to actor blacklist (may specify multiple times) (eosio::chain_plugin)
|
||||
# actor-blacklist =
|
||||
|
||||
# Contract account added to contract whitelist (may specify multiple times) (eosio::chain_plugin)
|
||||
# contract-whitelist =
|
||||
|
||||
# Contract account added to contract blacklist (may specify multiple times) (eosio::chain_plugin)
|
||||
# contract-blacklist =
|
||||
|
||||
# Action (in the form code::action) added to action blacklist (may specify multiple times) (eosio::chain_plugin)
|
||||
# action-blacklist =
|
||||
|
||||
# Public key added to blacklist of keys that should not be included in authorities (may specify multiple times) (eosio::chain_plugin)
|
||||
# key-blacklist =
|
||||
|
||||
# Deferred transactions sent by accounts in this list do not have any of the subjective whitelist/blacklist checks applied to them (may specify multiple times) (eosio::chain_plugin)
|
||||
# sender-bypass-whiteblacklist =
|
||||
|
||||
# Database read mode ("head", "irreversible", "speculative").
|
||||
# In "head" mode: database contains state changes up to the head block; transactions received by the node are relayed if valid.
|
||||
# In "irreversible" mode: database contains state changes up to the last irreversible block; transactions received via the P2P network are not relayed and transactions cannot be pushed via the chain API.
|
||||
# In "speculative" mode: database contains state changes by transactions in the blockchain up to the head block as well as some transactions not yet included in the blockchain; transactions received by the node are relayed if valid.
|
||||
# (eosio::chain_plugin)
|
||||
# read-mode = head
|
||||
|
||||
# Allow API transactions to be evaluated and relayed if valid. (eosio::chain_plugin)
|
||||
api-accept-transactions = true
|
||||
|
||||
# Chain validation mode ("full" or "light").
|
||||
# In "full" mode all incoming blocks will be fully validated.
|
||||
# In "light" mode all incoming blocks headers will be fully validated; transactions in those validated blocks will be trusted
|
||||
# (eosio::chain_plugin)
|
||||
# validation-mode = full
|
||||
|
||||
# Disable the check which subjectively fails a transaction if a contract bills more RAM to another account within the context of a notification handler (i.e. when the receiver is not the code of the action). (eosio::chain_plugin)
|
||||
# disable-ram-billing-notify-checks = false
|
||||
|
||||
# Subjectively limit the maximum length of variable components in a variable legnth signature to this size in bytes (eosio::chain_plugin)
|
||||
# maximum-variable-signature-length = 16384
|
||||
|
||||
# Indicate a producer whose blocks headers signed by it will be fully validated, but transactions in those validated blocks will be trusted. (eosio::chain_plugin)
|
||||
# trusted-producer =
|
||||
|
||||
# Database map mode ("mapped", "heap", or "locked").
|
||||
# In "mapped" mode database is memory mapped as a file.
|
||||
# In "heap" mode database is preloaded in to swappable memory and will use huge pages if available.
|
||||
# In "locked" mode database is preloaded, locked in to memory, and will use huge pages if available.
|
||||
# (eosio::chain_plugin)
|
||||
# database-map-mode = mapped
|
||||
|
||||
# Maximum size (in MiB) of the EOS VM OC code cache (eosio::chain_plugin)
|
||||
# eos-vm-oc-cache-size-mb = 1024
|
||||
|
||||
# Number of threads to use for EOS VM OC tier-up (eosio::chain_plugin)
|
||||
# eos-vm-oc-compile-threads = 1
|
||||
|
||||
# Enable EOS VM OC tier-up runtime (eosio::chain_plugin)
|
||||
# eos-vm-oc-enable = false
|
||||
|
||||
# enable queries to find accounts by various metadata. (eosio::chain_plugin)
|
||||
# enable-account-queries = false
|
||||
|
||||
# maximum allowed size (in bytes) of an inline action for a nonprivileged account (eosio::chain_plugin)
|
||||
# max-nonprivileged-inline-action-size = 4096
|
||||
|
||||
# Maximum size (in GiB) allowed to be allocated for the Transaction Retry feature. Setting above 0 enables this feature. (eosio::chain_plugin)
|
||||
# transaction-retry-max-storage-size-gb =
|
||||
|
||||
# How often, in seconds, to resend an incoming transaction to network if not seen in a block.
|
||||
# Needs to be at least twice as large as p2p-dedup-cache-expire-time-sec. (eosio::chain_plugin)
|
||||
# transaction-retry-interval-sec = 20
|
||||
|
||||
# Maximum allowed transaction expiration for retry transactions, will retry transactions up to this value.
|
||||
# Should be larger than transaction-retry-interval-sec. (eosio::chain_plugin)
|
||||
# transaction-retry-max-expiration-sec = 120
|
||||
|
||||
# Maximum size (in GiB) allowed to be allocated for the Transaction Finality Status feature. Setting above 0 enables this feature. (eosio::chain_plugin)
|
||||
# transaction-finality-status-max-storage-size-gb =
|
||||
|
||||
# Duration (in seconds) a successful transaction's Finality Status will remain available from being first identified. (eosio::chain_plugin)
|
||||
# transaction-finality-status-success-duration-sec = 180
|
||||
|
||||
# Duration (in seconds) a failed transaction's Finality Status will remain available from being first identified. (eosio::chain_plugin)
|
||||
# transaction-finality-status-failure-duration-sec = 180
|
||||
|
||||
# Log the state integrity hash on startup (eosio::chain_plugin)
|
||||
# integrity-hash-on-start = false
|
||||
|
||||
# Log the state integrity hash on shutdown (eosio::chain_plugin)
|
||||
# integrity-hash-on-stop = false
|
||||
|
||||
# If set to greater than 0, periodically prune the block log to store only configured number of most recent blocks.
|
||||
# If set to 0, no blocks are be written to the block log; block log file is removed after startup. (eosio::chain_plugin)
|
||||
# block-log-retain-blocks =
|
||||
|
||||
# The filename (relative to data-dir) to create a unix socket for HTTP RPC; set blank to disable. (eosio::http_plugin)
|
||||
# unix-socket-path =
|
||||
|
||||
# The local IP and port to listen for incoming http connections; set blank to disable. (eosio::http_plugin)
|
||||
# http-server-address = 127.0.0.1:8888
|
||||
|
||||
# Specify the Access-Control-Allow-Origin to be returned on each request (eosio::http_plugin)
|
||||
# access-control-allow-origin =
|
||||
|
||||
# Specify the Access-Control-Allow-Headers to be returned on each request (eosio::http_plugin)
|
||||
# access-control-allow-headers =
|
||||
|
||||
# Specify the Access-Control-Max-Age to be returned on each request. (eosio::http_plugin)
|
||||
# access-control-max-age =
|
||||
|
||||
# Specify if Access-Control-Allow-Credentials: true should be returned on each request. (eosio::http_plugin)
|
||||
# access-control-allow-credentials = false
|
||||
|
||||
# The maximum body size in bytes allowed for incoming RPC requests (eosio::http_plugin)
|
||||
# max-body-size = 2097152
|
||||
|
||||
# Maximum size in megabytes http_plugin should use for processing http requests. -1 for unlimited. 429 error response when exceeded. (eosio::http_plugin)
|
||||
# http-max-bytes-in-flight-mb = 500
|
||||
|
||||
# Maximum number of requests http_plugin should use for processing http requests. 429 error response when exceeded. (eosio::http_plugin)
|
||||
# http-max-in-flight-requests = -1
|
||||
|
||||
# Maximum time for processing a request, -1 for unlimited (eosio::http_plugin)
|
||||
# http-max-response-time-ms = 30
|
||||
|
||||
# Append the error log to HTTP responses (eosio::http_plugin)
|
||||
# verbose-http-errors = false
|
||||
|
||||
# If set to false, then any incoming "Host" header is considered valid (eosio::http_plugin)
|
||||
# http-validate-host = true
|
||||
|
||||
# Additionaly acceptable values for the "Host" header of incoming HTTP requests, can be specified multiple times. Includes http/s_server_address by default. (eosio::http_plugin)
|
||||
# http-alias =
|
||||
|
||||
# Number of worker threads in http thread pool (eosio::http_plugin)
|
||||
# http-threads = 2
|
||||
|
||||
# If set to false, do not keep HTTP connections alive, even if client requests. (eosio::http_plugin)
|
||||
# http-keep-alive = true
|
||||
|
||||
# The actual host:port used to listen for incoming p2p connections. (eosio::net_plugin)
|
||||
# p2p-listen-endpoint = 0.0.0.0:9876
|
||||
|
||||
# An externally accessible host:port for identifying this node. Defaults to p2p-listen-endpoint. (eosio::net_plugin)
|
||||
# p2p-server-address =
|
||||
|
||||
# The public endpoint of a peer node to connect to. Use multiple p2p-peer-address options as needed to compose a network.
|
||||
# Syntax: host:port[:<trx>|<blk>]
|
||||
# The optional 'trx' and 'blk' indicates to node that only transactions 'trx' or blocks 'blk' should be sent. Examples:
|
||||
# p2p.eos.io:9876
|
||||
# p2p.trx.eos.io:9876:trx
|
||||
# p2p.blk.eos.io:9876:blk
|
||||
# (eosio::net_plugin)
|
||||
# p2p-peer-address =
|
||||
|
||||
# Maximum number of client nodes from any single IP address (eosio::net_plugin)
|
||||
# p2p-max-nodes-per-host = 1
|
||||
|
||||
# Allow transactions received over p2p network to be evaluated and relayed if valid. (eosio::net_plugin)
|
||||
# p2p-accept-transactions = true
|
||||
|
||||
# The account and public p2p endpoint of a block producer node to automatically connect to when the it is in producer schedule proximity
|
||||
# . Syntax: account,host:port
|
||||
# Example,
|
||||
# eosproducer1,p2p.eos.io:9876
|
||||
# eosproducer2,p2p.trx.eos.io:9876:trx
|
||||
# eosproducer3,p2p.blk.eos.io:9876:blk
|
||||
# (eosio::net_plugin)
|
||||
# p2p-auto-bp-peer =
|
||||
|
||||
# The name supplied to identify this node amongst the peers. (eosio::net_plugin)
|
||||
# agent-name = EOS Test Agent
|
||||
|
||||
# Can be 'any' or 'producers' or 'specified' or 'none'. If 'specified', peer-key must be specified at least once. If only 'producers', peer-key is not required. 'producers' and 'specified' may be combined. (eosio::net_plugin)
|
||||
# allowed-connection = any
|
||||
|
||||
# Optional public key of peer allowed to connect. May be used multiple times. (eosio::net_plugin)
|
||||
# peer-key =
|
||||
|
||||
# Tuple of [PublicKey, WIF private key] (may specify multiple times) (eosio::net_plugin)
|
||||
# peer-private-key =
|
||||
|
||||
# Maximum number of clients from which connections are accepted, use 0 for no limit (eosio::net_plugin)
|
||||
# max-clients = 25
|
||||
|
||||
# number of seconds to wait before cleaning up dead connections (eosio::net_plugin)
|
||||
# connection-cleanup-period = 30
|
||||
|
||||
# max connection cleanup time per cleanup call in milliseconds (eosio::net_plugin)
|
||||
# max-cleanup-time-msec = 10
|
||||
|
||||
# Maximum time to track transaction for duplicate optimization (eosio::net_plugin)
|
||||
# p2p-dedup-cache-expire-time-sec = 10
|
||||
|
||||
# Number of worker threads in net_plugin thread pool (eosio::net_plugin)
|
||||
# net-threads = 4
|
||||
|
||||
# number of blocks to retrieve in a chunk from any individual peer during synchronization (eosio::net_plugin)
|
||||
# sync-fetch-span = 100
|
||||
|
||||
# Enable experimental socket read watermark optimization (eosio::net_plugin)
|
||||
# use-socket-read-watermark = false
|
||||
|
||||
# The string used to format peers when logging messages about them. Variables are escaped with ${<variable name>}.
|
||||
# Available Variables:
|
||||
# _name self-reported name
|
||||
#
|
||||
# _cid assigned connection id
|
||||
#
|
||||
# _id self-reported ID (64 hex characters)
|
||||
#
|
||||
# _sid first 8 characters of _peer.id
|
||||
#
|
||||
# _ip remote IP address of peer
|
||||
#
|
||||
# _port remote port number of peer
|
||||
#
|
||||
# _lip local IP address connected to peer
|
||||
#
|
||||
# _lport local port number connected to peer
|
||||
#
|
||||
# (eosio::net_plugin)
|
||||
# peer-log-format = ["${_name}" - ${_cid} ${_ip}:${_port}]
|
||||
|
||||
# peer heartbeat keepalive message interval in milliseconds (eosio::net_plugin)
|
||||
# p2p-keepalive-interval-ms = 10000
|
||||
|
||||
# Enable block production, even if the chain is stale. (eosio::producer_plugin)
|
||||
# enable-stale-production = false
|
||||
|
||||
# Start this node in a state where production is paused (eosio::producer_plugin)
|
||||
# pause-on-startup = false
|
||||
|
||||
# Limits the maximum time (in milliseconds) that is allowed a pushed transaction's code to execute before being considered invalid (eosio::producer_plugin)
|
||||
# max-transaction-time = 30
|
||||
|
||||
# Limits the maximum age (in seconds) of the DPOS Irreversible Block for a chain this node will produce blocks on (use negative value to indicate unlimited) (eosio::producer_plugin)
|
||||
# max-irreversible-block-age = -1
|
||||
|
||||
# ID of producer controlled by this node (e.g. inita; may specify multiple times) (eosio::producer_plugin)
|
||||
# producer-name =
|
||||
|
||||
# Key=Value pairs in the form <public-key>=<provider-spec>
|
||||
# Where:
|
||||
# <public-key> is a string form of a vaild EOSIO public key
|
||||
#
|
||||
# <provider-spec> is a string in the form <provider-type>:<data>
|
||||
#
|
||||
# <provider-type> is KEY, KEOSD, or SE
|
||||
#
|
||||
# KEY:<data> is a string form of a valid EOSIO private key which maps to the provided public key
|
||||
#
|
||||
# KEOSD:<data> is the URL where keosd is available and the approptiate wallet(s) are unlocked
|
||||
#
|
||||
# (eosio::producer_plugin)
|
||||
# signature-provider = EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV=KEY:5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
|
||||
|
||||
# account that can not access to extended CPU/NET virtual resources (eosio::producer_plugin)
|
||||
# greylist-account =
|
||||
|
||||
# Limit (between 1 and 1000) on the multiple that CPU/NET virtual resources can extend during low usage (only enforced subjectively; use 1000 to not enforce any limit) (eosio::producer_plugin)
|
||||
# greylist-limit = 1000
|
||||
|
||||
# Offset of non last block producing time in microseconds. Valid range 0 .. -block_time_interval. (eosio::producer_plugin)
|
||||
# produce-time-offset-us = 0
|
||||
|
||||
# Offset of last block producing time in microseconds. Valid range 0 .. -block_time_interval. (eosio::producer_plugin)
|
||||
# last-block-time-offset-us = -200000
|
||||
|
||||
# Percentage of cpu block production time used to produce block. Whole number percentages, e.g. 80 for 80% (eosio::producer_plugin)
|
||||
# cpu-effort-percent = 10
|
||||
|
||||
# Percentage of cpu block production time used to produce last block. Whole number percentages, e.g. 80 for 80% (eosio::producer_plugin)
|
||||
# last-block-cpu-effort-percent = 10
|
||||
|
||||
# Threshold of CPU block production to consider block full; when within threshold of max-block-cpu-usage block can be produced immediately (eosio::producer_plugin)
|
||||
# max-block-cpu-usage-threshold-us = 5000
|
||||
|
||||
# Threshold of NET block production to consider block full; when within threshold of max-block-net-usage block can be produced immediately (eosio::producer_plugin)
|
||||
# max-block-net-usage-threshold-bytes = 1024
|
||||
|
||||
# Maximum wall-clock time, in milliseconds, spent retiring scheduled transactions (and incoming transactions according to incoming-defer-ratio) in any block before returning to normal transaction processing. (eosio::producer_plugin)
|
||||
# max-scheduled-transaction-time-per-block-ms = 100
|
||||
|
||||
# Time in microseconds allowed for a transaction that starts with insufficient CPU quota to complete and cover its CPU usage. (eosio::producer_plugin)
|
||||
# subjective-cpu-leeway-us = 31000
|
||||
|
||||
# Sets the maximum amount of failures that are allowed for a given account per window size. (eosio::producer_plugin)
|
||||
# subjective-account-max-failures = 3
|
||||
|
||||
# Sets the window size in number of blocks for subjective-account-max-failures. (eosio::producer_plugin)
|
||||
# subjective-account-max-failures-window-size = 1
|
||||
|
||||
# Sets the time to return full subjective cpu for accounts (eosio::producer_plugin)
|
||||
# subjective-account-decay-time-minutes = 1440
|
||||
|
||||
# ratio between incoming transactions and deferred transactions when both are queued for execution (eosio::producer_plugin)
|
||||
# incoming-defer-ratio = 1
|
||||
|
||||
# Maximum size (in MiB) of the incoming transaction queue. Exceeding this value will subjectively drop transaction with resource exhaustion. (eosio::producer_plugin)
|
||||
# incoming-transaction-queue-size-mb = 1024
|
||||
|
||||
# Disable subjective CPU billing for API/P2P transactions (eosio::producer_plugin)
|
||||
# disable-subjective-billing = true
|
||||
|
||||
# Account which is excluded from subjective CPU billing (eosio::producer_plugin)
|
||||
# disable-subjective-account-billing =
|
||||
|
||||
# Disable subjective CPU billing for P2P transactions (eosio::producer_plugin)
|
||||
# disable-subjective-p2p-billing = true
|
||||
|
||||
# Disable subjective CPU billing for API transactions (eosio::producer_plugin)
|
||||
# disable-subjective-api-billing = true
|
||||
|
||||
# Number of worker threads in producer thread pool (eosio::producer_plugin)
|
||||
# producer-threads = 1
|
||||
|
||||
# the location of the snapshots directory (absolute path or relative to application data dir) (eosio::producer_plugin)
|
||||
# snapshots-dir = "snapshots"
|
||||
|
||||
# Number of worker threads in read-only execution thread pool. Max 8. (eosio::producer_plugin)
|
||||
# read-only-threads =
|
||||
|
||||
# Time in microseconds the write window lasts. (eosio::producer_plugin)
|
||||
# read-only-write-window-time-us = 200000
|
||||
|
||||
# Time in microseconds the read window lasts. (eosio::producer_plugin)
|
||||
# read-only-read-window-time-us = 60000
|
||||
|
||||
# The local IP and port to listen for incoming prometheus metrics http request. (eosio::prometheus_plugin)
|
||||
# prometheus-exporter-address = 127.0.0.1:9101
|
||||
|
||||
# Time in seconds between two consecutive checks of resource usage. Should be between 1 and 300 (eosio::resource_monitor_plugin)
|
||||
# resource-monitor-interval-seconds = 2
|
||||
|
||||
# Threshold in terms of percentage of used space vs total space. If used space is above (threshold - 5%), a warning is generated. Unless resource-monitor-not-shutdown-on-threshold-exceeded is enabled, a graceful shutdown is initiated if used space is above the threshold. The value should be between 6 and 99 (eosio::resource_monitor_plugin)
|
||||
# resource-monitor-space-threshold = 90
|
||||
|
||||
# Absolute threshold in gibibytes of remaining space; applied to each monitored directory. If remaining space is less than value for any monitored directories then threshold is considered exceeded.Overrides resource-monitor-space-threshold value. (eosio::resource_monitor_plugin)
|
||||
# resource-monitor-space-absolute-gb =
|
||||
|
||||
# Used to indicate nodeos will not shutdown when threshold is exceeded. (eosio::resource_monitor_plugin)
|
||||
# resource-monitor-not-shutdown-on-threshold-exceeded =
|
||||
|
||||
# Number of resource monitor intervals between two consecutive warnings when the threshold is hit. Should be between 1 and 450 (eosio::resource_monitor_plugin)
|
||||
# resource-monitor-warning-interval = 30
|
||||
|
||||
# Limits the maximum time (in milliseconds) that is allowed for sending requests to a keosd provider for signing (eosio::signature_provider_plugin)
|
||||
# keosd-provider-timeout = 5
|
||||
|
||||
# the location of the state-history directory (absolute path or relative to application data dir) (eosio::state_history_plugin)
|
||||
# state-history-dir = "state-history"
|
||||
|
||||
# the location of the state history retained directory (absolute path or relative to state-history dir). (eosio::state_history_plugin)
|
||||
# state-history-retained-dir =
|
||||
|
||||
# the location of the state history archive directory (absolute path or relative to state-history dir).
|
||||
# If the value is empty string, blocks files beyond the retained limit will be deleted.
|
||||
# All files in the archive directory are completely under user's control, i.e. they won't be accessed by nodeos anymore. (eosio::state_history_plugin)
|
||||
# state-history-archive-dir =
|
||||
|
||||
# split the state history log files when the block number is the multiple of the stride
|
||||
# When the stride is reached, the current history log and index will be renamed '*-history-<start num>-<end num>.log/index'
|
||||
# and a new current history log and index will be created with the most recent blocks. All files following
|
||||
# this format will be used to construct an extended history log. (eosio::state_history_plugin)
|
||||
# state-history-stride =
|
||||
|
||||
# the maximum number of history file groups to retain so that the blocks in those files can be queried.
|
||||
# When the number is reached, the oldest history file would be moved to archive dir or deleted if the archive dir is empty.
|
||||
# The retained history log files should not be manipulated by users. (eosio::state_history_plugin)
|
||||
# max-retained-history-files =
|
||||
|
||||
# enable trace history (eosio::state_history_plugin)
|
||||
# trace-history = false
|
||||
|
||||
# enable chain state history (eosio::state_history_plugin)
|
||||
# chain-state-history = false
|
||||
|
||||
# the endpoint upon which to listen for incoming connections. Caution: only expose this port to your internal network. (eosio::state_history_plugin)
|
||||
# state-history-endpoint = 127.0.0.1:8080
|
||||
|
||||
# the path (relative to data-dir) to create a unix socket upon which to listen for incoming connections. (eosio::state_history_plugin)
|
||||
# state-history-unix-socket-path =
|
||||
|
||||
# enable debug mode for trace history (eosio::state_history_plugin)
|
||||
# trace-history-debug-mode = false
|
||||
|
||||
# if set, periodically prune the state history files to store only configured number of most recent blocks (eosio::state_history_plugin)
|
||||
# state-history-log-retain-blocks =
|
||||
|
||||
# the location of the trace directory (absolute path or relative to application data dir) (eosio::trace_api_plugin)
|
||||
# trace-dir = "traces"
|
||||
|
||||
# the number of blocks each "slice" of trace data will contain on the filesystem (eosio::trace_api_plugin)
|
||||
# trace-slice-stride = 10000
|
||||
|
||||
# Number of blocks to ensure are kept past LIB for retrieval before "slice" files can be automatically removed.
|
||||
# A value of -1 indicates that automatic removal of "slice" files will be turned off. (eosio::trace_api_plugin)
|
||||
# trace-minimum-irreversible-history-blocks = -1
|
||||
|
||||
# Number of blocks to ensure are uncompressed past LIB. Compressed "slice" files are still accessible but may carry a performance loss on retrieval
|
||||
# A value of -1 indicates that automatic compression of "slice" files will be turned off. (eosio::trace_api_plugin)
|
||||
# trace-minimum-uncompressed-irreversible-history-blocks = -1
|
||||
|
||||
# ABIs used when decoding trace RPC responses.
|
||||
# There must be at least one ABI specified OR the flag trace-no-abis must be used.
|
||||
# ABIs are specified as "Key=Value" pairs in the form <account-name>=<abi-def>
|
||||
# Where <abi-def> can be:
|
||||
# an absolute path to a file containing a valid JSON-encoded ABI
|
||||
# a relative path from `data-dir` to a file containing a valid JSON-encoded ABI
|
||||
# (eosio::trace_api_plugin)
|
||||
# trace-rpc-abi =
|
||||
|
||||
# Use to indicate that the RPC responses will not use ABIs.
|
||||
# Failure to specify this option when there are no trace-rpc-abi configuations will result in an Error.
|
||||
# This option is mutually exclusive with trace-rpc-api (eosio::trace_api_plugin)
|
||||
# trace-no-abis =
|
||||
|
||||
# Plugin(s) to enable, may be specified multiple times
|
||||
# plugin =
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/* eslint-disable node/prefer-global/process */
|
||||
import path from 'node:path'
|
||||
import { config } from 'dotenv'
|
||||
|
||||
config()
|
||||
|
||||
export default [
|
||||
{
|
||||
name: 'eosio.boot',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.boot'),
|
||||
target: 'eosio',
|
||||
},
|
||||
{
|
||||
name: 'eosio.system',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.system'),
|
||||
target: 'eosio',
|
||||
},
|
||||
{
|
||||
name: 'eosio.token',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.token'),
|
||||
target: 'eosio.token',
|
||||
},
|
||||
{
|
||||
name: 'eosio.msig',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.msig'),
|
||||
target: 'eosio.msig',
|
||||
},
|
||||
{
|
||||
name: 'eosio.wrap',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/system/contracts/eosio.wrap'),
|
||||
target: 'eosio.wrap',
|
||||
},
|
||||
{
|
||||
name: 'registrator',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/registrator'),
|
||||
target: 'registrator',
|
||||
},
|
||||
{
|
||||
name: 'soviet',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/soviet'),
|
||||
target: 'soviet',
|
||||
},
|
||||
{
|
||||
name: 'marketplace',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/marketplace'),
|
||||
target: 'marketplace',
|
||||
},
|
||||
{
|
||||
name: 'draft',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/draft'),
|
||||
target: 'draft',
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/branch'),
|
||||
target: 'branch',
|
||||
},
|
||||
{
|
||||
name: 'gateway',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/gateway'),
|
||||
target: 'gateway',
|
||||
},
|
||||
{
|
||||
name: 'fund',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/fund'),
|
||||
target: 'fund',
|
||||
},
|
||||
{
|
||||
name: 'ledger',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/ledger'),
|
||||
target: 'ledger',
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/test'),
|
||||
target: 'test',
|
||||
},
|
||||
{
|
||||
name: 'contributor',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/contributor'),
|
||||
target: 'contributor',
|
||||
},
|
||||
{
|
||||
name: 'capital',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/capital'),
|
||||
target: 'capital',
|
||||
},
|
||||
{
|
||||
name: 'wallet',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/wallet'),
|
||||
target: 'wallet',
|
||||
},
|
||||
{
|
||||
name: 'loan',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/loan'),
|
||||
target: 'loan',
|
||||
},
|
||||
{
|
||||
name: 'meet',
|
||||
path: path.resolve(process.cwd(), '../contracts/build/contracts/meet'),
|
||||
target: 'meet',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"initial_timestamp": "2024-06-19T06:00:00.000",
|
||||
"initial_key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
|
||||
"initial_configuration": {
|
||||
"max_block_net_usage": 1048576,
|
||||
"target_block_net_usage_pct": 1000,
|
||||
"max_transaction_net_usage": 1048575,
|
||||
"base_per_transaction_net_usage": 12,
|
||||
"net_usage_leeway": 500,
|
||||
"context_free_discount_net_usage_num": 20,
|
||||
"context_free_discount_net_usage_den": 100,
|
||||
"max_block_cpu_usage": 300000,
|
||||
"target_block_cpu_usage_pct": 500,
|
||||
"max_transaction_cpu_usage": 290000,
|
||||
"min_transaction_cpu_usage": 100,
|
||||
"max_transaction_lifetime": 3600,
|
||||
"deferred_trx_expiration_window": 600,
|
||||
"max_transaction_delay": 3888000,
|
||||
"max_inline_action_size": 4096,
|
||||
"max_inline_action_depth": 10,
|
||||
"max_authority_depth": 6
|
||||
},
|
||||
"initial_chain_id": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/* eslint-disable node/prefer-global/process */
|
||||
import path from 'node:path'
|
||||
import { config } from 'dotenv'
|
||||
import contracts from './contracts'
|
||||
|
||||
config()
|
||||
|
||||
const network = {
|
||||
name: 'local',
|
||||
protocol: 'http',
|
||||
host: 'localhost',
|
||||
port: ':8888',
|
||||
}
|
||||
|
||||
// const network = {
|
||||
// name: 'local',
|
||||
// protocol: 'https',
|
||||
// host: 'test.coopenomics.world:443/api',
|
||||
// port: '',
|
||||
// }
|
||||
|
||||
export const SYMBOL = 'AXON'
|
||||
export const GOVERN_SYMBOL = 'RUB'
|
||||
export const provider = 'voskhod'
|
||||
export const provider_chairman = 'ant'
|
||||
|
||||
export const public_key = process.env.EOSIO_PUB_KEY as string
|
||||
|
||||
export const private_key = process.env.EOSIO_PRV_KEY as string
|
||||
|
||||
export default {
|
||||
network,
|
||||
default_public_key: public_key,
|
||||
system: 'eosio',
|
||||
provider,
|
||||
provider_chairman,
|
||||
private_keys: [private_key],
|
||||
emission: {
|
||||
left_border: `1000.0000 ${SYMBOL}`,
|
||||
tact_duration: 86400,
|
||||
emission_factor: 0.618,
|
||||
},
|
||||
token: {
|
||||
symbol: SYMBOL,
|
||||
govern_symbol: GOVERN_SYMBOL,
|
||||
precision: 4,
|
||||
max_supply: `1000000000.0000 ${SYMBOL}`,
|
||||
},
|
||||
powerup: {
|
||||
days: 1,
|
||||
min_powerup: `0.0001 ${SYMBOL}`,
|
||||
},
|
||||
allocations: [
|
||||
{
|
||||
to: 'eosio',
|
||||
quantity: `10000.0000 ${SYMBOL}`,
|
||||
},
|
||||
],
|
||||
accounts: [
|
||||
{
|
||||
name: 'eosio.token',
|
||||
},
|
||||
{
|
||||
name: 'eosio.bpay',
|
||||
},
|
||||
{
|
||||
name: 'eosio.vpay',
|
||||
},
|
||||
{
|
||||
name: 'eosio.msig',
|
||||
},
|
||||
{
|
||||
name: 'eosio.wrap',
|
||||
},
|
||||
{
|
||||
name: 'eosio.power',
|
||||
},
|
||||
{
|
||||
name: 'eosio.saving',
|
||||
code_permissions_to: ['eosio.saving'],
|
||||
},
|
||||
{
|
||||
name: 'registrator',
|
||||
code_permissions_to: ['registrator'],
|
||||
},
|
||||
{
|
||||
name: 'capital',
|
||||
code_permissions_to: ['capital'],
|
||||
},
|
||||
{
|
||||
name: 'loan',
|
||||
code_permissions_to: ['loan'],
|
||||
},
|
||||
{
|
||||
name: 'ledger',
|
||||
code_permissions_to: ['ledger'],
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
code_permissions_to: ['test'],
|
||||
},
|
||||
{
|
||||
name: 'meet',
|
||||
code_permissions_to: ['meet'],
|
||||
},
|
||||
{
|
||||
name: 'wallet',
|
||||
code_permissions_to: ['wallet'],
|
||||
},
|
||||
{
|
||||
name: 'soviet',
|
||||
code_permissions_to: ['soviet'],
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
code_permissions_to: ['branch'],
|
||||
},
|
||||
{
|
||||
name: 'marketplace',
|
||||
code_permissions_to: ['marketplace'],
|
||||
},
|
||||
{
|
||||
name: 'draft',
|
||||
code_permissions_to: ['draft'],
|
||||
},
|
||||
{
|
||||
name: 'gateway',
|
||||
code_permissions_to: ['gateway'],
|
||||
},
|
||||
{
|
||||
name: 'fund',
|
||||
code_permissions_to: ['fund'],
|
||||
},
|
||||
{
|
||||
name: 'contributor',
|
||||
code_permissions_to: ['contributor'],
|
||||
},
|
||||
{
|
||||
name: provider_chairman,
|
||||
},
|
||||
{
|
||||
name: provider,
|
||||
code_permissions_to: ['registrator'],
|
||||
},
|
||||
],
|
||||
contracts,
|
||||
features: [
|
||||
{
|
||||
name: 'ACTION_RETURN_VALUE',
|
||||
hash: 'c3a6138c5061cf291310887c0b5c71fcaffeab90d5deb50d3b9e687cead45071',
|
||||
},
|
||||
{
|
||||
name: 'CONFIGURABLE_WASM_LIMITS2',
|
||||
hash: 'd528b9f6e9693f45ed277af93474fd473ce7d831dae2180cca35d907bd10cb40',
|
||||
},
|
||||
{
|
||||
name: 'BLOCKCHAIN_PARAMETERS',
|
||||
hash: '5443fcf88330c586bc0e5f3dee10e7f63c76c00249c87fe4fbf7f38c082006b4',
|
||||
},
|
||||
{
|
||||
name: 'GET_SENDER',
|
||||
hash: 'f0af56d2c5a48d60a4a5b5c903edfb7db3a736a94ed589d0b797df33ff9d3e1d',
|
||||
},
|
||||
{
|
||||
name: 'FORWARD_SETCODE',
|
||||
hash: '2652f5f96006294109b3dd0bbde63693f55324af452b799ee137a81a905eed25',
|
||||
},
|
||||
{
|
||||
name: 'ONLY_BILL_FIRST_AUTHORIZER',
|
||||
hash: '8ba52fe7a3956c5cd3a656a3174b931d3bb2abb45578befc59f283ecd816a405',
|
||||
},
|
||||
{
|
||||
name: 'RESTRICT_ACTION_TO_SELF',
|
||||
hash: 'ad9e3d8f650687709fd68f4b90b41f7d825a365b02c23a636cef88ac2ac00c43',
|
||||
},
|
||||
{
|
||||
name: 'DISALLOW_EMPTY_PRODUCER_SCHEDULE',
|
||||
hash: '68dcaa34c0517d19666e6b33add67351d8c5f69e999ca1e37931bc410a297428',
|
||||
},
|
||||
{
|
||||
name: 'FIX_LINKAUTH_RESTRICTION',
|
||||
hash: 'e0fb64b1085cc5538970158d05a009c24e276fb94e1a0bf6a528b48fbc4ff526',
|
||||
},
|
||||
{
|
||||
name: 'REPLACE_DEFERRED',
|
||||
hash: 'ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99',
|
||||
},
|
||||
{
|
||||
name: 'NO_DUPLICATE_DEFERRED_ID',
|
||||
hash: '4a90c00d55454dc5b059055ca213579c6ea856967712a56017487886a4d4cc0f',
|
||||
},
|
||||
{
|
||||
name: 'ONLY_LINK_TO_EXISTING_PERMISSION',
|
||||
hash: '1a99a59d87e06e09ec5b028a9cbb7749b4a5ad8819004365d02dc4379a8b7241',
|
||||
},
|
||||
{
|
||||
name: 'RAM_RESTRICTIONS',
|
||||
hash: '4e7bf348da00a945489b2a681749eb56f5de00b900014e137ddae39f48f69d67',
|
||||
},
|
||||
{
|
||||
name: 'WEBAUTHN_KEY',
|
||||
hash: '4fca8bd82bbd181e714e283f83e1b45d95ca5af40fb89ad3977b653c448f78c2',
|
||||
},
|
||||
{
|
||||
name: 'WTMSIG_BLOCK_SIGNATURES',
|
||||
hash: '299dcb6af692324b899b39f16d5a530a33062804e41f09dc97e9f156b4476707',
|
||||
},
|
||||
{
|
||||
name: 'GET_CODE_HASH',
|
||||
hash: 'bcd2a26394b36614fd4894241d3c451ab0f6fd110958c3423073621a70826e99',
|
||||
},
|
||||
{
|
||||
name: 'GET_BLOCK_NUM',
|
||||
hash: '35c2186cc36f7bb4aeaf4487b36e57039ccf45a9136aa856a5d569ecca55ef2b',
|
||||
},
|
||||
{
|
||||
name: 'CRYPTO_PRIMITIVES',
|
||||
hash: '6bcb40a24e49c26d0a60513b6aeb8551d264e4717f306b81a37a5afb3b47cedc',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Network } from '../types'
|
||||
|
||||
export const networks: Network[] = [
|
||||
{
|
||||
name: 'local',
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: ':8888',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "69b064c5178e2738e144ed6caa9349a3995370d78db29e494b3126ebd9111966",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "ACTION_RETURN_VALUE"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "70787548dcea1a2c52c913a37f74ce99e6caae79110d7ca7b859936a0075b314",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "BLOCKCHAIN_PARAMETERS"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "c0cce5bcd8ea19a28d9e12eafda65ebe6d0e0177e280d4f20c7ad66dcd9e011b",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "BLS_PRIMITIVES2"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "8139e99247b87f18ef7eae99f07f00ea3adf39ed53f4d2da3f44e6aa0bfd7c62",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "CONFIGURABLE_WASM_LIMITS2"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "68d6405cb8df3de95bd834ebb408196578500a9f818ff62ccc68f60b932f7d82",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "CRYPTO_PRIMITIVES"
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "440c3efaaab212c387ce967c574dc813851cf8332d041beb418dfaf55facd5a9",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "DISABLE_DEFERRED_TRXS_STAGE_1"
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [
|
||||
"fce57d2331667353a0eac6b4209b67b843a7262a848af0a49a6e2fa9f6584eb4"
|
||||
],
|
||||
"description_digest": "a857eeb932774c511a40efb30346ec01bfb7796916b54c3c69fe7e5fb70d5cba",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "DISABLE_DEFERRED_TRXS_STAGE_2"
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "2853617cec3eabd41881eb48882e6fc5e81a0db917d375057864b3befbe29acd",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "DISALLOW_EMPTY_PRODUCER_SCHEDULE"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "a98241c83511dc86c857221b9372b4aa7cea3aaebc567a48604e1d3db3557050",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "FIX_LINKAUTH_RESTRICTION"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "898082c59f921d0042e581f00a59d5ceb8be6f1d9c7a45b6f07c0e26eaee0222",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "FORWARD_SETCODE"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "e5d7992006e628a38c5e6c28dd55ff5e57ea682079bf41fef9b3cced0f46b491",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "GET_BLOCK_NUM"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "d2596697fed14a0840013647b99045022ae6a885089f35a7e78da7a43ad76ed4",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "GET_CODE_HASH"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "1eab748b95a2e6f4d7cb42065bdee5566af8efddf01a55a0a8d831b823f8828a",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "GET_SENDER"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [
|
||||
"ef43112c6543b88db2283a2e077278c315ae2c84719a8b25f25cc88565fbea99"
|
||||
],
|
||||
"description_digest": "45967387ee92da70171efd9fefd1ca8061b5efe6f124d269cd2468b47f1575a0",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "NO_DUPLICATE_DEFERRED_ID"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "2f1f13e291c79da5a2bbad259ed7c1f2d34f697ea460b14b565ac33b063b73e2",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "ONLY_BILL_FIRST_AUTHORIZER"
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "f3c3d91c4603cde2397268bfed4e662465293aab10cd9416db0d442b8cec2949",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "ONLY_LINK_TO_EXISTING_PERMISSION"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "64fe7df32e9b86be2b296b3f81dfd527f84e82b98e363bc97e40bc7a83733310",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": false,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "PREACTIVATE_FEATURE"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "1812fdb5096fd854a4958eb9d53b43219d114de0e858ce00255bd46569ad2c68",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "RAM_RESTRICTIONS"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "9908b3f8413c8474ab2a6be149d3f4f6d0421d37886033f27d4759c47a26d944",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "REPLACE_DEFERRED"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "e71b6712188391994c78d8c722c1d42c477cf091e5601b5cf1befd05721a57f3",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "RESTRICT_ACTION_TO_SELF"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "927fdf78c51e77a899f2db938249fb1f8bb38f4e43d9c1f75b190492080cbc34",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "WEBAUTHN_KEY"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"protocol_feature_type": "builtin",
|
||||
"dependencies": [],
|
||||
"description_digest": "ab76031cad7a457f4fd5f5fca97a3f03b8a635278e0416f77dcc91eb99a48e10",
|
||||
"subjective_restrictions": {
|
||||
"earliest_allowed_activation_time": "1970-01-01T00:00:00.000",
|
||||
"preactivation_required": true,
|
||||
"enabled": true
|
||||
},
|
||||
"builtin_feature_codename": "WTMSIG_BLOCK_SIGNATURES"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { runContainer } from './run'
|
||||
|
||||
export const container = await runContainer()
|
||||
@@ -0,0 +1,34 @@
|
||||
/* eslint-disable node/prefer-global/process */
|
||||
import Blockchain from '../blockchain'
|
||||
import contracts from '../configs/contracts'
|
||||
import type { INetwork } from '../configs/networks'
|
||||
import { networks } from '../configs/networks'
|
||||
import type { Contract } from '../types'
|
||||
import { execCommand } from './exec'
|
||||
|
||||
export async function deployCommand(name: string, pre_target: string, pre_network: string): Promise<void> {
|
||||
const network: INetwork | undefined = networks.find(el => el.name === pre_network)
|
||||
|
||||
if (!network)
|
||||
throw new Error('Сеть не найдена')
|
||||
|
||||
const contract = contracts.find(el => el.name === name)
|
||||
|
||||
if (!contract)
|
||||
throw new Error('Контракт не найден')
|
||||
|
||||
let target = contract.target
|
||||
|
||||
if (pre_target)
|
||||
target = pre_target
|
||||
|
||||
const contract_for_deploy: Contract = {
|
||||
path: contract.path,
|
||||
name,
|
||||
target,
|
||||
}
|
||||
|
||||
const blockchain = new Blockchain(network, [process.env.EOSIO_PRV_KEY as string])
|
||||
await blockchain.update_pass_instance()
|
||||
await blockchain.setContract(contract_for_deploy)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { exec } from 'node:child_process'
|
||||
import { promisify } from 'node:util'
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
export async function execCommand(command: string[]): Promise<void> {
|
||||
const fullCmd = ['docker', 'compose', 'exec', 'node', ...command].join(' ')
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
const { stdout, stderr } = await execAsync(fullCmd, { env: process.env })
|
||||
if (stdout)
|
||||
console.log(stdout.trim())
|
||||
if (stderr)
|
||||
console.error(stderr.trim())
|
||||
}
|
||||
catch (err: any) {
|
||||
if (err.stdout)
|
||||
console.log(err.stdout.trim())
|
||||
if (err.stderr)
|
||||
console.error(err.stderr.trim())
|
||||
// опционально:
|
||||
// console.error(`Command failed with code ${err.code}`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import Docker from 'dockerode'
|
||||
|
||||
const docker = new Docker()
|
||||
|
||||
export async function findContainerByName(name: string) {
|
||||
const containers = await docker.listContainers({ all: true })
|
||||
const containerInfo = containers.find(c => c.Names.includes(`/${name}`))
|
||||
|
||||
if (containerInfo) {
|
||||
return docker.getContainer(containerInfo.Id)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import axios from 'axios'
|
||||
|
||||
async function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export async function checkHealth() {
|
||||
const check = async (): Promise<void> => {
|
||||
try {
|
||||
const response = await axios.post('http://127.0.0.1:8888/v1/chain/get_info')
|
||||
const result = response.data
|
||||
await sleep(1000)
|
||||
|
||||
console.log('Node is healthy. Continue.', result)
|
||||
}
|
||||
|
||||
catch (error: any) {
|
||||
console.error(error)
|
||||
console.log('Блокчейн не доступен с ошибкой. Он поднят? docker compose up -d')
|
||||
|
||||
await sleep(1000)
|
||||
|
||||
return check() // Повторный вызов функции
|
||||
}
|
||||
}
|
||||
|
||||
return check()
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { promises as fs } from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
export async function clearDB(): Promise<void> {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
await mongoose.connect(process.env.MONGO_URI as string)
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('users').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции users')
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('sync').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции sync')
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('actions').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции actions')
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('deltas').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции deltas')
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearDirectory(dirPath: string): Promise<void> {
|
||||
try {
|
||||
const files = await fs.readdir(dirPath)
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dirPath, file)
|
||||
const stats = await fs.stat(filePath)
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await clearDirectory(filePath)
|
||||
await fs.rmdir(filePath)
|
||||
}
|
||||
else {
|
||||
await fs.unlink(filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Error clearing directory ${dirPath}:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteFile(filePath: string): Promise<void> {
|
||||
try {
|
||||
await fs.unlink(filePath)
|
||||
}
|
||||
catch (err) {
|
||||
// console.error(`Error deleting file ${filePath}:`, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { exec } from 'node:child_process'
|
||||
import { config } from 'dotenv'
|
||||
|
||||
config()
|
||||
|
||||
export async function runContainer(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec('docker compose up -d node', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Ошибка при запуске контейнера: ${error.message}`)
|
||||
reject(error)
|
||||
return
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import Docker from 'dockerode'
|
||||
import { findContainerByName } from './find'
|
||||
|
||||
export async function stopContainerByName(name: string) {
|
||||
const container = await findContainerByName(name)
|
||||
|
||||
if (container) {
|
||||
await container.stop()
|
||||
console.log(`Container stopped.`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
/* eslint-disable node/prefer-global/process */
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { Command } from 'commander'
|
||||
import { config } from 'dotenv'
|
||||
import { execCommand } from './docker/exec'
|
||||
import { stopContainerByName } from './docker/stop'
|
||||
import { runContainer } from './docker/run'
|
||||
import { boot } from './init/booter'
|
||||
import { sleep } from './utils'
|
||||
import { checkHealth } from './docker/health'
|
||||
import { clearDB, clearDirectory, deleteFile } from './docker/purge'
|
||||
import { deployCommand } from './docker/deploy'
|
||||
|
||||
config()
|
||||
|
||||
const basePath = path.resolve(process.cwd(), './blockchain-data')
|
||||
const keosdPath = path.resolve(process.cwd(), './wallet-data/keosd.sock')
|
||||
|
||||
if (!fs.existsSync(basePath)) {
|
||||
fs.mkdirSync(basePath, { recursive: true })
|
||||
}
|
||||
|
||||
if (!fs.existsSync(keosdPath)) {
|
||||
fs.mkdirSync(keosdPath, { recursive: true })
|
||||
}
|
||||
|
||||
const program = new Command()
|
||||
|
||||
program.version('0.1.0')
|
||||
|
||||
// Команда для запуска команды в контейнере
|
||||
program
|
||||
.command('cleos [cmd...]')
|
||||
.description('Execute a cleos command in a Node container')
|
||||
.allowUnknownOption()
|
||||
.action(async (cmd: string[] = []) => {
|
||||
await execCommand(['cleos', ...cmd])
|
||||
})
|
||||
|
||||
// Команда для запуска команды в контейнере
|
||||
program
|
||||
.command('deploy <contract_name>')
|
||||
.description('Execute a deploy command in a Node container')
|
||||
.option('-t, --target <target>', 'Specify the target')
|
||||
.option('-n, --network <network>', 'Specify the network', 'local')
|
||||
.allowUnknownOption()
|
||||
.action(async (cmd: string, options: any) => {
|
||||
try {
|
||||
const { target, network } = options
|
||||
await deleteFile(keosdPath)
|
||||
|
||||
await deployCommand(cmd, target, network)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Command execution failed:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для получения списка контейнеров
|
||||
program
|
||||
.command('stop')
|
||||
.description('Stop blockchain node as is')
|
||||
.action(async () => {
|
||||
try {
|
||||
await stopContainerByName('node')
|
||||
|
||||
console.log('Container is stopped')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to list containers:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для запуска команды в контейнере
|
||||
program
|
||||
.command('unlock')
|
||||
.description('Unlock a cleos wallet with a .env password')
|
||||
.allowUnknownOption()
|
||||
.action(async () => {
|
||||
try {
|
||||
await execCommand([
|
||||
'cleos',
|
||||
'wallet',
|
||||
'unlock',
|
||||
'--password',
|
||||
process.env.PASSWORD!,
|
||||
])
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Command execution failed:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для получения списка контейнеров
|
||||
program
|
||||
.command('start')
|
||||
.description('Start blockchain node as is')
|
||||
.action(async () => {
|
||||
try {
|
||||
await deleteFile(keosdPath)
|
||||
await runContainer()
|
||||
console.log('Container is started')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to list containers:', error)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для получения списка контейнеров
|
||||
program
|
||||
.command('boot')
|
||||
.description('Purge blockchain data and boot a Protocol')
|
||||
.action(async () => {
|
||||
try {
|
||||
await stopContainerByName('node')
|
||||
}
|
||||
catch (e) {
|
||||
console.log('Нет контейнера для остановки. Стартуем новый..')
|
||||
}
|
||||
|
||||
try {
|
||||
await clearDirectory(basePath)
|
||||
await clearDB()
|
||||
await runContainer()
|
||||
|
||||
await sleep(5000)
|
||||
await checkHealth()
|
||||
await boot()
|
||||
console.log(`
|
||||
|
||||
Boot is
|
||||
.d8888b. .d88888b. 888b d888 8888888b. 888 8888888888 88888888888 8888888888
|
||||
d88P Y88b d88P" "Y88b 8888b d8888 888 Y88b 888 888 888 888
|
||||
888 888 888 888 88888b.d88888 888 888 888 888 888 888
|
||||
888 888 888 888Y88888P888 888 d88P 888 8888888 888 8888888
|
||||
888 888 888 888 Y888P 888 8888888P" 888 888 888 888
|
||||
888 888 888 888 888 Y8P 888 888 888 888 888 888
|
||||
Y88b d88P Y88b. .d88P 888 " 888 888 888 888 888 888
|
||||
"Y8888P" "Y88888P" 888 888 888 88888888 8888888888 888 8888888888
|
||||
|
||||
`)
|
||||
process.exit(0)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to boot:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для получения списка контейнеров
|
||||
program
|
||||
.command('clear')
|
||||
.description('Purge blockchain data and boot a Protocol')
|
||||
.action(async () => {
|
||||
try {
|
||||
await stopContainerByName('node')
|
||||
await deleteFile(keosdPath)
|
||||
await clearDirectory(basePath)
|
||||
await sleep(5000)
|
||||
await clearDB()
|
||||
await runContainer()
|
||||
console.log('Блокчейн очищен и перезапущен. Запустите загрузку: pnpm run boot')
|
||||
process.exit(0)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to boot:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
|
||||
// Команда для получения списка контейнеров
|
||||
program
|
||||
.command('only-boot')
|
||||
.description('Boot a Protocol')
|
||||
.action(async () => {
|
||||
try {
|
||||
await boot()
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to boot:', error)
|
||||
}
|
||||
})
|
||||
|
||||
program.parse(process.argv) // Пуск парсинга аргументов
|
||||
|
||||
async function gracefulShutdown() {
|
||||
// console.log('Stopping container...')
|
||||
// await stopContainerByName('node')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
process.on('SIGINT', gracefulShutdown)
|
||||
process.on('SIGTERM', gracefulShutdown)
|
||||
@@ -0,0 +1,7 @@
|
||||
import { startInfra } from './infra'
|
||||
import { startCoop } from './cooperative'
|
||||
|
||||
export async function boot() {
|
||||
await startInfra()
|
||||
await startCoop()
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import type { Cooperative } from 'cooptypes'
|
||||
import type { Keys } from '../types'
|
||||
import config, { GOVERN_SYMBOL, SYMBOL } from '../configs'
|
||||
import Blockchain from '../blockchain'
|
||||
import { generateRandomSHA256 } from '../utils/randomHash'
|
||||
import { processLastDecision } from '../tests/soviet/processLastDecision'
|
||||
|
||||
const test_hash
|
||||
= '157192b276da23cc84ab078fc8755c051c5f0430bf4802e55718221e6b76c777'
|
||||
const test_sign
|
||||
= 'SIG_K1_KmKWPBC8dZGGDGhbKEoZEzPr3h5crRrR2uLdGRF5DJbeibY1MY1bZ9sPwHsgmPfiGFv9psfoCVsXFh9TekcLuvaeuxRKA8'
|
||||
const test_pkey = 'EOS5JhMfxbsNebajHcTEK8yC9uNN9Dit9hEmzE8ri8yMhhzxrLg3J'
|
||||
const test_meta = JSON.stringify({})
|
||||
|
||||
const document: Cooperative.Document.ISignedDocument2 = {
|
||||
hash: test_hash,
|
||||
signatures: [
|
||||
{
|
||||
id: 1,
|
||||
signed_hash: test_hash,
|
||||
public_key: test_pkey,
|
||||
signed_at: '2025-05-14T12:22:26',
|
||||
meta: test_meta,
|
||||
signer: 'cooperative1',
|
||||
signature: test_sign,
|
||||
},
|
||||
],
|
||||
meta: test_meta,
|
||||
version: '1.0.0',
|
||||
doc_hash: test_hash,
|
||||
meta_hash: test_hash,
|
||||
}
|
||||
|
||||
export class CooperativeClass {
|
||||
public blockchain: Blockchain
|
||||
|
||||
constructor(blockchain: Blockchain) {
|
||||
this.blockchain = blockchain
|
||||
}
|
||||
|
||||
async createPrograms(coopname: string) {
|
||||
await this.blockchain.createProgram({
|
||||
coopname,
|
||||
username: coopname,
|
||||
type: 'marketplace',
|
||||
title: 'КООПЛЕЙС',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
images: '',
|
||||
calculation_type: 'relative',
|
||||
fixed_membership_contribution: `${Number(0).toFixed(4)} ${GOVERN_SYMBOL}`,
|
||||
membership_percent_fee: 10000, // 10%
|
||||
meta: '',
|
||||
is_can_coop_spend_share_contributions: false,
|
||||
})
|
||||
}
|
||||
|
||||
async createCooperative(username: string, keys?: Keys) {
|
||||
const account = await this.blockchain.generateKeypair(
|
||||
username,
|
||||
keys,
|
||||
'Аккаунт кооператива',
|
||||
)
|
||||
console.log('Регистрируем аккаунт')
|
||||
|
||||
await this.blockchain.createAccount({
|
||||
coopname: config.provider,
|
||||
referer: '',
|
||||
username: account.username,
|
||||
public_key: account.publicKey,
|
||||
meta: '',
|
||||
})
|
||||
|
||||
console.log('Регистрируем аккаунт как пользователя')
|
||||
const registration_hash = generateRandomSHA256()
|
||||
|
||||
await this.blockchain.registerUser({
|
||||
coopname: config.provider,
|
||||
braname: '',
|
||||
username: account.username,
|
||||
type: 'organization',
|
||||
statement: document,
|
||||
registration_hash,
|
||||
})
|
||||
|
||||
console.log('Отправляем заявление на вступление')
|
||||
|
||||
await this.blockchain.ConfirmPayment({
|
||||
coopname: config.provider,
|
||||
income_hash: registration_hash,
|
||||
})
|
||||
|
||||
await processLastDecision(this.blockchain, config.provider)
|
||||
// console.log('Голосуем по решению в провайдере')
|
||||
|
||||
// await this.blockchain.votefor({
|
||||
// version: '1.0.0',
|
||||
// signed_at: '2025-05-14T12:22:26',
|
||||
// signed_hash: test_hash,
|
||||
// signature: test_sign,
|
||||
// public_key: test_pkey,
|
||||
// coopname: config.provider,
|
||||
// username: config.provider_chairman,
|
||||
// decision_id: 1,
|
||||
// })
|
||||
|
||||
// console.log('Утверждаем решение в провайдере')
|
||||
|
||||
// await this.blockchain.authorize({
|
||||
// coopname: config.provider,
|
||||
// chairman: config.provider_chairman,
|
||||
// decision_id: 1,
|
||||
// document,
|
||||
// })
|
||||
|
||||
// console.log('Исполняем решение в провайдере')
|
||||
|
||||
// await this.blockchain.exec({
|
||||
// executer: config.provider_chairman,
|
||||
// coopname: config.provider,
|
||||
// decision_id: 1,
|
||||
// })
|
||||
|
||||
console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
|
||||
|
||||
await this.blockchain.sendAgreement({
|
||||
coopname: config.provider,
|
||||
administrator: config.provider,
|
||||
username: username!,
|
||||
agreement_type: 'wallet',
|
||||
document,
|
||||
})
|
||||
|
||||
console.log('Переводим аккаунт в кооператив')
|
||||
|
||||
await this.blockchain.registerCooperative({
|
||||
username: account.username,
|
||||
coopname: account.username,
|
||||
params: {
|
||||
is_cooperative: true,
|
||||
coop_type: 'conscoop',
|
||||
announce: 'Тестовый кооператив',
|
||||
description: 'Тестовое описание',
|
||||
initial: `100.0000 ${config.token.govern_symbol}`,
|
||||
minimum: `300.0000 ${config.token.govern_symbol}`,
|
||||
org_initial: `1000.0000 ${config.token.govern_symbol}`,
|
||||
org_minimum: `3000.0000 ${config.token.govern_symbol}`,
|
||||
},
|
||||
document,
|
||||
})
|
||||
|
||||
console.log('Переводим инициализационные токены')
|
||||
await this.blockchain.transfer({ from: 'eosio', to: account.username, quantity: `100.0000 ${SYMBOL}`, memo: '' })
|
||||
|
||||
console.log(`Арендуем ресурсы кооперативу`)
|
||||
|
||||
await this.blockchain.powerup({
|
||||
payer: 'eosio',
|
||||
receiver: username,
|
||||
days: config.powerup.days,
|
||||
payment: `100.0000 ${config.token.symbol}`,
|
||||
transfer: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function startCoop() {
|
||||
// инициализируем инстанс с ключами
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
const cooperative = new CooperativeClass(blockchain)
|
||||
|
||||
await cooperative.createCooperative('cooperative1', {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
})
|
||||
|
||||
await blockchain.preInit({
|
||||
coopname: 'cooperative1',
|
||||
username: config.provider,
|
||||
status: 'active',
|
||||
})
|
||||
|
||||
console.log('Кооператив предварительно подготовлен к установке совета.')
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import axios from 'axios'
|
||||
import { Generator, Registry } from '@coopenomics/factory'
|
||||
import type { Cooperative } from 'cooptypes'
|
||||
import { DraftContract } from 'cooptypes'
|
||||
import mongoose from 'mongoose'
|
||||
import type { Account, Contract } from '../types'
|
||||
import config from '../configs'
|
||||
import Blockchain from '../blockchain'
|
||||
import { sleep } from '../utils'
|
||||
import { CooperativeClass } from './cooperative'
|
||||
|
||||
export async function startInfra() {
|
||||
// инициализируем инстанс с ключами
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
await blockchain.update_pass_instance()
|
||||
|
||||
// регистрируем базовые аккаунты
|
||||
for (const account of config.accounts) {
|
||||
const { name, ownerPublicKey, activePublicKey } = account as Account
|
||||
await blockchain.createStandartAccount(
|
||||
'eosio',
|
||||
name,
|
||||
ownerPublicKey || config.default_public_key,
|
||||
activePublicKey || config.default_public_key,
|
||||
)
|
||||
}
|
||||
|
||||
// пре-активируем фичу для запуска
|
||||
const url = `${config.network.protocol}://${config.network.host}${config.network.port}`
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${url}/v1/producer/schedule_protocol_feature_activations`,
|
||||
{
|
||||
protocol_features_to_activate: [
|
||||
'0ec7e080177b2c02b278d5088611686b49d739925a92d9bfcacd7fc6b74053bd',
|
||||
],
|
||||
},
|
||||
)
|
||||
console.log('ok -> init activation: ', response.data)
|
||||
}
|
||||
catch (e) {
|
||||
console.log('error -> init activation: ', e)
|
||||
}
|
||||
|
||||
// чуть ждём
|
||||
await sleep(1000)
|
||||
|
||||
// устанавливаем биос
|
||||
const bios = config.contracts.find(el => el.name === 'eosio.boot')
|
||||
await blockchain.setContract(bios as Contract)
|
||||
|
||||
// чуть ждём
|
||||
await sleep(2000)
|
||||
|
||||
// активируем все оставшиеся фичи
|
||||
for (const feature of config.features)
|
||||
await blockchain.activateFeature(feature)
|
||||
|
||||
// чуть ждём
|
||||
await sleep(2000)
|
||||
|
||||
// устанавливаем все оставшиеся контракты
|
||||
const filtered_contracts = config.contracts.filter(
|
||||
el => el.name !== 'eosio.boot',
|
||||
)
|
||||
for (const contract of filtered_contracts)
|
||||
await blockchain.setContract(contract)
|
||||
|
||||
await sleep(2000)
|
||||
|
||||
console.log('создаём токен')
|
||||
await blockchain.createToken({
|
||||
issuer: 'eosio',
|
||||
maximum_supply: config.token.max_supply,
|
||||
})
|
||||
|
||||
await sleep(2000)
|
||||
|
||||
console.log('выпускаем токены')
|
||||
for (const allocation of config.allocations) {
|
||||
await blockchain.issueToken({
|
||||
to: allocation.to,
|
||||
quantity: allocation.quantity,
|
||||
memo: '',
|
||||
})
|
||||
}
|
||||
|
||||
await sleep(2000)
|
||||
|
||||
// выдаём кодовые разрешения всем указанным аккаунтам
|
||||
for (const account of config.accounts.filter(
|
||||
el => !!el.code_permissions_to,
|
||||
)) {
|
||||
for (const permission_to of account.code_permissions_to ?? []) {
|
||||
await blockchain.updateAccountPermissionsToCode(
|
||||
account.name,
|
||||
permission_to,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await sleep(1000)
|
||||
|
||||
// инициализируем системный контракт
|
||||
await blockchain.initSystem({
|
||||
version: 0,
|
||||
core: `${config.token.precision},${config.token.symbol}`,
|
||||
})
|
||||
|
||||
await sleep(1000)
|
||||
|
||||
// инициализируем эмиссию
|
||||
await blockchain.initEmission({
|
||||
init_supply: config.emission.left_border,
|
||||
tact_duration: config.emission.tact_duration,
|
||||
emission_factor: config.emission.emission_factor,
|
||||
})
|
||||
|
||||
await sleep(2000)
|
||||
|
||||
await blockchain.initPowerup({
|
||||
args: {
|
||||
powerup_days: config.powerup.days,
|
||||
min_powerup_fee: config.powerup.min_powerup,
|
||||
},
|
||||
})
|
||||
|
||||
await sleep(2000)
|
||||
|
||||
for (const id in Registry) {
|
||||
const template = Registry[(id as unknown) as keyof typeof Registry]
|
||||
|
||||
await blockchain.createDraft({
|
||||
scope: DraftContract.contractName.production,
|
||||
username: 'eosio',
|
||||
registry_id: id,
|
||||
lang: 'ru',
|
||||
title: template.Template.title,
|
||||
description: template.Template.description,
|
||||
context: template.Template.context,
|
||||
model: JSON.stringify(template.Template.model),
|
||||
translation_data: JSON.stringify(template.Template.translations.ru),
|
||||
})
|
||||
}
|
||||
|
||||
const organizationData: Cooperative.Users.IOrganizationData = {
|
||||
username: 'voskhod',
|
||||
type: 'coop',
|
||||
short_name: '"ПК Восход"',
|
||||
full_name: 'Потребительский Кооператив "ВОСХОД"',
|
||||
represented_by: {
|
||||
first_name: 'Иван',
|
||||
last_name: 'Иванов',
|
||||
middle_name: 'Иванович',
|
||||
position: 'Председатель',
|
||||
based_on: 'Решение общего собрания №1',
|
||||
},
|
||||
country: 'Российская Федерация',
|
||||
city: 'Москва',
|
||||
fact_address: '117593 г. Москва, муниципальный округ Ясенево, проезд Соловьиный, дом 1, помещение 1/1',
|
||||
full_address:
|
||||
'117593 г. Москва, муниципальный округ Ясенево, проезд Соловьиный, дом 1, помещение 1/1',
|
||||
email: 'copenomics@yandex.ru',
|
||||
phone: '+71234567890',
|
||||
details: {
|
||||
inn: '9728130611',
|
||||
ogrn: '1247700283346',
|
||||
kpp: '772801001',
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const generator = new Generator()
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
await generator.connect(process.env.MONGO_URI as string)
|
||||
|
||||
await generator.save('organization', organizationData)
|
||||
console.log('Провайдер добавлен: ', organizationData)
|
||||
|
||||
await generator.save('paymentMethod', {
|
||||
is_default: true,
|
||||
method_id: randomUUID(),
|
||||
method_type: 'bank_transfer',
|
||||
username: 'voskhod',
|
||||
data: {
|
||||
account_number: '40703810038000110117',
|
||||
currency: 'RUB',
|
||||
card_number: '',
|
||||
bank_name: 'ПАО Сбербанк',
|
||||
details: {
|
||||
bik: '044525225',
|
||||
corr: '30101810400000000225',
|
||||
kpp: '773643001',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const userData: Cooperative.Users.IIndividualData = {
|
||||
username: 'ant',
|
||||
first_name: 'Иван',
|
||||
last_name: 'Иванов',
|
||||
middle_name: 'Иванович',
|
||||
birthdate: '1990/04/01',
|
||||
phone: '+71234567890',
|
||||
email: 'ivanov@example.com',
|
||||
full_address: 'Переулок Правды д. 1',
|
||||
passport: {
|
||||
series: 7122,
|
||||
number: 112233,
|
||||
issued_by: 'отделом УФМС по г. Москва',
|
||||
issued_at: '2010/05/10',
|
||||
code: '111-232',
|
||||
},
|
||||
}
|
||||
|
||||
await generator.save('individual', userData)
|
||||
|
||||
// добавляем переменные кооператива
|
||||
const vars: Cooperative.Model.IVars = {
|
||||
coopname: 'voskhod',
|
||||
full_abbr: 'потребительский кооператив',
|
||||
full_abbr_genitive: 'потребительского кооператива',
|
||||
full_abbr_dative: 'потребительскому кооперативу',
|
||||
short_abbr: 'ПК',
|
||||
website: 'цифровой-кооператив.рф',
|
||||
name: 'Восход',
|
||||
confidential_link: 'coopenomics.world/privacy',
|
||||
confidential_email: 'privacy@coopenomics.world',
|
||||
contact_email: 'contact@coopenomics.world',
|
||||
passport_request: 'no',
|
||||
wallet_agreement: {
|
||||
protocol_number: '10-04-2024',
|
||||
protocol_day_month_year: '10 апреля 2024 г.',
|
||||
},
|
||||
signature_agreement: {
|
||||
protocol_number: '10-04-2024',
|
||||
protocol_day_month_year: '10 апреля 2024 г.',
|
||||
},
|
||||
privacy_agreement: {
|
||||
protocol_number: '10-04-2024',
|
||||
protocol_day_month_year: '10 апреля 2024 г.',
|
||||
},
|
||||
user_agreement: {
|
||||
protocol_number: '10-04-2024',
|
||||
protocol_day_month_year: '10 апреля 2024 г.',
|
||||
},
|
||||
participant_application: {
|
||||
protocol_number: '10-04-2024',
|
||||
protocol_day_month_year: '10 апреля 2024 г.',
|
||||
},
|
||||
}
|
||||
|
||||
await generator.save('vars', vars)
|
||||
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
await mongoose.connect(process.env.MONGO_URI as string)
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('sync').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции sync')
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('actions').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции actions')
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
try {
|
||||
await mongoose.connection.collection('deltas').deleteMany({})
|
||||
console.log('Все документы удалены из коллекции deltas')
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Ошибка при удалении:', e)
|
||||
}
|
||||
|
||||
// добавляем пользователя для подключений
|
||||
try {
|
||||
await mongoose.connection.collection('users').insertOne({
|
||||
email: 'ivanov@example.com',
|
||||
username: 'ant',
|
||||
type: 'individual',
|
||||
role: 'chairman',
|
||||
is_registered: true,
|
||||
})
|
||||
}
|
||||
catch (e) { console.log('user is exist') }
|
||||
|
||||
// имитируем установку
|
||||
try {
|
||||
await mongoose.connection.collection('monos').insertOne({
|
||||
coopname: 'voskhod',
|
||||
status: 'active',
|
||||
})
|
||||
}
|
||||
catch (e) {
|
||||
console.log('system is exist')
|
||||
}
|
||||
// сохраняем зашированный ключ в vault
|
||||
try {
|
||||
await mongoose.connection.collection('vaults').insertOne({
|
||||
username: 'voskhod',
|
||||
permission: 'active',
|
||||
wif: '9d6479a9d77ead53fb0e5e54b3608a95:2046ee3c1577d48aecbee49e8f25c4c2df37ab02f15d73d0d1b6352f53a4b774cb9e71b6028fd7caf64568e195c7878dfbb5d2bf10a3766d90ba9e92ea724428',
|
||||
})
|
||||
}
|
||||
catch (e) { console.log('vault is exist') }
|
||||
console.log('Создаём программы и соглашения')
|
||||
|
||||
const cooperative = new CooperativeClass(blockchain)
|
||||
|
||||
await cooperative.createPrograms(config.provider)
|
||||
|
||||
console.log(`Арендуем ресурсы провайдеру`)
|
||||
await blockchain.powerup({
|
||||
payer: 'eosio',
|
||||
receiver: config.provider,
|
||||
days: config.powerup.days,
|
||||
payment: `100.0000 ${config.token.symbol}`,
|
||||
transfer: true,
|
||||
})
|
||||
|
||||
await blockchain.transfer({
|
||||
from: 'eosio',
|
||||
to: config.provider,
|
||||
quantity: `1000.0000 ${config.token.symbol}`,
|
||||
memo: '',
|
||||
})
|
||||
|
||||
console.log('Базовая установка завершена')
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import axios from 'axios'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Registry } from '@coopenomics/factory'
|
||||
import { RegistratorContract, Cooperative as TCooperative } from 'cooptypes'
|
||||
import type { Account, Contract, Keys } from '../types'
|
||||
import config, { GOVERN_SYMBOL, SYMBOL } from '../configs'
|
||||
import Blockchain from '../blockchain'
|
||||
import { sendPostToCoopbackWithSecret, sleep } from '../utils'
|
||||
import { fakeDocument } from '../tests/shared/fakeDocument'
|
||||
|
||||
export class ParticipantsClass {
|
||||
public blockchain: Blockchain
|
||||
|
||||
constructor(blockchain: Blockchain) {
|
||||
this.blockchain = blockchain
|
||||
}
|
||||
|
||||
async addUser2(coopname: string, username: string, keys?: Keys, referer?: string) {
|
||||
const account = await this.blockchain.generateKeypair(
|
||||
username,
|
||||
keys,
|
||||
'Аккаунт участника',
|
||||
)
|
||||
console.log('Регистрируем аккаунт')
|
||||
|
||||
const data: RegistratorContract.Actions.AddUser.IAddUser = {
|
||||
coopname,
|
||||
referer: referer || '',
|
||||
username: account.username,
|
||||
type: 'individual',
|
||||
created_at: '2025-02-05T09:34:27',
|
||||
initial: '100.0000 RUB',
|
||||
minimum: '100.0000 RUB',
|
||||
spread_initial: false,
|
||||
meta: '',
|
||||
}
|
||||
|
||||
await this.blockchain.api.transact({
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.AddUser.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
})
|
||||
|
||||
console.log('Регистрируем аккаунт как пользователя')
|
||||
|
||||
const changeKey: RegistratorContract.Actions.ChangeKey.IChangeKey = {
|
||||
coopname,
|
||||
username,
|
||||
public_key: account.publicKey,
|
||||
changer: coopname,
|
||||
}
|
||||
|
||||
await this.blockchain.api.transact({
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.ChangeKey.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: changeKey,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
})
|
||||
|
||||
// console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
|
||||
|
||||
// await this.blockchain.sendAgreement({
|
||||
// coopname: config.provider,
|
||||
// administrator: config.provider,
|
||||
// username: username!,
|
||||
// agreement_type: 'wallet',
|
||||
// document: fakeDocument,
|
||||
// })
|
||||
|
||||
// console.log('создаём кошелёк')
|
||||
|
||||
// console.log(`Арендуем ресурсы кооперативу`)
|
||||
|
||||
// await this.blockchain.powerup({
|
||||
// payer: 'eosio',
|
||||
// receiver: username,
|
||||
// days: config.powerup.days,
|
||||
// payment: `100.0000 ${config.token.symbol}`,
|
||||
// transfer: true,
|
||||
// })
|
||||
}
|
||||
|
||||
async addUser(username: string, keys?: Keys, referer?: string) {
|
||||
const account = await this.blockchain.generateKeypair(
|
||||
username,
|
||||
keys,
|
||||
'Аккаунт участника',
|
||||
)
|
||||
console.log('Регистрируем аккаунт')
|
||||
|
||||
const data: RegistratorContract.Actions.AddUser.IAddUser = {
|
||||
coopname: config.provider,
|
||||
referer: referer || '',
|
||||
username: account.username,
|
||||
type: 'individual',
|
||||
created_at: '2025-02-05T09:34:27',
|
||||
initial: '100.0000 RUB',
|
||||
minimum: '100.0000 RUB',
|
||||
spread_initial: false,
|
||||
meta: '',
|
||||
}
|
||||
|
||||
await this.blockchain.api.transact({
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.AddUser.actionName,
|
||||
authorization: [{ actor: config.provider, permission: 'active' }],
|
||||
data,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
})
|
||||
|
||||
console.log('Регистрируем аккаунт как пользователя')
|
||||
|
||||
const changeKey: RegistratorContract.Actions.ChangeKey.IChangeKey = {
|
||||
coopname: config.provider,
|
||||
changer: config.provider,
|
||||
username: account.username,
|
||||
public_key: account.publicKey,
|
||||
}
|
||||
|
||||
await this.blockchain.api.transact({
|
||||
actions: [
|
||||
{
|
||||
account: RegistratorContract.contractName.production,
|
||||
name: RegistratorContract.Actions.ChangeKey.actionName,
|
||||
authorization: [{ actor: config.provider, permission: 'active' }],
|
||||
data: changeKey,
|
||||
},
|
||||
],
|
||||
}, {
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
})
|
||||
|
||||
console.log('Отправляем подписанное положение о ЦПП Кошелька оператору')
|
||||
|
||||
await this.blockchain.sendAgreement({
|
||||
coopname: config.provider,
|
||||
administrator: config.provider,
|
||||
username: username!,
|
||||
agreement_type: 'wallet',
|
||||
document: fakeDocument,
|
||||
})
|
||||
|
||||
console.log('создаём кошелёк')
|
||||
|
||||
// console.log(`Арендуем ресурсы кооперативу`)
|
||||
|
||||
// await this.blockchain.powerup({
|
||||
// payer: 'eosio',
|
||||
// receiver: username,
|
||||
// days: config.powerup.days,
|
||||
// payment: `100.0000 ${config.token.symbol}`,
|
||||
// transfer: true,
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
export async function addUser(username: string, referer?: string) {
|
||||
// инициализируем инстанс с ключами
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
const participants = new ParticipantsClass(blockchain)
|
||||
|
||||
await participants.addUser(username, {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
}, referer)
|
||||
}
|
||||
|
||||
export async function addUser2(coopname: string, username: string, referer?: string) {
|
||||
// инициализируем инстанс с ключами
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
const participants = new ParticipantsClass(blockchain)
|
||||
|
||||
await participants.addUser2(coopname, username, {
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
// eslint-disable-next-line node/prefer-global/process
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
}, referer)
|
||||
}
|
||||
@@ -0,0 +1,450 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { CapitalContract, SovietContract } from 'cooptypes'
|
||||
import Blockchain from '../blockchain'
|
||||
import config from '../configs'
|
||||
import { getTotalRamUsage, globalRamStats } from '../utils/getTotalRamUsage'
|
||||
import { addUser, addUser2 } from '../init/participant'
|
||||
import { generateRandomUsername } from '../utils/randomUsername'
|
||||
import { generateRandomSHA256 } from '../utils/randomHash'
|
||||
import { sleep } from '../utils'
|
||||
import { CooperativeClass } from '../init/cooperative'
|
||||
import { getCirculationAccount, getCoopProgramWallet, getLedgerAccountById, getUserProgramWalletAmount } from './wallet/walletUtils'
|
||||
import { registerContributor } from './capital/registerContributor'
|
||||
import { signAppendix } from './capital/signAppendix'
|
||||
import { capitalProgramId, circulationAccountId, sourceProgramId } from './capital/consts'
|
||||
import { commitToResult } from './capital/commitToResult'
|
||||
import { refreshSegment } from './capital/refreshSegment'
|
||||
import { signWalletAgreement } from './wallet/signWalletAgreement'
|
||||
import { fakeDocument } from './shared/fakeDocument'
|
||||
import { depositToWallet } from './wallet/depositToWallet'
|
||||
import { signCapitalAgreement } from './capital/signCapitalAgreement'
|
||||
import { addAuthor } from './capital/addAuthor'
|
||||
import { getSegment } from './capital/getSegment'
|
||||
import { investInProject } from './capital/investInProject'
|
||||
import { processDebt } from './capital/processDebt'
|
||||
import { processCreateProjectProperty } from './capital/processCreateProjectProperty'
|
||||
import { processCreateProgramProperty } from './capital/processCreateProgramProperty'
|
||||
import { processStartVoting } from './capital/processStartVoting'
|
||||
import { createVoteDistribution, submitVote } from './capital/submitVote'
|
||||
import { processCompleteVoting } from './capital/processCompleteVoting'
|
||||
import { processPushResult } from './capital/processPushResult'
|
||||
import { processConvertSegment } from './capital/processConvertSegment'
|
||||
import { processCalculateVotes } from './capital/processCalculateVotes'
|
||||
import { processAddContributor } from './capital/processAddContributor'
|
||||
import { processDeleteProject } from './capital/processDeleteProject'
|
||||
import { getProject } from './capital/getProject'
|
||||
import { processFundProgram } from './capital/processFundProgram'
|
||||
import { processRefreshProg } from './capital/processRefreshProg'
|
||||
import { processFundProject } from './capital/processFundProject'
|
||||
import { processRefreshProj } from './capital/processRefreshProj'
|
||||
// const CLI_PATH = 'src/index.ts'
|
||||
|
||||
const blockchain = new Blockchain(config.network, config.private_keys)
|
||||
let metaProject: CapitalContract.Actions.CreateProject.ICreateProject
|
||||
let componentProject: CapitalContract.Actions.CreateProject.ICreateProject
|
||||
let newComponentProject: CapitalContract.Actions.CreateProject.ICreateProject
|
||||
let testCoop: string
|
||||
let chairman: string
|
||||
|
||||
let tester1: string
|
||||
let tester2: string
|
||||
let tester3: string
|
||||
let tester4: string
|
||||
let tester5: string
|
||||
|
||||
let investor1: string
|
||||
let investor2: string
|
||||
let investor3: string
|
||||
// let result1: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
// let result2: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
// let result3: CapitalContract.Actions.CreateResult.ICreateResult
|
||||
|
||||
const commits: string[] = []
|
||||
const tester1CommitHours = 10
|
||||
const tester2CommitHours = 20
|
||||
const tester3CommitHours = 100
|
||||
const investAmount1 = 100000
|
||||
const investAmount2 = 25000
|
||||
const investAmount3 = 25000
|
||||
|
||||
const totalToCapitalConvertAmount = 0
|
||||
const totalToProjectConvertAmount = 0
|
||||
// Хранение хэшей долгов для каждого пользователя
|
||||
let userDebtHashes: { [username: string]: string[] } = {}
|
||||
beforeAll(async () => {
|
||||
testCoop = generateRandomUsername()
|
||||
chairman = generateRandomUsername()
|
||||
|
||||
await blockchain.update_pass_instance()
|
||||
|
||||
const coop = new CooperativeClass(blockchain)
|
||||
|
||||
await coop.createCooperative(testCoop, {
|
||||
privateKey: process.env.EOSIO_PRV_KEY!,
|
||||
publicKey: process.env.EOSIO_PUB_KEY!,
|
||||
})
|
||||
|
||||
await blockchain.preInit({
|
||||
coopname: testCoop,
|
||||
username: config.provider,
|
||||
status: 'active',
|
||||
})
|
||||
|
||||
await addUser2(testCoop, chairman)
|
||||
|
||||
await blockchain.createBoard({
|
||||
coopname: testCoop,
|
||||
username: testCoop,
|
||||
type: 'soviet',
|
||||
members: [{
|
||||
username: chairman,
|
||||
is_voting: true,
|
||||
position_title: 'Председатель совета',
|
||||
position: 'chairman',
|
||||
}],
|
||||
name: 'Совет',
|
||||
description: '',
|
||||
})
|
||||
|
||||
console.log('testcoop: ', testCoop)
|
||||
|
||||
investor1 = generateRandomUsername()
|
||||
console.log('investor1: ', investor1)
|
||||
await addUser2(testCoop, investor1)
|
||||
|
||||
tester1 = generateRandomUsername()
|
||||
console.log('tester1: ', tester1)
|
||||
await addUser2(testCoop, tester1)
|
||||
|
||||
tester2 = generateRandomUsername()
|
||||
console.log('tester2: ', tester2)
|
||||
await addUser2(testCoop, tester2)
|
||||
|
||||
tester3 = generateRandomUsername()
|
||||
console.log('tester3: ', tester3)
|
||||
await addUser2(testCoop, tester3)
|
||||
|
||||
tester4 = generateRandomUsername()
|
||||
console.log('tester4: ', tester4)
|
||||
await addUser2(testCoop, tester4)
|
||||
|
||||
tester5 = generateRandomUsername()
|
||||
console.log('tester5: ', tester5)
|
||||
await addUser2(testCoop, tester5)
|
||||
|
||||
// Инициализируем массивы хэшей долгов для каждого пользователя
|
||||
userDebtHashes = {
|
||||
[tester1]: [],
|
||||
[tester2]: [],
|
||||
[tester3]: [],
|
||||
[tester4]: [],
|
||||
[tester5]: [],
|
||||
[investor1]: [],
|
||||
[investor2]: [],
|
||||
[investor3]: [],
|
||||
}
|
||||
|
||||
investor2 = generateRandomUsername()
|
||||
console.log('investor2: ', investor2)
|
||||
await addUser2(testCoop, investor2, tester4)
|
||||
|
||||
investor3 = generateRandomUsername()
|
||||
console.log('investor3: ', investor3)
|
||||
await addUser2(testCoop, investor3, tester5)
|
||||
|
||||
// const { stdout } = await execa('esno', [CLI_PATH, 'boot'], { stdio: 'inherit' })
|
||||
// expect(stdout).toContain('Boot process completed')
|
||||
}, 500_000)
|
||||
|
||||
afterAll(() => {
|
||||
console.log('\n📊 **RAM USAGE SUMMARY** 📊')
|
||||
let totalGlobalRam = 0
|
||||
|
||||
for (const [key, ramUsed] of Object.entries(globalRamStats)) {
|
||||
const ramKb = (ramUsed / 1024).toFixed(2)
|
||||
console.log(` ${key} = ${ramKb} kb`)
|
||||
totalGlobalRam += ramUsed
|
||||
}
|
||||
|
||||
console.log(`\n💾 **TOTAL RAM USED IN TESTS**: ${(totalGlobalRam / 1024).toFixed(2)} kb\n`)
|
||||
|
||||
console.log(`testCoop: ${testCoop}\ntester1: ${tester1}\ntester2: ${tester2}\ntester3: ${tester3}\ntester4: ${tester4}\ntester5: ${tester5}\ninvestor1: ${investor1}\ninvestor2: ${investor2}\ninvestor3: ${investor3}`)
|
||||
})
|
||||
|
||||
describe('тест контракта CAPITAL', () => {
|
||||
it('создаём программу генерации', async () => {
|
||||
const program = await getCoopProgramWallet(blockchain, testCoop, sourceProgramId)
|
||||
if (!program) {
|
||||
const data: SovietContract.Actions.Programs.CreateProgram.ICreateProgram = {
|
||||
coopname: testCoop,
|
||||
is_can_coop_spend_share_contributions: true,
|
||||
username: chairman,
|
||||
title: 'Договор УХД',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
images: '',
|
||||
calculation_type: 'free',
|
||||
fixed_membership_contribution: '0.0000 RUB',
|
||||
membership_percent_fee: '0',
|
||||
meta: '',
|
||||
type: 'source',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Programs.CreateProgram.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: chairman,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('создаём программу капитализации', async () => {
|
||||
const program = await getCoopProgramWallet(blockchain, testCoop, capitalProgramId)
|
||||
|
||||
if (!program) {
|
||||
const data: SovietContract.Actions.Programs.CreateProgram.ICreateProgram = {
|
||||
coopname: testCoop,
|
||||
is_can_coop_spend_share_contributions: false,
|
||||
username: chairman,
|
||||
title: 'Капитализация',
|
||||
announce: '',
|
||||
description: '',
|
||||
preview: '',
|
||||
images: '',
|
||||
calculation_type: 'free',
|
||||
fixed_membership_contribution: '0.0000 RUB',
|
||||
membership_percent_fee: '0',
|
||||
meta: '',
|
||||
type: 'capital',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: SovietContract.contractName.production,
|
||||
name: SovietContract.Actions.Programs.CreateProgram.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: chairman,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('импорт вкладчика ДО установки конфига - должен работать', async () => {
|
||||
// Создаем тестового пользователя для импорта
|
||||
const importUsername = generateRandomUsername()
|
||||
console.log('importUsername: ', importUsername)
|
||||
await addUser2(testCoop, importUsername)
|
||||
|
||||
const contributorHash = generateRandomSHA256()
|
||||
const contributionAmount = '10000.0000 RUB'
|
||||
|
||||
// Получаем состояние паевого счета ДО импорта
|
||||
const shareFundBefore = await getCirculationAccount(blockchain, testCoop)
|
||||
const shareFundBalanceBefore = parseFloat(shareFundBefore.available)
|
||||
|
||||
console.log('Share fund before import:', shareFundBalanceBefore)
|
||||
|
||||
// Импортируем вкладчика
|
||||
const importData: CapitalContract.Actions.ImportContributor.IImportContributor = {
|
||||
coopname: testCoop,
|
||||
username: importUsername,
|
||||
contributor_hash: contributorHash,
|
||||
contribution_amount: contributionAmount,
|
||||
memo: 'договор №123 от такого то числа',
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ImportContributor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: testCoop,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: importData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
// Проверяем, что вкладчик был создан
|
||||
const contributorTable = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
testCoop,
|
||||
'contributors',
|
||||
)
|
||||
console.log('contributorTable: ', contributorTable)
|
||||
|
||||
expect(contributorTable.length).toBe(1)
|
||||
const contributor = contributorTable[0]
|
||||
expect(contributor.username).toBe(importUsername)
|
||||
expect(contributor.status).toBe('active')
|
||||
|
||||
// Проверяем, что кошелек программы был создан
|
||||
const wallet = await getUserProgramWalletAmount(blockchain, testCoop, importUsername, capitalProgramId)
|
||||
expect(parseFloat(wallet)).toBe(parseFloat(contributionAmount))
|
||||
|
||||
// Проверяем увеличение паевого счета
|
||||
const shareFundAfter = await getCirculationAccount(blockchain, testCoop)
|
||||
const shareFundBalanceAfter = parseFloat(shareFundAfter.available)
|
||||
console.log('Share fund after import:', shareFundBalanceAfter)
|
||||
|
||||
expect(shareFundBalanceAfter).toBe(shareFundBalanceBefore + parseFloat(contributionAmount))
|
||||
})
|
||||
|
||||
it('инициализируем контракт CAPITAL и сверяем конфиг', async () => {
|
||||
const data: CapitalContract.Actions.SetConfig.ISetConfig = {
|
||||
coopname: testCoop,
|
||||
config: {
|
||||
coordinator_bonus_percent: 4,
|
||||
expense_pool_percent: 100,
|
||||
coordinator_invite_validity_days: 30,
|
||||
voting_period_in_days: 7,
|
||||
authors_voting_percent: 38.2,
|
||||
creators_voting_percent: 38.2,
|
||||
},
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.SetConfig.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: testCoop,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
const state = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
CapitalContract.contractName.production,
|
||||
'state',
|
||||
1,
|
||||
testCoop,
|
||||
testCoop,
|
||||
))[0]
|
||||
console.log('state: ', state)
|
||||
expect(state).toBeDefined()
|
||||
expect(state.coopname).toBe(testCoop)
|
||||
expect(parseFloat(state.config.coordinator_bonus_percent)).toBe(data.config.coordinator_bonus_percent)
|
||||
expect(parseFloat(state.config.expense_pool_percent)).toBe(data.config.expense_pool_percent)
|
||||
expect(parseFloat(state.config.coordinator_invite_validity_days)).toBe(data.config.coordinator_invite_validity_days)
|
||||
expect(parseFloat(state.config.voting_period_in_days)).toBe(data.config.voting_period_in_days)
|
||||
expect(parseFloat(state.config.authors_voting_percent)).toBe(data.config.authors_voting_percent)
|
||||
expect(parseFloat(state.config.creators_voting_percent)).toBe(data.config.creators_voting_percent)
|
||||
})
|
||||
|
||||
it('импорт вкладчика ПОСЛЕ установки конфига - должен провалиться', async () => {
|
||||
// Создаем еще одного тестового пользователя для импорта
|
||||
const importUsername2 = generateRandomUsername()
|
||||
console.log('importUsername2: ', importUsername2)
|
||||
await addUser(importUsername2)
|
||||
|
||||
const contributorHash2 = generateRandomSHA256()
|
||||
const contributionAmount2 = '5000.0000 RUB'
|
||||
|
||||
// Пытаемся импортировать вкладчика после установки конфига
|
||||
const importData2: CapitalContract.Actions.ImportContributor.IImportContributor = {
|
||||
coopname: testCoop,
|
||||
username: importUsername2,
|
||||
contributor_hash: contributorHash2,
|
||||
contribution_amount: contributionAmount2,
|
||||
memo: 'договор №123 от такого то числа',
|
||||
}
|
||||
|
||||
// Ожидаем, что транзакция провалится
|
||||
await expect(
|
||||
blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.ImportContributor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: testCoop,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data: importData2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
),
|
||||
).rejects.toThrow() // Ожидаем ошибку
|
||||
|
||||
// Проверяем, что вкладчик НЕ был создан
|
||||
const contributorTable = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
testCoop,
|
||||
'contributors',
|
||||
)
|
||||
expect(contributorTable.length).toBe(1)
|
||||
|
||||
// Проверяем, что кошелек программы НЕ был создан
|
||||
const wallet = await getUserProgramWalletAmount(blockchain, testCoop, importUsername2, capitalProgramId)
|
||||
expect(wallet).toBe('0.0000 RUB')
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { expect } from 'vitest'
|
||||
import type Blockchain from '../../blockchain'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { makeCombinedChecksum256NameIndexKey } from '../shared/combinedKeys'
|
||||
|
||||
export async function addAuthor(blockchain: Blockchain, coopname: string, project_hash: string, author: string) {
|
||||
const data: CapitalContract.Actions.AddAuthor.IAddAuthor = {
|
||||
coopname,
|
||||
project_hash,
|
||||
author,
|
||||
}
|
||||
|
||||
const result = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.AddAuthor.actionName,
|
||||
authorization: [
|
||||
{
|
||||
actor: coopname,
|
||||
permission: 'active',
|
||||
},
|
||||
],
|
||||
data,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
getTotalRamUsage(result)
|
||||
expect(result.transaction_id).toBeDefined()
|
||||
|
||||
const key = makeCombinedChecksum256NameIndexKey(project_hash, author)
|
||||
const rows = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Segments.tableName,
|
||||
1000,
|
||||
key,
|
||||
key,
|
||||
3,
|
||||
'i128',
|
||||
))
|
||||
|
||||
const segment = rows[0]
|
||||
|
||||
console.log('segment: ', segment)
|
||||
expect(segment).toBeDefined()
|
||||
expect(segment.username).toBe(author)
|
||||
expect(segment.project_hash).toBe(project_hash)
|
||||
expect(segment.is_author).toBe(1)
|
||||
|
||||
const project = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
CapitalContract.Tables.Projects.tableName,
|
||||
1000,
|
||||
project_hash,
|
||||
project_hash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
expect(project).toBeDefined()
|
||||
|
||||
return {
|
||||
project,
|
||||
segment,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
|
||||
export async function allocateFundsToResult(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
projectHash: string,
|
||||
resultHash: string,
|
||||
amount: string,
|
||||
) {
|
||||
console.log(`\n🔹 Начало финансирования результата: ${resultHash}, сумма: ${amount}`)
|
||||
|
||||
// Получение текущих данных перед финансированием
|
||||
const prevResult = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'results',
|
||||
1,
|
||||
resultHash,
|
||||
resultHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0] || { available: '0.0000 RUB' }
|
||||
|
||||
const prevProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0] || { available: '0.0000 RUB' }
|
||||
|
||||
console.log('📊 Балансы до финансирования:')
|
||||
console.log('▶ Результат:', prevResult)
|
||||
console.log('▶ Проект:', prevProject)
|
||||
|
||||
// Отправка транзакции финансирования
|
||||
const allocateData: CapitalContract.Actions.Allocate.IAllocate = {
|
||||
coopname,
|
||||
application: coopname,
|
||||
project_hash: projectHash,
|
||||
result_hash: resultHash,
|
||||
amount,
|
||||
}
|
||||
|
||||
console.log(`\n🚀 Отправка транзакции Allocate для результата ${resultHash}`)
|
||||
const allocateResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.Allocate.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: allocateData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(allocateResult)
|
||||
expect(allocateResult.transaction_id).toBeDefined()
|
||||
|
||||
// Получение обновленных данных
|
||||
const updatedResult = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'results',
|
||||
1,
|
||||
resultHash,
|
||||
resultHash,
|
||||
2,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
const updatedProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('\n📊 Балансы после финансирования:')
|
||||
console.log('▶ Результат:', updatedResult)
|
||||
console.log('▶ Проект:', updatedProject)
|
||||
|
||||
// Проверка изменения available
|
||||
expect(parseFloat(updatedResult.available)).toBe(parseFloat(prevResult.available) + parseFloat(amount))
|
||||
expect(parseFloat(updatedProject.available)).toBe(parseFloat(prevProject.available) - parseFloat(amount))
|
||||
expect(parseFloat(updatedProject.allocated)).toBe(parseFloat(prevProject.allocated) + parseFloat(amount))
|
||||
|
||||
console.log(`\n✅ Финансирование ${amount} завершено успешно!`)
|
||||
|
||||
return { transactionId: allocateResult.transaction_id }
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import { expect } from 'vitest'
|
||||
import { CapitalContract, SovietContract } from 'cooptypes'
|
||||
import { getTotalRamUsage } from '../../utils/getTotalRamUsage'
|
||||
import { generateRandomSHA256 } from '../../utils/randomHash'
|
||||
import { fakeDocument } from '../shared/fakeDocument'
|
||||
import { processMasterApprove } from './processMasterApprove'
|
||||
|
||||
export async function commitToResult(
|
||||
blockchain: any,
|
||||
coopname: string,
|
||||
projectHash: string,
|
||||
creator: string,
|
||||
spendHours: number,
|
||||
) {
|
||||
const commitHash = generateRandomSHA256()
|
||||
console.log(`\n🔹 Начало коммита: ${commitHash}, проект: ${projectHash}, часы: ${spendHours}`)
|
||||
|
||||
// Получение текущих данных проекта перед коммитом
|
||||
const prevProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0] || { spended: '0.0000 RUB' }
|
||||
|
||||
const prevContributor = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
creator,
|
||||
creator,
|
||||
2,
|
||||
'i64',
|
||||
))[0] || { contributed_hours: 0, available: '0.0000 RUB' }
|
||||
|
||||
console.log('📊 Балансы до коммита:')
|
||||
console.log('▶ Проект:', prevProject)
|
||||
console.log('▶ Контрибьютор:', prevContributor)
|
||||
|
||||
// Обновим состояние проекта после возможной смены статуса
|
||||
const projectBefore = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
const prevTotalCommits = projectBefore?.counts?.total_commits ?? 0
|
||||
const prevCreatorsHours = projectBefore?.fact?.creators_hours ?? 0
|
||||
|
||||
// Создание коммита
|
||||
const commitData: CapitalContract.Actions.CreateCommit.ICommit = {
|
||||
coopname,
|
||||
username: creator,
|
||||
project_hash: projectHash,
|
||||
commit_hash: commitHash,
|
||||
creator_hours: spendHours,
|
||||
}
|
||||
|
||||
const createCommitResult = await blockchain.api.transact(
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
account: CapitalContract.contractName.production,
|
||||
name: CapitalContract.Actions.CreateCommit.actionName,
|
||||
authorization: [{ actor: coopname, permission: 'active' }],
|
||||
data: commitData,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
blocksBehind: 3,
|
||||
expireSeconds: 30,
|
||||
},
|
||||
)
|
||||
|
||||
getTotalRamUsage(createCommitResult)
|
||||
expect(createCommitResult.transaction_id).toBeDefined()
|
||||
|
||||
// Проверка созданного коммита
|
||||
const blockchainCommit = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'commits',
|
||||
1,
|
||||
commitHash,
|
||||
commitHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
console.log('🔍 Коммит после создания:', blockchainCommit)
|
||||
expect(blockchainCommit).toBeDefined()
|
||||
expect(blockchainCommit.commit_hash).toBe(commitHash)
|
||||
expect(blockchainCommit.project_hash).toBe(projectHash)
|
||||
expect(blockchainCommit.status).toBe('created')
|
||||
|
||||
// Получение данных проекта для определения мастера
|
||||
const projectData = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
expect(projectData).toBeDefined()
|
||||
expect(projectData.master).toBeDefined()
|
||||
|
||||
// Утверждение коммита мастером проекта
|
||||
console.log(`\n✅ Подтверждение коммита ${commitHash} мастером ${projectData.master}`)
|
||||
const approveCommitResult = await processMasterApprove(blockchain, coopname, commitHash, projectData.master)
|
||||
|
||||
// Проверка утвержденного коммита
|
||||
const blockchainEmptyCommit = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'commits',
|
||||
1,
|
||||
commitHash,
|
||||
commitHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
// Коммит удаляем после утверждения
|
||||
expect(blockchainEmptyCommit).toBeUndefined()
|
||||
|
||||
// Проверка проекта после утверждения коммита
|
||||
const finalProject = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'projects',
|
||||
1,
|
||||
projectHash,
|
||||
projectHash,
|
||||
3,
|
||||
'sha256',
|
||||
))[0]
|
||||
|
||||
const finalContributor = (await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'contributors',
|
||||
1,
|
||||
creator,
|
||||
creator,
|
||||
2,
|
||||
'i64',
|
||||
))[0]
|
||||
|
||||
console.log('\n📊 Балансы после утверждения коммита:')
|
||||
console.log('▶ Проект:', finalProject)
|
||||
|
||||
// Проверки по проекту
|
||||
expect(finalProject.counts.total_commits).toBe(prevTotalCommits + 1)
|
||||
expect(parseInt(finalProject.fact.creators_hours)).toBe(prevCreatorsHours + spendHours)
|
||||
|
||||
console.log(`\n✅ Коммит ${commitHash} завершен успешно!`)
|
||||
|
||||
return {
|
||||
commitHash,
|
||||
commit: blockchainCommit,
|
||||
transactionId: approveCommitResult.transaction_id,
|
||||
transaction: approveCommitResult,
|
||||
finalProject,
|
||||
finalContributor,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export const sourceProgramId = 3
|
||||
export const capitalProgramId = 4
|
||||
export const walletProgramId = 1
|
||||
export const ratePerHour = '1000.0000 RUB'
|
||||
export const sourceProgramName = 'source'
|
||||
export const capitalProgramName = 'capital'
|
||||
export const circulationAccountId = 80
|
||||
@@ -0,0 +1,27 @@
|
||||
import { CapitalContract } from 'cooptypes'
|
||||
import type Blockchain from '../../blockchain'
|
||||
|
||||
/**
|
||||
* Получает кошелек капитализации пользователя
|
||||
* @param blockchain экземпляр блокчейна
|
||||
* @param coopname название кооператива
|
||||
* @param username имя пользователя
|
||||
* @returns кошелек капитализации или null если не найден
|
||||
*/
|
||||
export async function getCapitalWallet(
|
||||
blockchain: Blockchain,
|
||||
coopname: string,
|
||||
username: string,
|
||||
) {
|
||||
const capitalWallets = await blockchain.getTableRows(
|
||||
CapitalContract.contractName.production,
|
||||
coopname,
|
||||
'capwallets',
|
||||
100,
|
||||
username,
|
||||
username,
|
||||
2, // by_username index
|
||||
)
|
||||
|
||||
return capitalWallets[0] || null
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user