@crossmint/client-sdk-react-ui 1.7.1 → 1.8.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.
@@ -0,0 +1,65 @@
1
+ import type { EmbeddedCheckoutV3IFrameEmitter } from "@crossmint/client-sdk-base";
2
+ import {
3
+ type BlockchainIncludingTestnet,
4
+ blockchainToChainId,
5
+ type EVMBlockchainIncludingTestnet,
6
+ } from "@crossmint/common-sdk-base";
7
+ import type { EthereumWallet } from "@dynamic-labs/ethereum-core";
8
+ import { parseTransaction, type TransactionSerializableEIP1559 } from "viem";
9
+
10
+ export async function handleEvmTransaction({
11
+ primaryWallet,
12
+ chain,
13
+ serializedTransaction,
14
+ iframeClient,
15
+ }: {
16
+ primaryWallet: EthereumWallet;
17
+ chain: BlockchainIncludingTestnet;
18
+ serializedTransaction: string;
19
+ iframeClient: EmbeddedCheckoutV3IFrameEmitter;
20
+ }) {
21
+ try {
22
+ await primaryWallet.switchNetwork(blockchainToChainId(chain as EVMBlockchainIncludingTestnet));
23
+ } catch (error) {
24
+ console.error("[CryptoWalletConnectionHandler] failed to switch network", error);
25
+ iframeClient.send("crypto:send-transaction:failed", {
26
+ error: (error as Error).message,
27
+ });
28
+ return;
29
+ }
30
+
31
+ let walletClient: Awaited<ReturnType<typeof primaryWallet.getWalletClient>>;
32
+ try {
33
+ walletClient = await primaryWallet.getWalletClient();
34
+ } catch (error) {
35
+ console.error("[CryptoWalletConnectionHandler] failed to get wallet client", error);
36
+ iframeClient.send("crypto:send-transaction:failed", {
37
+ error: (error as Error).message,
38
+ });
39
+ return;
40
+ }
41
+
42
+ let parsedTransaction: TransactionSerializableEIP1559;
43
+ try {
44
+ parsedTransaction = parseTransaction(serializedTransaction as `0x${string}`) as TransactionSerializableEIP1559;
45
+ } catch (error) {
46
+ console.error("[CryptoWalletConnectionHandler] failed to parse transaction", error);
47
+ iframeClient.send("crypto:send-transaction:failed", {
48
+ error: (error as Error).message,
49
+ });
50
+ return;
51
+ }
52
+
53
+ try {
54
+ const txId = await walletClient.sendTransaction(parsedTransaction);
55
+ console.log("[CryptoWalletConnectionHandler] txId", txId);
56
+ iframeClient.send("crypto:send-transaction:success", {
57
+ txId,
58
+ });
59
+ } catch (error) {
60
+ console.error("[CryptoWalletConnectionHandler] failed to send transaction", error);
61
+ iframeClient.send("crypto:send-transaction:failed", {
62
+ error: (error as Error).message,
63
+ });
64
+ }
65
+ }
@@ -0,0 +1,31 @@
1
+ import type { Wallet } from "@dynamic-labs/sdk-react-core";
2
+
3
+ import type { BlockchainIncludingTestnet } from "@crossmint/common-sdk-base";
4
+
5
+ import { handleEvmTransaction } from "./handleEvmTransaction";
6
+
7
+ import { isSolanaWallet } from "@dynamic-labs/solana";
8
+ import { handleSolanaTransaction } from "./handleSolanaTransaction";
9
+ import { isEthereumWallet } from "@dynamic-labs/ethereum";
10
+ import type { EmbeddedCheckoutV3IFrameEmitter } from "@crossmint/client-sdk-base";
11
+
12
+ export async function handleSendTransaction(
13
+ primaryWallet: Wallet,
14
+ chain: BlockchainIncludingTestnet,
15
+ serializedTransaction: string,
16
+ iframeClient: EmbeddedCheckoutV3IFrameEmitter
17
+ ) {
18
+ const commonParams = {
19
+ chain,
20
+ serializedTransaction,
21
+ iframeClient,
22
+ };
23
+ if (isSolanaWallet(primaryWallet)) {
24
+ return await handleSolanaTransaction({
25
+ ...commonParams,
26
+ primaryWallet,
27
+ });
28
+ } else if (isEthereumWallet(primaryWallet)) {
29
+ return await handleEvmTransaction({ ...commonParams, primaryWallet });
30
+ }
31
+ }
@@ -0,0 +1,51 @@
1
+ import type { EmbeddedCheckoutV3IFrameEmitter } from "@crossmint/client-sdk-base";
2
+ import type { SolanaWallet } from "@dynamic-labs/solana-core";
3
+ import { Transaction } from "@solana/web3.js";
4
+ import base58 from "bs58";
5
+
6
+ export async function handleSolanaTransaction({
7
+ primaryWallet,
8
+ serializedTransaction,
9
+ iframeClient,
10
+ }: {
11
+ primaryWallet: SolanaWallet;
12
+ serializedTransaction: string;
13
+ iframeClient: EmbeddedCheckoutV3IFrameEmitter;
14
+ }) {
15
+ // TODO: Handle switch network
16
+
17
+ let signer: Awaited<ReturnType<typeof primaryWallet.getSigner>>;
18
+ try {
19
+ signer = await primaryWallet.getSigner();
20
+ } catch (error) {
21
+ console.error("[CryptoWalletConnectionHandler] failed to get signer", error);
22
+ iframeClient.send("crypto:send-transaction:failed", {
23
+ error: "Failed to get signer",
24
+ });
25
+ return;
26
+ }
27
+
28
+ let deserializedTransaction: Transaction;
29
+ try {
30
+ deserializedTransaction = Transaction.from(base58.decode(serializedTransaction));
31
+ } catch (error) {
32
+ console.error("[CryptoWalletConnectionHandler] failed to deserialize transaction", error);
33
+ iframeClient.send("crypto:send-transaction:failed", {
34
+ error: "Failed to deserialize transaction",
35
+ });
36
+ return;
37
+ }
38
+
39
+ try {
40
+ const { signature: txId } = await signer.signAndSendTransaction(deserializedTransaction);
41
+ console.log("[CryptoWalletConnectionHandler] txId", txId);
42
+ iframeClient.send("crypto:send-transaction:success", {
43
+ txId,
44
+ });
45
+ } catch (error) {
46
+ console.error("[CryptoWalletConnectionHandler] failed to send transaction", error);
47
+ iframeClient.send("crypto:send-transaction:failed", {
48
+ error: (error as Error).message,
49
+ });
50
+ }
51
+ }
@@ -0,0 +1 @@
1
+ export * from "./CrossmintEmbeddedCheckoutV3";
@@ -3,3 +3,5 @@ export * from "./CrossmintNFTDetail";
3
3
 
4
4
  export * from "./embed";
5
5
  export * from "./hosted";
6
+
7
+ export * from "./embed/v3";
@@ -1,4 +1,5 @@
1
1
  export * from "./useCrossmint";
2
+ export * from "./useCrossmintCheckout";
2
3
  export * from "./useWallet";
3
4
  export * from "./useAuth";
4
5
  export * from "./useRefreshToken";
@@ -0,0 +1,54 @@
1
+ import { createContext, type ReactNode, useContext, useEffect, useState } from "react";
2
+ import type { embeddedCheckoutV3IncomingEvents } from "@crossmint/client-sdk-base";
3
+ import type { z } from "zod";
4
+ import { useCrossmint } from "./useCrossmint";
5
+ import { createCrossmintApiClient } from "@/utils/createCrossmintApiClient";
6
+
7
+ export interface CrossmintCheckoutContext {
8
+ order?: any;
9
+ orderClientSecret?: string;
10
+ }
11
+
12
+ const CrossmintCheckoutContext = createContext<CrossmintCheckoutContext | undefined>(undefined);
13
+
14
+ export function CrossmintCheckoutProvider({ children }: { children: ReactNode }) {
15
+ const [order, setOrder] = useState<any>();
16
+ const [orderClientSecret, setOrderClientSecret] = useState<string>();
17
+
18
+ const { crossmint } = useCrossmint();
19
+ const apiClient = createCrossmintApiClient(crossmint);
20
+
21
+ useEffect(() => {
22
+ const listener = (event: MessageEvent) => {
23
+ if (event.origin !== new URL(apiClient.baseUrl).origin) {
24
+ return;
25
+ }
26
+ if (event.data.event !== "order:updated") {
27
+ return;
28
+ }
29
+ const { order, orderClientSecret } = event.data.data as z.infer<
30
+ (typeof embeddedCheckoutV3IncomingEvents)["order:updated"]
31
+ >;
32
+ setOrder(order);
33
+ setOrderClientSecret(orderClientSecret);
34
+ };
35
+ window.addEventListener("message", listener);
36
+ return () => {
37
+ window.removeEventListener("message", listener);
38
+ };
39
+ }, [order]);
40
+
41
+ return (
42
+ <CrossmintCheckoutContext.Provider value={{ order, orderClientSecret }}>
43
+ {children}
44
+ </CrossmintCheckoutContext.Provider>
45
+ );
46
+ }
47
+
48
+ export function useCrossmintCheckout() {
49
+ const context = useContext(CrossmintCheckoutContext);
50
+ if (!context) {
51
+ throw new Error("useCrossmintCheckout must be used within a CrossmintCheckoutProvider");
52
+ }
53
+ return context;
54
+ }
@@ -1,11 +1,12 @@
1
1
  import { act, renderHook } from "@testing-library/react";
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
3
 
4
+ import type { AuthMaterialWithUser } from "@crossmint/common-sdk-auth";
4
5
  import { type CrossmintAuthService, getJWTExpiration } from "@crossmint/client-sdk-auth";
5
6
  import { queueTask } from "@crossmint/client-sdk-base";
6
7
 
7
8
  import * as authCookies from "../utils/authCookies";
8
- import { type AuthMaterial, useRefreshToken } from "./useRefreshToken";
9
+ import { useRefreshToken } from "./useRefreshToken";
9
10
 
10
11
  vi.mock("@crossmint/client-sdk-auth", () => ({
11
12
  CrossmintAuthService: vi.fn(),
@@ -17,9 +18,13 @@ vi.mock("../utils/authCookies", () => ({
17
18
  REFRESH_TOKEN_PREFIX: "crossmint-refresh-token",
18
19
  }));
19
20
 
20
- vi.mock("@crossmint/client-sdk-base", () => ({
21
- queueTask: vi.fn(),
22
- }));
21
+ vi.mock("@crossmint/client-sdk-base", async () => {
22
+ const actual = await vi.importActual("@crossmint/client-sdk-base");
23
+ return {
24
+ ...actual,
25
+ queueTask: vi.fn(),
26
+ };
27
+ });
23
28
 
24
29
  describe("useRefreshToken", () => {
25
30
  const mockCrossmintAuthService = {
@@ -59,12 +64,16 @@ describe("useRefreshToken", () => {
59
64
 
60
65
  it("should refresh token if refresh token is present", async () => {
61
66
  const mockRefreshToken = "mock-refresh-token";
62
- const mockAuthMaterial: AuthMaterial = {
63
- jwtToken: "mock-jwt-token",
67
+ const mockAuthMaterial: AuthMaterialWithUser = {
68
+ jwt: "mock-jwt-token",
64
69
  refreshToken: {
65
70
  secret: "mock-secret",
66
71
  expiresAt: "2023-04-01T00:00:00Z",
67
72
  },
73
+ user: {
74
+ id: "123",
75
+ email: "test@test.com",
76
+ },
68
77
  };
69
78
 
70
79
  vi.mocked(authCookies.getCookie).mockReturnValue(mockRefreshToken);
@@ -89,12 +98,16 @@ describe("useRefreshToken", () => {
89
98
 
90
99
  it("should schedule next refresh before token expiration", async () => {
91
100
  const mockRefreshToken = "mock-refresh-token";
92
- const mockAuthMaterial: AuthMaterial = {
93
- jwtToken: "mock-jwt-token",
101
+ const mockAuthMaterial: AuthMaterialWithUser = {
102
+ jwt: "mock-jwt-token",
94
103
  refreshToken: {
95
104
  secret: "mock-secret",
96
105
  expiresAt: "2023-04-01T00:00:00Z",
97
106
  },
107
+ user: {
108
+ id: "123",
109
+ email: "test@test.com",
110
+ },
98
111
  };
99
112
 
100
113
  vi.mocked(authCookies.getCookie).mockReturnValue(mockRefreshToken);
@@ -1,18 +1,19 @@
1
1
  import { useCallback, useEffect, useRef } from "react";
2
2
 
3
- import type { AuthMaterial } from "@crossmint/common-sdk-auth";
3
+ import type { AuthMaterialWithUser } from "@crossmint/common-sdk-auth";
4
4
  import type { CrossmintAuthService } from "@crossmint/client-sdk-auth";
5
5
  import { getJWTExpiration } from "@crossmint/client-sdk-auth";
6
6
  import { queueTask, type CancellableTask } from "@crossmint/client-sdk-base";
7
+ import { REFRESH_TOKEN_PREFIX } from "@crossmint/common-sdk-auth";
7
8
 
8
- import { REFRESH_TOKEN_PREFIX, getCookie } from "../utils/authCookies";
9
+ import { getCookie } from "../utils/authCookies";
9
10
 
10
11
  // 2 minutes before jwt expiration
11
12
  const TIME_BEFORE_EXPIRING_JWT_IN_SECONDS = 120;
12
13
 
13
14
  type UseAuthTokenRefreshProps = {
14
15
  crossmintAuthService: CrossmintAuthService;
15
- setAuthMaterial: (authMaterial: AuthMaterial) => void;
16
+ setAuthMaterial: (authMaterial: AuthMaterialWithUser) => void;
16
17
  logout: () => void;
17
18
  };
18
19
 
@@ -34,7 +35,7 @@ export function useRefreshToken({ crossmintAuthService, setAuthMaterial, logout
34
35
  try {
35
36
  const result = await crossmintAuthService.refreshAuthMaterial(refreshToken);
36
37
  setAuthMaterial(result);
37
- const jwtExpiration = getJWTExpiration(result.jwtToken);
38
+ const jwtExpiration = getJWTExpiration(result.jwt);
38
39
 
39
40
  if (jwtExpiration == null) {
40
41
  throw new Error("Invalid JWT");
@@ -1,4 +1,5 @@
1
- import { deleteCookie, REFRESH_TOKEN_PREFIX, SESSION_PREFIX } from "@/utils/authCookies";
1
+ import { deleteCookie } from "@/utils/authCookies";
2
+ import { SESSION_PREFIX, REFRESH_TOKEN_PREFIX } from "@crossmint/common-sdk-auth";
2
3
  import { fireEvent, render } from "@testing-library/react";
3
4
  import { type ReactNode, act } from "react";
4
5
  import { beforeEach, describe, expect, vi } from "vitest";
@@ -39,7 +40,7 @@ vi.mock("@crossmint/client-sdk-auth", async () => {
39
40
  getJWTExpiration: vi.fn(),
40
41
  CrossmintAuthService: vi.fn().mockImplementation(() => ({
41
42
  refreshAuthMaterial: vi.fn().mockResolvedValue({
42
- jwtToken: "new-mock-jwt",
43
+ jwt: "new-mock-jwt",
43
44
  refreshToken: {
44
45
  secret: "new-mock-refresh-token",
45
46
  expiresAt: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
@@ -123,7 +124,7 @@ describe("CrossmintAuthProvider", () => {
123
124
 
124
125
  mockCrossmintAuthService = {
125
126
  refreshAuthMaterial: vi.fn().mockResolvedValue({
126
- jwtToken: "new-mock-jwt",
127
+ jwt: "new-mock-jwt",
127
128
  refreshToken: {
128
129
  secret: "new-mock-refresh-token",
129
130
  expiresAt: new Date(Date.now() + 1000 * 60 * 60).toISOString(),
@@ -1,15 +1,20 @@
1
- import { REFRESH_TOKEN_PREFIX, SESSION_PREFIX, deleteCookie, getCookie, setCookie } from "@/utils/authCookies";
2
1
  import { type ReactNode, createContext, useEffect, useState } from "react";
3
2
  import { createPortal } from "react-dom";
4
3
 
5
4
  import { CrossmintAuthService } from "@crossmint/client-sdk-auth";
6
5
  import type { EVMSmartWalletChain } from "@crossmint/client-sdk-smart-wallet";
7
6
  import { type UIConfig, validateApiKeyAndGetCrossmintBaseUrl } from "@crossmint/common-sdk-base";
7
+ import {
8
+ SESSION_PREFIX,
9
+ REFRESH_TOKEN_PREFIX,
10
+ type AuthMaterialWithUser,
11
+ type SDKExternalUser,
12
+ } from "@crossmint/common-sdk-auth";
8
13
 
9
14
  import AuthModal from "../components/auth/AuthModal";
10
15
  import { useCrossmint, useRefreshToken, useWallet } from "../hooks";
11
16
  import { CrossmintWalletProvider } from "./CrossmintWalletProvider";
12
- import type { AuthMaterial, SDKExternalUser } from "@crossmint/common-sdk-auth";
17
+ import { deleteCookie, getCookie, setCookie } from "@/utils/authCookies";
13
18
 
14
19
  export type CrossmintAuthWalletConfig = {
15
20
  defaultChain: EVMSmartWalletChain;
@@ -66,10 +71,10 @@ export function CrossmintAuthProvider({
66
71
  const crossmintBaseUrl = validateApiKeyAndGetCrossmintBaseUrl(crossmint.apiKey);
67
72
  const [modalOpen, setModalOpen] = useState(false);
68
73
 
69
- const setAuthMaterial = (authMaterial: AuthMaterial) => {
70
- setCookie(SESSION_PREFIX, authMaterial.jwtToken);
74
+ const setAuthMaterial = (authMaterial: AuthMaterialWithUser) => {
75
+ setCookie(SESSION_PREFIX, authMaterial.jwt);
71
76
  setCookie(REFRESH_TOKEN_PREFIX, authMaterial.refreshToken.secret, authMaterial.refreshToken.expiresAt);
72
- setJwt(authMaterial.jwtToken);
77
+ setJwt(authMaterial.jwt);
73
78
  setRefreshToken(authMaterial.refreshToken.secret);
74
79
  setUser(authMaterial.user);
75
80
  };
@@ -118,7 +123,7 @@ export function CrossmintAuthProvider({
118
123
  return "logged-out";
119
124
  };
120
125
 
121
- const fetchAuthMaterial = async (refreshToken: string): Promise<AuthMaterial> => {
126
+ const fetchAuthMaterial = async (refreshToken: string): Promise<AuthMaterialWithUser> => {
122
127
  const authMaterial = await crossmintAuthService.refreshAuthMaterial(refreshToken);
123
128
  setAuthMaterial(authMaterial);
124
129
  return authMaterial;
@@ -1,6 +1,3 @@
1
- export const SESSION_PREFIX = "crossmint-jwt";
2
- export const REFRESH_TOKEN_PREFIX = "crossmint-refresh-token";
3
-
4
1
  export function getCookie(name: string): string | undefined {
5
2
  const crossmintRefreshToken = document.cookie.split("; ").find((row) => row.startsWith(name));
6
3
  return crossmintRefreshToken ? crossmintRefreshToken.split("=")[1] : undefined;
@@ -0,0 +1,17 @@
1
+ import { LIB_VERSION } from "@/consts/version";
2
+ import { type Crossmint, CrossmintApiClient, type CrossmintApiClientInternalConfig } from "@crossmint/common-sdk-base";
3
+
4
+ export function createCrossmintApiClient(
5
+ crossmint: Crossmint,
6
+ apiKeyExpectations?: CrossmintApiClientInternalConfig["apiKeyExpectations"]
7
+ ) {
8
+ return new CrossmintApiClient(crossmint, {
9
+ internalConfig: {
10
+ sdkMetadata: {
11
+ name: "@crossmint/client-sdk-react-ui",
12
+ version: LIB_VERSION,
13
+ },
14
+ apiKeyExpectations,
15
+ },
16
+ });
17
+ }