@leofcoin/standards 0.1.7 → 0.2.0

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.
@@ -0,0 +1,8 @@
1
+ # Changesets
2
+
3
+ Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4
+ with multi-package repos, or single-package repos to help you version and publish your code. You can
5
+ find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6
+
7
+ We have a quick list of common questions to get you started engaging with this project in
8
+ [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
3
+ "changelog": "@changesets/cli/changelog",
4
+ "commit": false,
5
+ "fixed": [],
6
+ "linked": [],
7
+ "access": "public",
8
+ "baseBranch": "main",
9
+ "updateInternalDependencies": "patch",
10
+ "ignore": []
11
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @leofcoin/standards
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add voting & tokenReceiver
package/README.md CHANGED
@@ -1,21 +1,23 @@
1
1
  # standards
2
+
2
3
  > Contract standards
3
4
 
4
5
  standards used in leofcoin chain kinda like erc20's but the native coin included
5
6
 
6
7
  ## install
8
+
7
9
  ```sh
8
10
  npm i @leofcoin/standards
9
11
  ```
10
12
 
11
13
  ## usage
14
+
12
15
  ```js
13
- import {Token} from '@leofcoin/standards'
16
+ import { Token } from '@leofcoin/standards'
14
17
 
15
18
  class myCoolToken extends Token {
16
- constructor() {
17
- super('myCoolToken', 'MCT', 18, state?)
19
+ constructor(state) {
20
+ super('myCoolToken', 'MCT', 18, state)
18
21
  }
19
22
  }
20
23
  ```
21
-
@@ -1,4 +1,6 @@
1
1
  export { default as Token } from './token.js';
2
2
  export { default as Roles } from './roles.js';
3
- export { default as Voting } from './voting.js';
3
+ export { default as TokenReceiver } from './token-receiver.js';
4
+ export { default as PublicVoting } from './voting/public-voting.js';
5
+ export { default as PrivateVoting } from './voting/private-voting.js';
4
6
  export * from './helpers.js';
package/exports/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export { default as Token } from './token.js';
2
2
  export { default as Roles } from './roles.js';
3
- export { default as Voting } from './voting.js';
3
+ export { default as TokenReceiver } from './token-receiver.js';
4
+ export { default as PublicVoting } from './public-voting.js';
5
+ export { default as PrivateVoting } from './private-voting.js';
4
6
  export { restoreApprovals, restoreBalances } from './helpers.js';
@@ -0,0 +1,133 @@
1
+ class PrivateVoting {
2
+ #voters;
3
+ #votes;
4
+ #votingDisabled;
5
+ constructor(state) {
6
+ if (state) {
7
+ this.#voters = state.voters;
8
+ this.#votes = state.votes;
9
+ this.#votingDisabled = state.votingDisabled;
10
+ }
11
+ else {
12
+ this.#voters = [msg.sender];
13
+ }
14
+ }
15
+ get votes() {
16
+ return { ...this.#votes };
17
+ }
18
+ get voters() {
19
+ return { ...this.#voters };
20
+ }
21
+ get votingDisabled() {
22
+ return this.#votingDisabled;
23
+ }
24
+ /**
25
+ *
26
+ */
27
+ get state() {
28
+ return { voters: this.#voters, votes: this.#votes, votingDisabled: this.#votingDisabled };
29
+ }
30
+ get inProgress() {
31
+ return Object.entries(this.#votes)
32
+ .filter(([id, vote]) => !vote.finished)
33
+ .map(([id, vote]) => {
34
+ return { ...vote, id };
35
+ });
36
+ }
37
+ /**
38
+ * create vote
39
+ * @param {string} vote
40
+ * @param {string} description
41
+ * @param {number} endTime
42
+ * @param {string} method function to run when agree amount is bigger
43
+ */
44
+ createVote(title, description, endTime, method, args = []) {
45
+ if (!this.canVote(msg.sender))
46
+ throw new Error(`Not allowed to create a vote`);
47
+ const id = crypto.randomUUID();
48
+ this.#votes[id] = {
49
+ title,
50
+ description,
51
+ method,
52
+ endTime,
53
+ args
54
+ };
55
+ }
56
+ canVote(address) {
57
+ return this.#voters.includes(address);
58
+ }
59
+ #enoughVotes(id) {
60
+ return this.#voters.length - 2 <= Object.keys(this.#votes[id]).length;
61
+ }
62
+ #endVoting(voteId) {
63
+ let agree = Object.values(this.#votes[voteId].results).filter((result) => result === 1);
64
+ let disagree = Object.values(this.#votes[voteId].results).filter((result) => result === 0);
65
+ this.#votes[voteId].enoughVotes = this.#enoughVotes(voteId);
66
+ if (agree.length > disagree.length && this.#votes[voteId].enoughVotes)
67
+ this[this.#votes[voteId].method](...this.#votes[voteId].args);
68
+ this.#votes[voteId].finished = true;
69
+ }
70
+ vote(voteId, vote) {
71
+ vote = Number(vote);
72
+ if (vote !== 0 && vote !== 0.5 && vote !== 1)
73
+ throw new Error(`invalid vote value ${vote}`);
74
+ if (!this.#votes[voteId])
75
+ throw new Error(`Nothing found for ${voteId}`);
76
+ const ended = new Date().getTime() > this.#votes[voteId].endTime;
77
+ if (ended && !this.#votes[voteId].finished)
78
+ this.#endVoting(voteId);
79
+ if (ended)
80
+ throw new Error('voting already ended');
81
+ if (!this.canVote(msg.sender))
82
+ throw new Error(`Not allowed to vote`);
83
+ this.#votes[voteId][msg.sender] = vote;
84
+ if (this.#enoughVotes(voteId)) {
85
+ this.#endVoting(voteId);
86
+ }
87
+ }
88
+ #disableVoting() {
89
+ this.#votingDisabled = true;
90
+ this.#voters = [];
91
+ }
92
+ #grantVotingPower(address) {
93
+ this.#voters.push(address);
94
+ }
95
+ #revokeVotingPower(address) {
96
+ this.#voters.splice(this.#voters.indexOf(address));
97
+ }
98
+ disableVoting() {
99
+ if (!this.canVote(msg.sender))
100
+ throw new Error('not a allowed');
101
+ if (this.#voters.length === 1)
102
+ this.#disableVoting();
103
+ else {
104
+ this.createVote(`disable voting`, `Warning this disables all voting features forever`, new Date().getTime() + 172800000, '#disableVoting', []);
105
+ }
106
+ }
107
+ grantVotingPower(address, voteId) {
108
+ if (this.#voters.length === 1 && this.canVote(msg.sender))
109
+ this.#grantVotingPower(address);
110
+ else {
111
+ this.createVote(`grant voting power to ${address}`, `Should we grant ${address} voting power?`, new Date().getTime() + 172800000, '#grantVotingPower', [address]);
112
+ }
113
+ }
114
+ revokeVotingPower(address, voteId) {
115
+ if (!this.canVote(msg.sender))
116
+ throw new Error('not a allowed to vote');
117
+ if (this.#voters.length === 1 && address === msg.sender && !this.#votingDisabled)
118
+ throw new Error('only one voter left, disable voting before making this contract voteless');
119
+ if (this.#voters.length === 1)
120
+ this.#revokeVotingPower(address);
121
+ else {
122
+ this.createVote(`revoke voting power for ${address}`, `Should we revoke ${address} it's voting power?`, new Date().getTime() + 172800000, '#revokeVotingPower', [address]);
123
+ }
124
+ }
125
+ sync() {
126
+ for (const vote of this.inProgress) {
127
+ if (vote.endTime < new Date().getTime())
128
+ this.#endVoting(vote.id);
129
+ }
130
+ }
131
+ }
132
+
133
+ export { PrivateVoting as default };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * allows everybody that has a balance greater or equeal then/to tokenAmountToReceive to vote
3
+ */
4
+ class PublicVoting {
5
+ #votes;
6
+ #votingDisabled;
7
+ #votingDuration = 172800000;
8
+ constructor(state) {
9
+ if (state) {
10
+ this.#votes = state.votes;
11
+ this.#votingDisabled = state.votingDisabled;
12
+ }
13
+ }
14
+ get votes() {
15
+ return { ...this.#votes };
16
+ }
17
+ get votingDuration() {
18
+ return this.#votingDuration;
19
+ }
20
+ get votingDisabled() {
21
+ return this.#votingDisabled;
22
+ }
23
+ /**
24
+ *
25
+ */
26
+ get state() {
27
+ return { votes: this.#votes, votingDisabled: this.#votingDisabled, votingDuration: this.#votingDuration };
28
+ }
29
+ get inProgress() {
30
+ return Object.entries(this.#votes)
31
+ .filter(([id, vote]) => !vote.finished)
32
+ .map(([id, vote]) => {
33
+ return { ...vote, id };
34
+ });
35
+ }
36
+ /**
37
+ * create vote
38
+ * @param {string} vote
39
+ * @param {string} description
40
+ * @param {number} endTime
41
+ * @param {string} method function to run when agree amount is bigger
42
+ */
43
+ createVote(title, description, endTime, method, args = []) {
44
+ if (!this.#canVote())
45
+ throw new Error(`Not allowed to create a vote`);
46
+ const id = crypto.randomUUID();
47
+ this.#votes[id] = {
48
+ title,
49
+ description,
50
+ method,
51
+ endTime,
52
+ args
53
+ };
54
+ }
55
+ #canVote() {
56
+ // @ts-expect-error
57
+ return this._canVote?.();
58
+ }
59
+ #beforeVote() {
60
+ // @ts-expect-error
61
+ return this._beforeVote?.();
62
+ }
63
+ #endVoting(voteId) {
64
+ let agree = Object.values(this.#votes[voteId].results).filter((result) => result === 1);
65
+ let disagree = Object.values(this.#votes[voteId].results).filter((result) => result === 0);
66
+ if (agree.length > disagree.length && this.#votes[voteId].enoughVotes)
67
+ this[this.#votes[voteId].method](...this.#votes[voteId].args);
68
+ this.#votes[voteId].finished = true;
69
+ }
70
+ async vote(voteId, vote) {
71
+ vote = Number(vote);
72
+ if (vote !== 0 && vote !== 0.5 && vote !== 1)
73
+ throw new Error(`invalid vote value ${vote}`);
74
+ if (!this.#votes[voteId])
75
+ throw new Error(`Nothing found for ${voteId}`);
76
+ const ended = new Date().getTime() > this.#votes[voteId].endTime;
77
+ if (ended && !this.#votes[voteId].finished)
78
+ this.#endVoting(voteId);
79
+ if (ended)
80
+ throw new Error('voting already ended');
81
+ if (!this.#canVote())
82
+ throw new Error(`Not allowed to vote`);
83
+ await this.#beforeVote();
84
+ this.#votes[voteId][msg.sender] = vote;
85
+ }
86
+ #disableVoting() {
87
+ this.#votingDisabled = true;
88
+ }
89
+ disableVoting() {
90
+ if (!this.#canVote())
91
+ throw new Error('not a allowed');
92
+ else {
93
+ this.createVote(`disable voting`, `Warning this disables all voting features forever`, new Date().getTime() + this.#votingDuration, '#disableVoting', []);
94
+ }
95
+ }
96
+ _sync() {
97
+ for (const vote of this.inProgress) {
98
+ if (vote.endTime < new Date().getTime())
99
+ this.#endVoting(vote.id);
100
+ }
101
+ }
102
+ }
103
+
104
+ export { PublicVoting as default };
@@ -0,0 +1,47 @@
1
+ import { IPublicVoting } from './voting/interfaces/public-voting.js';
2
+ import PublicVoting from './voting/public-voting.js';
3
+ import { VotingState } from './voting/types.js';
4
+ export interface TokenReceiverState extends VotingState {
5
+ tokenToReceive: address;
6
+ tokenReceiver: address;
7
+ tokenAmountToReceive: typeof BigNumber;
8
+ voteType: 'burn' | 'transfer';
9
+ }
10
+ export default class TokenReceiver extends PublicVoting implements IPublicVoting {
11
+ #private;
12
+ constructor(tokenToReceive: address, tokenAmountToReceive: typeof BigNumber, burns: boolean, state?: TokenReceiverState);
13
+ get tokenToReceive(): string;
14
+ get tokenAmountToReceive(): import("@ethersproject/bignumber").BigNumber;
15
+ get tokenReceiver(): string;
16
+ get state(): {
17
+ tokenReceiver: string;
18
+ tokenToReceive: string;
19
+ tokenAmountToReceive: import("@ethersproject/bignumber").BigNumber;
20
+ voteType: "burn" | "transfer";
21
+ votes: {
22
+ [id: string]: import("./voting/types.js").Vote;
23
+ };
24
+ votingDisabled: boolean;
25
+ votingDuration: number;
26
+ };
27
+ /**
28
+ * check if sender can pay
29
+ * @returns {boolean} promise
30
+ */
31
+ _canVote(): Promise<boolean>;
32
+ _beforeVote(): Promise<any>;
33
+ /**
34
+ * check if sender can pay
35
+ * @returns {boolean} promise
36
+ */
37
+ _payTokenToReceive(): Promise<boolean>;
38
+ /**
39
+ * check if sender can pay
40
+ * @returns {boolean} promise
41
+ */
42
+ _burnTokenToReceive(): Promise<boolean>;
43
+ changeVoteType(type: TokenReceiverState['voteType']): Promise<void>;
44
+ getTokensOut(amount: typeof BigNumber, receiver: address): void;
45
+ changeTokenAmountToReceive(): void;
46
+ changeTokenToReceive(): Promise<void>;
47
+ }
@@ -0,0 +1,128 @@
1
+ import PublicVoting from './public-voting.js';
2
+
3
+ class TokenReceiver extends PublicVoting {
4
+ #tokenToReceive;
5
+ #tokenAmountToReceive;
6
+ #tokenReceiver;
7
+ #voteType = 'transfer';
8
+ constructor(tokenToReceive, tokenAmountToReceive, burns, state) {
9
+ super(state);
10
+ if (state) {
11
+ this.#tokenReceiver = state.tokenReceiver;
12
+ this.#tokenToReceive = state.tokenToReceive;
13
+ this.#tokenAmountToReceive = BigNumber['from'](state.tokenAmountToReceive);
14
+ this.#voteType = state.voteType;
15
+ }
16
+ else {
17
+ this.#tokenReceiver = msg.contract;
18
+ this.#tokenToReceive = tokenToReceive;
19
+ this.#tokenAmountToReceive = BigNumber['from'](tokenAmountToReceive);
20
+ if (burns)
21
+ this.#voteType = 'burn';
22
+ }
23
+ }
24
+ get tokenToReceive() {
25
+ return this.#tokenToReceive;
26
+ }
27
+ get tokenAmountToReceive() {
28
+ return this.#tokenAmountToReceive;
29
+ }
30
+ get tokenReceiver() {
31
+ return this.#tokenReceiver;
32
+ }
33
+ get state() {
34
+ return {
35
+ ...super.state,
36
+ tokenReceiver: this.#tokenReceiver,
37
+ tokenToReceive: this.#tokenToReceive,
38
+ tokenAmountToReceive: this.#tokenAmountToReceive,
39
+ voteType: this.#voteType
40
+ };
41
+ }
42
+ async #canVote() {
43
+ const amount = (await msg.staticCall(this.#tokenToReceive, 'balanceOf', [msg.sender]));
44
+ return amount.gte(this.#tokenAmountToReceive);
45
+ }
46
+ /**
47
+ * check if sender can pay
48
+ * @returns {boolean} promise
49
+ */
50
+ async _canVote() {
51
+ return this.#canVote();
52
+ }
53
+ async #beforeVote() {
54
+ if (this.#voteType === 'burn')
55
+ return msg.staticCall(this.tokenToReceive, 'burn', [this.tokenAmountToReceive]);
56
+ return msg.staticCall(this.tokenToReceive, 'transfer', [msg.sender, this.tokenReceiver, this.tokenAmountToReceive]);
57
+ }
58
+ async _beforeVote() {
59
+ await this.#beforeVote();
60
+ }
61
+ /**
62
+ * check if sender can pay
63
+ * @returns {boolean} promise
64
+ */
65
+ async _payTokenToReceive() {
66
+ return msg.staticCall(this.#tokenToReceive, 'transfer', [
67
+ msg.sender,
68
+ this.#tokenReceiver,
69
+ this.#tokenAmountToReceive
70
+ ]);
71
+ }
72
+ /**
73
+ * check if sender can pay
74
+ * @returns {boolean} promise
75
+ */
76
+ async _burnTokenToReceive() {
77
+ return msg.staticCall(this.#tokenToReceive, 'burn', [this.#tokenAmountToReceive]);
78
+ }
79
+ #changeTokenToReceive(address) {
80
+ this.#tokenToReceive = address;
81
+ }
82
+ #changeTokenAmountToReceive(amount) {
83
+ this.#tokenAmountToReceive = amount;
84
+ }
85
+ #changeVoteType(type) {
86
+ this.#voteType = type;
87
+ }
88
+ #getTokensOut(amount, receiver) {
89
+ return msg.call(this.#tokenReceiver, 'transfer', [this.#tokenReceiver, receiver, amount]);
90
+ }
91
+ async changeVoteType(type) {
92
+ if (!this.#canVote())
93
+ throw new Error('not a allowed');
94
+ if (this.#voteType === 'transfer' && (await this.#balance()).gt(0))
95
+ throw new Error('get tokens out first or they be lost forever');
96
+ else {
97
+ this.createVote(`change the token amount to receive`, `set tokenAmountToReceive`, new Date().getTime() + this.votingDuration, '#changeVoteType', [type]);
98
+ }
99
+ }
100
+ getTokensOut(amount, receiver) {
101
+ if (!this.#canVote())
102
+ throw new Error('not a allowed');
103
+ else {
104
+ this.createVote(`withdraw all tokens`, `withdraw all tokens to ${receiver}`, new Date().getTime() + this.votingDuration, '#getTokensOut', [amount, receiver]);
105
+ }
106
+ }
107
+ changeTokenAmountToReceive() {
108
+ if (!this.#canVote())
109
+ throw new Error('not a allowed');
110
+ else {
111
+ this.createVote(`change the token amount to receive`, `set tokenAmountToReceive`, new Date().getTime() + this.votingDuration, '#changeTokenAmountToReceive', []);
112
+ }
113
+ }
114
+ #balance() {
115
+ return msg.staticCall(this.#tokenToReceive, 'balanceOf', [this.#tokenReceiver]);
116
+ }
117
+ async changeTokenToReceive() {
118
+ if (!this.#canVote())
119
+ throw new Error('not a allowed');
120
+ if (!(await this.#balance()).eq(0) && this.#voteType === 'transfer')
121
+ throw new Error('get tokens out first or they be lost forever');
122
+ else {
123
+ this.createVote(`change the token to receive`, `set tokenToReceive to a new address`, new Date().getTime() + this.votingDuration, '#changeTokenToReceive', []);
124
+ }
125
+ }
126
+ }
127
+
128
+ export { TokenReceiver as default };
@@ -26,9 +26,15 @@ export default class Token extends Roles {
26
26
  get symbol(): string;
27
27
  get holders(): {};
28
28
  get balances(): {};
29
+ get approvals(): {
30
+ [owner: string]: {
31
+ [operator: string]: import("@ethersproject/bignumber").BigNumber;
32
+ };
33
+ };
29
34
  get decimals(): number;
30
35
  mint(to: address, amount: BigNumberish): void;
31
36
  burn(from: address, amount: BigNumberish): void;
37
+ balance(): any;
32
38
  balanceOf(address: address): BigNumberish;
33
39
  setApproval(operator: address, amount: BigNumberish): void;
34
40
  approved(owner: address, operator: address, amount: BigNumberish): boolean;
package/exports/token.js CHANGED
@@ -73,6 +73,9 @@ class Token extends Roles {
73
73
  get balances() {
74
74
  return { ...this.#balances };
75
75
  }
76
+ get approvals() {
77
+ return this.#approvals;
78
+ }
76
79
  get decimals() {
77
80
  return this.#decimals;
78
81
  }
@@ -110,6 +113,9 @@ class Token extends Roles {
110
113
  this.#balances[address] = this.#balances[address].sub(amount);
111
114
  this.#updateHolders(address, previousBalance);
112
115
  }
116
+ balance() {
117
+ return this.#balances[msg.sender];
118
+ }
113
119
  balanceOf(address) {
114
120
  return this.#balances[address];
115
121
  }
@@ -0,0 +1,4 @@
1
+ export interface IPublicVoting {
2
+ _canVote(): Promise<any>;
3
+ _beforeVote(): Promise<any>;
4
+ }
@@ -0,0 +1,32 @@
1
+ import { VoteResult, VoteView, VotingState } from './types.js';
2
+ export interface PrivateVotingState extends VotingState {
3
+ voters: any;
4
+ }
5
+ export default class PrivateVoting {
6
+ #private;
7
+ constructor(state: PrivateVotingState);
8
+ get votes(): {
9
+ [x: string]: import("./types.js").Vote;
10
+ };
11
+ get voters(): any;
12
+ get votingDisabled(): boolean;
13
+ /**
14
+ *
15
+ */
16
+ get state(): {};
17
+ get inProgress(): VoteView[];
18
+ /**
19
+ * create vote
20
+ * @param {string} vote
21
+ * @param {string} description
22
+ * @param {number} endTime
23
+ * @param {string} method function to run when agree amount is bigger
24
+ */
25
+ createVote(title: string, description: string, endTime: EpochTimeStamp, method: string, args?: any[]): void;
26
+ canVote(address: address): any;
27
+ vote(voteId: string, vote: VoteResult): void;
28
+ disableVoting(): void;
29
+ grantVotingPower(address: address, voteId: string): void;
30
+ revokeVotingPower(address: address, voteId: string): void;
31
+ sync(): void;
32
+ }
@@ -0,0 +1,47 @@
1
+ import { VotingState, VoteResult } from './types.js';
2
+ /**
3
+ * allows everybody that has a balance greater or equeal then/to tokenAmountToReceive to vote
4
+ */
5
+ export default class PublicVoting {
6
+ #private;
7
+ constructor(state: VotingState);
8
+ get votes(): {
9
+ [x: string]: import("./types.js").Vote;
10
+ };
11
+ get votingDuration(): number;
12
+ get votingDisabled(): boolean;
13
+ /**
14
+ *
15
+ */
16
+ get state(): {
17
+ votes: {
18
+ [id: string]: import("./types.js").Vote;
19
+ };
20
+ votingDisabled: boolean;
21
+ votingDuration: number;
22
+ };
23
+ get inProgress(): {
24
+ id: string;
25
+ title: string;
26
+ method: string;
27
+ args: any[];
28
+ description: string;
29
+ endTime: number;
30
+ results?: {
31
+ [address: string]: VoteResult;
32
+ };
33
+ finished?: boolean;
34
+ enoughVotes?: boolean;
35
+ }[];
36
+ /**
37
+ * create vote
38
+ * @param {string} vote
39
+ * @param {string} description
40
+ * @param {number} endTime
41
+ * @param {string} method function to run when agree amount is bigger
42
+ */
43
+ createVote(title: string, description: string, endTime: EpochTimeStamp, method: string, args?: any[]): void;
44
+ vote(voteId: string, vote: VoteResult): Promise<void>;
45
+ disableVoting(): void;
46
+ _sync(): void;
47
+ }
@@ -0,0 +1,23 @@
1
+ export type VoteResult = 0 | 0.5 | 1;
2
+ export type Vote = {
3
+ title: string;
4
+ method: string;
5
+ args: any[];
6
+ description: string;
7
+ endTime: EpochTimeStamp;
8
+ results?: {
9
+ [address: string]: VoteResult;
10
+ };
11
+ finished?: boolean;
12
+ enoughVotes?: boolean;
13
+ };
14
+ export interface VotingState {
15
+ votes: {
16
+ [id: string]: Vote;
17
+ };
18
+ votingDisabled: boolean;
19
+ votingDuration: number;
20
+ }
21
+ export interface VoteView extends Vote {
22
+ id: string;
23
+ }
package/package.json CHANGED
@@ -1,56 +1,66 @@
1
- {
2
- "name": "@leofcoin/standards",
3
- "version": "0.1.7",
4
- "description": "Contract standards",
5
- "type": "module",
6
- "exports": {
7
- ".": {
8
- "import": "./exports/index.js",
9
- "types": "./exports/index.d.ts"
10
- },
11
- "./token": {
12
- "import": "./exports/token.js",
13
- "types": "./exports/token.d.ts"
14
- },
15
- "./roles": {
16
- "import": "./exports/roles.js",
17
- "types": "./exports/roles.d.ts"
18
- },
19
- "./voting": {
20
- "import": "./exports/voting.js",
21
- "types": "./exports/voting.d.ts"
22
- },
23
- "./helpers": {
24
- "import": "./exports/helpers.js",
25
- "types": "./exports/helpers.d.ts"
26
- },
27
- "./token.js": "./exports/token.js",
28
- "./roles.js": "./exports/roles.js",
29
- "./voting.js": "./exports/voting.js",
30
- "./helpers.js": "./exports/helpers.js"
31
- },
32
- "scripts": {
33
- "build": "rollup -c",
34
- "test": "echo \"Error: no test specified\" && exit 1"
35
- },
36
- "repository": {
37
- "type": "git",
38
- "url": "git+https://github.com/ArteonToken/standards.git"
39
- },
40
- "keywords": [],
41
- "author": "",
42
- "license": "MIT",
43
- "bugs": {
44
- "url": "https://github.com/ArteonToken/standards/issues"
45
- },
46
- "homepage": "https://github.com/ArteonToken/standards#readme",
47
- "devDependencies": {
48
- "@leofcoin/global-types": "^1.0.0",
49
- "@rollup/plugin-typescript": "^11.0.0",
50
- "rollup": "^3.17.1",
51
- "tslib": "^2.5.0"
52
- },
53
- "dependencies": {
54
- "@ethersproject/bignumber": "^5.7.0"
55
- }
56
- }
1
+ {
2
+ "name": "@leofcoin/standards",
3
+ "version": "0.2.0",
4
+ "description": "Contract standards",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./exports/index.js",
9
+ "types": "./exports/index.d.ts"
10
+ },
11
+ "./token": {
12
+ "import": "./exports/token.js",
13
+ "types": "./exports/token.d.ts"
14
+ },
15
+ "./roles": {
16
+ "import": "./exports/roles.js",
17
+ "types": "./exports/roles.d.ts"
18
+ },
19
+ "./public-voting": {
20
+ "import": "./exports/public-voting.js",
21
+ "types": "./exports/public-voting.d.ts"
22
+ },
23
+ "./private-voting": {
24
+ "import": "./exports/private-voting.js",
25
+ "types": "./exports/private-voting.d.ts"
26
+ },
27
+ "./token-receiver": {
28
+ "import": "./exports/token-receiver.js",
29
+ "types": "./exports/token-receiver.d.ts"
30
+ },
31
+ "./helpers": {
32
+ "import": "./exports/helpers.js",
33
+ "types": "./exports/helpers.d.ts"
34
+ },
35
+ "./token.js": "./exports/token.js",
36
+ "./roles.js": "./exports/roles.js",
37
+ "./public-voting.js": "./exports/public-voting.js",
38
+ "./private-voting.js": "./exports/private-voting.js",
39
+ "./helpers.js": "./exports/helpers.js"
40
+ },
41
+ "scripts": {
42
+ "build": "rollup -c",
43
+ "test": "echo \"Error: no test specified\" && exit 1"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/ArteonToken/standards.git"
48
+ },
49
+ "keywords": [],
50
+ "author": "",
51
+ "license": "MIT",
52
+ "bugs": {
53
+ "url": "https://github.com/ArteonToken/standards/issues"
54
+ },
55
+ "homepage": "https://github.com/ArteonToken/standards#readme",
56
+ "devDependencies": {
57
+ "@leofcoin/global-types": "^1.0.0",
58
+ "@rollup/plugin-typescript": "^11.1.6",
59
+ "rollup": "^4.12.0",
60
+ "tslib": "^2.5.0"
61
+ },
62
+ "dependencies": {
63
+ "@changesets/cli": "^2.27.1",
64
+ "@ethersproject/bignumber": "^5.7.0"
65
+ }
66
+ }
package/rollup.config.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import typescript from '@rollup/plugin-typescript'
2
- import tsConfig from './tsconfig.json' assert { type: 'json' }
3
2
  import { execSync } from 'child_process'
4
3
 
5
4
  // const templates = (await readdir('./src/templates')).map(path => join('./src/templates', path))
@@ -10,11 +9,19 @@ const clean = () => {
10
9
 
11
10
  export default [
12
11
  {
13
- input: ['src/index.ts', 'src/token.ts', 'src/roles.ts', 'src/voting.ts', 'src/helpers.ts'],
12
+ input: [
13
+ 'src/index.ts',
14
+ 'src/token.ts',
15
+ 'src/roles.ts',
16
+ 'src/voting/public-voting.ts',
17
+ 'src/voting/private-voting.ts',
18
+ 'src/helpers.ts',
19
+ 'src/token-receiver.ts'
20
+ ],
14
21
  output: {
15
22
  dir: './exports',
16
23
  format: 'es'
17
24
  },
18
- plugins: [typescript(tsConfig)]
25
+ plugins: [typescript()]
19
26
  }
20
27
  ]
package/src/index.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export { default as Token } from './token.js'
2
2
  export { default as Roles } from './roles.js'
3
- export { default as Voting } from './voting.js'
3
+ export { default as TokenReceiver } from './token-receiver.js'
4
+ export { default as PublicVoting } from './voting/public-voting.js'
5
+ export { default as PrivateVoting } from './voting/private-voting.js'
6
+
4
7
  export * from './helpers.js'
@@ -0,0 +1,176 @@
1
+ import { IPublicVoting } from './voting/interfaces/public-voting.js'
2
+ import PublicVoting from './voting/public-voting.js'
3
+ import { VotingState } from './voting/types.js'
4
+
5
+ export interface TokenReceiverState extends VotingState {
6
+ tokenToReceive: address
7
+ tokenReceiver: address
8
+ tokenAmountToReceive: typeof BigNumber
9
+ voteType: 'burn' | 'transfer'
10
+ }
11
+ export default class TokenReceiver extends PublicVoting implements IPublicVoting {
12
+ #tokenToReceive: address
13
+ #tokenAmountToReceive: typeof BigNumber
14
+ #tokenReceiver: address
15
+ #voteType: TokenReceiverState['voteType'] = 'transfer'
16
+
17
+ constructor(
18
+ tokenToReceive: address,
19
+ tokenAmountToReceive: typeof BigNumber,
20
+ burns: boolean,
21
+ state?: TokenReceiverState
22
+ ) {
23
+ super(state)
24
+ if (state) {
25
+ this.#tokenReceiver = state.tokenReceiver
26
+ this.#tokenToReceive = state.tokenToReceive
27
+ this.#tokenAmountToReceive = BigNumber['from'](state.tokenAmountToReceive)
28
+ this.#voteType = state.voteType
29
+ } else {
30
+ this.#tokenReceiver = msg.contract
31
+ this.#tokenToReceive = tokenToReceive
32
+ this.#tokenAmountToReceive = BigNumber['from'](tokenAmountToReceive)
33
+ if (burns) this.#voteType = 'burn'
34
+ }
35
+ }
36
+
37
+ get tokenToReceive() {
38
+ return this.#tokenToReceive
39
+ }
40
+
41
+ get tokenAmountToReceive() {
42
+ return this.#tokenAmountToReceive
43
+ }
44
+
45
+ get tokenReceiver() {
46
+ return this.#tokenReceiver
47
+ }
48
+
49
+ get state() {
50
+ return {
51
+ ...super.state,
52
+ tokenReceiver: this.#tokenReceiver,
53
+ tokenToReceive: this.#tokenToReceive,
54
+ tokenAmountToReceive: this.#tokenAmountToReceive,
55
+ voteType: this.#voteType
56
+ }
57
+ }
58
+
59
+ async #canVote() {
60
+ const amount = (await msg.staticCall(this.#tokenToReceive, 'balanceOf', [msg.sender])) as typeof BigNumber
61
+ return amount.gte(this.#tokenAmountToReceive)
62
+ }
63
+
64
+ /**
65
+ * check if sender can pay
66
+ * @returns {boolean} promise
67
+ */
68
+ async _canVote(): Promise<boolean> {
69
+ return this.#canVote()
70
+ }
71
+
72
+ async #beforeVote(): Promise<any> {
73
+ if (this.#voteType === 'burn') return msg.staticCall(this.tokenToReceive, 'burn', [this.tokenAmountToReceive])
74
+ return msg.staticCall(this.tokenToReceive, 'transfer', [msg.sender, this.tokenReceiver, this.tokenAmountToReceive])
75
+ }
76
+
77
+ async _beforeVote(): Promise<any> {
78
+ await this.#beforeVote()
79
+ }
80
+
81
+ /**
82
+ * check if sender can pay
83
+ * @returns {boolean} promise
84
+ */
85
+ async _payTokenToReceive(): Promise<boolean> {
86
+ return msg.staticCall(this.#tokenToReceive, 'transfer', [
87
+ msg.sender,
88
+ this.#tokenReceiver,
89
+ this.#tokenAmountToReceive
90
+ ])
91
+ }
92
+
93
+ /**
94
+ * check if sender can pay
95
+ * @returns {boolean} promise
96
+ */
97
+ async _burnTokenToReceive(): Promise<boolean> {
98
+ return msg.staticCall(this.#tokenToReceive, 'burn', [this.#tokenAmountToReceive])
99
+ }
100
+
101
+ #changeTokenToReceive(address: address) {
102
+ this.#tokenToReceive = address
103
+ }
104
+
105
+ #changeTokenAmountToReceive(amount: typeof BigNumber) {
106
+ this.#tokenAmountToReceive = amount
107
+ }
108
+
109
+ #changeVoteType(type: TokenReceiverState['voteType']) {
110
+ this.#voteType = type
111
+ }
112
+
113
+ #getTokensOut(amount: typeof BigNumber, receiver: address) {
114
+ return msg.call(this.#tokenReceiver, 'transfer', [this.#tokenReceiver, receiver, amount])
115
+ }
116
+
117
+ async changeVoteType(type: TokenReceiverState['voteType']) {
118
+ if (!this.#canVote()) throw new Error('not a allowed')
119
+ if (this.#voteType === 'transfer' && (await this.#balance()).gt(0))
120
+ throw new Error('get tokens out first or they be lost forever')
121
+ else {
122
+ this.createVote(
123
+ `change the token amount to receive`,
124
+ `set tokenAmountToReceive`,
125
+ new Date().getTime() + this.votingDuration,
126
+ '#changeVoteType',
127
+ [type]
128
+ )
129
+ }
130
+ }
131
+
132
+ getTokensOut(amount: typeof BigNumber, receiver: address) {
133
+ if (!this.#canVote()) throw new Error('not a allowed')
134
+ else {
135
+ this.createVote(
136
+ `withdraw all tokens`,
137
+ `withdraw all tokens to ${receiver}`,
138
+ new Date().getTime() + this.votingDuration,
139
+ '#getTokensOut',
140
+ [amount, receiver]
141
+ )
142
+ }
143
+ }
144
+
145
+ changeTokenAmountToReceive() {
146
+ if (!this.#canVote()) throw new Error('not a allowed')
147
+ else {
148
+ this.createVote(
149
+ `change the token amount to receive`,
150
+ `set tokenAmountToReceive`,
151
+ new Date().getTime() + this.votingDuration,
152
+ '#changeTokenAmountToReceive',
153
+ []
154
+ )
155
+ }
156
+ }
157
+
158
+ #balance(): Promise<typeof BigNumber> {
159
+ return msg.staticCall(this.#tokenToReceive, 'balanceOf', [this.#tokenReceiver])
160
+ }
161
+
162
+ async changeTokenToReceive() {
163
+ if (!this.#canVote()) throw new Error('not a allowed')
164
+ if (!(await this.#balance()).eq(0) && this.#voteType === 'transfer')
165
+ throw new Error('get tokens out first or they be lost forever')
166
+ else {
167
+ this.createVote(
168
+ `change the token to receive`,
169
+ `set tokenToReceive to a new address`,
170
+ new Date().getTime() + this.votingDuration,
171
+ '#changeTokenToReceive',
172
+ []
173
+ )
174
+ }
175
+ }
176
+ }
package/src/token.ts CHANGED
@@ -90,6 +90,10 @@ export default class Token extends Roles {
90
90
  return { ...this.#balances }
91
91
  }
92
92
 
93
+ get approvals() {
94
+ return this.#approvals
95
+ }
96
+
93
97
  get decimals() {
94
98
  return this.#decimals
95
99
  }
@@ -132,6 +136,10 @@ export default class Token extends Roles {
132
136
  this.#updateHolders(address, previousBalance)
133
137
  }
134
138
 
139
+ balance() {
140
+ return this.#balances[msg.sender]
141
+ }
142
+
135
143
  balanceOf(address: address): BigNumberish {
136
144
  return this.#balances[address]
137
145
  }
@@ -0,0 +1,4 @@
1
+ export interface IPublicVoting {
2
+ _canVote(): Promise<any>
3
+ _beforeVote(): Promise<any>
4
+ }
@@ -1,33 +1,15 @@
1
- export type VoteResult = 0 | 0.5 | 1
2
-
3
- export type Vote = {
4
- title: string
5
- method: string
6
- args: any[]
7
- description: string
8
- endTime: EpochTimeStamp
9
- results?: { [address: address]: VoteResult }
10
- finished?: boolean
11
- enoughVotes?: boolean
12
- }
1
+ import { VoteResult, VoteView, VotingState } from './types.js'
13
2
 
14
- export type VotingState = {
15
- voters: address[]
16
- votes: {
17
- [id: string]: Vote
18
- }
19
- votingDisabled: boolean
20
- }
21
- export interface VoteView extends Vote {
22
- id: string
3
+ export interface PrivateVotingState extends VotingState {
4
+ voters
23
5
  }
24
6
 
25
- export default class Voting {
26
- #voters: VotingState['voters']
27
- #votes: VotingState['votes']
7
+ export default class PrivateVoting {
8
+ #voters: PrivateVotingState['voters']
9
+ #votes: PrivateVotingState['votes']
28
10
  #votingDisabled: boolean
29
11
 
30
- constructor(state: VotingState) {
12
+ constructor(state: PrivateVotingState) {
31
13
  if (state) {
32
14
  this.#voters = state.voters
33
15
  this.#votes = state.votes
@@ -0,0 +1,116 @@
1
+ import { VotingState, VoteResult } from './types.js'
2
+
3
+ /**
4
+ * allows everybody that has a balance greater or equeal then/to tokenAmountToReceive to vote
5
+ */
6
+ export default class PublicVoting {
7
+ #votes: VotingState['votes']
8
+ #votingDisabled: boolean
9
+ #votingDuration: number = 172800000
10
+
11
+ constructor(state: VotingState) {
12
+ if (state) {
13
+ this.#votes = state.votes
14
+ this.#votingDisabled = state.votingDisabled
15
+ }
16
+ }
17
+
18
+ get votes() {
19
+ return { ...this.#votes }
20
+ }
21
+
22
+ get votingDuration() {
23
+ return this.#votingDuration
24
+ }
25
+
26
+ get votingDisabled() {
27
+ return this.#votingDisabled
28
+ }
29
+
30
+ /**
31
+ *
32
+ */
33
+ get state() {
34
+ return { votes: this.#votes, votingDisabled: this.#votingDisabled, votingDuration: this.#votingDuration }
35
+ }
36
+
37
+ get inProgress() {
38
+ return Object.entries(this.#votes)
39
+ .filter(([id, vote]) => !vote.finished)
40
+ .map(([id, vote]) => {
41
+ return { ...vote, id }
42
+ })
43
+ }
44
+ /**
45
+ * create vote
46
+ * @param {string} vote
47
+ * @param {string} description
48
+ * @param {number} endTime
49
+ * @param {string} method function to run when agree amount is bigger
50
+ */
51
+
52
+ createVote(title: string, description: string, endTime: EpochTimeStamp, method: string, args: any[] = []) {
53
+ if (!this.#canVote()) throw new Error(`Not allowed to create a vote`)
54
+ const id = crypto.randomUUID()
55
+ this.#votes[id] = {
56
+ title,
57
+ description,
58
+ method,
59
+ endTime,
60
+ args
61
+ }
62
+ }
63
+
64
+ #canVote() {
65
+ // @ts-expect-error
66
+ return this._canVote?.()
67
+ }
68
+
69
+ #beforeVote() {
70
+ // @ts-expect-error
71
+ return this._beforeVote?.()
72
+ }
73
+
74
+ #endVoting(voteId) {
75
+ let agree = Object.values(this.#votes[voteId].results).filter((result) => result === 1)
76
+ let disagree = Object.values(this.#votes[voteId].results).filter((result) => result === 0)
77
+ if (agree.length > disagree.length && this.#votes[voteId].enoughVotes)
78
+ this[this.#votes[voteId].method](...this.#votes[voteId].args)
79
+ this.#votes[voteId].finished = true
80
+ }
81
+
82
+ async vote(voteId: string, vote: VoteResult) {
83
+ vote = Number(vote) as VoteResult
84
+ if (vote !== 0 && vote !== 0.5 && vote !== 1) throw new Error(`invalid vote value ${vote}`)
85
+ if (!this.#votes[voteId]) throw new Error(`Nothing found for ${voteId}`)
86
+ const ended = new Date().getTime() > this.#votes[voteId].endTime
87
+ if (ended && !this.#votes[voteId].finished) this.#endVoting(voteId)
88
+ if (ended) throw new Error('voting already ended')
89
+ if (!this.#canVote()) throw new Error(`Not allowed to vote`)
90
+ await this.#beforeVote()
91
+ this.#votes[voteId][msg.sender] = vote
92
+ }
93
+
94
+ #disableVoting() {
95
+ this.#votingDisabled = true
96
+ }
97
+
98
+ disableVoting() {
99
+ if (!this.#canVote()) throw new Error('not a allowed')
100
+ else {
101
+ this.createVote(
102
+ `disable voting`,
103
+ `Warning this disables all voting features forever`,
104
+ new Date().getTime() + this.#votingDuration,
105
+ '#disableVoting',
106
+ []
107
+ )
108
+ }
109
+ }
110
+
111
+ _sync() {
112
+ for (const vote of this.inProgress) {
113
+ if (vote.endTime < new Date().getTime()) this.#endVoting(vote.id)
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,24 @@
1
+ export type VoteResult = 0 | 0.5 | 1
2
+
3
+ export type Vote = {
4
+ title: string
5
+ method: string
6
+ args: any[]
7
+ description: string
8
+ endTime: EpochTimeStamp
9
+ results?: { [address: string]: VoteResult }
10
+ finished?: boolean
11
+ enoughVotes?: boolean
12
+ }
13
+
14
+ export interface VotingState {
15
+ votes: {
16
+ [id: string]: Vote
17
+ }
18
+ votingDisabled: boolean
19
+ votingDuration: number
20
+ }
21
+
22
+ export interface VoteView extends Vote {
23
+ id: string
24
+ }
@@ -0,0 +1,6 @@
1
+ import Voting from './../exports/public-voting.js'
2
+
3
+ const voting = new Voting('0x0', '1000')
4
+
5
+ console.log(voting)
6
+ console.log(voting.votingDuration)
package/tsconfig.json CHANGED
@@ -3,12 +3,14 @@
3
3
  "module": "NodeNext",
4
4
  "target": "es2022",
5
5
  "outDir": "./exports",
6
- "moduleResolution":"NodeNext",
6
+ "moduleResolution": "NodeNext",
7
7
  "declaration": true,
8
8
  "declarationDir": "./exports"
9
9
  },
10
10
  "include": [
11
11
  "./src/*",
12
- "./node_modules/@leofcoin/global-types/*"
12
+ "./node_modules/@leofcoin/global-types/*",
13
+ "src/voting/private-voting.ts",
14
+ "src/voting/public-voting.ts"
13
15
  ]
14
- }
16
+ }