@stabilitydao/host 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/npm.yml +19 -0
- package/.github/workflows/prettier.yml +25 -0
- package/.github/workflows/test.yml +29 -0
- package/.prettierignore +4 -0
- package/.prettierrc +1 -0
- package/LICENSE +674 -0
- package/README.md +35 -0
- package/jest.config.js +9 -0
- package/logo.png +0 -0
- package/out/index.js +30 -0
- package/package.json +28 -0
- package/src/activity/builder.ts +141 -0
- package/src/activity/index.ts +34 -0
- package/src/agents.ts +31 -0
- package/src/api.ts +51 -0
- package/src/assets.ts +1579 -0
- package/src/chains.ts +604 -0
- package/src/host.ts +1212 -0
- package/src/index.ts +36 -0
- package/src/storage/daoMetaData.ts +312 -0
- package/src/storage/daos.ts +306 -0
- package/src/tokenlist.json +1938 -0
- package/tests/assets.test.ts +61 -0
- package/tests/chains.test.ts +22 -0
- package/tests/host.test.ts +811 -0
- package/tsconfig.json +18 -0
package/src/host.ts
ADDED
|
@@ -0,0 +1,1212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Host prototype.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ChainName, chains, getChainByName } from "./chains";
|
|
6
|
+
import { IAgent } from "./agents";
|
|
7
|
+
import { IBuilderActivity } from "./activity/builder";
|
|
8
|
+
import { Activity } from "./activity";
|
|
9
|
+
import { IDAOAPIData } from "./api";
|
|
10
|
+
|
|
11
|
+
export const HOST_DESCRIPTION = "A Cozy Home for DAOs";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
Represents a DAO running on Host.
|
|
15
|
+
|
|
16
|
+
todo: Optimize for cross-chain
|
|
17
|
+
host-contracts: `IHost.DAOData`
|
|
18
|
+
|
|
19
|
+
@version 0.2.0
|
|
20
|
+
@alpha
|
|
21
|
+
@interface
|
|
22
|
+
*/
|
|
23
|
+
export interface IDAOData {
|
|
24
|
+
/** SEGMENT 1: ON-CHAIN on all chains where Host deployed */
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Tradeable interchain ERC-20 token symbol.
|
|
28
|
+
* Lowercased used as slug.
|
|
29
|
+
* While token symbol is SYM then additional DAO tokens symbols are:
|
|
30
|
+
* seedSYM, saleSYM, xSYM, SYM_DAO
|
|
31
|
+
*
|
|
32
|
+
* host-contracts: HostLib.OsStorage.usedSymbols
|
|
33
|
+
*/
|
|
34
|
+
symbol: string;
|
|
35
|
+
|
|
36
|
+
/** SEGMENT 2: ON-CHAIN on chains where DAO bridged */
|
|
37
|
+
|
|
38
|
+
/** Unique ID of DAO */
|
|
39
|
+
uid?: string;
|
|
40
|
+
|
|
41
|
+
/** Name of the DAO, used in token names. Without DAO word. */
|
|
42
|
+
name: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
DAO lifecycle phase.
|
|
46
|
+
Changes permissionless when next phase start timestamp reached.
|
|
47
|
+
*/
|
|
48
|
+
phase: LifecyclePhase;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
Deployed smart-contracts.
|
|
52
|
+
host-contracts: deployments of current instance chain only.
|
|
53
|
+
*/
|
|
54
|
+
deployments: IDAODeployments;
|
|
55
|
+
|
|
56
|
+
/** Settings of DAO for current chain. This is the only place to save settings of DAO for chains. */
|
|
57
|
+
chainSettings: IDAOChainSettings;
|
|
58
|
+
|
|
59
|
+
/** IDs of Units running on current chain */
|
|
60
|
+
unitIds?: string[];
|
|
61
|
+
|
|
62
|
+
/** On-chain DAO parameters for tokenomics and revenue sharing */
|
|
63
|
+
params: IDAOParameters;
|
|
64
|
+
|
|
65
|
+
/** SEGMENT 3: ON-CHAIN on initial chain of DAO */
|
|
66
|
+
|
|
67
|
+
/** Where initial deployment became */
|
|
68
|
+
initialChain: ChainName;
|
|
69
|
+
|
|
70
|
+
/** Community socials. Update by `Host.updateSocials` */
|
|
71
|
+
socials: string[];
|
|
72
|
+
|
|
73
|
+
/** Activities of the organization. */
|
|
74
|
+
activity: Activity[];
|
|
75
|
+
|
|
76
|
+
/** Images of tokens. Absolute or relative from stabilitydao/.github repo /os/ folder. */
|
|
77
|
+
images: IDAOImages;
|
|
78
|
+
|
|
79
|
+
/** Revenue generating units owned by the organization. */
|
|
80
|
+
units: IUnit[];
|
|
81
|
+
|
|
82
|
+
/** Fundraising */
|
|
83
|
+
funding: IFunding[];
|
|
84
|
+
|
|
85
|
+
/** Vesting allocations */
|
|
86
|
+
vesting: IVesting[];
|
|
87
|
+
|
|
88
|
+
/** Settings of DAO Governance */
|
|
89
|
+
governanceSettings: IGovernanceSettings;
|
|
90
|
+
|
|
91
|
+
/** Deployer of a DAO have power only at DRAFT phase. */
|
|
92
|
+
deployer: string;
|
|
93
|
+
|
|
94
|
+
/** DAO custom metadata stored off-chain. */
|
|
95
|
+
daoMetaDataLocation?: string; // "local","https://..."
|
|
96
|
+
|
|
97
|
+
/** SEGMENT 4: OFF-CHAIN emitted data */
|
|
98
|
+
|
|
99
|
+
unitsMetaData: IUnitMetaData[];
|
|
100
|
+
|
|
101
|
+
/** SEGMENT 5: OFF-CHAIN custom data managed by DAO */
|
|
102
|
+
|
|
103
|
+
/** Storage for BUILDER activity and Agents data. */
|
|
104
|
+
daoMetaData?: IDAOMetaData;
|
|
105
|
+
|
|
106
|
+
/** SEGMENT 6: API data of DAO */
|
|
107
|
+
|
|
108
|
+
/** Hot data updates each minute */
|
|
109
|
+
api?: IDAOAPIData;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface IDAOMetaData {
|
|
113
|
+
/** DAOs engaging BUILDER activity settings */
|
|
114
|
+
builderActivity?: IBuilderActivity;
|
|
115
|
+
|
|
116
|
+
/** Operating agents managed by the organization. */
|
|
117
|
+
agents?: IAgent[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Images of tokens. Absolute or relative from stabilitydao/.github repo /os/ folder. */
|
|
121
|
+
export interface IDAOImages {
|
|
122
|
+
seedToken?: string;
|
|
123
|
+
tgeToken?: string;
|
|
124
|
+
token?: string;
|
|
125
|
+
xToken?: string;
|
|
126
|
+
daoToken?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
Lifecycle phase represents DAO tokenomics stage.
|
|
131
|
+
*/
|
|
132
|
+
export enum LifecyclePhase {
|
|
133
|
+
/** Created */
|
|
134
|
+
DRAFT = "DRAFT",
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
Initial funding. DAO project passed requirements.
|
|
138
|
+
Since SEED started a DAO become real DAO:
|
|
139
|
+
- noncustodial
|
|
140
|
+
- tokenized share holdings
|
|
141
|
+
- collective management via voting
|
|
142
|
+
*/
|
|
143
|
+
SEED = "SEED",
|
|
144
|
+
|
|
145
|
+
/** Seed was not success. Raised funds sent back to seeders. */
|
|
146
|
+
SEED_FAILED = "SEED_FAILED",
|
|
147
|
+
|
|
148
|
+
/** Using SEED funds to launch MVP / Unit generating */
|
|
149
|
+
DEVELOPMENT = "DEVELOPMENT",
|
|
150
|
+
|
|
151
|
+
/** TGE is funding event for token liquidity and DAO developments (optionally) */
|
|
152
|
+
TGE = "TGE",
|
|
153
|
+
|
|
154
|
+
/** Delay before any vesting allocation started */
|
|
155
|
+
LIVE_CLIFF = "LIVE_CLIFF",
|
|
156
|
+
|
|
157
|
+
/** Vesting period active */
|
|
158
|
+
LIVE_VESTING = "LIVE_VESTING",
|
|
159
|
+
|
|
160
|
+
/** Vesting ended - token fully distributed */
|
|
161
|
+
LIVE = "LIVE",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
Parameters of VE-tokenomics and revenue sharing.
|
|
166
|
+
@interface
|
|
167
|
+
*/
|
|
168
|
+
export interface IDAOParameters {
|
|
169
|
+
/** Vested Escrow period, days. */
|
|
170
|
+
vePeriod: number;
|
|
171
|
+
/** Instant exit fee, percent */
|
|
172
|
+
pvpFee: number;
|
|
173
|
+
/** Minimal power (min stake amount) to be a holder of DAO */
|
|
174
|
+
minPower?: number;
|
|
175
|
+
/** Share of total DAO revenue going to accidents compensations, percent */
|
|
176
|
+
recoveryShare?: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface IDAOChainSettings {
|
|
180
|
+
bbRate: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface IGovernanceSettings {
|
|
184
|
+
/** Minimal total voting power (self and delegated) need to create a proposal */
|
|
185
|
+
proposalThreshold?: number;
|
|
186
|
+
/** Bribe share for Tokenomics Transactions (vested funds spending), percent */
|
|
187
|
+
ttBribe?: number;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface IFunding {
|
|
191
|
+
type: FundingType;
|
|
192
|
+
start: number;
|
|
193
|
+
end: number;
|
|
194
|
+
minRaise: number;
|
|
195
|
+
maxRaise: number;
|
|
196
|
+
raised: number;
|
|
197
|
+
claim?: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export enum FundingType {
|
|
201
|
+
SEED = "SEED",
|
|
202
|
+
TGE = "TGE",
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
Vesting allocation data
|
|
207
|
+
@interface
|
|
208
|
+
*/
|
|
209
|
+
export interface IVesting {
|
|
210
|
+
/** Short name of vesting allocation */
|
|
211
|
+
name: string;
|
|
212
|
+
/** How must be spent */
|
|
213
|
+
description?: string;
|
|
214
|
+
/** Vesting supply. 10 == 10e18 TOKEN */
|
|
215
|
+
allocation: number;
|
|
216
|
+
/** Start timestamp */
|
|
217
|
+
start: number;
|
|
218
|
+
/** End timestamp */
|
|
219
|
+
end: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
Deployments of running DAO on blockchains.
|
|
224
|
+
|
|
225
|
+
@interface
|
|
226
|
+
*/
|
|
227
|
+
export interface IDAODeployments {
|
|
228
|
+
[chainId: string]: {
|
|
229
|
+
/** Seed round receipt token. */
|
|
230
|
+
seedToken?: `0x${string}`;
|
|
231
|
+
/** TGE pre-sale receipt token. */
|
|
232
|
+
tgeToken?: `0x${string}`;
|
|
233
|
+
/** Main tradable DAO token. */
|
|
234
|
+
token?: `0x${string}`;
|
|
235
|
+
/** VE-tokenomics entry token. */
|
|
236
|
+
xToken?: `0x${string}`;
|
|
237
|
+
/** Staking contract. */
|
|
238
|
+
staking?: `0x${string}`;
|
|
239
|
+
/** Governance token. */
|
|
240
|
+
daoToken?: `0x${string}`;
|
|
241
|
+
/** Revenue utilization and distributing contract. */
|
|
242
|
+
revenueRouter?: `0x${string}`;
|
|
243
|
+
/** Accident recovery system contract. */
|
|
244
|
+
recovery?: `0x${string}`;
|
|
245
|
+
/** Set of vesting contracts. */
|
|
246
|
+
vesting?: { [name: string]: `0x${string}` };
|
|
247
|
+
/** Bridge for Token */
|
|
248
|
+
tokenBridge?: `0x${string}`;
|
|
249
|
+
/** Bridge for XToken */
|
|
250
|
+
xTokenBridge?: `0x${string}`;
|
|
251
|
+
/** Bridge for Governance token */
|
|
252
|
+
daoTokenBridge?: `0x${string}`;
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
Revenue generating unit owned by a DAO.
|
|
258
|
+
@interface
|
|
259
|
+
*/
|
|
260
|
+
export interface IUnit {
|
|
261
|
+
/** Unique unit string id. For DeFi protocol its defiOrg:protocolKey. */
|
|
262
|
+
unitId: string;
|
|
263
|
+
/** Blockchains where Unit deployed. Filled only for initial DAO chain Host instance. */
|
|
264
|
+
chainIds?: string[];
|
|
265
|
+
/** DAO UID of Unit Developer (Pool tasks solver) */
|
|
266
|
+
developerUid?: string;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Unit data that emitted, indexed and saved, translated by Host API later. */
|
|
270
|
+
export interface IUnitMetaData {
|
|
271
|
+
/** Short name of the unit */
|
|
272
|
+
name: string;
|
|
273
|
+
/** Status of unit changes appear when unit starting to work and starting earning revenue */
|
|
274
|
+
status: UnitStatus;
|
|
275
|
+
/** Supported type of the Unit */
|
|
276
|
+
type: UnitType;
|
|
277
|
+
/** The share of a Unit's profit received by the DAO to which it belongs. 100 - 100%. */
|
|
278
|
+
revenueShare: number;
|
|
279
|
+
/** A unique emoji for the shortest possible representation of a Unit. */
|
|
280
|
+
emoji?: string;
|
|
281
|
+
/** Frontend endpoints of Unit */
|
|
282
|
+
ui?: IUnitUILink[];
|
|
283
|
+
/** Links to API of the Unit */
|
|
284
|
+
api?: string[];
|
|
285
|
+
/** Components of the Unit. */
|
|
286
|
+
//components?: { [category in UnitComponentCategory]?: UnitComponent[] };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Supported unit types */
|
|
290
|
+
export enum UnitType {
|
|
291
|
+
/** VE-token early exit fees */
|
|
292
|
+
PVP = "PVP",
|
|
293
|
+
/** Decentralized finance protocol */
|
|
294
|
+
DEFI_PROTOCOL = "DEFI_PROTOCOL",
|
|
295
|
+
/** Maximum Extractable Value opportunities searcher and submitter. */
|
|
296
|
+
MEV_SEARCHER = "MEV_SEARCHER",
|
|
297
|
+
/** Software as a Service business */
|
|
298
|
+
//SAAS = "SAAS",
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Unit status can be changed automatically on DAO lifecycle phase changes or manually by DAO holders */
|
|
302
|
+
export enum UnitStatus {
|
|
303
|
+
RESEARCH = "RESEARCH",
|
|
304
|
+
BUILDING = "BUILDING",
|
|
305
|
+
LIVE = "LIVE",
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Supported categories of running units. */
|
|
309
|
+
export enum UnitComponentCategory {
|
|
310
|
+
CHAIN_SUPPORT = "CHAIN_SUPPORT",
|
|
311
|
+
ENGINE_SUPPORT = "ENGINE_SUPPORT",
|
|
312
|
+
DEFI_STRATEGY = "DEFI_STRATEGY",
|
|
313
|
+
MEV_STRATEGY = "MEV_STRATEGY",
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export interface IUnitUILink {
|
|
317
|
+
href: `https://${string}`;
|
|
318
|
+
title: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
//export type UnitComponent = StrategyShortId | ChainName | LendingEngine;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
Typescript implementation of the Host
|
|
325
|
+
Object of this class is Host instance deployed on a single blockchain.
|
|
326
|
+
|
|
327
|
+
@class
|
|
328
|
+
*/
|
|
329
|
+
export class Host {
|
|
330
|
+
/** Chain ID where instance deployed */
|
|
331
|
+
chainId: string;
|
|
332
|
+
|
|
333
|
+
/** Chain block timestamp */
|
|
334
|
+
blockTimestamp: number = Math.floor(new Date().getTime() / 1000);
|
|
335
|
+
|
|
336
|
+
/** Local DAOs storage (in form of a mapping) */
|
|
337
|
+
daos: { [symbol: string]: IDAOData } = {};
|
|
338
|
+
|
|
339
|
+
/** Actual DAO symbols at all blockchains */
|
|
340
|
+
usedSymbols: { [name: string]: boolean } = {};
|
|
341
|
+
|
|
342
|
+
/** All emitted events */
|
|
343
|
+
events: string[] = [];
|
|
344
|
+
|
|
345
|
+
/** Governance proposals. Can be created only at initialChain of DAO. */
|
|
346
|
+
proposals: { [proposalId: string]: IProposal } = {};
|
|
347
|
+
|
|
348
|
+
/** Current user address */
|
|
349
|
+
from: string = "0x00";
|
|
350
|
+
|
|
351
|
+
settings: IOSSettings = {
|
|
352
|
+
priceDao: 1000,
|
|
353
|
+
priceUnit: 1000,
|
|
354
|
+
priceOracle: 1000,
|
|
355
|
+
priceBridge: 1000,
|
|
356
|
+
minNameLength: 1,
|
|
357
|
+
maxNameLength: 20,
|
|
358
|
+
minSymbolLength: 1,
|
|
359
|
+
maxSymbolLength: 7,
|
|
360
|
+
minVePeriod: 14,
|
|
361
|
+
maxVePeriod: 365 * 4,
|
|
362
|
+
minPvPFee: 10,
|
|
363
|
+
maxPvPFee: 100,
|
|
364
|
+
minFundingDuration: 1,
|
|
365
|
+
maxFundingDuration: 180,
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
constructor(chainId: string) {
|
|
369
|
+
this.chainId = chainId;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
static getTokensNaming(name: string, symbol: string) {
|
|
373
|
+
return {
|
|
374
|
+
seedName: `${name} SEED`,
|
|
375
|
+
seedSymbol: `seed${symbol}`,
|
|
376
|
+
tgeName: `${name} PRESALE`,
|
|
377
|
+
tgeSymbol: `sale${symbol}`,
|
|
378
|
+
tokenName: name,
|
|
379
|
+
tokenSymbol: symbol,
|
|
380
|
+
xName: `x${name}`,
|
|
381
|
+
xSymbol: `x${symbol}`,
|
|
382
|
+
daoName: `${name} DAO`,
|
|
383
|
+
daoSymbol: `${symbol}_DAO`,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
static isLiveDAO(phase: LifecyclePhase) {
|
|
388
|
+
return [
|
|
389
|
+
LifecyclePhase.LIVE_CLIFF,
|
|
390
|
+
LifecyclePhase.LIVE_VESTING,
|
|
391
|
+
LifecyclePhase.LIVE,
|
|
392
|
+
].includes(phase);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Create new DAO
|
|
397
|
+
* @throws Error
|
|
398
|
+
*/
|
|
399
|
+
createDAO(
|
|
400
|
+
name: string,
|
|
401
|
+
symbol: string,
|
|
402
|
+
activity: Activity[],
|
|
403
|
+
params: IDAOParameters,
|
|
404
|
+
funding: IFunding[],
|
|
405
|
+
metaDataLocation?: string,
|
|
406
|
+
): IDAOData {
|
|
407
|
+
const dao: IDAOData = {
|
|
408
|
+
phase: LifecyclePhase.DRAFT,
|
|
409
|
+
name,
|
|
410
|
+
symbol,
|
|
411
|
+
activity,
|
|
412
|
+
socials: [],
|
|
413
|
+
images: {},
|
|
414
|
+
deployments: {},
|
|
415
|
+
units: [],
|
|
416
|
+
params,
|
|
417
|
+
chainSettings: {
|
|
418
|
+
bbRate: 50,
|
|
419
|
+
},
|
|
420
|
+
initialChain: chains[this.chainId].name,
|
|
421
|
+
funding,
|
|
422
|
+
vesting: [],
|
|
423
|
+
governanceSettings: {},
|
|
424
|
+
deployer: this.from,
|
|
425
|
+
daoMetaDataLocation: metaDataLocation,
|
|
426
|
+
unitsMetaData: [],
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
this.validate(dao);
|
|
430
|
+
|
|
431
|
+
this.daos[dao.symbol] = dao;
|
|
432
|
+
this.usedSymbols[dao.symbol] = true;
|
|
433
|
+
this._emit("DAO created");
|
|
434
|
+
this._sendCrossChainMessage(CROSS_CHAIN_MESSAGE.NEW_DAO_SYMBOL, {
|
|
435
|
+
symbol,
|
|
436
|
+
});
|
|
437
|
+
return dao;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Add live compatible DAO */
|
|
441
|
+
addLiveDAO(dao: IDAOData) {
|
|
442
|
+
// todo _onlyVerifier
|
|
443
|
+
this.validate(dao);
|
|
444
|
+
this.daos[dao.symbol] = dao;
|
|
445
|
+
this.usedSymbols[dao.symbol] = true;
|
|
446
|
+
this._emit("DAO created");
|
|
447
|
+
this._sendCrossChainMessage(CROSS_CHAIN_MESSAGE.NEW_DAO_SYMBOL, {
|
|
448
|
+
symbol: dao.symbol,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
getDAOMetaData(
|
|
453
|
+
daoMetaData: { [symbolLowerCase: string]: IDAOMetaData },
|
|
454
|
+
symbol: string,
|
|
455
|
+
): IDAOMetaData {
|
|
456
|
+
const dao = this.getDAO(symbol);
|
|
457
|
+
if (dao.daoMetaDataLocation === "local") {
|
|
458
|
+
return daoMetaData[symbol.toLowerCase()] as IDAOMetaData;
|
|
459
|
+
}
|
|
460
|
+
return {};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** Change lifecycle phase of a DAO */
|
|
464
|
+
changePhase(symbol: string) {
|
|
465
|
+
// anybody can call this
|
|
466
|
+
|
|
467
|
+
const dao = this.getDAO(symbol);
|
|
468
|
+
const currentTasks = this.tasks(symbol);
|
|
469
|
+
if (currentTasks.length > 0) {
|
|
470
|
+
throw new Error("SolveTasksFirst");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (dao.phase === LifecyclePhase.DRAFT) {
|
|
474
|
+
const seed = dao.funding[this.getFundingIndex(symbol, FundingType.SEED)];
|
|
475
|
+
if (seed.start > this.blockTimestamp) {
|
|
476
|
+
throw new Error("WaitFundingStart");
|
|
477
|
+
}
|
|
478
|
+
// SEED can be started not later than 1 week after must start
|
|
479
|
+
// todo settings.maxSeedStartDelay
|
|
480
|
+
if (
|
|
481
|
+
seed.start < this.blockTimestamp &&
|
|
482
|
+
this.blockTimestamp - seed.start > 7 * 86400
|
|
483
|
+
) {
|
|
484
|
+
throw new Error("TooLateSoSetupFundingAgain");
|
|
485
|
+
}
|
|
486
|
+
/*// SEED can be started not later than 1 week before end
|
|
487
|
+
if (seed.end - this.blockTimestamp < 7 * 86400) {
|
|
488
|
+
throw new Error("TooLateSoSetupFundingAgain")
|
|
489
|
+
}*/
|
|
490
|
+
|
|
491
|
+
// deploy seedToken
|
|
492
|
+
this.daos[symbol].deployments[this.chainId] = {
|
|
493
|
+
seedToken: "0xProxyDeployed",
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
this.daos[symbol].phase = LifecyclePhase.SEED;
|
|
497
|
+
} else if (dao.phase === LifecyclePhase.SEED) {
|
|
498
|
+
const seed = dao.funding[this.getFundingIndex(symbol, FundingType.SEED)];
|
|
499
|
+
if (seed.end > this.blockTimestamp) {
|
|
500
|
+
throw new Error("WaitFundingEnd");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const sucess = seed.raised >= seed.minRaise;
|
|
504
|
+
|
|
505
|
+
if (sucess) {
|
|
506
|
+
this.daos[symbol].phase = LifecyclePhase.DEVELOPMENT;
|
|
507
|
+
} else {
|
|
508
|
+
// send all raised back to seeders
|
|
509
|
+
|
|
510
|
+
this.daos[symbol].phase = LifecyclePhase.SEED_FAILED;
|
|
511
|
+
}
|
|
512
|
+
} else if (dao.phase === LifecyclePhase.DEVELOPMENT) {
|
|
513
|
+
const tge = dao.funding[this.getFundingIndex(symbol, FundingType.TGE)];
|
|
514
|
+
if (tge.start > this.blockTimestamp) {
|
|
515
|
+
throw new Error("WaitFundingStart");
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// deploy tgeToken
|
|
519
|
+
this.daos[symbol].deployments[this.chainId].tgeToken =
|
|
520
|
+
"0xProxyDeployedTge";
|
|
521
|
+
|
|
522
|
+
this.daos[symbol].phase = LifecyclePhase.TGE;
|
|
523
|
+
} else if (dao.phase === LifecyclePhase.TGE) {
|
|
524
|
+
const tge = dao.funding[this.getFundingIndex(symbol, FundingType.TGE)];
|
|
525
|
+
|
|
526
|
+
if (tge.end > this.blockTimestamp) {
|
|
527
|
+
throw new Error("WaitFundingEnd");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const success = tge.raised >= tge.minRaise;
|
|
531
|
+
|
|
532
|
+
if (success) {
|
|
533
|
+
// deploy token, xToken, staking, daoToken
|
|
534
|
+
this.daos[symbol].deployments[this.chainId].token = "0xProxyToken";
|
|
535
|
+
this.daos[symbol].deployments[this.chainId].xToken = "0xProxyXToken";
|
|
536
|
+
this.daos[symbol].deployments[this.chainId].staking = "0xProxyStaking";
|
|
537
|
+
this.daos[symbol].deployments[this.chainId].daoToken =
|
|
538
|
+
"0xProxyDAOToken";
|
|
539
|
+
|
|
540
|
+
// todo deploy vesting contracts and allocate token
|
|
541
|
+
|
|
542
|
+
// todo seedToken holders became xToken holders by predefined rate
|
|
543
|
+
|
|
544
|
+
// todo deploy v2 liquidity from TGE funds at predefined price
|
|
545
|
+
|
|
546
|
+
this.daos[symbol].phase = LifecyclePhase.LIVE_CLIFF;
|
|
547
|
+
} else {
|
|
548
|
+
// send all raised TGE funds back to funders
|
|
549
|
+
|
|
550
|
+
this.daos[symbol].phase = LifecyclePhase.DEVELOPMENT;
|
|
551
|
+
}
|
|
552
|
+
} else if (dao.phase === LifecyclePhase.LIVE_CLIFF) {
|
|
553
|
+
// if any vesting started then phase changed
|
|
554
|
+
const isVestingStarted = !!dao.vesting?.filter(
|
|
555
|
+
(v) => v.start < this.blockTimestamp,
|
|
556
|
+
).length;
|
|
557
|
+
if (!isVestingStarted) {
|
|
558
|
+
throw new Error("WaitVestingStart");
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
this.daos[symbol].phase = LifecyclePhase.LIVE_VESTING;
|
|
562
|
+
} else if (dao.phase === LifecyclePhase.LIVE_VESTING) {
|
|
563
|
+
// if any vesting started then phase changed
|
|
564
|
+
const isVestingEnded = !dao.vesting?.filter(
|
|
565
|
+
(v) => v.end > this.blockTimestamp,
|
|
566
|
+
).length;
|
|
567
|
+
if (!isVestingEnded) {
|
|
568
|
+
throw new Error("WaitVestingEnd");
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
this.daos[symbol].phase = LifecyclePhase.LIVE;
|
|
572
|
+
} else {
|
|
573
|
+
// nothing to change
|
|
574
|
+
throw new Error("ForeverLive");
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** @throws Error */
|
|
579
|
+
updateImages(symbol: string, images: IDAOImages) {
|
|
580
|
+
// check DAO symbol
|
|
581
|
+
const dao = this.getDAO(symbol);
|
|
582
|
+
|
|
583
|
+
// instant execute for DRAFT
|
|
584
|
+
if (dao.phase === LifecyclePhase.DRAFT) {
|
|
585
|
+
this._onlyOwnerOf(symbol);
|
|
586
|
+
this._updateImages(symbol, images);
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// create proposal for other phases
|
|
591
|
+
return this._proposeAction(symbol, DAOAction.UPDATE_IMAGES, {
|
|
592
|
+
images,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** @throws Error */
|
|
597
|
+
updateSocials(symbol: string, socials: string[]) {
|
|
598
|
+
// check DAO symbol
|
|
599
|
+
const dao = this.getDAO(symbol);
|
|
600
|
+
|
|
601
|
+
// instant execute for DRAFT
|
|
602
|
+
if (dao.phase === LifecyclePhase.DRAFT) {
|
|
603
|
+
this._onlyOwnerOf(symbol);
|
|
604
|
+
this._updateSocials(symbol, socials);
|
|
605
|
+
return true;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// create proposal for other phases
|
|
609
|
+
return this._proposeAction(symbol, DAOAction.UPDATE_SOCIALS, {
|
|
610
|
+
socials,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** @throws Error */
|
|
615
|
+
updateUnits(
|
|
616
|
+
symbol: string,
|
|
617
|
+
units: IUnit[],
|
|
618
|
+
unitsMetaData: IUnitMetaData[],
|
|
619
|
+
): string | true {
|
|
620
|
+
// check DAO symbol
|
|
621
|
+
const dao = this.getDAO(symbol);
|
|
622
|
+
|
|
623
|
+
// instant execute for DRAFT
|
|
624
|
+
if (dao.phase === LifecyclePhase.DRAFT) {
|
|
625
|
+
this._onlyOwnerOf(symbol);
|
|
626
|
+
this._updateUnits(symbol, units, unitsMetaData);
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// create proposal for other phases
|
|
631
|
+
return this._proposeAction(symbol, DAOAction.UPDATE_UNITS, {
|
|
632
|
+
units,
|
|
633
|
+
unitsMetaData,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** @throws Error */
|
|
638
|
+
updateFunding(symbol: string, funding: IFunding): string | true {
|
|
639
|
+
// check DAO symbol
|
|
640
|
+
const dao = this.getDAO(symbol);
|
|
641
|
+
|
|
642
|
+
// validate payload
|
|
643
|
+
this._validateFunding(dao.phase, [funding]);
|
|
644
|
+
|
|
645
|
+
// instant execute for DRAFT
|
|
646
|
+
if (dao.phase === LifecyclePhase.DRAFT) {
|
|
647
|
+
this._onlyOwnerOf(symbol);
|
|
648
|
+
this._updateFunding(symbol, funding);
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// create proposal for other phases
|
|
653
|
+
return this._proposeAction(symbol, DAOAction.UPDATE_FUNDING, {
|
|
654
|
+
funding,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
private _updateSocials(symbol: string, socials: string[]) {
|
|
659
|
+
this.daos[symbol].socials = socials;
|
|
660
|
+
this._emit(`Action ${DAOAction.UPDATE_SOCIALS}`);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private _updateUnits(
|
|
664
|
+
symbol: string,
|
|
665
|
+
units: IUnit[],
|
|
666
|
+
unitsMetaData: IUnitMetaData[],
|
|
667
|
+
) {
|
|
668
|
+
this.daos[symbol].units = units;
|
|
669
|
+
this.daos[symbol].unitsMetaData = unitsMetaData;
|
|
670
|
+
this._emit(`Action ${DAOAction.UPDATE_UNITS}`);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private _updateFunding(symbol: string, funding: IFunding) {
|
|
674
|
+
const dao = this.getDAO(symbol);
|
|
675
|
+
|
|
676
|
+
const fundingExist =
|
|
677
|
+
dao.funding.filter((f) => f.type === funding.type).length === 1;
|
|
678
|
+
if (fundingExist) {
|
|
679
|
+
const fundingIndex = this.getFundingIndex(symbol, funding.type);
|
|
680
|
+
this.daos[symbol].funding[fundingIndex] = funding;
|
|
681
|
+
} else {
|
|
682
|
+
this.daos[symbol].funding.push(funding);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
this._emit(`Action ${DAOAction.UPDATE_FUNDING}`);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
updateVesting(symbol: string, vestings: IVesting[]) {
|
|
689
|
+
// check DAO symbol
|
|
690
|
+
const dao = this.getDAO(symbol);
|
|
691
|
+
|
|
692
|
+
// validate
|
|
693
|
+
this._validateVesting(dao.phase, vestings);
|
|
694
|
+
|
|
695
|
+
// instant execute for DRAFT
|
|
696
|
+
if (dao.phase === LifecyclePhase.DRAFT) {
|
|
697
|
+
this._onlyOwnerOf(symbol);
|
|
698
|
+
this._updateVesting(symbol, vestings);
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// create proposal for other phases
|
|
703
|
+
return this._proposeAction(symbol, DAOAction.UPDATE_VESTING, {
|
|
704
|
+
vestings,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
fund(symbol: string, amount: number) {
|
|
709
|
+
// todo settings.minFunding
|
|
710
|
+
const dao = this.getDAO(symbol);
|
|
711
|
+
if (dao.phase === LifecyclePhase.SEED) {
|
|
712
|
+
const seedIndex = this.getFundingIndex(symbol, FundingType.SEED);
|
|
713
|
+
const seed = dao.funding[seedIndex];
|
|
714
|
+
if (seed.raised + amount >= seed.maxRaise) {
|
|
715
|
+
throw new Error("RaiseMaxExceed");
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// transfer amount of exchangeAsset to seedToken contract
|
|
719
|
+
this.daos[symbol].funding[seedIndex].raised += amount;
|
|
720
|
+
|
|
721
|
+
// mint seedToken to user
|
|
722
|
+
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (dao.phase === LifecyclePhase.TGE) {
|
|
727
|
+
const tgeIndex = this.getFundingIndex(symbol, FundingType.TGE);
|
|
728
|
+
const tge = dao.funding[tgeIndex];
|
|
729
|
+
if (tge.raised + amount >= tge.maxRaise) {
|
|
730
|
+
throw new Error("RaiseMaxExceed");
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// transfer amount of exchangeAsset to tgeToken contract
|
|
734
|
+
|
|
735
|
+
this.daos[symbol].funding[tgeIndex].raised += amount;
|
|
736
|
+
|
|
737
|
+
// mint tgeToken to user
|
|
738
|
+
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
throw new Error("NotFundingPhase");
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
receiveVotingResults(proposalId: string, succeed: boolean) {
|
|
746
|
+
const proposal = this.proposals[proposalId];
|
|
747
|
+
if (!proposal) {
|
|
748
|
+
throw new Error("IncorrectProposal");
|
|
749
|
+
}
|
|
750
|
+
if (proposal.status !== VotingStatus.VOTING) {
|
|
751
|
+
throw new Error("AlreadyReceived");
|
|
752
|
+
}
|
|
753
|
+
this.proposals[proposalId].status = succeed
|
|
754
|
+
? VotingStatus.APPROVED
|
|
755
|
+
: VotingStatus.REJECTED;
|
|
756
|
+
|
|
757
|
+
if (succeed) {
|
|
758
|
+
if (proposal.action === DAOAction.UPDATE_IMAGES) {
|
|
759
|
+
this._updateImages(proposal.symbol, proposal.payload.images);
|
|
760
|
+
}
|
|
761
|
+
if (proposal.action === DAOAction.UPDATE_SOCIALS) {
|
|
762
|
+
this._updateSocials(proposal.symbol, proposal.payload.socials);
|
|
763
|
+
}
|
|
764
|
+
if (proposal.action === DAOAction.UPDATE_UNITS) {
|
|
765
|
+
this._updateUnits(
|
|
766
|
+
proposal.symbol,
|
|
767
|
+
proposal.payload.units,
|
|
768
|
+
proposal.payload.unitsMetaData,
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
if (proposal.action === DAOAction.UPDATE_FUNDING) {
|
|
772
|
+
this._updateFunding(proposal.symbol, proposal.payload.funding);
|
|
773
|
+
}
|
|
774
|
+
if (proposal.action === DAOAction.UPDATE_VESTING) {
|
|
775
|
+
this._updateVesting(proposal.symbol, proposal.payload.vestings);
|
|
776
|
+
}
|
|
777
|
+
// todo other actions
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/** OFF-CHAIN only **/
|
|
782
|
+
/** @throws Error */
|
|
783
|
+
roadmap(symbol: string): IRoadmapItem[] {
|
|
784
|
+
const dao: IDAOData = this.getDAO(symbol);
|
|
785
|
+
const r: IRoadmapItem[] = [];
|
|
786
|
+
let tgeRun = 0;
|
|
787
|
+
|
|
788
|
+
for (const funding of dao.funding) {
|
|
789
|
+
if (funding.type === FundingType.SEED) {
|
|
790
|
+
r.push({
|
|
791
|
+
phase: LifecyclePhase.SEED,
|
|
792
|
+
start: funding.start,
|
|
793
|
+
end: funding.end,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
if (funding.type === FundingType.TGE) {
|
|
797
|
+
// if SEED was done
|
|
798
|
+
if (r.length > 0) {
|
|
799
|
+
r.push({
|
|
800
|
+
phase: LifecyclePhase.DEVELOPMENT,
|
|
801
|
+
start: (r[0].end as number) + 1,
|
|
802
|
+
end: funding.start - 1,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
tgeRun = funding.claim || funding.end;
|
|
807
|
+
r.push({
|
|
808
|
+
phase: LifecyclePhase.TGE,
|
|
809
|
+
start: funding.start,
|
|
810
|
+
end: tgeRun,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (dao.vesting.length > 0) {
|
|
816
|
+
let vestingStart = this.blockTimestamp;
|
|
817
|
+
let vestingEnd = this.blockTimestamp;
|
|
818
|
+
for (const vesting of dao.vesting) {
|
|
819
|
+
if (vesting.start < vestingStart) {
|
|
820
|
+
vestingStart = vesting.start;
|
|
821
|
+
}
|
|
822
|
+
if (vesting.end > vestingEnd) {
|
|
823
|
+
vestingEnd = vesting.end;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
r.push({
|
|
827
|
+
phase: LifecyclePhase.LIVE_CLIFF,
|
|
828
|
+
start: tgeRun + 1,
|
|
829
|
+
end: vestingStart - 1,
|
|
830
|
+
});
|
|
831
|
+
r.push({
|
|
832
|
+
phase: LifecyclePhase.LIVE_VESTING,
|
|
833
|
+
start: vestingStart,
|
|
834
|
+
end: vestingEnd,
|
|
835
|
+
});
|
|
836
|
+
r.push({
|
|
837
|
+
phase: LifecyclePhase.LIVE,
|
|
838
|
+
start: vestingEnd + 1,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
return r;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/** @throws Error */
|
|
846
|
+
tasks(symbol: string): ITask[] {
|
|
847
|
+
const dao: IDAOData = this.getDAO(symbol);
|
|
848
|
+
const r: ITask[] = [];
|
|
849
|
+
|
|
850
|
+
if (dao.phase === LifecyclePhase.DRAFT) {
|
|
851
|
+
// images
|
|
852
|
+
if (!dao.images.seedToken || !dao.images.token) {
|
|
853
|
+
r.push({
|
|
854
|
+
name: "Need images of token and seedToken",
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// socials
|
|
859
|
+
if (dao.socials.length < 2) {
|
|
860
|
+
r.push({
|
|
861
|
+
name: "Need at least 2 socials",
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// units projected
|
|
866
|
+
if (dao.units.length === 0) {
|
|
867
|
+
r.push({
|
|
868
|
+
name: "Need at least 1 projected unit",
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
} else if (dao.phase === LifecyclePhase.SEED) {
|
|
872
|
+
const seedIndex = this.getFundingIndex(symbol, FundingType.SEED);
|
|
873
|
+
if (
|
|
874
|
+
dao.funding[seedIndex].raised < dao.funding[seedIndex].minRaise &&
|
|
875
|
+
dao.funding[seedIndex].end > this.blockTimestamp
|
|
876
|
+
) {
|
|
877
|
+
r.push({
|
|
878
|
+
name: "Need attract minimal seed funding",
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
} else if (dao.phase === LifecyclePhase.DEVELOPMENT) {
|
|
882
|
+
// check funding
|
|
883
|
+
const tgeExist =
|
|
884
|
+
dao.funding.filter((f) => f.type === FundingType.TGE).length === 1;
|
|
885
|
+
if (!tgeExist) {
|
|
886
|
+
r.push({
|
|
887
|
+
name: "Need add pre-TGE funding",
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// images
|
|
892
|
+
if (!dao.images.tgeToken || !dao.images.xToken || !dao.images.daoToken) {
|
|
893
|
+
r.push({
|
|
894
|
+
name: "Need images of all DAO tokens",
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
// setup vesting allocations
|
|
899
|
+
if (!dao.vesting?.length) {
|
|
900
|
+
r.push({
|
|
901
|
+
name: "Need vesting allocations",
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (
|
|
906
|
+
dao.unitsMetaData?.filter(
|
|
907
|
+
(unitMetaData) => unitMetaData.status === UnitStatus.LIVE,
|
|
908
|
+
).length === 0
|
|
909
|
+
) {
|
|
910
|
+
r.push({
|
|
911
|
+
name: "Run revenue generating units",
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
} else if (dao.phase === LifecyclePhase.TGE) {
|
|
915
|
+
const tgeIndex = this.getFundingIndex(symbol, FundingType.TGE);
|
|
916
|
+
if (
|
|
917
|
+
dao.funding[tgeIndex].raised < dao.funding[tgeIndex].minRaise &&
|
|
918
|
+
dao.funding[tgeIndex].end > this.blockTimestamp
|
|
919
|
+
) {
|
|
920
|
+
r.push({
|
|
921
|
+
name: "Need attract minimal TGE funding",
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
} else if (dao.phase === LifecyclePhase.LIVE_CLIFF) {
|
|
925
|
+
// establish and improve
|
|
926
|
+
// build money markets
|
|
927
|
+
// bridge to chains
|
|
928
|
+
} else if (dao.phase === LifecyclePhase.LIVE_VESTING) {
|
|
929
|
+
// distribute vesting funds to leverage token
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/*if (dao.phase === LifecyclePhase.LIVE)*/
|
|
933
|
+
// lifetime revenue generating for DAO holders (till ABSORBED proposed feature)
|
|
934
|
+
|
|
935
|
+
return r;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/** Strict on-chain validation */
|
|
939
|
+
/** @throws Error */
|
|
940
|
+
validate(dao: IDAOData) {
|
|
941
|
+
this._validateName(dao.name);
|
|
942
|
+
this._validateSymbol(dao.symbol);
|
|
943
|
+
if (
|
|
944
|
+
dao.params.vePeriod < this.settings.minVePeriod ||
|
|
945
|
+
dao.params.vePeriod > this.settings.maxVePeriod
|
|
946
|
+
) {
|
|
947
|
+
throw new Error(`VePeriod(${dao.params.vePeriod})`);
|
|
948
|
+
}
|
|
949
|
+
this._validatePvpFee(dao.params.pvpFee);
|
|
950
|
+
if (!dao.funding.length) {
|
|
951
|
+
throw new Error("NeedFunding");
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// todo: check activity are correct
|
|
955
|
+
// todo: check funding array has unique funding types
|
|
956
|
+
// todo: check funding dates
|
|
957
|
+
// todo: check funding raise goals
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/** @throws Error */
|
|
961
|
+
getDAO(symbol: string): IDAOData {
|
|
962
|
+
if (this.daos[symbol]) {
|
|
963
|
+
return this.daos[symbol];
|
|
964
|
+
}
|
|
965
|
+
throw new Error("DAONotFound");
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
getDaoOwner(symbol: string): string {
|
|
969
|
+
const dao = this.getDAO(symbol);
|
|
970
|
+
|
|
971
|
+
if (dao.phase === LifecyclePhase.DRAFT) {
|
|
972
|
+
return dao.deployer;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if (
|
|
976
|
+
[
|
|
977
|
+
LifecyclePhase.SEED,
|
|
978
|
+
LifecyclePhase.DEVELOPMENT,
|
|
979
|
+
LifecyclePhase.TGE,
|
|
980
|
+
].includes(dao.phase)
|
|
981
|
+
) {
|
|
982
|
+
return dao.deployments[getChainByName(dao.initialChain).chainId]
|
|
983
|
+
.seedToken as string;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return dao.deployments[this.chainId]?.daoToken as string;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
getFundingIndex(symbol: string, type: FundingType) {
|
|
990
|
+
const dao = this.getDAO(symbol);
|
|
991
|
+
for (let i = 0; i < dao.funding.length; i++) {
|
|
992
|
+
if (type === dao.funding[i].type) {
|
|
993
|
+
return i;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
throw new Error("FundingNotFound");
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
warpDays(days: number = 7) {
|
|
1000
|
+
this.blockTimestamp += days * 86400;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/** @throws Error */
|
|
1004
|
+
private _onlyOwnerOf(symbol: string) {
|
|
1005
|
+
if (this.from != this.getDaoOwner(symbol)) {
|
|
1006
|
+
throw new Error(`YouAreNotOwnerOf(${symbol})`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
private _emit(event: string) {
|
|
1011
|
+
this.events.push(event);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private _validateName(name: string) {
|
|
1015
|
+
if (
|
|
1016
|
+
name.length < this.settings.minNameLength ||
|
|
1017
|
+
name.length > this.settings.maxNameLength
|
|
1018
|
+
) {
|
|
1019
|
+
throw new Error(`NameLength(${name.length})`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
private _validateSymbol(symbol: string) {
|
|
1024
|
+
if (
|
|
1025
|
+
symbol.length < this.settings.minSymbolLength ||
|
|
1026
|
+
symbol.length > this.settings.maxSymbolLength
|
|
1027
|
+
) {
|
|
1028
|
+
throw new Error(`SymbolLength(${symbol.length})`);
|
|
1029
|
+
}
|
|
1030
|
+
if (this.usedSymbols[symbol]) {
|
|
1031
|
+
throw new Error(`SymbolNotUnique(${symbol})`);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
private _validatePvpFee(pvpFee: number) {
|
|
1036
|
+
if (pvpFee < this.settings.minPvPFee || pvpFee > this.settings.maxPvPFee) {
|
|
1037
|
+
throw new Error(`PvPFee(${pvpFee})`);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
private _validateFunding(daoPhase: LifecyclePhase, fundings: IFunding[]) {
|
|
1042
|
+
for (const funding of fundings) {
|
|
1043
|
+
if (
|
|
1044
|
+
funding.type === FundingType.SEED &&
|
|
1045
|
+
daoPhase !== LifecyclePhase.DRAFT
|
|
1046
|
+
) {
|
|
1047
|
+
throw new Error("TooLateToUpdateSuchFunding");
|
|
1048
|
+
}
|
|
1049
|
+
if (
|
|
1050
|
+
funding.type === FundingType.TGE &&
|
|
1051
|
+
![
|
|
1052
|
+
LifecyclePhase.DRAFT,
|
|
1053
|
+
LifecyclePhase.SEED,
|
|
1054
|
+
LifecyclePhase.DEVELOPMENT,
|
|
1055
|
+
].includes(daoPhase)
|
|
1056
|
+
) {
|
|
1057
|
+
throw new Error("TooLateToUpdateSuchFunding");
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// todo check min round duration
|
|
1061
|
+
// todo check max round duration
|
|
1062
|
+
// todo check start date delay
|
|
1063
|
+
// todo check min amount
|
|
1064
|
+
// todo check max amount
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
private _validateVesting(daoPhase: LifecyclePhase, vestings: IVesting[]) {
|
|
1069
|
+
if (
|
|
1070
|
+
[
|
|
1071
|
+
LifecyclePhase.LIVE_CLIFF,
|
|
1072
|
+
LifecyclePhase.LIVE_VESTING,
|
|
1073
|
+
LifecyclePhase.LIVE,
|
|
1074
|
+
].includes(daoPhase)
|
|
1075
|
+
) {
|
|
1076
|
+
throw new Error("TooLateToUpdateVesting");
|
|
1077
|
+
}
|
|
1078
|
+
for (const vesting of vestings) {
|
|
1079
|
+
// todo check vesting consistency
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
private _sendCrossChainMessage(type: CROSS_CHAIN_MESSAGE, payload: any) {
|
|
1084
|
+
// todo some stub
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private _proposeAction(
|
|
1088
|
+
symbol: string,
|
|
1089
|
+
action: DAOAction,
|
|
1090
|
+
payload: any,
|
|
1091
|
+
): string {
|
|
1092
|
+
const dao = this.getDAO(symbol);
|
|
1093
|
+
|
|
1094
|
+
// todo check for initial chain
|
|
1095
|
+
// todo get user power
|
|
1096
|
+
// todo check proposalThreshold
|
|
1097
|
+
// todo validate payload
|
|
1098
|
+
|
|
1099
|
+
const proposalId = Math.round(Math.random() * Math.random()).toString();
|
|
1100
|
+
|
|
1101
|
+
this.proposals[proposalId] = {
|
|
1102
|
+
id: proposalId,
|
|
1103
|
+
created: this.blockTimestamp,
|
|
1104
|
+
action,
|
|
1105
|
+
symbol,
|
|
1106
|
+
payload,
|
|
1107
|
+
status: VotingStatus.VOTING,
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
return proposalId;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
private _updateImages(symbol: string, images: IDAOImages) {
|
|
1114
|
+
this.daos[symbol].images = images;
|
|
1115
|
+
this._emit(`Action ${DAOAction.UPDATE_IMAGES}`);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
private _updateVesting(symbol: string, vestings: IVesting[]) {
|
|
1119
|
+
this.daos[symbol].vesting = vestings;
|
|
1120
|
+
this._emit(`Action ${DAOAction.UPDATE_VESTING}`);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
export enum DAOAction {
|
|
1125
|
+
UPDATE_IMAGES = 0,
|
|
1126
|
+
UPDATE_SOCIALS,
|
|
1127
|
+
UPDATE_NAMING,
|
|
1128
|
+
UPDATE_UNITS,
|
|
1129
|
+
UPDATE_FUNDING,
|
|
1130
|
+
UPDATE_VESTING,
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
interface IOSSettings {
|
|
1134
|
+
priceDao: number;
|
|
1135
|
+
priceUnit: number;
|
|
1136
|
+
priceOracle: number;
|
|
1137
|
+
priceBridge: number;
|
|
1138
|
+
minNameLength: number;
|
|
1139
|
+
maxNameLength: number;
|
|
1140
|
+
minSymbolLength: number;
|
|
1141
|
+
maxSymbolLength: number;
|
|
1142
|
+
minVePeriod: number;
|
|
1143
|
+
maxVePeriod: number;
|
|
1144
|
+
minPvPFee: number;
|
|
1145
|
+
maxPvPFee: number;
|
|
1146
|
+
minFundingDuration: number;
|
|
1147
|
+
maxFundingDuration: number;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
enum VotingStatus {
|
|
1151
|
+
VOTING = 0,
|
|
1152
|
+
APPROVED,
|
|
1153
|
+
REJECTED,
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
enum CROSS_CHAIN_MESSAGE {
|
|
1157
|
+
NEW_DAO_SYMBOL = 0,
|
|
1158
|
+
DAO_RENAME_SYMBOL,
|
|
1159
|
+
DAO_BRIDGED,
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
interface ITask {
|
|
1163
|
+
name: string;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
interface IProposal {
|
|
1167
|
+
id: string;
|
|
1168
|
+
created: number;
|
|
1169
|
+
symbol: string;
|
|
1170
|
+
action: DAOAction;
|
|
1171
|
+
payload: any;
|
|
1172
|
+
status: VotingStatus;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
interface IRoadmapItem {
|
|
1176
|
+
phase: LifecyclePhase;
|
|
1177
|
+
start: number;
|
|
1178
|
+
end?: number;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
export function getDAOUnit(
|
|
1182
|
+
daos: IDAOData[],
|
|
1183
|
+
symbol: string,
|
|
1184
|
+
unitId: string,
|
|
1185
|
+
): IUnit | undefined {
|
|
1186
|
+
for (const dao of daos) {
|
|
1187
|
+
if (dao.symbol.toLowerCase() === symbol.toLowerCase()) {
|
|
1188
|
+
for (const unit of dao.units) {
|
|
1189
|
+
if (unit.unitId === unitId) {
|
|
1190
|
+
return unit;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
export function getDAOUnitMetaData(
|
|
1198
|
+
daos: IDAOData[],
|
|
1199
|
+
symbol: string,
|
|
1200
|
+
unitId: string,
|
|
1201
|
+
): IUnitMetaData | undefined {
|
|
1202
|
+
for (const dao of daos) {
|
|
1203
|
+
if (dao.symbol.toLowerCase() === symbol.toLowerCase()) {
|
|
1204
|
+
for (let i = 0; i < dao.units.length; i++) {
|
|
1205
|
+
const unit = dao.units[i];
|
|
1206
|
+
if (unit.unitId === unitId) {
|
|
1207
|
+
return dao.unitsMetaData[i];
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|