@swivy/node 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1248 @@
1
+ import crypto from "node:crypto";
2
+ import { Chain as PolymarketChain, ClobClient, OrderType as PolymarketOrderType, Side as PolymarketSide, SignatureTypeV2 as PolymarketSignatureType, } from "@polymarket/clob-client-v2";
3
+ import { ExchangeClient, HttpTransport, InfoClient, } from "@nktkas/hyperliquid";
4
+ import { generateExportRecipientKeyPair, openSealedExport } from "@swivy/core";
5
+ import { createSessionGrantAuthorizationMessage as buildSessionGrantAuthorizationMessage } from "@swivy/core";
6
+ function assertHyperliquidEvmAddress(address) {
7
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
8
+ throw new Error("invalid_hyperliquid_account_address");
9
+ }
10
+ return address.toLowerCase();
11
+ }
12
+ function createHyperliquidTransport(options = {}) {
13
+ const transportOptions = {
14
+ isTestnet: options.network === "testnet",
15
+ };
16
+ if (options.apiUrl) {
17
+ transportOptions.apiUrl = options.apiUrl;
18
+ }
19
+ if (options.timeoutMs !== undefined) {
20
+ transportOptions.timeout = options.timeoutMs;
21
+ }
22
+ return new HttpTransport(transportOptions);
23
+ }
24
+ export function buildHyperliquidOrderPreview(order, options = {}) {
25
+ return {
26
+ provider: "hyperliquid",
27
+ network: options.network ?? "mainnet",
28
+ order: {
29
+ asset: order.asset,
30
+ side: order.isBuy ? "buy" : "sell",
31
+ price: order.price,
32
+ size: order.size,
33
+ reduceOnly: order.reduceOnly ?? false,
34
+ tif: order.tif ?? "Gtc",
35
+ grouping: order.grouping ?? "na",
36
+ clientOrderId: order.clientOrderId,
37
+ },
38
+ execution: {
39
+ ready: false,
40
+ requiresSwivyWallet: true,
41
+ hint: "Use --account acct_... to sign with a Swivy EVM wallet.",
42
+ },
43
+ };
44
+ }
45
+ export async function getHyperliquidMids(options = {}) {
46
+ const client = new InfoClient({ transport: createHyperliquidTransport(options) });
47
+ return client.allMids();
48
+ }
49
+ export async function getHyperliquidPerpMarkets(options = {}) {
50
+ const client = new InfoClient({ transport: createHyperliquidTransport(options) });
51
+ return client.metaAndAssetCtxs();
52
+ }
53
+ export async function getHyperliquidAccountStatus(address, options = {}) {
54
+ const accountAddress = assertHyperliquidEvmAddress(address);
55
+ const network = options.network ?? "mainnet";
56
+ const client = new InfoClient({ transport: createHyperliquidTransport(options) });
57
+ try {
58
+ const state = await client.clearinghouseState({ user: accountAddress });
59
+ const marginSummary = state && typeof state === "object"
60
+ ? state.marginSummary
61
+ : undefined;
62
+ const accountValue = marginSummary?.accountValue ?? null;
63
+ const withdrawable = state && typeof state === "object" && typeof state.withdrawable === "string"
64
+ ? state.withdrawable
65
+ : null;
66
+ const funded = Number(accountValue ?? "0") > 0 || Number(withdrawable ?? "0") > 0;
67
+ return {
68
+ provider: "hyperliquid",
69
+ network,
70
+ address: accountAddress,
71
+ initialized: true,
72
+ funded,
73
+ accountValue,
74
+ withdrawable,
75
+ marginSummary,
76
+ state,
77
+ next: funded
78
+ ? "Ready for Swivy-signed Hyperliquid orders."
79
+ : network === "testnet"
80
+ ? "Claim mock USDC for this Swivy address from Hyperliquid testnet, then rerun status."
81
+ : "Deposit USDC to this Swivy address on Hyperliquid, then rerun status.",
82
+ };
83
+ }
84
+ catch (error) {
85
+ const message = error instanceof Error ? error.message : String(error);
86
+ const missing = /does not exist|not found|unknown user/i.test(message);
87
+ return {
88
+ provider: "hyperliquid",
89
+ network,
90
+ address: accountAddress,
91
+ initialized: false,
92
+ funded: false,
93
+ accountValue: null,
94
+ withdrawable: null,
95
+ error: message,
96
+ next: missing
97
+ ? network === "testnet"
98
+ ? "Initialize this Swivy address on Hyperliquid testnet by claiming mock USDC after mainnet eligibility is satisfied."
99
+ : "Initialize this Swivy address on Hyperliquid by depositing USDC to it."
100
+ : "Hyperliquid account status check failed; retry or inspect with --json.",
101
+ };
102
+ }
103
+ }
104
+ export async function placeHyperliquidOrder(payload, options = {}) {
105
+ const client = new ExchangeClient({
106
+ transport: createHyperliquidTransport(options),
107
+ wallet: payload.wallet,
108
+ });
109
+ const response = await client.order({
110
+ orders: [{
111
+ a: payload.asset,
112
+ b: payload.isBuy,
113
+ p: payload.price,
114
+ s: payload.size,
115
+ r: payload.reduceOnly ?? false,
116
+ t: { limit: { tif: payload.tif ?? "Gtc" } },
117
+ c: payload.clientOrderId,
118
+ }],
119
+ grouping: payload.grouping ?? "na",
120
+ }, {
121
+ expiresAfter: payload.expiresAfter,
122
+ });
123
+ return {
124
+ preview: {
125
+ ...buildHyperliquidOrderPreview(payload, options),
126
+ execution: {
127
+ ready: true,
128
+ requiresSwivyWallet: true,
129
+ hint: "Order was signed by the Swivy EVM wallet and submitted to Hyperliquid.",
130
+ },
131
+ },
132
+ response,
133
+ };
134
+ }
135
+ function createSwivyHyperliquidWallet(params) {
136
+ const address = assertHyperliquidEvmAddress(params.accountAddress);
137
+ return {
138
+ address,
139
+ async getAddress() {
140
+ return address;
141
+ },
142
+ async signTypedData(typedData) {
143
+ const intent = await params.client.signTypedDataAndWait({
144
+ accountId: params.accountId,
145
+ challengeId: params.challengeId,
146
+ typedData: {
147
+ domain: typedData.domain,
148
+ types: typedData.types,
149
+ primaryType: typedData.primaryType,
150
+ message: typedData.message,
151
+ },
152
+ }, params.wait);
153
+ if (intent.status !== "completed" || !intent.result?.signature) {
154
+ throw new Error(intent.result?.error ?? "hyperliquid_swivy_signing_failed");
155
+ }
156
+ return intent.result.signature;
157
+ },
158
+ };
159
+ }
160
+ function assertPolymarketEvmAddress(address) {
161
+ if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
162
+ throw new Error("invalid_polymarket_account_address");
163
+ }
164
+ return address.toLowerCase();
165
+ }
166
+ function resolvePolymarketOrderType(orderType) {
167
+ switch (orderType ?? "GTC") {
168
+ case "GTD":
169
+ return PolymarketOrderType.GTD;
170
+ case "FOK":
171
+ return PolymarketOrderType.FOK;
172
+ case "FAK":
173
+ return PolymarketOrderType.FAK;
174
+ case "GTC":
175
+ default:
176
+ return PolymarketOrderType.GTC;
177
+ }
178
+ }
179
+ function buildPolymarketOrderPreviewPayload(payload) {
180
+ return {
181
+ provider: "polymarket",
182
+ network: "polygon-mainnet",
183
+ order: {
184
+ tokenId: payload.tokenId,
185
+ conditionId: payload.conditionId ?? null,
186
+ side: payload.side,
187
+ price: payload.price,
188
+ size: payload.size,
189
+ requestedValueUsd: payload.requestedValueUsd ?? payload.price * payload.size,
190
+ orderType: payload.orderType ?? "GTC",
191
+ builderCode: payload.builderCode ?? null,
192
+ },
193
+ execution: {
194
+ ready: false,
195
+ requiresTypedDataSignature: true,
196
+ hint: "Order has not been signed or submitted yet.",
197
+ },
198
+ };
199
+ }
200
+ function numberFromOrderbookLevel(level) {
201
+ if (Array.isArray(level)) {
202
+ const value = Number(level[0]);
203
+ return Number.isFinite(value) ? value : null;
204
+ }
205
+ if (level && typeof level === "object") {
206
+ const record = level;
207
+ const value = Number(record.price ?? record.p);
208
+ return Number.isFinite(value) ? value : null;
209
+ }
210
+ return null;
211
+ }
212
+ export function getPolymarketObservedPriceFromOrderbook(orderbook) {
213
+ const record = orderbook && typeof orderbook === "object"
214
+ ? orderbook
215
+ : {};
216
+ const bids = Array.isArray(record.bids) ? record.bids : [];
217
+ const asks = Array.isArray(record.asks) ? record.asks : [];
218
+ const bestBid = bids.length ? numberFromOrderbookLevel(bids[bids.length - 1]) ?? numberFromOrderbookLevel(bids[0]) : null;
219
+ const bestAsk = asks.length ? numberFromOrderbookLevel(asks[0]) : null;
220
+ if (bestBid !== null && bestAsk !== null) {
221
+ return (bestBid + bestAsk) / 2;
222
+ }
223
+ return bestAsk ?? bestBid;
224
+ }
225
+ function isPolymarketStopTriggered(params) {
226
+ return params.triggerDirection === "above"
227
+ ? params.observedPrice >= params.triggerPrice
228
+ : params.observedPrice <= params.triggerPrice;
229
+ }
230
+ function createSwivyPolymarketSigner(params) {
231
+ const address = assertPolymarketEvmAddress(params.accountAddress);
232
+ return {
233
+ async getAddress() {
234
+ return address;
235
+ },
236
+ async _signTypedData(domain, types, value) {
237
+ const intent = await params.client.signTypedDataAndWait({
238
+ accountId: params.accountId,
239
+ challengeId: params.challengeId,
240
+ typedData: {
241
+ domain,
242
+ types,
243
+ primaryType: "Order",
244
+ message: value,
245
+ },
246
+ }, params.wait);
247
+ if (intent.status !== "completed" || !intent.result?.signature) {
248
+ throw new Error(intent.result?.error ?? "polymarket_swivy_signing_failed");
249
+ }
250
+ return intent.result.signature;
251
+ },
252
+ };
253
+ }
254
+ function parseJsonResponseBody(response, text) {
255
+ if (!text) {
256
+ return {};
257
+ }
258
+ try {
259
+ return JSON.parse(text);
260
+ }
261
+ catch {
262
+ const contentType = response.headers.get("content-type") ?? "";
263
+ if (response.ok) {
264
+ throw new Error(contentType ? `invalid_json_response:${contentType}` : "invalid_json_response");
265
+ }
266
+ throw new Error(`http_${response.status}_non_json_response`);
267
+ }
268
+ }
269
+ function resolveResponseErrorMessage(response, payload) {
270
+ if (typeof payload?.error === "string" && payload.error) {
271
+ return payload.error;
272
+ }
273
+ if (typeof payload?.error?.message === "string" && payload.error.message) {
274
+ return payload.error.message;
275
+ }
276
+ if (typeof payload?.message === "string" && payload.message) {
277
+ return payload.message;
278
+ }
279
+ return response.statusText || `http_${response.status}`;
280
+ }
281
+ function randomNonce(bytes = 24) {
282
+ return crypto.randomBytes(bytes).toString("base64url");
283
+ }
284
+ export function generateSessionKeyPair() {
285
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
286
+ return {
287
+ curve: "ed25519",
288
+ publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
289
+ privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString(),
290
+ };
291
+ }
292
+ export function createSessionGrantAuthorization(params) {
293
+ const nonce = params.nonce ?? randomNonce();
294
+ const expiresAt = new Date(Date.now() + (params.expiresInSeconds ?? 60) * 1000).toISOString();
295
+ const message = buildSessionGrantAuthorizationMessage({
296
+ grantId: params.grantId,
297
+ accountId: params.accountId,
298
+ chain: params.chain,
299
+ kind: params.kind,
300
+ payload: params.payload,
301
+ nonce,
302
+ expiresAt,
303
+ });
304
+ const signatureBase64 = crypto
305
+ .sign(null, Buffer.from(message, "utf8"), crypto.createPrivateKey(params.privateKeyPem))
306
+ .toString("base64");
307
+ return {
308
+ nonce,
309
+ expiresAt,
310
+ signatureBase64,
311
+ };
312
+ }
313
+ function countDefined(values) {
314
+ return values.filter((value) => value !== undefined && value !== null).length;
315
+ }
316
+ function resolveSessionGrantSigningKind(payload) {
317
+ const messageCount = countDefined([payload.message, payload.messageHex, payload.messageBase64]);
318
+ const typedDataCount = countDefined([payload.typedData]);
319
+ const digestCount = countDefined([payload.digestHex]);
320
+ const transactionCount = countDefined([payload.transaction, payload.serializedTransactionBase64]);
321
+ const totalModes = messageCount + typedDataCount + digestCount + transactionCount;
322
+ if (totalModes !== 1) {
323
+ throw new Error("unsupported_signing_payload");
324
+ }
325
+ if (typedDataCount === 1) {
326
+ return "sign_typed_data";
327
+ }
328
+ if (digestCount === 1) {
329
+ return "sign_digest";
330
+ }
331
+ if (messageCount === 1) {
332
+ return "sign_message";
333
+ }
334
+ return "sign_transaction";
335
+ }
336
+ /**
337
+ * Verify a Swivy webhook: `x-swivy-signature` is hex HMAC-SHA256 of the raw body.
338
+ * Reject events whose `createdAt` is older than you are willing to accept.
339
+ */
340
+ export function verifySwivyWebhookSignature(params) {
341
+ if (!params.signature) {
342
+ return false;
343
+ }
344
+ const expected = crypto.createHmac("sha256", params.secret).update(params.rawBody).digest();
345
+ const received = Buffer.from(params.signature, "hex");
346
+ return received.length === expected.length && crypto.timingSafeEqual(received, expected);
347
+ }
348
+ export class SwivyNodeClient {
349
+ config;
350
+ sessionToken;
351
+ constructor(config) {
352
+ this.config = config;
353
+ this.sessionToken = config.sessionToken;
354
+ }
355
+ setSessionToken(token) {
356
+ this.sessionToken = token;
357
+ }
358
+ clearSessionToken() {
359
+ this.sessionToken = undefined;
360
+ }
361
+ async createOrganization(name) {
362
+ return this.request("/v1/control/organizations", {
363
+ method: "POST",
364
+ body: { name },
365
+ adminScope: "deployment",
366
+ });
367
+ }
368
+ async createProject(organizationId, name) {
369
+ return this.request(`/v1/control/organizations/${organizationId}/projects`, {
370
+ method: "POST",
371
+ body: { name },
372
+ adminScope: "scoped",
373
+ });
374
+ }
375
+ async updateProject(projectId, name) {
376
+ return this.request(`/v1/control/projects/${projectId}`, {
377
+ method: "PATCH",
378
+ body: { name },
379
+ adminScope: "scoped",
380
+ });
381
+ }
382
+ async createEnvironment(projectId, name) {
383
+ return this.request(`/v1/control/projects/${projectId}/environments`, {
384
+ method: "POST",
385
+ body: { name },
386
+ adminScope: "scoped",
387
+ });
388
+ }
389
+ async updateEnvironment(environmentId, name) {
390
+ return this.request(`/v1/control/environments/${environmentId}`, {
391
+ method: "PATCH",
392
+ body: { name },
393
+ adminScope: "scoped",
394
+ });
395
+ }
396
+ async getOrganizationOverview(organizationId) {
397
+ return this.request(`/v1/control/organizations/${organizationId}/overview`, {
398
+ method: "GET",
399
+ adminScope: "scoped",
400
+ });
401
+ }
402
+ async listEnvironmentCredentials(environmentId) {
403
+ return this.request(`/v1/control/environments/${environmentId}/credentials`, {
404
+ method: "GET",
405
+ adminScope: "scoped",
406
+ });
407
+ }
408
+ async createEnvironmentCredential(environmentId, label = "Rotated key") {
409
+ return this.request(`/v1/control/environments/${environmentId}/credentials`, {
410
+ method: "POST",
411
+ body: { label },
412
+ adminScope: "scoped",
413
+ });
414
+ }
415
+ async revokeEnvironmentCredential(environmentId, credentialId) {
416
+ return this.request(`/v1/control/environments/${environmentId}/credentials/${credentialId}`, {
417
+ method: "DELETE",
418
+ adminScope: "scoped",
419
+ });
420
+ }
421
+ async rotateWebhookSecret(environmentId, webhookId) {
422
+ return this.request(`/v1/control/environments/${environmentId}/webhooks/${webhookId}/rotate-secret`, {
423
+ method: "POST",
424
+ adminScope: "scoped",
425
+ });
426
+ }
427
+ async listWebhooks(environmentId) {
428
+ return this.request(`/v1/control/environments/${environmentId}/webhooks`, {
429
+ method: "GET",
430
+ adminScope: "scoped",
431
+ });
432
+ }
433
+ async revokeWebhook(environmentId, webhookId) {
434
+ return this.request(`/v1/control/environments/${environmentId}/webhooks/${webhookId}`, {
435
+ method: "DELETE",
436
+ adminScope: "scoped",
437
+ });
438
+ }
439
+ async listOrganizationAdminCredentials(organizationId) {
440
+ return this.request(`/v1/control/organizations/${organizationId}/admin-credentials`, {
441
+ method: "GET",
442
+ adminScope: "scoped",
443
+ });
444
+ }
445
+ async createOrganizationAdminCredential(organizationId, body = {}) {
446
+ return this.request(`/v1/control/organizations/${organizationId}/admin-credentials`, {
447
+ method: "POST",
448
+ body,
449
+ adminScope: "scoped",
450
+ });
451
+ }
452
+ async revokeOrganizationAdminCredential(organizationId, credentialId) {
453
+ return this.request(`/v1/control/organizations/${organizationId}/admin-credentials/${credentialId}`, {
454
+ method: "DELETE",
455
+ adminScope: "scoped",
456
+ });
457
+ }
458
+ async listOrganizationMembers(organizationId) {
459
+ return this.request(`/v1/control/organizations/${organizationId}/members`, {
460
+ method: "GET",
461
+ adminScope: "scoped",
462
+ });
463
+ }
464
+ async revokeOrganizationMember(organizationId, memberId) {
465
+ return this.request(`/v1/control/organizations/${organizationId}/members/${memberId}`, {
466
+ method: "DELETE",
467
+ adminScope: "scoped",
468
+ });
469
+ }
470
+ async updateOrganizationMemberRole(organizationId, memberId, role) {
471
+ return this.request(`/v1/control/organizations/${organizationId}/members/${memberId}`, {
472
+ method: "PATCH",
473
+ body: { role },
474
+ adminScope: "scoped",
475
+ });
476
+ }
477
+ async listOrganizationInvites(organizationId) {
478
+ return this.request(`/v1/control/organizations/${organizationId}/invites`, {
479
+ method: "GET",
480
+ adminScope: "scoped",
481
+ });
482
+ }
483
+ async createOrganizationInvite(organizationId, body) {
484
+ return this.request(`/v1/control/organizations/${organizationId}/invites`, {
485
+ method: "POST",
486
+ body,
487
+ adminScope: "scoped",
488
+ });
489
+ }
490
+ async revokeOrganizationInvite(organizationId, inviteId) {
491
+ return this.request(`/v1/control/organizations/${organizationId}/invites/${inviteId}`, {
492
+ method: "DELETE",
493
+ adminScope: "scoped",
494
+ });
495
+ }
496
+ async acceptOrganizationInvite(inviteId, body) {
497
+ return this.request(`/v1/control/organization-invites/${inviteId}/accept`, {
498
+ method: "POST",
499
+ body,
500
+ });
501
+ }
502
+ async updateEnvironmentPolicy(environmentId, policy) {
503
+ return this.request(`/v1/control/environments/${environmentId}/policy`, {
504
+ method: "PUT",
505
+ body: policy,
506
+ adminScope: "scoped",
507
+ });
508
+ }
509
+ async listEnvironmentChainRpcConfigs(environmentId) {
510
+ return this.request(`/v1/control/environments/${environmentId}/chains`, {
511
+ method: "GET",
512
+ environment: true,
513
+ });
514
+ }
515
+ async upsertEnvironmentChainRpcConfig(environmentId, chain, config) {
516
+ return this.request(`/v1/control/environments/${environmentId}/chains/${chain}`, {
517
+ method: "PUT",
518
+ body: config,
519
+ adminScope: "scoped",
520
+ });
521
+ }
522
+ async requestEmailCode(email, externalUserId) {
523
+ return this.request("/v1/runtime/auth/email/request", {
524
+ method: "POST",
525
+ body: { email, externalUserId },
526
+ environment: true,
527
+ });
528
+ }
529
+ async verifyEmailCode(email, code, externalUserId) {
530
+ const result = await this.request("/v1/runtime/auth/email/verify", {
531
+ method: "POST",
532
+ body: { email, code, externalUserId },
533
+ environment: true,
534
+ });
535
+ if (result.sessionToken) {
536
+ this.sessionToken = result.sessionToken;
537
+ }
538
+ return result;
539
+ }
540
+ /** Request a primary login code. The environment must enable SMS login. */
541
+ async requestSmsCode(phoneNumber) {
542
+ return this.request("/v1/runtime/auth/sms/request", {
543
+ method: "POST",
544
+ body: { phoneNumber },
545
+ environment: true,
546
+ });
547
+ }
548
+ async verifySmsCode(challengeId, code) {
549
+ const result = await this.request("/v1/runtime/auth/sms/verify", {
550
+ method: "POST",
551
+ body: { challengeId, code },
552
+ environment: true,
553
+ });
554
+ if (result.sessionToken)
555
+ this.sessionToken = result.sessionToken;
556
+ return result;
557
+ }
558
+ async getMe() {
559
+ return this.request("/v1/runtime/me", {
560
+ method: "GET",
561
+ runtime: true,
562
+ });
563
+ }
564
+ async claimUsername(username) {
565
+ return this.request("/v1/runtime/id/username", {
566
+ method: "POST",
567
+ body: { username },
568
+ runtime: true,
569
+ });
570
+ }
571
+ async lookupUsername(username) {
572
+ return this.request(`/v1/runtime/id/${encodeURIComponent(username)}`, {
573
+ method: "GET",
574
+ runtime: true,
575
+ });
576
+ }
577
+ async startTotp(label = "Authenticator app") {
578
+ return this.request("/v1/runtime/mfa/totp/start", {
579
+ method: "POST",
580
+ body: { label },
581
+ runtime: true,
582
+ });
583
+ }
584
+ async verifyTotp(authMethodId, code) {
585
+ return this.request("/v1/runtime/mfa/totp/verify", {
586
+ method: "POST",
587
+ body: { authMethodId, code },
588
+ runtime: true,
589
+ });
590
+ }
591
+ async createChallenge(authMethodId, purpose) {
592
+ return this.request("/v1/runtime/mfa/challenges", {
593
+ method: "POST",
594
+ body: { authMethodId, purpose },
595
+ runtime: true,
596
+ });
597
+ }
598
+ async verifyChallenge(challengeId, payload) {
599
+ return this.request("/v1/runtime/mfa/challenges/verify", {
600
+ method: "POST",
601
+ body: { challengeId, ...payload },
602
+ runtime: true,
603
+ });
604
+ }
605
+ async createManagedAccount(chain, options) {
606
+ const normalizedOptions = typeof options === "string" ? { label: options } : options;
607
+ return this.request("/v1/runtime/accounts/managed", {
608
+ method: "POST",
609
+ body: { chain, ...normalizedOptions },
610
+ runtime: true,
611
+ });
612
+ }
613
+ async listAccounts() {
614
+ return this.request("/v1/runtime/accounts", {
615
+ method: "GET",
616
+ runtime: true,
617
+ });
618
+ }
619
+ async listWalletTools() {
620
+ return this.request("/v1/runtime/tools", {
621
+ method: "GET",
622
+ runtime: true,
623
+ });
624
+ }
625
+ async createLifiQuote(body) {
626
+ return this.request("/v1/runtime/tools/lifi/quote", {
627
+ method: "POST",
628
+ body,
629
+ runtime: true,
630
+ });
631
+ }
632
+ async createJupiterQuote(body) {
633
+ return this.request("/v1/runtime/tools/jupiter/quote", {
634
+ method: "POST",
635
+ body,
636
+ runtime: true,
637
+ });
638
+ }
639
+ async listPolymarketMarkets(nextCursor) {
640
+ const path = nextCursor
641
+ ? `/v1/runtime/tools/polymarket/markets?next_cursor=${encodeURIComponent(nextCursor)}`
642
+ : "/v1/runtime/tools/polymarket/markets";
643
+ return this.request(path, {
644
+ method: "GET",
645
+ runtime: true,
646
+ });
647
+ }
648
+ async getPolymarketOrderbook(tokenId) {
649
+ return this.request(`/v1/runtime/tools/polymarket/orderbook?tokenId=${encodeURIComponent(tokenId)}`, {
650
+ method: "GET",
651
+ runtime: true,
652
+ });
653
+ }
654
+ async previewPolymarketOrder(body) {
655
+ return this.request("/v1/runtime/tools/polymarket/orders/preview", {
656
+ method: "POST",
657
+ body,
658
+ runtime: true,
659
+ });
660
+ }
661
+ async createPolymarketStopLimitOrder(body) {
662
+ return this.request("/v1/runtime/tools/polymarket/stop-limit-orders", {
663
+ method: "POST",
664
+ body,
665
+ runtime: true,
666
+ });
667
+ }
668
+ async listPolymarketStopLimitOrders(status = "active") {
669
+ return this.request(`/v1/runtime/tools/polymarket/stop-limit-orders?status=${encodeURIComponent(status)}`, {
670
+ method: "GET",
671
+ runtime: true,
672
+ });
673
+ }
674
+ async cancelPolymarketStopLimitOrder(orderId) {
675
+ return this.request(`/v1/runtime/tools/polymarket/stop-limit-orders/${encodeURIComponent(orderId)}/cancel`, {
676
+ method: "POST",
677
+ runtime: true,
678
+ });
679
+ }
680
+ async evaluatePolymarketStopLimitOrder(orderId, body) {
681
+ return this.request(`/v1/runtime/tools/polymarket/stop-limit-orders/${encodeURIComponent(orderId)}/evaluate`, {
682
+ method: "POST",
683
+ body,
684
+ runtime: true,
685
+ });
686
+ }
687
+ async createSwapQuote(body) {
688
+ return this.request("/v1/runtime/swaps/quote", {
689
+ method: "POST",
690
+ body,
691
+ runtime: true,
692
+ });
693
+ }
694
+ async getHyperliquidMids(options) {
695
+ return getHyperliquidMids(options);
696
+ }
697
+ async createOrDerivePolymarketApiKeyWithSwivyWallet(payload, options = {}) {
698
+ const signer = createSwivyPolymarketSigner({
699
+ client: this,
700
+ accountId: payload.accountId,
701
+ accountAddress: payload.accountAddress,
702
+ challengeId: payload.challengeId,
703
+ wait: payload.wait,
704
+ });
705
+ const client = new ClobClient({
706
+ host: options.host ?? "https://clob.polymarket.com",
707
+ chain: options.chain ?? PolymarketChain.POLYGON,
708
+ signer,
709
+ signatureType: options.signatureType ?? PolymarketSignatureType.EOA,
710
+ funderAddress: options.funderAddress,
711
+ useServerTime: options.useServerTime ?? true,
712
+ throwOnError: true,
713
+ });
714
+ return client.createOrDeriveApiKey(payload.nonce);
715
+ }
716
+ async placePolymarketOrderWithSwivyWallet(payload, options = {}) {
717
+ const previewResponse = await this.previewPolymarketOrder(payload);
718
+ const accountAddress = payload.accountAddress ?? previewResponse.account?.address;
719
+ if (!accountAddress) {
720
+ throw new Error("missing_polymarket_account_address");
721
+ }
722
+ const signer = createSwivyPolymarketSigner({
723
+ client: this,
724
+ accountId: payload.accountId,
725
+ accountAddress,
726
+ challengeId: payload.challengeId,
727
+ wait: payload.wait,
728
+ });
729
+ const client = new ClobClient({
730
+ host: options.host ?? "https://clob.polymarket.com",
731
+ chain: options.chain ?? PolymarketChain.POLYGON,
732
+ signer,
733
+ creds: payload.apiCreds,
734
+ signatureType: options.signatureType ?? PolymarketSignatureType.EOA,
735
+ funderAddress: options.funderAddress,
736
+ useServerTime: options.useServerTime ?? true,
737
+ throwOnError: true,
738
+ });
739
+ const orderType = resolvePolymarketOrderType(payload.orderType);
740
+ const signedOrder = await client.createOrder({
741
+ tokenID: payload.tokenId,
742
+ price: payload.price,
743
+ size: payload.size,
744
+ side: payload.side === "buy" ? PolymarketSide.BUY : PolymarketSide.SELL,
745
+ builderCode: payload.builderCode ?? previewResponse.order?.builderCode ?? undefined,
746
+ metadata: payload.metadata,
747
+ expiration: payload.expiration,
748
+ });
749
+ const response = await client.postOrder(signedOrder, orderType, payload.postOnly, payload.deferExec);
750
+ return {
751
+ preview: {
752
+ ...buildPolymarketOrderPreviewPayload(payload),
753
+ execution: {
754
+ ready: true,
755
+ requiresTypedDataSignature: true,
756
+ hint: "Order was signed through Swivy and submitted to Polymarket CLOB V2.",
757
+ },
758
+ },
759
+ signedOrder,
760
+ response,
761
+ };
762
+ }
763
+ async evaluateAndPlacePolymarketStopLimitOrder(stopLimitOrder, options = {}) {
764
+ const orderbookPayload = await this.getPolymarketOrderbook(stopLimitOrder.tokenId);
765
+ const observedPrice = getPolymarketObservedPriceFromOrderbook(orderbookPayload.orderbook);
766
+ if (observedPrice === null) {
767
+ throw new Error("polymarket_observed_price_unavailable");
768
+ }
769
+ const shouldTrigger = isPolymarketStopTriggered({
770
+ observedPrice,
771
+ triggerPrice: stopLimitOrder.triggerPrice,
772
+ triggerDirection: stopLimitOrder.triggerDirection,
773
+ });
774
+ if (!shouldTrigger) {
775
+ return {
776
+ observedPrice,
777
+ triggered: false,
778
+ evaluation: await this.evaluatePolymarketStopLimitOrder(stopLimitOrder.id, {
779
+ observedPrice,
780
+ }),
781
+ };
782
+ }
783
+ const order = await this.placePolymarketOrderWithSwivyWallet({
784
+ accountId: stopLimitOrder.accountId,
785
+ accountAddress: stopLimitOrder.accountAddress,
786
+ tokenId: stopLimitOrder.tokenId,
787
+ conditionId: stopLimitOrder.conditionId,
788
+ side: stopLimitOrder.side,
789
+ price: stopLimitOrder.limitPrice,
790
+ size: stopLimitOrder.size,
791
+ requestedValueUsd: stopLimitOrder.requestedValueUsd ?? stopLimitOrder.limitPrice * stopLimitOrder.size,
792
+ jurisdiction: stopLimitOrder.jurisdiction,
793
+ agent: stopLimitOrder.agent,
794
+ builderCode: stopLimitOrder.builderCode,
795
+ metadata: stopLimitOrder.metadata,
796
+ expiration: stopLimitOrder.expiration,
797
+ challengeId: stopLimitOrder.challengeId,
798
+ wait: stopLimitOrder.wait,
799
+ apiCreds: stopLimitOrder.apiCreds,
800
+ }, options);
801
+ const responseRecord = order.response && typeof order.response === "object"
802
+ ? order.response
803
+ : {};
804
+ const triggeredOrderId = typeof responseRecord.orderID === "string"
805
+ ? responseRecord.orderID
806
+ : typeof responseRecord.orderId === "string"
807
+ ? responseRecord.orderId
808
+ : undefined;
809
+ return {
810
+ observedPrice,
811
+ triggered: true,
812
+ order,
813
+ evaluation: await this.evaluatePolymarketStopLimitOrder(stopLimitOrder.id, {
814
+ observedPrice,
815
+ triggeredOrderId,
816
+ }),
817
+ };
818
+ }
819
+ async getHyperliquidPerpMarkets(options) {
820
+ return getHyperliquidPerpMarkets(options);
821
+ }
822
+ async getHyperliquidAccountStatus(address, options) {
823
+ return getHyperliquidAccountStatus(address, options);
824
+ }
825
+ buildHyperliquidOrderPreview(order, options) {
826
+ return buildHyperliquidOrderPreview(order, options);
827
+ }
828
+ async placeHyperliquidOrder(payload, options) {
829
+ return placeHyperliquidOrder(payload, options);
830
+ }
831
+ async placeHyperliquidOrderWithSwivyWallet(payload, options) {
832
+ const account = payload.accountAddress
833
+ ? null
834
+ : (await this.listAccounts()).managedAccounts.find((managedAccount) => managedAccount.id === payload.accountId) ?? null;
835
+ const accountAddress = payload.accountAddress ?? account?.address;
836
+ if (!accountAddress) {
837
+ throw new Error("hyperliquid_account_not_found");
838
+ }
839
+ if (account && account.chain !== "evm") {
840
+ throw new Error("hyperliquid_requires_evm_account");
841
+ }
842
+ const wallet = createSwivyHyperliquidWallet({
843
+ client: this,
844
+ accountId: payload.accountId,
845
+ accountAddress,
846
+ challengeId: payload.challengeId,
847
+ wait: payload.wait,
848
+ });
849
+ return placeHyperliquidOrder({
850
+ ...payload,
851
+ wallet,
852
+ }, options);
853
+ }
854
+ async listEmbeddedWallets() {
855
+ const payload = await this.listAccounts();
856
+ return payload.managedAccounts;
857
+ }
858
+ async getManagedAccount(chain, options) {
859
+ const payload = await this.listAccounts();
860
+ return (payload.managedAccounts.find((account) => account.chain === chain &&
861
+ (!options?.purpose || ((account.purpose ?? "agent") === options.purpose))) ?? null);
862
+ }
863
+ async getEmbeddedWallet(chain) {
864
+ return this.getManagedAccount(chain);
865
+ }
866
+ async ensureManagedAccount(chain, options) {
867
+ const normalizedOptions = typeof options === "string" ? { label: options } : options;
868
+ const purpose = normalizedOptions?.purpose ??
869
+ (normalizedOptions?.custodyMode === "split_custody" ? "personal" : "agent");
870
+ const existing = await this.getManagedAccount(chain, {
871
+ purpose,
872
+ });
873
+ if (existing) {
874
+ return {
875
+ account: existing,
876
+ existing: true,
877
+ };
878
+ }
879
+ const created = await this.createManagedAccount(chain, normalizedOptions);
880
+ return {
881
+ account: created.account,
882
+ existing: Boolean(created.existing),
883
+ attestation: created.attestation,
884
+ };
885
+ }
886
+ async ensureEmbeddedWallet(chain, options) {
887
+ return this.ensureManagedAccount(chain, options);
888
+ }
889
+ async linkWallet(body) {
890
+ return this.request("/v1/runtime/accounts/linked", {
891
+ method: "POST",
892
+ body,
893
+ runtime: true,
894
+ });
895
+ }
896
+ async createSigningIntent(body) {
897
+ return this.request("/v1/runtime/intents/signing", {
898
+ method: "POST",
899
+ body,
900
+ runtime: true,
901
+ });
902
+ }
903
+ async createTransactionIntent(body) {
904
+ return this.request("/v1/runtime/intents/transactions", {
905
+ method: "POST",
906
+ body,
907
+ runtime: true,
908
+ });
909
+ }
910
+ async getIntent(intentId) {
911
+ return this.request(`/v1/runtime/intents/${intentId}`, {
912
+ method: "GET",
913
+ runtime: true,
914
+ });
915
+ }
916
+ async waitForIntent(intentId, options = {}) {
917
+ const timeoutMs = options.timeoutMs ?? 30_000;
918
+ const pollIntervalMs = options.pollIntervalMs ?? 1_000;
919
+ const startedAt = Date.now();
920
+ for (;;) {
921
+ if (options.signal?.aborted) {
922
+ throw new Error("intent_wait_aborted");
923
+ }
924
+ const payload = (await this.getIntent(intentId));
925
+ const intent = payload.intent;
926
+ if (intent.status === "completed" || intent.status === "failed") {
927
+ return intent;
928
+ }
929
+ if (Date.now() - startedAt >= timeoutMs) {
930
+ throw new Error("intent_wait_timeout");
931
+ }
932
+ await this.delay(pollIntervalMs, options.signal);
933
+ }
934
+ }
935
+ async createSigningIntentAndWait(body, options) {
936
+ const payload = (await this.createSigningIntent(body));
937
+ return this.waitForIntent(payload.intent.id, options);
938
+ }
939
+ async signMessageAndWait(body, options) {
940
+ return this.createSigningIntentAndWait(body, options);
941
+ }
942
+ async signTypedDataAndWait(body, options) {
943
+ return this.createSigningIntentAndWait({
944
+ accountId: body.accountId,
945
+ chain: "evm",
946
+ challengeId: body.challengeId,
947
+ payload: {
948
+ typedData: body.typedData,
949
+ },
950
+ }, options);
951
+ }
952
+ async signTransactionAndWait(body, options) {
953
+ return this.createSigningIntentAndWait(body, options);
954
+ }
955
+ async createTransactionIntentAndWait(body, options) {
956
+ const payload = (await this.createTransactionIntent(body));
957
+ return this.waitForIntent(payload.intent.id, options);
958
+ }
959
+ async executeTransactionAndWait(body, options) {
960
+ return this.createTransactionIntentAndWait(body, options);
961
+ }
962
+ async exportManagedAccount(accountId, challengeId) {
963
+ // The key comes back HPKE-sealed to a one-time key made here, so it is never in
964
+ // clear on the wire or in the API; it is opened locally.
965
+ const recipient = generateExportRecipientKeyPair();
966
+ const payload = await this.request(`/v1/runtime/accounts/${accountId}/export`, {
967
+ method: "POST",
968
+ body: { challengeId, recipientPublicKey: recipient.publicKeyBase64 },
969
+ runtime: true,
970
+ });
971
+ if (!payload.encryption) {
972
+ // An older API ignored recipientPublicKey and sent the key in clear: refuse it.
973
+ throw new Error("sealed_export_not_supported_by_server");
974
+ }
975
+ const { encryption, ...rest } = payload;
976
+ // Pin what the server says it bound the ciphertext to.
977
+ if (encryption.info !== "swivy-export-v1" || !String(encryption.aad).startsWith(`swivy-export:${accountId}:`)) {
978
+ throw new Error("sealed_export_binding_mismatch");
979
+ }
980
+ return { ...rest, privateKey: openSealedExport(encryption, recipient.privateKey) };
981
+ }
982
+ async rawSign(accountId, challengeId, payload) {
983
+ return this.request(`/v1/runtime/accounts/${accountId}/raw-sign`, {
984
+ method: "POST",
985
+ body: { challengeId, payload },
986
+ runtime: true,
987
+ });
988
+ }
989
+ async getAccountCustody(accountId) {
990
+ return this.request(`/v1/runtime/accounts/${accountId}/custody`, {
991
+ method: "GET",
992
+ runtime: true,
993
+ });
994
+ }
995
+ async enableSplitCustody(accountId, splitCustody) {
996
+ return this.request(`/v1/runtime/accounts/${accountId}/custody/split`, {
997
+ method: "POST",
998
+ body: splitCustody,
999
+ runtime: true,
1000
+ });
1001
+ }
1002
+ async createSessionGrant(body) {
1003
+ return this.request("/v1/runtime/session-grants", {
1004
+ method: "POST",
1005
+ body,
1006
+ runtime: true,
1007
+ });
1008
+ }
1009
+ async listSessionGrants() {
1010
+ return this.request("/v1/runtime/session-grants", {
1011
+ method: "GET",
1012
+ runtime: true,
1013
+ });
1014
+ }
1015
+ async revokeSessionGrant(grantId) {
1016
+ return this.request(`/v1/runtime/session-grants/${grantId}/revoke`, {
1017
+ method: "POST",
1018
+ runtime: true,
1019
+ });
1020
+ }
1021
+ async createSigningIntentWithSessionGrant(grant, privateKeyPem, payload) {
1022
+ const authorization = createSessionGrantAuthorization({
1023
+ grantId: grant.id,
1024
+ accountId: grant.accountId,
1025
+ chain: grant.chain,
1026
+ kind: resolveSessionGrantSigningKind(payload),
1027
+ payload,
1028
+ privateKeyPem,
1029
+ });
1030
+ return this.request(`/v1/runtime/session-grants/${grant.id}/intents/signing`, {
1031
+ method: "POST",
1032
+ body: { payload, authorization },
1033
+ environment: true,
1034
+ });
1035
+ }
1036
+ async createTransactionIntentWithSessionGrant(grant, privateKeyPem, payload) {
1037
+ const authorization = createSessionGrantAuthorization({
1038
+ grantId: grant.id,
1039
+ accountId: grant.accountId,
1040
+ chain: grant.chain,
1041
+ kind: "execute_transaction",
1042
+ payload,
1043
+ privateKeyPem,
1044
+ });
1045
+ return this.request(`/v1/runtime/session-grants/${grant.id}/intents/transactions`, {
1046
+ method: "POST",
1047
+ body: { payload, authorization },
1048
+ environment: true,
1049
+ });
1050
+ }
1051
+ async createServerWallet(chain, options = {}) {
1052
+ return this.request("/v1/server/wallets", {
1053
+ method: "POST",
1054
+ body: {
1055
+ chain,
1056
+ label: options.label,
1057
+ idempotencyKey: options.idempotencyKey,
1058
+ authorizationPublicKey: options.authorizationPublicKeyPem,
1059
+ },
1060
+ server: true,
1061
+ });
1062
+ }
1063
+ async listServerWallets() {
1064
+ return this.request("/v1/server/wallets", { method: "GET", server: true });
1065
+ }
1066
+ async getServerWallet(walletId) {
1067
+ return this.request(`/v1/server/wallets/${encodeURIComponent(walletId)}`, { method: "GET", server: true });
1068
+ }
1069
+ async createServerWalletSigningIntent(wallet, payload, options = {}) {
1070
+ return this.request(`/v1/server/wallets/${encodeURIComponent(wallet.id)}/intents/signing`, {
1071
+ method: "POST",
1072
+ body: {
1073
+ payload,
1074
+ authorization: options.authorizationPrivateKeyPem
1075
+ ? createSessionGrantAuthorization({
1076
+ grantId: wallet.id,
1077
+ accountId: wallet.accountId,
1078
+ chain: wallet.chain,
1079
+ kind: resolveSessionGrantSigningKind(payload),
1080
+ payload,
1081
+ privateKeyPem: options.authorizationPrivateKeyPem,
1082
+ })
1083
+ : undefined,
1084
+ },
1085
+ server: true,
1086
+ });
1087
+ }
1088
+ async createServerWalletTransactionIntent(wallet, payload, options = {}) {
1089
+ return this.request(`/v1/server/wallets/${encodeURIComponent(wallet.id)}/intents/transactions`, {
1090
+ method: "POST",
1091
+ body: {
1092
+ payload,
1093
+ authorization: options.authorizationPrivateKeyPem
1094
+ ? createSessionGrantAuthorization({
1095
+ grantId: wallet.id,
1096
+ accountId: wallet.accountId,
1097
+ chain: wallet.chain,
1098
+ kind: "execute_transaction",
1099
+ payload,
1100
+ privateKeyPem: options.authorizationPrivateKeyPem,
1101
+ })
1102
+ : undefined,
1103
+ },
1104
+ server: true,
1105
+ });
1106
+ }
1107
+ async getServerWalletIntent(walletId, intentId) {
1108
+ return this.request(`/v1/server/wallets/${encodeURIComponent(walletId)}/intents/${encodeURIComponent(intentId)}`, { method: "GET", server: true });
1109
+ }
1110
+ async getUser(userId) {
1111
+ return this.request(`/v1/server/users/${encodeURIComponent(userId)}`, { method: "GET", server: true });
1112
+ }
1113
+ async getUserByEmail(email) {
1114
+ return this.request(`/v1/server/users?email=${encodeURIComponent(email)}`, { method: "GET", server: true });
1115
+ }
1116
+ /** Create (or find by email) a user and optionally give it wallets before first login. */
1117
+ async createUser(email, options = {}) {
1118
+ return this.request("/v1/server/users", {
1119
+ method: "POST",
1120
+ body: { email, externalUserId: options.externalUserId, createWallets: options.createWallets },
1121
+ server: true,
1122
+ });
1123
+ }
1124
+ async createUserWallet(userId, chain, label) {
1125
+ return this.request(`/v1/server/users/${encodeURIComponent(userId)}/wallets`, {
1126
+ method: "POST",
1127
+ body: { chain, label },
1128
+ server: true,
1129
+ });
1130
+ }
1131
+ async listUsers(options = {}) {
1132
+ const query = new URLSearchParams();
1133
+ if (options.limit)
1134
+ query.set("limit", String(options.limit));
1135
+ if (options.cursor)
1136
+ query.set("cursor", options.cursor);
1137
+ const suffix = query.size ? `?${query.toString()}` : "";
1138
+ return this.request(`/v1/server/users/list${suffix}`, { method: "GET", server: true });
1139
+ }
1140
+ /** Remove a user's personal data and access. Their wallets are kept. */
1141
+ async deleteUser(userId) {
1142
+ return this.request(`/v1/server/users/${encodeURIComponent(userId)}`, { method: "DELETE", server: true });
1143
+ }
1144
+ /** Server-side check of a user's session token (Privy verifyAccessToken equivalent). */
1145
+ async verifySessionToken(sessionToken) {
1146
+ return this.request("/v1/server/sessions/verify", {
1147
+ method: "POST",
1148
+ body: { sessionToken },
1149
+ server: true,
1150
+ });
1151
+ }
1152
+ async listWebhookDeliveries(options = {}) {
1153
+ const query = new URLSearchParams();
1154
+ if (options.status)
1155
+ query.set("status", options.status);
1156
+ if (options.limit)
1157
+ query.set("limit", String(options.limit));
1158
+ const suffix = query.size ? `?${query.toString()}` : "";
1159
+ return this.request(`/v1/server/webhooks/deliveries${suffix}`, { method: "GET", server: true });
1160
+ }
1161
+ async replayWebhookDelivery(deliveryId) {
1162
+ return this.request(`/v1/server/webhooks/deliveries/${encodeURIComponent(deliveryId)}/replay`, {
1163
+ method: "POST",
1164
+ server: true,
1165
+ });
1166
+ }
1167
+ async request(path, options) {
1168
+ const headers = {};
1169
+ if (options.server) {
1170
+ const appId = this.config.appId ?? this.config.environmentId;
1171
+ const secretKey = this.config.secretKey ?? this.config.apiKey;
1172
+ if (!appId || !secretKey) {
1173
+ throw new Error("appId and secretKey are required for server routes");
1174
+ }
1175
+ headers["x-swivy-app-id"] = appId;
1176
+ headers["x-swivy-secret-key"] = secretKey;
1177
+ }
1178
+ if (options.adminScope === "deployment") {
1179
+ if (!this.config.adminToken) {
1180
+ throw new Error("deployment admin token is required");
1181
+ }
1182
+ headers["x-admin-token"] = this.config.adminToken;
1183
+ }
1184
+ if (options.adminScope === "scoped") {
1185
+ if (this.config.controlPlaneKey) {
1186
+ headers["x-swivy-admin-key"] = this.config.controlPlaneKey;
1187
+ }
1188
+ else if (this.config.adminToken) {
1189
+ headers["x-admin-token"] = this.config.adminToken;
1190
+ }
1191
+ else {
1192
+ throw new Error("control plane credential is required");
1193
+ }
1194
+ }
1195
+ if (options.environment || options.runtime) {
1196
+ const appId = this.config.appId ?? this.config.environmentId;
1197
+ const secretKey = this.config.secretKey ?? this.config.apiKey;
1198
+ const credential = secretKey ?? this.config.publishableKey;
1199
+ if (!appId || !credential) {
1200
+ throw new Error("environment credentials are required");
1201
+ }
1202
+ headers["x-swivy-app-id"] = appId;
1203
+ if (secretKey) {
1204
+ headers["x-swivy-secret-key"] = secretKey;
1205
+ }
1206
+ else {
1207
+ headers["x-swivy-publishable-key"] = credential;
1208
+ }
1209
+ }
1210
+ if (options.runtime) {
1211
+ if (!this.sessionToken) {
1212
+ throw new Error("session token is required");
1213
+ }
1214
+ headers.authorization = `Bearer ${this.sessionToken}`;
1215
+ }
1216
+ if (options.body !== undefined) {
1217
+ headers["content-type"] = "application/json";
1218
+ }
1219
+ const response = await fetch(`${this.config.baseUrl}${path}`, {
1220
+ method: options.method,
1221
+ headers,
1222
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
1223
+ });
1224
+ const text = await response.text();
1225
+ const payload = parseJsonResponseBody(response, text);
1226
+ if (!response.ok) {
1227
+ throw new Error(resolveResponseErrorMessage(response, payload));
1228
+ }
1229
+ return payload;
1230
+ }
1231
+ async delay(ms, signal) {
1232
+ await new Promise((resolve, reject) => {
1233
+ const timer = setTimeout(() => {
1234
+ cleanup();
1235
+ resolve();
1236
+ }, ms);
1237
+ const onAbort = () => {
1238
+ cleanup();
1239
+ reject(new Error("intent_wait_aborted"));
1240
+ };
1241
+ const cleanup = () => {
1242
+ clearTimeout(timer);
1243
+ signal?.removeEventListener("abort", onAbort);
1244
+ };
1245
+ signal?.addEventListener("abort", onAbort, { once: true });
1246
+ });
1247
+ }
1248
+ }