Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add expiration to session summary #1269

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 17 additions & 43 deletions packages/keychain/src/components/connect/CreateSession.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Container, Content, Footer } from "@/components/layout";
import { BigNumberish, shortString } from "starknet";
import { ControllerError } from "@/utils/connection";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useConnection } from "@/hooks/connection";
import { ControllerErrorAlert } from "@/components/ErrorAlert";
import { SessionConsent } from "@/components/connect";
Expand All @@ -12,15 +12,7 @@ import { ParsedSessionPolicies } from "@/hooks/session";
import { UnverifiedSessionSummary } from "@/components/session/UnverifiedSessionSummary";
import { VerifiedSessionSummary } from "@/components/session/VerifiedSessionSummary";
import { DEFAULT_SESSION_DURATION } from "@/const";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Button,
Checkbox,
} from "@cartridge/ui-next";
import { Button, Checkbox } from "@cartridge/ui-next";

export function CreateSession({
policies,
Expand All @@ -36,10 +28,6 @@ export function CreateSession({
const [isDisabled, setIsDisabled] = useState(false);
const [isConsent, setIsConsent] = useState(false);
const [duration, setDuration] = useState<bigint>(DEFAULT_SESSION_DURATION);
const expiresAt = useMemo(
() => duration + BigInt(Math.floor(Date.now() / 1000)),
[duration],
);
const [maxFee] = useState<BigNumberish>();
const [error, setError] = useState<ControllerError | Error>();

Expand Down Expand Up @@ -93,26 +81,26 @@ export function CreateSession({
});
}

await controller.createSession(expiresAt, policies, maxFee);
await controller.createSession(duration, policies, maxFee);
onConnect();
} catch (e) {
setError(e as unknown as Error);
setIsConnecting(false);
}
}, [controller, expiresAt, policies, maxFee, onConnect]);
}, [controller, duration, policies, maxFee, onConnect]);

const onSkipSession = useCallback(async () => {
if (!controller || !policies) return;
try {
setError(undefined);
setIsConnecting(true);
await controller.createSession(expiresAt, policies, maxFee);
await controller.createSession(duration, policies, maxFee);
onConnect();
} catch (e) {
setError(e as unknown as Error);
setIsConnecting(false);
}
}, [controller, expiresAt, policies, maxFee, onConnect]);
}, [controller, duration, policies, maxFee, onConnect]);

if (!upgrade.isSynced) {
return <></>;
Expand All @@ -139,35 +127,21 @@ export function CreateSession({
<Content gap={6}>
<SessionConsent isVerified={policies?.verified} />
{policies?.verified ? (
<VerifiedSessionSummary game={theme.name} policies={policies} />
<VerifiedSessionSummary
game={theme.name}
policies={policies}
duration={duration}
onDurationChange={setDuration}
/>
) : (
<UnverifiedSessionSummary policies={policies} />
<UnverifiedSessionSummary
policies={policies}
duration={duration}
onDurationChange={setDuration}
/>
)}
</Content>
<Footer>
<div className="flex items-center text-sm text-muted-foreground py-4 gap-2">
<div className="font-medium">Expires in </div>
<Select
value={duration.toString()}
onValueChange={(val) => setDuration(BigInt(val))}
>
<SelectTrigger className="w-28">
<SelectValue
defaultValue={(60 * 60 * 24).toString()}
placeholder="1 HR"
/>
</SelectTrigger>

<SelectContent>
<SelectItem value={(60 * 60).toString()}>1 HR</SelectItem>
<SelectItem value={(60 * 60 * 24).toString()}>24 HRS</SelectItem>
<SelectItem value={(60 * 60 * 24 * 7).toString()}>
1 WEEK
</SelectItem>
</SelectContent>
</Select>
</div>

{!policies?.verified && (
<div
className="flex items-center p-3 mb-3 gap-5 border border-solid-primary rounded-md cursor-pointer border-error-icon text-error-icon"
Expand Down
25 changes: 17 additions & 8 deletions packages/keychain/src/components/connect/RegisterSession.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
TransactionExecutionStatus,
TransactionFinalityStatus,
} from "starknet";
import { SESSION_EXPIRATION } from "@/const";
import { DEFAULT_SESSION_DURATION } from "@/const";
import { UnverifiedSessionSummary } from "@/components/session/UnverifiedSessionSummary";
import { VerifiedSessionSummary } from "@/components/session/VerifiedSessionSummary";
import { ParsedSessionPolicies } from "@/hooks/session";
Expand All @@ -22,7 +22,7 @@ export function RegisterSession({
publicKey?: string;
}) {
const { controller, theme } = useConnection();
const [expiresAt] = useState<bigint>(SESSION_EXPIRATION);
const [duration, setDuration] = useState<bigint>(DEFAULT_SESSION_DURATION);
const [transactions, setTransactions] = useState<
| {
contractAddress: string;
Expand All @@ -37,7 +37,7 @@ export function RegisterSession({
setTransactions(undefined);
} else {
controller
.registerSessionCalldata(expiresAt, policies, publicKey)
.registerSessionCalldata(duration, policies, publicKey)
.then((calldata) => {
setTransactions([
{
Expand All @@ -48,7 +48,7 @@ export function RegisterSession({
]);
});
}
}, [controller, expiresAt, policies, publicKey]);
}, [controller, duration, policies, publicKey]);

const onRegisterSession = useCallback(
async (maxFee?: bigint) => {
Expand All @@ -57,7 +57,7 @@ export function RegisterSession({
}

const { transaction_hash } = await controller.registerSession(
expiresAt,
duration,
policies,
publicKey,
maxFee,
Expand All @@ -73,7 +73,7 @@ export function RegisterSession({

onConnect(transaction_hash);
},
[controller, expiresAt, policies, publicKey, onConnect],
[controller, duration, policies, publicKey, onConnect],
);

return (
Expand All @@ -86,9 +86,18 @@ export function RegisterSession({
<Content>
<SessionConsent isVerified={policies?.verified} />
{policies?.verified ? (
<VerifiedSessionSummary game={theme.name} policies={policies} />
<VerifiedSessionSummary
game={theme.name}
policies={policies}
duration={duration}
onDurationChange={setDuration}
/>
) : (
<UnverifiedSessionSummary policies={policies} />
<UnverifiedSessionSummary
policies={policies}
duration={duration}
onDurationChange={setDuration}
/>
)}
</Content>
</ExecutionContainer>
Expand Down
66 changes: 66 additions & 0 deletions packages/keychain/src/components/session/ExpirationCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ClockIcon } from "@cartridge/ui-next";
import { AccordionCard } from "./AccordionCard";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@cartridge/ui-next";

interface ExpirationCardProps {
duration: bigint;
onDurationChange: (duration: bigint) => void;
isExpanded?: boolean;
}

export function ExpirationCard({
duration,
onDurationChange,
isExpanded,
}: ExpirationCardProps) {
return (
<AccordionCard
icon={<ClockIcon variant="solid" />}
title="Session Expiration"
trigger={
<div className="text-xs text-muted-foreground">
Expires in&nbsp;
<span className="text-accent-foreground font-bold">
{formatDuration(duration)}
</span>
</div>
}
isExpanded={isExpanded}
>
<div className="flex flex-col gap-4 p-3 text-xs">
<div className="flex items-center justify-between">
<div className="text-muted-foreground">Duration</div>
<Select
value={duration.toString()}
onValueChange={(val) => onDurationChange(BigInt(val))}
>
<SelectTrigger className="w-28">
<SelectValue placeholder="1 HR" />
</SelectTrigger>
<SelectContent>
<SelectItem value={(60 * 60).toString()}>1 HR</SelectItem>
<SelectItem value={(60 * 60 * 24).toString()}>24 HRS</SelectItem>
<SelectItem value={(60 * 60 * 24 * 7).toString()}>
1 WEEK
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</AccordionCard>
);
}

function formatDuration(seconds: bigint): string {
const hours = Number(seconds) / 3600;
if (hours === 1) return "1 hour";
if (hours === 24) return "24 hours";
if (hours === 168) return "1 week";
return `${hours} hours`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { ParsedSessionPolicies } from "@/hooks/session";

import { MessageCard } from "./MessageCard";
import { ContractCard } from "./ContractCard";
import { ExpirationCard } from "./ExpirationCard";

export function UnverifiedSessionSummary({
policies,
duration,
onDurationChange,
}: {
policies: ParsedSessionPolicies;
duration: bigint;
onDurationChange: (duration: bigint) => void;
}) {
return (
<div className="flex flex-col gap-4">
Expand All @@ -31,6 +36,8 @@ export function UnverifiedSessionSummary({
{policies.messages && policies.messages.length > 0 && (
<MessageCard messages={policies.messages} isExpanded />
)}

<ExpirationCard duration={duration} onDurationChange={onDurationChange} />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import { ParsedSessionPolicies } from "@/hooks/session";
import { AggregateCard } from "./AggregateCard";
import { CodeIcon } from "@cartridge/ui-next";
import { ContractCard } from "./ContractCard";
import { ExpirationCard } from "./ExpirationCard";

export function VerifiedSessionSummary({
game,
policies,
duration,
onDurationChange,
}: {
game: string;
policies: ParsedSessionPolicies;
duration: bigint;
onDurationChange: (duration: bigint) => void;
}) {
// Extract token and VRF contracts
const individual = Object.entries(policies.contracts ?? {}).filter(
Expand Down Expand Up @@ -44,6 +49,8 @@ export function VerifiedSessionSummary({
methods={contract.methods}
/>
))}

<ExpirationCard duration={duration} onDurationChange={onDurationChange} />
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function ConfirmTransaction() {
return (
<ExecutionContainer
Icon={TransactionDuoIcon}
title="Review Transaction"
title={`Review Transaction${transactions.length > 1 ? "s" : ""}`}
description={origin}
executionError={ctx.error}
transactions={ctx.transactions}
Expand Down
7 changes: 5 additions & 2 deletions packages/keychain/src/utils/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default class Controller extends Account {
}

async createSession(
expiresAt: bigint,
duration: bigint,
policies: ParsedSessionPolicies,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_maxFee?: BigNumberish,
Expand All @@ -106,6 +106,7 @@ export default class Controller extends Account {
throw new Error("Account not found");
}

const expiresAt = duration + BigInt(Math.floor(Date.now() / 1000));
await this.cartridge.createSession(toWasmPolicies(policies), expiresAt);
}

Expand All @@ -122,7 +123,7 @@ export default class Controller extends Account {
}

async registerSession(
expiresAt: bigint,
duration: bigint,
policies: ParsedSessionPolicies,
publicKey: string,
maxFee: BigNumberish,
Expand All @@ -131,6 +132,8 @@ export default class Controller extends Account {
throw new Error("Account not found");
}

const expiresAt = duration + BigInt(Math.floor(Date.now() / 1000));

return await this.cartridge.registerSession(
toWasmPolicies(policies),
expiresAt,
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-next/src/components/icons/state/clock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const ClockIcon = memo(
forwardRef<SVGSVGElement, StateIconProps>(
({ className, size, variant, ...props }, forwardedRef) => (
<svg
viewBox="0 0 24 24"
viewBox="0 0 20 20"
className={iconVariants({ size, className })}
ref={forwardedRef}
{...props}
Expand Down
Loading