@thru/wallet 0.2.25 → 0.2.28

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.
Files changed (47) hide show
  1. package/README.md +1 -0
  2. package/dist/{BrowserSDK-CpRFiJsW.d.ts → BrowserSDK-CRQTOT8S.d.ts} +178 -3
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +376 -12
  5. package/dist/index.js.map +1 -1
  6. package/dist/native/react/transparent.d.ts +104 -0
  7. package/dist/native/react/transparent.js +2210 -0
  8. package/dist/native/react/transparent.js.map +1 -0
  9. package/dist/native/react.d.ts +5 -90
  10. package/dist/native/react.js +765 -32
  11. package/dist/native/react.js.map +1 -1
  12. package/dist/native.d.ts +105 -1
  13. package/dist/native.js +521 -31
  14. package/dist/native.js.map +1 -1
  15. package/dist/react-ui.js +5 -0
  16. package/dist/react-ui.js.map +1 -1
  17. package/dist/react.d.ts +2 -2
  18. package/dist/react.js +376 -12
  19. package/dist/react.js.map +1 -1
  20. package/package.json +8 -2
  21. package/src/BrowserSDK.ts +32 -1
  22. package/src/encoding.ts +39 -0
  23. package/src/index.ts +5 -1
  24. package/src/interfaces/IThruChain.ts +50 -1
  25. package/src/interfaces/types.ts +52 -0
  26. package/src/native/NativeSDK.test.ts +200 -1
  27. package/src/native/NativeSDK.ts +124 -10
  28. package/src/native/index.ts +12 -0
  29. package/src/native/provider/NativeProvider.ts +106 -5
  30. package/src/native/provider/WebViewBridge.test.ts +22 -1
  31. package/src/native/provider/WebViewBridge.ts +17 -7
  32. package/src/native/provider/chains/ThruChain.ts +215 -5
  33. package/src/native/react/ThruContext.ts +3 -1
  34. package/src/native/react/ThruProvider.tsx +25 -0
  35. package/src/native/react/ThruTransparentWalletBridge.tsx +281 -0
  36. package/src/native/react/hooks/useWallet.ts +12 -1
  37. package/src/native/react/index.ts +11 -0
  38. package/src/native/react/transparent.ts +35 -0
  39. package/src/protocol/postMessage.ts +127 -2
  40. package/src/provider/EmbeddedProvider.ts +7 -1
  41. package/src/provider/IframeManager.test.ts +18 -0
  42. package/src/provider/IframeManager.ts +8 -1
  43. package/src/provider/chains/ThruChain.ts +210 -4
  44. package/src/provider/types/messages.ts +16 -0
  45. package/src/react/index.ts +6 -0
  46. package/src/signing-sessions.test.ts +182 -0
  47. package/src/signing-sessions.ts +204 -0
package/dist/index.js CHANGED
@@ -43,14 +43,20 @@ var ThruTransactionEncoding = {
43
43
  // src/protocol/postMessage.ts
44
44
  var POST_MESSAGE_REQUEST_TYPES = {
45
45
  CONNECT: "connect",
46
+ CREATE_ACCOUNT: "createAccount",
46
47
  DISCONNECT: "disconnect",
47
48
  SIGN_MESSAGE: "signMessage",
48
49
  SIGN_TRANSACTION: "signTransaction",
50
+ SIGN_PASSKEY_CHALLENGE: "signPasskeyChallenge",
49
51
  GET_ACCOUNTS: "getAccounts",
50
52
  GET_CONNECTION_STATE: "getConnectionState",
51
53
  GET_SIGNING_CONTEXT: "getSigningContext",
52
54
  SELECT_ACCOUNT: "selectAccount",
53
- MANAGE_ACCOUNTS: "manageAccounts"
55
+ MANAGE_ACCOUNTS: "manageAccounts",
56
+ CREATE_SIGNING_SESSION: "createSigningSession",
57
+ CREATE_SIGNING_SESSION_INSTRUCTION: "createSigningSessionInstruction",
58
+ CONFIRM_SIGNING_SESSION: "confirmSigningSession",
59
+ REVOKE_SIGNING_SESSION: "revokeSigningSession"
54
60
  };
55
61
  var EMBEDDED_PROVIDER_EVENTS = {
56
62
  CONNECT_START: "connect_start",
@@ -64,7 +70,7 @@ var EMBEDDED_PROVIDER_EVENTS = {
64
70
  };
65
71
  var POST_MESSAGE_EVENT_TYPE = "event";
66
72
  var IFRAME_READY_EVENT = "iframe:ready";
67
- var DEFAULT_IFRAME_URL = "http://localhost:3000/embedded";
73
+ var DEFAULT_IFRAME_URL = "http://localhost:3010/embedded";
68
74
  var REQUEST_ID_PREFIX = "req";
69
75
  var createRequestId = (prefix = REQUEST_ID_PREFIX) => {
70
76
  const random = Math.random().toString(36).slice(2, 11);
@@ -94,7 +100,10 @@ function normalizeConnectionStateResult(result) {
94
100
  }
95
101
 
96
102
  // src/provider/IframeManager.ts
97
- var PRODUCTION_IFRAME_ORIGINS = ["https://wallet.thru.org"];
103
+ var PRODUCTION_IFRAME_ORIGINS = [
104
+ "https://wallet.thru.org",
105
+ "https://wallet.tid.sh"
106
+ ];
98
107
  var SLOW_REQUEST_TIMEOUT_MS = 5 * 60 * 1e3;
99
108
  var FAST_REQUEST_TIMEOUT_MS = 30 * 1e3;
100
109
  var PARENT_ORIGIN_SEARCH_PARAM = "tn_parent_origin";
@@ -102,7 +111,11 @@ var SLOW_REQUEST_TYPES = /* @__PURE__ */ new Set([
102
111
  POST_MESSAGE_REQUEST_TYPES.CONNECT,
103
112
  POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE,
104
113
  POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
105
- POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS
114
+ POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
115
+ POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
116
+ POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
117
+ POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
118
+ POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION
106
119
  ]);
107
120
  function isPrivateIpv4Host(hostname) {
108
121
  const parts = hostname.split(".").map((part) => Number(part));
@@ -415,11 +428,202 @@ var IframeManager = class {
415
428
  }
416
429
  };
417
430
 
431
+ // src/encoding.ts
432
+ var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
433
+ var BASE64_LOOKUP = new Map(
434
+ [...BASE64_ALPHABET].map((char, index) => [char, index])
435
+ );
436
+ function base64ToBytes(value) {
437
+ const normalized = value.replace(/\s+/g, "");
438
+ if (normalized.length === 0) return new Uint8Array();
439
+ if (normalized.length % 4 === 1) {
440
+ throw new Error("Invalid base64 data");
441
+ }
442
+ const padded = normalized.padEnd(
443
+ normalized.length + (4 - normalized.length % 4) % 4,
444
+ "="
445
+ );
446
+ const padding = padded.endsWith("==") ? 2 : padded.endsWith("=") ? 1 : 0;
447
+ const output = new Uint8Array(padded.length / 4 * 3 - padding);
448
+ let outIdx = 0;
449
+ for (let i = 0; i < padded.length; i += 4) {
450
+ const chars = padded.slice(i, i + 4);
451
+ const a = BASE64_LOOKUP.get(chars[0]);
452
+ const b = BASE64_LOOKUP.get(chars[1]);
453
+ const c = chars[2] === "=" ? 0 : BASE64_LOOKUP.get(chars[2]);
454
+ const d = chars[3] === "=" ? 0 : BASE64_LOOKUP.get(chars[3]);
455
+ if (a === void 0 || b === void 0 || c === void 0 || d === void 0) {
456
+ throw new Error("Invalid base64 data");
457
+ }
458
+ const chunk = a << 18 | b << 12 | c << 6 | d;
459
+ if (outIdx < output.length) output[outIdx++] = chunk >> 16 & 255;
460
+ if (outIdx < output.length) output[outIdx++] = chunk >> 8 & 255;
461
+ if (outIdx < output.length) output[outIdx++] = chunk & 255;
462
+ }
463
+ return output;
464
+ }
465
+
466
+ // src/signing-sessions.ts
467
+ var STORAGE_VERSION = 1;
468
+ var KEY_PREFIX = "thru.wallet.signing-sessions.v1";
469
+ function encodeKeyPart(input) {
470
+ return encodeURIComponent(input).replace(
471
+ /[!'()*]/g,
472
+ (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`
473
+ );
474
+ }
475
+ function nowSeconds() {
476
+ return Math.floor(Date.now() / 1e3);
477
+ }
478
+ function resolveSigningSessionStorageKey(params) {
479
+ if (params.storageKey) return params.storageKey;
480
+ return `${KEY_PREFIX}:${encodeKeyPart(params.walletOrigin)}:${encodeKeyPart(params.appOrigin)}`;
481
+ }
482
+ function getDefaultBrowserSigningSessionStorage() {
483
+ if (typeof window === "undefined") return null;
484
+ try {
485
+ return window.localStorage ?? null;
486
+ } catch {
487
+ return null;
488
+ }
489
+ }
490
+ function normalizeExpiresAt(value, label = "expiresAt") {
491
+ if (value instanceof Date) {
492
+ const millis = value.getTime();
493
+ if (!Number.isFinite(millis)) throw new Error(`${label} must be a valid Date`);
494
+ return Math.floor(millis / 1e3);
495
+ }
496
+ if (typeof value === "bigint") {
497
+ if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) {
498
+ throw new Error(`${label} must fit in a JavaScript safe integer`);
499
+ }
500
+ return Number(value);
501
+ }
502
+ if (typeof value === "string") {
503
+ const trimmed = value.trim();
504
+ if (!/^\d+$/.test(trimmed)) {
505
+ throw new Error(`${label} must be a Unix timestamp in seconds`);
506
+ }
507
+ return normalizeExpiresAt(BigInt(trimmed), label);
508
+ }
509
+ if (!Number.isFinite(value) || value < 0) {
510
+ throw new Error(`${label} must be a finite positive Unix timestamp`);
511
+ }
512
+ return Math.floor(value);
513
+ }
514
+ function resolveSessionExpirySeconds(options) {
515
+ const hasDuration = options.durationSeconds !== void 0;
516
+ const hasExpiresAt = options.expiresAt !== void 0;
517
+ if (hasDuration === hasExpiresAt) {
518
+ throw new Error("Provide exactly one of durationSeconds or expiresAt");
519
+ }
520
+ if (hasDuration) {
521
+ const duration = options.durationSeconds;
522
+ if (typeof duration !== "number" || !Number.isFinite(duration) || duration <= 0) {
523
+ throw new Error("durationSeconds must be a positive number");
524
+ }
525
+ return nowSeconds() + Math.floor(duration);
526
+ }
527
+ return normalizeExpiresAt(options.expiresAt, "expiresAt");
528
+ }
529
+ function assertSigningSessionWalletAccountIdx(walletAccountIdx) {
530
+ if (!Number.isInteger(walletAccountIdx) || walletAccountIdx < 2 || walletAccountIdx > 65535) {
531
+ throw new Error("walletAccountIdx must be an account index between 2 and 65535");
532
+ }
533
+ }
534
+ function normalizeDescriptor(descriptor) {
535
+ return {
536
+ id: descriptor.id,
537
+ walletAddress: descriptor.walletAddress,
538
+ publicKey: descriptor.publicKey,
539
+ authIdx: Number(descriptor.authIdx),
540
+ expiresAt: normalizeExpiresAt(descriptor.expiresAt, "descriptor.expiresAt"),
541
+ createdAt: normalizeExpiresAt(descriptor.createdAt, "descriptor.createdAt")
542
+ };
543
+ }
544
+ function isActive(descriptor) {
545
+ return nowSeconds() < descriptor.expiresAt;
546
+ }
547
+ var SigningSessionDescriptorStore = class {
548
+ constructor(storage, key) {
549
+ this.storage = storage;
550
+ this.key = key;
551
+ }
552
+ async list() {
553
+ const sessions = await this.read();
554
+ const active = sessions.filter(isActive);
555
+ if (active.length !== sessions.length) {
556
+ await this.write(active);
557
+ }
558
+ return active;
559
+ }
560
+ async get(id) {
561
+ const sessions = await this.list();
562
+ return sessions.find((session) => session.id === id) ?? null;
563
+ }
564
+ async save(descriptor) {
565
+ const normalized = normalizeDescriptor(descriptor);
566
+ const sessions = (await this.list()).filter((session) => session.id !== normalized.id);
567
+ sessions.push(normalized);
568
+ await this.write(sessions);
569
+ }
570
+ async saveReplacingWalletSessions(descriptor) {
571
+ const normalized = normalizeDescriptor(descriptor);
572
+ const sessions = (await this.list()).filter(
573
+ (session) => session.id === normalized.id || session.walletAddress !== normalized.walletAddress
574
+ );
575
+ const withoutCurrent = sessions.filter((session) => session.id !== normalized.id);
576
+ withoutCurrent.push(normalized);
577
+ await this.write(withoutCurrent);
578
+ }
579
+ async remove(id) {
580
+ const sessions = (await this.list()).filter((session) => session.id !== id);
581
+ if (sessions.length === 0) {
582
+ await this.storage.removeItem(this.key);
583
+ return;
584
+ }
585
+ await this.write(sessions);
586
+ }
587
+ async read() {
588
+ const raw = await this.storage.getItem(this.key);
589
+ if (!raw) return [];
590
+ try {
591
+ const parsed = JSON.parse(raw);
592
+ if (parsed.version !== STORAGE_VERSION || !Array.isArray(parsed.sessions)) {
593
+ await this.storage.removeItem(this.key);
594
+ return [];
595
+ }
596
+ return parsed.sessions.map(normalizeDescriptor);
597
+ } catch {
598
+ await this.storage.removeItem(this.key);
599
+ return [];
600
+ }
601
+ }
602
+ async write(sessions) {
603
+ const payload = {
604
+ version: STORAGE_VERSION,
605
+ sessions: sessions.map(normalizeDescriptor)
606
+ };
607
+ await this.storage.setItem(this.key, JSON.stringify(payload));
608
+ }
609
+ };
610
+
418
611
  // src/provider/chains/ThruChain.ts
612
+ function descriptorFromWire(session) {
613
+ return {
614
+ id: session.id,
615
+ walletAddress: session.walletAddress,
616
+ publicKey: session.publicKey,
617
+ authIdx: session.authIdx,
618
+ expiresAt: Number(BigInt(session.expiresAt)),
619
+ createdAt: Number(BigInt(session.createdAt))
620
+ };
621
+ }
419
622
  var EmbeddedThruChain = class {
420
- constructor(iframeManager, provider) {
623
+ constructor(iframeManager, provider, signingSessions) {
421
624
  this.iframeManager = iframeManager;
422
625
  this.provider = provider;
626
+ this.signingSessions = signingSessions;
423
627
  }
424
628
  get connected() {
425
629
  return this.provider.isConnected();
@@ -448,29 +652,172 @@ var EmbeddedThruChain = class {
448
652
  return response.result.signingContext;
449
653
  }
450
654
  async signTransaction(transaction) {
451
- if (!this.provider.isConnected()) {
655
+ const signingSessionId = transaction.signingSessionId;
656
+ if (!signingSessionId && !this.provider.isConnected()) {
452
657
  throw new Error("Wallet not connected");
453
658
  }
454
- this.iframeManager.show();
659
+ const session = signingSessionId ? await this.requireSigningSession(signingSessionId) : null;
660
+ const shouldShowWallet = !signingSessionId;
661
+ if (shouldShowWallet) {
662
+ this.iframeManager.show();
663
+ }
455
664
  try {
456
665
  const response = await this.iframeManager.sendMessage({
457
666
  id: createRequestId(),
458
667
  type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
459
668
  payload: {
460
- walletAddress: transaction.walletAddress,
669
+ walletAddress: transaction.walletAddress ?? session?.walletAddress,
461
670
  programAddress: transaction.programAddress,
462
671
  instructionData: transaction.instructionData,
463
672
  readWriteAddresses: transaction.readWriteAddresses,
464
673
  readOnlyAddresses: transaction.readOnlyAddresses,
465
- review: transaction.review
674
+ review: transaction.review,
675
+ signingSessionId
466
676
  },
467
677
  origin: window.location.origin
468
678
  });
469
679
  return response.result.signedTransaction;
680
+ } finally {
681
+ if (shouldShowWallet) {
682
+ this.iframeManager.hide();
683
+ }
684
+ }
685
+ }
686
+ async signPasskeyChallenge(challenge) {
687
+ if (!this.provider.isConnected()) {
688
+ throw new Error("Wallet not connected");
689
+ }
690
+ this.iframeManager.show();
691
+ try {
692
+ const response = await this.iframeManager.sendMessage({
693
+ id: createRequestId(),
694
+ type: POST_MESSAGE_REQUEST_TYPES.SIGN_PASSKEY_CHALLENGE,
695
+ payload: {
696
+ challenge: challenge.challenge,
697
+ walletAddress: challenge.walletAddress
698
+ },
699
+ origin: window.location.origin
700
+ });
701
+ return response.result;
702
+ } finally {
703
+ this.iframeManager.hide();
704
+ }
705
+ }
706
+ async createSigningSession(options) {
707
+ if (!this.provider.isConnected()) {
708
+ throw new Error("Wallet not connected");
709
+ }
710
+ if (!this.signingSessions) {
711
+ throw new Error("Signing session storage is not available");
712
+ }
713
+ const expiresAt = resolveSessionExpirySeconds(options);
714
+ this.iframeManager.show();
715
+ try {
716
+ const response = await this.iframeManager.sendMessage({
717
+ id: createRequestId(),
718
+ type: POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION,
719
+ payload: {
720
+ walletAddress: options.walletAddress,
721
+ expiresAt: String(expiresAt),
722
+ review: options.review
723
+ },
724
+ origin: window.location.origin
725
+ });
726
+ const descriptor = descriptorFromWire(response.result.session);
727
+ await this.signingSessions.saveReplacingWalletSessions(descriptor);
728
+ return this.toSigningSession(descriptor);
470
729
  } finally {
471
730
  this.iframeManager.hide();
472
731
  }
473
732
  }
733
+ async createSigningSessionInstruction(options) {
734
+ if (!this.provider.isConnected()) {
735
+ throw new Error("Wallet not connected");
736
+ }
737
+ if (!this.signingSessions) {
738
+ throw new Error("Signing session storage is not available");
739
+ }
740
+ const expiresAt = resolveSessionExpirySeconds(options);
741
+ assertSigningSessionWalletAccountIdx(options.walletAccountIdx);
742
+ const response = await this.iframeManager.sendMessage({
743
+ id: createRequestId(),
744
+ type: POST_MESSAGE_REQUEST_TYPES.CREATE_SIGNING_SESSION_INSTRUCTION,
745
+ payload: {
746
+ walletAddress: options.walletAddress,
747
+ expiresAt: String(expiresAt),
748
+ walletAccountIdx: options.walletAccountIdx
749
+ },
750
+ origin: window.location.origin
751
+ });
752
+ const descriptor = descriptorFromWire(response.result.session);
753
+ return {
754
+ session: this.toSigningSession(descriptor),
755
+ programAddress: response.result.programAddress,
756
+ instructionData: base64ToBytes(response.result.instructionData)
757
+ };
758
+ }
759
+ async confirmSigningSession(id) {
760
+ if (!this.provider.isConnected()) {
761
+ throw new Error("Wallet not connected");
762
+ }
763
+ if (!this.signingSessions) {
764
+ throw new Error("Signing session storage is not available");
765
+ }
766
+ const response = await this.iframeManager.sendMessage({
767
+ id: createRequestId(),
768
+ type: POST_MESSAGE_REQUEST_TYPES.CONFIRM_SIGNING_SESSION,
769
+ payload: { sessionId: id },
770
+ origin: window.location.origin
771
+ });
772
+ const descriptor = descriptorFromWire(response.result.session);
773
+ await this.signingSessions.saveReplacingWalletSessions(descriptor);
774
+ return this.toSigningSession(descriptor);
775
+ }
776
+ async getSigningSession(id) {
777
+ if (!this.signingSessions) return null;
778
+ const descriptor = await this.signingSessions.get(id);
779
+ return descriptor ? this.toSigningSession(descriptor) : null;
780
+ }
781
+ async getSigningSessions() {
782
+ if (!this.signingSessions) return [];
783
+ return (await this.signingSessions.list()).map(
784
+ (descriptor) => this.toSigningSession(descriptor)
785
+ );
786
+ }
787
+ async revokeSigningSession(id) {
788
+ try {
789
+ await this.iframeManager.sendMessage({
790
+ id: createRequestId(),
791
+ type: POST_MESSAGE_REQUEST_TYPES.REVOKE_SIGNING_SESSION,
792
+ payload: { sessionId: id },
793
+ origin: window.location.origin
794
+ });
795
+ } finally {
796
+ await this.signingSessions?.remove(id);
797
+ }
798
+ }
799
+ async requireSigningSession(id) {
800
+ if (!this.signingSessions) {
801
+ throw new Error("Signing session storage is not available");
802
+ }
803
+ const session = await this.signingSessions.get(id);
804
+ if (!session) {
805
+ throw new Error("Signing session is not known to this app");
806
+ }
807
+ return session;
808
+ }
809
+ toSigningSession(descriptor) {
810
+ return {
811
+ ...descriptor,
812
+ signTransaction: (transaction) => this.signTransaction({
813
+ ...transaction,
814
+ walletAddress: transaction.walletAddress ?? descriptor.walletAddress,
815
+ signingSessionId: descriptor.id
816
+ }),
817
+ revoke: () => this.revokeSigningSession(descriptor.id),
818
+ toJSON: () => ({ ...descriptor })
819
+ };
820
+ }
474
821
  };
475
822
 
476
823
  // src/provider/EmbeddedProvider.ts
@@ -504,7 +851,11 @@ var EmbeddedProvider = class {
504
851
  };
505
852
  const addressTypes = config.addressTypes || [AddressType.THRU];
506
853
  if (addressTypes.includes(AddressType.THRU)) {
507
- this._thruChain = new EmbeddedThruChain(this.iframeManager, this);
854
+ this._thruChain = new EmbeddedThruChain(
855
+ this.iframeManager,
856
+ this,
857
+ config.signingSessions
858
+ );
508
859
  }
509
860
  }
510
861
  /**
@@ -723,9 +1074,22 @@ var BrowserSDK = class {
723
1074
  this.initialized = false;
724
1075
  this.connectInFlight = null;
725
1076
  this.lastConnectResult = null;
1077
+ const iframeUrl = config.iframeUrl;
1078
+ const walletOrigin = new URL(iframeUrl ?? DEFAULT_IFRAME_URL).origin;
1079
+ const appOrigin = typeof window !== "undefined" && window.location.origin ? window.location.origin : "unknown";
1080
+ const storage = config.signingSessionStorage === false ? null : config.signingSessionStorage ?? getDefaultBrowserSigningSessionStorage();
1081
+ const signingSessions = storage ? new SigningSessionDescriptorStore(
1082
+ storage,
1083
+ resolveSigningSessionStorageKey({
1084
+ walletOrigin,
1085
+ appOrigin,
1086
+ storageKey: config.signingSessionStorageKey
1087
+ })
1088
+ ) : void 0;
726
1089
  this.provider = new EmbeddedProvider({
727
- iframeUrl: config.iframeUrl,
728
- addressTypes: config.addressTypes || [AddressType.THRU]
1090
+ iframeUrl,
1091
+ addressTypes: config.addressTypes || [AddressType.THRU],
1092
+ signingSessions
729
1093
  });
730
1094
  this.thruClient = createThruClient({
731
1095
  baseUrl: config.rpcUrl