@nadohq/mobile-client 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/MobileClient-9JSAzblH.cjs +218 -0
- package/dist/MobileClient-9JSAzblH.cjs.map +1 -0
- package/dist/MobileClient.cjs +4 -0
- package/dist/MobileClient.d.cts +114 -0
- package/dist/MobileClient.d.cts.map +1 -0
- package/dist/MobileClient.d.ts +114 -0
- package/dist/MobileClient.d.ts.map +1 -0
- package/dist/MobileClient.js +185 -0
- package/dist/MobileClient.js.map +1 -0
- package/dist/MobileServerFailureError-Belp_yef.d.cts +25 -0
- package/dist/MobileServerFailureError-Belp_yef.d.cts.map +1 -0
- package/dist/MobileServerFailureError-DqM0T6CB.d.ts +25 -0
- package/dist/MobileServerFailureError-DqM0T6CB.d.ts.map +1 -0
- package/dist/buildSignedMobileRequest-BsbBkU0K.d.cts +14 -0
- package/dist/buildSignedMobileRequest-BsbBkU0K.d.cts.map +1 -0
- package/dist/buildSignedMobileRequest-CDRsRNHm.d.ts +14 -0
- package/dist/buildSignedMobileRequest-CDRsRNHm.d.ts.map +1 -0
- package/dist/canonicalize-Dm7sXwPg.d.cts +14 -0
- package/dist/canonicalize-Dm7sXwPg.d.cts.map +1 -0
- package/dist/canonicalize-PX6dTIdF.d.ts +14 -0
- package/dist/canonicalize-PX6dTIdF.d.ts.map +1 -0
- package/dist/clientTypes-DWW2N66c.d.ts +168 -0
- package/dist/clientTypes-DWW2N66c.d.ts.map +1 -0
- package/dist/clientTypes-Et0PUa2d.d.cts +168 -0
- package/dist/clientTypes-Et0PUa2d.d.cts.map +1 -0
- package/dist/dataMappers.cjs +89 -0
- package/dist/dataMappers.cjs.map +1 -0
- package/dist/dataMappers.d.cts +27 -0
- package/dist/dataMappers.d.cts.map +1 -0
- package/dist/dataMappers.d.ts +27 -0
- package/dist/dataMappers.d.ts.map +1 -0
- package/dist/dataMappers.js +84 -0
- package/dist/dataMappers.js.map +1 -0
- package/dist/eip712MethodByType-DJgXal9r.d.ts +10 -0
- package/dist/eip712MethodByType-DJgXal9r.d.ts.map +1 -0
- package/dist/eip712MethodByType-DLh9MRsD.d.cts +10 -0
- package/dist/eip712MethodByType-DLh9MRsD.d.cts.map +1 -0
- package/dist/endpoints.cjs +14 -0
- package/dist/endpoints.cjs.map +1 -0
- package/dist/endpoints.d.cts +10 -0
- package/dist/endpoints.d.cts.map +1 -0
- package/dist/endpoints.d.ts +10 -0
- package/dist/endpoints.d.ts.map +1 -0
- package/dist/endpoints.js +13 -0
- package/dist/endpoints.js.map +1 -0
- package/dist/index.cjs +27 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +15 -0
- package/dist/mobileErrorCodes-D8KzJXbU.d.cts +29 -0
- package/dist/mobileErrorCodes-D8KzJXbU.d.cts.map +1 -0
- package/dist/mobileErrorCodes-DM09jnNe.d.ts +29 -0
- package/dist/mobileErrorCodes-DM09jnNe.d.ts.map +1 -0
- package/dist/nonce-DCOrXXkQ.d.ts +15 -0
- package/dist/nonce-DCOrXXkQ.d.ts.map +1 -0
- package/dist/nonce-zGyisRlj.d.cts +15 -0
- package/dist/nonce-zGyisRlj.d.cts.map +1 -0
- package/dist/patterns-B9J2ldpx.d.cts +8 -0
- package/dist/patterns-B9J2ldpx.d.cts.map +1 -0
- package/dist/patterns-BbxH3_VA.d.ts +8 -0
- package/dist/patterns-BbxH3_VA.d.ts.map +1 -0
- package/dist/payloadHash-Bmhzs78N.d.ts +13 -0
- package/dist/payloadHash-Bmhzs78N.d.ts.map +1 -0
- package/dist/payloadHash-nj8WuLUP.d.cts +13 -0
- package/dist/payloadHash-nj8WuLUP.d.cts.map +1 -0
- package/dist/serverBaseTypes-BicXMrZV.d.cts +43 -0
- package/dist/serverBaseTypes-BicXMrZV.d.cts.map +1 -0
- package/dist/serverBaseTypes-ewbo8l4R.d.ts +43 -0
- package/dist/serverBaseTypes-ewbo8l4R.d.ts.map +1 -0
- package/dist/serverExecuteTypes-CXnMjKnt.d.ts +11 -0
- package/dist/serverExecuteTypes-CXnMjKnt.d.ts.map +1 -0
- package/dist/serverExecuteTypes-DKYK1V7C.d.cts +11 -0
- package/dist/serverExecuteTypes-DKYK1V7C.d.cts.map +1 -0
- package/dist/serverModelTypes-CQgfqgW3.d.cts +69 -0
- package/dist/serverModelTypes-CQgfqgW3.d.cts.map +1 -0
- package/dist/serverModelTypes-vEPhHIit.d.ts +69 -0
- package/dist/serverModelTypes-vEPhHIit.d.ts.map +1 -0
- package/dist/serverQueryTypes-BI-2TD8F.d.ts +53 -0
- package/dist/serverQueryTypes-BI-2TD8F.d.ts.map +1 -0
- package/dist/serverQueryTypes-D7Yharse.d.cts +53 -0
- package/dist/serverQueryTypes-D7Yharse.d.cts.map +1 -0
- package/dist/serverResponseGuards-C_vZIgGl.d.ts +16 -0
- package/dist/serverResponseGuards-C_vZIgGl.d.ts.map +1 -0
- package/dist/serverResponseGuards-DjCNL3CR.d.cts +16 -0
- package/dist/serverResponseGuards-DjCNL3CR.d.cts.map +1 -0
- package/dist/signing/buildSignedMobileRequest.cjs +47 -0
- package/dist/signing/buildSignedMobileRequest.cjs.map +1 -0
- package/dist/signing/buildSignedMobileRequest.d.cts +2 -0
- package/dist/signing/buildSignedMobileRequest.d.ts +2 -0
- package/dist/signing/buildSignedMobileRequest.js +45 -0
- package/dist/signing/buildSignedMobileRequest.js.map +1 -0
- package/dist/signing/canonicalize.cjs +68 -0
- package/dist/signing/canonicalize.cjs.map +1 -0
- package/dist/signing/canonicalize.d.cts +2 -0
- package/dist/signing/canonicalize.d.ts +2 -0
- package/dist/signing/canonicalize.js +67 -0
- package/dist/signing/canonicalize.js.map +1 -0
- package/dist/signing/eip712MethodByType.cjs +20 -0
- package/dist/signing/eip712MethodByType.cjs.map +1 -0
- package/dist/signing/eip712MethodByType.d.cts +2 -0
- package/dist/signing/eip712MethodByType.d.ts +2 -0
- package/dist/signing/eip712MethodByType.js +19 -0
- package/dist/signing/eip712MethodByType.js.map +1 -0
- package/dist/signing/index.cjs +11 -0
- package/dist/signing/index.d.cts +7 -0
- package/dist/signing/index.d.ts +7 -0
- package/dist/signing/index.js +6 -0
- package/dist/signing/nonce.cjs +19 -0
- package/dist/signing/nonce.cjs.map +1 -0
- package/dist/signing/nonce.d.cts +2 -0
- package/dist/signing/nonce.d.ts +2 -0
- package/dist/signing/nonce.js +18 -0
- package/dist/signing/nonce.js.map +1 -0
- package/dist/signing/payloadHash.cjs +17 -0
- package/dist/signing/payloadHash.cjs.map +1 -0
- package/dist/signing/payloadHash.d.cts +2 -0
- package/dist/signing/payloadHash.d.ts +2 -0
- package/dist/signing/payloadHash.js +15 -0
- package/dist/signing/payloadHash.js.map +1 -0
- package/dist/signing/types.cjs +0 -0
- package/dist/signing/types.d.cts +2 -0
- package/dist/signing/types.d.ts +2 -0
- package/dist/signing/types.js +1 -0
- package/dist/types/MobileServerFailureError.cjs +17 -0
- package/dist/types/MobileServerFailureError.cjs.map +1 -0
- package/dist/types/MobileServerFailureError.d.cts +2 -0
- package/dist/types/MobileServerFailureError.d.ts +2 -0
- package/dist/types/MobileServerFailureError.js +16 -0
- package/dist/types/MobileServerFailureError.js.map +1 -0
- package/dist/types/clientTypes.cjs +0 -0
- package/dist/types/clientTypes.d.cts +2 -0
- package/dist/types/clientTypes.d.ts +2 -0
- package/dist/types/clientTypes.js +1 -0
- package/dist/types/index.cjs +5 -0
- package/dist/types/index.d.cts +8 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +3 -0
- package/dist/types/mobileErrorCodes.cjs +27 -0
- package/dist/types/mobileErrorCodes.cjs.map +1 -0
- package/dist/types/mobileErrorCodes.d.cts +2 -0
- package/dist/types/mobileErrorCodes.d.ts +2 -0
- package/dist/types/mobileErrorCodes.js +26 -0
- package/dist/types/mobileErrorCodes.js.map +1 -0
- package/dist/types/serverBaseTypes.cjs +0 -0
- package/dist/types/serverBaseTypes.d.cts +2 -0
- package/dist/types/serverBaseTypes.d.ts +2 -0
- package/dist/types/serverBaseTypes.js +1 -0
- package/dist/types/serverExecuteTypes.cjs +0 -0
- package/dist/types/serverExecuteTypes.d.cts +2 -0
- package/dist/types/serverExecuteTypes.d.ts +2 -0
- package/dist/types/serverExecuteTypes.js +1 -0
- package/dist/types/serverModelTypes.cjs +0 -0
- package/dist/types/serverModelTypes.d.cts +2 -0
- package/dist/types/serverModelTypes.d.ts +2 -0
- package/dist/types/serverModelTypes.js +1 -0
- package/dist/types/serverQueryTypes.cjs +0 -0
- package/dist/types/serverQueryTypes.d.cts +2 -0
- package/dist/types/serverQueryTypes.d.ts +2 -0
- package/dist/types/serverQueryTypes.js +1 -0
- package/dist/types-B76Pn7Sy.d.ts +73 -0
- package/dist/types-B76Pn7Sy.d.ts.map +1 -0
- package/dist/types-uCD4BNZR.d.cts +73 -0
- package/dist/types-uCD4BNZR.d.cts.map +1 -0
- package/dist/utils/index.cjs +6 -0
- package/dist/utils/index.d.cts +3 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/patterns.cjs +10 -0
- package/dist/utils/patterns.cjs.map +1 -0
- package/dist/utils/patterns.d.cts +2 -0
- package/dist/utils/patterns.d.ts +2 -0
- package/dist/utils/patterns.js +9 -0
- package/dist/utils/patterns.js.map +1 -0
- package/dist/utils/serverResponseGuards.cjs +19 -0
- package/dist/utils/serverResponseGuards.cjs.map +1 -0
- package/dist/utils/serverResponseGuards.d.cts +2 -0
- package/dist/utils/serverResponseGuards.d.ts +2 -0
- package/dist/utils/serverResponseGuards.js +17 -0
- package/dist/utils/serverResponseGuards.js.map +1 -0
- package/package.json +58 -0
- package/src/MobileClient.ts +395 -0
- package/src/dataMappers.ts +106 -0
- package/src/endpoints.ts +10 -0
- package/src/index.ts +5 -0
- package/src/signing/buildSignedMobileRequest.ts +58 -0
- package/src/signing/canonicalize.ts +69 -0
- package/src/signing/eip712MethodByType.ts +19 -0
- package/src/signing/index.ts +6 -0
- package/src/signing/nonce.ts +16 -0
- package/src/signing/payloadHash.ts +12 -0
- package/src/signing/signing.test.ts +224 -0
- package/src/signing/types.ts +71 -0
- package/src/types/MobileServerFailureError.ts +18 -0
- package/src/types/clientTypes.ts +182 -0
- package/src/types/index.ts +7 -0
- package/src/types/mobileErrorCodes.ts +27 -0
- package/src/types/serverBaseTypes.ts +43 -0
- package/src/types/serverExecuteTypes.ts +12 -0
- package/src/types/serverModelTypes.ts +74 -0
- package/src/types/serverQueryTypes.ts +74 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/patterns.ts +4 -0
- package/src/utils/serverResponseGuards.ts +27 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WalletClientWithAccount,
|
|
3
|
+
WalletNotProvidedError,
|
|
4
|
+
} from '@nadohq/shared';
|
|
5
|
+
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
|
6
|
+
import {
|
|
7
|
+
mapMobileIdentity,
|
|
8
|
+
mapMobileNotificationPreferences,
|
|
9
|
+
mapMobileNotificationPreferencesToServer,
|
|
10
|
+
mapMobilePublicProfile,
|
|
11
|
+
mapMobileRegisteredDevice,
|
|
12
|
+
} from './dataMappers';
|
|
13
|
+
import {
|
|
14
|
+
buildSignedMobileRequest,
|
|
15
|
+
MobileSignedInner,
|
|
16
|
+
MobileSignedInnerParams,
|
|
17
|
+
MobileSignedRequest,
|
|
18
|
+
} from './signing';
|
|
19
|
+
import {
|
|
20
|
+
GetMobileNotificationPreferencesParams,
|
|
21
|
+
GetMobilePublicProfileParams,
|
|
22
|
+
GetMobileRegisteredDevicesParams,
|
|
23
|
+
GetMobileSelfIdentityParams,
|
|
24
|
+
GetMobileUsernameAvailabilityParams,
|
|
25
|
+
MobileClaimUsernameParams,
|
|
26
|
+
MobileIdentity,
|
|
27
|
+
MobileNotificationPreferences,
|
|
28
|
+
MobilePublicProfile,
|
|
29
|
+
MobileRegisteredDevice,
|
|
30
|
+
MobileRegisterExpoTokenParams,
|
|
31
|
+
MobileSetPrivateModeParams,
|
|
32
|
+
MobileSignedRequestParams,
|
|
33
|
+
MobileUnregisterExpoTokenParams,
|
|
34
|
+
MobileUpdateNotificationPreferencesParams,
|
|
35
|
+
MobileUpdateUsernameParams,
|
|
36
|
+
MobileUsernameAvailability,
|
|
37
|
+
} from './types/clientTypes';
|
|
38
|
+
import { MobileServerFailureError } from './types/MobileServerFailureError';
|
|
39
|
+
import { MobileServerSuccessResponse } from './types/serverBaseTypes';
|
|
40
|
+
import { MobileServerExecuteResult } from './types/serverExecuteTypes';
|
|
41
|
+
import {
|
|
42
|
+
MobileServerNotificationPreferencesResponse,
|
|
43
|
+
MobileServerProfileRequest,
|
|
44
|
+
MobileServerProfileResponse,
|
|
45
|
+
MobileServerRegisteredDevicesResponse,
|
|
46
|
+
MobileServerSelfIdentityResponse,
|
|
47
|
+
MobileServerUsernameAvailabilityRequest,
|
|
48
|
+
MobileServerUsernameAvailabilityResponse,
|
|
49
|
+
} from './types/serverQueryTypes';
|
|
50
|
+
import {
|
|
51
|
+
isMobileServerFailureResponse,
|
|
52
|
+
isMobileServerSuccessResponse,
|
|
53
|
+
} from './utils/serverResponseGuards';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Options for constructing a {@link MobileClient}.
|
|
57
|
+
*/
|
|
58
|
+
export interface MobileClientOpts {
|
|
59
|
+
/**
|
|
60
|
+
* Base URL of the mobile service API, e.g. {@link MOBILE_CLIENT_ENDPOINTS}.
|
|
61
|
+
*/
|
|
62
|
+
url: string;
|
|
63
|
+
/**
|
|
64
|
+
* Wallet client used to sign requests.
|
|
65
|
+
*/
|
|
66
|
+
walletClient?: WalletClientWithAccount;
|
|
67
|
+
/**
|
|
68
|
+
* If provided, this signer is used instead of `walletClient` for all signed requests.
|
|
69
|
+
*/
|
|
70
|
+
linkedSignerWalletClient?: WalletClientWithAccount;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Client for the Nado mobile service API: username claims, public profile lookups, privacy settings, and
|
|
75
|
+
* push notification device/preference management.
|
|
76
|
+
*/
|
|
77
|
+
export class MobileClient {
|
|
78
|
+
readonly opts: MobileClientOpts;
|
|
79
|
+
readonly axiosInstance: AxiosInstance;
|
|
80
|
+
|
|
81
|
+
constructor(opts: MobileClientOpts) {
|
|
82
|
+
this.opts = opts;
|
|
83
|
+
this.axiosInstance = axios.create({
|
|
84
|
+
withCredentials: true,
|
|
85
|
+
// We have custom logic to validate response status and create an appropriate error
|
|
86
|
+
validateStatus: () => true,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sets the linked signer for requests
|
|
92
|
+
*
|
|
93
|
+
* @param linkedSignerWalletClient The linkedSigner to use for all signatures. Set to null to revert to the chain signer
|
|
94
|
+
*/
|
|
95
|
+
public setLinkedSigner(
|
|
96
|
+
linkedSignerWalletClient: WalletClientWithAccount | null,
|
|
97
|
+
) {
|
|
98
|
+
this.opts.linkedSignerWalletClient = linkedSignerWalletClient ?? undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
Public queries
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Checks whether a username derived from the given display name is available to claim.
|
|
107
|
+
*/
|
|
108
|
+
async getUsernameAvailability(
|
|
109
|
+
params: GetMobileUsernameAvailabilityParams,
|
|
110
|
+
): Promise<MobileUsernameAvailability> {
|
|
111
|
+
const body: MobileServerUsernameAvailabilityRequest = {
|
|
112
|
+
type: 'username_availability',
|
|
113
|
+
display_name: params.displayName,
|
|
114
|
+
};
|
|
115
|
+
const data =
|
|
116
|
+
await this.publicQuery<MobileServerUsernameAvailabilityResponse>(body);
|
|
117
|
+
return { username: data.username, available: data.available };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Looks up a subaccount's public profile by username.
|
|
122
|
+
*
|
|
123
|
+
* @throws {MobileServerFailureError} With error code `PROFILE_NOT_FOUND` if no identity has claimed the
|
|
124
|
+
* username or it is invalid. Private Mode does not hide the profile itself, only the account's activity.
|
|
125
|
+
*/
|
|
126
|
+
async getPublicProfile(
|
|
127
|
+
params: GetMobilePublicProfileParams,
|
|
128
|
+
): Promise<MobilePublicProfile> {
|
|
129
|
+
const body: MobileServerProfileRequest = {
|
|
130
|
+
type: 'profile',
|
|
131
|
+
username: params.username,
|
|
132
|
+
};
|
|
133
|
+
const data = await this.publicQuery<MobileServerProfileResponse>(body);
|
|
134
|
+
return mapMobilePublicProfile(data.profile);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
Signed queries
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Fetches the caller's own identity for a subaccount. Returns `null` if the subaccount has not claimed a
|
|
143
|
+
* username yet — this is normal data, not an error.
|
|
144
|
+
*/
|
|
145
|
+
async getSelfIdentity(
|
|
146
|
+
params: GetMobileSelfIdentityParams,
|
|
147
|
+
): Promise<MobileIdentity | null> {
|
|
148
|
+
const signedRequest = await this.getSignedRequest(
|
|
149
|
+
'self_identity',
|
|
150
|
+
params,
|
|
151
|
+
{},
|
|
152
|
+
);
|
|
153
|
+
const data =
|
|
154
|
+
await this.query<MobileServerSelfIdentityResponse>(signedRequest);
|
|
155
|
+
return data.identity ? mapMobileIdentity(data.identity) : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Fetches a wallet's push notification preferences. Falls back to the backend's defaults if the wallet has
|
|
160
|
+
* never updated its preferences.
|
|
161
|
+
*/
|
|
162
|
+
async getNotificationPreferences(
|
|
163
|
+
params: GetMobileNotificationPreferencesParams,
|
|
164
|
+
): Promise<MobileNotificationPreferences> {
|
|
165
|
+
const signedRequest = await this.getSignedRequest(
|
|
166
|
+
'notification_preferences',
|
|
167
|
+
params,
|
|
168
|
+
{},
|
|
169
|
+
);
|
|
170
|
+
const data =
|
|
171
|
+
await this.query<MobileServerNotificationPreferencesResponse>(
|
|
172
|
+
signedRequest,
|
|
173
|
+
);
|
|
174
|
+
return mapMobileNotificationPreferences(data.preferences);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Fetches the devices registered for push notifications for a wallet.
|
|
179
|
+
*/
|
|
180
|
+
async getRegisteredDevices(
|
|
181
|
+
params: GetMobileRegisteredDevicesParams,
|
|
182
|
+
): Promise<MobileRegisteredDevice[]> {
|
|
183
|
+
const signedRequest = await this.getSignedRequest(
|
|
184
|
+
'registered_devices',
|
|
185
|
+
params,
|
|
186
|
+
{},
|
|
187
|
+
);
|
|
188
|
+
const data =
|
|
189
|
+
await this.query<MobileServerRegisteredDevicesResponse>(signedRequest);
|
|
190
|
+
return data.devices.map(mapMobileRegisteredDevice);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/*
|
|
194
|
+
Signed executes
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Claims a username for a subaccount, derived from the given display name.
|
|
199
|
+
*/
|
|
200
|
+
async claimUsername(
|
|
201
|
+
params: MobileClaimUsernameParams,
|
|
202
|
+
): Promise<MobileServerSuccessResponse> {
|
|
203
|
+
const signedRequest = await this.getSignedRequest(
|
|
204
|
+
'claim_username',
|
|
205
|
+
params,
|
|
206
|
+
{
|
|
207
|
+
display_name: params.displayName,
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
return this.execute(signedRequest);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Updates the display name for a subaccount's already-claimed identity.
|
|
215
|
+
*/
|
|
216
|
+
async updateUsername(
|
|
217
|
+
params: MobileUpdateUsernameParams,
|
|
218
|
+
): Promise<MobileServerSuccessResponse> {
|
|
219
|
+
const signedRequest = await this.getSignedRequest(
|
|
220
|
+
'update_username',
|
|
221
|
+
params,
|
|
222
|
+
{ display_name: params.displayName },
|
|
223
|
+
);
|
|
224
|
+
return this.execute(signedRequest);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Sets whether a subaccount's profile is private.
|
|
229
|
+
*/
|
|
230
|
+
async setPrivateMode(
|
|
231
|
+
params: MobileSetPrivateModeParams,
|
|
232
|
+
): Promise<MobileServerSuccessResponse> {
|
|
233
|
+
const signedRequest = await this.getSignedRequest(
|
|
234
|
+
'set_private_mode',
|
|
235
|
+
params,
|
|
236
|
+
{ private_mode: params.privateMode },
|
|
237
|
+
);
|
|
238
|
+
return this.execute(signedRequest);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Registers an Expo push token for the wallet, transferring active ownership of the token if another
|
|
243
|
+
* wallet had registered it. Idempotent per `(wallet, token)`.
|
|
244
|
+
*
|
|
245
|
+
* @throws {MobileServerFailureError} With error code `INVALID_EXPO_TOKEN` if the token is not a valid Expo
|
|
246
|
+
* push token, or `INVALID_DEVICE_METADATA` if `locale` (max 35 chars) or `appVersion` (max 64 chars) is
|
|
247
|
+
* too long.
|
|
248
|
+
*/
|
|
249
|
+
async registerExpoToken(
|
|
250
|
+
params: MobileRegisterExpoTokenParams,
|
|
251
|
+
): Promise<MobileServerSuccessResponse> {
|
|
252
|
+
const signedRequest = await this.getSignedRequest(
|
|
253
|
+
'register_expo_token',
|
|
254
|
+
params,
|
|
255
|
+
{
|
|
256
|
+
expo_token: params.expoToken,
|
|
257
|
+
platform: params.platform,
|
|
258
|
+
locale: params.locale ?? null,
|
|
259
|
+
app_version: params.appVersion ?? null,
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
return this.execute(signedRequest);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Unregisters an Expo push token for the wallet. Idempotent — unregistering an unknown token succeeds.
|
|
267
|
+
*/
|
|
268
|
+
async unregisterExpoToken(
|
|
269
|
+
params: MobileUnregisterExpoTokenParams,
|
|
270
|
+
): Promise<MobileServerSuccessResponse> {
|
|
271
|
+
const signedRequest = await this.getSignedRequest(
|
|
272
|
+
'unregister_expo_token',
|
|
273
|
+
params,
|
|
274
|
+
{ expo_token: params.expoToken },
|
|
275
|
+
);
|
|
276
|
+
return this.execute(signedRequest);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Replaces the wallet's push notification preferences. The backend requires `schemaVersion` of 1, exactly
|
|
281
|
+
* one entry per known category, and empty `scopes` on every entry.
|
|
282
|
+
*
|
|
283
|
+
* @throws {MobileServerFailureError} With error code `INVALID_PREFERENCES` if those rules are violated.
|
|
284
|
+
*/
|
|
285
|
+
async updateNotificationPreferences(
|
|
286
|
+
params: MobileUpdateNotificationPreferencesParams,
|
|
287
|
+
): Promise<MobileServerSuccessResponse> {
|
|
288
|
+
const signedRequest = await this.getSignedRequest(
|
|
289
|
+
'update_preferences',
|
|
290
|
+
params,
|
|
291
|
+
{
|
|
292
|
+
preferences: mapMobileNotificationPreferencesToServer(
|
|
293
|
+
params.preferences,
|
|
294
|
+
),
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
return this.execute(signedRequest);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/*
|
|
301
|
+
Base Fns
|
|
302
|
+
*/
|
|
303
|
+
|
|
304
|
+
protected async getSignedRequest<T extends MobileSignedInner['type']>(
|
|
305
|
+
type: T,
|
|
306
|
+
params: MobileSignedRequestParams,
|
|
307
|
+
innerParams: MobileSignedInnerParams<T>,
|
|
308
|
+
): Promise<MobileSignedRequest> {
|
|
309
|
+
// Use the linked signer if provided, otherwise use the default signer provided to the client
|
|
310
|
+
const walletClient =
|
|
311
|
+
this.opts.linkedSignerWalletClient ?? this.opts.walletClient;
|
|
312
|
+
|
|
313
|
+
if (walletClient == null) {
|
|
314
|
+
throw new WalletNotProvidedError();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const inner = { type, ...innerParams } as MobileSignedInner;
|
|
318
|
+
return buildSignedMobileRequest({ ...params, walletClient, inner });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
protected async publicQuery<TResponse extends { status: 'success' }>(
|
|
322
|
+
body: object,
|
|
323
|
+
): Promise<TResponse> {
|
|
324
|
+
const response = await this.axiosInstance.post<unknown>(
|
|
325
|
+
`${this.opts.url}/mobile/public_query`,
|
|
326
|
+
body,
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
this.checkResponseStatus(response);
|
|
330
|
+
this.checkServerStatus(response);
|
|
331
|
+
|
|
332
|
+
// checkServerStatus throws on failure responses so the cast to the success response is acceptable here
|
|
333
|
+
return response.data as TResponse;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
protected async query<TResponse extends { status: 'success' }>(
|
|
337
|
+
body: MobileSignedRequest,
|
|
338
|
+
): Promise<TResponse> {
|
|
339
|
+
const response = await this.axiosInstance.post<unknown>(
|
|
340
|
+
`${this.opts.url}/mobile/query`,
|
|
341
|
+
body,
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
this.checkResponseStatus(response);
|
|
345
|
+
this.checkServerStatus(response);
|
|
346
|
+
|
|
347
|
+
// checkServerStatus throws on failure responses so the cast to the success response is acceptable here
|
|
348
|
+
return response.data as TResponse;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
protected async execute(
|
|
352
|
+
body: MobileSignedRequest,
|
|
353
|
+
): Promise<MobileServerSuccessResponse> {
|
|
354
|
+
const response = await this.axiosInstance.post<MobileServerExecuteResult>(
|
|
355
|
+
`${this.opts.url}/mobile/execute`,
|
|
356
|
+
body,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
this.checkResponseStatus(response);
|
|
360
|
+
this.checkServerStatus(response);
|
|
361
|
+
|
|
362
|
+
// checkServerStatus catches the failure result and throws the error, so the cast to the success response is acceptable here
|
|
363
|
+
return response.data as MobileServerSuccessResponse;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Validates the HTTP status before interpreting the body. Unlike the engine, the mobile backend returns
|
|
368
|
+
* failure envelopes with non-2xx statuses (e.g. 404 for `PROFILE_NOT_FOUND`), so a non-2xx response with a
|
|
369
|
+
* failure envelope still throws the typed error; anything else is a transport-level error.
|
|
370
|
+
*/
|
|
371
|
+
private checkResponseStatus(response: AxiosResponse<unknown>) {
|
|
372
|
+
if (response.status >= 200 && response.status < 300) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (isMobileServerFailureResponse(response.data)) {
|
|
376
|
+
throw new MobileServerFailureError(response.data, response.status);
|
|
377
|
+
}
|
|
378
|
+
throw new Error(
|
|
379
|
+
`Unexpected response from mobile service: ${response.status} ${response.statusText}. Data: ${JSON.stringify(response.data)}`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private checkServerStatus(response: AxiosResponse<unknown>) {
|
|
384
|
+
const { data } = response;
|
|
385
|
+
|
|
386
|
+
if (isMobileServerFailureResponse(data)) {
|
|
387
|
+
throw new MobileServerFailureError(data, response.status);
|
|
388
|
+
}
|
|
389
|
+
if (!isMobileServerSuccessResponse(data)) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
`Unexpected response from mobile service: missing success envelope. Data: ${JSON.stringify(data)}`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MobileIdentity,
|
|
3
|
+
MobileNotificationPreferenceScope,
|
|
4
|
+
MobileNotificationPreferences,
|
|
5
|
+
MobilePublicProfile,
|
|
6
|
+
MobileRegisteredDevice,
|
|
7
|
+
} from './types/clientTypes';
|
|
8
|
+
import {
|
|
9
|
+
MobileServerIdentity,
|
|
10
|
+
MobileServerNotificationPreferenceScope,
|
|
11
|
+
MobileServerNotificationPreferences,
|
|
12
|
+
MobileServerProfile,
|
|
13
|
+
MobileServerRegisteredDevice,
|
|
14
|
+
} from './types/serverModelTypes';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Maps a server-side identity (snake_case) to its client-side (camelCase) representation.
|
|
18
|
+
*/
|
|
19
|
+
export function mapMobileIdentity(
|
|
20
|
+
server: MobileServerIdentity,
|
|
21
|
+
): MobileIdentity {
|
|
22
|
+
return {
|
|
23
|
+
subaccount: server.subaccount,
|
|
24
|
+
username: server.username,
|
|
25
|
+
displayName: server.display_name,
|
|
26
|
+
privateMode: server.private_mode,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Maps a server-side public profile (snake_case) to its client-side (camelCase) representation.
|
|
32
|
+
*/
|
|
33
|
+
export function mapMobilePublicProfile(
|
|
34
|
+
server: MobileServerProfile,
|
|
35
|
+
): MobilePublicProfile {
|
|
36
|
+
return {
|
|
37
|
+
subaccount: server.subaccount,
|
|
38
|
+
username: server.username,
|
|
39
|
+
displayName: server.display_name,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Maps server-side notification preferences (snake_case) to their client-side (camelCase) representation.
|
|
45
|
+
*/
|
|
46
|
+
export function mapMobileNotificationPreferences(
|
|
47
|
+
server: MobileServerNotificationPreferences,
|
|
48
|
+
): MobileNotificationPreferences {
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: server.schema_version,
|
|
51
|
+
categories: server.categories.map((category) => ({
|
|
52
|
+
category: category.category,
|
|
53
|
+
enabled: category.enabled,
|
|
54
|
+
scopes: category.scopes.map(mapMobileNotificationPreferenceScope),
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function mapMobileNotificationPreferenceScope(
|
|
60
|
+
server: MobileServerNotificationPreferenceScope,
|
|
61
|
+
): MobileNotificationPreferenceScope {
|
|
62
|
+
if (server.type === 'subaccount') {
|
|
63
|
+
return { type: 'subaccount', subaccount: server.subaccount };
|
|
64
|
+
}
|
|
65
|
+
return { type: 'product', productId: server.product_id };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Maps client-side notification preferences (camelCase) to the server-side (snake_case) wire shape.
|
|
70
|
+
*/
|
|
71
|
+
export function mapMobileNotificationPreferencesToServer(
|
|
72
|
+
preferences: MobileNotificationPreferences,
|
|
73
|
+
): MobileServerNotificationPreferences {
|
|
74
|
+
return {
|
|
75
|
+
schema_version: preferences.schemaVersion,
|
|
76
|
+
categories: preferences.categories.map((category) => ({
|
|
77
|
+
category: category.category,
|
|
78
|
+
enabled: category.enabled,
|
|
79
|
+
scopes: category.scopes.map(mapMobileNotificationPreferenceScopeToServer),
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function mapMobileNotificationPreferenceScopeToServer(
|
|
85
|
+
scope: MobileNotificationPreferenceScope,
|
|
86
|
+
): MobileServerNotificationPreferenceScope {
|
|
87
|
+
if (scope.type === 'subaccount') {
|
|
88
|
+
return { type: 'subaccount', subaccount: scope.subaccount };
|
|
89
|
+
}
|
|
90
|
+
return { type: 'product', product_id: scope.productId };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Maps a server-side registered device (snake_case) to its client-side (camelCase) representation.
|
|
95
|
+
*/
|
|
96
|
+
export function mapMobileRegisteredDevice(
|
|
97
|
+
server: MobileServerRegisteredDevice,
|
|
98
|
+
): MobileRegisteredDevice {
|
|
99
|
+
return {
|
|
100
|
+
platform: server.platform,
|
|
101
|
+
locale: server.locale,
|
|
102
|
+
appVersion: server.app_version,
|
|
103
|
+
tokenFingerprintPrefix: server.token_fingerprint_prefix,
|
|
104
|
+
lastSeenAt: server.last_seen_at,
|
|
105
|
+
};
|
|
106
|
+
}
|
package/src/endpoints.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ChainEnv } from '@nadohq/shared';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base URLs for the Nado mobile service API, keyed by chain environment.
|
|
5
|
+
*/
|
|
6
|
+
export const MOBILE_CLIENT_ENDPOINTS: Record<ChainEnv, string> = {
|
|
7
|
+
local: 'http://mobile.local.nado.xyz:8004/v1',
|
|
8
|
+
inkTestnet: 'https://mobile.test.nado.xyz/v1',
|
|
9
|
+
inkMainnet: 'https://mobile.prod.nado.xyz/v1',
|
|
10
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getSignedTransactionRequest, subaccountToHex } from '@nadohq/shared';
|
|
2
|
+
import { canonicalizeMobileInner } from './canonicalize';
|
|
3
|
+
import { MOBILE_EIP712_METHOD_BY_TYPE } from './eip712MethodByType';
|
|
4
|
+
import { getMobileNonce } from './nonce';
|
|
5
|
+
import { getMobilePayloadHash } from './payloadHash';
|
|
6
|
+
import {
|
|
7
|
+
BuildSignedMobileRequestParams,
|
|
8
|
+
MobileSignedInner,
|
|
9
|
+
MobileSignedRequest,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Builds and signs a mobile service API request: canonicalizes the inner payload, derives `sender` from the
|
|
14
|
+
* subaccount owner (never the signer), computes the EIP-712 `payloadHash`, signs the `NadoAuthentication`
|
|
15
|
+
* typed data, and returns the flattened signed body ready to POST.
|
|
16
|
+
*/
|
|
17
|
+
export async function buildSignedMobileRequest<T extends MobileSignedInner>(
|
|
18
|
+
params: BuildSignedMobileRequestParams & { inner: T },
|
|
19
|
+
): Promise<MobileSignedRequest<T>> {
|
|
20
|
+
const {
|
|
21
|
+
walletClient,
|
|
22
|
+
subaccountOwner,
|
|
23
|
+
subaccountName,
|
|
24
|
+
chainId,
|
|
25
|
+
verifyingAddr,
|
|
26
|
+
inner,
|
|
27
|
+
} = params;
|
|
28
|
+
|
|
29
|
+
const canonicalInner = canonicalizeMobileInner(inner) as T;
|
|
30
|
+
const sender = subaccountToHex({
|
|
31
|
+
subaccountOwner,
|
|
32
|
+
subaccountName,
|
|
33
|
+
});
|
|
34
|
+
const payloadHash = getMobilePayloadHash(canonicalInner);
|
|
35
|
+
const nonce = params.nonce ?? getMobileNonce();
|
|
36
|
+
const method = MOBILE_EIP712_METHOD_BY_TYPE[canonicalInner.type];
|
|
37
|
+
|
|
38
|
+
const signature = await getSignedTransactionRequest({
|
|
39
|
+
requestType: 'nado_authentication',
|
|
40
|
+
requestParams: {
|
|
41
|
+
method,
|
|
42
|
+
subaccountOwner,
|
|
43
|
+
subaccountName,
|
|
44
|
+
payloadHash,
|
|
45
|
+
nonce: nonce.toString(),
|
|
46
|
+
},
|
|
47
|
+
chainId,
|
|
48
|
+
verifyingContract: verifyingAddr,
|
|
49
|
+
walletClient,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
...canonicalInner,
|
|
54
|
+
signature,
|
|
55
|
+
sender,
|
|
56
|
+
nonce: nonce.toString(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MobileServerNotificationPreferences } from '../types/serverModelTypes';
|
|
2
|
+
import { MobileSignedInner } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rebuilds an inner payload with its keys in the backend's struct declaration order (`type` first, then the
|
|
6
|
+
* remaining fields in the exact order the backend serializes them). msgpack encodes object keys in insertion
|
|
7
|
+
* order and the payload hash must be reproducible regardless of how the caller constructed the object, so
|
|
8
|
+
* every inner payload — and each of its sub-types — is canonicalized here before hashing. This function (with
|
|
9
|
+
* {@link canonicalizeNotificationPreferences} for nested preferences) is the source of truth for that order.
|
|
10
|
+
*/
|
|
11
|
+
export function canonicalizeMobileInner(
|
|
12
|
+
inner: MobileSignedInner,
|
|
13
|
+
): MobileSignedInner {
|
|
14
|
+
switch (inner.type) {
|
|
15
|
+
case 'claim_username':
|
|
16
|
+
return { type: 'claim_username', display_name: inner.display_name };
|
|
17
|
+
case 'update_username':
|
|
18
|
+
return { type: 'update_username', display_name: inner.display_name };
|
|
19
|
+
case 'set_private_mode':
|
|
20
|
+
return { type: 'set_private_mode', private_mode: inner.private_mode };
|
|
21
|
+
case 'self_identity':
|
|
22
|
+
return { type: 'self_identity' };
|
|
23
|
+
case 'register_expo_token':
|
|
24
|
+
return {
|
|
25
|
+
type: 'register_expo_token',
|
|
26
|
+
expo_token: inner.expo_token,
|
|
27
|
+
platform: inner.platform,
|
|
28
|
+
locale: inner.locale,
|
|
29
|
+
app_version: inner.app_version,
|
|
30
|
+
};
|
|
31
|
+
case 'unregister_expo_token':
|
|
32
|
+
return { type: 'unregister_expo_token', expo_token: inner.expo_token };
|
|
33
|
+
case 'update_preferences':
|
|
34
|
+
return {
|
|
35
|
+
type: 'update_preferences',
|
|
36
|
+
preferences: canonicalizeNotificationPreferences(inner.preferences),
|
|
37
|
+
};
|
|
38
|
+
case 'notification_preferences':
|
|
39
|
+
return { type: 'notification_preferences' };
|
|
40
|
+
case 'registered_devices':
|
|
41
|
+
return { type: 'registered_devices' };
|
|
42
|
+
default: {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`canonicalizeMobileInner: unhandled inner payload type: ${JSON.stringify(inner)}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Rebuilds notification preferences with keys in the backend's struct declaration order, so the msgpack
|
|
52
|
+
* encoding matches the backend's `rmp_serde::to_vec_named` output field-for-field.
|
|
53
|
+
*/
|
|
54
|
+
function canonicalizeNotificationPreferences(
|
|
55
|
+
preferences: MobileServerNotificationPreferences,
|
|
56
|
+
): MobileServerNotificationPreferences {
|
|
57
|
+
return {
|
|
58
|
+
schema_version: preferences.schema_version,
|
|
59
|
+
categories: preferences.categories.map((category) => ({
|
|
60
|
+
category: category.category,
|
|
61
|
+
enabled: category.enabled,
|
|
62
|
+
scopes: category.scopes.map((scope) =>
|
|
63
|
+
scope.type === 'subaccount'
|
|
64
|
+
? { type: 'subaccount', subaccount: scope.subaccount }
|
|
65
|
+
: { type: 'product', product_id: scope.product_id },
|
|
66
|
+
),
|
|
67
|
+
})),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MobileSignedInner } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EIP-712 `method` string for each signed inner payload type.
|
|
5
|
+
*/
|
|
6
|
+
export const MOBILE_EIP712_METHOD_BY_TYPE: Record<
|
|
7
|
+
MobileSignedInner['type'],
|
|
8
|
+
string
|
|
9
|
+
> = {
|
|
10
|
+
claim_username: 'mobile:execute_claim_username',
|
|
11
|
+
update_username: 'mobile:execute_update_username',
|
|
12
|
+
set_private_mode: 'mobile:execute_set_private_mode',
|
|
13
|
+
self_identity: 'mobile:query_self_identity',
|
|
14
|
+
register_expo_token: 'mobile:execute_register_expo_token',
|
|
15
|
+
unregister_expo_token: 'mobile:execute_unregister_expo_token',
|
|
16
|
+
update_preferences: 'mobile:execute_update_preferences',
|
|
17
|
+
notification_preferences: 'mobile:query_notification_preferences',
|
|
18
|
+
registered_devices: 'mobile:query_registered_devices',
|
|
19
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a `nonce` for a signed mobile service API request, following the same shape as `getOrderNonce`:
|
|
3
|
+
* a receive-deadline timestamp in the high digits plus a random low-digits component to avoid collisions. The
|
|
4
|
+
* server reads `nonce / 1_000_000` as a millisecond receive deadline, which must be later than server time
|
|
5
|
+
* and no more than 100s ahead — hence the 30s default window. The random low six digits keep nonces
|
|
6
|
+
* generated within the same millisecond distinct.
|
|
7
|
+
*
|
|
8
|
+
* @param deadlineMs - Receive deadline in milliseconds; defaults to 30s ahead of now.
|
|
9
|
+
* @param randomInt - Random integer in `[0, 1_000_000)` placed in the low six digits to avoid collisions.
|
|
10
|
+
*/
|
|
11
|
+
export function getMobileNonce(
|
|
12
|
+
deadlineMs: number = Date.now() + 30_000,
|
|
13
|
+
randomInt: number = Math.floor(Math.random() * 1_000_000),
|
|
14
|
+
): bigint {
|
|
15
|
+
return BigInt(deadlineMs) * 1_000_000n + BigInt(randomInt);
|
|
16
|
+
}
|