@coin-voyage/paykit 2.4.5-beta.2 → 2.4.5-beta.3

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.
@@ -1 +1,6 @@
1
1
  export default function CardPayment(): import("react/jsx-runtime").JSX.Element;
2
+ export declare const CryptoElements: ({ stripeOnramp, children }: {
3
+ stripeOnramp: any;
4
+ children: any;
5
+ }) => import("react/jsx-runtime").JSX.Element;
6
+ export declare const useStripeOnramp: () => any;
@@ -1,9 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { getFiatPaymentData } from "@coin-voyage/shared/payment";
3
3
  import { PayOrderMode, PayOrderStatus } from "@coin-voyage/shared/types";
4
- import { loadStripeOnramp } from "@stripe/crypto/pure";
5
4
  import { useQuery } from "@tanstack/react-query";
6
- import { memo, useCallback, useEffect, useMemo, useRef } from "react";
5
+ import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
7
6
  import { AlertIcon } from "../../../assets/icons";
8
7
  import useLocales from "../../../hooks/useLocales";
9
8
  import styled from "../../../styles/styled";
@@ -66,29 +65,34 @@ function StatusCard({ title, body, actionLabel, onAction, loading = false, warni
66
65
  }, children: [loading ? _jsx(Spinner, {}) : warning ? _jsx(AlertIcon, {}) : null, _jsx(ModalH1, { "$warning": warning, children: title }), _jsx(ModalBody, { children: body }), actionLabel && onAction ? (_jsx(ActionRow, { children: _jsx(Button, { onClick: onAction, children: actionLabel }) })) : null] }));
67
66
  }
68
67
  function StripeOnrampCheckout({ paymentData }) {
69
- const { paymentState, mode, triggerResize } = usePayContext();
70
- const { data: stripeOnramp, isLoading, error, refetch } = useStripeOnramp(paymentData.stripe_publishable_key);
71
- const refreshOrderRef = useLatestRef(paymentState.refreshOrder);
68
+ const { mode, triggerResize } = usePayContext();
69
+ //const { data: stripeOnramp, isLoading, error, refetch } = useStripeOnramp(paymentData.stripe_publishable_key)
72
70
  const refreshDebounceRef = useRef(null);
73
- const lastStatusRef = useRef(null);
74
- const theme = useMemo(() => {
75
- return mode === "dark" ? "dark" : "light";
71
+ const appearance = useMemo(() => {
72
+ return {
73
+ theme: mode === "dark" ? "night" : "stripe",
74
+ variables: {
75
+ colorBackground: "var(--ck-body-background-secondary)",
76
+ },
77
+ };
76
78
  }, [mode]);
77
- const handleSessionUpdate = useCallback((status) => {
78
- if (status === lastStatusRef.current)
79
- return;
80
- lastStatusRef.current = status;
81
- const shouldRefresh = status === "fulfillment_complete" || status === "rejected";
82
- if (!shouldRefresh) {
83
- return;
84
- }
85
- if (refreshDebounceRef.current) {
86
- window.clearTimeout(refreshDebounceRef.current);
87
- }
88
- refreshDebounceRef.current = window.setTimeout(() => {
89
- void refreshOrderRef.current();
90
- }, 500);
91
- }, [refreshOrderRef]);
79
+ // const handleSessionUpdate = useCallback(
80
+ // (status: OnrampSessionStatus) => {
81
+ // if (status === lastStatusRef.current) return
82
+ // lastStatusRef.current = status
83
+ // const shouldRefresh = status === "fulfillment_complete" || status === "rejected"
84
+ // if (!shouldRefresh) {
85
+ // return
86
+ // }
87
+ // if (refreshDebounceRef.current) {
88
+ // window.clearTimeout(refreshDebounceRef.current)
89
+ // }
90
+ // refreshDebounceRef.current = window.setTimeout(() => {
91
+ // void refreshOrderRef.current()
92
+ // }, 500)
93
+ // },
94
+ // [refreshOrderRef]
95
+ // )
92
96
  useEffect(() => {
93
97
  return () => {
94
98
  if (refreshDebounceRef.current) {
@@ -96,44 +100,46 @@ function StripeOnrampCheckout({ paymentData }) {
96
100
  }
97
101
  };
98
102
  }, []);
99
- if (error) {
100
- return (_jsx(StatusCard, { warning: true, title: "Stripe unavailable", body: error.message, actionLabel: "Try again", onAction: () => {
101
- void refetch();
102
- } }));
103
- }
104
- return (_jsxs(OnrampShell, { children: [isLoading ? (_jsx(OnrampOverlay, { children: _jsx(Spinner, {}) })) : null, stripeOnramp ? (_jsx(OnrampSession, { stripeOnramp: stripeOnramp, clientSecret: paymentData.client_secret, theme: theme, onUiLoaded: triggerResize, onSessionUpdate: handleSessionUpdate }, paymentData.client_secret)) : null] }));
103
+ // if (error) {
104
+ // return (
105
+ // <StatusCard
106
+ // warning
107
+ // title="Stripe unavailable"
108
+ // body={error.message}
109
+ // actionLabel="Try again"
110
+ // onAction={() => {
111
+ // void refetch()
112
+ // }}
113
+ // />
114
+ // )
115
+ // }
116
+ return (_jsx(OnrampShell, { children: paymentData.client_secret ? (_jsx(OnrampElement, { id: "onramp-element", clientSecret: paymentData.client_secret, appearance: appearance, onReady: triggerResize })) : (_jsx(OnrampOverlay, { children: _jsx(Spinner, {}) })) }));
105
117
  }
106
- const OnrampSession = memo(function OnrampSession({ stripeOnramp, clientSecret, theme, onUiLoaded, onSessionUpdate, }) {
107
- const mountNodeRef = useRef(null);
108
- const onUiLoadedRef = useLatestRef(onUiLoaded);
109
- const onSessionUpdateRef = useLatestRef(onSessionUpdate);
118
+ function OnrampElement({ clientSecret, appearance, onReady, onChange, ...props }) {
119
+ const stripeOnramp = useStripeOnramp();
120
+ const onrampElementRef = useRef(null);
121
+ const [session, setSession] = useState();
122
+ const appearanceJSON = JSON.stringify(appearance);
110
123
  useEffect(() => {
111
- const mountNode = mountNodeRef.current;
112
- if (!mountNode) {
113
- return;
124
+ const containerRef = onrampElementRef.current;
125
+ if (containerRef) {
126
+ // NB: ideally we want to be able to hot swap/update onramp iframe
127
+ // This currently results a flash if one needs to mint a new session when they need to update fixed transaction details
128
+ containerRef.innerHTML = "";
129
+ if (clientSecret && stripeOnramp) {
130
+ setSession(stripeOnramp
131
+ .createSession({
132
+ clientSecret,
133
+ appearance: appearanceJSON ? JSON.parse(appearanceJSON) : {},
134
+ })
135
+ .mount(containerRef));
136
+ }
114
137
  }
115
- mountNode.replaceChildren();
116
- const session = stripeOnramp.createSession({
117
- clientSecret,
118
- appearance: theme ? { theme } : undefined,
119
- });
120
- const handleUiLoaded = () => {
121
- onUiLoadedRef.current?.();
122
- };
123
- const handleSessionUpdated = (event) => {
124
- onSessionUpdateRef.current?.(event.payload.session.status);
125
- };
126
- session.addEventListener("onramp_ui_loaded", handleUiLoaded);
127
- session.addEventListener("onramp_session_updated", handleSessionUpdated);
128
- session.mount(mountNode);
129
- return () => {
130
- session.removeEventListener("onramp_ui_loaded", handleUiLoaded);
131
- session.removeEventListener("onramp_session_updated", handleSessionUpdated);
132
- mountNode.replaceChildren();
133
- };
134
- }, [clientSecret, onSessionUpdateRef, onUiLoadedRef, stripeOnramp, theme]);
135
- return _jsx(OnrampMount, { ref: mountNodeRef });
136
- });
138
+ }, [appearanceJSON, clientSecret, stripeOnramp]);
139
+ useOnrampSessionListener("onramp_ui_loaded", session, onReady);
140
+ useOnrampSessionListener("onramp_session_updated", session, onChange);
141
+ return _jsx("div", { ...props, ref: onrampElementRef });
142
+ }
137
143
  function useCardPaymentData() {
138
144
  const { paymentState } = usePayContext();
139
145
  const { payOrder, payWithCard } = paymentState;
@@ -163,41 +169,44 @@ function useCardPaymentData() {
163
169
  data: paymentData ?? query.data,
164
170
  };
165
171
  }
166
- function useStripeOnramp(publishableKey) {
167
- return useQuery({
168
- queryKey: ["stripe-onramp", publishableKey],
169
- enabled: Boolean(publishableKey),
170
- staleTime: Infinity,
171
- retry: false,
172
- queryFn: async () => {
173
- if (!publishableKey) {
174
- throw new Error("Missing Stripe publishable key.");
175
- }
176
- const instance = await getStripeOnrampPromise(publishableKey);
177
- if (!instance) {
178
- throw new Error("Stripe Onramp is only available in the browser.");
172
+ // ReactContext to simplify access of StripeOnramp object
173
+ const CryptoElementsContext = createContext(null);
174
+ CryptoElementsContext.displayName = "CryptoElementsContext";
175
+ export const CryptoElements = ({ stripeOnramp, children }) => {
176
+ const [ctx, setContext] = useState(() => ({
177
+ onramp: null,
178
+ }));
179
+ useEffect(() => {
180
+ let isMounted = true;
181
+ Promise.resolve(stripeOnramp).then((onramp) => {
182
+ if (onramp && isMounted) {
183
+ setContext((ctx) => (ctx.onramp ? ctx : { onramp }));
179
184
  }
180
- return instance;
181
- },
182
- });
183
- }
184
- const stripeOnrampPromiseCache = new Map();
185
- function getStripeOnrampPromise(publishableKey) {
186
- const cached = stripeOnrampPromiseCache.get(publishableKey);
187
- if (cached) {
188
- return cached;
189
- }
190
- const nextPromise = loadStripeOnramp(publishableKey);
191
- stripeOnrampPromiseCache.set(publishableKey, nextPromise);
192
- return nextPromise;
193
- }
194
- function useLatestRef(value) {
195
- const ref = useRef(value);
185
+ });
186
+ return () => {
187
+ isMounted = false;
188
+ };
189
+ }, [stripeOnramp]);
190
+ return _jsx(CryptoElementsContext.Provider, { value: ctx, children: children });
191
+ };
192
+ // React hook to get StripeOnramp from context
193
+ export const useStripeOnramp = () => {
194
+ const context = useContext(CryptoElementsContext);
195
+ return context?.onramp;
196
+ };
197
+ // React element to render Onramp UI
198
+ const useOnrampSessionListener = (type, session, callback) => {
196
199
  useEffect(() => {
197
- ref.current = value;
198
- }, [value]);
199
- return ref;
200
- }
200
+ if (session && callback) {
201
+ const listener = (e) => callback(e.payload);
202
+ session.addEventListener(type, listener);
203
+ return () => {
204
+ session.removeEventListener(type, listener);
205
+ };
206
+ }
207
+ return () => { };
208
+ }, [session, callback, type]);
209
+ };
201
210
  const OnrampShell = styled.div `
202
211
  position: relative;
203
212
  min-height: 480px;
@@ -12,6 +12,7 @@ import { ModalContent, PageContent } from "../../ui/Modal/styles";
12
12
  import { ScrollArea } from "../../ui/ScrollArea";
13
13
  import { Spinner } from "../../ui/Spinner";
14
14
  import { Container, WalletIcon, WalletItem, WalletLabel, WalletList } from "./styles";
15
+ import { getConnectorId } from "@coin-voyage/crypto/utils";
15
16
  const MoreIcon = (_jsx("svg", { width: "60", height: "60", viewBox: "0 0 60 60", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M30 42V19M19 30.5H42", stroke: "var(--ck-body-color-muted)", strokeWidth: "3", strokeLinecap: "round" }) }));
16
17
  export default function MobileConnectors() {
17
18
  const { paymentState } = usePayContext();
@@ -29,7 +30,7 @@ export default function MobileConnectors() {
29
30
  // check if wallet is supports currentChainType
30
31
  if (connectorChainType && wallet.chainTypes && !wallet.chainTypes.includes(connectorChainType))
31
32
  return false;
32
- if (wallets.find((w) => w.connectors.find((c) => c.connector.id === walletId)))
33
+ if (wallets.find((w) => w.connectors.find((c) => getConnectorId(c.connector) === walletId)))
33
34
  return false;
34
35
  return true;
35
36
  }) ?? [];
@@ -3,7 +3,7 @@ import { useConfig as useBigmiConfig } from "@bigmi/react";
3
3
  import { Arbitrum, Base, Bitcoin, Ethereum, Solana, Sui } from "@coin-voyage/shared/chain";
4
4
  import { ChainType, PayOrderMode } from "@coin-voyage/shared/types";
5
5
  import { truncateAddress, truncateENSName } from "@coin-voyage/shared/utils";
6
- import { useWallets } from "@mysten/dapp-kit";
6
+ import { useWallets } from "@mysten/dapp-kit-react";
7
7
  import { useWallet } from "@solana/wallet-adapter-react";
8
8
  import { useCallback, useMemo } from "react";
9
9
  import { useConfig as useWagmiConfig } from "wagmi";
@@ -1,7 +1,6 @@
1
- import { useAccount } from "@coin-voyage/crypto/hooks";
1
+ import { useAccount, useSuiNSName } from "@coin-voyage/crypto/hooks";
2
2
  import { zPayOrder } from "@coin-voyage/shared/schemas";
3
3
  import { ChainType, PayOrderMode, } from "@coin-voyage/shared/types";
4
- import { useResolveSuiNSName } from "@mysten/dapp-kit";
5
4
  import { useCallback, useRef, useState } from "react";
6
5
  import { mainnet } from "viem/chains";
7
6
  import { useEnsName } from "wagmi";
@@ -21,7 +20,7 @@ export function usePaymentState({ payOrder, setPayOrder, setRoute, log, }) {
21
20
  selectedWallet,
22
21
  chainType: connectorChainType,
23
22
  });
24
- const { data: suiEnsName } = useResolveSuiNSName(senderAccount.address, {
23
+ const { data: suiEnsName } = useSuiNSName(senderAccount.address, {
25
24
  enabled: !!senderAccount.address && senderAccount.chainType === ChainType.SUI && senderAccount.isConnected,
26
25
  });
27
26
  const { data: evmEnsName } = useEnsName({
@@ -3,6 +3,7 @@ import { useInstalledWallets } from "@coin-voyage/crypto/hooks";
3
3
  import usePayContext from "../components/contexts/pay";
4
4
  import { walletConfigs } from "../lib/config/wallet";
5
5
  import { isInjectedConnector } from "../utils";
6
+ import { getConnectorId } from "@coin-voyage/crypto/utils";
6
7
  export const useWallets = () => {
7
8
  const { paymentState } = usePayContext();
8
9
  const installedWallets = useInstalledWallets(paymentState.connectorChainType);
@@ -54,8 +55,8 @@ export const useWallets = () => {
54
55
  self.find((w) => w.id === "io.metamask" || w.id === "io.metamask.mobile")))
55
56
  // order by isInstalled injected connectors first
56
57
  .sort((a, b) => {
57
- const AisInstalled = a.isInstalled && isInjectedConnector(a.connectors[0].connector.id);
58
- const BisInstalled = b.isInstalled && isInjectedConnector(b.connectors[0].connector.id);
58
+ const AisInstalled = a.isInstalled && isInjectedConnector(getConnectorId(a.connectors[0].connector));
59
+ const BisInstalled = b.isInstalled && isInjectedConnector(getConnectorId(b.connectors[0].connector));
59
60
  if (AisInstalled && !BisInstalled)
60
61
  return -1;
61
62
  if (!AisInstalled && BisInstalled)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coin-voyage/paykit",
3
3
  "description": "Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.",
4
- "version": "2.4.5-beta.2",
4
+ "version": "2.4.5-beta.3",
5
5
  "private": false,
6
6
  "sideEffects": false,
7
7
  "author": "Lars <lars@coinvoyage.io>",
@@ -57,11 +57,11 @@
57
57
  "react-transition-state": "^1.1.4",
58
58
  "react-use-measure": "^2.1.1",
59
59
  "@stripe/stripe-js": "8.10.0",
60
- "@stripe/crypto": "0.0.4",
60
+ "@stripe/crypto": "1.0.2",
61
61
  "styled-components": "^5.3.11",
62
62
  "uuid": "13.0.0",
63
- "@coin-voyage/shared": "2.4.5-beta.0",
64
- "@coin-voyage/crypto": "2.4.4"
63
+ "@coin-voyage/crypto": "2.4.5-beta.1",
64
+ "@coin-voyage/shared": "2.4.5-beta.0"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/qrcode": "1.5.5",
@@ -70,8 +70,8 @@
70
70
  "typescript-plugin-styled-components": "^3.0.0"
71
71
  },
72
72
  "peerDependencies": {
73
- "@bigmi/react": "^0.6.1",
74
- "@mysten/dapp-kit": "^0.19.8",
73
+ "@bigmi/react": "^0.8.0",
74
+ "@mysten/dapp-kit-react": "^2.0.3",
75
75
  "@solana/wallet-adapter-react": "^0.15.39",
76
76
  "@tanstack/react-query": "^5.90.6",
77
77
  "react": "^18 || ^19",