@phantom/react-native-sdk 1.0.7 → 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.d.ts +1 -5
- package/dist/index.js +107 -549
- package/dist/index.mjs +99 -540
- package/package.json +11 -11
package/dist/index.mjs
CHANGED
|
@@ -3,9 +3,10 @@ import { useState as useState5, useEffect as useEffect2, useMemo as useMemo2, us
|
|
|
3
3
|
import { EmbeddedProvider } from "@phantom/embedded-provider-core";
|
|
4
4
|
import {
|
|
5
5
|
ANALYTICS_HEADERS,
|
|
6
|
+
DEFAULT_AUTH_API_BASE_URL,
|
|
6
7
|
DEFAULT_WALLET_API_URL,
|
|
7
8
|
DEFAULT_EMBEDDED_WALLET_TYPE,
|
|
8
|
-
DEFAULT_AUTH_URL
|
|
9
|
+
DEFAULT_AUTH_URL
|
|
9
10
|
} from "@phantom/constants";
|
|
10
11
|
import { ThemeProvider, darkTheme } from "@phantom/wallet-sdk-ui";
|
|
11
12
|
|
|
@@ -488,115 +489,13 @@ var ExpoSecureStorage = class {
|
|
|
488
489
|
}
|
|
489
490
|
};
|
|
490
491
|
|
|
491
|
-
// src/providers/embedded/auth.ts
|
|
492
|
-
import * as WebBrowser from "expo-web-browser";
|
|
493
|
-
import { Platform } from "react-native";
|
|
494
|
-
import { DEFAULT_AUTH_URL } from "@phantom/constants";
|
|
495
|
-
var ExpoAuthProvider = class {
|
|
496
|
-
async authenticate(options) {
|
|
497
|
-
if ("jwtToken" in options) {
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
const phantomOptions = options;
|
|
501
|
-
const { authUrl, redirectUrl, publicKey, sessionId, provider, appId } = phantomOptions;
|
|
502
|
-
if (!redirectUrl) {
|
|
503
|
-
throw new Error("redirectUrl is required for web browser authentication");
|
|
504
|
-
}
|
|
505
|
-
if (!publicKey || !sessionId || !appId) {
|
|
506
|
-
throw new Error("publicKey, sessionId and appId are required for authentication");
|
|
507
|
-
}
|
|
508
|
-
try {
|
|
509
|
-
const baseUrl = authUrl || DEFAULT_AUTH_URL;
|
|
510
|
-
const params = new URLSearchParams({
|
|
511
|
-
public_key: publicKey,
|
|
512
|
-
app_id: appId,
|
|
513
|
-
redirect_uri: redirectUrl,
|
|
514
|
-
session_id: sessionId,
|
|
515
|
-
// OAuth session management - defaults to allow refresh unless explicitly clearing after logout
|
|
516
|
-
clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
|
|
517
|
-
allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
|
|
518
|
-
sdk_version: "1.0.7",
|
|
519
|
-
sdk_type: "react-native",
|
|
520
|
-
platform: Platform.OS
|
|
521
|
-
});
|
|
522
|
-
if (provider) {
|
|
523
|
-
console.log("[ExpoAuthProvider] Provider specified, will skip selection", { provider });
|
|
524
|
-
params.append("provider", provider);
|
|
525
|
-
} else {
|
|
526
|
-
console.log("[ExpoAuthProvider] No provider specified, defaulting to Google");
|
|
527
|
-
params.append("provider", "google");
|
|
528
|
-
}
|
|
529
|
-
const fullAuthUrl = `${baseUrl}?${params.toString()}`;
|
|
530
|
-
console.log("[ExpoAuthProvider] Starting authentication", {
|
|
531
|
-
baseUrl,
|
|
532
|
-
redirectUrl,
|
|
533
|
-
publicKey,
|
|
534
|
-
sessionId,
|
|
535
|
-
provider
|
|
536
|
-
});
|
|
537
|
-
await WebBrowser.warmUpAsync();
|
|
538
|
-
const result = await WebBrowser.openAuthSessionAsync(fullAuthUrl, redirectUrl, {
|
|
539
|
-
// Use system browser on iOS for ASWebAuthenticationSession
|
|
540
|
-
preferEphemeralSession: false
|
|
541
|
-
});
|
|
542
|
-
console.log("[ExpoAuthProvider] Authentication result", {
|
|
543
|
-
type: result.type,
|
|
544
|
-
url: result.type === "success" && result.url ? result.url.substring(0, 100) + "..." : void 0
|
|
545
|
-
});
|
|
546
|
-
if (result.type === "success" && result.url) {
|
|
547
|
-
const url = new URL(result.url);
|
|
548
|
-
const walletId = url.searchParams.get("wallet_id");
|
|
549
|
-
const organizationId = url.searchParams.get("organization_id");
|
|
550
|
-
const accountDerivationIndex = url.searchParams.get("selected_account_index");
|
|
551
|
-
const expiresInMs = url.searchParams.get("expires_in_ms");
|
|
552
|
-
const authUserId = url.searchParams.get("auth_user_id");
|
|
553
|
-
if (!walletId) {
|
|
554
|
-
throw new Error("Authentication failed: no walletId in redirect URL");
|
|
555
|
-
}
|
|
556
|
-
if (!organizationId) {
|
|
557
|
-
console.error("[ExpoAuthProvider] Missing organizationId in redirect URL", { url: result.url });
|
|
558
|
-
throw new Error("Authentication failed: no organizationId in redirect URL");
|
|
559
|
-
}
|
|
560
|
-
console.log("[ExpoAuthProvider] Auth redirect parameters", {
|
|
561
|
-
walletId,
|
|
562
|
-
organizationId,
|
|
563
|
-
provider,
|
|
564
|
-
accountDerivationIndex,
|
|
565
|
-
expiresInMs,
|
|
566
|
-
authUserId
|
|
567
|
-
});
|
|
568
|
-
return {
|
|
569
|
-
walletId,
|
|
570
|
-
organizationId,
|
|
571
|
-
provider: provider || void 0,
|
|
572
|
-
accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : 0,
|
|
573
|
-
expiresInMs: expiresInMs ? parseInt(expiresInMs) : 0,
|
|
574
|
-
authUserId: authUserId || void 0
|
|
575
|
-
};
|
|
576
|
-
} else if (result.type === "cancel") {
|
|
577
|
-
throw new Error("User cancelled authentication");
|
|
578
|
-
} else {
|
|
579
|
-
throw new Error("Authentication failed");
|
|
580
|
-
}
|
|
581
|
-
} catch (error) {
|
|
582
|
-
console.error("[ExpoAuthProvider] Authentication error", error);
|
|
583
|
-
throw error;
|
|
584
|
-
} finally {
|
|
585
|
-
await WebBrowser.coolDownAsync();
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
isAvailable() {
|
|
589
|
-
return Promise.resolve(true);
|
|
590
|
-
}
|
|
591
|
-
};
|
|
592
|
-
|
|
593
492
|
// src/providers/embedded/ExpoAuth2AuthProvider.ts
|
|
594
|
-
import * as
|
|
493
|
+
import * as WebBrowser from "expo-web-browser";
|
|
595
494
|
import {
|
|
596
|
-
createCodeVerifier,
|
|
597
|
-
exchangeAuthCode,
|
|
598
495
|
Auth2KmsRpcClient,
|
|
599
|
-
|
|
496
|
+
prepareAuth2Flow,
|
|
497
|
+
validateAuth2Callback,
|
|
498
|
+
completeAuth2Exchange
|
|
600
499
|
} from "@phantom/auth2";
|
|
601
500
|
var ExpoAuth2AuthProvider = class {
|
|
602
501
|
constructor(stamper, auth2ProviderOptions, kmsClientOptions) {
|
|
@@ -612,246 +511,111 @@ var ExpoAuth2AuthProvider = class {
|
|
|
612
511
|
* so the token exchange and KMS calls all happen here before returning AuthResult.
|
|
613
512
|
*/
|
|
614
513
|
async authenticate(options) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const keyPair = this.stamper.getCryptoKeyPair();
|
|
619
|
-
if (!keyPair) {
|
|
620
|
-
throw new Error("Stamper key pair not found.");
|
|
621
|
-
}
|
|
622
|
-
const codeVerifier = createCodeVerifier();
|
|
623
|
-
const url = await createConnectStartUrl({
|
|
624
|
-
keyPair,
|
|
625
|
-
connectLoginUrl: this.auth2ProviderOptions.connectLoginUrl,
|
|
626
|
-
clientId: this.auth2ProviderOptions.clientId,
|
|
627
|
-
redirectUri: this.auth2ProviderOptions.redirectUri,
|
|
514
|
+
const { url, codeVerifier } = await prepareAuth2Flow({
|
|
515
|
+
stamper: this.stamper,
|
|
516
|
+
auth2Options: this.auth2ProviderOptions,
|
|
628
517
|
sessionId: options.sessionId,
|
|
629
|
-
provider: options.provider
|
|
630
|
-
codeVerifier,
|
|
631
|
-
// The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
|
|
632
|
-
salt: ""
|
|
518
|
+
provider: options.provider
|
|
633
519
|
});
|
|
634
|
-
await
|
|
520
|
+
await WebBrowser.warmUpAsync();
|
|
635
521
|
let result;
|
|
636
522
|
try {
|
|
637
|
-
result = await
|
|
523
|
+
result = await WebBrowser.openAuthSessionAsync(url, this.auth2ProviderOptions.redirectUri);
|
|
638
524
|
} finally {
|
|
639
|
-
await
|
|
525
|
+
await WebBrowser.coolDownAsync();
|
|
640
526
|
}
|
|
641
527
|
if (!result.url) {
|
|
642
528
|
throw new Error("Authentication failed");
|
|
643
529
|
}
|
|
644
530
|
const callbackUrl = new URL(result.url);
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
const code = callbackUrl.searchParams.get("code");
|
|
655
|
-
if (!code) {
|
|
656
|
-
throw new Error("Auth2 callback missing authorization code");
|
|
657
|
-
}
|
|
658
|
-
const { idToken, bearerToken, authUserId, expiresInMs, refreshToken } = await exchangeAuthCode({
|
|
659
|
-
authApiBaseUrl: this.auth2ProviderOptions.authApiBaseUrl,
|
|
660
|
-
clientId: this.auth2ProviderOptions.clientId,
|
|
661
|
-
redirectUri: this.auth2ProviderOptions.redirectUri,
|
|
531
|
+
const code = validateAuth2Callback({
|
|
532
|
+
getParam: (key) => callbackUrl.searchParams.get(key),
|
|
533
|
+
expectedSessionId: options.sessionId
|
|
534
|
+
});
|
|
535
|
+
return completeAuth2Exchange({
|
|
536
|
+
stamper: this.stamper,
|
|
537
|
+
kms: this.kms,
|
|
538
|
+
auth2Options: this.auth2ProviderOptions,
|
|
662
539
|
code,
|
|
663
|
-
codeVerifier
|
|
540
|
+
codeVerifier,
|
|
541
|
+
provider: options.provider
|
|
664
542
|
});
|
|
665
|
-
await this.stamper.setTokens({ idToken, bearerToken, refreshToken, expiresInMs });
|
|
666
|
-
const { organizationId, walletId } = await this.kms.discoverOrganizationAndWalletId(bearerToken, authUserId);
|
|
667
|
-
return {
|
|
668
|
-
walletId,
|
|
669
|
-
organizationId,
|
|
670
|
-
provider: options.provider,
|
|
671
|
-
accountDerivationIndex: 0,
|
|
672
|
-
// discoverWalletId uses derivation index of 0.
|
|
673
|
-
expiresInMs,
|
|
674
|
-
authUserId,
|
|
675
|
-
bearerToken
|
|
676
|
-
};
|
|
677
543
|
}
|
|
678
544
|
};
|
|
679
545
|
|
|
680
|
-
// src/
|
|
546
|
+
// src/PhantomProvider.tsx
|
|
547
|
+
import { Auth2Stamper } from "@phantom/auth2";
|
|
548
|
+
|
|
549
|
+
// src/providers/embedded/SecureStoreAuth2StamperStorage.ts
|
|
681
550
|
import * as SecureStore2 from "expo-secure-store";
|
|
682
551
|
import bs58 from "bs58";
|
|
683
552
|
import { Buffer } from "buffer";
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
import { refreshToken as refreshTokenRequest } from "@phantom/auth2";
|
|
687
|
-
import { TOKEN_REFRESH_BUFFER_MS } from "@phantom/constants";
|
|
688
|
-
var ExpoAuth2Stamper = class {
|
|
689
|
-
/**
|
|
690
|
-
* @param storageKey - expo-secure-store key used to persist the P-256 private key.
|
|
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.
|
|
694
|
-
*/
|
|
695
|
-
constructor(storageKey, refreshConfig) {
|
|
553
|
+
var SecureStoreAuth2StamperStorage = class {
|
|
554
|
+
constructor(storageKey) {
|
|
696
555
|
this.storageKey = storageKey;
|
|
697
|
-
this.
|
|
698
|
-
this._keyPair = null;
|
|
699
|
-
this._keyInfo = null;
|
|
700
|
-
this._idToken = null;
|
|
701
|
-
this._bearerToken = null;
|
|
702
|
-
this._refreshToken = null;
|
|
703
|
-
this._tokenExpiresAt = null;
|
|
704
|
-
this.algorithm = Algorithm.secp256r1;
|
|
705
|
-
this.type = "OIDC";
|
|
556
|
+
this.requiresExtractableKeys = true;
|
|
706
557
|
}
|
|
707
|
-
async
|
|
708
|
-
const
|
|
709
|
-
if (
|
|
710
|
-
|
|
711
|
-
privateKey: await this.importPrivateKey(stored.privateKeyPkcs8),
|
|
712
|
-
publicKey: await this.importPublicKeyFromBase58(stored.keyInfo.publicKey)
|
|
713
|
-
};
|
|
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
|
-
}
|
|
727
|
-
return this._keyInfo;
|
|
558
|
+
async load() {
|
|
559
|
+
const raw = await SecureStore2.getItemAsync(this.storageKey);
|
|
560
|
+
if (raw === null) {
|
|
561
|
+
return null;
|
|
728
562
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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);
|
|
563
|
+
let record;
|
|
564
|
+
try {
|
|
565
|
+
record = JSON.parse(raw);
|
|
566
|
+
} catch (err) {
|
|
567
|
+
await SecureStore2.deleteItemAsync(this.storageKey);
|
|
568
|
+
throw new Error(`SecureStoreAuth2StamperStorage: corrupt stored record (JSON parse failed): ${err}`);
|
|
750
569
|
}
|
|
751
|
-
|
|
752
|
-
|
|
570
|
+
let privateKey;
|
|
571
|
+
let publicKey;
|
|
572
|
+
try {
|
|
573
|
+
privateKey = await this.importPrivateKey(record.privateKeyPkcs8);
|
|
574
|
+
publicKey = await this.importPublicKeyFromBase58(record.keyInfo.publicKey);
|
|
575
|
+
} catch (err) {
|
|
576
|
+
await SecureStore2.deleteItemAsync(this.storageKey);
|
|
577
|
+
throw new Error(`SecureStoreAuth2StamperStorage: corrupt stored record (key import failed): ${err}`);
|
|
753
578
|
}
|
|
754
579
|
return {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
580
|
+
keyPair: { privateKey, publicKey },
|
|
581
|
+
keyInfo: record.keyInfo,
|
|
582
|
+
accessToken: record.accessToken,
|
|
583
|
+
idType: record.idType,
|
|
584
|
+
refreshToken: record.refreshToken,
|
|
585
|
+
tokenExpiresAt: record.tokenExpiresAt
|
|
758
586
|
};
|
|
759
587
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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
|
-
}
|
|
791
|
-
}
|
|
792
|
-
async stamp(params) {
|
|
793
|
-
if (!this._keyPair || !this._keyInfo || this._idToken === null) {
|
|
794
|
-
throw new Error("ExpoAuth2Stamper not initialized. Call init() first.");
|
|
795
|
-
}
|
|
796
|
-
const signatureRaw = await crypto.subtle.sign(
|
|
797
|
-
{ name: "ECDSA", hash: "SHA-256" },
|
|
798
|
-
this._keyPair.privateKey,
|
|
799
|
-
new Uint8Array(params.data)
|
|
800
|
-
);
|
|
801
|
-
const rawPublicKey = bs58.decode(this._keyInfo.publicKey);
|
|
802
|
-
const stampData = {
|
|
803
|
-
kind: this.type,
|
|
804
|
-
idToken: this._idToken,
|
|
805
|
-
publicKey: base64urlEncode(rawPublicKey),
|
|
806
|
-
algorithm: this.algorithm,
|
|
807
|
-
// The P-256 ephemeral key is unique per wallet, so no additional salt is needed.
|
|
808
|
-
salt: "",
|
|
809
|
-
signature: base64urlEncode(new Uint8Array(signatureRaw))
|
|
588
|
+
async save(record) {
|
|
589
|
+
const pkcs8Buffer = await crypto.subtle.exportKey("pkcs8", record.keyPair.privateKey);
|
|
590
|
+
const privateKeyPkcs8 = Buffer.from(pkcs8Buffer).toString("base64");
|
|
591
|
+
const serialized = {
|
|
592
|
+
privateKeyPkcs8,
|
|
593
|
+
keyInfo: record.keyInfo,
|
|
594
|
+
accessToken: record.accessToken,
|
|
595
|
+
idType: record.idType,
|
|
596
|
+
refreshToken: record.refreshToken,
|
|
597
|
+
tokenExpiresAt: record.tokenExpiresAt
|
|
810
598
|
};
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
await this.clear();
|
|
815
|
-
return this.generateAndStore();
|
|
599
|
+
await SecureStore2.setItemAsync(this.storageKey, JSON.stringify(serialized), {
|
|
600
|
+
requireAuthentication: false
|
|
601
|
+
});
|
|
816
602
|
}
|
|
817
603
|
async clear() {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
this._idToken = null;
|
|
822
|
-
this._bearerToken = null;
|
|
823
|
-
this._refreshToken = null;
|
|
824
|
-
this._tokenExpiresAt = null;
|
|
825
|
-
}
|
|
826
|
-
// Auth2 doesn't use key rotation; minimal no-op implementations.
|
|
827
|
-
async rotateKeyPair() {
|
|
828
|
-
return this.init();
|
|
829
|
-
}
|
|
830
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
831
|
-
async commitRotation(authenticatorId) {
|
|
832
|
-
if (this._keyInfo) {
|
|
833
|
-
this._keyInfo.authenticatorId = authenticatorId;
|
|
604
|
+
try {
|
|
605
|
+
await SecureStore2.deleteItemAsync(this.storageKey);
|
|
606
|
+
} catch {
|
|
834
607
|
}
|
|
835
608
|
}
|
|
836
|
-
async
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
609
|
+
async importPrivateKey(pkcs8Base64) {
|
|
610
|
+
const pkcs8Bytes = Buffer.from(pkcs8Base64, "base64");
|
|
611
|
+
return crypto.subtle.importKey(
|
|
612
|
+
"pkcs8",
|
|
613
|
+
pkcs8Bytes,
|
|
840
614
|
{ name: "ECDSA", namedCurve: "P-256" },
|
|
841
|
-
|
|
842
|
-
// extractable
|
|
843
|
-
["sign"
|
|
615
|
+
this.requiresExtractableKeys,
|
|
616
|
+
// extractable so save() can re-export via pkcs8
|
|
617
|
+
["sign"]
|
|
844
618
|
);
|
|
845
|
-
const rawPublicKey = new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey));
|
|
846
|
-
const publicKeyBase58 = bs58.encode(rawPublicKey);
|
|
847
|
-
const keyIdBuffer = await crypto.subtle.digest("SHA-256", rawPublicKey.buffer);
|
|
848
|
-
const keyId = base64urlEncode(new Uint8Array(keyIdBuffer)).substring(0, 16);
|
|
849
|
-
this._keyPair = keyPair;
|
|
850
|
-
this._keyInfo = { keyId, publicKey: publicKeyBase58, createdAt: Date.now() };
|
|
851
|
-
const pkcs8Buffer = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
|
|
852
|
-
const privateKeyPkcs8 = Buffer.from(pkcs8Buffer).toString("base64");
|
|
853
|
-
await this.storeRecord({ privateKeyPkcs8, keyInfo: this._keyInfo });
|
|
854
|
-
return this._keyInfo;
|
|
855
619
|
}
|
|
856
620
|
async importPublicKeyFromBase58(base58PublicKey) {
|
|
857
621
|
const rawBytes = bs58.decode(base58PublicKey);
|
|
@@ -864,34 +628,6 @@ var ExpoAuth2Stamper = class {
|
|
|
864
628
|
["verify"]
|
|
865
629
|
);
|
|
866
630
|
}
|
|
867
|
-
async importPrivateKey(pkcs8Base64) {
|
|
868
|
-
const pkcs8Bytes = Buffer.from(pkcs8Base64, "base64");
|
|
869
|
-
return crypto.subtle.importKey(
|
|
870
|
-
"pkcs8",
|
|
871
|
-
pkcs8Bytes,
|
|
872
|
-
{ name: "ECDSA", namedCurve: "P-256" },
|
|
873
|
-
false,
|
|
874
|
-
// non-extractable once loaded into memory
|
|
875
|
-
["sign"]
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
async loadRecord() {
|
|
879
|
-
try {
|
|
880
|
-
const raw = await SecureStore2.getItemAsync(this.storageKey);
|
|
881
|
-
return raw ? JSON.parse(raw) : null;
|
|
882
|
-
} catch {
|
|
883
|
-
return null;
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
async storeRecord(record) {
|
|
887
|
-
await SecureStore2.setItemAsync(this.storageKey, JSON.stringify(record), { requireAuthentication: false });
|
|
888
|
-
}
|
|
889
|
-
async clearStoredRecord() {
|
|
890
|
-
try {
|
|
891
|
-
await SecureStore2.deleteItemAsync(this.storageKey);
|
|
892
|
-
} catch {
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
631
|
};
|
|
896
632
|
|
|
897
633
|
// src/providers/embedded/url-params.ts
|
|
@@ -962,180 +698,6 @@ var ExpoURLParamsAccessor = class {
|
|
|
962
698
|
}
|
|
963
699
|
};
|
|
964
700
|
|
|
965
|
-
// src/providers/embedded/stamper.ts
|
|
966
|
-
import * as SecureStore3 from "expo-secure-store";
|
|
967
|
-
import { ApiKeyStamper } from "@phantom/api-key-stamper";
|
|
968
|
-
import { DEFAULT_AUTHENTICATOR_ALGORITHM } from "@phantom/constants";
|
|
969
|
-
import { generateKeyPair } from "@phantom/crypto";
|
|
970
|
-
import { base64urlEncode as base64urlEncode2 } from "@phantom/base64url";
|
|
971
|
-
var ReactNativeStamper = class {
|
|
972
|
-
// Optional for PKI, required for OIDC
|
|
973
|
-
constructor(config = {}) {
|
|
974
|
-
this.activeKeyRecord = null;
|
|
975
|
-
this.pendingKeyRecord = null;
|
|
976
|
-
this.algorithm = DEFAULT_AUTHENTICATOR_ALGORITHM;
|
|
977
|
-
this.type = "PKI";
|
|
978
|
-
this.keyPrefix = config.keyPrefix || "phantom-rn-stamper";
|
|
979
|
-
this.appId = config.appId || "default";
|
|
980
|
-
}
|
|
981
|
-
/**
|
|
982
|
-
* Initialize the stamper and generate/load cryptographic keys
|
|
983
|
-
*/
|
|
984
|
-
async init() {
|
|
985
|
-
this.activeKeyRecord = await this.loadActiveKeyRecord();
|
|
986
|
-
if (!this.activeKeyRecord) {
|
|
987
|
-
this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
|
|
988
|
-
}
|
|
989
|
-
this.pendingKeyRecord = await this.loadPendingKeyRecord();
|
|
990
|
-
return this.activeKeyRecord.keyInfo;
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Get the current key information
|
|
994
|
-
*/
|
|
995
|
-
getKeyInfo() {
|
|
996
|
-
return this.activeKeyRecord?.keyInfo || null;
|
|
997
|
-
}
|
|
998
|
-
/**
|
|
999
|
-
* Generate and store a new key pair, replacing any existing keys
|
|
1000
|
-
*/
|
|
1001
|
-
async resetKeyPair() {
|
|
1002
|
-
await this.clear();
|
|
1003
|
-
this.activeKeyRecord = await this.generateAndStoreNewKeyRecord("active");
|
|
1004
|
-
this.pendingKeyRecord = null;
|
|
1005
|
-
return this.activeKeyRecord.keyInfo;
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* Create X-Phantom-Stamp header value using stored secret key
|
|
1009
|
-
* @param params - Parameters object with data to sign and optional override params
|
|
1010
|
-
* @returns Complete X-Phantom-Stamp header value
|
|
1011
|
-
*/
|
|
1012
|
-
async stamp(params) {
|
|
1013
|
-
if (!this.activeKeyRecord) {
|
|
1014
|
-
throw new Error("Stamper not initialized. Call init() first.");
|
|
1015
|
-
}
|
|
1016
|
-
const apiKeyStamper = new ApiKeyStamper({ apiSecretKey: this.activeKeyRecord.secretKey });
|
|
1017
|
-
return await apiKeyStamper.stamp(params);
|
|
1018
|
-
}
|
|
1019
|
-
/**
|
|
1020
|
-
* Clear all stored keys from SecureStore
|
|
1021
|
-
*/
|
|
1022
|
-
async clear() {
|
|
1023
|
-
const activeKey = this.getActiveKeyName();
|
|
1024
|
-
const pendingKey = this.getPendingKeyName();
|
|
1025
|
-
try {
|
|
1026
|
-
await SecureStore3.deleteItemAsync(activeKey);
|
|
1027
|
-
} catch (error) {
|
|
1028
|
-
}
|
|
1029
|
-
try {
|
|
1030
|
-
await SecureStore3.deleteItemAsync(pendingKey);
|
|
1031
|
-
} catch (error) {
|
|
1032
|
-
}
|
|
1033
|
-
this.activeKeyRecord = null;
|
|
1034
|
-
this.pendingKeyRecord = null;
|
|
1035
|
-
}
|
|
1036
|
-
/**
|
|
1037
|
-
* Generate a new keypair for rotation without making it active
|
|
1038
|
-
*/
|
|
1039
|
-
async rotateKeyPair() {
|
|
1040
|
-
this.pendingKeyRecord = await this.generateAndStoreNewKeyRecord("pending");
|
|
1041
|
-
return this.pendingKeyRecord.keyInfo;
|
|
1042
|
-
}
|
|
1043
|
-
/**
|
|
1044
|
-
* Switch to the pending keypair, making it active and cleaning up the old one
|
|
1045
|
-
*/
|
|
1046
|
-
async commitRotation(authenticatorId) {
|
|
1047
|
-
if (!this.pendingKeyRecord) {
|
|
1048
|
-
throw new Error("No pending keypair to commit");
|
|
1049
|
-
}
|
|
1050
|
-
if (this.activeKeyRecord) {
|
|
1051
|
-
try {
|
|
1052
|
-
await SecureStore3.deleteItemAsync(this.getActiveKeyName());
|
|
1053
|
-
} catch (error) {
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
this.pendingKeyRecord.status = "active";
|
|
1057
|
-
this.pendingKeyRecord.authenticatorId = authenticatorId;
|
|
1058
|
-
this.pendingKeyRecord.keyInfo.authenticatorId = authenticatorId;
|
|
1059
|
-
this.activeKeyRecord = this.pendingKeyRecord;
|
|
1060
|
-
this.pendingKeyRecord = null;
|
|
1061
|
-
await this.storeKeyRecord(this.activeKeyRecord, "active");
|
|
1062
|
-
try {
|
|
1063
|
-
await SecureStore3.deleteItemAsync(this.getPendingKeyName());
|
|
1064
|
-
} catch (error) {
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
/**
|
|
1068
|
-
* Discard the pending keypair on rotation failure
|
|
1069
|
-
*/
|
|
1070
|
-
async rollbackRotation() {
|
|
1071
|
-
if (!this.pendingKeyRecord) {
|
|
1072
|
-
return;
|
|
1073
|
-
}
|
|
1074
|
-
try {
|
|
1075
|
-
await SecureStore3.deleteItemAsync(this.getPendingKeyName());
|
|
1076
|
-
} catch (error) {
|
|
1077
|
-
}
|
|
1078
|
-
this.pendingKeyRecord = null;
|
|
1079
|
-
}
|
|
1080
|
-
async generateAndStoreNewKeyRecord(type) {
|
|
1081
|
-
const keypair = generateKeyPair();
|
|
1082
|
-
const keyId = this.createKeyId(keypair.publicKey);
|
|
1083
|
-
const now = Date.now();
|
|
1084
|
-
const keyInfo = {
|
|
1085
|
-
keyId,
|
|
1086
|
-
publicKey: keypair.publicKey,
|
|
1087
|
-
createdAt: now
|
|
1088
|
-
};
|
|
1089
|
-
const record = {
|
|
1090
|
-
keyInfo,
|
|
1091
|
-
secretKey: keypair.secretKey,
|
|
1092
|
-
createdAt: now,
|
|
1093
|
-
expiresAt: 0,
|
|
1094
|
-
// Not used anymore, kept for backward compatibility
|
|
1095
|
-
status: type
|
|
1096
|
-
};
|
|
1097
|
-
await this.storeKeyRecord(record, type);
|
|
1098
|
-
return record;
|
|
1099
|
-
}
|
|
1100
|
-
createKeyId(publicKey) {
|
|
1101
|
-
return base64urlEncode2(new TextEncoder().encode(publicKey)).substring(0, 16);
|
|
1102
|
-
}
|
|
1103
|
-
async storeKeyRecord(record, type) {
|
|
1104
|
-
const keyName = type === "active" ? this.getActiveKeyName() : this.getPendingKeyName();
|
|
1105
|
-
await SecureStore3.setItemAsync(keyName, JSON.stringify(record), {
|
|
1106
|
-
requireAuthentication: false
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
async loadActiveKeyRecord() {
|
|
1110
|
-
try {
|
|
1111
|
-
const activeKey = this.getActiveKeyName();
|
|
1112
|
-
const storedRecord = await SecureStore3.getItemAsync(activeKey);
|
|
1113
|
-
if (storedRecord) {
|
|
1114
|
-
return JSON.parse(storedRecord);
|
|
1115
|
-
}
|
|
1116
|
-
} catch (error) {
|
|
1117
|
-
}
|
|
1118
|
-
return null;
|
|
1119
|
-
}
|
|
1120
|
-
async loadPendingKeyRecord() {
|
|
1121
|
-
try {
|
|
1122
|
-
const pendingKey = this.getPendingKeyName();
|
|
1123
|
-
const storedRecord = await SecureStore3.getItemAsync(pendingKey);
|
|
1124
|
-
if (storedRecord) {
|
|
1125
|
-
return JSON.parse(storedRecord);
|
|
1126
|
-
}
|
|
1127
|
-
} catch (error) {
|
|
1128
|
-
}
|
|
1129
|
-
return null;
|
|
1130
|
-
}
|
|
1131
|
-
getActiveKeyName() {
|
|
1132
|
-
return `${this.keyPrefix}-${this.appId}-active`;
|
|
1133
|
-
}
|
|
1134
|
-
getPendingKeyName() {
|
|
1135
|
-
return `${this.keyPrefix}-${this.appId}-pending`;
|
|
1136
|
-
}
|
|
1137
|
-
};
|
|
1138
|
-
|
|
1139
701
|
// src/providers/embedded/logger.ts
|
|
1140
702
|
var ExpoLogger = class {
|
|
1141
703
|
constructor(enabled = false) {
|
|
@@ -1178,7 +740,7 @@ var ReactNativePhantomAppProvider = class {
|
|
|
1178
740
|
};
|
|
1179
741
|
|
|
1180
742
|
// src/PhantomProvider.tsx
|
|
1181
|
-
import { Platform
|
|
743
|
+
import { Platform } from "react-native";
|
|
1182
744
|
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
1183
745
|
function PhantomProvider({ children, config, debugConfig, theme, appIcon, appName }) {
|
|
1184
746
|
const [isConnected, setIsConnected] = useState5(false);
|
|
@@ -1194,9 +756,9 @@ function PhantomProvider({ children, config, debugConfig, theme, appIcon, appNam
|
|
|
1194
756
|
apiBaseUrl: config.apiBaseUrl || DEFAULT_WALLET_API_URL,
|
|
1195
757
|
embeddedWalletType: config.embeddedWalletType || DEFAULT_EMBEDDED_WALLET_TYPE,
|
|
1196
758
|
authOptions: {
|
|
1197
|
-
...config.authOptions || {},
|
|
1198
759
|
redirectUrl,
|
|
1199
|
-
authUrl: config.authOptions?.authUrl ||
|
|
760
|
+
authUrl: config.authOptions?.authUrl || DEFAULT_AUTH_URL,
|
|
761
|
+
authApiBaseUrl: config.authOptions?.authApiBaseUrl || DEFAULT_AUTH_API_BASE_URL
|
|
1200
762
|
}
|
|
1201
763
|
};
|
|
1202
764
|
}, [config]);
|
|
@@ -1204,28 +766,25 @@ function PhantomProvider({ children, config, debugConfig, theme, appIcon, appNam
|
|
|
1204
766
|
const storage = new ExpoSecureStorage();
|
|
1205
767
|
const urlParamsAccessor = new ExpoURLParamsAccessor();
|
|
1206
768
|
const logger = new ExpoLogger(debugConfig?.enabled || false);
|
|
1207
|
-
const stamper =
|
|
1208
|
-
authApiBaseUrl:
|
|
1209
|
-
clientId:
|
|
1210
|
-
redirectUri:
|
|
1211
|
-
}) : new ReactNativeStamper({
|
|
1212
|
-
keyPrefix: `phantom-rn-${memoizedConfig.appId}`,
|
|
1213
|
-
appId: memoizedConfig.appId
|
|
769
|
+
const stamper = new Auth2Stamper(new SecureStoreAuth2StamperStorage(`phantom-auth2-${memoizedConfig.appId}`), {
|
|
770
|
+
authApiBaseUrl: memoizedConfig.authOptions.authApiBaseUrl,
|
|
771
|
+
clientId: memoizedConfig.appId,
|
|
772
|
+
redirectUri: memoizedConfig.authOptions.redirectUrl
|
|
1214
773
|
});
|
|
1215
|
-
const authProvider =
|
|
774
|
+
const authProvider = new ExpoAuth2AuthProvider(
|
|
1216
775
|
stamper,
|
|
1217
776
|
{
|
|
1218
|
-
redirectUri:
|
|
1219
|
-
connectLoginUrl:
|
|
1220
|
-
clientId:
|
|
1221
|
-
authApiBaseUrl:
|
|
777
|
+
redirectUri: memoizedConfig.authOptions.redirectUrl,
|
|
778
|
+
connectLoginUrl: memoizedConfig.authOptions.authUrl,
|
|
779
|
+
clientId: memoizedConfig.appId,
|
|
780
|
+
authApiBaseUrl: memoizedConfig.authOptions.authApiBaseUrl
|
|
1222
781
|
},
|
|
1223
782
|
{
|
|
1224
|
-
apiBaseUrl:
|
|
1225
|
-
appId:
|
|
783
|
+
apiBaseUrl: memoizedConfig.apiBaseUrl,
|
|
784
|
+
appId: memoizedConfig.appId
|
|
1226
785
|
}
|
|
1227
|
-
)
|
|
1228
|
-
const platformName = `${
|
|
786
|
+
);
|
|
787
|
+
const platformName = `${Platform.OS}-${Platform.Version}`;
|
|
1229
788
|
const platform = {
|
|
1230
789
|
storage,
|
|
1231
790
|
authProvider,
|
|
@@ -1236,11 +795,11 @@ function PhantomProvider({ children, config, debugConfig, theme, appIcon, appNam
|
|
|
1236
795
|
analyticsHeaders: {
|
|
1237
796
|
[ANALYTICS_HEADERS.SDK_TYPE]: "react-native",
|
|
1238
797
|
[ANALYTICS_HEADERS.PLATFORM]: "ext-sdk",
|
|
1239
|
-
[ANALYTICS_HEADERS.PLATFORM_VERSION]: `${
|
|
1240
|
-
[ANALYTICS_HEADERS.CLIENT]:
|
|
798
|
+
[ANALYTICS_HEADERS.PLATFORM_VERSION]: `${Platform.Version}`,
|
|
799
|
+
[ANALYTICS_HEADERS.CLIENT]: Platform.OS,
|
|
1241
800
|
[ANALYTICS_HEADERS.APP_ID]: config.appId,
|
|
1242
801
|
[ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
|
|
1243
|
-
[ANALYTICS_HEADERS.SDK_VERSION]: "
|
|
802
|
+
[ANALYTICS_HEADERS.SDK_VERSION]: "2.0.0-beta.0"
|
|
1244
803
|
// Replaced at build time
|
|
1245
804
|
}
|
|
1246
805
|
};
|
|
@@ -1355,14 +914,14 @@ function useEthereum() {
|
|
|
1355
914
|
// src/index.ts
|
|
1356
915
|
import { AddressType } from "@phantom/client";
|
|
1357
916
|
import { NetworkId } from "@phantom/constants";
|
|
1358
|
-
import { base64urlEncode
|
|
917
|
+
import { base64urlEncode, base64urlDecode } from "@phantom/base64url";
|
|
1359
918
|
import { darkTheme as darkTheme2, lightTheme } from "@phantom/wallet-sdk-ui";
|
|
1360
919
|
export {
|
|
1361
920
|
AddressType,
|
|
1362
921
|
NetworkId,
|
|
1363
922
|
PhantomProvider,
|
|
1364
923
|
base64urlDecode,
|
|
1365
|
-
|
|
924
|
+
base64urlEncode,
|
|
1366
925
|
darkTheme2 as darkTheme,
|
|
1367
926
|
lightTheme,
|
|
1368
927
|
useAccounts,
|