From aefc72a480669547c5398bb3cb075c32df39f6ea Mon Sep 17 00:00:00 2001 From: bluecco Date: Tue, 17 Dec 2024 15:35:31 +0100 Subject: [PATCH 01/22] chore: refactor abi --- src/abi/DummyContract.json | 99 ------------- src/abi/ERC20.json | 258 ---------------------------------- src/abi/ERC20TransferAbi.json | 18 --- src/abi/dummyContractAbi.ts | 18 +++ src/abi/erc20TransferAbi.ts | 22 +++ 5 files changed, 40 insertions(+), 375 deletions(-) delete mode 100644 src/abi/DummyContract.json delete mode 100644 src/abi/ERC20.json delete mode 100644 src/abi/ERC20TransferAbi.json create mode 100644 src/abi/dummyContractAbi.ts create mode 100644 src/abi/erc20TransferAbi.ts diff --git a/src/abi/DummyContract.json b/src/abi/DummyContract.json deleted file mode 100644 index d1707c1..0000000 --- a/src/abi/DummyContract.json +++ /dev/null @@ -1,99 +0,0 @@ -[ - { - "type": "impl", - "name": "MockDappImpl", - "interface_name": "argent::mocks::mock_dapp::IMockDapp" - }, - { - "type": "interface", - "name": "argent::mocks::mock_dapp::IMockDapp", - "items": [ - { - "type": "function", - "name": "set_number", - "inputs": [ - { - "name": "number", - "type": "core::felt252" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "set_number_double", - "inputs": [ - { - "name": "number", - "type": "core::felt252" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "set_number_times3", - "inputs": [ - { - "name": "number", - "type": "core::felt252" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "increase_number", - "inputs": [ - { - "name": "number", - "type": "core::felt252" - } - ], - "outputs": [ - { - "type": "core::felt252" - } - ], - "state_mutability": "external" - }, - { - "type": "function", - "name": "throw_error", - "inputs": [ - { - "name": "number", - "type": "core::felt252" - } - ], - "outputs": [], - "state_mutability": "external" - }, - { - "type": "function", - "name": "get_number", - "inputs": [ - { - "name": "user", - "type": "core::starknet::contract_address::ContractAddress" - } - ], - "outputs": [ - { - "type": "core::felt252" - } - ], - "state_mutability": "view" - } - ] - }, - { - "type": "event", - "name": "argent::mocks::mock_dapp::MockDapp::Event", - "kind": "enum", - "variants": [] - } -] diff --git a/src/abi/ERC20.json b/src/abi/ERC20.json deleted file mode 100644 index 8203392..0000000 --- a/src/abi/ERC20.json +++ /dev/null @@ -1,258 +0,0 @@ -[ - { - "members": [ - { - "name": "low", - "offset": 0, - "type": "felt" - }, - { - "name": "high", - "offset": 1, - "type": "felt" - } - ], - "name": "Uint256", - "size": 2, - "type": "struct" - }, - { - "inputs": [ - { - "name": "name", - "type": "felt" - }, - { - "name": "symbol", - "type": "felt" - }, - { - "name": "recipient", - "type": "felt" - } - ], - "name": "constructor", - "outputs": [], - "type": "constructor" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "name", - "type": "felt" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "symbol", - "type": "felt" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "totalSupply", - "type": "Uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "decimals", - "type": "felt" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "name": "account", - "type": "felt" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "Uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "name": "owner", - "type": "felt" - }, - { - "name": "spender", - "type": "felt" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "remaining", - "type": "Uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "name": "recipient", - "type": "felt" - }, - { - "name": "amount", - "type": "Uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "success", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "sender", - "type": "felt" - }, - { - "name": "recipient", - "type": "felt" - }, - { - "name": "amount", - "type": "Uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "success", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "spender", - "type": "felt" - }, - { - "name": "amount", - "type": "Uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "success", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "spender", - "type": "felt" - }, - { - "name": "added_value", - "type": "Uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "name": "success", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "spender", - "type": "felt" - }, - { - "name": "subtracted_value", - "type": "Uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "name": "success", - "type": "felt" - } - ], - "type": "function" - }, - { - "inputs": [ - { - "name": "recipient", - "type": "felt" - }, - { - "name": "amount", - "type": "Uint256" - } - ], - "name": "mint", - "outputs": [], - "type": "function" - }, - { - "inputs": [ - { - "name": "user", - "type": "felt" - }, - { - "name": "amount", - "type": "Uint256" - } - ], - "name": "burn", - "outputs": [], - "type": "function" - } -] diff --git a/src/abi/ERC20TransferAbi.json b/src/abi/ERC20TransferAbi.json deleted file mode 100644 index c6ca556..0000000 --- a/src/abi/ERC20TransferAbi.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "type": "function", - "name": "transfer", - "state_mutability": "external", - "inputs": [ - { - "name": "recipient", - "type": "core::starknet::contract_address::ContractAddress" - }, - { - "name": "amount", - "type": "core::integer::u256" - } - ], - "outputs": [] - } -] diff --git a/src/abi/dummyContractAbi.ts b/src/abi/dummyContractAbi.ts new file mode 100644 index 0000000..0b22c19 --- /dev/null +++ b/src/abi/dummyContractAbi.ts @@ -0,0 +1,18 @@ +import { Abi } from "@starknet-react/core" + +const dummyContractAbi = [ + { + type: "function", + name: "set_number", + state_mutability: "external", + inputs: [ + { + name: "number", + type: "core::felt252", + }, + ], + outputs: [], + }, +] as const satisfies Abi + +export { dummyContractAbi } diff --git a/src/abi/erc20TransferAbi.ts b/src/abi/erc20TransferAbi.ts new file mode 100644 index 0000000..f8de4a2 --- /dev/null +++ b/src/abi/erc20TransferAbi.ts @@ -0,0 +1,22 @@ +import { Abi } from "@starknet-react/core" + +const erco20TransferAbi = [ + { + type: "function", + name: "transfer", + state_mutability: "external", + inputs: [ + { + name: "recipient", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "amount", + type: "core::integer::u256", + }, + ], + outputs: [], + }, +] as const satisfies Abi + +export { erco20TransferAbi } From e3587cfccf4f0c7c8e589d6d91924c11d87228b0 Mon Sep 17 00:00:00 2001 From: bluecco Date: Tue, 17 Dec 2024 15:35:52 +0100 Subject: [PATCH 02/22] chore: refactor helpers --- src/{helper => helpers}/token.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{helper => helpers}/token.ts (100%) diff --git a/src/helper/token.ts b/src/helpers/token.ts similarity index 100% rename from src/helper/token.ts rename to src/helpers/token.ts From 38ee68863be458dd2ba3c7e680d7111ff26c92bf Mon Sep 17 00:00:00 2001 From: bluecco Date: Tue, 17 Dec 2024 16:42:03 +0100 Subject: [PATCH 03/22] chore: update x-sessions --- package.json | 1 + pnpm-lock.yaml | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/package.json b/package.json index b247fd4..e29fa7d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "prepare": "husky" }, "dependencies": { + "@argent/x-sessions": "7.0.0-beta.2", "@starknet-io/get-starknet-core": "4.0.4", "@starknet-react/chains": "^3.1.0", "@starknet-react/core": "^3.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9428c9..063b74d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@argent/x-sessions': + specifier: 7.0.0-beta.2 + version: 7.0.0-beta.2(starknet@6.11.0) '@starknet-io/get-starknet-core': specifier: 4.0.4 version: 4.0.4 @@ -167,6 +170,11 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@argent/x-sessions@7.0.0-beta.2': + resolution: {integrity: sha512-SMD0RKqZbUQ/3nN4JVwBi4t+LSE00ZNieY4ivkqgHqMfnl1x6A/BqIM4NnWBn8GiOByZiCBAtb8OT7le+MJmcw==} + peerDependencies: + starknet: 6.11.0 + '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -3316,6 +3324,11 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@argent/x-sessions@7.0.0-beta.2(starknet@6.11.0)': + dependencies: + minimalistic-assert: 1.0.1 + starknet: 6.11.0 + '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 From cf2a0bcdd3c41b09525484fe3dec35366a01fc56 Mon Sep 17 00:00:00 2001 From: bluecco Date: Tue, 17 Dec 2024 16:47:29 +0100 Subject: [PATCH 04/22] refactor: types, sessionHelper and transfer components --- src/components/StarknetDapp.tsx | 13 +++- src/components/sections/SessionKeys/types.ts | 10 +++ .../sections/Transactions/SendERC20.tsx | 6 +- .../sections/Transactions/SendMulticall.tsx | 4 +- src/components/sections/Transactions/abi.ts | 22 ------- src/components/sections/types.ts | 1 + src/components/ui/Spinner.tsx | 19 ++++++ src/helpers/sessionKeys.ts | 61 +++++++++++++++++++ 8 files changed, 107 insertions(+), 29 deletions(-) create mode 100644 src/components/sections/SessionKeys/types.ts delete mode 100644 src/components/sections/Transactions/abi.ts create mode 100644 src/components/ui/Spinner.tsx create mode 100644 src/helpers/sessionKeys.ts diff --git a/src/components/StarknetDapp.tsx b/src/components/StarknetDapp.tsx index e870202..b947435 100644 --- a/src/components/StarknetDapp.tsx +++ b/src/components/StarknetDapp.tsx @@ -5,13 +5,14 @@ import { useAccount } from "@starknet-react/core" import { useState } from "react" import { Connect } from "./connect/Connect" import { Header } from "./Header" +import { GithubLogo } from "./icons/GithubLogo" import { AccountStatus } from "./sections/AccountStatus" import { AddToken } from "./sections/ERC20/AddToken" import { Network } from "./sections/Network/Network" import { SectionButton } from "./sections/SectionButton" -import { Section } from "./sections/types" -import { GithubLogo } from "./icons/GithubLogo" import { SectionLayout } from "./sections/SectionLayout" +import { SessionKeysSign } from "./sections/SessionKeys/SessionKeysSign" +import { Section } from "./sections/types" const StarknetDapp = () => { const [section, setSection] = useState
(undefined) @@ -91,6 +92,13 @@ const StarknetDapp = () => { disabled={!isConnected} className={`${!section ? "flex" : section === "ERC20" ? "flex" : "md:flex hidden"}`} /> + @@ -107,6 +115,7 @@ const StarknetDapp = () => { {section === "Signing" && } {section === "Network" && } {section === "ERC20" && } + {section === "SessionKeys" && } diff --git a/src/components/sections/SessionKeys/types.ts b/src/components/sections/SessionKeys/types.ts new file mode 100644 index 0000000..8207ab6 --- /dev/null +++ b/src/components/sections/SessionKeys/types.ts @@ -0,0 +1,10 @@ +import { Session } from "@argent/x-sessions" +import { Account, AccountInterface } from "starknet" + +export interface WithSessionAccount { + sessionAccount?: Account | AccountInterface +} + +export interface WithSession extends WithSessionAccount { + session: Session | undefined +} diff --git a/src/components/sections/Transactions/SendERC20.tsx b/src/components/sections/Transactions/SendERC20.tsx index 893386d..1642d47 100644 --- a/src/components/sections/Transactions/SendERC20.tsx +++ b/src/components/sections/Transactions/SendERC20.tsx @@ -1,5 +1,5 @@ import { ETHTokenAddress } from "@/constants" -import { parseInputAmountToUint256 } from "@/helper/token" +import { parseInputAmountToUint256 } from "@/helpers/token" import { useAccount, useContract, @@ -7,7 +7,7 @@ import { } from "@starknet-react/core" import { useState } from "react" import { Button } from "../../ui/Button" -import { abi } from "./abi" +import { erco20TransferAbi } from "../../../abi/erc20TransferAbi" const SendERC20 = () => { const { account } = useAccount() @@ -16,7 +16,7 @@ const SendERC20 = () => { const [lastTxError, setLastTxError] = useState("") const { contract } = useContract({ - abi, + abi: erco20TransferAbi, address: ETHTokenAddress, }) diff --git a/src/components/sections/Transactions/SendMulticall.tsx b/src/components/sections/Transactions/SendMulticall.tsx index 23bc9ab..9aa798f 100644 --- a/src/components/sections/Transactions/SendMulticall.tsx +++ b/src/components/sections/Transactions/SendMulticall.tsx @@ -1,6 +1,6 @@ // TokenOperations.tsx import { ETHTokenAddress } from "@/constants" -import { parseInputAmountToUint256 } from "@/helper/token" +import { parseInputAmountToUint256 } from "@/helpers/token" import { useAccount, useContract, @@ -8,7 +8,7 @@ import { } from "@starknet-react/core" import { useState } from "react" import { Button } from "../../ui/Button" -import { abi } from "./abi" +import { abi } from "../../../abi/erc20TransferAbi" const SendMulticall = () => { const { account } = useAccount() diff --git a/src/components/sections/Transactions/abi.ts b/src/components/sections/Transactions/abi.ts deleted file mode 100644 index 928d0de..0000000 --- a/src/components/sections/Transactions/abi.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Abi } from "@starknet-react/core" - -const abi = [ - { - type: "function", - name: "transfer", - state_mutability: "external", - inputs: [ - { - name: "recipient", - type: "core::starknet::contract_address::ContractAddress", - }, - { - name: "amount", - type: "core::integer::u256", - }, - ], - outputs: [], - }, -] as const satisfies Abi - -export { abi } diff --git a/src/components/sections/types.ts b/src/components/sections/types.ts index 722ea9d..3b4746d 100644 --- a/src/components/sections/types.ts +++ b/src/components/sections/types.ts @@ -5,3 +5,4 @@ export type Section = | "Signing" | "Network" | "ERC20" + | "SessionKeys" diff --git a/src/components/ui/Spinner.tsx b/src/components/ui/Spinner.tsx new file mode 100644 index 0000000..06e326b --- /dev/null +++ b/src/components/ui/Spinner.tsx @@ -0,0 +1,19 @@ +const Spinner = () => ( + + + + +) + +export { Spinner } diff --git a/src/helpers/sessionKeys.ts b/src/helpers/sessionKeys.ts new file mode 100644 index 0000000..2861b90 --- /dev/null +++ b/src/helpers/sessionKeys.ts @@ -0,0 +1,61 @@ +import { + ARGENT_DUMMY_CONTRACT_ADDRESS, + CHAIN_ID, + ETHTokenAddress, +} from "@/constants" +import { SessionKey } from "@argent/x-sessions" +import { constants, ec } from "starknet" +import { parseUnits } from "./token" + +/* Hardcoded values for session example */ +const ETHFees = [ + { + tokenAddress: ETHTokenAddress, + maxAmount: parseUnits("0.1", 18).value.toString(), + }, +] + +const STRKFees = [ + { + tokenAddress: + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + maxAmount: "10000000000000000", + }, + { + tokenAddress: + "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", + maxAmount: "200000000000000000000", + }, +] + +const allowedMethods = + CHAIN_ID === constants.NetworkName.SN_MAIN + ? [ + { + "Contract Address": ARGENT_DUMMY_CONTRACT_ADDRESS, + selector: "set_number", + }, + ] + : [ + { + "Contract Address": ETHTokenAddress, + selector: "transfer", + }, + ] + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const expiry = Math.floor((Date.now() + 1000 * 60 * 60 * 24) / 1000) as any + +const metaData = (isStarkFeeToken: boolean) => ({ + projectID: "test-dapp", + txFees: isStarkFeeToken ? STRKFees : ETHFees, +}) + +const privateKey = ec.starkCurve.utils.randomPrivateKey() + +const sessionKey: SessionKey = { + privateKey, + publicKey: ec.starkCurve.getStarkKey(privateKey), +} + +export { allowedMethods, sessionKey, expiry, metaData } From d67894c52461c519069292e27fe6f7c91e597458 Mon Sep 17 00:00:00 2001 From: bluecco Date: Tue, 17 Dec 2024 16:48:13 +0100 Subject: [PATCH 05/22] feat: session keys sign and execute --- .../Execute/SessionKeysExecute.tsx | 50 ++++++++++ .../SessionKeys/Execute/useMainnetContract.ts | 52 ++++++++++ .../SessionKeys/Execute/useTestnetContract.ts | 54 +++++++++++ .../sections/SessionKeys/SessionKeysSign.tsx | 94 +++++++++++++++++++ 4 files changed, 250 insertions(+) create mode 100644 src/components/sections/SessionKeys/Execute/SessionKeysExecute.tsx create mode 100644 src/components/sections/SessionKeys/Execute/useMainnetContract.ts create mode 100644 src/components/sections/SessionKeys/Execute/useTestnetContract.ts create mode 100644 src/components/sections/SessionKeys/SessionKeysSign.tsx diff --git a/src/components/sections/SessionKeys/Execute/SessionKeysExecute.tsx b/src/components/sections/SessionKeys/Execute/SessionKeysExecute.tsx new file mode 100644 index 0000000..4ecdbc6 --- /dev/null +++ b/src/components/sections/SessionKeys/Execute/SessionKeysExecute.tsx @@ -0,0 +1,50 @@ +import { CHAIN_ID } from "@/constants" +import { FC, useState } from "react" +import { constants } from "starknet" +import { WithSessionAccount } from "../types" +import { useMainnetContract } from "./useMainnetContract" +import { useTestnetContract } from "./useTestnetContract" +import { Button } from "@/components/ui/Button" +import { Spinner } from "@/components/ui/Spinner" + +const SessionKeysExecute: FC = ({ sessionAccount }) => { + const submitTestnetTransaction = useTestnetContract({ sessionAccount }) + const submitMainnetTransaction = useMainnetContract({ sessionAccount }) + const [isSubmitting, setIsSubmitting] = useState(false) + + const handleSignSessionKeys = async () => { + try { + setIsSubmitting(true) + const transaction_hash = + CHAIN_ID === constants.NetworkName.SN_MAIN + ? await submitMainnetTransaction?.() + : await submitTestnetTransaction?.() + + setTimeout(() => { + alert(`Transaction sent: ${transaction_hash}`) + }) + + setIsSubmitting(true) + } catch (error) { + console.error(error) + } finally { + setIsSubmitting(false) + } + } + + return ( + <> +

Execute session transaction

+ + + ) +} + +export { SessionKeysExecute } diff --git a/src/components/sections/SessionKeys/Execute/useMainnetContract.ts b/src/components/sections/SessionKeys/Execute/useMainnetContract.ts new file mode 100644 index 0000000..2f0d642 --- /dev/null +++ b/src/components/sections/SessionKeys/Execute/useMainnetContract.ts @@ -0,0 +1,52 @@ +import { ARGENT_DUMMY_CONTRACT_ADDRESS, CHAIN_ID } from "@/constants" +import { useAccount, useContract } from "@starknet-react/core" +import { constants } from "starknet" +import { dummyContractAbi } from "../../../../abi/dummyContractAbi" +import { WithSessionAccount } from "../types" + +const useMainnetContract = ({ sessionAccount }: WithSessionAccount) => { + const { address } = useAccount() + const { contract: mainnetContract } = useContract({ + abi: dummyContractAbi, + address: ARGENT_DUMMY_CONTRACT_ADDRESS, + provider: sessionAccount, + }) + + const submitMainnetTransaction = async () => { + if (!address) { + throw new Error("No address") + } + + if (!sessionAccount) { + throw new Error("No session account") + } + + const transferCallData = mainnetContract.populate("set_number", { + number: 1, + }) + + // https://www.starknetjs.com/docs/guides/estimate_fees/#estimateinvokefee + const { suggestedMaxFee } = await sessionAccount.estimateInvokeFee({ + contractAddress: ARGENT_DUMMY_CONTRACT_ADDRESS, + entrypoint: "set_number", + calldata: transferCallData.calldata, + }) + + // https://www.starknetjs.com/docs/guides/estimate_fees/#fee-limitation + const maxFee = (suggestedMaxFee * BigInt(15)) / BigInt(10) + // send to same account + const { transaction_hash } = await mainnetContract.set_number( + transferCallData.calldata, + { + maxFee, + }, + ) + return transaction_hash + } + + return CHAIN_ID === constants.NetworkName.SN_MAIN + ? submitMainnetTransaction + : null +} + +export { useMainnetContract } diff --git a/src/components/sections/SessionKeys/Execute/useTestnetContract.ts b/src/components/sections/SessionKeys/Execute/useTestnetContract.ts new file mode 100644 index 0000000..5360b4e --- /dev/null +++ b/src/components/sections/SessionKeys/Execute/useTestnetContract.ts @@ -0,0 +1,54 @@ +import { CHAIN_ID, ETHTokenAddress } from "@/constants" +import { parseInputAmountToUint256 } from "@/helpers/token" +import { useAccount, useContract } from "@starknet-react/core" +import { constants } from "starknet" +import { erco20TransferAbi } from "../../../../abi/erc20TransferAbi" +import { WithSessionAccount } from "../types" + +const useTestnetContract = ({ sessionAccount }: WithSessionAccount) => { + const { address } = useAccount() + const { contract } = useContract({ + abi: erco20TransferAbi, + address: ETHTokenAddress, + provider: sessionAccount, + }) + + const submitTestnetTransaction = async () => { + if (!address) { + throw new Error("No address") + } + + if (!sessionAccount) { + throw new Error("No session account") + } + + const transferCallData = contract.populate("transfer", { + recipient: address.toString(), + amount: parseInputAmountToUint256("0.000000001"), + }) + + // https://www.starknetjs.com/docs/guides/estimate_fees/#estimateinvokefee + const { suggestedMaxFee } = await sessionAccount.estimateInvokeFee({ + contractAddress: ETHTokenAddress, + entrypoint: "transfer", + calldata: transferCallData.calldata, + }) + + // https://www.starknetjs.com/docs/guides/estimate_fees/#fee-limitation + const maxFee = (suggestedMaxFee * BigInt(15)) / BigInt(10) + // send to same account + const { transaction_hash } = await contract.transfer( + transferCallData.calldata, + { + maxFee, + }, + ) + return transaction_hash + } + + return CHAIN_ID === constants.NetworkName.SN_SEPOLIA + ? submitTestnetTransaction + : null +} + +export { useTestnetContract } diff --git a/src/components/sections/SessionKeys/SessionKeysSign.tsx b/src/components/sections/SessionKeys/SessionKeysSign.tsx new file mode 100644 index 0000000..a58e8d6 --- /dev/null +++ b/src/components/sections/SessionKeys/SessionKeysSign.tsx @@ -0,0 +1,94 @@ +import { Button } from "@/components/ui/Button" +import { toHexChainid } from "@/helpers/chainId" +import { + allowedMethods, + expiry, + metaData, + sessionKey, +} from "@/helpers/sessionKeys" +import { + buildSessionAccount, + createSession, + CreateSessionParams, + createSessionRequest, + Session, +} from "@argent/x-sessions" +import { useAccount, useSignTypedData } from "@starknet-react/core" +import { useState } from "react" +import { Account, AccountInterface, constants } from "starknet" +import { SectionLayout } from "../SectionLayout" +import { ARGENT_SESSION_SERVICE_BASE_URL, provider } from "@/constants" +import { SessionKeysExecute } from "./Execute/SessionKeysExecute" +import { SessionKeysExecuteOutside } from "./ExecuteFromOutside/SessionKeysExecuteOutside" +import { SessionKeysTypedDataOutside } from "./TypedDataExecuteFromOutside/SessionKeysTypedDataOutside" + +const SessionKeysSign = () => { + const { address, chainId } = useAccount() + const [session, setSession] = useState() + const [sessionAccount, setSessionAccount] = useState< + Account | AccountInterface | undefined + >() + + const sessionParams: CreateSessionParams = { + allowedMethods, + expiry, + metaData: metaData(false), + sessionKey, + } + + const hexChainId = toHexChainid(chainId) + + const sessionRequest = createSessionRequest({ + sessionParams, + chainId: hexChainId as constants.StarknetChainId, + }) + + const { signTypedDataAsync } = useSignTypedData({ + params: sessionRequest.sessionTypedData, + }) + + const handleSignSessionKeys = async () => { + if (!address || !chainId) { + throw new Error("No address or chainId") + } + + const authorisationSignature = await signTypedDataAsync() + const sessionObj = await createSession({ + address: address, + chainId: hexChainId as constants.StarknetChainId, + authorisationSignature, + sessionRequest, + }) + + const sessionAccount = await buildSessionAccount({ + session: sessionObj, + sessionKey, + provider, + argentSessionServiceBaseUrl: ARGENT_SESSION_SERVICE_BASE_URL, + }) + + setSession(sessionObj) + setSessionAccount(sessionAccount) + } + + return ( + +
+ +
+ + + +
+ ) +} + +export { SessionKeysSign } From 24dd02ab05dbc46aa4995d0d93bd28651398f6fb Mon Sep 17 00:00:00 2001 From: bluecco Date: Tue, 17 Dec 2024 16:49:09 +0100 Subject: [PATCH 06/22] feat: execute from outside components and logic --- .../SessionKeysExecuteOutside.tsx | 49 +++++++++++++++ .../ExecuteFromOutside/useMainnetContract.ts | 52 ++++++++++++++++ .../ExecuteFromOutside/useTestnetContract.ts | 60 +++++++++++++++++++ .../SessionKeys/SessionKeysEFOLayout.tsx | 46 ++++++++++++++ .../SessionKeysTypedDataOutside.tsx | 49 +++++++++++++++ .../useMainnetContract.ts | 52 ++++++++++++++++ .../useTestnetContract.ts | 60 +++++++++++++++++++ 7 files changed, 368 insertions(+) create mode 100644 src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx create mode 100644 src/components/sections/SessionKeys/ExecuteFromOutside/useMainnetContract.ts create mode 100644 src/components/sections/SessionKeys/ExecuteFromOutside/useTestnetContract.ts create mode 100644 src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx create mode 100644 src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx create mode 100644 src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useMainnetContract.ts create mode 100644 src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useTestnetContract.ts diff --git a/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx b/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx new file mode 100644 index 0000000..a01bda0 --- /dev/null +++ b/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx @@ -0,0 +1,49 @@ +import { CHAIN_ID } from "@/constants" +import { FC, useState } from "react" +import { constants } from "starknet" +import { SessionKeysEFOLayout } from "../SessionKeysEFOLayout" +import { WithSession } from "../types" +import { useMainnetContract } from "./useMainnetContract" +import { useTestnetContract } from "./useTestnetContract" + +const SessionKeysExecuteOutside: FC = ({ + session, + sessionAccount, +}) => { + const submitTestnetEFO = useTestnetContract({ + session, + sessionAccount, + }) + const submitMainnetEFO = useMainnetContract({ + session, + sessionAccount, + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [outsideExecution, setOutsideExecution] = useState() + + const handleSubmitEFO = async () => { + try { + const efoExecute = + CHAIN_ID === constants.NetworkName.SN_MAIN + ? await submitMainnetEFO?.() + : await submitTestnetEFO?.() + + setOutsideExecution(efoExecute) + } catch (error) { + console.error(error) + } + } + + return ( + + ) +} + +export { SessionKeysExecuteOutside } diff --git a/src/components/sections/SessionKeys/ExecuteFromOutside/useMainnetContract.ts b/src/components/sections/SessionKeys/ExecuteFromOutside/useMainnetContract.ts new file mode 100644 index 0000000..231979c --- /dev/null +++ b/src/components/sections/SessionKeys/ExecuteFromOutside/useMainnetContract.ts @@ -0,0 +1,52 @@ +import { dummyContractAbi } from "@/abi/dummyContractAbi" +import { + ARGENT_DUMMY_CONTRACT_ADDRESS, + ARGENT_SESSION_SERVICE_BASE_URL, + CHAIN_ID, +} from "@/constants" +import { sessionKey } from "@/helpers/sessionKeys" +import { createOutsideExecutionCall } from "@argent/x-sessions" +import { useContract } from "@starknet-react/core" +import { constants } from "starknet" +import { WithSession } from "../types" + +const useMainnetContract = ({ session, sessionAccount }: WithSession) => { + const { contract: mainnetContract } = useContract({ + abi: dummyContractAbi, + address: ARGENT_DUMMY_CONTRACT_ADDRESS, + provider: sessionAccount, + }) + + const submitMainnetEFO = async () => { + try { + if (!session || !sessionAccount) { + throw new Error("No open session") + } + // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 + // check .populate + const transferCallData = mainnetContract.populate("set_number", { + number: 1, + }) + + const efoExecutionCall = await createOutsideExecutionCall({ + session, + sessionKey, + calls: [transferCallData], + argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, + }) + + console.log( + "execute from outside response", + JSON.stringify(efoExecutionCall), + ) + + return efoExecutionCall + } catch (e) { + console.error(e) + } + } + + return CHAIN_ID === constants.NetworkName.SN_MAIN ? submitMainnetEFO : null +} + +export { useMainnetContract } diff --git a/src/components/sections/SessionKeys/ExecuteFromOutside/useTestnetContract.ts b/src/components/sections/SessionKeys/ExecuteFromOutside/useTestnetContract.ts new file mode 100644 index 0000000..770d939 --- /dev/null +++ b/src/components/sections/SessionKeys/ExecuteFromOutside/useTestnetContract.ts @@ -0,0 +1,60 @@ +import { erco20TransferAbi } from "@/abi/erc20TransferAbi" +import { + ARGENT_SESSION_SERVICE_BASE_URL, + CHAIN_ID, + ETHTokenAddress, +} from "@/constants" +import { sessionKey } from "@/helpers/sessionKeys" +import { parseInputAmountToUint256 } from "@/helpers/token" +import { createOutsideExecutionCall } from "@argent/x-sessions" +import { useAccount, useContract } from "@starknet-react/core" +import { constants } from "starknet" +import { WithSession } from "../types" + +const useTestnetContract = ({ session, sessionAccount }: WithSession) => { + const { address } = useAccount() + const { contract: testnetContract } = useContract({ + abi: erco20TransferAbi, + address: ETHTokenAddress, + provider: sessionAccount, + }) + + const submitTestnetEFO = async () => { + try { + if (!address) { + throw new Error("No address") + } + + if (!session || !sessionAccount) { + throw new Error("No open session") + } + + // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 + // check .populate + const transferCallData = testnetContract.populate("transfer", { + recipient: address.toString(), + amount: parseInputAmountToUint256("0.000000001"), + }) + + const efoExecutionCall = await createOutsideExecutionCall({ + session, + sessionKey, + calls: [transferCallData], + argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, + }) + + console.log( + "execute from outside response", + JSON.stringify(efoExecutionCall), + ) + + return efoExecutionCall + } catch (e) { + console.error(e) + } + } + + return CHAIN_ID === constants.NetworkName.SN_SEPOLIA ? submitTestnetEFO : null +} + +export { useTestnetContract } diff --git a/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx b/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx new file mode 100644 index 0000000..edaa939 --- /dev/null +++ b/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx @@ -0,0 +1,46 @@ +import { Button } from "@/components/ui/Button" +import { FC } from "react" + +interface SessionKeysEFOLayoutProps { + handleSubmit: () => Promise + copyData: string + submitDisabled: boolean + copyDataDisabled: boolean + title: string +} + +const SessionKeysEFOLayout: FC = ({ + handleSubmit, + copyData, + submitDisabled, + copyDataDisabled, + title, +}) => { + const copy = () => { + navigator.clipboard.writeText(JSON.stringify(copyData)) + } + + return ( +
+

{title}

+ + +
+ ) +} + +export { SessionKeysEFOLayout } diff --git a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx new file mode 100644 index 0000000..55a35c8 --- /dev/null +++ b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx @@ -0,0 +1,49 @@ +import { CHAIN_ID } from "@/constants" +import { FC, useState } from "react" +import { constants } from "starknet" +import { SessionKeysEFOLayout } from "../SessionKeysEFOLayout" +import { WithSession } from "../types" +import { useMainnetContract } from "./useMainnetContract" +import { useTestnetContract } from "./useTestnetContract" + +const SessionKeysTypedDataOutside: FC = ({ + session, + sessionAccount, +}) => { + const submitTestnetEFO = useTestnetContract({ + session, + sessionAccount, + }) + const submitMainnetEFO = useMainnetContract({ + session, + sessionAccount, + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [outsideTypedData, setOutsideTypedData] = useState() + + const handleSubmitEFO = async () => { + try { + const efoTypedData = + CHAIN_ID === constants.NetworkName.SN_MAIN + ? await submitMainnetEFO?.() + : await submitTestnetEFO?.() + + setOutsideTypedData(efoTypedData) + } catch (error) { + console.error(error) + } + } + + return ( + + ) +} + +export { SessionKeysTypedDataOutside } diff --git a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useMainnetContract.ts b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useMainnetContract.ts new file mode 100644 index 0000000..b00627e --- /dev/null +++ b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useMainnetContract.ts @@ -0,0 +1,52 @@ +import { dummyContractAbi } from "@/abi/dummyContractAbi" +import { + ARGENT_DUMMY_CONTRACT_ADDRESS, + ARGENT_SESSION_SERVICE_BASE_URL, + CHAIN_ID, +} from "@/constants" +import { sessionKey } from "@/helpers/sessionKeys" +import { createOutsideExecutionTypedData } from "@argent/x-sessions" +import { useContract } from "@starknet-react/core" +import { constants } from "starknet" +import { WithSession } from "../types" + +const useMainnetContract = ({ session, sessionAccount }: WithSession) => { + const { contract: mainnetContract } = useContract({ + abi: dummyContractAbi, + address: ARGENT_DUMMY_CONTRACT_ADDRESS, + provider: sessionAccount, + }) + + const submitMainnetEFO = async () => { + try { + if (!session || !sessionAccount) { + throw new Error("No open session") + } + // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 + // check .populate + const transferCallData = mainnetContract.populate("set_number", { + number: 1, + }) + + const efoTypedData = await createOutsideExecutionTypedData({ + session, + sessionKey, + calls: [transferCallData], + argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, + }) + + console.log( + "execute from outside typed data response", + JSON.stringify(efoTypedData), + ) + + return efoTypedData + } catch (e) { + console.error(e) + } + } + + return CHAIN_ID === constants.NetworkName.SN_MAIN ? submitMainnetEFO : null +} + +export { useMainnetContract } diff --git a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useTestnetContract.ts b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useTestnetContract.ts new file mode 100644 index 0000000..57cc18d --- /dev/null +++ b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useTestnetContract.ts @@ -0,0 +1,60 @@ +import { erco20TransferAbi } from "@/abi/erc20TransferAbi" +import { + ARGENT_SESSION_SERVICE_BASE_URL, + CHAIN_ID, + ETHTokenAddress, +} from "@/constants" +import { sessionKey } from "@/helpers/sessionKeys" +import { parseInputAmountToUint256 } from "@/helpers/token" +import { createOutsideExecutionTypedData } from "@argent/x-sessions" +import { useAccount, useContract } from "@starknet-react/core" +import { constants } from "starknet" +import { WithSession } from "../types" + +const useTestnetContract = ({ session, sessionAccount }: WithSession) => { + const { address } = useAccount() + const { contract: testnetContract } = useContract({ + abi: erco20TransferAbi, + address: ETHTokenAddress, + provider: sessionAccount, + }) + + const submitTestnetEFO = async () => { + try { + if (!address) { + throw new Error("No address") + } + + if (!session || !sessionAccount) { + throw new Error("No open session") + } + + // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 + // check .populate + const transferCallData = testnetContract.populate("transfer", { + recipient: address.toString(), + amount: parseInputAmountToUint256("0.000000001"), + }) + + const efoTypedData = await createOutsideExecutionTypedData({ + session, + sessionKey, + calls: [transferCallData], + argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, + }) + + console.log( + "execute from outside typed data response", + JSON.stringify(efoTypedData), + ) + + return efoTypedData + } catch (e) { + console.error(e) + } + } + + return CHAIN_ID === constants.NetworkName.SN_SEPOLIA ? submitTestnetEFO : null +} + +export { useTestnetContract } From 91205cef49265b41db786fcd73ff7ea87c954ff8 Mon Sep 17 00:00:00 2001 From: bluecco Date: Tue, 17 Dec 2024 16:58:49 +0100 Subject: [PATCH 07/22] fix: build --- src/components/sections/Transactions/SendMulticall.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/sections/Transactions/SendMulticall.tsx b/src/components/sections/Transactions/SendMulticall.tsx index 9aa798f..4e50f0a 100644 --- a/src/components/sections/Transactions/SendMulticall.tsx +++ b/src/components/sections/Transactions/SendMulticall.tsx @@ -8,7 +8,7 @@ import { } from "@starknet-react/core" import { useState } from "react" import { Button } from "../../ui/Button" -import { abi } from "../../../abi/erc20TransferAbi" +import { erco20TransferAbi } from "../../../abi/erc20TransferAbi" const SendMulticall = () => { const { account } = useAccount() @@ -17,7 +17,7 @@ const SendMulticall = () => { const [lastTxError, setLastTxError] = useState("") const { contract } = useContract({ - abi, + abi: erco20TransferAbi, address: ETHTokenAddress, }) From 6d4fff4ed76d6c91551fcc039fcf3f99202cb83a Mon Sep 17 00:00:00 2001 From: bluecco Date: Tue, 17 Dec 2024 17:54:25 +0100 Subject: [PATCH 08/22] chore: add e2e --- e2e/src/webwallet/page-objects/Dapps.ts | 26 ++++++++++++++ e2e/src/webwallet/specs/sessionKeys.spec.ts | 18 ++++++++++ src/components/StarknetDapp.tsx | 1 + src/components/sections/SectionButton.tsx | 4 ++- .../Execute/SessionKeysExecute.tsx | 2 +- .../SessionKeysExecuteOutside.tsx | 1 + .../SessionKeys/SessionKeysEFOLayout.tsx | 6 ++-- .../sections/SessionKeys/SessionKeysSign.tsx | 34 +++++++++++-------- .../SessionKeysTypedDataOutside.tsx | 1 + 9 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 e2e/src/webwallet/specs/sessionKeys.spec.ts diff --git a/e2e/src/webwallet/page-objects/Dapps.ts b/e2e/src/webwallet/page-objects/Dapps.ts index a070b84..09b4e31 100644 --- a/e2e/src/webwallet/page-objects/Dapps.ts +++ b/e2e/src/webwallet/page-objects/Dapps.ts @@ -154,4 +154,30 @@ export default class Dapps extends Navigation { expect(dialog.message()).toContain("Not implemented") await dialog.accept() } + + async sessionKeys() { + const popupPromise = this.dApp.waitForEvent("popup") + + await this.dApp.locator('button :text-is("Session Keys")').click() + await this.dApp.waitForTimeout(2500) + const [, popup] = await Promise.all([ + this.dApp.locator(`button :text-is("Create session")`).click(), + popupPromise, + ]) + + await popup + .getByRole("button") + .and(popup.getByText("Start session")) + .click() + await this.dApp.waitForTimeout(2500) + + const dialogPromise = this.dApp.waitForEvent("dialog") + const [, dialog] = await Promise.all([ + this.dApp.getByText("Submit session tx").click(), + dialogPromise, + ]) + + expect(dialog.message()).toContain("Transaction sent") + await dialog.accept() + } } diff --git a/e2e/src/webwallet/specs/sessionKeys.spec.ts b/e2e/src/webwallet/specs/sessionKeys.spec.ts new file mode 100644 index 0000000..5a6672d --- /dev/null +++ b/e2e/src/webwallet/specs/sessionKeys.spec.ts @@ -0,0 +1,18 @@ +import test from "../test" +import config from "../config" + +test.describe(`Session Keys`, () => { + test(`create a sessions, send a transaction a get EFO data`, async ({ + webWallet, + dApp, + }) => { + await webWallet.dapps.requestConnectionFromDapp({ + dApp, + credentials: config.validLogin, + newAccount: false, + useStarknetKitModal: true, + }) + + await webWallet.dapps.sessionKeys() + }) +}) diff --git a/src/components/StarknetDapp.tsx b/src/components/StarknetDapp.tsx index b947435..3a9805f 100644 --- a/src/components/StarknetDapp.tsx +++ b/src/components/StarknetDapp.tsx @@ -94,6 +94,7 @@ const StarknetDapp = () => { /> = ({ disabled, section, + label, selected, setSection, className, @@ -32,7 +34,7 @@ const SectionButton: FC = ({ ) }} > - {section} + {label || section} ) diff --git a/src/components/sections/SessionKeys/Execute/SessionKeysExecute.tsx b/src/components/sections/SessionKeys/Execute/SessionKeysExecute.tsx index 4ecdbc6..b783584 100644 --- a/src/components/sections/SessionKeys/Execute/SessionKeysExecute.tsx +++ b/src/components/sections/SessionKeys/Execute/SessionKeysExecute.tsx @@ -41,7 +41,7 @@ const SessionKeysExecute: FC = ({ sessionAccount }) => { onClick={handleSignSessionKeys} hideChevron > - Submit {isSubmitting ? : ""} + Submit session tx {isSubmitting ? : ""} ) diff --git a/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx b/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx index a01bda0..5fd9cd4 100644 --- a/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx +++ b/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx @@ -42,6 +42,7 @@ const SessionKeysExecuteOutside: FC = ({ handleSubmit={handleSubmitEFO} submitDisabled={!sessionAccount} title="Create outside execution call" + submitText="EFO" /> ) } diff --git a/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx b/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx index edaa939..ae5ceba 100644 --- a/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx +++ b/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx @@ -7,6 +7,7 @@ interface SessionKeysEFOLayoutProps { submitDisabled: boolean copyDataDisabled: boolean title: string + submitText: string } const SessionKeysEFOLayout: FC = ({ @@ -15,6 +16,7 @@ const SessionKeysEFOLayout: FC = ({ submitDisabled, copyDataDisabled, title, + submitText, }) => { const copy = () => { navigator.clipboard.writeText(JSON.stringify(copyData)) @@ -29,7 +31,7 @@ const SessionKeysEFOLayout: FC = ({ disabled={submitDisabled} hideChevron > - Submit + Submit {submitText} ) diff --git a/src/components/sections/SessionKeys/SessionKeysSign.tsx b/src/components/sections/SessionKeys/SessionKeysSign.tsx index a58e8d6..0492320 100644 --- a/src/components/sections/SessionKeys/SessionKeysSign.tsx +++ b/src/components/sections/SessionKeys/SessionKeysSign.tsx @@ -52,23 +52,27 @@ const SessionKeysSign = () => { throw new Error("No address or chainId") } - const authorisationSignature = await signTypedDataAsync() - const sessionObj = await createSession({ - address: address, - chainId: hexChainId as constants.StarknetChainId, - authorisationSignature, - sessionRequest, - }) + try { + const authorisationSignature = await signTypedDataAsync() + const sessionObj = await createSession({ + address: address, + chainId: hexChainId as constants.StarknetChainId, + authorisationSignature, + sessionRequest, + }) - const sessionAccount = await buildSessionAccount({ - session: sessionObj, - sessionKey, - provider, - argentSessionServiceBaseUrl: ARGENT_SESSION_SERVICE_BASE_URL, - }) + const sessionAccount = await buildSessionAccount({ + session: sessionObj, + sessionKey, + provider, + argentSessionServiceBaseUrl: ARGENT_SESSION_SERVICE_BASE_URL, + }) - setSession(sessionObj) - setSessionAccount(sessionAccount) + setSession(sessionObj) + setSessionAccount(sessionAccount) + } catch (e) { + console.error(e) + } } return ( diff --git a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx index 55a35c8..20f2c05 100644 --- a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx +++ b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx @@ -42,6 +42,7 @@ const SessionKeysTypedDataOutside: FC = ({ handleSubmit={handleSubmitEFO} submitDisabled={!sessionAccount} title="Create outside typed data" + submitText="EFO TypedData" /> ) } From db01a9b76cd333df8bb14e92f284ca7d976dcfd9 Mon Sep 17 00:00:00 2001 From: bluecco Date: Wed, 18 Dec 2024 13:26:40 +0100 Subject: [PATCH 09/22] chore: e2e tests --- e2e/src/webwallet/page-objects/Dapps.ts | 25 ++++++++++++- src/components/icons/SessionKesIcon.tsx | 37 +++++++++++++++++++ src/components/sections/SectionButton.tsx | 1 + .../SessionKeysExecuteOutside.tsx | 3 +- .../SessionKeys/SessionKeysEFOLayout.tsx | 37 +++++++++++-------- .../sections/SessionKeys/SessionKeysSign.tsx | 11 +++--- .../SessionKeysTypedDataOutside.tsx | 3 +- src/components/ui/Button.tsx | 4 +- 8 files changed, 94 insertions(+), 27 deletions(-) create mode 100644 src/components/icons/SessionKesIcon.tsx diff --git a/e2e/src/webwallet/page-objects/Dapps.ts b/e2e/src/webwallet/page-objects/Dapps.ts index 09b4e31..ff34d6e 100644 --- a/e2e/src/webwallet/page-objects/Dapps.ts +++ b/e2e/src/webwallet/page-objects/Dapps.ts @@ -159,7 +159,7 @@ export default class Dapps extends Navigation { const popupPromise = this.dApp.waitForEvent("popup") await this.dApp.locator('button :text-is("Session Keys")').click() - await this.dApp.waitForTimeout(2500) + await this.dApp.waitForTimeout(100) const [, popup] = await Promise.all([ this.dApp.locator(`button :text-is("Create session")`).click(), popupPromise, @@ -169,7 +169,7 @@ export default class Dapps extends Navigation { .getByRole("button") .and(popup.getByText("Start session")) .click() - await this.dApp.waitForTimeout(2500) + await this.dApp.waitForTimeout(100) const dialogPromise = this.dApp.waitForEvent("dialog") const [, dialog] = await Promise.all([ @@ -179,5 +179,26 @@ export default class Dapps extends Navigation { expect(dialog.message()).toContain("Transaction sent") await dialog.accept() + + await this.dApp.waitForTimeout(500) + await this.dApp.getByText("Submit EFO call").click() + const dialogPromiseEFO = this.dApp.waitForEvent("dialog") + await this.dApp.waitForTimeout(100) + this.dApp.getByText("Copy EFO call").click() + const dialogEFO = await dialogPromiseEFO + await this.dApp.waitForTimeout(500) + expect(dialogEFO.message()).toContain("Data copied in your clipboard") + await dialogEFO.accept() + + await this.dApp.getByText("Submit EFO TypedData").click() + const dialogPromiseEFOTypedData = this.dApp.waitForEvent("dialog") + await this.dApp.waitForTimeout(100) + this.dApp.getByText("Copy EFO TypedData").click() + const dialogEFOTypedData = await dialogPromiseEFOTypedData + await this.dApp.waitForTimeout(500) + expect(dialogEFOTypedData.message()).toContain( + "Data copied in your clipboard", + ) + await dialogEFOTypedData.accept() } } diff --git a/src/components/icons/SessionKesIcon.tsx b/src/components/icons/SessionKesIcon.tsx new file mode 100644 index 0000000..fdea433 --- /dev/null +++ b/src/components/icons/SessionKesIcon.tsx @@ -0,0 +1,37 @@ +const SessionKeysIcon = () => ( + + + + + + + + + + + + +) + +export { SessionKeysIcon } diff --git a/src/components/sections/SectionButton.tsx b/src/components/sections/SectionButton.tsx index fe14ce6..a2f67d8 100644 --- a/src/components/sections/SectionButton.tsx +++ b/src/components/sections/SectionButton.tsx @@ -24,6 +24,7 @@ const SectionButton: FC = ({ className, }) => ( - +
+ + +
) } diff --git a/src/components/sections/SessionKeys/SessionKeysSign.tsx b/src/components/sections/SessionKeys/SessionKeysSign.tsx index 0492320..572b273 100644 --- a/src/components/sections/SessionKeys/SessionKeysSign.tsx +++ b/src/components/sections/SessionKeys/SessionKeysSign.tsx @@ -21,6 +21,7 @@ import { ARGENT_SESSION_SERVICE_BASE_URL, provider } from "@/constants" import { SessionKeysExecute } from "./Execute/SessionKeysExecute" import { SessionKeysExecuteOutside } from "./ExecuteFromOutside/SessionKeysExecuteOutside" import { SessionKeysTypedDataOutside } from "./TypedDataExecuteFromOutside/SessionKeysTypedDataOutside" +import { SessionKeysIcon } from "@/components/icons/SessionKesIcon" const SessionKeysSign = () => { const { address, chainId } = useAccount() @@ -76,12 +77,10 @@ const SessionKeysSign = () => { } return ( - -
- -
+ }> + = ({ handleSubmit={handleSubmitEFO} submitDisabled={!sessionAccount} title="Create outside typed data" - submitText="EFO TypedData" + submitText="Submit EFO TypedData" + copyText="Copy EFO TypedData" /> ) } diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index add709d..e215d6e 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -5,6 +5,7 @@ interface ButtonProps extends ButtonHTMLAttributes { selected?: boolean leftIcon?: ReactNode rightIcon?: ReactNode + responsiveChevron?: boolean } const Button: FC = ({ @@ -15,6 +16,7 @@ const Button: FC = ({ selected, leftIcon, rightIcon, + responsiveChevron, ...props }) => ( - - ) -} - -export { SessionKeysExecute } diff --git a/src/components/sections/SessionKeys/Execute/useMainnetContract.ts b/src/components/sections/SessionKeys/Execute/useMainnetContract.ts deleted file mode 100644 index 2f0d642..0000000 --- a/src/components/sections/SessionKeys/Execute/useMainnetContract.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ARGENT_DUMMY_CONTRACT_ADDRESS, CHAIN_ID } from "@/constants" -import { useAccount, useContract } from "@starknet-react/core" -import { constants } from "starknet" -import { dummyContractAbi } from "../../../../abi/dummyContractAbi" -import { WithSessionAccount } from "../types" - -const useMainnetContract = ({ sessionAccount }: WithSessionAccount) => { - const { address } = useAccount() - const { contract: mainnetContract } = useContract({ - abi: dummyContractAbi, - address: ARGENT_DUMMY_CONTRACT_ADDRESS, - provider: sessionAccount, - }) - - const submitMainnetTransaction = async () => { - if (!address) { - throw new Error("No address") - } - - if (!sessionAccount) { - throw new Error("No session account") - } - - const transferCallData = mainnetContract.populate("set_number", { - number: 1, - }) - - // https://www.starknetjs.com/docs/guides/estimate_fees/#estimateinvokefee - const { suggestedMaxFee } = await sessionAccount.estimateInvokeFee({ - contractAddress: ARGENT_DUMMY_CONTRACT_ADDRESS, - entrypoint: "set_number", - calldata: transferCallData.calldata, - }) - - // https://www.starknetjs.com/docs/guides/estimate_fees/#fee-limitation - const maxFee = (suggestedMaxFee * BigInt(15)) / BigInt(10) - // send to same account - const { transaction_hash } = await mainnetContract.set_number( - transferCallData.calldata, - { - maxFee, - }, - ) - return transaction_hash - } - - return CHAIN_ID === constants.NetworkName.SN_MAIN - ? submitMainnetTransaction - : null -} - -export { useMainnetContract } diff --git a/src/components/sections/SessionKeys/Execute/useTestnetContract.ts b/src/components/sections/SessionKeys/Execute/useTestnetContract.ts deleted file mode 100644 index 5360b4e..0000000 --- a/src/components/sections/SessionKeys/Execute/useTestnetContract.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { CHAIN_ID, ETHTokenAddress } from "@/constants" -import { parseInputAmountToUint256 } from "@/helpers/token" -import { useAccount, useContract } from "@starknet-react/core" -import { constants } from "starknet" -import { erco20TransferAbi } from "../../../../abi/erc20TransferAbi" -import { WithSessionAccount } from "../types" - -const useTestnetContract = ({ sessionAccount }: WithSessionAccount) => { - const { address } = useAccount() - const { contract } = useContract({ - abi: erco20TransferAbi, - address: ETHTokenAddress, - provider: sessionAccount, - }) - - const submitTestnetTransaction = async () => { - if (!address) { - throw new Error("No address") - } - - if (!sessionAccount) { - throw new Error("No session account") - } - - const transferCallData = contract.populate("transfer", { - recipient: address.toString(), - amount: parseInputAmountToUint256("0.000000001"), - }) - - // https://www.starknetjs.com/docs/guides/estimate_fees/#estimateinvokefee - const { suggestedMaxFee } = await sessionAccount.estimateInvokeFee({ - contractAddress: ETHTokenAddress, - entrypoint: "transfer", - calldata: transferCallData.calldata, - }) - - // https://www.starknetjs.com/docs/guides/estimate_fees/#fee-limitation - const maxFee = (suggestedMaxFee * BigInt(15)) / BigInt(10) - // send to same account - const { transaction_hash } = await contract.transfer( - transferCallData.calldata, - { - maxFee, - }, - ) - return transaction_hash - } - - return CHAIN_ID === constants.NetworkName.SN_SEPOLIA - ? submitTestnetTransaction - : null -} - -export { useTestnetContract } diff --git a/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx b/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx deleted file mode 100644 index a6f6fc0..0000000 --- a/src/components/sections/SessionKeys/ExecuteFromOutside/SessionKeysExecuteOutside.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { ARGENT_SESSION_SERVICE_BASE_URL, CHAIN_ID } from "@/constants" -import { FC, useState } from "react" -import { constants } from "starknet" -import { SessionKeysEFOLayout } from "../SessionKeysEFOLayout" -import { WithSession } from "../types" -import { useMainnetContract } from "./useMainnetContract" -import { useTestnetContract } from "./useTestnetContract" - -const SessionKeysExecuteOutside: FC = ({ - session, - sessionAccount, -}) => { - const submitTestnetEFO = useTestnetContract({ - session, - sessionAccount, - }) - const submitMainnetEFO = useMainnetContract({ - session, - sessionAccount, - }) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const [outsideExecution, setOutsideExecution] = useState() - - const handleSubmitEFO = async () => { - try { - const efoExecute = - CHAIN_ID === constants.NetworkName.SN_MAIN || - ARGENT_SESSION_SERVICE_BASE_URL.includes("staging") - ? await submitMainnetEFO?.() - : await submitTestnetEFO?.() - - setOutsideExecution(efoExecute) - } catch (error) { - console.error(error) - } - } - - return ( - - ) -} - -export { SessionKeysExecuteOutside } diff --git a/src/components/sections/SessionKeys/ExecuteFromOutside/useMainnetContract.ts b/src/components/sections/SessionKeys/ExecuteFromOutside/useMainnetContract.ts deleted file mode 100644 index 231979c..0000000 --- a/src/components/sections/SessionKeys/ExecuteFromOutside/useMainnetContract.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { dummyContractAbi } from "@/abi/dummyContractAbi" -import { - ARGENT_DUMMY_CONTRACT_ADDRESS, - ARGENT_SESSION_SERVICE_BASE_URL, - CHAIN_ID, -} from "@/constants" -import { sessionKey } from "@/helpers/sessionKeys" -import { createOutsideExecutionCall } from "@argent/x-sessions" -import { useContract } from "@starknet-react/core" -import { constants } from "starknet" -import { WithSession } from "../types" - -const useMainnetContract = ({ session, sessionAccount }: WithSession) => { - const { contract: mainnetContract } = useContract({ - abi: dummyContractAbi, - address: ARGENT_DUMMY_CONTRACT_ADDRESS, - provider: sessionAccount, - }) - - const submitMainnetEFO = async () => { - try { - if (!session || !sessionAccount) { - throw new Error("No open session") - } - // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 - // check .populate - const transferCallData = mainnetContract.populate("set_number", { - number: 1, - }) - - const efoExecutionCall = await createOutsideExecutionCall({ - session, - sessionKey, - calls: [transferCallData], - argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, - }) - - console.log( - "execute from outside response", - JSON.stringify(efoExecutionCall), - ) - - return efoExecutionCall - } catch (e) { - console.error(e) - } - } - - return CHAIN_ID === constants.NetworkName.SN_MAIN ? submitMainnetEFO : null -} - -export { useMainnetContract } diff --git a/src/components/sections/SessionKeys/ExecuteFromOutside/useTestnetContract.ts b/src/components/sections/SessionKeys/ExecuteFromOutside/useTestnetContract.ts deleted file mode 100644 index 770d939..0000000 --- a/src/components/sections/SessionKeys/ExecuteFromOutside/useTestnetContract.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { erco20TransferAbi } from "@/abi/erc20TransferAbi" -import { - ARGENT_SESSION_SERVICE_BASE_URL, - CHAIN_ID, - ETHTokenAddress, -} from "@/constants" -import { sessionKey } from "@/helpers/sessionKeys" -import { parseInputAmountToUint256 } from "@/helpers/token" -import { createOutsideExecutionCall } from "@argent/x-sessions" -import { useAccount, useContract } from "@starknet-react/core" -import { constants } from "starknet" -import { WithSession } from "../types" - -const useTestnetContract = ({ session, sessionAccount }: WithSession) => { - const { address } = useAccount() - const { contract: testnetContract } = useContract({ - abi: erco20TransferAbi, - address: ETHTokenAddress, - provider: sessionAccount, - }) - - const submitTestnetEFO = async () => { - try { - if (!address) { - throw new Error("No address") - } - - if (!session || !sessionAccount) { - throw new Error("No open session") - } - - // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 - // check .populate - const transferCallData = testnetContract.populate("transfer", { - recipient: address.toString(), - amount: parseInputAmountToUint256("0.000000001"), - }) - - const efoExecutionCall = await createOutsideExecutionCall({ - session, - sessionKey, - calls: [transferCallData], - argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, - }) - - console.log( - "execute from outside response", - JSON.stringify(efoExecutionCall), - ) - - return efoExecutionCall - } catch (e) { - console.error(e) - } - } - - return CHAIN_ID === constants.NetworkName.SN_SEPOLIA ? submitTestnetEFO : null -} - -export { useTestnetContract } diff --git a/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx b/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx index 14633e7..096fe5e 100644 --- a/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx +++ b/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx @@ -20,8 +20,8 @@ const SessionKeysEFOLayout: FC = ({ submitText, copyText, }) => { - const copy = () => { - navigator.clipboard.writeText(JSON.stringify(copyData)) + const copy = async () => { + await navigator.clipboard.writeText(JSON.stringify(copyData)) alert(`Data copied in your clipboard`) } diff --git a/src/components/sections/SessionKeys/SessionKeysExecute.tsx b/src/components/sections/SessionKeys/SessionKeysExecute.tsx new file mode 100644 index 0000000..d5fb2ef --- /dev/null +++ b/src/components/sections/SessionKeys/SessionKeysExecute.tsx @@ -0,0 +1,78 @@ +import { dummyContractAbi } from "@/abi/dummyContractAbi" +import { Button } from "@/components/ui/Button" +import { Spinner } from "@/components/ui/Spinner" +import { ARGENT_DUMMY_CONTRACT_ADDRESS } from "@/constants" +import { useAccount, useContract } from "@starknet-react/core" +import { FC, useState } from "react" +import { WithSessionAccount } from "./types" + +const SessionKeysExecute: FC = ({ sessionAccount }) => { + const { address } = useAccount() + const [isSubmitting, setIsSubmitting] = useState(false) + + const { contract } = useContract({ + abi: dummyContractAbi, + address: ARGENT_DUMMY_CONTRACT_ADDRESS, + provider: sessionAccount, + }) + + const handleSessionExecute = async () => { + try { + setIsSubmitting(true) + + if (!address) { + throw new Error("No address") + } + + if (!sessionAccount) { + throw new Error("No session account") + } + + const transferCallData = contract.populate("set_number", { + number: 1, + }) + + // https://www.starknetjs.com/docs/guides/estimate_fees/#estimateinvokefee + const { suggestedMaxFee } = await sessionAccount.estimateInvokeFee({ + contractAddress: ARGENT_DUMMY_CONTRACT_ADDRESS, + entrypoint: "set_number", + calldata: transferCallData.calldata, + }) + + // https://www.starknetjs.com/docs/guides/estimate_fees/#fee-limitation + const maxFee = (suggestedMaxFee * BigInt(15)) / BigInt(10) + // send to same account + const { transaction_hash } = await contract.set_number( + transferCallData.calldata, + { + maxFee, + }, + ) + setTimeout(() => { + alert(`Transaction sent: ${transaction_hash}`) + }) + + setIsSubmitting(true) + } catch (error) { + console.error(error) + } finally { + setIsSubmitting(false) + } + } + + return ( + <> +

Execute session transaction

+ + + ) +} + +export { SessionKeysExecute } diff --git a/src/components/sections/SessionKeys/SessionKeysExecuteOutside.tsx b/src/components/sections/SessionKeys/SessionKeysExecuteOutside.tsx new file mode 100644 index 0000000..24d8bfa --- /dev/null +++ b/src/components/sections/SessionKeys/SessionKeysExecuteOutside.tsx @@ -0,0 +1,72 @@ +import { dummyContractAbi } from "@/abi/dummyContractAbi" +import { + ARGENT_DUMMY_CONTRACT_ADDRESS, + ARGENT_SESSION_SERVICE_BASE_URL, + CHAIN_ID, +} from "@/constants" +import { sessionKey } from "@/helpers/sessionKeys" +import { createOutsideExecutionCall } from "@argent/x-sessions" +import { useContract } from "@starknet-react/core" +import { FC, useState } from "react" +import { constants } from "starknet" +import { SessionKeysEFOLayout } from "./SessionKeysEFOLayout" +import { WithSession } from "./types" + +const SessionKeysExecuteOutside: FC = ({ + session, + sessionAccount, +}) => { + const { contract } = useContract({ + abi: dummyContractAbi, + address: ARGENT_DUMMY_CONTRACT_ADDRESS, + provider: sessionAccount, + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [outsideExecution, setOutsideExecution] = useState() + + const handleSubmitEFO = async () => { + try { + if (!session || !sessionAccount) { + throw new Error("No open session") + } + // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 + // check .populate + const transferCallData = contract.populate("set_number", { + number: 1, + }) + + const efoExecutionCall = await createOutsideExecutionCall({ + session, + sessionKey, + calls: [transferCallData], + argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, + network: + CHAIN_ID === constants.NetworkName.SN_SEPOLIA ? "sepolia" : "mainnet", + }) + + console.log( + "execute from outside response", + JSON.stringify(efoExecutionCall), + ) + + setOutsideExecution(efoExecutionCall) + } catch (error) { + console.error(error) + } + } + + return ( + + ) +} + +export { SessionKeysExecuteOutside } diff --git a/src/components/sections/SessionKeys/SessionKeysSign.tsx b/src/components/sections/SessionKeys/SessionKeysSign.tsx index 572b273..92e0ca8 100644 --- a/src/components/sections/SessionKeys/SessionKeysSign.tsx +++ b/src/components/sections/SessionKeys/SessionKeysSign.tsx @@ -18,9 +18,9 @@ import { useState } from "react" import { Account, AccountInterface, constants } from "starknet" import { SectionLayout } from "../SectionLayout" import { ARGENT_SESSION_SERVICE_BASE_URL, provider } from "@/constants" -import { SessionKeysExecute } from "./Execute/SessionKeysExecute" -import { SessionKeysExecuteOutside } from "./ExecuteFromOutside/SessionKeysExecuteOutside" -import { SessionKeysTypedDataOutside } from "./TypedDataExecuteFromOutside/SessionKeysTypedDataOutside" +import { SessionKeysExecute } from "./SessionKeysExecute" +import { SessionKeysExecuteOutside } from "./SessionKeysExecuteOutside" +import { SessionKeysTypedDataOutside } from "./SessionKeysTypedDataOutside" import { SessionKeysIcon } from "@/components/icons/SessionKesIcon" const SessionKeysSign = () => { diff --git a/src/components/sections/SessionKeys/SessionKeysTypedDataOutside.tsx b/src/components/sections/SessionKeys/SessionKeysTypedDataOutside.tsx new file mode 100644 index 0000000..0bc43b5 --- /dev/null +++ b/src/components/sections/SessionKeys/SessionKeysTypedDataOutside.tsx @@ -0,0 +1,72 @@ +import { dummyContractAbi } from "@/abi/dummyContractAbi" +import { + ARGENT_DUMMY_CONTRACT_ADDRESS, + ARGENT_SESSION_SERVICE_BASE_URL, + CHAIN_ID, +} from "@/constants" +import { sessionKey } from "@/helpers/sessionKeys" +import { createOutsideExecutionTypedData } from "@argent/x-sessions" +import { useContract } from "@starknet-react/core" +import { FC, useState } from "react" +import { constants } from "starknet" +import { SessionKeysEFOLayout } from "./SessionKeysEFOLayout" +import { WithSession } from "./types" + +const SessionKeysTypedDataOutside: FC = ({ + session, + sessionAccount, +}) => { + const { contract } = useContract({ + abi: dummyContractAbi, + address: ARGENT_DUMMY_CONTRACT_ADDRESS, + provider: sessionAccount, + }) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [outsideTypedData, setOutsideTypedData] = useState() + + const handleSubmitEFO = async () => { + try { + if (!session || !sessionAccount) { + throw new Error("No open session") + } + // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 + // check .populate + const transferCallData = contract.populate("set_number", { + number: 1, + }) + + const efoTypedData = await createOutsideExecutionTypedData({ + session, + sessionKey, + calls: [transferCallData], + argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, + network: + CHAIN_ID === constants.NetworkName.SN_SEPOLIA ? "sepolia" : "mainnet", + }) + + console.log( + "execute from outside typed data response", + JSON.stringify(efoTypedData), + ) + + setOutsideTypedData(efoTypedData) + } catch (error) { + console.error(error) + } + } + + return ( + + ) +} + +export { SessionKeysTypedDataOutside } diff --git a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx deleted file mode 100644 index 79c8405..0000000 --- a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/SessionKeysTypedDataOutside.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { ARGENT_SESSION_SERVICE_BASE_URL, CHAIN_ID } from "@/constants" -import { FC, useState } from "react" -import { constants } from "starknet" -import { SessionKeysEFOLayout } from "../SessionKeysEFOLayout" -import { WithSession } from "../types" -import { useMainnetContract } from "./useMainnetContract" -import { useTestnetContract } from "./useTestnetContract" - -const SessionKeysTypedDataOutside: FC = ({ - session, - sessionAccount, -}) => { - const submitTestnetEFO = useTestnetContract({ - session, - sessionAccount, - }) - const submitMainnetEFO = useMainnetContract({ - session, - sessionAccount, - }) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const [outsideTypedData, setOutsideTypedData] = useState() - - const handleSubmitEFO = async () => { - try { - const efoTypedData = - CHAIN_ID === constants.NetworkName.SN_MAIN || - ARGENT_SESSION_SERVICE_BASE_URL.includes("staging") - ? await submitMainnetEFO?.() - : await submitTestnetEFO?.() - - setOutsideTypedData(efoTypedData) - } catch (error) { - console.error(error) - } - } - - return ( - - ) -} - -export { SessionKeysTypedDataOutside } diff --git a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useMainnetContract.ts b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useMainnetContract.ts deleted file mode 100644 index b00627e..0000000 --- a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useMainnetContract.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { dummyContractAbi } from "@/abi/dummyContractAbi" -import { - ARGENT_DUMMY_CONTRACT_ADDRESS, - ARGENT_SESSION_SERVICE_BASE_URL, - CHAIN_ID, -} from "@/constants" -import { sessionKey } from "@/helpers/sessionKeys" -import { createOutsideExecutionTypedData } from "@argent/x-sessions" -import { useContract } from "@starknet-react/core" -import { constants } from "starknet" -import { WithSession } from "../types" - -const useMainnetContract = ({ session, sessionAccount }: WithSession) => { - const { contract: mainnetContract } = useContract({ - abi: dummyContractAbi, - address: ARGENT_DUMMY_CONTRACT_ADDRESS, - provider: sessionAccount, - }) - - const submitMainnetEFO = async () => { - try { - if (!session || !sessionAccount) { - throw new Error("No open session") - } - // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 - // check .populate - const transferCallData = mainnetContract.populate("set_number", { - number: 1, - }) - - const efoTypedData = await createOutsideExecutionTypedData({ - session, - sessionKey, - calls: [transferCallData], - argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, - }) - - console.log( - "execute from outside typed data response", - JSON.stringify(efoTypedData), - ) - - return efoTypedData - } catch (e) { - console.error(e) - } - } - - return CHAIN_ID === constants.NetworkName.SN_MAIN ? submitMainnetEFO : null -} - -export { useMainnetContract } diff --git a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useTestnetContract.ts b/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useTestnetContract.ts deleted file mode 100644 index 57cc18d..0000000 --- a/src/components/sections/SessionKeys/TypedDataExecuteFromOutside/useTestnetContract.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { erco20TransferAbi } from "@/abi/erc20TransferAbi" -import { - ARGENT_SESSION_SERVICE_BASE_URL, - CHAIN_ID, - ETHTokenAddress, -} from "@/constants" -import { sessionKey } from "@/helpers/sessionKeys" -import { parseInputAmountToUint256 } from "@/helpers/token" -import { createOutsideExecutionTypedData } from "@argent/x-sessions" -import { useAccount, useContract } from "@starknet-react/core" -import { constants } from "starknet" -import { WithSession } from "../types" - -const useTestnetContract = ({ session, sessionAccount }: WithSession) => { - const { address } = useAccount() - const { contract: testnetContract } = useContract({ - abi: erco20TransferAbi, - address: ETHTokenAddress, - provider: sessionAccount, - }) - - const submitTestnetEFO = async () => { - try { - if (!address) { - throw new Error("No address") - } - - if (!session || !sessionAccount) { - throw new Error("No open session") - } - - // https://www.starknetjs.com/docs/guides/use_erc20/#interact-with-an-erc20 - // check .populate - const transferCallData = testnetContract.populate("transfer", { - recipient: address.toString(), - amount: parseInputAmountToUint256("0.000000001"), - }) - - const efoTypedData = await createOutsideExecutionTypedData({ - session, - sessionKey, - calls: [transferCallData], - argentSessionServiceUrl: ARGENT_SESSION_SERVICE_BASE_URL, - }) - - console.log( - "execute from outside typed data response", - JSON.stringify(efoTypedData), - ) - - return efoTypedData - } catch (e) { - console.error(e) - } - } - - return CHAIN_ID === constants.NetworkName.SN_SEPOLIA ? submitTestnetEFO : null -} - -export { useTestnetContract } diff --git a/src/constants/index.ts b/src/constants/index.ts index 7848dde..76c2207 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -6,9 +6,12 @@ export const ETHTokenAddress = export const DAITokenAddress = "0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3" -export const ARGENT_DUMMY_CONTRACT_ADDRESS = +export const ARGENT_DUMMY_CONTRACT_MAINNET_ADDRESS = "0x001c515f991f706039696a54f6f33730e9b0e8cc5d04187b13c2c714401acfd4" +export const ARGENT_DUMMY_CONTRACT_SEPOLIA_ADDRESS = + "0x88d3cc4377a6cdfd27545a11548bd070c4e2e1e3df3d402922dbc4350b416" + export const CHAIN_ID = process.env.NEXT_PUBLIC_CHAIN_ID === constants.NetworkName.SN_MAIN ? constants.NetworkName.SN_MAIN @@ -36,3 +39,13 @@ export const ARGENT_SESSION_SERVICE_BASE_URL = export const ARGENT_WEBWALLET_URL = process.env.NEXT_PUBLIC_ARGENT_WEBWALLET_URL || "https://sepolia-web.argent.xyz" + +export const USE_SEPOLIA_DUMMY_CONTRACT = process.env + .NEXT_PUBLIC_USE_SEPOLIA_DUMMY_CONTRACT + ? process.env.NEXT_PUBLIC_USE_SEPOLIA_DUMMY_CONTRACT === "true" + : false + +export const ARGENT_DUMMY_CONTRACT_ADDRESS = + CHAIN_ID === constants.NetworkName.SN_SEPOLIA + ? ARGENT_DUMMY_CONTRACT_SEPOLIA_ADDRESS + : ARGENT_DUMMY_CONTRACT_MAINNET_ADDRESS diff --git a/src/helpers/sessionKeys.ts b/src/helpers/sessionKeys.ts index b84dec3..58d5990 100644 --- a/src/helpers/sessionKeys.ts +++ b/src/helpers/sessionKeys.ts @@ -1,11 +1,6 @@ -import { - ARGENT_DUMMY_CONTRACT_ADDRESS, - ARGENT_SESSION_SERVICE_BASE_URL, - CHAIN_ID, - ETHTokenAddress, -} from "@/constants" -import { SessionKey } from "@argent/x-sessions" -import { constants, ec } from "starknet" +import { ARGENT_DUMMY_CONTRACT_ADDRESS, ETHTokenAddress } from "@/constants" +import { bytesToHexString, SessionKey } from "@argent/x-sessions" +import { ec } from "starknet" import { parseUnits } from "./token" /* Hardcoded values for session example */ @@ -29,21 +24,12 @@ const STRKFees = [ }, ] -const allowedMethods = - CHAIN_ID === constants.NetworkName.SN_MAIN || - ARGENT_SESSION_SERVICE_BASE_URL.includes("staging") - ? [ - { - "Contract Address": ARGENT_DUMMY_CONTRACT_ADDRESS, - selector: "set_number", - }, - ] - : [ - { - "Contract Address": ETHTokenAddress, - selector: "transfer", - }, - ] +const allowedMethods = [ + { + "Contract Address": ARGENT_DUMMY_CONTRACT_ADDRESS, + selector: "set_number", + }, +] // eslint-disable-next-line @typescript-eslint/no-explicit-any const expiry = Math.floor((Date.now() + 1000 * 60 * 60 * 24) / 1000) as any @@ -56,8 +42,8 @@ const metaData = (isStarkFeeToken: boolean) => ({ const privateKey = ec.starkCurve.utils.randomPrivateKey() const sessionKey: SessionKey = { - privateKey, + privateKey: bytesToHexString(privateKey), publicKey: ec.starkCurve.getStarkKey(privateKey), } -export { allowedMethods, sessionKey, expiry, metaData } +export { allowedMethods, expiry, metaData, sessionKey }