@talken/talkenkit 2.3.5 → 2.3.6
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.js +14 -66
- package/dist/wallets/walletConnectors/abcWallet/abcApi.js +1 -1
- package/dist/wallets/walletConnectors/abcWallet/abcConnector.js +2 -2
- package/dist/wallets/walletConnectors/abcWallet/abcSolanaWallet.js +2 -2
- package/dist/wallets/walletConnectors/abcWallet/abcWallet.js +3 -3
- package/dist/wallets/walletConnectors/abcWallet/api/index.js +4 -4
- package/dist/wallets/walletConnectors/abcWallet/index.js +6 -6
- package/dist/wallets/walletConnectors/chunk-2J6LHJAF.js +228 -0
- package/dist/wallets/walletConnectors/chunk-7L33BY4P.js +44 -0
- package/dist/wallets/walletConnectors/chunk-BHMMHU7Y.js +228 -0
- package/dist/wallets/walletConnectors/chunk-KRAIITU7.js +39 -0
- package/dist/wallets/walletConnectors/chunk-L7I5I4CZ.js +44 -0
- package/dist/wallets/walletConnectors/chunk-LTBQU2GW.js +63 -0
- package/dist/wallets/walletConnectors/chunk-OEB7MRS5.js +39 -0
- package/dist/wallets/walletConnectors/chunk-P2E4LSYH.js +1678 -0
- package/dist/wallets/walletConnectors/chunk-R6JCHV55.js +39 -0
- package/dist/wallets/walletConnectors/chunk-RSZS2RMC.js +44 -0
- package/dist/wallets/walletConnectors/chunk-VPNKJ7PB.js +1674 -0
- package/dist/wallets/walletConnectors/chunk-WR7HSVMH.js +228 -0
- package/dist/wallets/walletConnectors/chunk-YZBFKVTX.js +1675 -0
- package/dist/wallets/walletConnectors/index.js +21 -21
- package/package.json +12 -11
- package/LICENSE +0 -9
|
@@ -0,0 +1,1675 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
secure_default
|
|
4
|
+
} from "./chunk-G2LI5MVX.js";
|
|
5
|
+
import {
|
|
6
|
+
calculateExpiryTimestamp,
|
|
7
|
+
createAbcError,
|
|
8
|
+
isTokenExpired,
|
|
9
|
+
loadFromStorage,
|
|
10
|
+
parseApiError,
|
|
11
|
+
removeFromStorage,
|
|
12
|
+
saveToStorage
|
|
13
|
+
} from "./chunk-W23N7VC4.js";
|
|
14
|
+
import {
|
|
15
|
+
ABC_AUDIENCE,
|
|
16
|
+
ABC_ENDPOINTS,
|
|
17
|
+
DEFAULT_HEADERS,
|
|
18
|
+
REQUEST_TIMEOUT
|
|
19
|
+
} from "./chunk-VETRBBA2.js";
|
|
20
|
+
|
|
21
|
+
// src/wallets/walletConnectors/abcWallet/abcApi.ts
|
|
22
|
+
import { getAddress } from "viem";
|
|
23
|
+
var AbcWaasClient = class {
|
|
24
|
+
constructor(config) {
|
|
25
|
+
this.accessToken = null;
|
|
26
|
+
this.refreshToken = null;
|
|
27
|
+
this.expiresAt = null;
|
|
28
|
+
this.isRefreshing = false;
|
|
29
|
+
this.refreshPromise = null;
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.baseURL = config.waasUrl;
|
|
32
|
+
this.secure = new secure_default(this.baseURL);
|
|
33
|
+
this.loadTokens();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Load tokens from storage
|
|
37
|
+
*/
|
|
38
|
+
loadTokens() {
|
|
39
|
+
this.accessToken = loadFromStorage("access_token" /* ACCESS_TOKEN */);
|
|
40
|
+
this.refreshToken = loadFromStorage("refresh_token" /* REFRESH_TOKEN */);
|
|
41
|
+
this.expiresAt = loadFromStorage("expires_at" /* EXPIRES_AT */);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Save tokens to storage
|
|
45
|
+
*/
|
|
46
|
+
saveTokens(accessToken, refreshToken, expiresIn) {
|
|
47
|
+
this.accessToken = accessToken;
|
|
48
|
+
this.refreshToken = refreshToken;
|
|
49
|
+
this.expiresAt = calculateExpiryTimestamp(expiresIn);
|
|
50
|
+
saveToStorage("access_token" /* ACCESS_TOKEN */, accessToken);
|
|
51
|
+
saveToStorage("refresh_token" /* REFRESH_TOKEN */, refreshToken);
|
|
52
|
+
saveToStorage("expires_at" /* EXPIRES_AT */, this.expiresAt);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Clear tokens
|
|
56
|
+
*/
|
|
57
|
+
clearTokens() {
|
|
58
|
+
this.accessToken = null;
|
|
59
|
+
this.refreshToken = null;
|
|
60
|
+
this.expiresAt = null;
|
|
61
|
+
removeFromStorage("access_token" /* ACCESS_TOKEN */);
|
|
62
|
+
removeFromStorage("refresh_token" /* REFRESH_TOKEN */);
|
|
63
|
+
removeFromStorage("expires_at" /* EXPIRES_AT */);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if access token is expired
|
|
67
|
+
*/
|
|
68
|
+
isTokenExpired() {
|
|
69
|
+
return isTokenExpired(this.expiresAt);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get current access token
|
|
73
|
+
*/
|
|
74
|
+
getAccessToken() {
|
|
75
|
+
return this.accessToken;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Refresh access token
|
|
79
|
+
*/
|
|
80
|
+
async refreshAccessToken() {
|
|
81
|
+
if (this.isRefreshing) {
|
|
82
|
+
if (this.refreshPromise) {
|
|
83
|
+
await this.refreshPromise;
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!this.refreshToken) {
|
|
88
|
+
throw createAbcError(
|
|
89
|
+
"TOKEN_EXPIRED" /* TOKEN_EXPIRED */,
|
|
90
|
+
"No refresh token available"
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
this.isRefreshing = true;
|
|
94
|
+
this.refreshPromise = (async () => {
|
|
95
|
+
try {
|
|
96
|
+
const isIframe = typeof window !== "undefined" && window.self !== window.top;
|
|
97
|
+
const talkenApiUrl = process.env.NEXT_PUBLIC_API_SERVER || "https://dev.walletapi.talken.io";
|
|
98
|
+
const response = await fetch(
|
|
99
|
+
`${talkenApiUrl}${ABC_ENDPOINTS.REFRESH_TOKEN}`,
|
|
100
|
+
{
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: DEFAULT_HEADERS,
|
|
103
|
+
credentials: "include",
|
|
104
|
+
// Important: withCredentials from reference
|
|
105
|
+
body: new URLSearchParams({
|
|
106
|
+
refresh_token: this.refreshToken || "",
|
|
107
|
+
// Use snake_case (reference pattern)
|
|
108
|
+
isIframe: String(isIframe)
|
|
109
|
+
}).toString()
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
const text = await response.text();
|
|
113
|
+
const data = text ? JSON.parse(text) : {};
|
|
114
|
+
if (response.ok && data.access_token) {
|
|
115
|
+
const expiresIn = data.expire_in || 3600;
|
|
116
|
+
this.saveTokens(
|
|
117
|
+
data.access_token,
|
|
118
|
+
data.refresh_token || this.refreshToken,
|
|
119
|
+
// Keep old if not provided
|
|
120
|
+
expiresIn
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
throw createAbcError(
|
|
124
|
+
"TOKEN_EXPIRED" /* TOKEN_EXPIRED */,
|
|
125
|
+
"Failed to refresh token"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
this.isRefreshing = false;
|
|
130
|
+
this.refreshPromise = null;
|
|
131
|
+
}
|
|
132
|
+
})();
|
|
133
|
+
await this.refreshPromise;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Make HTTP request
|
|
137
|
+
*/
|
|
138
|
+
async request(endpoint, options = {}) {
|
|
139
|
+
const {
|
|
140
|
+
method = "GET",
|
|
141
|
+
body,
|
|
142
|
+
headers = {},
|
|
143
|
+
skipAuth = false,
|
|
144
|
+
isRetry = false
|
|
145
|
+
} = options;
|
|
146
|
+
if (!skipAuth && this.isTokenExpired() && this.refreshToken) {
|
|
147
|
+
await this.refreshAccessToken();
|
|
148
|
+
}
|
|
149
|
+
const url = `${this.baseURL}${endpoint}`;
|
|
150
|
+
const requestHeaders = {
|
|
151
|
+
...DEFAULT_HEADERS,
|
|
152
|
+
...headers
|
|
153
|
+
};
|
|
154
|
+
if (!skipAuth && this.accessToken) {
|
|
155
|
+
requestHeaders.Authorization = `Bearer ${this.accessToken}`;
|
|
156
|
+
}
|
|
157
|
+
if (this.config.apiKey) {
|
|
158
|
+
requestHeaders["X-API-Key"] = this.config.apiKey;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const controller = new AbortController();
|
|
162
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
|
|
163
|
+
const requestBody = body ? new URLSearchParams(body).toString() : void 0;
|
|
164
|
+
const response = await fetch(url, {
|
|
165
|
+
method,
|
|
166
|
+
headers: requestHeaders,
|
|
167
|
+
body: requestBody,
|
|
168
|
+
signal: controller.signal
|
|
169
|
+
});
|
|
170
|
+
clearTimeout(timeoutId);
|
|
171
|
+
const text = await response.text();
|
|
172
|
+
let data;
|
|
173
|
+
if (text) {
|
|
174
|
+
try {
|
|
175
|
+
data = JSON.parse(text);
|
|
176
|
+
} catch (parseError) {
|
|
177
|
+
console.error("[AbcWaasClient] \u274C JSON parse error:", {
|
|
178
|
+
url,
|
|
179
|
+
method,
|
|
180
|
+
status: response.status,
|
|
181
|
+
responseText: text.substring(0, 200)
|
|
182
|
+
// Log first 200 chars
|
|
183
|
+
});
|
|
184
|
+
throw createAbcError(
|
|
185
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
186
|
+
"Invalid JSON response from server",
|
|
187
|
+
{ text, parseError }
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
console.log("[AbcWaasClient] \u2139\uFE0F Empty response received:", {
|
|
192
|
+
url,
|
|
193
|
+
method,
|
|
194
|
+
status: response.status
|
|
195
|
+
});
|
|
196
|
+
data = { status: "success" };
|
|
197
|
+
}
|
|
198
|
+
if (response.status === 401 && !skipAuth && this.refreshToken && !isRetry) {
|
|
199
|
+
try {
|
|
200
|
+
await this.refreshAccessToken();
|
|
201
|
+
return await this.request(endpoint, {
|
|
202
|
+
...options,
|
|
203
|
+
isRetry: true
|
|
204
|
+
// Prevent infinite retry loop
|
|
205
|
+
});
|
|
206
|
+
} catch (refreshError) {
|
|
207
|
+
console.error("Token refresh failed:", refreshError);
|
|
208
|
+
throw parseApiError({
|
|
209
|
+
response: {
|
|
210
|
+
status: response.status,
|
|
211
|
+
data
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
console.error("[AbcWaasClient] \u274C API Error:", {
|
|
218
|
+
url,
|
|
219
|
+
method,
|
|
220
|
+
status: response.status,
|
|
221
|
+
statusText: response.statusText,
|
|
222
|
+
data
|
|
223
|
+
});
|
|
224
|
+
throw parseApiError({
|
|
225
|
+
response: {
|
|
226
|
+
status: response.status,
|
|
227
|
+
data
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
return data;
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error("[AbcWaasClient] \u274C Request failed:", {
|
|
234
|
+
url,
|
|
235
|
+
method,
|
|
236
|
+
error: error.message,
|
|
237
|
+
errorName: error.name,
|
|
238
|
+
errorType: error.constructor.name,
|
|
239
|
+
stack: error.stack
|
|
240
|
+
});
|
|
241
|
+
if (error.name === "AbortError") {
|
|
242
|
+
throw createAbcError(
|
|
243
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
244
|
+
"Request timeout",
|
|
245
|
+
error
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (error.code && error.message) {
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
throw parseApiError(error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Login with email and password
|
|
256
|
+
* NOTE: Tokens are stored in memory only. Call persistTokens() after PIN verification to save to localStorage.
|
|
257
|
+
*/
|
|
258
|
+
async loginWithEmail(email, password) {
|
|
259
|
+
const response = await this.request(
|
|
260
|
+
ABC_ENDPOINTS.SNS_LOGIN,
|
|
261
|
+
{
|
|
262
|
+
method: "POST",
|
|
263
|
+
body: {
|
|
264
|
+
email,
|
|
265
|
+
token: password,
|
|
266
|
+
// Use password as token
|
|
267
|
+
service: "email",
|
|
268
|
+
audience: ABC_AUDIENCE
|
|
269
|
+
},
|
|
270
|
+
skipAuth: true
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
if (response.status === "success" && response.data) {
|
|
274
|
+
this.accessToken = response.data.accessToken;
|
|
275
|
+
this.refreshToken = response.data.refreshToken;
|
|
276
|
+
this.expiresAt = calculateExpiryTimestamp(response.data.expiresIn);
|
|
277
|
+
console.log(
|
|
278
|
+
"[AbcWaasClient] \u2705 Login successful, tokens stored in memory (not persisted yet)"
|
|
279
|
+
);
|
|
280
|
+
return response.data;
|
|
281
|
+
}
|
|
282
|
+
throw createAbcError(
|
|
283
|
+
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
|
|
284
|
+
"Email login failed",
|
|
285
|
+
response
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Login with email and OTP (for PIN recovery)
|
|
290
|
+
* NOTE: Tokens are stored in memory only. Call persistTokens() after PIN verification to save to localStorage.
|
|
291
|
+
*/
|
|
292
|
+
async loginWithOtp(email, otpCode) {
|
|
293
|
+
const response = await this.request(
|
|
294
|
+
ABC_ENDPOINTS.SNS_LOGIN,
|
|
295
|
+
{
|
|
296
|
+
method: "POST",
|
|
297
|
+
body: {
|
|
298
|
+
email,
|
|
299
|
+
token: otpCode,
|
|
300
|
+
service: "email",
|
|
301
|
+
audience: ABC_AUDIENCE
|
|
302
|
+
},
|
|
303
|
+
skipAuth: true
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
if (response.status === "success" && response.data) {
|
|
307
|
+
this.accessToken = response.data.accessToken;
|
|
308
|
+
this.refreshToken = response.data.refreshToken;
|
|
309
|
+
this.expiresAt = calculateExpiryTimestamp(response.data.expiresIn);
|
|
310
|
+
console.log(
|
|
311
|
+
"[AbcWaasClient] \u2705 OTP login successful, tokens stored in memory (not persisted yet)"
|
|
312
|
+
);
|
|
313
|
+
return response.data;
|
|
314
|
+
}
|
|
315
|
+
throw createAbcError(
|
|
316
|
+
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
|
|
317
|
+
"OTP login failed",
|
|
318
|
+
response
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Login with social provider
|
|
323
|
+
* NOTE: Tokens are stored in memory only. Call persistTokens() after PIN verification to save to localStorage.
|
|
324
|
+
*/
|
|
325
|
+
async loginWithSocial(provider, token, email) {
|
|
326
|
+
const response = await this.request(
|
|
327
|
+
ABC_ENDPOINTS.SNS_LOGIN,
|
|
328
|
+
{
|
|
329
|
+
method: "POST",
|
|
330
|
+
body: {
|
|
331
|
+
token,
|
|
332
|
+
service: provider,
|
|
333
|
+
audience: ABC_AUDIENCE,
|
|
334
|
+
email
|
|
335
|
+
},
|
|
336
|
+
skipAuth: true
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
if (response.status === "success" && response.data) {
|
|
340
|
+
this.accessToken = response.data.accessToken;
|
|
341
|
+
this.refreshToken = response.data.refreshToken;
|
|
342
|
+
this.expiresAt = calculateExpiryTimestamp(response.data.expiresIn);
|
|
343
|
+
console.log(
|
|
344
|
+
"[AbcWaasClient] \u2705 Social login successful, tokens stored in memory (not persisted yet)"
|
|
345
|
+
);
|
|
346
|
+
return response.data;
|
|
347
|
+
}
|
|
348
|
+
throw createAbcError(
|
|
349
|
+
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
|
|
350
|
+
"Social login failed",
|
|
351
|
+
response
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Refresh access token using refresh token (public wrapper)
|
|
356
|
+
*/
|
|
357
|
+
async refreshTokens(refreshTokenValue) {
|
|
358
|
+
const oldRefreshToken = this.refreshToken;
|
|
359
|
+
this.refreshToken = refreshTokenValue;
|
|
360
|
+
try {
|
|
361
|
+
await this.refreshAccessToken();
|
|
362
|
+
return {
|
|
363
|
+
accessToken: this.accessToken,
|
|
364
|
+
refreshToken: this.refreshToken,
|
|
365
|
+
expiresIn: Math.floor((this.expiresAt - Date.now()) / 1e3)
|
|
366
|
+
};
|
|
367
|
+
} catch (error) {
|
|
368
|
+
this.refreshToken = oldRefreshToken;
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Request OTP code for email
|
|
374
|
+
*/
|
|
375
|
+
async requestOtpCode(email) {
|
|
376
|
+
const response = await this.request(
|
|
377
|
+
ABC_ENDPOINTS.SEND_OTP,
|
|
378
|
+
{
|
|
379
|
+
method: "POST",
|
|
380
|
+
body: { email },
|
|
381
|
+
skipAuth: true
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
if (response.status === "success" && response.data) {
|
|
385
|
+
return response.data;
|
|
386
|
+
}
|
|
387
|
+
throw createAbcError(
|
|
388
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
389
|
+
"Failed to request OTP",
|
|
390
|
+
response
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Verify OTP code
|
|
395
|
+
*/
|
|
396
|
+
async verifyOtpCode(email, code) {
|
|
397
|
+
const response = await this.request(ABC_ENDPOINTS.VERIFY_OTP, {
|
|
398
|
+
method: "POST",
|
|
399
|
+
body: { email, code },
|
|
400
|
+
skipAuth: true
|
|
401
|
+
});
|
|
402
|
+
return response.status === "success";
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Check if email already exists
|
|
406
|
+
* Reference: tg-wallet-frontend src/features/user/api/index.ts:63-69
|
|
407
|
+
*/
|
|
408
|
+
async emailCheck(email) {
|
|
409
|
+
const talkenApiUrl = process.env.NEXT_PUBLIC_API_SERVER || "https://dev.walletapi.talken.io";
|
|
410
|
+
const url = `${talkenApiUrl}/abc/emailCheck?email=${encodeURIComponent(email)}`;
|
|
411
|
+
const method = "GET";
|
|
412
|
+
console.log("[AbcWaasClient] \u{1F4EC} Checking email:", {
|
|
413
|
+
email,
|
|
414
|
+
url,
|
|
415
|
+
apiServer: talkenApiUrl
|
|
416
|
+
});
|
|
417
|
+
try {
|
|
418
|
+
const response = await fetch(url, {
|
|
419
|
+
method,
|
|
420
|
+
headers: DEFAULT_HEADERS
|
|
421
|
+
});
|
|
422
|
+
const text = await response.text();
|
|
423
|
+
const data = text ? JSON.parse(text) : { status: "success" };
|
|
424
|
+
if (!response.ok) {
|
|
425
|
+
console.error("[AbcWaasClient] \u274C API Error:", {
|
|
426
|
+
url,
|
|
427
|
+
method,
|
|
428
|
+
status: response.status,
|
|
429
|
+
statusText: response.statusText,
|
|
430
|
+
data
|
|
431
|
+
});
|
|
432
|
+
throw parseApiError({
|
|
433
|
+
response: {
|
|
434
|
+
status: response.status,
|
|
435
|
+
data
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return data;
|
|
440
|
+
} catch (error) {
|
|
441
|
+
if (error.name === "AbortError") {
|
|
442
|
+
throw createAbcError(
|
|
443
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
444
|
+
"Request timeout",
|
|
445
|
+
error
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
throw parseApiError(error);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Send OTP code to email for signup
|
|
453
|
+
* Reference: tg-wallet-frontend src/features/user/api/index.ts:105-110
|
|
454
|
+
* Note: Uses ABC WaaS endpoint, not Talken API
|
|
455
|
+
*/
|
|
456
|
+
async sendOtpCode(email) {
|
|
457
|
+
const endpoint = `/member/mail-service/${encodeURIComponent(email)}/sendcode`;
|
|
458
|
+
console.log("[AbcWaasClient] \u{1F4E7} Sending OTP code:", {
|
|
459
|
+
email,
|
|
460
|
+
endpoint,
|
|
461
|
+
fullUrl: `${this.baseURL}${endpoint}`
|
|
462
|
+
});
|
|
463
|
+
const response = await this.request(endpoint, {
|
|
464
|
+
method: "GET",
|
|
465
|
+
skipAuth: true
|
|
466
|
+
});
|
|
467
|
+
if (response.status === "success") {
|
|
468
|
+
return response;
|
|
469
|
+
}
|
|
470
|
+
throw createAbcError(
|
|
471
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
472
|
+
"Failed to send OTP code",
|
|
473
|
+
response
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Verify OTP code for signup
|
|
478
|
+
* Reference: tg-wallet-frontend src/features/user/api/index.ts:112-123
|
|
479
|
+
* Note: Uses ABC WaaS endpoint, not Talken API
|
|
480
|
+
*/
|
|
481
|
+
async verifyOtpCodeSignup(email, code) {
|
|
482
|
+
const endpoint = `/member/mail-service/${encodeURIComponent(email)}/verifycode`;
|
|
483
|
+
const requestBody = {
|
|
484
|
+
email,
|
|
485
|
+
code,
|
|
486
|
+
serviceid: ABC_AUDIENCE
|
|
487
|
+
// 'https://mw.myabcwallet.com'
|
|
488
|
+
};
|
|
489
|
+
console.log("[AbcWaasClient] \u{1F510} Verifying OTP code:", {
|
|
490
|
+
email,
|
|
491
|
+
code: `${code.substring(0, 2)}****`,
|
|
492
|
+
// Mask OTP for security
|
|
493
|
+
serviceid: ABC_AUDIENCE,
|
|
494
|
+
endpoint,
|
|
495
|
+
fullUrl: `${this.baseURL}${endpoint}`,
|
|
496
|
+
requestBody
|
|
497
|
+
});
|
|
498
|
+
const response = await this.request(endpoint, {
|
|
499
|
+
method: "POST",
|
|
500
|
+
body: requestBody,
|
|
501
|
+
skipAuth: true
|
|
502
|
+
});
|
|
503
|
+
console.log("[verifyOtpCodeSignup] Response:", response);
|
|
504
|
+
const isSuccess = response.status === "success" || response.status === 200 || response.message === "success";
|
|
505
|
+
if (isSuccess) {
|
|
506
|
+
return response;
|
|
507
|
+
}
|
|
508
|
+
console.error("[verifyOtpCodeSignup] Failed:", response);
|
|
509
|
+
throw createAbcError(
|
|
510
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
511
|
+
"Invalid or expired OTP code",
|
|
512
|
+
response
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* SNS (Social) Login via Google/Apple/Kakao
|
|
517
|
+
* Reference: tg-wallet-frontend src/features/user/api/index.ts:43-51
|
|
518
|
+
* Note: Uses Talken API server, not ABC WaaS
|
|
519
|
+
*/
|
|
520
|
+
async snsLogin(params) {
|
|
521
|
+
const talkenApiUrl = process.env.NEXT_PUBLIC_API_SERVER || "https://dev.walletapi.talken.io";
|
|
522
|
+
const url = `${talkenApiUrl}/abc/snsLogin`;
|
|
523
|
+
console.log("[AbcWaasClient] \u{1F510} SNS Login:", {
|
|
524
|
+
service: params.service,
|
|
525
|
+
email: params.email,
|
|
526
|
+
audience: params.audience,
|
|
527
|
+
hasToken: !!params.token,
|
|
528
|
+
url
|
|
529
|
+
});
|
|
530
|
+
try {
|
|
531
|
+
const response = await fetch(url, {
|
|
532
|
+
method: "POST",
|
|
533
|
+
headers: {
|
|
534
|
+
...DEFAULT_HEADERS
|
|
535
|
+
},
|
|
536
|
+
credentials: "include",
|
|
537
|
+
// withCredentials from reference
|
|
538
|
+
body: new URLSearchParams(params).toString()
|
|
539
|
+
});
|
|
540
|
+
const data = await response.json();
|
|
541
|
+
if (!response.ok) {
|
|
542
|
+
console.error("[AbcWaasClient] \u274C SNS Login failed:", {
|
|
543
|
+
url,
|
|
544
|
+
status: response.status,
|
|
545
|
+
statusText: response.statusText,
|
|
546
|
+
data
|
|
547
|
+
});
|
|
548
|
+
throw parseApiError({
|
|
549
|
+
response: {
|
|
550
|
+
status: response.status,
|
|
551
|
+
data
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
if (!data.access_token) {
|
|
556
|
+
throw createAbcError(
|
|
557
|
+
"INVALID_CREDENTIALS" /* INVALID_CREDENTIALS */,
|
|
558
|
+
"SNS login failed: Invalid response from server",
|
|
559
|
+
{ service: params.service, response: data }
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
console.log("[AbcWaasClient] \u2705 SNS login successful:", params.email);
|
|
563
|
+
return {
|
|
564
|
+
uid: "",
|
|
565
|
+
// Will be fetched later via info() API
|
|
566
|
+
email: params.email,
|
|
567
|
+
access_token: data.access_token,
|
|
568
|
+
refresh_token: data.refresh_token,
|
|
569
|
+
user_type: "existing"
|
|
570
|
+
// Default to existing, will be determined later
|
|
571
|
+
};
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (error.name === "AbortError") {
|
|
574
|
+
throw createAbcError(
|
|
575
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
576
|
+
"SNS login request timeout",
|
|
577
|
+
error
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Register new user
|
|
585
|
+
* Reference: tg-wallet-frontend src/features/user/api/index.ts:71-78
|
|
586
|
+
* Note: Uses Talken API server, not ABC WaaS
|
|
587
|
+
*/
|
|
588
|
+
async registerUser(params) {
|
|
589
|
+
const talkenApiUrl = process.env.NEXT_PUBLIC_API_SERVER || "https://dev.walletapi.talken.io";
|
|
590
|
+
const url = `${talkenApiUrl}/abc/adduser`;
|
|
591
|
+
const method = "POST";
|
|
592
|
+
console.log("[AbcWaasClient] \u{1F4DD} Registering user:", {
|
|
593
|
+
username: params.username,
|
|
594
|
+
name: params.name,
|
|
595
|
+
hasPassword: !!params.password,
|
|
596
|
+
hasSecureChannel: !!params.secureChannel,
|
|
597
|
+
hasEmailCode: !!params.emailCode,
|
|
598
|
+
url
|
|
599
|
+
});
|
|
600
|
+
try {
|
|
601
|
+
const response = await fetch(url, {
|
|
602
|
+
method,
|
|
603
|
+
headers: {
|
|
604
|
+
...DEFAULT_HEADERS
|
|
605
|
+
},
|
|
606
|
+
credentials: "include",
|
|
607
|
+
// withCredentials from reference
|
|
608
|
+
body: new URLSearchParams(params).toString()
|
|
609
|
+
});
|
|
610
|
+
const text = await response.text();
|
|
611
|
+
const data = text ? JSON.parse(text) : { status: "success" };
|
|
612
|
+
if (!response.ok) {
|
|
613
|
+
console.error("[AbcWaasClient] \u274C API Error:", {
|
|
614
|
+
url,
|
|
615
|
+
method,
|
|
616
|
+
status: response.status,
|
|
617
|
+
statusText: response.statusText,
|
|
618
|
+
data
|
|
619
|
+
});
|
|
620
|
+
throw parseApiError({
|
|
621
|
+
response: {
|
|
622
|
+
status: response.status,
|
|
623
|
+
data
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return data;
|
|
628
|
+
} catch (error) {
|
|
629
|
+
if (error.name === "AbortError") {
|
|
630
|
+
throw createAbcError(
|
|
631
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
632
|
+
"Request timeout",
|
|
633
|
+
error
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
throw parseApiError(error);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Register new SNS user (email-based registration with OTP)
|
|
641
|
+
* Reference: tg-wallet-frontend src/features/user/api/index.ts:80-87
|
|
642
|
+
* Note: Uses Talken API server, not ABC WaaS
|
|
643
|
+
*/
|
|
644
|
+
async registerSnsUser(params) {
|
|
645
|
+
const talkenApiUrl = process.env.NEXT_PUBLIC_API_SERVER || "https://dev.walletapi.talken.io";
|
|
646
|
+
const url = `${talkenApiUrl}/abc/snsAdduser`;
|
|
647
|
+
const method = "POST";
|
|
648
|
+
console.log("[AbcWaasClient] \u{1F4DD} Registering SNS user:", {
|
|
649
|
+
username: params.username,
|
|
650
|
+
name: params.name,
|
|
651
|
+
hasOtp: !!params.sixCode,
|
|
652
|
+
url
|
|
653
|
+
});
|
|
654
|
+
try {
|
|
655
|
+
const response = await fetch(url, {
|
|
656
|
+
method,
|
|
657
|
+
headers: {
|
|
658
|
+
...DEFAULT_HEADERS
|
|
659
|
+
},
|
|
660
|
+
credentials: "include",
|
|
661
|
+
// withCredentials from reference
|
|
662
|
+
body: new URLSearchParams(params).toString()
|
|
663
|
+
});
|
|
664
|
+
const text = await response.text();
|
|
665
|
+
const data = text ? JSON.parse(text) : { status: "success" };
|
|
666
|
+
if (!response.ok) {
|
|
667
|
+
console.error("[AbcWaasClient] \u274C API Error:", {
|
|
668
|
+
url,
|
|
669
|
+
method,
|
|
670
|
+
status: response.status,
|
|
671
|
+
statusText: response.statusText,
|
|
672
|
+
data
|
|
673
|
+
});
|
|
674
|
+
throw parseApiError({
|
|
675
|
+
response: {
|
|
676
|
+
status: response.status,
|
|
677
|
+
data
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
console.log("[AbcWaasClient] \u2705 SNS user registered successfully");
|
|
682
|
+
return data;
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (error.name === "AbortError") {
|
|
685
|
+
throw createAbcError(
|
|
686
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
687
|
+
"Request timeout",
|
|
688
|
+
error
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
throw parseApiError(error);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Reset/Set password for existing user
|
|
696
|
+
* Reference: tg-wallet-frontend src/features/user/api/index.ts:89-95
|
|
697
|
+
* Note: Uses Talken API server, not ABC WaaS
|
|
698
|
+
*/
|
|
699
|
+
async resetPassword(params) {
|
|
700
|
+
const talkenApiUrl = process.env.NEXT_PUBLIC_API_SERVER || "https://dev.walletapi.talken.io";
|
|
701
|
+
const url = `${talkenApiUrl}/abc/initpassword`;
|
|
702
|
+
const method = "POST";
|
|
703
|
+
console.log("[AbcWaasClient] \u{1F510} Setting password:", {
|
|
704
|
+
username: params.username,
|
|
705
|
+
hasPassword: !!params.password,
|
|
706
|
+
hasSecureChannel: !!params.secureChannel,
|
|
707
|
+
hasEmailCode: !!params.emailCode,
|
|
708
|
+
url
|
|
709
|
+
});
|
|
710
|
+
try {
|
|
711
|
+
const response = await fetch(url, {
|
|
712
|
+
method,
|
|
713
|
+
headers: {
|
|
714
|
+
...DEFAULT_HEADERS
|
|
715
|
+
},
|
|
716
|
+
credentials: "include",
|
|
717
|
+
body: new URLSearchParams(params).toString()
|
|
718
|
+
});
|
|
719
|
+
const text = await response.text();
|
|
720
|
+
const data = text ? JSON.parse(text) : { status: "success" };
|
|
721
|
+
if (!response.ok) {
|
|
722
|
+
console.error("[AbcWaasClient] \u274C API Error:", {
|
|
723
|
+
url,
|
|
724
|
+
method,
|
|
725
|
+
status: response.status,
|
|
726
|
+
statusText: response.statusText,
|
|
727
|
+
data
|
|
728
|
+
});
|
|
729
|
+
throw parseApiError({
|
|
730
|
+
response: {
|
|
731
|
+
status: response.status,
|
|
732
|
+
data
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
console.log("[AbcWaasClient] \u2705 Password set successfully");
|
|
737
|
+
return data;
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (error.name === "AbortError") {
|
|
740
|
+
throw createAbcError(
|
|
741
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
742
|
+
"Request timeout",
|
|
743
|
+
error
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
throw parseApiError(error);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Generate or recover wallet
|
|
751
|
+
*/
|
|
752
|
+
async generateOrRecoverWallet(params) {
|
|
753
|
+
const response = await this.request(
|
|
754
|
+
// Use 'any' to get raw API response
|
|
755
|
+
ABC_ENDPOINTS.MPC_WALLETS,
|
|
756
|
+
// Use correct EVM endpoint, not WALLETS_V3 (Solana only)
|
|
757
|
+
{
|
|
758
|
+
method: "POST",
|
|
759
|
+
body: {
|
|
760
|
+
uid: params.uid,
|
|
761
|
+
pin: params.pin,
|
|
762
|
+
chainId: params.chainId,
|
|
763
|
+
network: params.network || "mainnet"
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
);
|
|
767
|
+
if (response.status === "success" && response.data) {
|
|
768
|
+
const raw = response.data;
|
|
769
|
+
console.log("[AbcWaasClient] Raw wallet API response:", {
|
|
770
|
+
hasUid: !!raw.uid,
|
|
771
|
+
hasSid: !!raw.sid,
|
|
772
|
+
hasWid: !!raw.wid,
|
|
773
|
+
hasPvencstr: !!raw.pvencstr,
|
|
774
|
+
hasEncryptDevicePassword: !!raw.encryptDevicePassword,
|
|
775
|
+
hasPubkey: !!raw.pubkey,
|
|
776
|
+
allKeys: Object.keys(raw)
|
|
777
|
+
});
|
|
778
|
+
return {
|
|
779
|
+
address: raw.address || "",
|
|
780
|
+
keyId: raw.keyId || raw.key_id || raw.sid,
|
|
781
|
+
encryptedShare: raw.encryptedShare || raw.encrypted_share || "",
|
|
782
|
+
uid: raw.uid,
|
|
783
|
+
sid: raw.sid,
|
|
784
|
+
wid: raw.wid,
|
|
785
|
+
// Preserve for Bitcoin signing
|
|
786
|
+
pvencstr: raw.pvencstr,
|
|
787
|
+
// Preserve for Bitcoin signing
|
|
788
|
+
encryptDevicePassword: raw.encryptDevicePassword,
|
|
789
|
+
// Preserve for Bitcoin signing
|
|
790
|
+
pubkey: raw.pubkey || null
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
throw createAbcError(
|
|
794
|
+
"WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */,
|
|
795
|
+
"Failed to generate wallet",
|
|
796
|
+
response
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Sign EVM transaction (EIP-1559)
|
|
801
|
+
* Based on ABC WaaS official documentation
|
|
802
|
+
*/
|
|
803
|
+
async signTransaction(params) {
|
|
804
|
+
this.loadTokens();
|
|
805
|
+
const wallet = loadFromStorage("wallet" /* WALLET */);
|
|
806
|
+
if (!wallet) {
|
|
807
|
+
throw createAbcError(
|
|
808
|
+
"WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */,
|
|
809
|
+
"Wallet information not found in storage"
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
const encryptedDevicePassword = await this.secure.getEncryptPlain(
|
|
813
|
+
wallet.encryptDevicePassword
|
|
814
|
+
);
|
|
815
|
+
const encryptedPvencstr = await this.secure.getEncryptPlain(
|
|
816
|
+
wallet.pvencstr
|
|
817
|
+
);
|
|
818
|
+
const encryptedWid = await this.secure.getEncryptPlain(wallet.wid);
|
|
819
|
+
const secureChannelId = await this.secure.getSecureChannelId();
|
|
820
|
+
const networkMap = {
|
|
821
|
+
// Ethereum Networks
|
|
822
|
+
1: "ethereum",
|
|
823
|
+
// Ethereum Mainnet
|
|
824
|
+
11155111: "ethereum_sepolia",
|
|
825
|
+
// Ethereum Sepolia Testnet
|
|
826
|
+
17e3: "ethereum_holesky",
|
|
827
|
+
// Ethereum Holesky Testnet
|
|
828
|
+
// Polygon Networks
|
|
829
|
+
137: "polygon",
|
|
830
|
+
// Polygon Mainnet
|
|
831
|
+
80001: "polygon_amoy",
|
|
832
|
+
// Polygon Amoy Testnet
|
|
833
|
+
// Arbitrum Networks
|
|
834
|
+
42161: "arbitrum",
|
|
835
|
+
// Arbitrum One
|
|
836
|
+
421614: "arbitrum_sepolia",
|
|
837
|
+
// Arbitrum Sepolia
|
|
838
|
+
// Optimism Networks
|
|
839
|
+
10: "optimism",
|
|
840
|
+
// Optimism Mainnet
|
|
841
|
+
420: "optimism_sepolia",
|
|
842
|
+
// Optimism Sepolia
|
|
843
|
+
// Binance Smart Chain
|
|
844
|
+
56: "binance",
|
|
845
|
+
// BSC Mainnet
|
|
846
|
+
97: "binance_testnet",
|
|
847
|
+
// BSC Testnet
|
|
848
|
+
// Avalanche
|
|
849
|
+
43114: "avalanche",
|
|
850
|
+
// Avalanche C-Chain
|
|
851
|
+
43113: "avalanche_fuji",
|
|
852
|
+
// Avalanche Fuji Testnet
|
|
853
|
+
// Kaia (formerly Klaytn)
|
|
854
|
+
8217: "kaia",
|
|
855
|
+
// Kaia Mainnet
|
|
856
|
+
1001: "kaia_kairos",
|
|
857
|
+
// Kaia Kairos Testnet
|
|
858
|
+
// Other EVM Networks
|
|
859
|
+
5e3: "mantle",
|
|
860
|
+
// Mantle Mainnet
|
|
861
|
+
5001: "mantle_testnet",
|
|
862
|
+
// Mantle Testnet
|
|
863
|
+
1284: "moonbeam",
|
|
864
|
+
// Moonbeam
|
|
865
|
+
1285: "moonriver",
|
|
866
|
+
// Moonriver
|
|
867
|
+
534352: "scroll",
|
|
868
|
+
// Scroll Mainnet
|
|
869
|
+
534351: "scroll_testnet",
|
|
870
|
+
// Scroll Testnet
|
|
871
|
+
2355: "silicon",
|
|
872
|
+
// Silicon
|
|
873
|
+
1722641160: "silicon_sepolia",
|
|
874
|
+
// Silicon Sepolia
|
|
875
|
+
// Note: Taiko Mainnet has same Chain ID as Ethereum Holesky (17000)
|
|
876
|
+
// 17000: 'taiko', // Taiko Mainnet (conflicts with ethereum_holesky)
|
|
877
|
+
167009: "taiko_hekla",
|
|
878
|
+
// Taiko Hekla Testnet
|
|
879
|
+
1111: "wemix",
|
|
880
|
+
// Wemix Mainnet
|
|
881
|
+
1112: "wemix_testnet",
|
|
882
|
+
// Wemix Testnet
|
|
883
|
+
51828: "chainbounty",
|
|
884
|
+
// ChainBounty
|
|
885
|
+
56580: "chainbounty_testnet"
|
|
886
|
+
// ChainBounty Testnet
|
|
887
|
+
};
|
|
888
|
+
const network = networkMap[params.chainId] || "ethereum";
|
|
889
|
+
const tx = params.transaction;
|
|
890
|
+
if (!tx.maxFeePerGas || !tx.maxPriorityFeePerGas || !tx.gasLimit || !tx.gas) {
|
|
891
|
+
try {
|
|
892
|
+
if (!tx.maxFeePerGas || !tx.maxPriorityFeePerGas) {
|
|
893
|
+
const gasFeeUrl = `${this.baseURL}${ABC_ENDPOINTS.GAS_SUGGESTED_FEES}?network=${network}`;
|
|
894
|
+
const gasFeeResponse = await fetch(gasFeeUrl, {
|
|
895
|
+
method: "GET",
|
|
896
|
+
headers: {
|
|
897
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
if (gasFeeResponse.ok) {
|
|
901
|
+
const gasData = await gasFeeResponse.json();
|
|
902
|
+
if (gasData.medium) {
|
|
903
|
+
if (!tx.maxFeePerGas) {
|
|
904
|
+
const maxFeeGwei = Number.parseFloat(
|
|
905
|
+
gasData.medium.suggestedMaxFeePerGas
|
|
906
|
+
);
|
|
907
|
+
tx.maxFeePerGas = Math.floor(maxFeeGwei * 1e9).toString();
|
|
908
|
+
}
|
|
909
|
+
if (!tx.maxPriorityFeePerGas) {
|
|
910
|
+
const priorityFeeGwei = Number.parseFloat(
|
|
911
|
+
gasData.medium.suggestedMaxPriorityFeePerGas
|
|
912
|
+
);
|
|
913
|
+
tx.maxPriorityFeePerGas = Math.floor(
|
|
914
|
+
priorityFeeGwei * 1e9
|
|
915
|
+
).toString();
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
if (!tx.gasLimit && !tx.gas) {
|
|
921
|
+
const estimateBody = new URLSearchParams({
|
|
922
|
+
network,
|
|
923
|
+
to: tx.to || "0x",
|
|
924
|
+
// Use '0x' for contract deployment
|
|
925
|
+
...tx.from ? { from: tx.from } : {},
|
|
926
|
+
...tx.value ? { value: tx.value } : {},
|
|
927
|
+
...tx.data && tx.data !== "0x" ? { data: tx.data } : {},
|
|
928
|
+
...tx.maxFeePerGas ? { maxFeePerGas: tx.maxFeePerGas } : {},
|
|
929
|
+
...tx.maxPriorityFeePerGas ? { maxPriorityFeePerGas: tx.maxPriorityFeePerGas } : {}
|
|
930
|
+
});
|
|
931
|
+
const estimateUrl = `${this.baseURL}${ABC_ENDPOINTS.GAS_ESTIMATE_EIP1559}`;
|
|
932
|
+
const estimateResponse = await fetch(estimateUrl, {
|
|
933
|
+
method: "POST",
|
|
934
|
+
headers: {
|
|
935
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
936
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
937
|
+
},
|
|
938
|
+
body: estimateBody.toString()
|
|
939
|
+
});
|
|
940
|
+
const estimateData = await estimateResponse.json();
|
|
941
|
+
if (estimateResponse.ok && estimateData.result) {
|
|
942
|
+
tx.gasLimit = estimateData.result;
|
|
943
|
+
} else {
|
|
944
|
+
const fallbackGasLimit = "0x2dc6c0";
|
|
945
|
+
tx.gasLimit = fallbackGasLimit;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
} catch (error) {
|
|
949
|
+
console.warn("Failed to fetch gas parameters from ABC WaaS:", error);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
const isContractDeployment = !params.transaction.to || params.transaction.to === "0x" || params.transaction.to === "0x0";
|
|
953
|
+
const bodyData = {
|
|
954
|
+
network,
|
|
955
|
+
encryptDevicePassword: encryptedDevicePassword,
|
|
956
|
+
pvencstr: encryptedPvencstr,
|
|
957
|
+
uid: wallet.uid,
|
|
958
|
+
wid: encryptedWid,
|
|
959
|
+
sid: getAddress(wallet.address),
|
|
960
|
+
// EIP-55 checksum format
|
|
961
|
+
// For contract deployment, 'to' is '0x' (ABC WaaS convention)
|
|
962
|
+
// Reference: talken-nft-drops/src/libs/abc-waas-provider/index.ts:800 - to: to ?? '0x'
|
|
963
|
+
to: tx.to === "0x" ? "0x" : getAddress(tx.to),
|
|
964
|
+
// Required by ABC WaaS API
|
|
965
|
+
// Always use EIP1559 type (ABC WaaS handles network-specific conversion)
|
|
966
|
+
type: "EIP1559",
|
|
967
|
+
// ourpubkey and ucpubkey are required by ABC WaaS (empty strings for now)
|
|
968
|
+
// Based on successful tg-wallet payload analysis
|
|
969
|
+
ourpubkey: "",
|
|
970
|
+
ucpubkey: ""
|
|
971
|
+
};
|
|
972
|
+
bodyData.from = getAddress(tx.from || wallet.address);
|
|
973
|
+
if (tx.value) {
|
|
974
|
+
let hexValue = tx.value.startsWith("0x") ? tx.value.slice(2) : tx.value;
|
|
975
|
+
if (hexValue.length % 2 !== 0) {
|
|
976
|
+
hexValue = `0${hexValue}`;
|
|
977
|
+
}
|
|
978
|
+
bodyData.value = `0x${hexValue}`;
|
|
979
|
+
} else {
|
|
980
|
+
bodyData.value = "0x0";
|
|
981
|
+
}
|
|
982
|
+
if (tx.gasLimit || tx.gas) {
|
|
983
|
+
const gasLimitValue = tx.gasLimit || tx.gas;
|
|
984
|
+
if (gasLimitValue.toString().startsWith("0x")) {
|
|
985
|
+
bodyData.gasLimit = gasLimitValue.toString();
|
|
986
|
+
} else {
|
|
987
|
+
bodyData.gasLimit = `0x${BigInt(gasLimitValue).toString(16)}`;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (tx.maxFeePerGas) {
|
|
991
|
+
if (tx.maxFeePerGas.toString().startsWith("0x")) {
|
|
992
|
+
bodyData.maxFeePerGas = tx.maxFeePerGas.toString();
|
|
993
|
+
} else {
|
|
994
|
+
bodyData.maxFeePerGas = `0x${BigInt(tx.maxFeePerGas).toString(16)}`;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
if (tx.maxPriorityFeePerGas) {
|
|
998
|
+
if (tx.maxPriorityFeePerGas.toString().startsWith("0x")) {
|
|
999
|
+
bodyData.maxPriorityFeePerGas = tx.maxPriorityFeePerGas.toString();
|
|
1000
|
+
} else {
|
|
1001
|
+
bodyData.maxPriorityFeePerGas = `0x${BigInt(tx.maxPriorityFeePerGas).toString(16)}`;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if (tx.gasPrice) {
|
|
1005
|
+
if (tx.gasPrice.toString().startsWith("0x")) {
|
|
1006
|
+
bodyData.gasPrice = tx.gasPrice.toString();
|
|
1007
|
+
} else {
|
|
1008
|
+
bodyData.gasPrice = `0x${BigInt(tx.gasPrice).toString(16)}`;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (tx.data !== void 0 && tx.data !== null) {
|
|
1012
|
+
bodyData.data = tx.data || "0x";
|
|
1013
|
+
} else {
|
|
1014
|
+
bodyData.data = "0x";
|
|
1015
|
+
}
|
|
1016
|
+
const bodyString = new URLSearchParams(bodyData).toString();
|
|
1017
|
+
const url = `${this.baseURL}${ABC_ENDPOINTS.SIGN_TRANSACTION}`;
|
|
1018
|
+
const response = await fetch(url, {
|
|
1019
|
+
method: "POST",
|
|
1020
|
+
headers: {
|
|
1021
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1022
|
+
"Secure-Channel": secureChannelId,
|
|
1023
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1024
|
+
},
|
|
1025
|
+
body: bodyString
|
|
1026
|
+
});
|
|
1027
|
+
const res = await response.json();
|
|
1028
|
+
console.log("\u{1F4E1} ABC WaaS /sign response:", {
|
|
1029
|
+
status: response.status,
|
|
1030
|
+
ok: response.ok,
|
|
1031
|
+
response: res
|
|
1032
|
+
});
|
|
1033
|
+
if (!response.ok) {
|
|
1034
|
+
console.error("\u274C ABC WaaS API error details:", {
|
|
1035
|
+
status: response.status,
|
|
1036
|
+
statusText: response.statusText,
|
|
1037
|
+
errorCode: res.code,
|
|
1038
|
+
errorMessage: res.msg || res.message || res.detail,
|
|
1039
|
+
fullResponse: res
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
if (response.status === 401 && this.refreshToken) {
|
|
1043
|
+
try {
|
|
1044
|
+
await this.refreshAccessToken();
|
|
1045
|
+
const retryResponse = await fetch(url, {
|
|
1046
|
+
method: "POST",
|
|
1047
|
+
headers: {
|
|
1048
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1049
|
+
"Secure-Channel": secureChannelId,
|
|
1050
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1051
|
+
},
|
|
1052
|
+
body: bodyString
|
|
1053
|
+
});
|
|
1054
|
+
const retryRes = await retryResponse.json();
|
|
1055
|
+
if (retryRes.serializedTx && retryRes.rawTx) {
|
|
1056
|
+
return {
|
|
1057
|
+
signature: retryRes.serializedTx,
|
|
1058
|
+
txHash: retryRes.rawTx
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
throw createAbcError(
|
|
1062
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1063
|
+
"Failed to sign transaction after token refresh",
|
|
1064
|
+
retryRes
|
|
1065
|
+
);
|
|
1066
|
+
} catch (refreshError) {
|
|
1067
|
+
console.error("Token refresh failed in signTransaction:", refreshError);
|
|
1068
|
+
throw refreshError;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (res.serializedTx && res.rawTx) {
|
|
1072
|
+
return {
|
|
1073
|
+
signature: res.serializedTx,
|
|
1074
|
+
txHash: res.rawTx
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
throw createAbcError(
|
|
1078
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1079
|
+
"Failed to sign transaction",
|
|
1080
|
+
res
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Sign message (Personal Sign - EIP-191)
|
|
1085
|
+
* Based on tg-wallet-frontend reference implementation
|
|
1086
|
+
*/
|
|
1087
|
+
async signMessage(params) {
|
|
1088
|
+
this.loadTokens();
|
|
1089
|
+
const wallet = loadFromStorage("wallet" /* WALLET */);
|
|
1090
|
+
if (!wallet) {
|
|
1091
|
+
throw createAbcError(
|
|
1092
|
+
"WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */,
|
|
1093
|
+
"Wallet information not found in storage"
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
const ethers = await import("ethers");
|
|
1097
|
+
const messageHash = ethers.ethers.utils.hashMessage(params.message);
|
|
1098
|
+
const hash = messageHash.startsWith("0x") ? messageHash.slice(2) : messageHash;
|
|
1099
|
+
const encryptedDevicePassword = await this.secure.getEncryptPlain(
|
|
1100
|
+
wallet.encryptDevicePassword
|
|
1101
|
+
);
|
|
1102
|
+
const encryptedPvencstr = await this.secure.getEncryptPlain(
|
|
1103
|
+
wallet.pvencstr
|
|
1104
|
+
);
|
|
1105
|
+
const encryptedWid = await this.secure.getEncryptPlain(wallet.wid);
|
|
1106
|
+
const secureChannelId = await this.secure.getSecureChannelId();
|
|
1107
|
+
const bodyData = {
|
|
1108
|
+
hash,
|
|
1109
|
+
encryptDevicePassword: encryptedDevicePassword,
|
|
1110
|
+
pvencstr: encryptedPvencstr,
|
|
1111
|
+
wid: encryptedWid,
|
|
1112
|
+
uid: wallet.uid,
|
|
1113
|
+
sid: wallet.sid
|
|
1114
|
+
};
|
|
1115
|
+
const bodyString = new URLSearchParams(bodyData).toString();
|
|
1116
|
+
const url = `${this.baseURL}${ABC_ENDPOINTS.SIGN_HASH}`;
|
|
1117
|
+
const response = await fetch(url, {
|
|
1118
|
+
method: "POST",
|
|
1119
|
+
headers: {
|
|
1120
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1121
|
+
"Secure-Channel": secureChannelId,
|
|
1122
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1123
|
+
},
|
|
1124
|
+
body: bodyString
|
|
1125
|
+
});
|
|
1126
|
+
const text = await response.text();
|
|
1127
|
+
if (!text) {
|
|
1128
|
+
throw createAbcError(
|
|
1129
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1130
|
+
`Empty response from sign API (status: ${response.status})`,
|
|
1131
|
+
{ status: response.status }
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
const res = JSON.parse(text);
|
|
1135
|
+
if (response.status === 401) {
|
|
1136
|
+
if (!this.refreshToken) {
|
|
1137
|
+
this.loadTokens();
|
|
1138
|
+
}
|
|
1139
|
+
if (this.refreshToken) {
|
|
1140
|
+
try {
|
|
1141
|
+
await this.refreshAccessToken();
|
|
1142
|
+
const retryResponse = await fetch(url, {
|
|
1143
|
+
method: "POST",
|
|
1144
|
+
headers: {
|
|
1145
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1146
|
+
"Secure-Channel": secureChannelId,
|
|
1147
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1148
|
+
},
|
|
1149
|
+
body: bodyString
|
|
1150
|
+
});
|
|
1151
|
+
const retryText = await retryResponse.text();
|
|
1152
|
+
if (!retryText) {
|
|
1153
|
+
throw createAbcError(
|
|
1154
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1155
|
+
`Empty response from retry (status: ${retryResponse.status})`,
|
|
1156
|
+
{ status: retryResponse.status }
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
const retryRes = JSON.parse(retryText);
|
|
1160
|
+
if (retryRes.status === "success" && retryRes.result?.signstr) {
|
|
1161
|
+
return { signature: retryRes.result.signstr };
|
|
1162
|
+
}
|
|
1163
|
+
throw createAbcError(
|
|
1164
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1165
|
+
"Failed to sign message after token refresh",
|
|
1166
|
+
retryRes
|
|
1167
|
+
);
|
|
1168
|
+
} catch (refreshError) {
|
|
1169
|
+
console.error("Token refresh failed in signMessage:", refreshError);
|
|
1170
|
+
throw refreshError;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
if (res.signstr) {
|
|
1175
|
+
const signData = JSON.parse(res.signstr);
|
|
1176
|
+
const sigList = signData.sig_list?.[0];
|
|
1177
|
+
if (!sigList || !sigList.r || !sigList.s) {
|
|
1178
|
+
throw createAbcError(
|
|
1179
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1180
|
+
"Invalid signature format",
|
|
1181
|
+
res
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
const v = (sigList.vsource || 0) + 27;
|
|
1185
|
+
const signature = sigList.r + sigList.s.slice(2) + v.toString(16).padStart(2, "0");
|
|
1186
|
+
return { signature };
|
|
1187
|
+
}
|
|
1188
|
+
throw createAbcError(
|
|
1189
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1190
|
+
"Failed to sign message",
|
|
1191
|
+
res
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Sign typed data (EIP-712)
|
|
1196
|
+
* Reference: tg-wallet-frontend src/features/wallet/api/index.ts sign_typed_data()
|
|
1197
|
+
*/
|
|
1198
|
+
async signTypedData(params) {
|
|
1199
|
+
this.loadTokens();
|
|
1200
|
+
const wallet = loadFromStorage("wallet" /* WALLET */);
|
|
1201
|
+
if (!wallet) {
|
|
1202
|
+
throw createAbcError(
|
|
1203
|
+
"WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */,
|
|
1204
|
+
"Wallet information not found in storage"
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
const encryptedDevicePassword = await this.secure.getEncryptPlain(
|
|
1208
|
+
wallet.encryptDevicePassword
|
|
1209
|
+
);
|
|
1210
|
+
const encryptedPvencstr = await this.secure.getEncryptPlain(
|
|
1211
|
+
wallet.pvencstr
|
|
1212
|
+
);
|
|
1213
|
+
const encryptedWid = await this.secure.getEncryptPlain(wallet.wid);
|
|
1214
|
+
const secureChannelId = await this.secure.getSecureChannelId();
|
|
1215
|
+
const getNetworkName = (chainId) => {
|
|
1216
|
+
const networkMap = {
|
|
1217
|
+
1: "ethereum",
|
|
1218
|
+
// Ethereum Mainnet
|
|
1219
|
+
11155111: "ethereum_sepolia",
|
|
1220
|
+
// Sepolia Testnet
|
|
1221
|
+
8217: "klaytn",
|
|
1222
|
+
// Klaytn Mainnet
|
|
1223
|
+
1001: "klaytn_baobab",
|
|
1224
|
+
// Klaytn Baobab Testnet
|
|
1225
|
+
137: "polygon",
|
|
1226
|
+
// Polygon Mainnet
|
|
1227
|
+
80002: "polygon_amoy",
|
|
1228
|
+
// Polygon Amoy Testnet
|
|
1229
|
+
42161: "arbitrum",
|
|
1230
|
+
// Arbitrum One
|
|
1231
|
+
421614: "arbitrum_sepolia",
|
|
1232
|
+
// Arbitrum Sepolia
|
|
1233
|
+
5e3: "mantle",
|
|
1234
|
+
// Mantle Mainnet
|
|
1235
|
+
5003: "mantle_testnet"
|
|
1236
|
+
// Mantle Testnet
|
|
1237
|
+
};
|
|
1238
|
+
return networkMap[chainId] || "ethereum";
|
|
1239
|
+
};
|
|
1240
|
+
const network = getNetworkName(wallet.chainId);
|
|
1241
|
+
const bodyData = {
|
|
1242
|
+
messageJson: params.typedData,
|
|
1243
|
+
// Server expects "messageJson" field
|
|
1244
|
+
version: "v4",
|
|
1245
|
+
// EIP-712 version
|
|
1246
|
+
network,
|
|
1247
|
+
// Network name (required for JSON-RPC)
|
|
1248
|
+
encryptDevicePassword: encryptedDevicePassword,
|
|
1249
|
+
pvencstr: encryptedPvencstr,
|
|
1250
|
+
wid: encryptedWid,
|
|
1251
|
+
uid: wallet.uid,
|
|
1252
|
+
sid: wallet.sid
|
|
1253
|
+
};
|
|
1254
|
+
const bodyString = new URLSearchParams(bodyData).toString();
|
|
1255
|
+
const url = `${this.baseURL}${ABC_ENDPOINTS.SIGN_TYPED}`;
|
|
1256
|
+
const response = await fetch(url, {
|
|
1257
|
+
method: "POST",
|
|
1258
|
+
headers: {
|
|
1259
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1260
|
+
"Secure-Channel": secureChannelId,
|
|
1261
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1262
|
+
},
|
|
1263
|
+
body: bodyString
|
|
1264
|
+
});
|
|
1265
|
+
const text = await response.text();
|
|
1266
|
+
if (!text) {
|
|
1267
|
+
throw createAbcError(
|
|
1268
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1269
|
+
`Empty response from signTypedData API (status: ${response.status})`,
|
|
1270
|
+
{ status: response.status }
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
const res = JSON.parse(text);
|
|
1274
|
+
if (response.status === 401 && this.refreshToken) {
|
|
1275
|
+
try {
|
|
1276
|
+
await this.refreshAccessToken();
|
|
1277
|
+
const retryResponse = await fetch(url, {
|
|
1278
|
+
method: "POST",
|
|
1279
|
+
headers: {
|
|
1280
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1281
|
+
"Secure-Channel": secureChannelId,
|
|
1282
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1283
|
+
},
|
|
1284
|
+
body: bodyString
|
|
1285
|
+
});
|
|
1286
|
+
const retryText = await retryResponse.text();
|
|
1287
|
+
if (!retryText) {
|
|
1288
|
+
throw createAbcError(
|
|
1289
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1290
|
+
`Empty response after retry (status: ${retryResponse.status})`,
|
|
1291
|
+
{ status: retryResponse.status }
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
const retryRes = JSON.parse(retryText);
|
|
1295
|
+
if (retryRes.serializedTx) {
|
|
1296
|
+
return { signature: retryRes.serializedTx };
|
|
1297
|
+
}
|
|
1298
|
+
throw createAbcError(
|
|
1299
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1300
|
+
"Failed to sign typed data after token refresh",
|
|
1301
|
+
retryRes
|
|
1302
|
+
);
|
|
1303
|
+
} catch (refreshError) {
|
|
1304
|
+
console.error("Token refresh failed in signTypedData:", refreshError);
|
|
1305
|
+
throw refreshError;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if (res.serializedTx) {
|
|
1309
|
+
return { signature: res.serializedTx };
|
|
1310
|
+
}
|
|
1311
|
+
throw createAbcError(
|
|
1312
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1313
|
+
"Failed to sign typed data",
|
|
1314
|
+
res
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Send raw transaction
|
|
1319
|
+
* Reference: tg-wallet-frontend src/features/wallet/api/index.ts sendRawTx()
|
|
1320
|
+
*/
|
|
1321
|
+
async sendRawTransaction(chainId, signedTransaction) {
|
|
1322
|
+
const wallet = loadFromStorage("wallet" /* WALLET */);
|
|
1323
|
+
if (!wallet) {
|
|
1324
|
+
throw createAbcError(
|
|
1325
|
+
"WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */,
|
|
1326
|
+
"Wallet information not found in storage"
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
const getNetworkName = (chainId2) => {
|
|
1330
|
+
const networkMap = {
|
|
1331
|
+
1: "ethereum",
|
|
1332
|
+
11155111: "ethereum_sepolia",
|
|
1333
|
+
8217: "klaytn",
|
|
1334
|
+
1001: "klaytn_baobab",
|
|
1335
|
+
137: "polygon",
|
|
1336
|
+
80002: "polygon_amoy",
|
|
1337
|
+
42161: "arbitrum",
|
|
1338
|
+
421614: "arbitrum_sepolia",
|
|
1339
|
+
5e3: "mantle",
|
|
1340
|
+
5003: "mantle_testnet"
|
|
1341
|
+
};
|
|
1342
|
+
return networkMap[chainId2] || "ethereum";
|
|
1343
|
+
};
|
|
1344
|
+
const network = getNetworkName(chainId);
|
|
1345
|
+
const secureChannelId = await this.secure.getSecureChannelId();
|
|
1346
|
+
const bodyData = {
|
|
1347
|
+
signedSerializeTx: signedTransaction,
|
|
1348
|
+
network
|
|
1349
|
+
// rpc parameter
|
|
1350
|
+
};
|
|
1351
|
+
const bodyString = new URLSearchParams(bodyData).toString();
|
|
1352
|
+
const url = `${this.baseURL}${ABC_ENDPOINTS.SEND_RAW_TX}`;
|
|
1353
|
+
const response = await fetch(url, {
|
|
1354
|
+
method: "POST",
|
|
1355
|
+
headers: {
|
|
1356
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1357
|
+
"Secure-Channel": secureChannelId,
|
|
1358
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1359
|
+
},
|
|
1360
|
+
body: bodyString
|
|
1361
|
+
});
|
|
1362
|
+
const text = await response.text();
|
|
1363
|
+
if (!text) {
|
|
1364
|
+
throw createAbcError(
|
|
1365
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1366
|
+
`Empty response from sendRawTx API (status: ${response.status})`,
|
|
1367
|
+
{ status: response.status }
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
const res = JSON.parse(text);
|
|
1371
|
+
console.log("\u{1F4E1} sendRawTransaction response:", {
|
|
1372
|
+
status: response.status,
|
|
1373
|
+
ok: response.ok,
|
|
1374
|
+
response: res
|
|
1375
|
+
});
|
|
1376
|
+
if (!response.ok) {
|
|
1377
|
+
console.error("\u274C sendRawTransaction error:", {
|
|
1378
|
+
status: response.status,
|
|
1379
|
+
errorMessage: res.errorMessage || res.msg || res.message,
|
|
1380
|
+
fullResponse: res
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
if (response.status === 401 && this.refreshToken) {
|
|
1384
|
+
try {
|
|
1385
|
+
await this.refreshAccessToken();
|
|
1386
|
+
const retryResponse = await fetch(url, {
|
|
1387
|
+
method: "POST",
|
|
1388
|
+
headers: {
|
|
1389
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1390
|
+
"Secure-Channel": secureChannelId,
|
|
1391
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1392
|
+
},
|
|
1393
|
+
body: bodyString
|
|
1394
|
+
});
|
|
1395
|
+
const retryText = await retryResponse.text();
|
|
1396
|
+
if (!retryText) {
|
|
1397
|
+
throw createAbcError(
|
|
1398
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1399
|
+
`Empty response after retry (status: ${retryResponse.status})`,
|
|
1400
|
+
{ status: retryResponse.status }
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
const retryRes = JSON.parse(retryText);
|
|
1404
|
+
if (retryRes.item || retryRes.txHash || retryRes.result?.txHash) {
|
|
1405
|
+
const txHash = retryRes.item || retryRes.txHash || retryRes.result.txHash;
|
|
1406
|
+
return { txHash };
|
|
1407
|
+
}
|
|
1408
|
+
throw createAbcError(
|
|
1409
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1410
|
+
"Failed to send transaction after token refresh",
|
|
1411
|
+
retryRes
|
|
1412
|
+
);
|
|
1413
|
+
} catch (refreshError) {
|
|
1414
|
+
console.error(
|
|
1415
|
+
"Token refresh failed in sendRawTransaction:",
|
|
1416
|
+
refreshError
|
|
1417
|
+
);
|
|
1418
|
+
throw refreshError;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
if (res.result) {
|
|
1422
|
+
if (typeof res.result === "string") {
|
|
1423
|
+
console.log("\u2705 Transaction hash from result:", res.result);
|
|
1424
|
+
return { txHash: res.result };
|
|
1425
|
+
}
|
|
1426
|
+
if (res.result.txHash) {
|
|
1427
|
+
return { txHash: res.result.txHash };
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
if (res.item) {
|
|
1431
|
+
return { txHash: res.item };
|
|
1432
|
+
}
|
|
1433
|
+
if (res.txHash) {
|
|
1434
|
+
return { txHash: res.txHash };
|
|
1435
|
+
}
|
|
1436
|
+
throw createAbcError(
|
|
1437
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1438
|
+
"Failed to send transaction",
|
|
1439
|
+
res
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Get wallet info
|
|
1444
|
+
*/
|
|
1445
|
+
async getWalletInfo(uid) {
|
|
1446
|
+
const response = await this.request(ABC_ENDPOINTS.INFO, {
|
|
1447
|
+
method: "POST",
|
|
1448
|
+
body: { uid }
|
|
1449
|
+
});
|
|
1450
|
+
if (response.status === "success") {
|
|
1451
|
+
return response.data;
|
|
1452
|
+
}
|
|
1453
|
+
throw createAbcError(
|
|
1454
|
+
"WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */,
|
|
1455
|
+
"Failed to get wallet info",
|
|
1456
|
+
response
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Logout (clear tokens)
|
|
1461
|
+
*/
|
|
1462
|
+
async logout() {
|
|
1463
|
+
this.clearTokens();
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Check if user is authenticated
|
|
1467
|
+
*/
|
|
1468
|
+
isAuthenticated() {
|
|
1469
|
+
return !!this.accessToken && !this.isTokenExpired();
|
|
1470
|
+
}
|
|
1471
|
+
/**
|
|
1472
|
+
* Set tokens in client instance (useful when restoring from storage or state)
|
|
1473
|
+
* Call this before persistTokens() if you need to set tokens from external source
|
|
1474
|
+
*/
|
|
1475
|
+
setTokens(accessToken, refreshToken, expiresAt) {
|
|
1476
|
+
this.accessToken = accessToken;
|
|
1477
|
+
this.refreshToken = refreshToken;
|
|
1478
|
+
this.expiresAt = expiresAt;
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Persist tokens to localStorage
|
|
1482
|
+
* Call this after PIN verification is complete
|
|
1483
|
+
*
|
|
1484
|
+
* SECURITY NOTE: This should only be called after full authentication (password + PIN)
|
|
1485
|
+
* to ensure tokens are only persisted after the user has proven ownership of both credentials.
|
|
1486
|
+
*/
|
|
1487
|
+
persistTokens() {
|
|
1488
|
+
if (!this.accessToken || !this.refreshToken || !this.expiresAt) {
|
|
1489
|
+
console.warn(
|
|
1490
|
+
"[AbcWaasClient] \u26A0\uFE0F No tokens to persist - ensure login was successful first"
|
|
1491
|
+
);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
console.log(
|
|
1495
|
+
"[AbcWaasClient] \u{1F4BE} Persisting tokens to localStorage after PIN verification"
|
|
1496
|
+
);
|
|
1497
|
+
saveToStorage("access_token" /* ACCESS_TOKEN */, this.accessToken);
|
|
1498
|
+
saveToStorage("refresh_token" /* REFRESH_TOKEN */, this.refreshToken);
|
|
1499
|
+
saveToStorage("expires_at" /* EXPIRES_AT */, this.expiresAt);
|
|
1500
|
+
}
|
|
1501
|
+
// ==========================================================================
|
|
1502
|
+
// Solana Methods
|
|
1503
|
+
// ==========================================================================
|
|
1504
|
+
/**
|
|
1505
|
+
* Generate or recover Solana wallet (Ed25519)
|
|
1506
|
+
* Reference: SigningService.ts:56-72, wallet/api/index.ts:56-72
|
|
1507
|
+
*/
|
|
1508
|
+
async generateSolanaWallet(pin, isRecover = false) {
|
|
1509
|
+
const url = `${this.baseURL}/v3/wallet/${isRecover ? "recover" : "generate"}`;
|
|
1510
|
+
const response = await fetch(url, {
|
|
1511
|
+
method: "POST",
|
|
1512
|
+
headers: {
|
|
1513
|
+
...DEFAULT_HEADERS,
|
|
1514
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1515
|
+
},
|
|
1516
|
+
body: JSON.stringify({
|
|
1517
|
+
curve: "ed25519",
|
|
1518
|
+
password: pin
|
|
1519
|
+
// PIN will be encrypted by Secure Channel
|
|
1520
|
+
})
|
|
1521
|
+
});
|
|
1522
|
+
const data = await response.json();
|
|
1523
|
+
if (!response.ok || data.status !== "success") {
|
|
1524
|
+
throw createAbcError(
|
|
1525
|
+
"WALLET_NOT_FOUND" /* WALLET_NOT_FOUND */,
|
|
1526
|
+
"Failed to generate Solana wallet",
|
|
1527
|
+
data
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
const result = data.result;
|
|
1531
|
+
const addressResult = await this.getSolanaAddress(
|
|
1532
|
+
result.public_key,
|
|
1533
|
+
this.config.environment === "production" ? "solana" : "solana_devnet"
|
|
1534
|
+
);
|
|
1535
|
+
return {
|
|
1536
|
+
uid: result.uid || "",
|
|
1537
|
+
sessionId: result.share_id,
|
|
1538
|
+
shareId: result.share_id,
|
|
1539
|
+
publicKey: result.public_key,
|
|
1540
|
+
address: addressResult,
|
|
1541
|
+
keyId: result.key_id || result.share_id,
|
|
1542
|
+
encryptedShare: result.encrypted_share || ""
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Get Solana address from Ed25519 public key
|
|
1547
|
+
* Reference: wallet/api/index.ts:74-82
|
|
1548
|
+
*/
|
|
1549
|
+
async getSolanaAddress(publicKey, network) {
|
|
1550
|
+
const url = `${this.baseURL}/wapi/v2/solana/wallet/getAddress`;
|
|
1551
|
+
const response = await fetch(url, {
|
|
1552
|
+
method: "POST",
|
|
1553
|
+
headers: {
|
|
1554
|
+
...DEFAULT_HEADERS,
|
|
1555
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1556
|
+
},
|
|
1557
|
+
body: JSON.stringify({
|
|
1558
|
+
network,
|
|
1559
|
+
publicKey
|
|
1560
|
+
})
|
|
1561
|
+
});
|
|
1562
|
+
const data = await response.json();
|
|
1563
|
+
if (!response.ok || data.status !== "success") {
|
|
1564
|
+
throw createAbcError(
|
|
1565
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1566
|
+
"Failed to get Solana address",
|
|
1567
|
+
data
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
return data.result?.data?.address;
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Sign Solana transaction with Ed25519
|
|
1574
|
+
* Reference: SigningService.ts:587-626, wallet/api/index.ts:114-122
|
|
1575
|
+
*/
|
|
1576
|
+
async signSolanaTransaction(params) {
|
|
1577
|
+
const url = `${this.baseURL}/v3/wallet/sign`;
|
|
1578
|
+
const response = await fetch(url, {
|
|
1579
|
+
method: "POST",
|
|
1580
|
+
headers: {
|
|
1581
|
+
...DEFAULT_HEADERS,
|
|
1582
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1583
|
+
},
|
|
1584
|
+
body: JSON.stringify({
|
|
1585
|
+
uid: params.uid,
|
|
1586
|
+
share_id: params.shareId,
|
|
1587
|
+
message: params.message,
|
|
1588
|
+
// Hex-encoded transaction
|
|
1589
|
+
password: params.pin
|
|
1590
|
+
// PIN will be encrypted
|
|
1591
|
+
})
|
|
1592
|
+
});
|
|
1593
|
+
const data = await response.json();
|
|
1594
|
+
if (!response.ok || data.status !== "success") {
|
|
1595
|
+
throw createAbcError(
|
|
1596
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1597
|
+
"Failed to sign Solana transaction",
|
|
1598
|
+
data
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
return {
|
|
1602
|
+
signature: data.result?.signature
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Sign Solana message with Ed25519
|
|
1607
|
+
* Reference: SigningService.ts:546-579
|
|
1608
|
+
*/
|
|
1609
|
+
async signSolanaMessage(params) {
|
|
1610
|
+
const url = `${this.baseURL}/v3/wallet/sign`;
|
|
1611
|
+
const response = await fetch(url, {
|
|
1612
|
+
method: "POST",
|
|
1613
|
+
headers: {
|
|
1614
|
+
...DEFAULT_HEADERS,
|
|
1615
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1616
|
+
},
|
|
1617
|
+
body: JSON.stringify({
|
|
1618
|
+
uid: params.uid,
|
|
1619
|
+
share_id: params.shareId,
|
|
1620
|
+
message: params.message,
|
|
1621
|
+
// Hex-encoded message
|
|
1622
|
+
password: params.pin
|
|
1623
|
+
})
|
|
1624
|
+
});
|
|
1625
|
+
const data = await response.json();
|
|
1626
|
+
if (!response.ok || data.status !== "success") {
|
|
1627
|
+
throw createAbcError(
|
|
1628
|
+
"SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
|
|
1629
|
+
"Failed to sign Solana message",
|
|
1630
|
+
data
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
return {
|
|
1634
|
+
signature: data.result?.signature
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Send Solana transaction
|
|
1639
|
+
* Reference: SigningService.ts:418, wallet/api/index.ts:124-132
|
|
1640
|
+
*/
|
|
1641
|
+
async sendSolanaTransaction(params) {
|
|
1642
|
+
const url = `${this.baseURL}/wapi/v2/solana/tx/sendTransaction`;
|
|
1643
|
+
const response = await fetch(url, {
|
|
1644
|
+
method: "POST",
|
|
1645
|
+
headers: {
|
|
1646
|
+
...DEFAULT_HEADERS,
|
|
1647
|
+
...this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}
|
|
1648
|
+
},
|
|
1649
|
+
body: JSON.stringify({
|
|
1650
|
+
network: params.network,
|
|
1651
|
+
serializedTX: params.serializedTX,
|
|
1652
|
+
signatures: params.signatures
|
|
1653
|
+
})
|
|
1654
|
+
});
|
|
1655
|
+
const data = await response.json();
|
|
1656
|
+
if (!response.ok || data.status !== "success") {
|
|
1657
|
+
throw createAbcError(
|
|
1658
|
+
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
1659
|
+
"Failed to send Solana transaction",
|
|
1660
|
+
data
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
return {
|
|
1664
|
+
txHash: data.result?.data
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
};
|
|
1668
|
+
function createAbcWaasClient(config) {
|
|
1669
|
+
return new AbcWaasClient(config);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
export {
|
|
1673
|
+
AbcWaasClient,
|
|
1674
|
+
createAbcWaasClient
|
|
1675
|
+
};
|