@msafe/sui3-sdk 0.0.13 → 0.0.14
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 +256 -374
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -145
- package/dist/index.d.ts +76 -145
- package/dist/index.js +260 -369
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/backend/BackendImpl.ts +106 -111
- package/src/backend/interface.ts +24 -14
- package/src/core/AddressBookSDK.ts +4 -5
- package/src/core/CreateHelper.ts +18 -32
- package/src/core/InvitationSDK.ts +15 -0
- package/src/core/MSafeAccount.ts +30 -17
- package/src/core/MSafeClient.ts +15 -8
- package/src/core/PublicKeyHelper.ts +3 -0
- package/src/core/index.ts +0 -1
- package/src/globals/MSafeGlobals.ts +1 -1
- package/src/globals/const.ts +17 -45
- package/src/types/create.ts +11 -0
- package/src/types/index.ts +1 -4
- package/src/types/msafe.ts +2 -2
- package/src/utils/crypto.ts +1 -32
- package/src/utils/sui.ts +2 -2
- package/src/backend/CoreDatabase.ts +0 -55
- package/src/backend/PseudoBackend.ts +0 -936
- package/src/core/MessageHelper.ts +0 -95
- package/src/types/address-book.ts +0 -32
- package/src/types/backend.ts +0 -19
- package/src/types/creation.ts +0 -19
- package/src/types/pagination.ts +0 -15
|
@@ -1,936 +0,0 @@
|
|
|
1
|
-
import 'reflect-metadata';
|
|
2
|
-
import {
|
|
3
|
-
User,
|
|
4
|
-
UserMSafe,
|
|
5
|
-
HistoryTransaction,
|
|
6
|
-
MSafe,
|
|
7
|
-
TransactionIntention,
|
|
8
|
-
UserVote,
|
|
9
|
-
PendingTransaction,
|
|
10
|
-
} from '@msafe/sui3-model/core';
|
|
11
|
-
import {
|
|
12
|
-
CreateMSafeAccountInfoRequest,
|
|
13
|
-
deserializePublicKey,
|
|
14
|
-
MSafeAccountInfo,
|
|
15
|
-
MultisigAccountManager,
|
|
16
|
-
OwnerWithWeightPK,
|
|
17
|
-
UserWithOwnedMSafe,
|
|
18
|
-
HistoryTransaction as HistoryTx,
|
|
19
|
-
} from '@msafe/sui3-utils';
|
|
20
|
-
import { SuiClient } from '@mysten/sui.js/client';
|
|
21
|
-
import { PublicKey, SerializedSignature } from '@mysten/sui.js/cryptography';
|
|
22
|
-
import { SignatureScheme } from '@mysten/sui.js/src/cryptography/signature-scheme';
|
|
23
|
-
import { MoreThanOrEqual } from 'typeorm';
|
|
24
|
-
|
|
25
|
-
import { CoreDB } from '@/backend/CoreDatabase';
|
|
26
|
-
import { IBackend } from '@/backend/interface';
|
|
27
|
-
import { MessageHelper } from '@/core/MessageHelper';
|
|
28
|
-
import { DBConfig } from '@/globals/const';
|
|
29
|
-
import { IntentionHelper, TxIntention } from '@/transactions/intention';
|
|
30
|
-
import {
|
|
31
|
-
GetAddressBookResult,
|
|
32
|
-
UpdateAddressBookEntry,
|
|
33
|
-
JWTToken,
|
|
34
|
-
PaginationOption,
|
|
35
|
-
PaginationResponse,
|
|
36
|
-
PagedResult,
|
|
37
|
-
} from '@/types';
|
|
38
|
-
import { FutureIntention, PendingTx } from '@/types/msafe';
|
|
39
|
-
import { Uint8ArrayToHex, HexToUint8Array } from '@/utils/buffer';
|
|
40
|
-
import { PublicKeySerde, SignatureVerifier } from '@/utils/crypto';
|
|
41
|
-
import { Formatter } from '@/utils/format';
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Represents a PseudoBackend class that implements the IBackend interface.
|
|
45
|
-
* This class is responsible for interacting with a database and performing various operations.
|
|
46
|
-
* Notice: For backend production code, all DB errors need to be handled correctly
|
|
47
|
-
* TODO: Use QueryRunner to make all db operations atomic.
|
|
48
|
-
* TODO: Permission validation for each request.
|
|
49
|
-
* TODO: More input validation and normalization.
|
|
50
|
-
*/
|
|
51
|
-
export class PseudoBackend implements IBackend {
|
|
52
|
-
private _token: JWTToken;
|
|
53
|
-
|
|
54
|
-
private constructor(
|
|
55
|
-
public readonly db: CoreDB,
|
|
56
|
-
private readonly _suiClient: SuiClient,
|
|
57
|
-
) {}
|
|
58
|
-
|
|
59
|
-
static async New(dbConfig: DBConfig, suiClient: SuiClient) {
|
|
60
|
-
const db = await CoreDB.New(dbConfig);
|
|
61
|
-
return new PseudoBackend(db, suiClient);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async authSign(input: {
|
|
65
|
-
address: string;
|
|
66
|
-
message: string;
|
|
67
|
-
signature: SerializedSignature;
|
|
68
|
-
walletType: string;
|
|
69
|
-
}): Promise<JWTToken> {
|
|
70
|
-
const timestamp = MessageHelper.deWelcomeMessage(input.message);
|
|
71
|
-
if (!timestamp) {
|
|
72
|
-
throw new Error('Invalid welcome message');
|
|
73
|
-
}
|
|
74
|
-
// Check if the signing message has expired
|
|
75
|
-
const date = Date.parse(timestamp);
|
|
76
|
-
if (new Date().getTime() - date < 0) {
|
|
77
|
-
throw new Error('Invalid timestamp');
|
|
78
|
-
}
|
|
79
|
-
if (new Date().getTime() - date > 10 * 1000) {
|
|
80
|
-
throw new Error('Signing message expired');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const publicKey = await SignatureVerifier.getPublicKeyFromPersonalSignature({
|
|
84
|
-
messageStr: input.message,
|
|
85
|
-
signature: input.signature,
|
|
86
|
-
});
|
|
87
|
-
if (!Formatter.isSuiAddressEqual(input.address, publicKey.toSuiAddress())) {
|
|
88
|
-
throw new Error('Invalid signature');
|
|
89
|
-
}
|
|
90
|
-
// Add user data to database
|
|
91
|
-
await this.db.upsertUser({
|
|
92
|
-
address: input.address,
|
|
93
|
-
publicKey,
|
|
94
|
-
lastLogin: new Date(),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Add wallet type
|
|
98
|
-
await this.db.updateUserWalletType({
|
|
99
|
-
address: input.address,
|
|
100
|
-
walletType: input.walletType,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
this._token = '';
|
|
104
|
-
return this._token;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async verifyToken(jwt?: JWTToken): Promise<boolean> {
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
setJWTToken(token: JWTToken) {
|
|
112
|
-
this._token = token;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async getPublicKey(address: string) {
|
|
116
|
-
const user = await this.model.user.findOneBy({
|
|
117
|
-
address,
|
|
118
|
-
});
|
|
119
|
-
if (user === null) {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
return PublicKeySerde.de({
|
|
123
|
-
publicKey: user.publicKey,
|
|
124
|
-
scheme: user.schema as SignatureScheme,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async getPublicKeyBatch(addresses: string[]): Promise<(PublicKey | undefined)[]> {
|
|
129
|
-
const res: (PublicKey | undefined)[] = [];
|
|
130
|
-
for (let i = 0; i < addresses.length; i++) {
|
|
131
|
-
const address = addresses[i];
|
|
132
|
-
const user = await this.getUser(address);
|
|
133
|
-
res.push(
|
|
134
|
-
user
|
|
135
|
-
? PublicKeySerde.de({
|
|
136
|
-
publicKey: user.publicKey,
|
|
137
|
-
scheme: user.schema as SignatureScheme,
|
|
138
|
-
})
|
|
139
|
-
: undefined,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
return res;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async createMSafeAccount(input: CreateMSafeAccountInfoRequest) {
|
|
146
|
-
const ownersWithWeightPK: OwnerWithWeightPK[] = input.ownersWithWeightPKEncoded.map((owner) => ({
|
|
147
|
-
address: owner.address,
|
|
148
|
-
weight: owner.weight,
|
|
149
|
-
publicKey: deserializePublicKey(owner.publicKeyEncoded, owner.schema),
|
|
150
|
-
}));
|
|
151
|
-
const multisigManager = new MultisigAccountManager({
|
|
152
|
-
threshold: input.threshold,
|
|
153
|
-
creationNonce: input.creationNonce,
|
|
154
|
-
ownersWithWeight: ownersWithWeightPK,
|
|
155
|
-
});
|
|
156
|
-
const msafeAddr = multisigManager.address;
|
|
157
|
-
|
|
158
|
-
const signingMsg = MessageHelper.createMSafeMessage(msafeAddr);
|
|
159
|
-
const targetAddr = input.ownersWithWeightPKEncoded[0].address;
|
|
160
|
-
const verifyResult = await SignatureVerifier.verifyPersonalSignature({
|
|
161
|
-
messageStr: signingMsg,
|
|
162
|
-
signature: input.signature,
|
|
163
|
-
targetAddress: targetAddr,
|
|
164
|
-
});
|
|
165
|
-
if (!verifyResult) {
|
|
166
|
-
throw new Error('Signature verification failed');
|
|
167
|
-
}
|
|
168
|
-
// TODO: In production, we will also need to verify whether the
|
|
169
|
-
// JWT token align with the creator (targetAddr)
|
|
170
|
-
// Check and verify the creation message.
|
|
171
|
-
if (input.name.length > 128) {
|
|
172
|
-
throw new Error('Name too long');
|
|
173
|
-
}
|
|
174
|
-
if (input.description && input.description.length > 512) {
|
|
175
|
-
throw new Error('Description too long');
|
|
176
|
-
}
|
|
177
|
-
const creatorAddress = input.ownersWithWeightPKEncoded[0].address;
|
|
178
|
-
const creator = await this.getUser(creatorAddress);
|
|
179
|
-
if (creator === null) {
|
|
180
|
-
throw new Error('Creator not found');
|
|
181
|
-
}
|
|
182
|
-
if (creator.nonce !== input.creationNonce) {
|
|
183
|
-
throw new Error('Nonce not match');
|
|
184
|
-
}
|
|
185
|
-
const msafeExistCheck = await this.model.msafe.findOneBy({
|
|
186
|
-
address: msafeAddr,
|
|
187
|
-
});
|
|
188
|
-
if (msafeExistCheck !== null) {
|
|
189
|
-
throw new Error('MSafe already exist in database');
|
|
190
|
-
}
|
|
191
|
-
input.ownersWithWeightPKEncoded.forEach((ownerInfo) => {
|
|
192
|
-
if (
|
|
193
|
-
!Formatter.isSuiAddressEqual(
|
|
194
|
-
ownerInfo.address,
|
|
195
|
-
PublicKeySerde.de({ publicKey: ownerInfo.publicKeyEncoded, scheme: ownerInfo.schema }).toSuiAddress(),
|
|
196
|
-
)
|
|
197
|
-
) {
|
|
198
|
-
throw new Error('Sui address public key not match');
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// For each manager, add user info if not exist. Add userMSafe entry.
|
|
203
|
-
for (let i = 0; i !== input.ownersWithWeightPKEncoded.length; i++) {
|
|
204
|
-
const ownerInfo = input.ownersWithWeightPKEncoded[i];
|
|
205
|
-
// Creator has been verified and already exist in database
|
|
206
|
-
if (i !== 0) {
|
|
207
|
-
const coManager = await this.getUser(ownerInfo.address);
|
|
208
|
-
// Add user with public key if not exist
|
|
209
|
-
if (coManager === null) {
|
|
210
|
-
const user: User = {
|
|
211
|
-
address: ownerInfo.address,
|
|
212
|
-
publicKey: ownerInfo.publicKeyEncoded,
|
|
213
|
-
schema: ownerInfo.schema,
|
|
214
|
-
nonce: 0,
|
|
215
|
-
lastLogin: new Date(),
|
|
216
|
-
};
|
|
217
|
-
await this.model.user.save(user);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Add userMSafe record
|
|
222
|
-
const userMSafe: UserMSafe = {
|
|
223
|
-
userAddress: ownerInfo.address,
|
|
224
|
-
msafeAddress: msafeAddr,
|
|
225
|
-
index: i,
|
|
226
|
-
weight: ownerInfo.weight,
|
|
227
|
-
status: i === 0 ? 'active' : 'pending',
|
|
228
|
-
};
|
|
229
|
-
await this.model.userMSafe.save(userMSafe);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Increment the creator nonce
|
|
233
|
-
const creationNonce = creator.nonce;
|
|
234
|
-
creator.nonce++;
|
|
235
|
-
await this.model.user.save(creator);
|
|
236
|
-
|
|
237
|
-
// Add MSafe record
|
|
238
|
-
const msafe: MSafe = {
|
|
239
|
-
address: msafeAddr,
|
|
240
|
-
creationNonce,
|
|
241
|
-
creator: creatorAddress,
|
|
242
|
-
name: input.name,
|
|
243
|
-
description: input.description,
|
|
244
|
-
threshold: input.threshold,
|
|
245
|
-
};
|
|
246
|
-
await this.model.msafe.save(msafe);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async getMSafeAccountInfo(msafeAddress: string): Promise<MSafeAccountInfo> {
|
|
250
|
-
// TODO: In production, we need verify whether the msafe
|
|
251
|
-
// account belongs to the single signer from JWT token.
|
|
252
|
-
|
|
253
|
-
const msafe = await this.model.msafe.findOneBy({ address: msafeAddress });
|
|
254
|
-
if (msafe === null) {
|
|
255
|
-
throw new Error('MSafe not found');
|
|
256
|
-
}
|
|
257
|
-
const userMSafes = await this.model.userMSafe.find({
|
|
258
|
-
where: {
|
|
259
|
-
msafeAddress,
|
|
260
|
-
},
|
|
261
|
-
order: {
|
|
262
|
-
index: 'asc',
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
if (userMSafes.length === 0) {
|
|
266
|
-
throw new Error('MSafe does not have user info.');
|
|
267
|
-
}
|
|
268
|
-
const users = await Promise.all(userMSafes.map((um) => this.getUser(um.userAddress)));
|
|
269
|
-
users.forEach((user) => {
|
|
270
|
-
if (user === null) {
|
|
271
|
-
throw new Error('User not found');
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
const ownersWithWeightPK = userMSafes.map((userMSafe, i) => ({
|
|
275
|
-
address: userMSafe.userAddress,
|
|
276
|
-
weight: userMSafe.weight,
|
|
277
|
-
publicKey: PublicKeySerde.de({ publicKey: users[i]!.publicKey, scheme: users[i]!.schema as SignatureScheme }),
|
|
278
|
-
}));
|
|
279
|
-
return {
|
|
280
|
-
address: msafeAddress,
|
|
281
|
-
ownersWithWeightPK,
|
|
282
|
-
threshold: msafe.threshold,
|
|
283
|
-
name: msafe.name,
|
|
284
|
-
description: msafe.description,
|
|
285
|
-
creationNonce: msafe.creationNonce,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async getUserInfo(userAddress: string): Promise<UserWithOwnedMSafe> {
|
|
290
|
-
// TODO: Do permission check on user JWT token.
|
|
291
|
-
const user = await this.getUser(userAddress);
|
|
292
|
-
if (user === null) {
|
|
293
|
-
throw new Error('404: user not found');
|
|
294
|
-
}
|
|
295
|
-
// TODO: Add invitation status as well.
|
|
296
|
-
const msafeAccounts = await this.model.userMSafe.findBy({ userAddress });
|
|
297
|
-
const ownedMSafe = await Promise.all(
|
|
298
|
-
msafeAccounts.map(async (msafeAccount) => this.getMSafeAccountInfo(msafeAccount.msafeAddress)),
|
|
299
|
-
);
|
|
300
|
-
return {
|
|
301
|
-
address: userAddress,
|
|
302
|
-
publicKey: user.publicKey,
|
|
303
|
-
schema: user.schema,
|
|
304
|
-
creationNonce: user.nonce,
|
|
305
|
-
ownedMSafe,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async getPendingTransactions(msafeAddress: string): Promise<PendingTx[]> {
|
|
310
|
-
const pendings = await this.db.coreModel.pendingTransaction.findBy({
|
|
311
|
-
msafeAddress,
|
|
312
|
-
});
|
|
313
|
-
if (pendings.length === 0) {
|
|
314
|
-
return [];
|
|
315
|
-
}
|
|
316
|
-
const votes = await Promise.all(
|
|
317
|
-
pendings.map((pending) => this.db.coreModel.userVote.findBy({ txDigest: pending.digest, isValid: true })),
|
|
318
|
-
);
|
|
319
|
-
const intention = await this.model.transactionIntention.findOneBy({
|
|
320
|
-
msafeAddress,
|
|
321
|
-
sequenceNumber: pendings[0].sequenceNumber,
|
|
322
|
-
});
|
|
323
|
-
if (intention === null) {
|
|
324
|
-
throw new Error('Intention not found');
|
|
325
|
-
}
|
|
326
|
-
const intent = IntentionHelper.de(intention.data);
|
|
327
|
-
return pendings.map((pending, i) => ({
|
|
328
|
-
digest: pending.digest,
|
|
329
|
-
payload: pending.payload,
|
|
330
|
-
msafeAddress: pending.msafeAddress,
|
|
331
|
-
isRejectTx: pending.isRejectTx,
|
|
332
|
-
creator: pending.creator,
|
|
333
|
-
createdAt: pending.createdAt as Date,
|
|
334
|
-
votes: votes[i].map((vote) => ({
|
|
335
|
-
userAddress: vote.userAddress,
|
|
336
|
-
signature: vote.signature,
|
|
337
|
-
timestamp: vote.updatedAt as Date,
|
|
338
|
-
})),
|
|
339
|
-
sequenceNumber: pending.sequenceNumber,
|
|
340
|
-
intention: pending.isRejectTx ? undefined : intent,
|
|
341
|
-
}));
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
async getCurrentSequenceNumber(msafeAddress: string): Promise<number> {
|
|
345
|
-
// TODO: validate user permission
|
|
346
|
-
const maxSNHistory = await this.model.historyTransaction.findOne({
|
|
347
|
-
where: { msafeAddress },
|
|
348
|
-
order: { sequenceNumber: 'desc' },
|
|
349
|
-
});
|
|
350
|
-
return maxSNHistory === null ? 0 : maxSNHistory.sequenceNumber + 1;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
async getNextSequenceNumber(msafeAddress: string): Promise<number> {
|
|
354
|
-
// TODO: validate user permission
|
|
355
|
-
const maxSNIntention = await this.model.transactionIntention.findOne({
|
|
356
|
-
where: { msafeAddress },
|
|
357
|
-
order: { sequenceNumber: 'desc' },
|
|
358
|
-
});
|
|
359
|
-
return maxSNIntention === null ? 0 : maxSNIntention.sequenceNumber + 1;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async getHistoryTransactions(
|
|
363
|
-
msafeAddress: string,
|
|
364
|
-
paginationOption?: PaginationOption,
|
|
365
|
-
): Promise<PagedResult<HistoryTx>> {
|
|
366
|
-
// TODO: Add pagination options. Filter by sequence number based
|
|
367
|
-
// on pagination options.
|
|
368
|
-
// Notice there can be multiple transactions with the same sequence
|
|
369
|
-
// number.
|
|
370
|
-
// TODO: Add permission validation
|
|
371
|
-
const transactions = await this.db.coreModel.historyTransaction.find({
|
|
372
|
-
where: {
|
|
373
|
-
msafeAddress,
|
|
374
|
-
},
|
|
375
|
-
order: { sequenceNumber: 'desc' },
|
|
376
|
-
});
|
|
377
|
-
const votes = await Promise.all(
|
|
378
|
-
transactions.map((tx) =>
|
|
379
|
-
this.db.coreModel.userVote.findBy({
|
|
380
|
-
txDigest: tx.digest,
|
|
381
|
-
isValid: true,
|
|
382
|
-
}),
|
|
383
|
-
),
|
|
384
|
-
);
|
|
385
|
-
return {
|
|
386
|
-
items: transactions.map((tx, i) => ({
|
|
387
|
-
digest: tx.digest,
|
|
388
|
-
payload: tx.payload,
|
|
389
|
-
msafeAddress: tx.msafeAddress,
|
|
390
|
-
isRejectTx: tx.isRejectTx,
|
|
391
|
-
status: tx.status,
|
|
392
|
-
creator: tx.creator,
|
|
393
|
-
createdAt: tx.createdAt as Date,
|
|
394
|
-
sequenceNumber: tx.sequenceNumber,
|
|
395
|
-
votes: votes[i].map((vote) => ({
|
|
396
|
-
userAddress: vote.userAddress,
|
|
397
|
-
timestamp: vote.updatedAt as Date,
|
|
398
|
-
})),
|
|
399
|
-
})),
|
|
400
|
-
meta: {
|
|
401
|
-
totalItems: 0,
|
|
402
|
-
itemCount: 0,
|
|
403
|
-
itemsPerPage: 0,
|
|
404
|
-
totalPages: 0,
|
|
405
|
-
currentPage: 0,
|
|
406
|
-
},
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
async getFutureIntentions(
|
|
411
|
-
msafeAddress: string,
|
|
412
|
-
paginationOption?: PaginationOption,
|
|
413
|
-
): Promise<PagedResult<FutureIntention>> {
|
|
414
|
-
// TODO: Add pagination options, add user permission validation
|
|
415
|
-
const currentSequenceNumber = await this.getCurrentSequenceNumber(msafeAddress);
|
|
416
|
-
const hasPending = (await this.model.pendingTransaction.findOneBy({ msafeAddress })) !== null;
|
|
417
|
-
const futureSNStart = hasPending ? currentSequenceNumber + 1 : currentSequenceNumber;
|
|
418
|
-
|
|
419
|
-
const futureTxs = await this.model.transactionIntention.find({
|
|
420
|
-
where: { msafeAddress, sequenceNumber: MoreThanOrEqual(futureSNStart) },
|
|
421
|
-
order: { sequenceNumber: 'asc' },
|
|
422
|
-
});
|
|
423
|
-
return {
|
|
424
|
-
items: futureTxs.map((tx) => ({
|
|
425
|
-
intention: IntentionHelper.de(tx.data),
|
|
426
|
-
msafeAddress: tx.msafeAddress,
|
|
427
|
-
sequenceNumber: tx.sequenceNumber,
|
|
428
|
-
rawData: tx.data,
|
|
429
|
-
txType: tx.txType,
|
|
430
|
-
txSubType: tx.txSubType,
|
|
431
|
-
processed: tx.processed,
|
|
432
|
-
status: tx.status,
|
|
433
|
-
statusRemark: tx.statusRemark,
|
|
434
|
-
creator: tx.creator,
|
|
435
|
-
createdAt: tx.createdAt as Date,
|
|
436
|
-
})),
|
|
437
|
-
meta: {
|
|
438
|
-
totalItems: 0,
|
|
439
|
-
itemCount: 0,
|
|
440
|
-
itemsPerPage: 0,
|
|
441
|
-
totalPages: 0,
|
|
442
|
-
currentPage: 0,
|
|
443
|
-
},
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
async proposeIntention(input: {
|
|
448
|
-
intention: TxIntention;
|
|
449
|
-
sequenceNumber: number;
|
|
450
|
-
userAddress: string;
|
|
451
|
-
msafeAddress: string;
|
|
452
|
-
signature: SerializedSignature;
|
|
453
|
-
application: string;
|
|
454
|
-
txType: string;
|
|
455
|
-
txSubType: string;
|
|
456
|
-
}) {
|
|
457
|
-
// Validation:
|
|
458
|
-
// 1. Whether the current wallet have permission
|
|
459
|
-
// 2. Verify signature
|
|
460
|
-
const userMSafe = await this.model.userMSafe.findOneBy({
|
|
461
|
-
msafeAddress: input.msafeAddress,
|
|
462
|
-
userAddress: input.userAddress,
|
|
463
|
-
});
|
|
464
|
-
if (!userMSafe) {
|
|
465
|
-
throw new Error('User does not have permission to propose intention');
|
|
466
|
-
}
|
|
467
|
-
const verified = await SignatureVerifier.verifyPersonalSignature({
|
|
468
|
-
messageStr: MessageHelper.proposeIntentionMessage({ intention: input.intention, sn: input.sequenceNumber }),
|
|
469
|
-
signature: input.signature,
|
|
470
|
-
targetAddress: input.userAddress,
|
|
471
|
-
});
|
|
472
|
-
if (!verified) {
|
|
473
|
-
throw new Error('Invalid signature');
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const sequenceNumber = await this.model.transactionIntention.count({
|
|
477
|
-
where: {
|
|
478
|
-
msafeAddress: input.msafeAddress,
|
|
479
|
-
},
|
|
480
|
-
});
|
|
481
|
-
if (sequenceNumber !== input.sequenceNumber) {
|
|
482
|
-
throw new Error('Sequence number not expected');
|
|
483
|
-
}
|
|
484
|
-
const intention: TransactionIntention = {
|
|
485
|
-
msafeAddress: input.msafeAddress,
|
|
486
|
-
sequenceNumber,
|
|
487
|
-
processed: false,
|
|
488
|
-
...IntentionHelper.getTxType(input.intention),
|
|
489
|
-
data: IntentionHelper.ser(input.intention),
|
|
490
|
-
status: 'NEW',
|
|
491
|
-
creator: input.userAddress,
|
|
492
|
-
};
|
|
493
|
-
await this.model.transactionIntention.save(intention);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Propose a pending transaction. Require the msafe account
|
|
497
|
-
// Does not have any pending transactions.
|
|
498
|
-
async proposePendingTransaction(input: {
|
|
499
|
-
intention: TxIntention;
|
|
500
|
-
userAddress: string;
|
|
501
|
-
msafeAddress: string;
|
|
502
|
-
digest: string;
|
|
503
|
-
signature: SerializedSignature;
|
|
504
|
-
}) {
|
|
505
|
-
const userMSafe = await this.model.userMSafe.findOneBy({
|
|
506
|
-
msafeAddress: input.msafeAddress,
|
|
507
|
-
userAddress: input.userAddress,
|
|
508
|
-
});
|
|
509
|
-
if (userMSafe === null) {
|
|
510
|
-
throw new Error('Unauthorized');
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
const msafePendings = await this.model.pendingTransaction.findBy({
|
|
514
|
-
msafeAddress: input.msafeAddress,
|
|
515
|
-
});
|
|
516
|
-
if (msafePendings.length !== 0) {
|
|
517
|
-
throw new Error('Still have pending transaction');
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const txb = await IntentionHelper.buildTxb({
|
|
521
|
-
suiClient: this._suiClient,
|
|
522
|
-
intention: input.intention,
|
|
523
|
-
sender: input.msafeAddress,
|
|
524
|
-
});
|
|
525
|
-
const payload = await txb.build({ client: this._suiClient });
|
|
526
|
-
const txDigest = await txb.getDigest({ client: this._suiClient });
|
|
527
|
-
if (txDigest !== input.digest) {
|
|
528
|
-
throw new Error('Transaction digest un-match');
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const verified = await SignatureVerifier.verifyTransactionSignature({
|
|
532
|
-
payload,
|
|
533
|
-
targetAddress: input.userAddress,
|
|
534
|
-
signature: input.signature,
|
|
535
|
-
});
|
|
536
|
-
if (!verified) {
|
|
537
|
-
throw new Error('Failed to verify signature');
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Write intention
|
|
541
|
-
const maxSNHistory = await this.model.historyTransaction.findOne({
|
|
542
|
-
where: { msafeAddress: input.msafeAddress },
|
|
543
|
-
order: { sequenceNumber: 'desc' },
|
|
544
|
-
});
|
|
545
|
-
const sequenceNumber = maxSNHistory === null ? 0 : maxSNHistory.sequenceNumber + 1;
|
|
546
|
-
const intention: TransactionIntention = {
|
|
547
|
-
msafeAddress: input.msafeAddress,
|
|
548
|
-
sequenceNumber,
|
|
549
|
-
processed: false,
|
|
550
|
-
...IntentionHelper.getTxType(input.intention),
|
|
551
|
-
data: IntentionHelper.ser(input.intention),
|
|
552
|
-
status: 'SUCCESS',
|
|
553
|
-
creator: input.userAddress,
|
|
554
|
-
};
|
|
555
|
-
await this.model.transactionIntention.save(intention);
|
|
556
|
-
|
|
557
|
-
// Write pending transaction
|
|
558
|
-
const pendingTx: PendingTransaction = {
|
|
559
|
-
digest: txDigest,
|
|
560
|
-
msafeAddress: input.msafeAddress,
|
|
561
|
-
payload: Uint8ArrayToHex(payload),
|
|
562
|
-
sequenceNumber,
|
|
563
|
-
isRejectTx: false,
|
|
564
|
-
creator: input.userAddress,
|
|
565
|
-
};
|
|
566
|
-
await this.model.pendingTransaction.save(pendingTx);
|
|
567
|
-
|
|
568
|
-
// Write user vote
|
|
569
|
-
const userVote: UserVote = {
|
|
570
|
-
userAddress: input.userAddress,
|
|
571
|
-
txDigest,
|
|
572
|
-
msafeAddress: input.msafeAddress,
|
|
573
|
-
signature: input.signature,
|
|
574
|
-
isValid: true,
|
|
575
|
-
};
|
|
576
|
-
await this.model.userVote.save(userVote);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Calls for the first reject transaction
|
|
580
|
-
async rejectCurrentTx(input: {
|
|
581
|
-
userAddress: string;
|
|
582
|
-
msafeAddress: string;
|
|
583
|
-
digest: string;
|
|
584
|
-
signature: SerializedSignature;
|
|
585
|
-
}) {
|
|
586
|
-
const userMSafe = await this.model.userMSafe.findOneBy({
|
|
587
|
-
userAddress: input.userAddress,
|
|
588
|
-
msafeAddress: input.msafeAddress,
|
|
589
|
-
});
|
|
590
|
-
if (userMSafe === null) {
|
|
591
|
-
throw new Error('User does not have permission to MSafe');
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
const currentPending = await this.model.pendingTransaction.findOneBy({
|
|
595
|
-
msafeAddress: input.msafeAddress,
|
|
596
|
-
isRejectTx: false,
|
|
597
|
-
});
|
|
598
|
-
if (currentPending === null) {
|
|
599
|
-
throw new Error('No active pending transaction');
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const txb = await IntentionHelper.buildRejectTransaction({
|
|
603
|
-
msafeAddress: input.msafeAddress,
|
|
604
|
-
payloadToReject: currentPending.payload,
|
|
605
|
-
});
|
|
606
|
-
const payload = await txb.build({ client: this._suiClient });
|
|
607
|
-
const digest = await txb.getDigest({ client: this._suiClient });
|
|
608
|
-
if (input.digest !== digest) {
|
|
609
|
-
throw new Error('Digest not match');
|
|
610
|
-
}
|
|
611
|
-
const verified = await SignatureVerifier.verifyTransactionSignature({
|
|
612
|
-
payload,
|
|
613
|
-
targetAddress: input.userAddress,
|
|
614
|
-
signature: input.signature,
|
|
615
|
-
});
|
|
616
|
-
if (!verified) {
|
|
617
|
-
throw new Error('Signature unverified');
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
const existPendingReject = await this.model.pendingTransaction.findOneBy({
|
|
621
|
-
msafeAddress: input.msafeAddress,
|
|
622
|
-
isRejectTx: true,
|
|
623
|
-
});
|
|
624
|
-
if (existPendingReject !== null) {
|
|
625
|
-
await this.voteForTransaction({
|
|
626
|
-
txDigest: existPendingReject.digest,
|
|
627
|
-
msafeAddress: input.msafeAddress,
|
|
628
|
-
userAddress: input.userAddress,
|
|
629
|
-
signature: input.signature,
|
|
630
|
-
});
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const rejectPayloadStr = Uint8ArrayToHex(payload);
|
|
635
|
-
const rejectDigest = await txb.getDigest({ client: this._suiClient });
|
|
636
|
-
const rejectPending: PendingTransaction = {
|
|
637
|
-
msafeAddress: input.msafeAddress,
|
|
638
|
-
digest: rejectDigest,
|
|
639
|
-
payload: rejectPayloadStr,
|
|
640
|
-
sequenceNumber: currentPending.sequenceNumber,
|
|
641
|
-
isRejectTx: true,
|
|
642
|
-
creator: input.userAddress,
|
|
643
|
-
};
|
|
644
|
-
await this.model.pendingTransaction.save(rejectPending);
|
|
645
|
-
|
|
646
|
-
// Vote for the transaction
|
|
647
|
-
const rejectVote: UserVote = {
|
|
648
|
-
userAddress: input.userAddress,
|
|
649
|
-
msafeAddress: input.msafeAddress,
|
|
650
|
-
txDigest: rejectDigest,
|
|
651
|
-
signature: input.signature,
|
|
652
|
-
isValid: true,
|
|
653
|
-
};
|
|
654
|
-
await this.model.userVote.save(rejectVote);
|
|
655
|
-
|
|
656
|
-
// If user has previously voted in the other way, revoke the vote
|
|
657
|
-
const existVote = await this.model.userVote.findOneBy({
|
|
658
|
-
txDigest: currentPending.digest,
|
|
659
|
-
userAddress: input.userAddress,
|
|
660
|
-
});
|
|
661
|
-
if (existVote !== null) {
|
|
662
|
-
await this.model.userVote.update(
|
|
663
|
-
{ userAddress: input.userAddress, txDigest: currentPending.digest },
|
|
664
|
-
{ isValid: false },
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
async voteForTransaction(input: {
|
|
670
|
-
txDigest: string;
|
|
671
|
-
msafeAddress: string;
|
|
672
|
-
userAddress: string;
|
|
673
|
-
signature: SerializedSignature;
|
|
674
|
-
}) {
|
|
675
|
-
const pendingTx = await this.model.pendingTransaction.findOneBy({ digest: input.txDigest });
|
|
676
|
-
if (!pendingTx) {
|
|
677
|
-
throw new Error(`Pending transaction not found: ${input.txDigest}`);
|
|
678
|
-
}
|
|
679
|
-
if (pendingTx.msafeAddress !== input.msafeAddress) {
|
|
680
|
-
throw new Error('MSafe address not match');
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Ensure user owns the msafe address
|
|
684
|
-
const userMSafe = await this.model.userMSafe.findOneBy({
|
|
685
|
-
userAddress: input.userAddress,
|
|
686
|
-
msafeAddress: input.msafeAddress,
|
|
687
|
-
});
|
|
688
|
-
if (userMSafe === null) {
|
|
689
|
-
throw new Error(`User (${input.userAddress}) does not have permission on MSafe account (${input.msafeAddress})`);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Verify the signature
|
|
693
|
-
const payload = HexToUint8Array(pendingTx.payload);
|
|
694
|
-
|
|
695
|
-
const verified = await SignatureVerifier.verifyTransactionSignature({
|
|
696
|
-
payload,
|
|
697
|
-
targetAddress: input.userAddress,
|
|
698
|
-
signature: input.signature,
|
|
699
|
-
});
|
|
700
|
-
if (!verified) {
|
|
701
|
-
throw new Error('Invalid signature');
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
// If user has other vote on the reject transaction, Add the user vote.
|
|
705
|
-
const rejectPendingTx = await this.model.pendingTransaction.findOneBy({
|
|
706
|
-
msafeAddress: input.msafeAddress,
|
|
707
|
-
sequenceNumber: pendingTx.sequenceNumber,
|
|
708
|
-
isRejectTx: !pendingTx.isRejectTx,
|
|
709
|
-
});
|
|
710
|
-
if (rejectPendingTx !== null) {
|
|
711
|
-
await this.model.userVote.update(
|
|
712
|
-
{
|
|
713
|
-
userAddress: input.userAddress,
|
|
714
|
-
txDigest: rejectPendingTx.digest,
|
|
715
|
-
isValid: true,
|
|
716
|
-
},
|
|
717
|
-
{ isValid: false },
|
|
718
|
-
);
|
|
719
|
-
}
|
|
720
|
-
const existVote = await this.model.userVote.exist({
|
|
721
|
-
where: {
|
|
722
|
-
txDigest: input.txDigest,
|
|
723
|
-
userAddress: input.userAddress,
|
|
724
|
-
},
|
|
725
|
-
});
|
|
726
|
-
if (!existVote) {
|
|
727
|
-
const userVote: UserVote = {
|
|
728
|
-
txDigest: input.txDigest,
|
|
729
|
-
userAddress: input.userAddress,
|
|
730
|
-
msafeAddress: input.msafeAddress,
|
|
731
|
-
signature: input.signature,
|
|
732
|
-
isValid: true,
|
|
733
|
-
};
|
|
734
|
-
await this.model.userVote.save(userVote);
|
|
735
|
-
} else {
|
|
736
|
-
await this.model.userVote.update(
|
|
737
|
-
{
|
|
738
|
-
txDigest: input.txDigest,
|
|
739
|
-
userAddress: input.userAddress,
|
|
740
|
-
},
|
|
741
|
-
{ isValid: true },
|
|
742
|
-
);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Mock the process of an executed transaction
|
|
747
|
-
// Here only the essential logic of transaction processing logic
|
|
748
|
-
// is implemented. Need more transaction parsing from the fetcher
|
|
749
|
-
// module.
|
|
750
|
-
//
|
|
751
|
-
// TODO: User queryRunner to make the transaction atomic.
|
|
752
|
-
async processExecutedTransaction(digest: string) {
|
|
753
|
-
// TODO: Process the transaction result from blockchain.
|
|
754
|
-
const historyTx = await this.model.historyTransaction.findOneBy({ digest });
|
|
755
|
-
if (historyTx) {
|
|
756
|
-
// Already processed, directly return
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
const pendingTx = await this.model.pendingTransaction.findOneBy({ digest });
|
|
760
|
-
if (!pendingTx) {
|
|
761
|
-
throw new Error('Transaction digest not found');
|
|
762
|
-
}
|
|
763
|
-
// Mark the intention as processed.
|
|
764
|
-
await this.model.transactionIntention.update(
|
|
765
|
-
{ msafeAddress: pendingTx.msafeAddress, sequenceNumber: pendingTx.sequenceNumber },
|
|
766
|
-
{ processed: true },
|
|
767
|
-
);
|
|
768
|
-
// Delete the current pending transaction.
|
|
769
|
-
// There can be multiple transactions. mark one as executed, other as rejected
|
|
770
|
-
const pendings = await this.model.pendingTransaction.findBy({
|
|
771
|
-
msafeAddress: pendingTx.msafeAddress,
|
|
772
|
-
sequenceNumber: pendingTx.sequenceNumber,
|
|
773
|
-
});
|
|
774
|
-
if (pendings.length === 0) {
|
|
775
|
-
throw new Error('Pending transaction not found');
|
|
776
|
-
}
|
|
777
|
-
// Add to transaction history and delete the pending transaction.
|
|
778
|
-
const executionResult: 'success' | 'failed' = 'success';
|
|
779
|
-
for (let i = 0; i !== pendings.length; i++) {
|
|
780
|
-
const pending = pendings[i];
|
|
781
|
-
const history: HistoryTransaction = {
|
|
782
|
-
digest: pending.digest,
|
|
783
|
-
payload: pending.payload,
|
|
784
|
-
msafeAddress: pending.msafeAddress,
|
|
785
|
-
sequenceNumber: pending.sequenceNumber,
|
|
786
|
-
isRejectTx: pending.isRejectTx,
|
|
787
|
-
creator: pending.creator,
|
|
788
|
-
createdAt: pending.createdAt as Date,
|
|
789
|
-
// TODO: Fill in this field based on transaction processing result
|
|
790
|
-
status: pending.digest === digest ? executionResult : 'rejected',
|
|
791
|
-
};
|
|
792
|
-
await this.model.historyTransaction.save(history);
|
|
793
|
-
await this.model.pendingTransaction.delete({ digest: pending.digest });
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
* Build the next transaction intention, and add the built transaction to pendings.
|
|
799
|
-
* @param input
|
|
800
|
-
*/
|
|
801
|
-
async buildNextIntentionAndAddToPending(input: { msafeAddress: string }) {
|
|
802
|
-
const maxSNHistory = await this.model.historyTransaction.findOne({
|
|
803
|
-
where: { msafeAddress: input.msafeAddress },
|
|
804
|
-
order: { sequenceNumber: 'desc' },
|
|
805
|
-
});
|
|
806
|
-
const nextSequenceNumber = maxSNHistory ? maxSNHistory.sequenceNumber + 1 : 0;
|
|
807
|
-
|
|
808
|
-
const currentPendings = await this.model.pendingTransaction.findBy({
|
|
809
|
-
msafeAddress: input.msafeAddress,
|
|
810
|
-
});
|
|
811
|
-
if (currentPendings.length !== 0) {
|
|
812
|
-
throw new Error('Still have pendings');
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const nextTx = await this.model.transactionIntention.findOneBy({
|
|
816
|
-
msafeAddress: input.msafeAddress,
|
|
817
|
-
sequenceNumber: nextSequenceNumber,
|
|
818
|
-
});
|
|
819
|
-
if (nextTx === null) {
|
|
820
|
-
throw new Error('No future intentions to build');
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
const nextIntention = IntentionHelper.de(nextTx.data);
|
|
824
|
-
try {
|
|
825
|
-
const newTxb = await IntentionHelper.buildTxb({
|
|
826
|
-
suiClient: this._suiClient,
|
|
827
|
-
sender: input.msafeAddress,
|
|
828
|
-
intention: nextIntention,
|
|
829
|
-
});
|
|
830
|
-
const payload = await newTxb.build({ client: this._suiClient });
|
|
831
|
-
const newDigest = await newTxb.getDigest({ client: this._suiClient });
|
|
832
|
-
const newPending: PendingTransaction = {
|
|
833
|
-
digest: newDigest,
|
|
834
|
-
payload: Uint8ArrayToHex(payload),
|
|
835
|
-
msafeAddress: input.msafeAddress,
|
|
836
|
-
sequenceNumber: nextTx.sequenceNumber,
|
|
837
|
-
isRejectTx: false,
|
|
838
|
-
creator: nextTx.creator,
|
|
839
|
-
};
|
|
840
|
-
await this.model.pendingTransaction.save(newPending);
|
|
841
|
-
await this.model.transactionIntention.update(
|
|
842
|
-
{
|
|
843
|
-
msafeAddress: input.msafeAddress,
|
|
844
|
-
sequenceNumber: nextSequenceNumber,
|
|
845
|
-
},
|
|
846
|
-
{
|
|
847
|
-
status: 'SUCCESS',
|
|
848
|
-
},
|
|
849
|
-
);
|
|
850
|
-
} catch (e: unknown) {
|
|
851
|
-
// Build failed. Mark the intention as failed.
|
|
852
|
-
await this.model.transactionIntention.update(
|
|
853
|
-
{
|
|
854
|
-
msafeAddress: input.msafeAddress,
|
|
855
|
-
sequenceNumber: nextSequenceNumber,
|
|
856
|
-
},
|
|
857
|
-
{
|
|
858
|
-
status: 'FAILED',
|
|
859
|
-
statusRemark: (e as any).toString(),
|
|
860
|
-
},
|
|
861
|
-
);
|
|
862
|
-
throw new Error(`Intention build failed: {sequenceNumber: ${nextSequenceNumber}}`);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
/**
|
|
867
|
-
* Skip next failed transaction if build of the intention has been failed before.
|
|
868
|
-
*/
|
|
869
|
-
async skipNextFailedIntention(input: { msafeAddress: string; userAddress: string }) {
|
|
870
|
-
const userMSafe = await this.model.userMSafe.findOneBy({
|
|
871
|
-
msafeAddress: input.msafeAddress,
|
|
872
|
-
userAddress: input.userAddress,
|
|
873
|
-
});
|
|
874
|
-
if (userMSafe === null) {
|
|
875
|
-
throw new Error('user does not have permission to MSafe');
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
const curSequenceNumber = await this.model.historyTransaction.findOne({
|
|
879
|
-
where: { msafeAddress: input.msafeAddress },
|
|
880
|
-
order: { sequenceNumber: 'desc' },
|
|
881
|
-
});
|
|
882
|
-
const nextSequenceNumber = curSequenceNumber ? curSequenceNumber.sequenceNumber + 1 : 0;
|
|
883
|
-
|
|
884
|
-
const failedIntention = await this.model.transactionIntention.findOneBy({
|
|
885
|
-
msafeAddress: input.msafeAddress,
|
|
886
|
-
sequenceNumber: nextSequenceNumber,
|
|
887
|
-
});
|
|
888
|
-
if (failedIntention === null) {
|
|
889
|
-
throw new Error('Next intention not found');
|
|
890
|
-
}
|
|
891
|
-
if (failedIntention.status !== 'FAILED') {
|
|
892
|
-
throw new Error('Next intention not failed');
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const history: HistoryTransaction = {
|
|
896
|
-
msafeAddress: input.msafeAddress,
|
|
897
|
-
digest: '0x0', // Special case for build has been failed,
|
|
898
|
-
payload: '',
|
|
899
|
-
sequenceNumber: nextSequenceNumber,
|
|
900
|
-
creator: failedIntention.creator,
|
|
901
|
-
isRejectTx: false,
|
|
902
|
-
status: 'build-failed',
|
|
903
|
-
};
|
|
904
|
-
await this.model.historyTransaction.save(history);
|
|
905
|
-
|
|
906
|
-
await this.model.transactionIntention.update(
|
|
907
|
-
{ msafeAddress: input.msafeAddress, sequenceNumber: nextSequenceNumber },
|
|
908
|
-
{ processed: true },
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
async getAddressBookEntries(pagination?: PaginationOption): Promise<GetAddressBookResult> {
|
|
913
|
-
return {
|
|
914
|
-
data: [],
|
|
915
|
-
meta: {
|
|
916
|
-
total: 0,
|
|
917
|
-
page: 0,
|
|
918
|
-
limit: 0,
|
|
919
|
-
hasNext: false,
|
|
920
|
-
},
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
async updateAddressBook(input: {
|
|
925
|
-
updates: UpdateAddressBookEntry[];
|
|
926
|
-
signature: SerializedSignature;
|
|
927
|
-
}): Promise<void> {}
|
|
928
|
-
|
|
929
|
-
private async getUser(address: string): Promise<User | null> {
|
|
930
|
-
return this.model.user.findOneBy({ address });
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
private get model() {
|
|
934
|
-
return this.db.coreModel;
|
|
935
|
-
}
|
|
936
|
-
}
|