@msafe/sui3-sdk 0.0.21 → 0.0.23
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/dist/index.cjs +106 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +107 -97
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/backend/BackendImpl.ts +88 -88
- package/src/backend/interface.ts +0 -1
- package/src/core/MSafeAccount.ts +31 -14
- package/src/types/msafe.ts +2 -0
- package/src/utils/format.ts +7 -0
|
@@ -31,9 +31,10 @@ import {
|
|
|
31
31
|
} from '@msafe/sui3-utils';
|
|
32
32
|
import { SerializedSignature } from '@mysten/sui.js/cryptography';
|
|
33
33
|
import { PublicKey } from '@mysten/sui.js/src/cryptography';
|
|
34
|
-
import axios from 'axios';
|
|
34
|
+
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
35
35
|
|
|
36
36
|
import { IBackend } from '@/backend/interface';
|
|
37
|
+
import { addPrefix } from '@/utils';
|
|
37
38
|
|
|
38
39
|
export class BackendImpl implements IBackend {
|
|
39
40
|
private _token: JWTToken;
|
|
@@ -41,18 +42,15 @@ export class BackendImpl implements IBackend {
|
|
|
41
42
|
constructor(private readonly apiURL: string) {}
|
|
42
43
|
|
|
43
44
|
async authSign(input: IAuthLoginReq): Promise<JWTToken> {
|
|
44
|
-
const res = await
|
|
45
|
-
|
|
46
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
47
|
-
throw new Error(`invalid authSign return: ${res}`);
|
|
48
|
-
}
|
|
45
|
+
const res = await this.post<IAuthLoginResp>(`/auth/login`, input);
|
|
46
|
+
|
|
49
47
|
this._token = res.data.accessToken;
|
|
50
48
|
return this._token;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
async verifyToken(jwt: JWTToken): Promise<boolean> {
|
|
54
52
|
try {
|
|
55
|
-
const res = await
|
|
53
|
+
const res = await this.get(`/auth`, { headers: this.headers(jwt) });
|
|
56
54
|
return res.status === 200;
|
|
57
55
|
} catch (_) {
|
|
58
56
|
return false;
|
|
@@ -71,15 +69,10 @@ export class BackendImpl implements IBackend {
|
|
|
71
69
|
const query: IGetPublicKeyBatchQuery = {
|
|
72
70
|
userAddressList: addresses,
|
|
73
71
|
};
|
|
74
|
-
const res = await
|
|
72
|
+
const res = await this.get<IGetPublicKeyBatchResp>(`/user/public-keys`, {
|
|
75
73
|
params: query,
|
|
76
74
|
headers: this.headers(),
|
|
77
75
|
});
|
|
78
|
-
|
|
79
|
-
// TODO unify response struct
|
|
80
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
81
|
-
throw new Error(`invalid getPublicKeyBatch return: ${res}`);
|
|
82
|
-
}
|
|
83
76
|
return res.data?.map((publicKeyWithSchema) =>
|
|
84
77
|
publicKeyWithSchema ? PublicKeySerde.de(publicKeyWithSchema) : undefined,
|
|
85
78
|
);
|
|
@@ -89,25 +82,17 @@ export class BackendImpl implements IBackend {
|
|
|
89
82
|
const q: IGetMSafeQuery = {
|
|
90
83
|
msafeAddress,
|
|
91
84
|
};
|
|
92
|
-
const res = await
|
|
85
|
+
const res = await this.get<IMSafeInfoResp>(`/msafe`, {
|
|
93
86
|
params: q,
|
|
94
87
|
headers: this.headers(),
|
|
95
88
|
});
|
|
96
|
-
// TODO unify response struct
|
|
97
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
98
|
-
throw new Error(`invalid getPublicKeyBatch return: ${res}`);
|
|
99
|
-
}
|
|
100
89
|
return BackendImpl.toMSafeConfig(res.data);
|
|
101
90
|
}
|
|
102
91
|
|
|
103
92
|
async getUserInfo(): Promise<IUserInfoResp> {
|
|
104
|
-
const userRes = await
|
|
93
|
+
const userRes = await this.get<IUserInfoResp>(`/user`, {
|
|
105
94
|
headers: this.headers(),
|
|
106
95
|
});
|
|
107
|
-
// TODO unify response struct
|
|
108
|
-
if (userRes.status !== 200 && userRes.status !== 201) {
|
|
109
|
-
throw new Error(`invalid getPublicKeyBatch return: ${userRes}`);
|
|
110
|
-
}
|
|
111
96
|
return userRes.data;
|
|
112
97
|
}
|
|
113
98
|
|
|
@@ -124,13 +109,10 @@ export class BackendImpl implements IBackend {
|
|
|
124
109
|
}
|
|
125
110
|
: {}),
|
|
126
111
|
};
|
|
127
|
-
const res = await
|
|
112
|
+
const res = await this.get<IPagedResult<IMSafeInfoResp>>(`/msafe/owned`, {
|
|
128
113
|
params: q,
|
|
129
114
|
headers: this.headers(),
|
|
130
115
|
});
|
|
131
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
132
|
-
throw new Error(`invalid getOwnedMSafeByStatus return: ${res}`);
|
|
133
|
-
}
|
|
134
116
|
return {
|
|
135
117
|
data: res.data.data.map(BackendImpl.toMSafeConfig),
|
|
136
118
|
meta: res.data.meta,
|
|
@@ -139,85 +121,61 @@ export class BackendImpl implements IBackend {
|
|
|
139
121
|
|
|
140
122
|
async updateMSafeStatus(input: { msafeAddress: string; status: UserMSafeStatus }): Promise<void> {
|
|
141
123
|
const p: IUpdateMSafeStatusReq = input;
|
|
142
|
-
|
|
143
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
144
|
-
throw new Error(`Invalid updateMSafeStatus return: ${res}`);
|
|
145
|
-
}
|
|
124
|
+
await this.post(`/msafe/status`, p, { headers: this.headers() });
|
|
146
125
|
}
|
|
147
126
|
|
|
148
127
|
async getPendingTransactions(input: IGetPendingTransactionRequest): Promise<GetPendingTransactionResponse> {
|
|
149
|
-
const res = await
|
|
128
|
+
const res = await this.get<GetPendingTransactionResponse>(`/transaction/pending`, {
|
|
150
129
|
params: input,
|
|
151
130
|
headers: this.headers(),
|
|
152
131
|
});
|
|
153
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
154
|
-
throw new Error(`invalid getPublicKeyBatch return: ${res}`);
|
|
155
|
-
}
|
|
156
132
|
return res.data;
|
|
157
133
|
}
|
|
158
134
|
|
|
159
135
|
async getHistoryTransactions(input: IGetHistoryTransactionsRequest): Promise<GetHistoryTransactionsResponse> {
|
|
160
|
-
const res = await
|
|
136
|
+
const res = await this.get<GetHistoryTransactionsResponse>(`/transaction/history`, {
|
|
161
137
|
params: input,
|
|
162
138
|
headers: this.headers(),
|
|
163
139
|
});
|
|
164
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
165
|
-
throw new Error(`invalid getCurrentSequenceNumber return: ${res}`);
|
|
166
|
-
}
|
|
167
140
|
return res.data;
|
|
168
141
|
}
|
|
169
142
|
|
|
170
143
|
async getFutureIntentions(input: IGetIntentionsRequest): Promise<GetTransactionIntentionsResponse> {
|
|
171
|
-
const res = await
|
|
144
|
+
const res = await this.get<GetTransactionIntentionsResponse>(`/transaction/intention`, {
|
|
172
145
|
params: input,
|
|
173
146
|
headers: this.headers(),
|
|
174
147
|
});
|
|
175
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
176
|
-
throw new Error(`invalid getCurrentSequenceNumber return: ${res}`);
|
|
177
|
-
}
|
|
178
148
|
return res.data;
|
|
179
149
|
}
|
|
180
150
|
|
|
181
151
|
async getCurrentSequenceNumber(address: string): Promise<number> {
|
|
182
|
-
const res = await
|
|
152
|
+
const res = await this.get<number>(`/transaction/sn/current`, {
|
|
183
153
|
params: {
|
|
184
154
|
msafeAddress: address,
|
|
185
155
|
},
|
|
186
156
|
headers: this.headers(),
|
|
187
157
|
});
|
|
188
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
189
|
-
throw new Error(`invalid getCurrentSequenceNumber return: ${res}`);
|
|
190
|
-
}
|
|
191
158
|
return res.data;
|
|
192
159
|
}
|
|
193
160
|
|
|
194
161
|
async getNextSequenceNumber(address: string): Promise<number> {
|
|
195
|
-
const res = await
|
|
162
|
+
const res = await this.get<number>(`/transaction/sn/next`, {
|
|
196
163
|
params: {
|
|
197
164
|
msafeAddress: address,
|
|
198
165
|
},
|
|
199
166
|
headers: this.headers(),
|
|
200
167
|
});
|
|
201
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
202
|
-
throw new Error(`invalid getNextSequenceNumber return: ${res}`);
|
|
203
|
-
}
|
|
204
168
|
return res.data;
|
|
205
169
|
}
|
|
206
170
|
|
|
207
171
|
async createMSafeAccount(input: ICreateMSafeReq): Promise<void> {
|
|
208
|
-
|
|
172
|
+
await this.post(`/msafe/create`, input, {
|
|
209
173
|
headers: this.headers(),
|
|
210
174
|
});
|
|
211
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
212
|
-
throw new Error(`invalid createMSafeAccount return: ${res}`);
|
|
213
|
-
}
|
|
214
175
|
}
|
|
215
176
|
|
|
216
177
|
async proposeIntention(input: IProposeIntentionRequest): Promise<void> {
|
|
217
|
-
|
|
218
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
219
|
-
throw new Error(`invalid proposeIntention return: ${res}`);
|
|
220
|
-
}
|
|
178
|
+
await this.post(`/transaction/intention`, input, { headers: this.headers() });
|
|
221
179
|
}
|
|
222
180
|
|
|
223
181
|
// TODO later
|
|
@@ -233,58 +191,35 @@ export class BackendImpl implements IBackend {
|
|
|
233
191
|
}
|
|
234
192
|
|
|
235
193
|
async rejectCurrentTx(input: IVoteTransactionRequest) {
|
|
236
|
-
|
|
194
|
+
await this.post(`/transaction/pending/reject`, input, {
|
|
237
195
|
headers: this.headers(),
|
|
238
196
|
});
|
|
239
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
240
|
-
throw new Error(`invalid voteForTransaction return: ${res}`);
|
|
241
|
-
}
|
|
242
197
|
}
|
|
243
198
|
|
|
244
199
|
async voteForTransaction(input: IVoteTransactionRequest) {
|
|
245
|
-
|
|
200
|
+
await this.post(`/transaction/pending/vote`, input, {
|
|
246
201
|
headers: this.headers(),
|
|
247
202
|
});
|
|
248
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
249
|
-
throw new Error(`invalid voteForTransaction return: ${res}`);
|
|
250
|
-
}
|
|
251
203
|
}
|
|
252
204
|
|
|
253
205
|
async buildNextIntentionAndAddToPending(input: IBuildTransactionRequest) {
|
|
254
|
-
|
|
255
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
256
|
-
throw new Error(`invalid buildNextIntentionAndAddToPending return: ${res}`);
|
|
257
|
-
}
|
|
206
|
+
await this.post(`/transaction/pending/build`, input, { headers: this.headers() });
|
|
258
207
|
}
|
|
259
208
|
|
|
260
209
|
async skipNextFailedIntention(input: ISkipIntentionRequest) {
|
|
261
|
-
|
|
262
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
263
|
-
throw new Error(`invalid skipNextFailedIntention return: ${res}`);
|
|
264
|
-
}
|
|
210
|
+
await this.post(`/transaction/pending/skip`, input, { headers: this.headers() });
|
|
265
211
|
}
|
|
266
212
|
|
|
267
213
|
async getAddressBookEntries(pagination?: IPageOptions): Promise<IGetAddressBookResult> {
|
|
268
|
-
const res = await
|
|
214
|
+
const res = await this.get<IGetAddressBookResult>(`/address-book`, {
|
|
269
215
|
headers: this.headers(),
|
|
270
216
|
params: pagination,
|
|
271
217
|
});
|
|
272
|
-
if (res.status !== 200) {
|
|
273
|
-
throw new Error(`Invalid address-book return: ${res}`);
|
|
274
|
-
}
|
|
275
218
|
return res.data;
|
|
276
219
|
}
|
|
277
220
|
|
|
278
221
|
async updateAddressBook(input: { updates: UpdateAddressBookEntry[]; signature: SerializedSignature }) {
|
|
279
|
-
|
|
280
|
-
if (res.status !== 200 && res.status !== 201) {
|
|
281
|
-
throw new Error(`invalid updateAddressBook return: ${res}`);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
286
|
-
async processExecutedTransaction(_digest: string) {
|
|
287
|
-
return undefined;
|
|
222
|
+
await this.post(`/address-book`, input, { headers: this.headers() });
|
|
288
223
|
}
|
|
289
224
|
|
|
290
225
|
private headers(token?: string) {
|
|
@@ -304,4 +239,69 @@ export class BackendImpl implements IBackend {
|
|
|
304
239
|
})),
|
|
305
240
|
};
|
|
306
241
|
}
|
|
242
|
+
|
|
243
|
+
private async get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R> {
|
|
244
|
+
const fullUrl = this.getFullUrl(url);
|
|
245
|
+
try {
|
|
246
|
+
return await axios.get<T, R, D>(fullUrl, config);
|
|
247
|
+
} catch (e: any) {
|
|
248
|
+
throw BackendError.fromError(e) ?? e;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private async post<T = any, R = AxiosResponse<T>, D = any>(
|
|
253
|
+
url: string,
|
|
254
|
+
data?: D,
|
|
255
|
+
config?: AxiosRequestConfig<D>,
|
|
256
|
+
): Promise<R> {
|
|
257
|
+
const fullUrl = this.getFullUrl(url);
|
|
258
|
+
try {
|
|
259
|
+
return await axios.post<T, R, D>(fullUrl, data, config);
|
|
260
|
+
} catch (e: unknown) {
|
|
261
|
+
throw BackendError.fromError(e) ?? e;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private getFullUrl(url: string) {
|
|
266
|
+
return url.startsWith(this.apiURL) ? url : `${this.apiURL}${addPrefix(url, '/')}`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export class BackendError extends Error {
|
|
271
|
+
constructor(public readonly e: AxiosError) {
|
|
272
|
+
super();
|
|
273
|
+
Error.captureStackTrace(this, this.constructor);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public readonly name: 'Backend';
|
|
277
|
+
|
|
278
|
+
static fromError(e: unknown) {
|
|
279
|
+
if (axios.isAxiosError(e) && e?.response?.data && 'message' in e.response.data) {
|
|
280
|
+
return new BackendError(e as AxiosError);
|
|
281
|
+
}
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
get status() {
|
|
286
|
+
return this.e.response?.status ?? undefined;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
get message(): string {
|
|
290
|
+
return `Request to ${this.endpoint} failed: ${this.status} ${this.respMessage() ?? 'Unknown resp'}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private respMessage() {
|
|
294
|
+
if (!this.e.response?.data) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
return (this.e.response?.data as any).message ?? undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
get endpoint() {
|
|
301
|
+
return this.e.config?.url ?? '';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
toString() {
|
|
305
|
+
return this.message;
|
|
306
|
+
}
|
|
307
307
|
}
|
package/src/backend/interface.ts
CHANGED
|
@@ -57,7 +57,6 @@ export interface IBackend {
|
|
|
57
57
|
voteForTransaction(input: IVoteTransactionRequest): Promise<void>;
|
|
58
58
|
buildNextIntentionAndAddToPending(input: IBuildTransactionRequest): Promise<void>;
|
|
59
59
|
skipNextFailedIntention(input: ISkipIntentionRequest): Promise<void>;
|
|
60
|
-
processExecutedTransaction(digest: string): Promise<void>;
|
|
61
60
|
|
|
62
61
|
getAddressBookEntries(pagination?: IPageOptions): Promise<IGetAddressBookResult>;
|
|
63
62
|
updateAddressBook(input: { updates: UpdateAddressBookEntry[]; signature: SerializedSignature }): Promise<void>;
|
package/src/core/MSafeAccount.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
TxIntention,
|
|
12
12
|
buildObjectTransferTxb,
|
|
13
13
|
buildRejectTxb,
|
|
14
|
+
isSameAddress,
|
|
14
15
|
} from '@msafe/sui3-utils';
|
|
15
16
|
import { SuiObjectData } from '@mysten/sui.js/client';
|
|
16
17
|
import { SerializedSignature } from '@mysten/sui.js/cryptography';
|
|
@@ -76,6 +77,7 @@ export class MSafeAccount {
|
|
|
76
77
|
return getAllOwnedObjects(this.suiClient, this.address, filterCoinObjectOptions);
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// TODO: Calculate the votes
|
|
79
81
|
async pendingTransaction(): Promise<PendingTx | undefined> {
|
|
80
82
|
const res = await this.backend.getPendingTransactions({ msafeAddress: this.address });
|
|
81
83
|
if (!res.pending) {
|
|
@@ -98,13 +100,16 @@ export class MSafeAccount {
|
|
|
98
100
|
sequenceNumber: pendingTx.sequenceNumber,
|
|
99
101
|
payload: pendingTx.payload,
|
|
100
102
|
votes: pendingTx.votes,
|
|
103
|
+
approvalWeight: this.calculateWeightFromVotes(pendingTx.votes),
|
|
101
104
|
msafeAddress: pendingTx.msafeAddress,
|
|
102
105
|
rejectDigest: rejectPending?.digest ?? '',
|
|
103
106
|
rejectPayload: rejectPending?.payload ?? '',
|
|
104
107
|
rejectVotes: rejectPending?.votes ?? [],
|
|
108
|
+
rejectWeight: this.calculateWeightFromVotes(rejectPending?.votes ?? []),
|
|
105
109
|
};
|
|
106
110
|
}
|
|
107
111
|
|
|
112
|
+
// TODO: Calculate the votes.
|
|
108
113
|
async historyTransaction(pagination?: Pagination) {
|
|
109
114
|
return this.backend.getHistoryTransactions({
|
|
110
115
|
msafeAddress: this.address,
|
|
@@ -241,28 +246,24 @@ export class MSafeAccount {
|
|
|
241
246
|
};
|
|
242
247
|
}
|
|
243
248
|
|
|
244
|
-
async executePendingTx(pending: PendingTx) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
} else if (pending.rejectVotes && pending.rejectPayload && pending.rejectVotes?.length >= this.info.threshold) {
|
|
251
|
-
gotSigs = new Map(pending.rejectVotes.map((vote) => [vote.userAddress, vote.signature]));
|
|
252
|
-
payload = pending.rejectPayload;
|
|
253
|
-
} else {
|
|
254
|
-
throw new Error('Not enough signatures');
|
|
249
|
+
async executePendingTx(pending: PendingTx, isRejectTx: boolean = false) {
|
|
250
|
+
const votes = isRejectTx ? pending.rejectVotes : pending.votes;
|
|
251
|
+
const payload = isRejectTx ? pending.rejectPayload : pending.payload;
|
|
252
|
+
|
|
253
|
+
if (this.calculateWeightFromVotes(votes) < this.info.threshold) {
|
|
254
|
+
throw new Error('Not enough signature');
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
const
|
|
257
|
+
const sortedSigs: SerializedSignature[] = [];
|
|
258
|
+
const gotSigs = new Map(votes.map((vote) => [vote.userAddress, vote.signature]));
|
|
258
259
|
for (let i = 0; i < this.info.owners.length; i++) {
|
|
259
260
|
const owner = this.info.owners[i];
|
|
260
261
|
const signature = gotSigs.get(owner.address);
|
|
261
262
|
if (signature) {
|
|
262
|
-
|
|
263
|
+
sortedSigs.push(signature);
|
|
263
264
|
}
|
|
264
265
|
}
|
|
265
|
-
const multiSignature = this.multiSig.combinePartialSignatures(
|
|
266
|
+
const multiSignature = this.multiSig.combinePartialSignatures(sortedSigs);
|
|
266
267
|
return this.suiClient.executeTransactionBlock({
|
|
267
268
|
transactionBlock: HexToUint8Array(payload),
|
|
268
269
|
signature: multiSignature,
|
|
@@ -270,6 +271,22 @@ export class MSafeAccount {
|
|
|
270
271
|
});
|
|
271
272
|
}
|
|
272
273
|
|
|
274
|
+
private calculateWeightFromVotes(votes: { userAddress: string }[]) {
|
|
275
|
+
return this.calculateWeight(votes.map((vote) => vote.userAddress));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private calculateWeight(addressList: string[]) {
|
|
279
|
+
let gotWeight = 0;
|
|
280
|
+
|
|
281
|
+
for (let i = 0; i < addressList.length; i++) {
|
|
282
|
+
const found = this.info.owners.find((owner) => isSameAddress(owner.address, addressList[i]));
|
|
283
|
+
if (found) {
|
|
284
|
+
gotWeight += found.weight;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return gotWeight;
|
|
288
|
+
}
|
|
289
|
+
|
|
273
290
|
get address() {
|
|
274
291
|
return this.multiSig.address;
|
|
275
292
|
}
|
package/src/types/msafe.ts
CHANGED
|
@@ -12,9 +12,11 @@ export interface PendingTx {
|
|
|
12
12
|
creator: string;
|
|
13
13
|
createdAt: Date;
|
|
14
14
|
sequenceNumber: number;
|
|
15
|
+
approvalWeight: number;
|
|
15
16
|
votes: PendingVote[];
|
|
16
17
|
rejectDigest: string;
|
|
17
18
|
rejectPayload: string;
|
|
19
|
+
rejectWeight: number;
|
|
18
20
|
rejectVotes: PendingVote[];
|
|
19
21
|
}
|
|
20
22
|
|