@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.
- package/README.md +3 -2
- package/dist/Account/index.d.ts +127 -3
- package/dist/Account/index.js +126 -2
- package/dist/Account/index.js.map +1 -1
- package/dist/Projects/Job.d.ts +5 -0
- package/dist/Projects/Job.js +6 -0
- package/dist/Projects/Job.js.map +1 -1
- package/dist/Projects/index.d.ts +6 -6
- package/dist/Projects/index.js +12 -2
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/events.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -3
- package/src/Account/CurrentAccount.ts +101 -0
- package/src/Account/index.ts +367 -0
- package/src/Account/types.ts +90 -0
- package/src/ApiClient/WebSocketClient/ErrorCode.ts +15 -0
- package/src/ApiClient/WebSocketClient/events.ts +94 -0
- package/src/ApiClient/WebSocketClient/index.ts +203 -0
- package/src/ApiClient/WebSocketClient/messages.ts +7 -0
- package/src/ApiClient/WebSocketClient/types.ts +1 -0
- package/src/ApiClient/events.ts +20 -0
- package/src/ApiClient/index.ts +124 -0
- package/src/ApiGroup.ts +25 -0
- package/src/Projects/Job.ts +132 -0
- package/src/Projects/Project.ts +185 -0
- package/src/Projects/createJobRequestMessage.ts +99 -0
- package/src/Projects/index.ts +350 -0
- package/src/Projects/models.json +8906 -0
- package/src/Projects/types/EstimationResponse.ts +45 -0
- package/src/Projects/types/events.ts +80 -0
- package/src/Projects/types/index.ts +146 -0
- package/src/Stats/index.ts +15 -0
- package/src/Stats/types.ts +34 -0
- package/src/events.ts +5 -0
- package/src/index.ts +120 -0
- package/src/lib/DataEntity.ts +38 -0
- package/src/lib/DefaultLogger.ts +47 -0
- package/src/lib/EIP712Helper.ts +57 -0
- package/src/lib/RestClient.ts +76 -0
- package/src/lib/TypedEventEmitter.ts +66 -0
- package/src/lib/base64.ts +9 -0
- package/src/lib/getUUID.ts +8 -0
- package/src/lib/isNodejs.ts +4 -0
- package/src/types/ErrorData.ts +6 -0
- package/src/types/json.ts +5 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface EstimationResponse {
|
|
2
|
+
request: Request;
|
|
3
|
+
rate: Rate;
|
|
4
|
+
quote: Quote;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface Quote {
|
|
8
|
+
model: Model;
|
|
9
|
+
job: Job;
|
|
10
|
+
project: Job;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Job {
|
|
14
|
+
costInRenderSec: string;
|
|
15
|
+
costInUSD: string;
|
|
16
|
+
costInToken: string;
|
|
17
|
+
calculatedStepCount?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Model {
|
|
21
|
+
weight: string;
|
|
22
|
+
secPerStep: string;
|
|
23
|
+
secPerPreview: string;
|
|
24
|
+
secForCN: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Rate {
|
|
28
|
+
costPerBaseHQRenderInUSD: string;
|
|
29
|
+
tokenMarkePriceUSD: string;
|
|
30
|
+
costPerRenderSecUSD: string;
|
|
31
|
+
costPerRenderSecToken: string;
|
|
32
|
+
network: string;
|
|
33
|
+
networkCostMultiplier: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Request {
|
|
37
|
+
model: string;
|
|
38
|
+
name: string;
|
|
39
|
+
imageCount: number;
|
|
40
|
+
stepCount: number;
|
|
41
|
+
previewCount: number;
|
|
42
|
+
cnEnabled: boolean;
|
|
43
|
+
denoiseStrength: string;
|
|
44
|
+
time: Date;
|
|
45
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { AvailableModel } from './index';
|
|
2
|
+
import ErrorData from '../../types/ErrorData';
|
|
3
|
+
|
|
4
|
+
export interface ProjectEventBase {
|
|
5
|
+
projectId: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ProjectQueued extends ProjectEventBase {
|
|
9
|
+
type: 'queued';
|
|
10
|
+
queuePosition: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ProjectCompleted extends ProjectEventBase {
|
|
14
|
+
type: 'completed';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProjectError extends ProjectEventBase {
|
|
18
|
+
type: 'error';
|
|
19
|
+
error: ErrorData;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ProjectEvent = ProjectQueued | ProjectCompleted | ProjectError;
|
|
23
|
+
|
|
24
|
+
export interface JobEventBase {
|
|
25
|
+
projectId: string;
|
|
26
|
+
jobId: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface JobInitiating extends JobEventBase {
|
|
30
|
+
type: 'initiating';
|
|
31
|
+
workerName: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface JobStarted extends JobEventBase {
|
|
35
|
+
type: 'started';
|
|
36
|
+
workerName: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface JobProgress extends JobEventBase {
|
|
40
|
+
type: 'progress';
|
|
41
|
+
step: number;
|
|
42
|
+
stepCount: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface JobPreview extends JobEventBase {
|
|
46
|
+
type: 'preview';
|
|
47
|
+
url: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface JobCompleted extends JobEventBase {
|
|
51
|
+
type: 'completed';
|
|
52
|
+
steps: number;
|
|
53
|
+
seed: number;
|
|
54
|
+
/**
|
|
55
|
+
* URL to the result image, could be null if the job was canceled or triggered NSFW filter while
|
|
56
|
+
* it was not disabled by the user
|
|
57
|
+
*/
|
|
58
|
+
resultUrl: string | null;
|
|
59
|
+
isNSFW: boolean;
|
|
60
|
+
userCanceled: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface JobError extends JobEventBase {
|
|
64
|
+
type: 'error';
|
|
65
|
+
error: ErrorData;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type JobEvent =
|
|
69
|
+
| JobInitiating
|
|
70
|
+
| JobStarted
|
|
71
|
+
| JobProgress
|
|
72
|
+
| JobPreview
|
|
73
|
+
| JobCompleted
|
|
74
|
+
| JobError;
|
|
75
|
+
|
|
76
|
+
export interface ProjectApiEvents {
|
|
77
|
+
availableModels: AvailableModel[];
|
|
78
|
+
project: ProjectEvent;
|
|
79
|
+
job: JobEvent;
|
|
80
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { SupernetType } from '../../ApiClient/WebSocketClient/types';
|
|
2
|
+
|
|
3
|
+
export interface AvailableModel {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
workerCount: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AiModel {
|
|
10
|
+
isSD3: boolean;
|
|
11
|
+
modelShortName: string;
|
|
12
|
+
isIOSslowest: boolean;
|
|
13
|
+
hasOriginalVersionOnly: boolean;
|
|
14
|
+
isUserModel: boolean;
|
|
15
|
+
isTurboXL: boolean;
|
|
16
|
+
isRealistic: boolean;
|
|
17
|
+
isArtistic: boolean;
|
|
18
|
+
tier: string;
|
|
19
|
+
splitAttentionSuffix: string;
|
|
20
|
+
isSD3XL: boolean;
|
|
21
|
+
originalAttentionSuffix: string;
|
|
22
|
+
isLCM: boolean;
|
|
23
|
+
zipWeight: number;
|
|
24
|
+
modelId: string;
|
|
25
|
+
modelVersion: string;
|
|
26
|
+
parentId: string;
|
|
27
|
+
quantized: boolean;
|
|
28
|
+
isXL: boolean;
|
|
29
|
+
splitAttentionV2Suffix: string;
|
|
30
|
+
supportsAttentionV2: boolean;
|
|
31
|
+
supportsControlNet: boolean;
|
|
32
|
+
supportsEncoder: boolean;
|
|
33
|
+
onlySplitEinsumV2available: boolean;
|
|
34
|
+
customSize?: number[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type Scheduler =
|
|
38
|
+
| 'DPM Solver Multistep (DPM-Solver++)'
|
|
39
|
+
| 'PNDM (Pseudo-linear multi-step)'
|
|
40
|
+
| 'LCM (Latent Consistency Model)'
|
|
41
|
+
| 'Discrete Flow Scheduler (SD3)'
|
|
42
|
+
| 'Euler'; // Used by Flux
|
|
43
|
+
|
|
44
|
+
export type TimeStepSpacing = 'Karras' | 'Leading' | 'Linear';
|
|
45
|
+
|
|
46
|
+
export interface ProjectParams {
|
|
47
|
+
/**
|
|
48
|
+
* ID of the model to use, available models are available in the `availableModels` property of the `ProjectsApi` instance.
|
|
49
|
+
*/
|
|
50
|
+
modelId: string;
|
|
51
|
+
/**
|
|
52
|
+
* Prompt for what to be created
|
|
53
|
+
*/
|
|
54
|
+
positivePrompt: string;
|
|
55
|
+
/**
|
|
56
|
+
* Prompt for what to be avoided
|
|
57
|
+
*/
|
|
58
|
+
negativePrompt: string;
|
|
59
|
+
/**
|
|
60
|
+
* Image style prompt
|
|
61
|
+
*/
|
|
62
|
+
stylePrompt: string;
|
|
63
|
+
/**
|
|
64
|
+
* Number of steps. For most Stable Diffusion models, optimal value is 20
|
|
65
|
+
*/
|
|
66
|
+
steps: number;
|
|
67
|
+
/**
|
|
68
|
+
* Guidance scale. For most Stable Diffusion models, optimal value is 7.5
|
|
69
|
+
*/
|
|
70
|
+
guidance: number;
|
|
71
|
+
/**
|
|
72
|
+
* Disable NSFW filter for Project. Default is false, meaning NSFW filter is enabled.
|
|
73
|
+
* If image triggers NSFW filter, it will not be available for download.
|
|
74
|
+
*/
|
|
75
|
+
disableNSFWFilter?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Seed for one of images in project. Other will get random seed. Must be Uint32
|
|
78
|
+
*/
|
|
79
|
+
seed?: number;
|
|
80
|
+
/**
|
|
81
|
+
* Number of images to generate
|
|
82
|
+
*/
|
|
83
|
+
numberOfImages: number;
|
|
84
|
+
/**
|
|
85
|
+
* Generate images based on starting image.
|
|
86
|
+
* `File` - file object from input[type=file]
|
|
87
|
+
* `Buffer` - buffer object with image data
|
|
88
|
+
* `Blob` - blob object with image data
|
|
89
|
+
*/
|
|
90
|
+
startingImage?: File | Buffer | Blob;
|
|
91
|
+
/**
|
|
92
|
+
* How strong effect of starting image should be. From 0 to 1, default 0.5
|
|
93
|
+
*/
|
|
94
|
+
startingImageStrength?: number;
|
|
95
|
+
/**
|
|
96
|
+
* Number of previews to generate. Note that previews affect project cost\
|
|
97
|
+
*/
|
|
98
|
+
numberOfPreviews?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Scheduler to use
|
|
101
|
+
*/
|
|
102
|
+
scheduler?: Scheduler;
|
|
103
|
+
/**
|
|
104
|
+
* Time step spacing method
|
|
105
|
+
*/
|
|
106
|
+
timeStepSpacing?: TimeStepSpacing;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type ImageUrlParams = {
|
|
110
|
+
imageId: string;
|
|
111
|
+
jobId: string;
|
|
112
|
+
type: 'preview' | 'complete' | 'startingImage' | 'cnImage';
|
|
113
|
+
// This seems to be unused currently
|
|
114
|
+
startContentType?: string;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export interface EstimateRequest {
|
|
118
|
+
/**
|
|
119
|
+
* Network to use. Can be 'fast' or 'relaxed'
|
|
120
|
+
*/
|
|
121
|
+
network: SupernetType;
|
|
122
|
+
/**
|
|
123
|
+
* Model ID
|
|
124
|
+
*/
|
|
125
|
+
model: string;
|
|
126
|
+
/**
|
|
127
|
+
* Number of images to generate
|
|
128
|
+
*/
|
|
129
|
+
imageCount: number;
|
|
130
|
+
/**
|
|
131
|
+
* Number of steps
|
|
132
|
+
*/
|
|
133
|
+
stepCount: number;
|
|
134
|
+
/**
|
|
135
|
+
* Number of preview images to generate
|
|
136
|
+
*/
|
|
137
|
+
previewCount: number;
|
|
138
|
+
/**
|
|
139
|
+
* Control network enabled
|
|
140
|
+
*/
|
|
141
|
+
cnEnabled?: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* How strong effect of starting image should be. From 0 to 1, default 0.5
|
|
144
|
+
*/
|
|
145
|
+
startingImageStrength?: number;
|
|
146
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import ApiGroup from '../ApiGroup';
|
|
2
|
+
import { ApiReponse } from '../ApiClient';
|
|
3
|
+
import { LeaderboardItem, LeaderboardParams } from './types';
|
|
4
|
+
|
|
5
|
+
class StatsApi extends ApiGroup {
|
|
6
|
+
async leaderboard(params: LeaderboardParams) {
|
|
7
|
+
const res = await this.client.rest.get<ApiReponse<LeaderboardItem[]>>(
|
|
8
|
+
'/v1/leaderboard/',
|
|
9
|
+
params
|
|
10
|
+
);
|
|
11
|
+
return res.data;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default StatsApi;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type LeaderboardType =
|
|
2
|
+
| 'renderUSDCompleteWorker'
|
|
3
|
+
| 'renderUSDCompleteArtist'
|
|
4
|
+
| 'renderSecCompleteWorker'
|
|
5
|
+
| 'renderSecCompleteArtist'
|
|
6
|
+
| 'renderTokenCompleteWorker'
|
|
7
|
+
| 'renderTokenCompleteArtist'
|
|
8
|
+
| 'jobCompleteWorker'
|
|
9
|
+
| 'jobCompleteArtist'
|
|
10
|
+
| 'projectCompleteArtist'
|
|
11
|
+
| 'uloginWorker'
|
|
12
|
+
| 'uloginArtist'
|
|
13
|
+
| 'tokenVolume'
|
|
14
|
+
| 'referral';
|
|
15
|
+
|
|
16
|
+
export interface LeaderboardParams {
|
|
17
|
+
type: LeaderboardType;
|
|
18
|
+
period: 'day' | 'lifetime';
|
|
19
|
+
username?: string;
|
|
20
|
+
network?: 'fast' | 'relaxed' | 'all';
|
|
21
|
+
page?: number;
|
|
22
|
+
limit?: number;
|
|
23
|
+
date?: number;
|
|
24
|
+
address?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LeaderboardItem {
|
|
28
|
+
rank: number;
|
|
29
|
+
username: string;
|
|
30
|
+
address: string;
|
|
31
|
+
country: string;
|
|
32
|
+
value: string;
|
|
33
|
+
role: string;
|
|
34
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { ApiClientEvents } from './ApiClient/events';
|
|
2
|
+
export type { ProjectApiEvents } from './Projects/types/events';
|
|
3
|
+
export type { EntityEvents } from './lib/DataEntity';
|
|
4
|
+
export type { JobEventMap } from './Projects/Job';
|
|
5
|
+
export type { ProjectEventMap } from './Projects/Project';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { AbstractProvider, JsonRpcProvider, getDefaultProvider } from 'ethers';
|
|
2
|
+
// Account API
|
|
3
|
+
import AccountApi from './Account';
|
|
4
|
+
import CurrentAccount from './Account/CurrentAccount';
|
|
5
|
+
// ApiClient
|
|
6
|
+
import ApiClient, { ApiError } from './ApiClient';
|
|
7
|
+
import { SupernetType } from './ApiClient/WebSocketClient/types';
|
|
8
|
+
import { ApiConfig } from './ApiGroup';
|
|
9
|
+
// Utils
|
|
10
|
+
import { DefaultLogger, Logger, LogLevel } from './lib/DefaultLogger';
|
|
11
|
+
import EIP712Helper from './lib/EIP712Helper';
|
|
12
|
+
// Projects API
|
|
13
|
+
import ProjectsApi from './Projects';
|
|
14
|
+
import Job, { JobStatus } from './Projects/Job';
|
|
15
|
+
import Project, { ProjectStatus } from './Projects/Project';
|
|
16
|
+
import { AvailableModel, ProjectParams, Scheduler, TimeStepSpacing } from './Projects/types';
|
|
17
|
+
// Stats API
|
|
18
|
+
import StatsApi from './Stats';
|
|
19
|
+
// Base Types
|
|
20
|
+
import ErrorData from './types/ErrorData';
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
AvailableModel,
|
|
24
|
+
ErrorData,
|
|
25
|
+
JobStatus,
|
|
26
|
+
Logger,
|
|
27
|
+
LogLevel,
|
|
28
|
+
ProjectParams,
|
|
29
|
+
ProjectStatus,
|
|
30
|
+
Scheduler,
|
|
31
|
+
SupernetType,
|
|
32
|
+
TimeStepSpacing
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { ApiError, CurrentAccount, Job, Project };
|
|
36
|
+
|
|
37
|
+
export interface SogniClientConfig {
|
|
38
|
+
/**
|
|
39
|
+
* The application ID string. Must be unique, multiple connections with the same ID will be rejected.
|
|
40
|
+
*/
|
|
41
|
+
appId: string;
|
|
42
|
+
/**
|
|
43
|
+
* Override the default REST API endpoint
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
restEndpoint?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Override the default WebSocket API endpoint
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
socketEndpoint?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Which network to use after logging in. Can be 'fast' or 'relaxed'
|
|
54
|
+
*/
|
|
55
|
+
network: SupernetType;
|
|
56
|
+
/**
|
|
57
|
+
* Logger to use. If not provided, a default console logger will be used
|
|
58
|
+
*/
|
|
59
|
+
logger?: Logger;
|
|
60
|
+
/**
|
|
61
|
+
* Log level to use. This option is ignored if a logger is provided
|
|
62
|
+
* @default 'warn'
|
|
63
|
+
**/
|
|
64
|
+
logLevel?: LogLevel;
|
|
65
|
+
/**
|
|
66
|
+
* If provided, the client will connect to this JSON-RPC endpoint to interact with the blockchain
|
|
67
|
+
*/
|
|
68
|
+
jsonRpcUrl?: string;
|
|
69
|
+
/**
|
|
70
|
+
* If true, the client will connect to the testnet. Ignored if jsonRpcUrl is provided
|
|
71
|
+
*/
|
|
72
|
+
testnet?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class SogniClient {
|
|
76
|
+
account: AccountApi;
|
|
77
|
+
projects: ProjectsApi;
|
|
78
|
+
stats: StatsApi;
|
|
79
|
+
|
|
80
|
+
apiClient: ApiClient;
|
|
81
|
+
|
|
82
|
+
private constructor(config: ApiConfig) {
|
|
83
|
+
this.account = new AccountApi(config);
|
|
84
|
+
this.projects = new ProjectsApi(config);
|
|
85
|
+
this.stats = new StatsApi(config);
|
|
86
|
+
|
|
87
|
+
this.apiClient = config.client;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get currentAccount() {
|
|
91
|
+
return this.account.currentAccount;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Instance creation may involve async operations, so we use a static method
|
|
96
|
+
* @param config
|
|
97
|
+
*/
|
|
98
|
+
static async createInstance(config: SogniClientConfig): Promise<SogniClient> {
|
|
99
|
+
const restEndpoint = config.restEndpoint || 'https://api.sogni.ai';
|
|
100
|
+
const socketEndpoint = config.socketEndpoint || 'wss://socket.sogni.ai';
|
|
101
|
+
const network = config.network || 'fast';
|
|
102
|
+
const logger = config.logger || new DefaultLogger(config.logLevel || 'warn');
|
|
103
|
+
const isTestnet = config.testnet !== undefined ? config.testnet : true;
|
|
104
|
+
|
|
105
|
+
const client = new ApiClient(restEndpoint, socketEndpoint, config.appId, network, logger);
|
|
106
|
+
let provider: AbstractProvider;
|
|
107
|
+
if ('jsonRpcUrl' in config) {
|
|
108
|
+
provider = new JsonRpcProvider(config.jsonRpcUrl);
|
|
109
|
+
} else {
|
|
110
|
+
provider = getDefaultProvider(isTestnet ? 84532 : 8453);
|
|
111
|
+
}
|
|
112
|
+
const chainId = await provider.getNetwork().then((network) => network.chainId);
|
|
113
|
+
const eip712 = new EIP712Helper({
|
|
114
|
+
name: 'Sogni-testnet',
|
|
115
|
+
version: '1',
|
|
116
|
+
chainId: chainId.toString()
|
|
117
|
+
});
|
|
118
|
+
return new SogniClient({ client, provider, eip712 });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { cloneDeep } from 'lodash';
|
|
2
|
+
import TypedEventEmitter from './TypedEventEmitter';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @inline
|
|
6
|
+
*/
|
|
7
|
+
export interface EntityEvents {
|
|
8
|
+
updated: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
abstract class DataEntity<D, E extends EntityEvents = EntityEvents> extends TypedEventEmitter<E> {
|
|
12
|
+
protected data: D;
|
|
13
|
+
|
|
14
|
+
constructor(data: D) {
|
|
15
|
+
super();
|
|
16
|
+
this.data = data;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @internal
|
|
21
|
+
* @param delta
|
|
22
|
+
*/
|
|
23
|
+
_update(delta: Partial<D>) {
|
|
24
|
+
//@ts-ignore
|
|
25
|
+
const changedKeys = Object.keys(delta).filter((key) => this.data[key] !== delta[key]);
|
|
26
|
+
this.data = { ...this.data, ...delta };
|
|
27
|
+
this.emit('updated', changedKeys);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get a copy of the entity's data
|
|
32
|
+
*/
|
|
33
|
+
toJSON(): D {
|
|
34
|
+
return cloneDeep(this.data);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default DataEntity;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
|
|
2
|
+
|
|
3
|
+
export const LOG_LEVELS: Record<LogLevel, number> = {
|
|
4
|
+
error: 0,
|
|
5
|
+
warn: 1,
|
|
6
|
+
info: 2,
|
|
7
|
+
debug: 3
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface Logger {
|
|
11
|
+
error(...args: any[]): void;
|
|
12
|
+
warn(...args: any[]): void;
|
|
13
|
+
info(...args: any[]): void;
|
|
14
|
+
debug(...args: any[]): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class DefaultLogger implements Logger {
|
|
18
|
+
private _level: number;
|
|
19
|
+
|
|
20
|
+
constructor(level: LogLevel = 'warn') {
|
|
21
|
+
this._level = LOG_LEVELS[level];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
error(...args: any[]) {
|
|
25
|
+
if (this._level >= LOG_LEVELS.error) {
|
|
26
|
+
console.error(...args);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
warn(...args: any[]) {
|
|
31
|
+
if (this._level >= LOG_LEVELS.warn) {
|
|
32
|
+
console.warn(...args);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
info(...args: any[]) {
|
|
37
|
+
if (this._level >= LOG_LEVELS.info) {
|
|
38
|
+
console.info(...args);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
debug(...args: any[]) {
|
|
43
|
+
if (this._level >= LOG_LEVELS.debug) {
|
|
44
|
+
console.debug(...args);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AbstractSigner, TypedDataDomain, TypedDataField } from 'ethers';
|
|
2
|
+
|
|
3
|
+
type EIP712Types = Record<string, TypedDataField[]>;
|
|
4
|
+
type SupportedTypes = 'authentication' | 'signup';
|
|
5
|
+
|
|
6
|
+
const EIP712_TYPES: Record<SupportedTypes, EIP712Types> = {
|
|
7
|
+
authentication: {
|
|
8
|
+
Authentication: [
|
|
9
|
+
{ name: 'walletAddress', type: 'address' },
|
|
10
|
+
{ name: 'nonce', type: 'string' }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
signup: {
|
|
14
|
+
Signup: [
|
|
15
|
+
{ name: 'appid', type: 'string' },
|
|
16
|
+
{ name: 'username', type: 'string' },
|
|
17
|
+
{ name: 'email', type: 'string' },
|
|
18
|
+
{ name: 'subscribe', type: 'uint256' },
|
|
19
|
+
{ name: 'walletAddress', type: 'address' },
|
|
20
|
+
{ name: 'nonce', type: 'string' }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
interface Options {
|
|
26
|
+
name: string;
|
|
27
|
+
version: string;
|
|
28
|
+
chainId: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class EIP712Helper {
|
|
32
|
+
private readonly EIP712Domain: TypedDataDomain;
|
|
33
|
+
|
|
34
|
+
constructor(options: Options) {
|
|
35
|
+
this.EIP712Domain = {
|
|
36
|
+
name: options.name,
|
|
37
|
+
version: options.version,
|
|
38
|
+
chainId: options.chainId,
|
|
39
|
+
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
signTypedData(
|
|
44
|
+
signer: AbstractSigner,
|
|
45
|
+
type: SupportedTypes,
|
|
46
|
+
data: Record<string, string | number>
|
|
47
|
+
) {
|
|
48
|
+
const types = EIP712_TYPES[type];
|
|
49
|
+
if (!types) {
|
|
50
|
+
throw new Error(`Unknown type: ${type}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return signer.signTypedData(this.EIP712Domain, types, data);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default EIP712Helper;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ApiError, ApiErrorResponse } from '../ApiClient';
|
|
2
|
+
import TypedEventEmitter, { EventMap } from './TypedEventEmitter';
|
|
3
|
+
import { JSONValue } from '../types/json';
|
|
4
|
+
import { Logger } from './DefaultLogger';
|
|
5
|
+
|
|
6
|
+
export interface AuthData {
|
|
7
|
+
token: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class RestClient<E extends EventMap = never> extends TypedEventEmitter<E> {
|
|
11
|
+
readonly baseUrl: string;
|
|
12
|
+
protected _auth: AuthData | null = null;
|
|
13
|
+
protected _logger: Logger;
|
|
14
|
+
|
|
15
|
+
constructor(baseUrl: string, logger: Logger) {
|
|
16
|
+
super();
|
|
17
|
+
this.baseUrl = baseUrl;
|
|
18
|
+
this._logger = logger;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get auth(): AuthData | null {
|
|
22
|
+
return this._auth;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
set auth(auth: AuthData | null) {
|
|
26
|
+
this._auth = auth;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private formatUrl(relativeUrl: string, query: Record<string, string> = {}): string {
|
|
30
|
+
const url = new URL(relativeUrl, this.baseUrl);
|
|
31
|
+
Object.keys(query).forEach((key) => {
|
|
32
|
+
url.searchParams.append(key, query[key]);
|
|
33
|
+
});
|
|
34
|
+
return url.toString();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private request<T = JSONValue>(url: string, options: RequestInit = {}): Promise<T> {
|
|
38
|
+
return fetch(url, {
|
|
39
|
+
...options,
|
|
40
|
+
headers: {
|
|
41
|
+
...(options.headers || {}),
|
|
42
|
+
...(this.auth ? { Authorization: this.auth.token } : {})
|
|
43
|
+
}
|
|
44
|
+
}).then((r) => this.processResponse(r) as T);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async processResponse(response: Response): Promise<JSONValue> {
|
|
48
|
+
let responseData;
|
|
49
|
+
try {
|
|
50
|
+
responseData = await response.json();
|
|
51
|
+
} catch (e) {
|
|
52
|
+
this._logger.error('Failed to parse response:', e);
|
|
53
|
+
throw new Error('Failed to parse response');
|
|
54
|
+
}
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new ApiError(response.status, responseData as ApiErrorResponse);
|
|
57
|
+
}
|
|
58
|
+
return responseData as JSONValue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get<T = JSONValue>(path: string, query: Record<string, any> = {}): Promise<T> {
|
|
62
|
+
return this.request<T>(this.formatUrl(path, query), query);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
post<T = JSONValue>(path: string, body: Record<string, unknown> = {}): Promise<T> {
|
|
66
|
+
return this.request<T>(this.formatUrl(path), {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json'
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify(body)
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default RestClient;
|