diff --git a/e2e/src/webwallet/config.ts b/e2e/src/webwallet/config.ts index 285eb30..0008ceb 100644 --- a/e2e/src/webwallet/config.ts +++ b/e2e/src/webwallet/config.ts @@ -16,7 +16,7 @@ const config = { emailPassword: process.env.EMAIL_PASSWORD!, acc_destination: commonConfig.destinationAddress! || "", vw_acc_addr: process.env.VW_ACC_ADDR! || "", - url: "https://web.argent.xyz", + url: "https://sepolia-web.argent.xyz/", /* TODO: wait for sepolia in prod process.env.ARGENT_X_ENVIRONMENT === "prod" diff --git a/e2e/src/webwallet/page-objects/Dapps.ts b/e2e/src/webwallet/page-objects/Dapps.ts index a070b84..64d4bff 100644 --- a/e2e/src/webwallet/page-objects/Dapps.ts +++ b/e2e/src/webwallet/page-objects/Dapps.ts @@ -125,7 +125,7 @@ export default class Dapps extends Navigation { await expect(popup.getByText("Sign Message")).toBeVisible() await expect(popup.getByText("Confirm")).toBeVisible() - await popup.getByText("Confirm").click({ timeout: 30000, force: true }) + await popup.getByText("Confirm").click({ timeout: 3000, force: true }) await Promise.all([ expect(this.dApp.getByText("Signer", { exact: true })).toBeVisible(), @@ -154,4 +154,54 @@ 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 expect( + this.dApp.locator(`button :text-is("Create session")`), + ).toBeVisible() + await this.dApp.waitForTimeout(100) + 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(1000) + + 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() + + 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/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/package.json b/package.json index b247fd4..a212614 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "prepare": "husky" }, "dependencies": { + "@argent/x-sessions": "7.0.0-beta.5", "@starknet-io/get-starknet-core": "4.0.4", "@starknet-react/chains": "^3.1.0", "@starknet-react/core": "^3.5.0", @@ -25,7 +26,7 @@ "react": "19.0.0-rc-02c0e824-20241028", "react-dom": "19.0.0-rc-02c0e824-20241028", "starknet": "^6.11.0", - "starknetkit": "^2.6.1" + "starknetkit": "2.6.2" }, "devDependencies": { "@commitlint/cli": "^19.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9428c9..b6f2f40 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@argent/x-sessions': + specifier: 7.0.0-beta.5 + version: 7.0.0-beta.5(starknet@6.11.0) '@starknet-io/get-starknet-core': specifier: 4.0.4 version: 4.0.4 @@ -30,8 +33,8 @@ importers: specifier: ^6.11.0 version: 6.11.0 starknetkit: - specifier: ^2.6.1 - version: 2.6.1(starknet@6.11.0) + specifier: 2.6.2 + version: 2.6.2(starknet@6.11.0) devDependencies: '@commitlint/cli': specifier: ^19.5.0 @@ -167,6 +170,11 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@argent/x-sessions@7.0.0-beta.5': + resolution: {integrity: sha512-nnQUrZ7MWZzTTX9gDFpE4iuj+BorIXapxdWoBo8kjWu+SIqnLZcFfcJApP6x+ANA1GUDjqeu7/+AcJSzPfbVtA==} + peerDependencies: + starknet: 6.11.0 + '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -2860,8 +2868,8 @@ packages: starknet@6.11.0: resolution: {integrity: sha512-u50KrGDi9fbu1Ogu7ynwF/tSeFlp3mzOg1/Y5x50tYFICImo3OfY4lOz9OtYDk404HK4eUujKkhov9tG7GAKlg==} - starknetkit@2.6.1: - resolution: {integrity: sha512-1w8F6BShtGr3IE8bTGvaXsiGn+ZUFB2jnC9fqMX89utBUEJtkft4K9r0YJWiPjTho+eap8o4dN0S0G4tisGW7A==} + starknetkit@2.6.2: + resolution: {integrity: sha512-rGEzyDtB6oC+LuyjFg3TCFJOkdNiB7D9FXirZw2VjtBiAltrB+6MVDPikZ0Ru30MqcQ4Kc+CbgT9JsAFsk15MQ==} peerDependencies: starknet: ^6.9.0 @@ -3316,6 +3324,11 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@argent/x-sessions@7.0.0-beta.5(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 @@ -6485,7 +6498,7 @@ snapshots: transitivePeerDependencies: - encoding - starknetkit@2.6.1(starknet@6.11.0): + starknetkit@2.6.2(starknet@6.11.0): dependencies: '@starknet-io/get-starknet': 4.0.4 '@starknet-io/get-starknet-core': 4.0.4 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/components/sections/Transactions/abi.ts b/src/abi/erc20TransferAbi.ts similarity index 87% rename from src/components/sections/Transactions/abi.ts rename to src/abi/erc20TransferAbi.ts index 928d0de..f8de4a2 100644 --- a/src/components/sections/Transactions/abi.ts +++ b/src/abi/erc20TransferAbi.ts @@ -1,6 +1,6 @@ import { Abi } from "@starknet-react/core" -const abi = [ +const erco20TransferAbi = [ { type: "function", name: "transfer", @@ -19,4 +19,4 @@ const abi = [ }, ] as const satisfies Abi -export { abi } +export { erco20TransferAbi } diff --git a/src/components/StarknetDapp.tsx b/src/components/StarknetDapp.tsx index e870202..c5e43fe 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) @@ -40,7 +41,7 @@ const StarknetDapp = () => { {!section && ( -
+
)}
@@ -91,6 +92,14 @@ const StarknetDapp = () => { disabled={!isConnected} className={`${!section ? "flex" : section === "ERC20" ? "flex" : "md:flex hidden"}`} /> +
@@ -107,6 +116,7 @@ const StarknetDapp = () => { {section === "Signing" && } {section === "Network" && } {section === "ERC20" && } + {section === "SessionKeys" && } 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 5f5e132..a2f67d8 100644 --- a/src/components/sections/SectionButton.tsx +++ b/src/components/sections/SectionButton.tsx @@ -5,6 +5,7 @@ import { Section } from "./types" interface SectionButtonProps { disabled?: boolean section: Section + label?: string selected: boolean className?: string setSection: ( @@ -17,11 +18,13 @@ interface SectionButtonProps { const SectionButton: FC = ({ disabled, section, + label, selected, setSection, className, }) => ( ) diff --git a/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx b/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx new file mode 100644 index 0000000..096fe5e --- /dev/null +++ b/src/components/sections/SessionKeys/SessionKeysEFOLayout.tsx @@ -0,0 +1,53 @@ +import { Button } from "@/components/ui/Button" +import { FC } from "react" + +interface SessionKeysEFOLayoutProps { + handleSubmit: () => Promise + copyData: string + submitDisabled: boolean + copyDataDisabled: boolean + title: string + submitText: string + copyText: string +} + +const SessionKeysEFOLayout: FC = ({ + handleSubmit, + copyData, + submitDisabled, + copyDataDisabled, + title, + submitText, + copyText, +}) => { + const copy = async () => { + await navigator.clipboard.writeText(JSON.stringify(copyData)) + alert(`Data copied in your clipboard`) + } + + return ( +
+

{title}

+
+ + +
+
+ ) +} + +export { SessionKeysEFOLayout } 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 new file mode 100644 index 0000000..92e0ca8 --- /dev/null +++ b/src/components/sections/SessionKeys/SessionKeysSign.tsx @@ -0,0 +1,97 @@ +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 "./SessionKeysExecute" +import { SessionKeysExecuteOutside } from "./SessionKeysExecuteOutside" +import { SessionKeysTypedDataOutside } from "./SessionKeysTypedDataOutside" +import { SessionKeysIcon } from "@/components/icons/SessionKesIcon" + +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") + } + + 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, + }) + + setSession(sessionObj) + setSessionAccount(sessionAccount) + } catch (e) { + console.error(e) + } + } + + return ( + }> + + + + + + ) +} + +export { 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/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..4e50f0a 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 { 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, }) 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/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 }) => (