@phantom/react-native-sdk 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +133 -59
  2. package/dist/index.mjs +119 -46
  3. package/package.json +11 -11
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AddressType: () => import_client.AddressType,
34
- NetworkId: () => import_constants4.NetworkId,
34
+ NetworkId: () => import_constants5.NetworkId,
35
35
  PhantomProvider: () => PhantomProvider,
36
36
  darkTheme: () => import_wallet_sdk_ui6.darkTheme,
37
37
  lightTheme: () => import_wallet_sdk_ui6.lightTheme,
@@ -48,7 +48,7 @@ module.exports = __toCommonJS(src_exports);
48
48
  // src/PhantomProvider.tsx
49
49
  var import_react8 = require("react");
50
50
  var import_embedded_provider_core = require("@phantom/embedded-provider-core");
51
- var import_constants3 = require("@phantom/constants");
51
+ var import_constants4 = require("@phantom/constants");
52
52
  var import_wallet_sdk_ui5 = require("@phantom/wallet-sdk-ui");
53
53
 
54
54
  // src/ModalProvider.tsx
@@ -557,7 +557,7 @@ var ExpoAuthProvider = class {
557
557
  // OAuth session management - defaults to allow refresh unless explicitly clearing after logout
558
558
  clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
559
559
  allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
560
- sdk_version: "1.0.4",
560
+ sdk_version: "1.0.6",
561
561
  sdk_type: "react-native",
562
562
  platform: import_react_native5.Platform.OS
563
563
  });
@@ -657,7 +657,6 @@ var ExpoAuth2AuthProvider = class {
657
657
  throw new Error("Stamper key pair not found.");
658
658
  }
659
659
  const codeVerifier = (0, import_auth2.createCodeVerifier)();
660
- const salt = (0, import_auth2.createSalt)();
661
660
  const url = await (0, import_auth2.createConnectStartUrl)({
662
661
  keyPair,
663
662
  connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
@@ -666,7 +665,8 @@ var ExpoAuth2AuthProvider = class {
666
665
  sessionId: options.sessionId,
667
666
  provider: options.provider,
668
667
  codeVerifier,
669
- salt
668
+ // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
669
+ salt: ""
670
670
  });
671
671
  await WebBrowser2.warmUpAsync();
672
672
  let result;
@@ -692,15 +692,14 @@ var ExpoAuth2AuthProvider = class {
692
692
  if (!code) {
693
693
  throw new Error("Auth2 callback missing authorization code");
694
694
  }
695
- const { idToken, bearerToken, authUserId, expiresInMs } = await (0, import_auth2.exchangeAuthCode)({
695
+ const { idToken, bearerToken, authUserId, expiresInMs, refreshToken } = await (0, import_auth2.exchangeAuthCode)({
696
696
  authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
697
697
  clientId: this.auth2ProviderOptions.clientId,
698
698
  redirectUri: this.auth2ProviderOptions.redirectUri,
699
699
  code,
700
700
  codeVerifier
701
701
  });
702
- this.stamper.idToken = idToken;
703
- this.stamper.salt = salt;
702
+ await this.stamper.setTokens({ idToken, bearerToken, refreshToken, expiresInMs });
704
703
  const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
705
704
  return {
706
705
  walletId,
@@ -721,25 +720,47 @@ var import_bs58 = __toESM(require("bs58"));
721
720
  var import_buffer = require("buffer");
722
721
  var import_base64url = require("@phantom/base64url");
723
722
  var import_sdk_types = require("@phantom/sdk-types");
723
+ var import_auth22 = require("@phantom/auth2");
724
+ var import_constants2 = require("@phantom/constants");
724
725
  var ExpoAuth2Stamper = class {
725
726
  /**
726
727
  * @param storageKey - expo-secure-store key used to persist the P-256 private key.
727
728
  * Use a unique key per app, e.g. `phantom-auth2-<appId>`.
729
+ * @param refreshConfig - When provided, the stamper will automatically refresh
730
+ * the id_token using the refresh_token before it expires.
728
731
  */
729
- constructor(storageKey) {
732
+ constructor(storageKey, refreshConfig) {
730
733
  this.storageKey = storageKey;
731
- this.privateKey = null;
732
- this.publicKey = null;
734
+ this.refreshConfig = refreshConfig;
735
+ this._keyPair = null;
733
736
  this._keyInfo = null;
737
+ this._idToken = null;
738
+ this._bearerToken = null;
739
+ this._refreshToken = null;
740
+ this._tokenExpiresAt = null;
734
741
  this.algorithm = import_sdk_types.Algorithm.secp256r1;
735
742
  this.type = "OIDC";
736
743
  }
737
744
  async init() {
738
745
  const stored = await this.loadRecord();
739
746
  if (stored) {
740
- this.privateKey = await this.importPrivateKey(stored.privateKeyPkcs8);
741
- this.publicKey = await this.importPublicKeyFromBase58(stored.keyInfo.publicKey);
747
+ this._keyPair = {
748
+ privateKey: await this.importPrivateKey(stored.privateKeyPkcs8),
749
+ publicKey: await this.importPublicKeyFromBase58(stored.keyInfo.publicKey)
750
+ };
742
751
  this._keyInfo = stored.keyInfo;
752
+ if (stored.idToken) {
753
+ this._idToken = stored.idToken;
754
+ }
755
+ if (stored.bearerToken) {
756
+ this._bearerToken = stored.bearerToken;
757
+ }
758
+ if (stored.refreshToken) {
759
+ this._refreshToken = stored.refreshToken;
760
+ }
761
+ if (stored.tokenExpiresAt) {
762
+ this._tokenExpiresAt = stored.tokenExpiresAt;
763
+ }
743
764
  return this._keyInfo;
744
765
  }
745
766
  return this.generateAndStore();
@@ -748,45 +769,96 @@ var ExpoAuth2Stamper = class {
748
769
  return this._keyInfo;
749
770
  }
750
771
  getCryptoKeyPair() {
751
- if (!this.privateKey || !this.publicKey)
772
+ return this._keyPair;
773
+ }
774
+ /**
775
+ * Returns the current token state (refreshing proactively if near expiry),
776
+ * or null if no token has been set yet.
777
+ */
778
+ async getTokens() {
779
+ if (this.refreshConfig && this._refreshToken && this._tokenExpiresAt !== null && Date.now() >= this._tokenExpiresAt - import_constants2.TOKEN_REFRESH_BUFFER_MS) {
780
+ const refreshed = await (0, import_auth22.refreshToken)({
781
+ authApiBaseUrl: this.refreshConfig.authApiBaseUrl,
782
+ clientId: this.refreshConfig.clientId,
783
+ redirectUri: this.refreshConfig.redirectUri,
784
+ refreshToken: this._refreshToken
785
+ });
786
+ await this.setTokens(refreshed);
787
+ }
788
+ if (!this._idToken || !this._bearerToken) {
752
789
  return null;
753
- return { privateKey: this.privateKey, publicKey: this.publicKey };
790
+ }
791
+ return {
792
+ idToken: this._idToken,
793
+ bearerToken: this._bearerToken,
794
+ refreshToken: this._refreshToken ?? void 0
795
+ };
796
+ }
797
+ /**
798
+ * Arms the stamper with the OIDC token data for subsequent KMS stamp() calls.
799
+ *
800
+ * Persists the tokens to SecureStore alongside the key pair so that
801
+ * auto-connect can restore them on the next app launch without a new login.
802
+ *
803
+ * @param refreshToken - When provided alongside a `refreshConfig`, enables
804
+ * silent token refresh before the token expires.
805
+ * @param expiresInMs - Token lifetime in milliseconds (from `expires_in * 1000`).
806
+ * Used to compute the absolute expiry time for proactive refresh.
807
+ */
808
+ async setTokens({
809
+ idToken,
810
+ bearerToken,
811
+ refreshToken,
812
+ expiresInMs
813
+ }) {
814
+ this._idToken = idToken;
815
+ this._bearerToken = bearerToken;
816
+ this._refreshToken = refreshToken ?? null;
817
+ this._tokenExpiresAt = expiresInMs != null ? Date.now() + expiresInMs : null;
818
+ const existing = await this.loadRecord();
819
+ if (existing) {
820
+ await this.storeRecord({
821
+ ...existing,
822
+ idToken,
823
+ bearerToken,
824
+ refreshToken,
825
+ tokenExpiresAt: this._tokenExpiresAt ?? void 0
826
+ });
827
+ }
754
828
  }
755
829
  async stamp(params) {
756
- if (!this.privateKey || !this._keyInfo) {
830
+ if (!this._keyPair || !this._keyInfo || this._idToken === null) {
757
831
  throw new Error("ExpoAuth2Stamper not initialized. Call init() first.");
758
832
  }
759
833
  const signatureRaw = await crypto.subtle.sign(
760
834
  { name: "ECDSA", hash: "SHA-256" },
761
- this.privateKey,
835
+ this._keyPair.privateKey,
762
836
  new Uint8Array(params.data)
763
837
  );
764
838
  const rawPublicKey = import_bs58.default.decode(this._keyInfo.publicKey);
765
- if (this.idToken === void 0 || this.salt === void 0) {
766
- throw new Error("ExpoAuth2Stamper not initialized with idToken or salt.");
767
- }
768
839
  const stampData = {
769
- kind: "OIDC",
770
- idToken: this.idToken,
840
+ kind: this.type,
841
+ idToken: this._idToken,
771
842
  publicKey: (0, import_base64url.base64urlEncode)(rawPublicKey),
772
- algorithm: "Secp256r1",
773
- salt: this.salt,
843
+ algorithm: this.algorithm,
844
+ // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
845
+ salt: "",
774
846
  signature: (0, import_base64url.base64urlEncode)(new Uint8Array(signatureRaw))
775
847
  };
776
848
  return (0, import_base64url.base64urlEncode)(new TextEncoder().encode(JSON.stringify(stampData)));
777
849
  }
778
850
  async resetKeyPair() {
779
- await this.clearStoredRecord();
780
- this.privateKey = null;
781
- this.publicKey = null;
782
- this._keyInfo = null;
851
+ await this.clear();
783
852
  return this.generateAndStore();
784
853
  }
785
854
  async clear() {
786
855
  await this.clearStoredRecord();
787
- this.privateKey = null;
788
- this.publicKey = null;
856
+ this._keyPair = null;
789
857
  this._keyInfo = null;
858
+ this._idToken = null;
859
+ this._bearerToken = null;
860
+ this._refreshToken = null;
861
+ this._tokenExpiresAt = null;
790
862
  }
791
863
  // Auth2 doesn't use key rotation; minimal no-op implementations.
792
864
  async rotateKeyPair() {
@@ -811,16 +883,11 @@ var ExpoAuth2Stamper = class {
811
883
  const publicKeyBase58 = import_bs58.default.encode(rawPublicKey);
812
884
  const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
813
885
  const keyId = (0, import_base64url.base64urlEncode)(new Uint8Array(keyIdBuffer)).substring(0, 16);
886
+ this._keyPair = keyPair;
814
887
  this._keyInfo = { keyId, publicKey: publicKeyBase58, createdAt: Date.now() };
815
888
  const pkcs8Buffer = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
816
889
  const privateKeyPkcs8 = import_buffer.Buffer.from(pkcs8Buffer).toString("base64");
817
- await SecureStore2.setItemAsync(
818
- this.storageKey,
819
- JSON.stringify({ privateKeyPkcs8, keyInfo: this._keyInfo }),
820
- { requireAuthentication: false }
821
- );
822
- this.privateKey = await this.importPrivateKey(privateKeyPkcs8);
823
- this.publicKey = keyPair.publicKey;
890
+ await this.storeRecord({ privateKeyPkcs8, keyInfo: this._keyInfo });
824
891
  return this._keyInfo;
825
892
  }
826
893
  async importPublicKeyFromBase58(base58PublicKey) {
@@ -853,6 +920,9 @@ var ExpoAuth2Stamper = class {
853
920
  return null;
854
921
  }
855
922
  }
923
+ async storeRecord(record) {
924
+ await SecureStore2.setItemAsync(this.storageKey, JSON.stringify(record), { requireAuthentication: false });
925
+ }
856
926
  async clearStoredRecord() {
857
927
  try {
858
928
  await SecureStore2.deleteItemAsync(this.storageKey);
@@ -932,7 +1002,7 @@ var ExpoURLParamsAccessor = class {
932
1002
  // src/providers/embedded/stamper.ts
933
1003
  var SecureStore3 = __toESM(require("expo-secure-store"));
934
1004
  var import_api_key_stamper = require("@phantom/api-key-stamper");
935
- var import_constants2 = require("@phantom/constants");
1005
+ var import_constants3 = require("@phantom/constants");
936
1006
  var import_crypto = require("@phantom/crypto");
937
1007
  var import_base64url2 = require("@phantom/base64url");
938
1008
  var ReactNativeStamper = class {
@@ -940,7 +1010,7 @@ var ReactNativeStamper = class {
940
1010
  constructor(config = {}) {
941
1011
  this.activeKeyRecord = null;
942
1012
  this.pendingKeyRecord = null;
943
- this.algorithm = import_constants2.DEFAULT_AUTHENTICATOR_ALGORITHM;
1013
+ this.algorithm = import_constants3.DEFAULT_AUTHENTICATOR_ALGORITHM;
944
1014
  this.type = "PKI";
945
1015
  this.keyPrefix = config.keyPrefix || "phantom-rn-stamper";
946
1016
  this.appId = config.appId || "default";
@@ -1108,24 +1178,24 @@ var ExpoLogger = class {
1108
1178
  constructor(enabled = false) {
1109
1179
  this.enabled = enabled;
1110
1180
  }
1111
- info(category, message, data) {
1181
+ info(message, ...args) {
1112
1182
  if (this.enabled) {
1113
- console.info(`[${category}] ${message}`, data);
1183
+ console.info(`[PHANTOM] ${message}`, ...args);
1114
1184
  }
1115
1185
  }
1116
- warn(category, message, data) {
1186
+ warn(message, ...args) {
1117
1187
  if (this.enabled) {
1118
- console.warn(`[${category}] ${message}`, data);
1188
+ console.warn(`[PHANTOM] ${message}`, ...args);
1119
1189
  }
1120
1190
  }
1121
- error(category, message, data) {
1191
+ error(message, ...args) {
1122
1192
  if (this.enabled) {
1123
- console.error(`[${category}] ${message}`, data);
1193
+ console.error(`[PHANTOM] ${message}`, ...args);
1124
1194
  }
1125
1195
  }
1126
- log(category, message, data) {
1196
+ debug(message, ...args) {
1127
1197
  if (this.enabled) {
1128
- console.log(`[${category}] ${message}`, data);
1198
+ console.log(`[PHANTOM] ${message}`, ...args);
1129
1199
  }
1130
1200
  }
1131
1201
  };
@@ -1158,12 +1228,12 @@ function PhantomProvider({ children, config, debugConfig, theme, appIcon, appNam
1158
1228
  const redirectUrl = config.authOptions?.redirectUrl || `${config.scheme}://phantom-auth-callback`;
1159
1229
  return {
1160
1230
  ...config,
1161
- apiBaseUrl: config.apiBaseUrl || import_constants3.DEFAULT_WALLET_API_URL,
1162
- embeddedWalletType: config.embeddedWalletType || import_constants3.DEFAULT_EMBEDDED_WALLET_TYPE,
1231
+ apiBaseUrl: config.apiBaseUrl || import_constants4.DEFAULT_WALLET_API_URL,
1232
+ embeddedWalletType: config.embeddedWalletType || import_constants4.DEFAULT_EMBEDDED_WALLET_TYPE,
1163
1233
  authOptions: {
1164
1234
  ...config.authOptions || {},
1165
1235
  redirectUrl,
1166
- authUrl: config.authOptions?.authUrl || import_constants3.DEFAULT_AUTH_URL
1236
+ authUrl: config.authOptions?.authUrl || import_constants4.DEFAULT_AUTH_URL
1167
1237
  }
1168
1238
  };
1169
1239
  }, [config]);
@@ -1171,7 +1241,11 @@ function PhantomProvider({ children, config, debugConfig, theme, appIcon, appNam
1171
1241
  const storage = new ExpoSecureStorage();
1172
1242
  const urlParamsAccessor = new ExpoURLParamsAccessor();
1173
1243
  const logger = new ExpoLogger(debugConfig?.enabled || false);
1174
- const stamper = config.unstable__auth2Options ? new ExpoAuth2Stamper(`phantom-auth2-${memoizedConfig.appId}`) : new ReactNativeStamper({
1244
+ const stamper = config.unstable__auth2Options && config.authOptions?.redirectUrl ? new ExpoAuth2Stamper(`phantom-auth2-${memoizedConfig.appId}`, {
1245
+ authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl,
1246
+ clientId: config.unstable__auth2Options.clientId,
1247
+ redirectUri: config.authOptions.redirectUrl
1248
+ }) : new ReactNativeStamper({
1175
1249
  keyPrefix: `phantom-rn-${memoizedConfig.appId}`,
1176
1250
  appId: memoizedConfig.appId
1177
1251
  });
@@ -1197,13 +1271,13 @@ function PhantomProvider({ children, config, debugConfig, theme, appIcon, appNam
1197
1271
  phantomAppProvider: new ReactNativePhantomAppProvider(),
1198
1272
  name: platformName,
1199
1273
  analyticsHeaders: {
1200
- [import_constants3.ANALYTICS_HEADERS.SDK_TYPE]: "react-native",
1201
- [import_constants3.ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
1202
- [import_constants3.ANALYTICS_HEADERS.PLATFORM_VERSION]: `${import_react_native7.Platform.Version}`,
1203
- [import_constants3.ANALYTICS_HEADERS.CLIENT]: import_react_native7.Platform.OS,
1204
- [import_constants3.ANALYTICS_HEADERS.APP_ID]: config.appId,
1205
- [import_constants3.ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
1206
- [import_constants3.ANALYTICS_HEADERS.SDK_VERSION]: "1.0.4"
1274
+ [import_constants4.ANALYTICS_HEADERS.SDK_TYPE]: "react-native",
1275
+ [import_constants4.ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
1276
+ [import_constants4.ANALYTICS_HEADERS.PLATFORM_VERSION]: `${import_react_native7.Platform.Version}`,
1277
+ [import_constants4.ANALYTICS_HEADERS.CLIENT]: import_react_native7.Platform.OS,
1278
+ [import_constants4.ANALYTICS_HEADERS.APP_ID]: config.appId,
1279
+ [import_constants4.ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
1280
+ [import_constants4.ANALYTICS_HEADERS.SDK_VERSION]: "1.0.6"
1207
1281
  // Replaced at build time
1208
1282
  }
1209
1283
  };
@@ -1317,7 +1391,7 @@ function useEthereum() {
1317
1391
 
1318
1392
  // src/index.ts
1319
1393
  var import_client = require("@phantom/client");
1320
- var import_constants4 = require("@phantom/constants");
1394
+ var import_constants5 = require("@phantom/constants");
1321
1395
  var import_wallet_sdk_ui6 = require("@phantom/wallet-sdk-ui");
1322
1396
  // Annotate the CommonJS export names for ESM import in node:
1323
1397
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -515,7 +515,7 @@ var ExpoAuthProvider = class {
515
515
  // OAuth session management - defaults to allow refresh unless explicitly clearing after logout
516
516
  clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
517
517
  allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
518
- sdk_version: "1.0.4",
518
+ sdk_version: "1.0.6",
519
519
  sdk_type: "react-native",
520
520
  platform: Platform.OS
521
521
  });
@@ -594,7 +594,6 @@ var ExpoAuthProvider = class {
594
594
  import * as WebBrowser2 from "expo-web-browser";
595
595
  import {
596
596
  createCodeVerifier,
597
- createSalt,
598
597
  exchangeAuthCode,
599
598
  Auth2KmsRpcClient,
600
599
  createConnectStartUrl
@@ -621,7 +620,6 @@ var ExpoAuth2AuthProvider = class {
621
620
  throw new Error("Stamper key pair not found.");
622
621
  }
623
622
  const codeVerifier = createCodeVerifier();
624
- const salt = createSalt();
625
623
  const url = await createConnectStartUrl({
626
624
  keyPair,
627
625
  connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
@@ -630,7 +628,8 @@ var ExpoAuth2AuthProvider = class {
630
628
  sessionId: options.sessionId,
631
629
  provider: options.provider,
632
630
  codeVerifier,
633
- salt
631
+ // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
632
+ salt: ""
634
633
  });
635
634
  await WebBrowser2.warmUpAsync();
636
635
  let result;
@@ -656,15 +655,14 @@ var ExpoAuth2AuthProvider = class {
656
655
  if (!code) {
657
656
  throw new Error("Auth2 callback missing authorization code");
658
657
  }
659
- const { idToken, bearerToken, authUserId, expiresInMs } = await exchangeAuthCode({
658
+ const { idToken, bearerToken, authUserId, expiresInMs, refreshToken } = await exchangeAuthCode({
660
659
  authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
661
660
  clientId: this.auth2ProviderOptions.clientId,
662
661
  redirectUri: this.auth2ProviderOptions.redirectUri,
663
662
  code,
664
663
  codeVerifier
665
664
  });
666
- this.stamper.idToken = idToken;
667
- this.stamper.salt = salt;
665
+ await this.stamper.setTokens({ idToken, bearerToken, refreshToken, expiresInMs });
668
666
  const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
669
667
  return {
670
668
  walletId,
@@ -685,25 +683,47 @@ import bs58 from "bs58";
685
683
  import { Buffer } from "buffer";
686
684
  import { base64urlEncode } from "@phantom/base64url";
687
685
  import { Algorithm } from "@phantom/sdk-types";
686
+ import { refreshToken as refreshTokenRequest } from "@phantom/auth2";
687
+ import { TOKEN_REFRESH_BUFFER_MS } from "@phantom/constants";
688
688
  var ExpoAuth2Stamper = class {
689
689
  /**
690
690
  * @param storageKey - expo-secure-store key used to persist the P-256 private key.
691
691
  * Use a unique key per app, e.g. `phantom-auth2-<appId>`.
692
+ * @param refreshConfig - When provided, the stamper will automatically refresh
693
+ * the id_token using the refresh_token before it expires.
692
694
  */
693
- constructor(storageKey) {
695
+ constructor(storageKey, refreshConfig) {
694
696
  this.storageKey = storageKey;
695
- this.privateKey = null;
696
- this.publicKey = null;
697
+ this.refreshConfig = refreshConfig;
698
+ this._keyPair = null;
697
699
  this._keyInfo = null;
700
+ this._idToken = null;
701
+ this._bearerToken = null;
702
+ this._refreshToken = null;
703
+ this._tokenExpiresAt = null;
698
704
  this.algorithm = Algorithm.secp256r1;
699
705
  this.type = "OIDC";
700
706
  }
701
707
  async init() {
702
708
  const stored = await this.loadRecord();
703
709
  if (stored) {
704
- this.privateKey = await this.importPrivateKey(stored.privateKeyPkcs8);
705
- this.publicKey = await this.importPublicKeyFromBase58(stored.keyInfo.publicKey);
710
+ this._keyPair = {
711
+ privateKey: await this.importPrivateKey(stored.privateKeyPkcs8),
712
+ publicKey: await this.importPublicKeyFromBase58(stored.keyInfo.publicKey)
713
+ };
706
714
  this._keyInfo = stored.keyInfo;
715
+ if (stored.idToken) {
716
+ this._idToken = stored.idToken;
717
+ }
718
+ if (stored.bearerToken) {
719
+ this._bearerToken = stored.bearerToken;
720
+ }
721
+ if (stored.refreshToken) {
722
+ this._refreshToken = stored.refreshToken;
723
+ }
724
+ if (stored.tokenExpiresAt) {
725
+ this._tokenExpiresAt = stored.tokenExpiresAt;
726
+ }
707
727
  return this._keyInfo;
708
728
  }
709
729
  return this.generateAndStore();
@@ -712,45 +732,96 @@ var ExpoAuth2Stamper = class {
712
732
  return this._keyInfo;
713
733
  }
714
734
  getCryptoKeyPair() {
715
- if (!this.privateKey || !this.publicKey)
735
+ return this._keyPair;
736
+ }
737
+ /**
738
+ * Returns the current token state (refreshing proactively if near expiry),
739
+ * or null if no token has been set yet.
740
+ */
741
+ async getTokens() {
742
+ if (this.refreshConfig && this._refreshToken && this._tokenExpiresAt !== null && Date.now() >= this._tokenExpiresAt - TOKEN_REFRESH_BUFFER_MS) {
743
+ const refreshed = await refreshTokenRequest({
744
+ authApiBaseUrl: this.refreshConfig.authApiBaseUrl,
745
+ clientId: this.refreshConfig.clientId,
746
+ redirectUri: this.refreshConfig.redirectUri,
747
+ refreshToken: this._refreshToken
748
+ });
749
+ await this.setTokens(refreshed);
750
+ }
751
+ if (!this._idToken || !this._bearerToken) {
716
752
  return null;
717
- return { privateKey: this.privateKey, publicKey: this.publicKey };
753
+ }
754
+ return {
755
+ idToken: this._idToken,
756
+ bearerToken: this._bearerToken,
757
+ refreshToken: this._refreshToken ?? void 0
758
+ };
759
+ }
760
+ /**
761
+ * Arms the stamper with the OIDC token data for subsequent KMS stamp() calls.
762
+ *
763
+ * Persists the tokens to SecureStore alongside the key pair so that
764
+ * auto-connect can restore them on the next app launch without a new login.
765
+ *
766
+ * @param refreshToken - When provided alongside a `refreshConfig`, enables
767
+ * silent token refresh before the token expires.
768
+ * @param expiresInMs - Token lifetime in milliseconds (from `expires_in * 1000`).
769
+ * Used to compute the absolute expiry time for proactive refresh.
770
+ */
771
+ async setTokens({
772
+ idToken,
773
+ bearerToken,
774
+ refreshToken,
775
+ expiresInMs
776
+ }) {
777
+ this._idToken = idToken;
778
+ this._bearerToken = bearerToken;
779
+ this._refreshToken = refreshToken ?? null;
780
+ this._tokenExpiresAt = expiresInMs != null ? Date.now() + expiresInMs : null;
781
+ const existing = await this.loadRecord();
782
+ if (existing) {
783
+ await this.storeRecord({
784
+ ...existing,
785
+ idToken,
786
+ bearerToken,
787
+ refreshToken,
788
+ tokenExpiresAt: this._tokenExpiresAt ?? void 0
789
+ });
790
+ }
718
791
  }
719
792
  async stamp(params) {
720
- if (!this.privateKey || !this._keyInfo) {
793
+ if (!this._keyPair || !this._keyInfo || this._idToken === null) {
721
794
  throw new Error("ExpoAuth2Stamper not initialized. Call init() first.");
722
795
  }
723
796
  const signatureRaw = await crypto.subtle.sign(
724
797
  { name: "ECDSA", hash: "SHA-256" },
725
- this.privateKey,
798
+ this._keyPair.privateKey,
726
799
  new Uint8Array(params.data)
727
800
  );
728
801
  const rawPublicKey = bs58.decode(this._keyInfo.publicKey);
729
- if (this.idToken === void 0 || this.salt === void 0) {
730
- throw new Error("ExpoAuth2Stamper not initialized with idToken or salt.");
731
- }
732
802
  const stampData = {
733
- kind: "OIDC",
734
- idToken: this.idToken,
803
+ kind: this.type,
804
+ idToken: this._idToken,
735
805
  publicKey: base64urlEncode(rawPublicKey),
736
- algorithm: "Secp256r1",
737
- salt: this.salt,
806
+ algorithm: this.algorithm,
807
+ // The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
808
+ salt: "",
738
809
  signature: base64urlEncode(new Uint8Array(signatureRaw))
739
810
  };
740
811
  return base64urlEncode(new TextEncoder().encode(JSON.stringify(stampData)));
741
812
  }
742
813
  async resetKeyPair() {
743
- await this.clearStoredRecord();
744
- this.privateKey = null;
745
- this.publicKey = null;
746
- this._keyInfo = null;
814
+ await this.clear();
747
815
  return this.generateAndStore();
748
816
  }
749
817
  async clear() {
750
818
  await this.clearStoredRecord();
751
- this.privateKey = null;
752
- this.publicKey = null;
819
+ this._keyPair = null;
753
820
  this._keyInfo = null;
821
+ this._idToken = null;
822
+ this._bearerToken = null;
823
+ this._refreshToken = null;
824
+ this._tokenExpiresAt = null;
754
825
  }
755
826
  // Auth2 doesn't use key rotation; minimal no-op implementations.
756
827
  async rotateKeyPair() {
@@ -775,16 +846,11 @@ var ExpoAuth2Stamper = class {
775
846
  const publicKeyBase58 = bs58.encode(rawPublicKey);
776
847
  const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
777
848
  const keyId = base64urlEncode(new Uint8Array(keyIdBuffer)).substring(0, 16);
849
+ this._keyPair = keyPair;
778
850
  this._keyInfo = { keyId, publicKey: publicKeyBase58, createdAt: Date.now() };
779
851
  const pkcs8Buffer = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
780
852
  const privateKeyPkcs8 = Buffer.from(pkcs8Buffer).toString("base64");
781
- await SecureStore2.setItemAsync(
782
- this.storageKey,
783
- JSON.stringify({ privateKeyPkcs8, keyInfo: this._keyInfo }),
784
- { requireAuthentication: false }
785
- );
786
- this.privateKey = await this.importPrivateKey(privateKeyPkcs8);
787
- this.publicKey = keyPair.publicKey;
853
+ await this.storeRecord({ privateKeyPkcs8, keyInfo: this._keyInfo });
788
854
  return this._keyInfo;
789
855
  }
790
856
  async importPublicKeyFromBase58(base58PublicKey) {
@@ -817,6 +883,9 @@ var ExpoAuth2Stamper = class {
817
883
  return null;
818
884
  }
819
885
  }
886
+ async storeRecord(record) {
887
+ await SecureStore2.setItemAsync(this.storageKey, JSON.stringify(record), { requireAuthentication: false });
888
+ }
820
889
  async clearStoredRecord() {
821
890
  try {
822
891
  await SecureStore2.deleteItemAsync(this.storageKey);
@@ -1072,24 +1141,24 @@ var ExpoLogger = class {
1072
1141
  constructor(enabled = false) {
1073
1142
  this.enabled = enabled;
1074
1143
  }
1075
- info(category, message, data) {
1144
+ info(message, ...args) {
1076
1145
  if (this.enabled) {
1077
- console.info(`[${category}] ${message}`, data);
1146
+ console.info(`[PHANTOM] ${message}`, ...args);
1078
1147
  }
1079
1148
  }
1080
- warn(category, message, data) {
1149
+ warn(message, ...args) {
1081
1150
  if (this.enabled) {
1082
- console.warn(`[${category}] ${message}`, data);
1151
+ console.warn(`[PHANTOM] ${message}`, ...args);
1083
1152
  }
1084
1153
  }
1085
- error(category, message, data) {
1154
+ error(message, ...args) {
1086
1155
  if (this.enabled) {
1087
- console.error(`[${category}] ${message}`, data);
1156
+ console.error(`[PHANTOM] ${message}`, ...args);
1088
1157
  }
1089
1158
  }
1090
- log(category, message, data) {
1159
+ debug(message, ...args) {
1091
1160
  if (this.enabled) {
1092
- console.log(`[${category}] ${message}`, data);
1161
+ console.log(`[PHANTOM] ${message}`, ...args);
1093
1162
  }
1094
1163
  }
1095
1164
  };
@@ -1135,7 +1204,11 @@ function PhantomProvider({ children, config, debugConfig, theme, appIcon, appNam
1135
1204
  const storage = new ExpoSecureStorage();
1136
1205
  const urlParamsAccessor = new ExpoURLParamsAccessor();
1137
1206
  const logger = new ExpoLogger(debugConfig?.enabled || false);
1138
- const stamper = config.unstable__auth2Options ? new ExpoAuth2Stamper(`phantom-auth2-${memoizedConfig.appId}`) : new ReactNativeStamper({
1207
+ const stamper = config.unstable__auth2Options && config.authOptions?.redirectUrl ? new ExpoAuth2Stamper(`phantom-auth2-${memoizedConfig.appId}`, {
1208
+ authApiBaseUrl: config.unstable__auth2Options.authApiBaseUrl,
1209
+ clientId: config.unstable__auth2Options.clientId,
1210
+ redirectUri: config.authOptions.redirectUrl
1211
+ }) : new ReactNativeStamper({
1139
1212
  keyPrefix: `phantom-rn-${memoizedConfig.appId}`,
1140
1213
  appId: memoizedConfig.appId
1141
1214
  });
@@ -1167,7 +1240,7 @@ function PhantomProvider({ children, config, debugConfig, theme, appIcon, appNam
1167
1240
  [ANALYTICS_HEADERS.CLIENT]: Platform2.OS,
1168
1241
  [ANALYTICS_HEADERS.APP_ID]: config.appId,
1169
1242
  [ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
1170
- [ANALYTICS_HEADERS.SDK_VERSION]: "1.0.4"
1243
+ [ANALYTICS_HEADERS.SDK_VERSION]: "1.0.6"
1171
1244
  // Replaced at build time
1172
1245
  }
1173
1246
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phantom/react-native-sdk",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Phantom Wallet SDK for React Native and Expo applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -45,16 +45,16 @@
45
45
  "directory": "packages/react-native-sdk"
46
46
  },
47
47
  "dependencies": {
48
- "@phantom/api-key-stamper": "^1.0.4",
49
- "@phantom/auth2": "^1.0.0",
50
- "@phantom/base64url": "^1.0.4",
51
- "@phantom/chain-interfaces": "^1.0.4",
52
- "@phantom/client": "^1.0.4",
53
- "@phantom/constants": "^1.0.4",
54
- "@phantom/crypto": "^1.0.4",
55
- "@phantom/embedded-provider-core": "^1.0.4",
56
- "@phantom/sdk-types": "^1.0.4",
57
- "@phantom/wallet-sdk-ui": "^1.0.4",
48
+ "@phantom/api-key-stamper": "^1.0.6",
49
+ "@phantom/auth2": "^1.0.2",
50
+ "@phantom/base64url": "^1.0.6",
51
+ "@phantom/chain-interfaces": "^1.0.6",
52
+ "@phantom/client": "^1.0.6",
53
+ "@phantom/constants": "^1.0.6",
54
+ "@phantom/crypto": "^1.0.6",
55
+ "@phantom/embedded-provider-core": "^1.0.6",
56
+ "@phantom/sdk-types": "^1.0.6",
57
+ "@phantom/wallet-sdk-ui": "^1.0.6",
58
58
  "@types/bs58": "^5.0.0",
59
59
  "bs58": "^6.0.0",
60
60
  "buffer": "^6.0.3"