@txwallet/graz-connector 0.1.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 +205 -0
- package/index.d.ts +152 -0
- package/index.js +921 -0
- package/package.json +42 -0
- package/react.d.ts +47 -0
- package/react.js +106 -0
package/index.js
ADDED
|
@@ -0,0 +1,921 @@
|
|
|
1
|
+
export const TX_WALLET_GRAZ_CONNECTOR_VERSION = '0.1.0';
|
|
2
|
+
export const TX_WALLET_GRAZ_DEFAULT_SIGNIN_CREATE_ENDPOINT = '/api/tx-wallet/signin/requests';
|
|
3
|
+
export const TX_WALLET_GRAZ_DEFAULT_TRANSACTION_CREATE_ENDPOINT = '/api/tx-wallet/requests';
|
|
4
|
+
|
|
5
|
+
const TERMINAL_SIGN_STATUSES = Object.freeze(['approved', 'rejected', 'failed']);
|
|
6
|
+
const AUTH_FINAL_STATUSES = Object.freeze(['approved', 'rejected', 'failed', 'expired']);
|
|
7
|
+
|
|
8
|
+
const DEFAULT_WALLET_BASE_URL = 'https://txwallet.tesbert.com';
|
|
9
|
+
const DEFAULT_POLL_INTERVAL_MS = 1500;
|
|
10
|
+
const DEFAULT_POLL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
11
|
+
const DEFAULT_WINDOW_TARGET = 'txwallet-auth-approval';
|
|
12
|
+
const DEFAULT_WINDOW_FEATURES = 'popup,width=430,height=760';
|
|
13
|
+
const DEFAULT_TRANSACTION_CREATE_ENDPOINT = TX_WALLET_GRAZ_DEFAULT_TRANSACTION_CREATE_ENDPOINT;
|
|
14
|
+
const REVIEW_ONLY_TRANSACTION_CATEGORIES = Object.freeze([]);
|
|
15
|
+
const TRANSACTION_FINAL_STATUSES = Object.freeze(['broadcasted', 'confirmed', 'rejected', 'failed', 'expired', 'unsupported', 'invalid', 'error']);
|
|
16
|
+
const DEFAULT_SUPPORTED_CHAINS = Object.freeze(['coreum-mainnet-1', 'coreum-testnet-1']);
|
|
17
|
+
|
|
18
|
+
const toStringValue = (value) => String(value || '').trim();
|
|
19
|
+
|
|
20
|
+
const normalizeSdkMetadata = (metadata, providerId) => {
|
|
21
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const normalized = {
|
|
26
|
+
...metadata,
|
|
27
|
+
provider_id: toStringValue(metadata.provider_id || metadata.providerId || providerId),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return Object.values(normalized).some((value) => value !== '' && value !== undefined && value !== null)
|
|
31
|
+
? normalized
|
|
32
|
+
: null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const normalizeCode = (inputCode) => {
|
|
36
|
+
const code = toStringValue(inputCode).toUpperCase();
|
|
37
|
+
if (!code) return 'INTERNAL_ERROR';
|
|
38
|
+
return code;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const normalizeSupportedChains = (supportedChains) => {
|
|
42
|
+
const source = Array.isArray(supportedChains) ? supportedChains : DEFAULT_SUPPORTED_CHAINS;
|
|
43
|
+
const normalized = source
|
|
44
|
+
.map((chainId) => toStringValue(chainId))
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
return normalized.length > 0 ? normalized : [...DEFAULT_SUPPORTED_CHAINS];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const assertSupportedChain = (chainId, supportedChains) => {
|
|
50
|
+
if (!supportedChains.includes(chainId)) {
|
|
51
|
+
throw new TxWalletGrazConnectorError(
|
|
52
|
+
`Unsupported chain ${chainId}. Supported chains: ${supportedChains.join(', ')}`,
|
|
53
|
+
{ code: 'UNSUPPORTED_CHAIN' },
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const isAbortError = (error) => {
|
|
59
|
+
if (!error) return false;
|
|
60
|
+
if (error instanceof DOMException) {
|
|
61
|
+
return error.name === 'AbortError';
|
|
62
|
+
}
|
|
63
|
+
return toStringValue(error.name) === 'AbortError';
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const looksLikeNetworkError = (error) => {
|
|
67
|
+
if (!error) return false;
|
|
68
|
+
|
|
69
|
+
if (error instanceof TypeError) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const name = toStringValue(error.name).toLowerCase();
|
|
74
|
+
if (name === 'networkerror') {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const message = toStringValue(error.message).toLowerCase();
|
|
79
|
+
return message.includes('failed to fetch')
|
|
80
|
+
|| message.includes('network request failed')
|
|
81
|
+
|| message.includes('load failed')
|
|
82
|
+
|| message.includes('network error')
|
|
83
|
+
|| message.includes('ecconnrefused')
|
|
84
|
+
|| message.includes('econnreset')
|
|
85
|
+
|| message.includes('enotfound')
|
|
86
|
+
|| message.includes('timed out');
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const normalizeTransactionStatus = (statusPayload) => {
|
|
90
|
+
if (!isObject(statusPayload)) {
|
|
91
|
+
throw new TxWalletGrazConnectorError('Transaction status payload must be an object.', {
|
|
92
|
+
code: 'REQUEST_FAILED',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const requestId = toStringValue(statusPayload?.request_id || statusPayload?.requestId);
|
|
97
|
+
const status = toStringValue(statusPayload?.status).toLowerCase();
|
|
98
|
+
if (!requestId || !status) {
|
|
99
|
+
throw new TxWalletGrazConnectorError('Transaction status payload is missing request_id or status.', {
|
|
100
|
+
code: 'REQUEST_FAILED',
|
|
101
|
+
details: statusPayload,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
...statusPayload,
|
|
107
|
+
request_id: requestId,
|
|
108
|
+
status,
|
|
109
|
+
receipt_url: toStringValue(statusPayload?.receipt_url || statusPayload?.receiptUrl),
|
|
110
|
+
tx_hash: toStringValue(statusPayload?.tx_hash || statusPayload?.txHash),
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const normalizeTransactionReceipt = (receiptPayload) => {
|
|
115
|
+
if (!isObject(receiptPayload)) {
|
|
116
|
+
throw new TxWalletGrazConnectorError('Transaction receipt payload must be an object.', {
|
|
117
|
+
code: 'REQUEST_FAILED',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const receiptId = toStringValue(receiptPayload?.receipt_id || receiptPayload?.receiptId);
|
|
122
|
+
const requestId = toStringValue(receiptPayload?.request_id || receiptPayload?.requestId);
|
|
123
|
+
const status = toStringValue(receiptPayload?.status).toLowerCase();
|
|
124
|
+
if (!receiptId || !requestId || !status) {
|
|
125
|
+
throw new TxWalletGrazConnectorError('Transaction receipt payload is missing receipt_id, request_id, or status.', {
|
|
126
|
+
code: 'REQUEST_FAILED',
|
|
127
|
+
details: receiptPayload,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
...receiptPayload,
|
|
133
|
+
receipt_id: receiptId,
|
|
134
|
+
request_id: requestId,
|
|
135
|
+
status,
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const normalizeTransactionRequest = (requestPayload) => {
|
|
140
|
+
if (!isObject(requestPayload)) {
|
|
141
|
+
throw new TxWalletGrazConnectorError('Transaction request payload must be an object.', {
|
|
142
|
+
code: 'REQUEST_FAILED',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const requestId = toStringValue(requestPayload?.request_id || requestPayload?.requestId);
|
|
147
|
+
const fallbackUrl = toStringValue(requestPayload?.fallback_url || requestPayload?.fallbackUrl);
|
|
148
|
+
const statusUrl = toStringValue(requestPayload?.status_url || requestPayload?.statusUrl);
|
|
149
|
+
if (!requestId || !fallbackUrl || !statusUrl) {
|
|
150
|
+
throw new TxWalletGrazConnectorError('Transaction request payload is missing request_id, fallback_url, or status_url.', {
|
|
151
|
+
code: 'REQUEST_FAILED',
|
|
152
|
+
details: requestPayload,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
...requestPayload,
|
|
158
|
+
requestId,
|
|
159
|
+
fallbackUrl,
|
|
160
|
+
statusUrl,
|
|
161
|
+
status: toStringValue(requestPayload?.status) || 'pending',
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const pollTransactionStatus = async ({ requestFetch, statusUrl, credentials, intervalMs, timeoutMs }) => {
|
|
166
|
+
const startedAt = Date.now();
|
|
167
|
+
|
|
168
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
169
|
+
const response = await requestFetch(statusUrl, {
|
|
170
|
+
method: 'GET',
|
|
171
|
+
credentials,
|
|
172
|
+
cache: 'no-store',
|
|
173
|
+
headers: {
|
|
174
|
+
Accept: 'application/json',
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const statusPayload = await parseJsonResponse(response);
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
throw new TxWalletGrazConnectorError(statusPayload.error || `Transaction status request failed with HTTP ${response.status}.`, {
|
|
181
|
+
code: normalizeCode(statusPayload.code),
|
|
182
|
+
details: statusPayload,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const normalizedStatus = normalizeTransactionStatus(statusPayload);
|
|
187
|
+
if (TRANSACTION_FINAL_STATUSES.includes(normalizedStatus.status)) {
|
|
188
|
+
return normalizedStatus;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await delay(intervalMs);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throw new TxWalletGrazConnectorError('Transaction status polling timed out.', {
|
|
195
|
+
code: 'REQUEST_FAILED',
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const isObject = (value) => value && typeof value === 'object';
|
|
200
|
+
|
|
201
|
+
const getBrowserWindow = () => (typeof window !== 'undefined' ? window : undefined);
|
|
202
|
+
|
|
203
|
+
const getFetch = (fetchImpl) => {
|
|
204
|
+
if (typeof fetchImpl === 'function') return fetchImpl;
|
|
205
|
+
if (typeof fetch === 'function') return fetch.bind(globalThis);
|
|
206
|
+
throw new TxWalletGrazConnectorError('TX Wallet Graz connector requires a fetch implementation.', {
|
|
207
|
+
code: 'INTERNAL_ERROR',
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const parseJsonResponse = async (response) => response.json().catch(() => ({}));
|
|
212
|
+
|
|
213
|
+
const delay = (intervalMs, signal) => new Promise((resolve, reject) => {
|
|
214
|
+
const timer = setTimeout(resolve, intervalMs);
|
|
215
|
+
if (!signal) return;
|
|
216
|
+
|
|
217
|
+
const abort = () => {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
reject(new DOMException('TX Wallet Graz polling aborted.', 'AbortError'));
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const resolveRequestOrigin = (requestOrigin) => {
|
|
226
|
+
const trimmed = toStringValue(requestOrigin);
|
|
227
|
+
if (trimmed) return trimmed.replace(/\/$/, '');
|
|
228
|
+
|
|
229
|
+
const browserWindow = getBrowserWindow();
|
|
230
|
+
if (browserWindow?.location?.origin) {
|
|
231
|
+
return browserWindow.location.origin;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return 'https://example.invalid';
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const resolveOrigin = (inputUrl, label) => {
|
|
238
|
+
try {
|
|
239
|
+
return new URL(inputUrl).origin;
|
|
240
|
+
} catch {
|
|
241
|
+
throw new TxWalletGrazConnectorError(`${label} must be an absolute URL.`, {
|
|
242
|
+
code: 'REQUEST_FAILED',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const assertOriginMatch = (inputUrl, expectedOrigin, label) => {
|
|
248
|
+
const actualOrigin = resolveOrigin(inputUrl, label);
|
|
249
|
+
if (actualOrigin !== expectedOrigin) {
|
|
250
|
+
throw new TxWalletGrazConnectorError(
|
|
251
|
+
`${label} origin mismatch. Expected ${expectedOrigin} but received ${actualOrigin}.`,
|
|
252
|
+
{ code: 'CALLBACK_ORIGIN_MISMATCH' },
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const assertRequestIdMatch = (expectedRequestId, actualRequestId, label) => {
|
|
258
|
+
const expected = toStringValue(expectedRequestId);
|
|
259
|
+
const actual = toStringValue(actualRequestId);
|
|
260
|
+
if (!expected || !actual) return;
|
|
261
|
+
if (expected !== actual) {
|
|
262
|
+
throw new TxWalletGrazConnectorError(
|
|
263
|
+
`${label} request id mismatch. Expected ${expected} but received ${actual}.`,
|
|
264
|
+
{ code: 'REQUEST_FAILED' },
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const resolveAccountAddress = (statusPayload) => {
|
|
270
|
+
const fromObject = toStringValue(statusPayload?.account?.address || statusPayload?.account?.accountAddress);
|
|
271
|
+
if (fromObject) return fromObject;
|
|
272
|
+
return toStringValue(statusPayload?.accountAddress || statusPayload?.account);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const extractStatusCode = (statusPayload) => {
|
|
276
|
+
const requestCode = normalizeCode(statusPayload?.code);
|
|
277
|
+
if (requestCode !== 'INTERNAL_ERROR') return requestCode;
|
|
278
|
+
|
|
279
|
+
const status = toStringValue(statusPayload?.status).toLowerCase();
|
|
280
|
+
if (status === 'rejected') return 'USER_REJECTED';
|
|
281
|
+
if (status === 'expired') return 'REQUEST_EXPIRED';
|
|
282
|
+
return 'REQUEST_FAILED';
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const buildSignInFallbackUrl = ({ walletBaseUrl, appId, requestId, callbackUrl }) => {
|
|
286
|
+
const fallbackUrl = new URL('/', walletBaseUrl);
|
|
287
|
+
fallbackUrl.searchParams.set('txwalletSignin', '1');
|
|
288
|
+
fallbackUrl.searchParams.set('app_id', appId);
|
|
289
|
+
fallbackUrl.searchParams.set('request_id', requestId);
|
|
290
|
+
fallbackUrl.searchParams.set('callback_url', callbackUrl);
|
|
291
|
+
return fallbackUrl.toString();
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const buildSignInStatusUrl = ({ requestOrigin, requestId }) => (
|
|
295
|
+
new URL(`/api/tx-wallet/signin/requests/${encodeURIComponent(requestId)}/status`, requestOrigin).toString()
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const openWalletWindow = (fallbackUrl, {
|
|
299
|
+
windowRef = getBrowserWindow(),
|
|
300
|
+
target = DEFAULT_WINDOW_TARGET,
|
|
301
|
+
features = DEFAULT_WINDOW_FEATURES,
|
|
302
|
+
fallbackToCurrentTab = true,
|
|
303
|
+
} = {}) => {
|
|
304
|
+
if (!fallbackUrl || !windowRef) return null;
|
|
305
|
+
|
|
306
|
+
const popup = windowRef.open?.(fallbackUrl, target, features) || null;
|
|
307
|
+
if (popup?.focus) popup.focus();
|
|
308
|
+
if (!popup && fallbackToCurrentTab && windowRef.location?.assign) {
|
|
309
|
+
windowRef.location.assign(fallbackUrl);
|
|
310
|
+
}
|
|
311
|
+
return popup;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const createSignInRequest = async ({
|
|
315
|
+
requestFetch,
|
|
316
|
+
endpoint,
|
|
317
|
+
credentials,
|
|
318
|
+
callbackUrl,
|
|
319
|
+
chainId,
|
|
320
|
+
statement,
|
|
321
|
+
body,
|
|
322
|
+
sdkMetadata,
|
|
323
|
+
}) => {
|
|
324
|
+
const response = await requestFetch(endpoint, {
|
|
325
|
+
method: 'POST',
|
|
326
|
+
credentials,
|
|
327
|
+
headers: {
|
|
328
|
+
'Content-Type': 'application/json',
|
|
329
|
+
Accept: 'application/json',
|
|
330
|
+
},
|
|
331
|
+
body: JSON.stringify({
|
|
332
|
+
...body,
|
|
333
|
+
callback_url: callbackUrl,
|
|
334
|
+
chain_id: chainId,
|
|
335
|
+
statement: statement || 'Sign in with TX Wallet.',
|
|
336
|
+
...(sdkMetadata ? { sdk_metadata: sdkMetadata } : {}),
|
|
337
|
+
}),
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const responseBody = await parseJsonResponse(response);
|
|
341
|
+
if (!response.ok) {
|
|
342
|
+
throw new TxWalletGrazConnectorError(responseBody.error || `Sign-in request failed with HTTP ${response.status}.`, {
|
|
343
|
+
code: normalizeCode(responseBody.code),
|
|
344
|
+
details: responseBody,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const requestId = toStringValue(responseBody.request_id || responseBody.requestId);
|
|
349
|
+
const fallbackUrl = toStringValue(responseBody.fallback_url || responseBody.fallbackUrl);
|
|
350
|
+
const statusUrl = toStringValue(responseBody.status_url || responseBody.statusUrl);
|
|
351
|
+
|
|
352
|
+
if (!requestId || !fallbackUrl || !statusUrl) {
|
|
353
|
+
throw new TxWalletGrazConnectorError('Sign-in request response missing request_id, fallback_url, or status_url.', {
|
|
354
|
+
code: 'REQUEST_FAILED',
|
|
355
|
+
details: responseBody,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
requestId,
|
|
361
|
+
fallbackUrl,
|
|
362
|
+
statusUrl,
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const pollStatusUntilFinal = async ({
|
|
367
|
+
requestFetch,
|
|
368
|
+
statusUrl,
|
|
369
|
+
credentials,
|
|
370
|
+
intervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
371
|
+
timeoutMs = DEFAULT_POLL_TIMEOUT_MS,
|
|
372
|
+
signal,
|
|
373
|
+
}) => {
|
|
374
|
+
const startedAt = Date.now();
|
|
375
|
+
|
|
376
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
377
|
+
if (signal?.aborted) {
|
|
378
|
+
throw new DOMException('TX Wallet Graz polling aborted.', 'AbortError');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const response = await requestFetch(statusUrl, {
|
|
382
|
+
method: 'GET',
|
|
383
|
+
credentials,
|
|
384
|
+
cache: 'no-store',
|
|
385
|
+
signal,
|
|
386
|
+
headers: {
|
|
387
|
+
Accept: 'application/json',
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
const statusPayload = await parseJsonResponse(response);
|
|
391
|
+
|
|
392
|
+
if (!response.ok) {
|
|
393
|
+
throw new TxWalletGrazConnectorError(statusPayload.error || `Status request failed with HTTP ${response.status}.`, {
|
|
394
|
+
code: normalizeCode(statusPayload.code),
|
|
395
|
+
details: statusPayload,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const status = toStringValue(statusPayload.status).toLowerCase();
|
|
400
|
+
if (AUTH_FINAL_STATUSES.includes(status)) {
|
|
401
|
+
return statusPayload;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
await delay(intervalMs, signal);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
throw new TxWalletGrazConnectorError('Sign-in status polling timed out.', {
|
|
408
|
+
code: 'REQUEST_FAILED',
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const normalizeConnectorError = (error) => {
|
|
413
|
+
if (error instanceof TxWalletGrazConnectorError) {
|
|
414
|
+
return error;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const fallbackMessage = 'TX Wallet Graz connector request failed.';
|
|
418
|
+
const message = error instanceof Error && error.message ? error.message : fallbackMessage;
|
|
419
|
+
const codeFromError = isObject(error) ? error.code : undefined;
|
|
420
|
+
const normalizedCode = codeFromError
|
|
421
|
+
? normalizeCode(codeFromError)
|
|
422
|
+
: isAbortError(error)
|
|
423
|
+
? 'REQUEST_FAILED'
|
|
424
|
+
: looksLikeNetworkError(error)
|
|
425
|
+
? 'NETWORK_ERROR'
|
|
426
|
+
: 'INTERNAL_ERROR';
|
|
427
|
+
|
|
428
|
+
return new TxWalletGrazConnectorError(message, {
|
|
429
|
+
code: normalizedCode,
|
|
430
|
+
details: isObject(error) ? error : undefined,
|
|
431
|
+
cause: error,
|
|
432
|
+
});
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const validateConnectedAccount = (result) => {
|
|
436
|
+
const accountAddress = toStringValue(result?.accountAddress);
|
|
437
|
+
const chainId = toStringValue(result?.chainId);
|
|
438
|
+
|
|
439
|
+
if (!accountAddress) {
|
|
440
|
+
throw new TxWalletGrazConnectorError('Connected account address is required.', { code: 'NO_ACTIVE_ACCOUNT' });
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!chainId) {
|
|
444
|
+
throw new TxWalletGrazConnectorError('Connected chain id is required.', { code: 'UNSUPPORTED_CHAIN' });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
accountAddress,
|
|
449
|
+
chainId,
|
|
450
|
+
walletName: 'TX Wallet',
|
|
451
|
+
};
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const validateSignResult = (result) => {
|
|
455
|
+
const status = toStringValue(result?.status).toLowerCase();
|
|
456
|
+
if (!TERMINAL_SIGN_STATUSES.includes(status)) {
|
|
457
|
+
throw new TxWalletGrazConnectorError('Sign result status must be approved, rejected, or failed.', { code: 'REQUEST_FAILED' });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const requestId = toStringValue(result?.requestId);
|
|
461
|
+
if (!requestId) {
|
|
462
|
+
throw new TxWalletGrazConnectorError('Sign result request id is required.', { code: 'REQUEST_FAILED' });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
status,
|
|
467
|
+
requestId,
|
|
468
|
+
txWalletResultUrl: toStringValue(result?.txWalletResultUrl) || undefined,
|
|
469
|
+
signature: result?.signature,
|
|
470
|
+
error: result?.error,
|
|
471
|
+
};
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
const validateRequestResult = (result) => {
|
|
475
|
+
const status = toStringValue(result?.status).toLowerCase();
|
|
476
|
+
if (!TRANSACTION_FINAL_STATUSES.includes(status)) {
|
|
477
|
+
throw new TxWalletGrazConnectorError('Transaction result status must be final.', { code: 'REQUEST_FAILED' });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const requestId = toStringValue(result?.requestId || result?.request_id);
|
|
481
|
+
if (!requestId) {
|
|
482
|
+
throw new TxWalletGrazConnectorError('Transaction result request id is required.', { code: 'REQUEST_FAILED' });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
status,
|
|
487
|
+
requestId,
|
|
488
|
+
txWalletResultUrl: toStringValue(result?.txWalletResultUrl || result?.tx_wallet_result_url) || undefined,
|
|
489
|
+
receiptUrl: toStringValue(result?.receiptUrl || result?.receipt_url) || undefined,
|
|
490
|
+
txHash: toStringValue(result?.txHash || result?.tx_hash) || undefined,
|
|
491
|
+
receipt: isObject(result?.receipt) ? result.receipt : undefined,
|
|
492
|
+
error: result?.error,
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
export class TxWalletGrazConnectorError extends Error {
|
|
497
|
+
constructor(message, { code, details, cause } = {}) {
|
|
498
|
+
super(message);
|
|
499
|
+
this.name = 'TxWalletGrazConnectorError';
|
|
500
|
+
this.code = normalizeCode(code);
|
|
501
|
+
this.details = details;
|
|
502
|
+
this.cause = cause;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export class TxWalletGrazConnector {
|
|
507
|
+
constructor({ handlers = {}, initialChainId = 'coreum-mainnet-1' } = {}) {
|
|
508
|
+
this.handlers = handlers;
|
|
509
|
+
this.state = 'idle';
|
|
510
|
+
this.chainId = toStringValue(initialChainId) || 'coreum-mainnet-1';
|
|
511
|
+
this.accountAddress = '';
|
|
512
|
+
this.listeners = new Map();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
on(eventName, listener) {
|
|
516
|
+
if (!this.listeners.has(eventName)) {
|
|
517
|
+
this.listeners.set(eventName, new Set());
|
|
518
|
+
}
|
|
519
|
+
this.listeners.get(eventName).add(listener);
|
|
520
|
+
return () => this.off(eventName, listener);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
off(eventName, listener) {
|
|
524
|
+
this.listeners.get(eventName)?.delete(listener);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
emit(eventName, detail) {
|
|
528
|
+
for (const listener of this.listeners.get(eventName) || []) {
|
|
529
|
+
listener(detail);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
setState(nextState) {
|
|
534
|
+
this.state = nextState;
|
|
535
|
+
this.emit('state', nextState);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
getState() {
|
|
539
|
+
return this.state;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
getSupportedSignStatuses() {
|
|
543
|
+
return [...TERMINAL_SIGN_STATUSES];
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
getSupportedRequestStatuses() {
|
|
547
|
+
return [...TRANSACTION_FINAL_STATUSES];
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async connect(input) {
|
|
551
|
+
this.setState('connecting');
|
|
552
|
+
try {
|
|
553
|
+
if (typeof this.handlers.connect !== 'function') {
|
|
554
|
+
throw new TxWalletGrazConnectorError('Connect handler is not configured.', { code: 'INTERNAL_ERROR' });
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const connected = validateConnectedAccount(await this.handlers.connect(input));
|
|
558
|
+
this.accountAddress = connected.accountAddress;
|
|
559
|
+
this.chainId = connected.chainId;
|
|
560
|
+
this.setState('connected');
|
|
561
|
+
this.emit('connected', connected);
|
|
562
|
+
return connected;
|
|
563
|
+
} catch (error) {
|
|
564
|
+
const normalized = normalizeConnectorError(error);
|
|
565
|
+
this.setState('error');
|
|
566
|
+
this.emit('error', normalized);
|
|
567
|
+
throw normalized;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async getAccount() {
|
|
572
|
+
if (!this.accountAddress) {
|
|
573
|
+
throw new TxWalletGrazConnectorError('No active TX Wallet account is connected.', {
|
|
574
|
+
code: 'NO_ACTIVE_ACCOUNT',
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return {
|
|
579
|
+
accountAddress: this.accountAddress,
|
|
580
|
+
chainId: this.chainId,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async signAdr36(input) {
|
|
585
|
+
if (!this.accountAddress) {
|
|
586
|
+
throw new TxWalletGrazConnectorError('Wallet must be connected before signing.', {
|
|
587
|
+
code: 'NO_ACTIVE_ACCOUNT',
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
this.setState('signing');
|
|
592
|
+
try {
|
|
593
|
+
if (typeof this.handlers.signAdr36 !== 'function') {
|
|
594
|
+
throw new TxWalletGrazConnectorError('signAdr36 handler is not configured.', {
|
|
595
|
+
code: 'INTERNAL_ERROR',
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const result = validateSignResult(await this.handlers.signAdr36(input));
|
|
600
|
+
this.setState('connected');
|
|
601
|
+
this.emit('signed', result);
|
|
602
|
+
return result;
|
|
603
|
+
} catch (error) {
|
|
604
|
+
const normalized = normalizeConnectorError(error);
|
|
605
|
+
this.setState('error');
|
|
606
|
+
this.emit('error', normalized);
|
|
607
|
+
throw normalized;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async requestTransaction(input) {
|
|
612
|
+
this.setState('requesting');
|
|
613
|
+
try {
|
|
614
|
+
if (typeof this.handlers.requestTransaction !== 'function') {
|
|
615
|
+
throw new TxWalletGrazConnectorError('requestTransaction handler is not configured.', {
|
|
616
|
+
code: 'INTERNAL_ERROR',
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const result = validateRequestResult(await this.handlers.requestTransaction(input));
|
|
621
|
+
this.setState(this.accountAddress ? 'connected' : 'idle');
|
|
622
|
+
this.emit('requested', result);
|
|
623
|
+
return result;
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const normalized = normalizeConnectorError(error);
|
|
626
|
+
this.setState('error');
|
|
627
|
+
this.emit('error', normalized);
|
|
628
|
+
throw normalized;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
async launchRequest(input) {
|
|
633
|
+
return this.requestTransaction(input);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async disconnect() {
|
|
637
|
+
try {
|
|
638
|
+
if (typeof this.handlers.disconnect === 'function') {
|
|
639
|
+
await this.handlers.disconnect();
|
|
640
|
+
}
|
|
641
|
+
} finally {
|
|
642
|
+
this.accountAddress = '';
|
|
643
|
+
this.setState('disconnected');
|
|
644
|
+
this.emit('disconnected', {
|
|
645
|
+
chainId: this.chainId,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export function createTxWalletGrazConnector(options = {}) {
|
|
652
|
+
return new TxWalletGrazConnector(options);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export function createTxWalletGrazAuthHandlers({
|
|
656
|
+
fetchImpl,
|
|
657
|
+
requestOrigin,
|
|
658
|
+
createEndpoint = TX_WALLET_GRAZ_DEFAULT_SIGNIN_CREATE_ENDPOINT,
|
|
659
|
+
walletBaseUrl = DEFAULT_WALLET_BASE_URL,
|
|
660
|
+
credentials = 'include',
|
|
661
|
+
pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
662
|
+
pollTimeoutMs = DEFAULT_POLL_TIMEOUT_MS,
|
|
663
|
+
openWallet = openWalletWindow,
|
|
664
|
+
sdkMetadata,
|
|
665
|
+
supportedChains,
|
|
666
|
+
} = {}) {
|
|
667
|
+
const requestFetch = getFetch(fetchImpl);
|
|
668
|
+
const resolvedOrigin = resolveRequestOrigin(requestOrigin);
|
|
669
|
+
const resolvedWalletOrigin = resolveOrigin(walletBaseUrl, 'walletBaseUrl');
|
|
670
|
+
const resolvedSupportedChains = normalizeSupportedChains(supportedChains);
|
|
671
|
+
|
|
672
|
+
const runSignIn = async ({ appId, callbackUrl, chainId, statement, body, requestId, statusUrl, fallbackUrl }) => {
|
|
673
|
+
const normalizedAppId = toStringValue(appId);
|
|
674
|
+
const normalizedCallbackUrl = toStringValue(callbackUrl);
|
|
675
|
+
const normalizedChainId = toStringValue(chainId) || 'coreum-mainnet-1';
|
|
676
|
+
|
|
677
|
+
if (!normalizedAppId) {
|
|
678
|
+
throw new TxWalletGrazConnectorError('appId is required.', { code: 'REQUEST_FAILED' });
|
|
679
|
+
}
|
|
680
|
+
if (!normalizedCallbackUrl) {
|
|
681
|
+
throw new TxWalletGrazConnectorError('callbackUrl is required.', { code: 'REQUEST_FAILED' });
|
|
682
|
+
}
|
|
683
|
+
assertOriginMatch(normalizedCallbackUrl, resolvedOrigin, 'callbackUrl');
|
|
684
|
+
assertSupportedChain(normalizedChainId, resolvedSupportedChains);
|
|
685
|
+
|
|
686
|
+
let resolvedRequestId = toStringValue(requestId);
|
|
687
|
+
let resolvedStatusUrl = toStringValue(statusUrl);
|
|
688
|
+
let resolvedFallbackUrl = toStringValue(fallbackUrl);
|
|
689
|
+
|
|
690
|
+
if (!resolvedRequestId || !resolvedStatusUrl || !resolvedFallbackUrl) {
|
|
691
|
+
const request = await createSignInRequest({
|
|
692
|
+
requestFetch,
|
|
693
|
+
endpoint: createEndpoint,
|
|
694
|
+
credentials,
|
|
695
|
+
callbackUrl: normalizedCallbackUrl,
|
|
696
|
+
chainId: normalizedChainId,
|
|
697
|
+
statement,
|
|
698
|
+
body,
|
|
699
|
+
sdkMetadata: normalizeSdkMetadata(sdkMetadata, 'graz'),
|
|
700
|
+
});
|
|
701
|
+
resolvedRequestId = request.requestId;
|
|
702
|
+
resolvedStatusUrl = request.statusUrl;
|
|
703
|
+
resolvedFallbackUrl = request.fallbackUrl;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
assertOriginMatch(resolvedStatusUrl, resolvedOrigin, 'statusUrl');
|
|
707
|
+
assertOriginMatch(resolvedFallbackUrl, resolvedWalletOrigin, 'fallbackUrl');
|
|
708
|
+
|
|
709
|
+
openWallet(resolvedFallbackUrl);
|
|
710
|
+
|
|
711
|
+
const finalStatus = await pollStatusUntilFinal({
|
|
712
|
+
requestFetch,
|
|
713
|
+
statusUrl: resolvedStatusUrl,
|
|
714
|
+
credentials,
|
|
715
|
+
intervalMs: pollIntervalMs,
|
|
716
|
+
timeoutMs: pollTimeoutMs,
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
assertRequestIdMatch(
|
|
720
|
+
resolvedRequestId,
|
|
721
|
+
finalStatus?.request_id || finalStatus?.requestId,
|
|
722
|
+
'Auth status',
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
const normalizedStatus = toStringValue(finalStatus.status).toLowerCase();
|
|
726
|
+
if (normalizedStatus === 'approved') {
|
|
727
|
+
const accountAddress = resolveAccountAddress(finalStatus);
|
|
728
|
+
if (!accountAddress) {
|
|
729
|
+
throw new TxWalletGrazConnectorError('Approved sign-in status did not include an account.', {
|
|
730
|
+
code: 'NO_ACTIVE_ACCOUNT',
|
|
731
|
+
details: finalStatus,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return {
|
|
736
|
+
status: 'approved',
|
|
737
|
+
requestId: resolvedRequestId,
|
|
738
|
+
accountAddress,
|
|
739
|
+
chainId: normalizedChainId,
|
|
740
|
+
txWalletResultUrl: resolvedStatusUrl,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
throw new TxWalletGrazConnectorError(finalStatus.error || `Sign-in request ${normalizedStatus || 'failed'}.`, {
|
|
745
|
+
code: extractStatusCode(finalStatus),
|
|
746
|
+
details: finalStatus,
|
|
747
|
+
});
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
return {
|
|
751
|
+
connect: async ({ chainId, appId, callbackUrl, statement }) => {
|
|
752
|
+
const result = await runSignIn({
|
|
753
|
+
chainId,
|
|
754
|
+
appId,
|
|
755
|
+
callbackUrl,
|
|
756
|
+
statement,
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
return {
|
|
760
|
+
accountAddress: result.accountAddress,
|
|
761
|
+
chainId: result.chainId,
|
|
762
|
+
};
|
|
763
|
+
},
|
|
764
|
+
|
|
765
|
+
signAdr36: async ({ appId, requestId, callbackUrl, challenge = {} }) => {
|
|
766
|
+
const challengeRecord = isObject(challenge) ? challenge : {};
|
|
767
|
+
const statusUrl = toStringValue(challengeRecord.statusUrl || challengeRecord.status_url)
|
|
768
|
+
|| (requestId ? buildSignInStatusUrl({ requestOrigin: resolvedOrigin, requestId }) : '');
|
|
769
|
+
const fallbackUrl = toStringValue(challengeRecord.fallbackUrl || challengeRecord.fallback_url)
|
|
770
|
+
|| (requestId ? buildSignInFallbackUrl({
|
|
771
|
+
walletBaseUrl,
|
|
772
|
+
appId,
|
|
773
|
+
requestId,
|
|
774
|
+
callbackUrl,
|
|
775
|
+
}) : '');
|
|
776
|
+
|
|
777
|
+
const result = await runSignIn({
|
|
778
|
+
appId,
|
|
779
|
+
callbackUrl,
|
|
780
|
+
chainId: toStringValue(challengeRecord.chainId || challengeRecord.chain_id) || 'coreum-mainnet-1',
|
|
781
|
+
statement: toStringValue(challengeRecord.statement),
|
|
782
|
+
body: challengeRecord,
|
|
783
|
+
requestId,
|
|
784
|
+
statusUrl,
|
|
785
|
+
fallbackUrl,
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
return {
|
|
789
|
+
status: 'approved',
|
|
790
|
+
requestId: result.requestId,
|
|
791
|
+
txWalletResultUrl: result.txWalletResultUrl,
|
|
792
|
+
};
|
|
793
|
+
},
|
|
794
|
+
|
|
795
|
+
disconnect: async () => undefined,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export function createTxWalletGrazAuthConnector(options = {}) {
|
|
800
|
+
const { initialChainId = 'coreum-mainnet-1', ...handlerOptions } = options;
|
|
801
|
+
return createTxWalletGrazConnector({
|
|
802
|
+
initialChainId,
|
|
803
|
+
handlers: createTxWalletGrazAuthHandlers(handlerOptions),
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
export function createTxWalletGrazTransactionHandlers({
|
|
808
|
+
fetchImpl,
|
|
809
|
+
requestOrigin,
|
|
810
|
+
createEndpoint = DEFAULT_TRANSACTION_CREATE_ENDPOINT,
|
|
811
|
+
walletBaseUrl = DEFAULT_WALLET_BASE_URL,
|
|
812
|
+
credentials = 'include',
|
|
813
|
+
pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
814
|
+
pollTimeoutMs = DEFAULT_POLL_TIMEOUT_MS,
|
|
815
|
+
openWallet = openWalletWindow,
|
|
816
|
+
allowReviewOnly = false,
|
|
817
|
+
sdkMetadata,
|
|
818
|
+
supportedChains,
|
|
819
|
+
} = {}) {
|
|
820
|
+
const requestFetch = getFetch(fetchImpl);
|
|
821
|
+
const resolvedOrigin = resolveRequestOrigin(requestOrigin);
|
|
822
|
+
const resolvedWalletOrigin = resolveOrigin(walletBaseUrl, 'walletBaseUrl');
|
|
823
|
+
const resolvedSupportedChains = normalizeSupportedChains(supportedChains);
|
|
824
|
+
|
|
825
|
+
const createTransactionRequest = async ({ appId, callbackUrl, chainId, body, requestId, statusUrl, fallbackUrl }) => {
|
|
826
|
+
const normalizedAppId = toStringValue(appId);
|
|
827
|
+
const normalizedCallbackUrl = toStringValue(callbackUrl);
|
|
828
|
+
const normalizedChainId = toStringValue(chainId) || 'coreum-mainnet-1';
|
|
829
|
+
|
|
830
|
+
if (!normalizedAppId) {
|
|
831
|
+
throw new TxWalletGrazConnectorError('appId is required.', { code: 'REQUEST_FAILED' });
|
|
832
|
+
}
|
|
833
|
+
if (!normalizedCallbackUrl) {
|
|
834
|
+
throw new TxWalletGrazConnectorError('callbackUrl is required.', { code: 'REQUEST_FAILED' });
|
|
835
|
+
}
|
|
836
|
+
assertOriginMatch(normalizedCallbackUrl, resolvedOrigin, 'callbackUrl');
|
|
837
|
+
assertSupportedChain(normalizedChainId, resolvedSupportedChains);
|
|
838
|
+
|
|
839
|
+
const requestCategory = toStringValue(body?.category).toLowerCase();
|
|
840
|
+
if (!allowReviewOnly && REVIEW_ONLY_TRANSACTION_CATEGORIES.includes(requestCategory)) {
|
|
841
|
+
throw new TxWalletGrazConnectorError(
|
|
842
|
+
`Transaction category ${requestCategory} is review-only and blocked by default in the Graz adapter. Set allowReviewOnly=true only for explicit review fixtures.`,
|
|
843
|
+
{ code: 'CATEGORY_REVIEW_ONLY' },
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
let resolvedRequestId = toStringValue(requestId);
|
|
848
|
+
let resolvedStatusUrl = toStringValue(statusUrl);
|
|
849
|
+
let resolvedFallbackUrl = toStringValue(fallbackUrl);
|
|
850
|
+
|
|
851
|
+
if (!resolvedRequestId || !resolvedStatusUrl || !resolvedFallbackUrl) {
|
|
852
|
+
const response = await requestFetch(createEndpoint, {
|
|
853
|
+
method: 'POST',
|
|
854
|
+
credentials,
|
|
855
|
+
headers: {
|
|
856
|
+
'Content-Type': 'application/json',
|
|
857
|
+
Accept: 'application/json',
|
|
858
|
+
},
|
|
859
|
+
body: JSON.stringify({
|
|
860
|
+
...(body || {}),
|
|
861
|
+
callback_url: normalizedCallbackUrl,
|
|
862
|
+
chain_id: normalizedChainId,
|
|
863
|
+
...(normalizeSdkMetadata(sdkMetadata, 'graz') ? { sdk_metadata: normalizeSdkMetadata(sdkMetadata, 'graz') } : {}),
|
|
864
|
+
}),
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
const responseBody = await parseJsonResponse(response);
|
|
868
|
+
if (!response.ok) {
|
|
869
|
+
throw new TxWalletGrazConnectorError(responseBody.error || `Transaction request failed with HTTP ${response.status}.`, {
|
|
870
|
+
code: normalizeCode(responseBody.code),
|
|
871
|
+
details: responseBody,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const request = normalizeTransactionRequest(responseBody);
|
|
876
|
+
resolvedRequestId = request.requestId;
|
|
877
|
+
resolvedStatusUrl = request.statusUrl;
|
|
878
|
+
resolvedFallbackUrl = request.fallbackUrl;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
assertOriginMatch(resolvedStatusUrl, resolvedOrigin, 'statusUrl');
|
|
882
|
+
assertOriginMatch(resolvedFallbackUrl, resolvedWalletOrigin, 'fallbackUrl');
|
|
883
|
+
|
|
884
|
+
openWallet(resolvedFallbackUrl);
|
|
885
|
+
|
|
886
|
+
const finalStatus = await pollTransactionStatus({
|
|
887
|
+
requestFetch,
|
|
888
|
+
statusUrl: resolvedStatusUrl,
|
|
889
|
+
credentials,
|
|
890
|
+
intervalMs: pollIntervalMs,
|
|
891
|
+
timeoutMs: pollTimeoutMs,
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
assertRequestIdMatch(resolvedRequestId, finalStatus.request_id, 'Transaction status');
|
|
895
|
+
|
|
896
|
+
return {
|
|
897
|
+
status: finalStatus.status,
|
|
898
|
+
requestId: resolvedRequestId,
|
|
899
|
+
txWalletResultUrl: resolvedStatusUrl,
|
|
900
|
+
receiptUrl: finalStatus.receipt_url || undefined,
|
|
901
|
+
txHash: finalStatus.tx_hash || undefined,
|
|
902
|
+
receipt: finalStatus.receipt || undefined,
|
|
903
|
+
error: finalStatus.error || undefined,
|
|
904
|
+
};
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
return {
|
|
908
|
+
requestTransaction: async (input) => {
|
|
909
|
+
const result = await createTransactionRequest(input);
|
|
910
|
+
return validateRequestResult(result);
|
|
911
|
+
},
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function createTxWalletGrazTransactionConnector(options = {}) {
|
|
916
|
+
const { initialChainId = 'coreum-mainnet-1', ...handlerOptions } = options;
|
|
917
|
+
return createTxWalletGrazConnector({
|
|
918
|
+
initialChainId,
|
|
919
|
+
handlers: createTxWalletGrazTransactionHandlers(handlerOptions),
|
|
920
|
+
});
|
|
921
|
+
}
|