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

Sessions #1

Open
wants to merge 4 commits into
base: master
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
8,279 changes: 0 additions & 8,279 deletions client/package-lock.json

This file was deleted.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"check-types": "tsc --noEmit --pretty"
},
"dependencies": {
"@argent/x-sessions": "^6.7.10",
"@avnu/gasless-sdk": "^0.1.6",
"axios": "^1.7.7",
"crypto-js": "^4.2.0",
Expand Down
126 changes: 86 additions & 40 deletions client/src/api/transactions.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,100 @@
import { Account, RpcProvider, Call, num, selector } from "starknet";

import { decryptPrivateKey } from "@/utils/encryption";
import { SessionService } from "@/services/SessionService";
import {
GaslessOptions,
SEPOLIA_BASE_URL,
fetchBuildTypedData,
fetchExecuteTransaction,
type GaslessOptions,
SEPOLIA_BASE_URL,
} from "@avnu/gasless-sdk";
import { decryptPrivateKey } from "@/utils/encryption";
import { Account, CallData, RpcProvider, constants, num, selector } from "starknet";
import { toast } from "react-toastify";

const options: GaslessOptions = {
baseUrl: SEPOLIA_BASE_URL,
apiKey: process.env.NEXT_PUBLIC_PAYMASTER_KEY,
};

const initialValue: Call[] = [
{
contractAddress: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS || "",
entrypoint:
process.env.NEXT_PUBLIC_CONTRACT_ENTRY_POINT_INCREASE_COUNTER?.toString() ||
"increase_counter",
calldata: [],
},
];

/**
* Invokes the contract to increase the counter.
* Invokes the contract to increase the counter using either session or password.
* @param userAddress - The user's wallet address
* @param userToken - The user's authentication token
* @param password - The user's password for decrypting the private key
* @param password - Optional password for non-session transactions
* @param wallet - The wallet type ('argent' or 'braavos')
* @returns The transaction hash if successful, null otherwise
*/
// src/api/transactions.tsx

// src/api/transactions.tsx

export const invokeContract = async (
userAddress: string,
userToken: string,
password: string,
wallet: string
password: string | null,
wallet: "argent" | "braavos"
): Promise<string | null> => {
try {
const provider = new RpcProvider({
nodeUrl: process.env.NEXT_PUBLIC_RPC_URL || "https://starknet-sepolia.public.blastapi.io",
});

// Try to get session account first
if (wallet === "argent") {
const user = JSON.parse(localStorage.getItem('user'));
console.log("Session check:", {
walletAddress: userAddress,
hasSession: !!user?.session,
wallet,
sessionWallet: user?.session?.wallet
});

if (user?.session?.wallet === wallet) {
const sessionAccount = await SessionService.getArgentSessionAccount(
provider,
userAddress
);

if (sessionAccount) {
console.log("Using session for transaction");

// First build the transaction with AVNU paymaster
const contractCall = {
contractAddress: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS || "",
entrypoint: "increase_counter",
calldata: []
};

// Get the paymaster data
const typeData = await fetchBuildTypedData(
userAddress,
[contractCall],
undefined,
undefined,
options
);

// Execute directly with session account
const tx = await sessionAccount.execute({
contractAddress: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS || "",
entrypoint: "increase_counter",
calldata: CallData.compile([]), // Using CallData from starknet.js
metadata: {
paymaster: {
...options,
paymentDetails: typeData // Include paymaster data
}
}
});

return tx.transaction_hash;
}
}
}

// Fallback to regular transaction if no session...
if (!password) {
throw new Error("Password required for transaction");
}

// Fetch the encrypted private key
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/profile/${wallet}/privatekey`,
Expand All @@ -50,38 +108,27 @@ export const invokeContract = async (
);

if (!response.ok) {
throw new Error(
`Failed to fetch the encrypted private key: ${response.statusText}`
);
throw new Error("Failed to fetch private key");
}

const json = await response.json();
const privateKeyDecrypted = decryptPrivateKey(json.privateKey, password);

if (!privateKeyDecrypted) {
throw new Error("Failed to decrypt private key");
}
// Create regular account instance
const account = new Account(provider, userAddress, privateKeyDecrypted);

// Initialising the provider
const provider = new RpcProvider({
nodeUrl: process.env.RPC_URL as string,
});

const accountAX = new Account(provider, userAddress, privateKeyDecrypted);

// Build the type data
// Build type data for AVNU's paymaster
const typeData = await fetchBuildTypedData(
userAddress,
initialValue,
[contractCall],
undefined,
undefined,
options
);

// Sign the message
const userSignature = await accountAX.signMessage(typeData);
// Sign and execute transaction
const userSignature = await account.signMessage(typeData);

// Execute the transaction
const executeTransaction = await fetchExecuteTransaction(
userAddress,
JSON.stringify(typeData),
Expand All @@ -92,10 +139,9 @@ export const invokeContract = async (
return executeTransaction.transactionHash;
} catch (error) {
console.error("Error invoking contract:", error);
return null;
throw error;
}
};

/**
* Retrieves the current counter value from the contract.
* @returns The current counter value, or 0 if an error occurs
Expand Down Expand Up @@ -144,4 +190,4 @@ export const getCounterValue = async (userAddress: string): Promise<number> => {
console.error("Error calling starknet function:", error);
return 0;
}
};
};
69 changes: 50 additions & 19 deletions client/src/api/wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {

import { AuthAction } from "@/context/AuthContext";
import { encryptPrivateKey } from "@/utils/encryption";
import { SessionService } from "@/services/SessionService";

const options: GaslessOptions = {
baseUrl: SEPOLIA_BASE_URL,
Expand All @@ -34,20 +35,21 @@ const createArgentWallet = async (
dispatch: Dispatch<AuthAction>,
password: string
): Promise<{ success: boolean; data?: string; error?: string }> => {
console.log("Starting Argent wallet creation...");

const provider = new RpcProvider({
nodeUrl: process.env.RPC_URL as string,
});

// Generating the private key with Stark Curve
const privateKeyAX = stark.randomAddress();
const starkKeyPubAX = ec.starkCurve.getStarkKey(privateKeyAX);
console.log("Generated keys - Public key:", starkKeyPubAX);

// Using Argent X Account v0.4.0 class hash
const accountClassHash = process.env.NEXT_PUBLIC_ARGENT_CLASSHASH as string;
console.log("Using Argent class hash:", accountClassHash);

// Calculate future address of the ArgentX account
const axSigner = new CairoCustomEnum({ Starknet: { pubkey: starkKeyPubAX } });
// Set the dApp Guardian address
const axGuardian = new CairoOption<unknown>(CairoOptionVariant.None);

const AXConstructorCallData = CallData.compile({
Expand All @@ -61,8 +63,8 @@ const createArgentWallet = async (
AXConstructorCallData,
0
);
console.log("Calculated contract address:", contractAddress);

// Initiating Account
const account = new Account(provider, contractAddress, privateKeyAX);

const initialValue: Call[] = [
Expand All @@ -76,6 +78,7 @@ const createArgentWallet = async (
];

try {
console.log("Building typed data for deployment...");
const typeData = await fetchBuildTypedData(
contractAddress,
initialValue,
Expand All @@ -85,6 +88,7 @@ const createArgentWallet = async (
accountClassHash
);

console.log("Signing deployment message...");
const userSignature = await account.signMessage(typeData);

const deploymentData: DeploymentData = {
Expand All @@ -94,6 +98,7 @@ const createArgentWallet = async (
calldata: AXConstructorCallData.map((value) => num.toHex(value)),
};

console.log("Executing deployment transaction...");
const executeTransaction = await fetchExecuteTransaction(
contractAddress,
JSON.stringify(typeData),
Expand All @@ -102,6 +107,9 @@ const createArgentWallet = async (
deploymentData
);

console.log("Deployment transaction hash:", executeTransaction.transactionHash);

console.log("Updating backend with wallet information...");
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/profile/`,
{
Expand All @@ -119,31 +127,54 @@ const createArgentWallet = async (
);

if (response.ok) {
// update local storage with public_key
let user = JSON.parse(localStorage.getItem("user"));

if (user) {
console.log("Updating user data with new wallet...");
user.argent_account = contractAddress;
localStorage.setItem("user", JSON.stringify(user));
user.argent_public_key = starkKeyPubAX; // Store public key for session usage

// Create session after successful wallet deployment
try {
console.log("Creating initial Argent session...");
const sessionResult = await SessionService.createArgentSession(
account,
process.env.NEXT_PUBLIC_CONTRACT_ADDRESS || "",
process.env.NEXT_PUBLIC_CONTRACT_ENTRY_POINT_INCREASE_COUNTER || "increase_counter"
);

console.log("Session created successfully:", {
expiry: sessionResult.sessionParams?.expiry
? new Date(Number(sessionResult.sessionParams.expiry) * 1000).toISOString()
: "No expiry",
dappKey: sessionResult.dappKey ? "Present" : "Missing"
});

// Re-fetch user from localStorage as it was updated by SessionService
user = JSON.parse(localStorage.getItem("user"));
console.log("Updated user data with session:", {
hasSession: !!user.session,
sessionExpiry: user.session?.expiry
? new Date(user.session.expiry).toISOString()
: "None"
});
} catch (sessionError) {
console.error("Failed to create initial session:", sessionError);
// Continue with wallet deployment even if session creation fails
}

localStorage.setItem("user", JSON.stringify(user));
dispatch({ type: "LOGIN", payload: user });
console.log("User state updated successfully");
}

return { success: true, data: executeTransaction.transactionHash };
} else {
const errorData = await response.json();
return {
success: false,
error: errorData.error || "Failed to update profile",
};
}
} catch (error: any) {
console.log(error);
console.error("Error:", error);
return {
success: false,
error: error.message || "An unexpected error occurred",
};

throw new Error(`Failed to update profile: ${response.statusText}`);
} catch (error) {
console.error("Error in wallet creation:", error);
return { success: false, error: error.message };
}
};

Expand Down
11 changes: 10 additions & 1 deletion client/src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@ import {
type User = {
id: string;
name: string;
public_key?: string;
argent_account?: string;
argent_public_key?: string;
braavos_account?: string;
braavos_public_key?: string;
token: string;
session?: {
expiry: number;
wallet: 'argent' | 'braavos';
dappKey?: string;
}
} | null;

export type AuthAction = { type: "LOGIN"; payload: User } | { type: "LOGOUT" };
Expand Down
Loading