Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions examples/next/src/components/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ import {
ETH_CONTRACT_ADDRESS,
} from "@cartridge/controller-ui/utils";

export const BUNDLE_REGISTRY_MAINNET =
// Nums bundle registries
export const NUMS_REGISTRY_MAINNET =
"0x1a8516498b484f209aefbbf5af67765a2b1e3889fd00902811f18576a4616b0";
export const BUNDLE_REGISTRY_SEPOLIA =
export const NUMS_REGISTRY_SEPOLIA =
"0x3110295929fc665972ae2ea4b99d5fa57547aa56d140dc73a7e85ddcaf5eaf1";
export const ABYSS_REGISTRY_MAINNET =
"0x529c074ad9540d56a8d6e7086bb3d7a3d8ebaf60dbbf4abb1aeae336be833fe";

// legacy starterpack registry (deprecated)
export const STARTERPACK_REGISTRY_MAINNET =
"0x3eb03b8f2be0ec2aafd186d72f6d8f3dd320dbc89f2b6802bca7465f6ccaa43";

export function Profile() {
const { account, connector } = useAccount();
Expand Down
20 changes: 17 additions & 3 deletions examples/next/src/components/Starterpack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { constants, num } from "starknet";
import { useAccount, useNetwork } from "@starknet-react/core";
import ControllerConnector from "@cartridge/connector/controller";
import { Button, Input } from "@cartridge/controller-ui";
import { BUNDLE_REGISTRY_MAINNET, BUNDLE_REGISTRY_SEPOLIA } from "./Profile";
import {
ABYSS_REGISTRY_MAINNET,
NUMS_REGISTRY_MAINNET,
NUMS_REGISTRY_SEPOLIA,
} from "./Profile";

export const Starterpack = () => {
const { account, connector } = useAccount();
Expand All @@ -19,14 +23,14 @@ export const Starterpack = () => {
bundleId: 1,
socialBundleId: 0,
starterpackId: 0,
registryAddress: BUNDLE_REGISTRY_MAINNET,
registryAddress: NUMS_REGISTRY_MAINNET,
};
}
return {
bundleId: 1,
socialBundleId: 0,
starterpackId: 0,
registryAddress: BUNDLE_REGISTRY_SEPOLIA,
registryAddress: NUMS_REGISTRY_SEPOLIA,
};
}, [chain]);

Expand Down Expand Up @@ -143,6 +147,16 @@ export const Starterpack = () => {
>
Nums Social Bundle
</Button>
<Button
onClick={() => {
controllerConnector.controller.openBundle(
2,
ABYSS_REGISTRY_MAINNET,
);
}}
>
Not With Credits
</Button>
</div>

<div className="flex flex-wrap gap-1">
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"prepare": "husky"
},
"dependencies": {
"@cartridge/presets": "github:cartridge-gg/presets#e94909f",
"@cartridge/presets": "catalog:",
"@cartridge/controller-ui": "workspace:*",
"tailwindcss": "catalog:",
"@graphql-codegen/cli": "^2.6.2",
Expand Down
6 changes: 5 additions & 1 deletion packages/keychain/src/components/NavigationHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useCallback } from "react";
import { LayoutHeader, HeaderProps } from "@cartridge/controller-ui";
import { useConnection } from "@/hooks/connection";
import { useNavigation } from "@/context/navigation";
import { useCallback } from "react";
import { useCreditsContext } from "@/components/credits/provider";

export type NavigationHeaderProps = {
// Navigation props
Expand Down Expand Up @@ -48,11 +49,14 @@ export function NavigationHeader({
navigateToRoot();
}, [onClose, closeModal, navigateToRoot]);

const { initiateCreditsDeposit } = useCreditsContext();

return (
<LayoutHeader
{...props}
onBack={shouldShowBack ? handleBack : undefined}
onClose={shouldShowClose ? handleCloseAction : undefined}
onDeposit={initiateCreditsDeposit}
hideSettings
/>
);
Expand Down
20 changes: 15 additions & 5 deletions packages/keychain/src/components/connect/create/social/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const useSocialAuthentication = (
socialProvider: SocialProvider,
username: string,
isSignup: boolean,
forceAccountSelection = false,
) => {
if (!chainId) {
throw new Error("No chainId");
Expand All @@ -25,7 +26,10 @@ export const useSocialAuthentication = (
rpcUrl,
socialProvider,
);
const { account, error, success } = await turnkeyWallet.connect(isSignup);
const { account, error, success } = await turnkeyWallet.connect(
isSignup,
forceAccountSelection,
);
if (error?.includes("Account mismatch")) {
setChangeWallet?.(true);
return;
Expand Down Expand Up @@ -57,9 +61,15 @@ export const useSocialAuthentication = (
);

return {
signup: (socialProvider: SocialProvider, username: string) =>
signup(socialProvider, username, true),
login: (socialProvider: SocialProvider, username: string) =>
signup(socialProvider, username, false),
signup: (
socialProvider: SocialProvider,
username: string,
forceAccountSelection = false,
) => signup(socialProvider, username, true, forceAccountSelection),
login: (
socialProvider: SocialProvider,
username: string,
forceAccountSelection = false,
) => signup(socialProvider, username, false, forceAccountSelection),
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,11 @@ export function useCreateController({
>(undefined);
const signupStartedRef = useRef(false);
const signupResolvedRef = useRef(false);
// The OAuth (Auth0/Turnkey) redirect callback consumes a single-use
// transaction. Guard against StrictMode double-invocation and dependency-
// driven re-runs so handleRedirectCallback runs exactly once — otherwise the
// second call throws "Invalid state" and the page loops on the login screen.
const redirectHandledRef = useRef(false);
const signupStartTimeRef = useRef(performance.now());
const authStepRef = useRef<AuthenticationStep>(AuthenticationStep.FillForm);

Expand Down Expand Up @@ -579,7 +584,11 @@ export function useCreateController({
}
case "google":
case "discord":
signupResponse = await signupWithSocial(authenticationMode, username);
signupResponse = await signupWithSocial(
authenticationMode,
username,
changeWallet,
);
if (!signupResponse) {
return;
}
Expand Down Expand Up @@ -694,6 +703,7 @@ export function useCreateController({
params,
handleCompletion,
searchParams,
changeWallet,
],
);

Expand Down Expand Up @@ -943,7 +953,11 @@ export function useCreateController({
case "google":
case "discord": {
setWaitingForConfirmation(true);
loginResponse = await loginWithSocial(authenticationMethod, username);
loginResponse = await loginWithSocial(
authenticationMethod,
username,
changeWallet,
);
if (!loginResponse) {
return;
}
Expand Down Expand Up @@ -1053,15 +1067,28 @@ export function useCreateController({
smsAuth,
smsState,
setWaitingForConfirmation,
changeWallet,
],
);

useEffect(() => {
if (!chainId) return;
if (redirectHandledRef.current) return;
if (
window.location.search.includes("code") &&
window.location.search.includes("state")
) {
redirectHandledRef.current = true;
const redirectUrl = window.location.href;
// Strip the single-use OAuth params from the address bar immediately.
// handleRedirect() consumes them from the captured `redirectUrl`, so a
// reload (or a remount that resets the guard) can't replay the already-
// consumed callback and fail with "Invalid state" — and the user is left
// on a clean URL from which a fresh login can be initiated.
const cleanUrl = new URL(redirectUrl);
cleanUrl.searchParams.delete("code");
cleanUrl.searchParams.delete("state");
window.history.replaceState({}, "", cleanUrl.toString());
(async () => {
setIsLoading(true);
try {
Expand All @@ -1080,14 +1107,8 @@ export function useCreateController({
searchParams,
chainId,
rpcUrl,
} = await turnkeyWallet.handleRedirect(
window.location.href,
setError,
);
} = await turnkeyWallet.handleRedirect(redirectUrl, setError);

if (error) {
throw error;
}
if (
!username ||
isSignup === undefined ||
Expand Down Expand Up @@ -1173,7 +1194,6 @@ export function useCreateController({
})();
}
}, [
error,
setIsLoading,
finishLogin,
finishSignup,
Expand Down
56 changes: 56 additions & 0 deletions packages/keychain/src/components/credits/AmountSelectionDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useState } from "react";
import {
Drawer,
DrawerContent,
DepositIcon,
Button,
} from "@cartridge/controller-ui";
import { CREDITS_DESCRIPTION } from "@/components/inventory/token";
import { AmountSelection } from "@/components/funding/AmountSelection";

interface AmountSelectionDrawerProps {
isOpen: boolean;
onClose: () => void;
minAmount?: number;
maxAmount?: number;
isLoading?: boolean;
onContinue: (amount: number) => void;
}

export function AmountSelectionDrawer({
isOpen,
onClose,
minAmount,
maxAmount,
isLoading,
onContinue,
}: AmountSelectionDrawerProps) {
const [amount, setAmount] = useState(0);
return (
<Drawer isOpen={isOpen} onClose={onClose} className="gap-4">
<DrawerContent title="Deposit USD" icon={<DepositIcon variant="solid" />}>
<AmountSelection
lockSelection={isLoading}
enableCustom={true}
minAmount={minAmount}
maxAmount={maxAmount}
onChange={setAmount}
/>

<div className="p-3 text-xs border border-background-200 rounded text-foreground-300">
{CREDITS_DESCRIPTION}
</div>

<Button
disabled={!amount}
isLoading={isLoading}
onClick={() => {
onContinue(amount);
}}
>
Continue
</Button>
</DrawerContent>
</Drawer>
);
}
Loading
Loading