@sogni-ai/sogni-client 0.3.2 → 0.4.0-aplha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +3 -2
  2. package/dist/Account/index.d.ts +127 -3
  3. package/dist/Account/index.js +126 -2
  4. package/dist/Account/index.js.map +1 -1
  5. package/dist/Projects/Job.d.ts +5 -0
  6. package/dist/Projects/Job.js +6 -0
  7. package/dist/Projects/Job.js.map +1 -1
  8. package/dist/Projects/index.d.ts +6 -6
  9. package/dist/Projects/index.js +12 -2
  10. package/dist/Projects/index.js.map +1 -1
  11. package/dist/Projects/types/events.d.ts +2 -0
  12. package/dist/version.d.ts +1 -1
  13. package/dist/version.js +1 -1
  14. package/dist/version.js.map +1 -1
  15. package/package.json +5 -3
  16. package/src/Account/CurrentAccount.ts +101 -0
  17. package/src/Account/index.ts +367 -0
  18. package/src/Account/types.ts +90 -0
  19. package/src/ApiClient/WebSocketClient/ErrorCode.ts +15 -0
  20. package/src/ApiClient/WebSocketClient/events.ts +94 -0
  21. package/src/ApiClient/WebSocketClient/index.ts +203 -0
  22. package/src/ApiClient/WebSocketClient/messages.ts +7 -0
  23. package/src/ApiClient/WebSocketClient/types.ts +1 -0
  24. package/src/ApiClient/events.ts +20 -0
  25. package/src/ApiClient/index.ts +124 -0
  26. package/src/ApiGroup.ts +25 -0
  27. package/src/Projects/Job.ts +132 -0
  28. package/src/Projects/Project.ts +185 -0
  29. package/src/Projects/createJobRequestMessage.ts +99 -0
  30. package/src/Projects/index.ts +350 -0
  31. package/src/Projects/models.json +8906 -0
  32. package/src/Projects/types/EstimationResponse.ts +45 -0
  33. package/src/Projects/types/events.ts +80 -0
  34. package/src/Projects/types/index.ts +146 -0
  35. package/src/Stats/index.ts +15 -0
  36. package/src/Stats/types.ts +34 -0
  37. package/src/events.ts +5 -0
  38. package/src/index.ts +120 -0
  39. package/src/lib/DataEntity.ts +38 -0
  40. package/src/lib/DefaultLogger.ts +47 -0
  41. package/src/lib/EIP712Helper.ts +57 -0
  42. package/src/lib/RestClient.ts +76 -0
  43. package/src/lib/TypedEventEmitter.ts +66 -0
  44. package/src/lib/base64.ts +9 -0
  45. package/src/lib/getUUID.ts +8 -0
  46. package/src/lib/isNodejs.ts +4 -0
  47. package/src/types/ErrorData.ts +6 -0
  48. package/src/types/json.ts +5 -0
  49. package/src/version.ts +1 -0
@@ -0,0 +1,101 @@
1
+ import DataEntity from '../lib/DataEntity';
2
+ import { BalanceData } from './types';
3
+ import { jwtDecode } from 'jwt-decode';
4
+ import { SupernetType } from '../ApiClient/WebSocketClient/types';
5
+ /**
6
+ * @inline
7
+ */
8
+ export interface AccountData {
9
+ token: string | null;
10
+ networkStatus: 'connected' | 'disconnected' | 'connecting';
11
+ network: SupernetType | null;
12
+ balance: BalanceData;
13
+ walletAddress?: string;
14
+ expiresAt?: Date;
15
+ username?: string;
16
+ }
17
+
18
+ function getDefaults(): AccountData {
19
+ return {
20
+ token: null,
21
+ networkStatus: 'disconnected',
22
+ network: null,
23
+ balance: {
24
+ credit: '0',
25
+ debit: '0',
26
+ net: '0',
27
+ settled: '0'
28
+ }
29
+ };
30
+ }
31
+
32
+ function decodeToken(token: string) {
33
+ const data = jwtDecode<{ addr: string; env: string; iat: number; exp: number }>(token);
34
+ return {
35
+ walletAddress: data.addr,
36
+ expiresAt: new Date(data.exp * 1000)
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Current account data.
42
+ * @expand
43
+ */
44
+ class CurrentAccount extends DataEntity<AccountData> {
45
+ constructor(data?: AccountData) {
46
+ super(data || getDefaults());
47
+ }
48
+
49
+ _update<K extends keyof AccountData>(delta: Partial<AccountData>) {
50
+ this.data = { ...this.data, ...(delta as Partial<AccountData>) };
51
+ const keys = Object.keys(delta);
52
+ if (delta.hasOwnProperty('token')) {
53
+ if (delta.token) {
54
+ Object.assign(this.data, decodeToken(delta.token));
55
+ } else {
56
+ delete this.data.walletAddress;
57
+ delete this.data.expiresAt;
58
+ }
59
+ keys.push('walletAddress', 'expiresAt');
60
+ }
61
+ this.emit('updated', keys);
62
+ }
63
+
64
+ _clear() {
65
+ this._update(getDefaults());
66
+ }
67
+
68
+ get isAuthenicated() {
69
+ return !!this.data.token && !!this.data.expiresAt && this.data.expiresAt > new Date();
70
+ }
71
+
72
+ get networkStatus() {
73
+ return this.data.networkStatus;
74
+ }
75
+
76
+ get network() {
77
+ return this.data.network;
78
+ }
79
+
80
+ get balance() {
81
+ return this.data.balance;
82
+ }
83
+
84
+ get walletAddress() {
85
+ return this.data.walletAddress;
86
+ }
87
+
88
+ get expiresAt() {
89
+ return this.data.expiresAt;
90
+ }
91
+
92
+ get username() {
93
+ return this.data.username;
94
+ }
95
+
96
+ get token() {
97
+ return this.data.token;
98
+ }
99
+ }
100
+
101
+ export default CurrentAccount;
@@ -0,0 +1,367 @@
1
+ import {
2
+ AccountCreateData,
3
+ BalanceData,
4
+ LoginData,
5
+ Nonce,
6
+ Reward,
7
+ RewardRaw,
8
+ TxHistoryData,
9
+ TxHistoryEntry,
10
+ TxHistoryParams
11
+ } from './types';
12
+ import ApiGroup, { ApiConfig } from '../ApiGroup';
13
+ import { Wallet, pbkdf2, toUtf8Bytes } from 'ethers';
14
+ import { ApiError, ApiReponse } from '../ApiClient';
15
+ import CurrentAccount from './CurrentAccount';
16
+ import { SupernetType } from '../ApiClient/WebSocketClient/types';
17
+
18
+ /**
19
+ * Account API methods that let you interact with the user's account.
20
+ * Can be accessed via `client.account`. Look for more samples below.
21
+ *
22
+ * @example Retrieve the current account balance
23
+ * ```typescript
24
+ * const balance = await client.account.refreshBalance();
25
+ * console.log(balance);
26
+ * ```
27
+ *
28
+ */
29
+ class AccountApi extends ApiGroup {
30
+ readonly currentAccount = new CurrentAccount();
31
+
32
+ constructor(config: ApiConfig) {
33
+ super(config);
34
+ this.client.socket.on('balanceUpdate', this.handleBalanceUpdate.bind(this));
35
+ this.client.on('connected', this.handleServerConnected.bind(this));
36
+ this.client.on('disconnected', this.handleServerDisconnected.bind(this));
37
+ }
38
+
39
+ private handleBalanceUpdate(data: BalanceData) {
40
+ this.currentAccount._update({ balance: data });
41
+ }
42
+
43
+ private handleServerConnected({ network }: { network: SupernetType }) {
44
+ this.currentAccount._update({
45
+ networkStatus: 'connected',
46
+ network
47
+ });
48
+ }
49
+
50
+ private handleServerDisconnected() {
51
+ this.currentAccount._clear();
52
+ }
53
+
54
+ private async getNonce(walletAddress: string): Promise<string> {
55
+ const res = await this.client.rest.post<ApiReponse<Nonce>>('/v1/account/nonce', {
56
+ walletAddress
57
+ });
58
+ return res.data.nonce;
59
+ }
60
+
61
+ /**
62
+ * Create Ethers.js Wallet instance from username and password.
63
+ * This method is used internally to create a wallet for the user.
64
+ * You can use this method to create a wallet if you need to sign transactions.
65
+ *
66
+ * @example Create a wallet from username and password
67
+ * ```typescript
68
+ * const wallet = client.account.getWallet('username', 'password');
69
+ * console.log(wallet.address);
70
+ * ```
71
+ *
72
+ * @param username - Sogni account username
73
+ * @param password - Sogni account password
74
+ */
75
+ getWallet(username: string, password: string): Wallet {
76
+ const pwd = toUtf8Bytes(username.toLowerCase() + password);
77
+ const salt = toUtf8Bytes('sogni-salt-value');
78
+ const pkey = pbkdf2(pwd, salt, 10000, 32, 'sha256');
79
+ return new Wallet(pkey, this.provider);
80
+ }
81
+
82
+ /**
83
+ * Create a new account with the given username, email, and password.
84
+ * @internal
85
+ *
86
+ * @param username
87
+ * @param email
88
+ * @param password
89
+ * @param subscribe
90
+ * @param referralCode
91
+ */
92
+ async create(
93
+ username: string,
94
+ email: string,
95
+ password: string,
96
+ subscribe = false,
97
+ referralCode?: string
98
+ ): Promise<AccountCreateData> {
99
+ const wallet = this.getWallet(username, password);
100
+ const nonce = await this.getNonce(wallet.address);
101
+ const payload = {
102
+ appid: this.client.appId,
103
+ username,
104
+ email,
105
+ subscribe: subscribe ? 1 : 0,
106
+ walletAddress: wallet.address
107
+ };
108
+ const signature = await this.eip712.signTypedData(wallet, 'signup', { ...payload, nonce });
109
+ const res = await this.client.rest.post<ApiReponse<AccountCreateData>>('/v1/account/create', {
110
+ ...payload,
111
+ referralCode,
112
+ signature
113
+ });
114
+ this.setToken(username, res.data.token);
115
+ return res.data;
116
+ }
117
+
118
+ /**
119
+ * Restore session with username and access token.
120
+ *
121
+ * You can save access token that you get from the login method and restore the session with this method.
122
+ *
123
+ * @example Store access token to local storage
124
+ * ```typescript
125
+ * const { username, token } = await client.account.login('username', 'password');
126
+ * localStorage.setItem('sogni-username', username);
127
+ * localStorage.setItem('sogni-token', token);
128
+ * ```
129
+ *
130
+ * @example Restore session from local storage
131
+ * ```typescript
132
+ * const username = localStorage.getItem('sogni-username');
133
+ * const token = localStorage.getItem('sogni-token');
134
+ * if (username && token) {
135
+ * client.account.setToken(username, token);
136
+ * console.log('Session restored');
137
+ * }
138
+ * ```
139
+ *
140
+ * @param username
141
+ * @param token
142
+ */
143
+ setToken(username: string, token: string): void {
144
+ this.client.authenticate(token);
145
+ this.currentAccount._update({
146
+ token,
147
+ username
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Login with username and password. WebSocket connection is established after successful login.
153
+ *
154
+ * @example Login with username and password
155
+ * ```typescript
156
+ * await client.account.login('username', 'password');
157
+ * console.log('Logged in');
158
+ * ```
159
+ *
160
+ * @param username
161
+ * @param password
162
+ */
163
+ async login(username: string, password: string): Promise<LoginData> {
164
+ const wallet = this.getWallet(username, password);
165
+ const nonce = await this.getNonce(wallet.address);
166
+ const signature = await this.eip712.signTypedData(wallet, 'authentication', {
167
+ walletAddress: wallet.address,
168
+ nonce
169
+ });
170
+ const res = await this.client.rest.post<ApiReponse<LoginData>>('/v1/account/login', {
171
+ walletAddress: wallet.address,
172
+ signature
173
+ });
174
+ this.setToken(username, res.data.token);
175
+ return res.data;
176
+ }
177
+
178
+ /**
179
+ * Logout the user and close the WebSocket connection.
180
+ *
181
+ * @example Logout the user
182
+ * ```typescript
183
+ * await client.account.logout();
184
+ * console.log('Logged out');
185
+ * ```
186
+ */
187
+ async logout(): Promise<void> {
188
+ this.client.rest.post('/v1/account/logout').catch((e) => {
189
+ this.client.logger.error('Failed to logout', e);
190
+ });
191
+ this.client.removeAuth();
192
+ this.currentAccount._clear();
193
+ }
194
+
195
+ /**
196
+ * Refresh the balance of the current account.
197
+ *
198
+ * Usually, you don't need to call this method manually. Balance is updated automatically
199
+ * through WebSocket events. But you can call this method to force a balance refresh.
200
+ *
201
+ * @example Refresh user account balance
202
+ * ```typescript
203
+ * const balance = await client.account.refreshBalance();
204
+ * console.log(balance);
205
+ * // { net: '100.000000', settled: '100.000000', credit: '0.000000', debit: '0.000000' }
206
+ * ```
207
+ */
208
+ async refreshBalance(): Promise<BalanceData> {
209
+ const res = await this.client.rest.get<ApiReponse<BalanceData>>('/v1/account/balance');
210
+ this.currentAccount._update({ balance: res.data });
211
+ return res.data;
212
+ }
213
+
214
+ /**
215
+ * Get the balance of the wallet address.
216
+ *
217
+ * This method is used to get the balance of the wallet address. It returns $SOGNI and ETH balance.
218
+ *
219
+ * @example Get the balance of the wallet address
220
+ * ```typescript
221
+ * const address = client.account.currentAccount.walletAddress;
222
+ * const balance = await client.account.walletBalance(address);
223
+ * console.log(balance);
224
+ * // { token: '100.000000', ether: '0.000000' }
225
+ * ```
226
+ *
227
+ * @param walletAddress
228
+ */
229
+ async walletBalance(walletAddress: string) {
230
+ const res = await this.client.rest.get<ApiReponse<{ token: string; ether: string }>>(
231
+ '/v1/wallet/balance',
232
+ {
233
+ walletAddress
234
+ }
235
+ );
236
+ return res.data;
237
+ }
238
+
239
+ /**
240
+ * Validate the username before signup
241
+ * @internal
242
+ * @param username
243
+ */
244
+ async validateUsername(username: string) {
245
+ try {
246
+ return await this.client.rest.post<ApiReponse<undefined>>('/v1/account/username/validate', {
247
+ username
248
+ });
249
+ } catch (e) {
250
+ if (e instanceof ApiError) {
251
+ // Username is already taken
252
+ if (e.payload.errorCode === 108) {
253
+ return e.payload;
254
+ }
255
+ }
256
+ throw e;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Switch between fast and relaxed networks.
262
+ * Note: This method will close the current WebSocket connection and establish a new one.
263
+ * Do not call this method if you have any active projects.
264
+ *
265
+ * @example Switch to the fast network
266
+ * ```typescript
267
+ * client.apiClient.once('connected', ({ network }) => {
268
+ * console.log('Switched to the network:', network);
269
+ * });
270
+ * await client.account.switchNetwork('fast');
271
+ * ```
272
+ * @param network
273
+ */
274
+ async switchNetwork(network: SupernetType) {
275
+ this.currentAccount._update({
276
+ networkStatus: 'connecting',
277
+ network: null
278
+ });
279
+ this.client.socket.switchNetwork(network);
280
+ }
281
+
282
+ /**
283
+ * Get the transaction history of the current account.
284
+ *
285
+ * @example Get the transaction history
286
+ * ```typescript
287
+ * const { entries, next } = await client.account.transactionHistory({
288
+ * status: 'completed',
289
+ * limit: 10,
290
+ * address: client.account.currentAccount.walletAddress
291
+ * });
292
+ * ```
293
+ *
294
+ * @param params - Transaction history query parameters
295
+ * @returns Transaction history entries and next query parameters
296
+ */
297
+ async transactionHistory(
298
+ params: TxHistoryParams
299
+ ): Promise<{ entries: TxHistoryEntry[]; next: TxHistoryParams }> {
300
+ const res = await this.client.rest.get<ApiReponse<TxHistoryData>>('/v1/transactions/list', {
301
+ status: params.status,
302
+ address: params.address,
303
+ limit: params.limit.toString()
304
+ });
305
+
306
+ return {
307
+ entries: res.data.transactions.map(
308
+ (tx): TxHistoryEntry => ({
309
+ id: tx.id,
310
+ address: tx.address,
311
+ createTime: new Date(tx.createTime),
312
+ updateTime: new Date(tx.updateTime),
313
+ status: tx.status,
314
+ role: tx.role,
315
+ amount: tx.amount,
316
+ description: tx.description,
317
+ source: tx.source,
318
+ endTime: new Date(tx.endTime),
319
+ type: tx.type
320
+ })
321
+ ),
322
+ next: {
323
+ ...params,
324
+ offset: res.data.next
325
+ }
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Get the rewards of the current account.
331
+ * @internal
332
+ */
333
+ async rewards(): Promise<Reward[]> {
334
+ const r =
335
+ await this.client.rest.get<ApiReponse<{ rewards: RewardRaw[] }>>('/v2/account/rewards');
336
+
337
+ return r.data.rewards.map(
338
+ (raw: RewardRaw): Reward => ({
339
+ id: raw.id,
340
+ type: raw.type,
341
+ title: raw.title,
342
+ description: raw.description,
343
+ amount: raw.amount,
344
+ claimed: !!raw.claimed,
345
+ canClaim: !!raw.canClaim,
346
+ lastClaim: new Date(raw.lastClaimTimestamp * 1000),
347
+ nextClaim:
348
+ raw.lastClaimTimestamp && raw.claimResetFrequencySec > -1
349
+ ? new Date(raw.lastClaimTimestamp * 1000 + raw.claimResetFrequencySec * 1000)
350
+ : null
351
+ })
352
+ );
353
+ }
354
+
355
+ /**
356
+ * Claim rewards by reward IDs.
357
+ * @internal
358
+ * @param rewardIds
359
+ */
360
+ async claimRewards(rewardIds: string[]): Promise<void> {
361
+ await this.client.rest.post('/v2/account/reward/claim', {
362
+ claims: rewardIds
363
+ });
364
+ }
365
+ }
366
+
367
+ export default AccountApi;
@@ -0,0 +1,90 @@
1
+ export interface Nonce {
2
+ nonce: string;
3
+ }
4
+
5
+ export interface AccountCreateData {
6
+ token: string;
7
+ }
8
+
9
+ export interface LoginData {
10
+ token: string;
11
+ username: string;
12
+ }
13
+
14
+ export interface BalanceData {
15
+ settled: string;
16
+ credit: string;
17
+ debit: string;
18
+ net: string;
19
+ }
20
+
21
+ export interface TxHistoryParams {
22
+ status: 'completed';
23
+ address: string;
24
+ limit: number;
25
+ offset?: number;
26
+ }
27
+
28
+ export interface TxHistoryData {
29
+ transactions: TxRaw[];
30
+ next: number;
31
+ }
32
+
33
+ export interface TxRaw {
34
+ _id: string;
35
+ id: string;
36
+ SID: number;
37
+ address: string;
38
+ createTime: number;
39
+ updateTime: number;
40
+ status: 'completed';
41
+ role: 'artist' | 'worker';
42
+ clientSID: number;
43
+ addressSID: number;
44
+ amount: number;
45
+ description: string;
46
+ source: 'project' | string;
47
+ sourceSID: string;
48
+ endTime: number;
49
+ type: 'debit' | string;
50
+ }
51
+
52
+ export interface TxHistoryEntry {
53
+ id: string;
54
+ address: string;
55
+ createTime: Date;
56
+ updateTime: Date;
57
+ status: 'completed';
58
+ role: 'artist' | 'worker';
59
+ amount: number;
60
+ description: string;
61
+ source: 'project' | string;
62
+ endTime: Date;
63
+ type: 'debit' | string;
64
+ }
65
+
66
+ export type RewardType = 'instant' | 'conditioned';
67
+
68
+ export interface RewardRaw {
69
+ id: string;
70
+ type: RewardType;
71
+ title: string;
72
+ description: string;
73
+ amount: string;
74
+ claimed: number;
75
+ canClaim: number;
76
+ lastClaimTimestamp: number;
77
+ claimResetFrequencySec: number;
78
+ }
79
+
80
+ export interface Reward {
81
+ id: string;
82
+ type: RewardType;
83
+ title: string;
84
+ description: string;
85
+ amount: string;
86
+ claimed: boolean;
87
+ canClaim: boolean;
88
+ lastClaim: Date;
89
+ nextClaim: Date | null;
90
+ }
@@ -0,0 +1,15 @@
1
+ export enum ErrorCode {
2
+ // App ID is blocked from connecting
3
+ APP_ID_BLOCKED = 4010,
4
+ // New connection from same app-id, server will switch to it
5
+ SWITCH_CONNECTION = 4015,
6
+ // Authentication error happened
7
+ AUTH_ERROR = 4021
8
+ }
9
+
10
+ export function isNotRecoverable(code: ErrorCode) {
11
+ //check if code is in 4xxx
12
+ return code >= 4000 && code < 5000;
13
+ }
14
+
15
+ export default ErrorCode;
@@ -0,0 +1,94 @@
1
+ import { SupernetType } from './types';
2
+
3
+ export type BalanceData = {
4
+ settled: string;
5
+ credit: string;
6
+ debit: string;
7
+ net: string;
8
+ };
9
+
10
+ export type JobErrorData = {
11
+ jobID: string;
12
+ imgID?: string;
13
+ isFromWorker: boolean;
14
+ error_message: string;
15
+ error: number;
16
+ };
17
+
18
+ export type JobProgressData = {
19
+ jobID: string;
20
+ imgID: string;
21
+ hasImage: boolean;
22
+ step: number;
23
+ stepCount: number;
24
+ };
25
+
26
+ export type JobResultData = {
27
+ jobID: string;
28
+ imgID: string;
29
+ performedStepCount: number;
30
+ lastSeed: string;
31
+ userCanceled: boolean;
32
+ triggeredNSFWFilter: boolean;
33
+ };
34
+
35
+ export type JobStateData =
36
+ | {
37
+ type: 'initiatingModel' | 'jobStarted';
38
+ jobID: string;
39
+ imgID: string;
40
+ workerName: string;
41
+ }
42
+ | {
43
+ jobID: string;
44
+ type: 'queued';
45
+ queuePosition: number;
46
+ }
47
+ | {
48
+ type: 'jobCompleted';
49
+ jobID: string;
50
+ };
51
+
52
+ export type ServerConnectData = {
53
+ network: SupernetType;
54
+ };
55
+
56
+ export type ServerDisconnectData = {
57
+ code: number;
58
+ reason: string;
59
+ };
60
+
61
+ export type SocketEventMap = {
62
+ /**
63
+ * @event WebSocketClient#balanceUpdate - Received balance update
64
+ */
65
+ balanceUpdate: BalanceData;
66
+ /**
67
+ * @event WebSocketClient#jobError - Job error occurred
68
+ */
69
+ jobError: JobErrorData;
70
+ /**
71
+ * @event WebSocketClient#jobProgress - Job progress update
72
+ */
73
+ jobProgress: JobProgressData;
74
+ /**
75
+ * @event WebSocketClient#jobResult - Job result received
76
+ */
77
+ jobResult: JobResultData;
78
+ /**
79
+ * @event WebSocketClient#jobState - Job state changed
80
+ */
81
+ jobState: JobStateData;
82
+ /**
83
+ * @event WebSocketClient#swarmModels - Received swarm model count
84
+ */
85
+ swarmModels: Record<string, number>;
86
+ /**
87
+ * @event WebSocketClient#connected - WebSocket connection opened
88
+ */
89
+ connected: ServerConnectData;
90
+ /**
91
+ * @event WebSocketClient#disconnected - WebSocket connection was closed
92
+ */
93
+ disconnected: ServerDisconnectData;
94
+ };