From 41b9bcd8e733c06fe51372817be55092e335105b Mon Sep 17 00:00:00 2001 From: JunichiSugiura Date: Fri, 29 Nov 2024 17:31:59 +0100 Subject: [PATCH 01/14] Add useTransactionSummary hook to utils --- packages/utils/src/hooks/contract.ts | 158 +++++++++++++++++++++++++++ packages/utils/src/hooks/index.ts | 1 + 2 files changed, 159 insertions(+) create mode 100644 packages/utils/src/hooks/contract.ts diff --git a/packages/utils/src/hooks/contract.ts b/packages/utils/src/hooks/contract.ts new file mode 100644 index 000000000..b5ccdb9fb --- /dev/null +++ b/packages/utils/src/hooks/contract.ts @@ -0,0 +1,158 @@ +import { useMemo } from "react"; +import { Provider, StarknetDomain, StarknetType } from "starknet"; +import useSWR from "swr"; + +type PreTransactionSummary = Omit; + +type TransactionSummary = { + default: Record; + ERC20: Record; + ERC721: Record; + messages: TypedDataPolicy[]; +}; + +type ContractType = keyof TransactionSummary; + +export function useTransactionSummary({ + policies, + provider, +}: { + policies: Policy[]; + provider: Provider; +}) { + const preSummary = useMemo( + () => + policies.reduce( + (prev, p) => + isCallPolicy(p) + ? { + ...prev, + default: { + ...prev.default, + [p.target]: prev.default[p.target] + ? [...prev.default[p.target], p] + : [p], + }, + } + : { ...prev, messages: [...prev.messages, p] }, + { default: {}, messages: [] }, + ), + [policies], + ); + + const res: TransactionSummary = { + default: {}, + ERC20: {}, + ERC721: {}, + messages: preSummary.messages, + }; + + const summary = useSWR( + `tx-summary`, + async () => { + const promises = Object.entries(preSummary.default).map( + async ([addr, calls]) => { + const contractType = await checkContractType(provider, addr); + console.log({ addr, contractType }); + switch (contractType) { + case "ERC20": + res.ERC20[addr] = calls; + return; + case "ERC721": + res.ERC721[addr] = calls; + return; + case "default": + default: + res.default[addr] = calls; + return; + } + }, + ); + await Promise.allSettled(promises); + + return res; + }, + { fallbackData: res }, + ); + + return summary; +} + +// TODO: What the id? +const IERC20_ID = ""; +const IERC721_ID = + "0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943"; + +async function checkContractType( + provider: Provider, + contractAddress: string, +): Promise { + try { + // SNIP-5: check with via `support_interface` method + const [erc20Res] = await provider.callContract({ + contractAddress, + entrypoint: "supports_interface", + calldata: [IERC20_ID], // ERC20 interface ID + }); + if (!!erc20Res) { + return "ERC20"; + } + + const [erc721Res] = await provider.callContract({ + contractAddress, + entrypoint: "supports_interface", + calldata: [IERC721_ID], // ERC721 interface ID + }); + if (!!erc721Res) { + return "ERC721"; + } + + return "default"; + } catch { + try { + await provider.callContract({ + contractAddress, + entrypoint: "balanceOf", + calldata: ["0x0"], // ERC721 interface ID + }); + + try { + await provider.callContract({ + contractAddress, + entrypoint: "decimals", + }); + + return "ERC20"; + } catch { + await provider.callContract({ + contractAddress, + entrypoint: "tokenId", + calldata: ["0x0"], + }); + + return "ERC721"; + } + } catch { + return "default"; + } + } +} + +function isCallPolicy(policy: Policy): policy is CallPolicy { + return !!(policy as CallPolicy).target; +} + +// Dup of @cartridge/controller/types +type Policy = CallPolicy | TypedDataPolicy; + +type CallPolicy = { + target: string; + method: string; + description?: string; +}; + +type TypedDataPolicy = { + types: Record; + primaryType: string; + domain: StarknetDomain; +}; diff --git a/packages/utils/src/hooks/index.ts b/packages/utils/src/hooks/index.ts index e3f8abf55..fcc5b674a 100644 --- a/packages/utils/src/hooks/index.ts +++ b/packages/utils/src/hooks/index.ts @@ -1,3 +1,4 @@ export * from "./api"; export * from "./balance"; +export * from "./contract"; export * from "./countervalue"; From c940c3a9c823fdc67d000723cf4da9b8f26e58a3 Mon Sep 17 00:00:00 2001 From: JunichiSugiura Date: Fri, 29 Nov 2024 18:23:32 +0100 Subject: [PATCH 02/14] Fix tailwind dark mode on keychain --- packages/keychain/public/noflash.js | 25 ++++++++ .../src/components/TransactionDetails.tsx | 64 +++++++++++++++++++ .../src/components/connect/CreateSession.tsx | 2 + packages/keychain/src/hooks/theme.tsx | 2 +- packages/keychain/src/index.css | 5 ++ packages/keychain/src/pages/_app.tsx | 2 + .../profile/src/components/context/theme.tsx | 40 ------------ packages/utils/src/hooks/contract.ts | 1 - 8 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 packages/keychain/public/noflash.js create mode 100644 packages/keychain/src/components/TransactionDetails.tsx diff --git a/packages/keychain/public/noflash.js b/packages/keychain/public/noflash.js new file mode 100644 index 000000000..6d2655bb2 --- /dev/null +++ b/packages/keychain/public/noflash.js @@ -0,0 +1,25 @@ +(function () { + const storageKey = "vite-ui-colorScheme"; + const classNameDark = "dark"; + const params = new URL(document.location).searchParams; + const colorScheme = + params.get("colorMode") ?? localStorage.getItem(storageKey) ?? "system"; + const html = document.getElementsByTagName("html")[0]; + + switch (colorScheme) { + case "light": + break; + case "dark": + html.classList.add(classNameDark); + break; + case "system": + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + html.classList.add(classNameDark); + } + break; + default: + break; + } + + localStorage.setItem(storageKey, colorScheme); +})(); diff --git a/packages/keychain/src/components/TransactionDetails.tsx b/packages/keychain/src/components/TransactionDetails.tsx new file mode 100644 index 000000000..73d9e5b50 --- /dev/null +++ b/packages/keychain/src/components/TransactionDetails.tsx @@ -0,0 +1,64 @@ +import { Policy } from "@cartridge/controller"; +import { Card, CardHeader, CardTitle } from "@cartridge/ui-next"; +// import { useTransactionSummary } from "@cartridge/utils"; + +export function TransactionDetails({ policies }: { policies: Policy[] }) { + // const { controller } = useConnection() + // const { data: summary } = useTransactionSummary({ policies, provider: controller }); + // console.log({ policies, summary }) + return ( +
+ {Object.entries(summary.default).map(([addr, calls]) => ( + + + Contract: {addr} + + + ))} + + {Object.entries(summary.ERC20).map(([addr, calls]) => ( + + + Contract: {addr} + + + ))} + + {Object.entries(summary.ERC721).map(([addr, calls]) => ( + + + Contract: {addr} + + + ))} + + + + Sign Messages + + +
+ ); +} + +const summary = { + default: { + "0x0000000000000": [ + { target: "0x0000000000000", method: "method 1" }, + { target: "0x0000000000000", method: "method 2" }, + ], + }, + ERC20: { + "0x0000000000000": [ + { target: "0x0000000000000", method: "approve" }, + { target: "0x0000000000000", method: "transfer" }, + ], + }, + ERC721: { + "0x0000000000000": [ + { target: "0x0000000000000", method: "approve" }, + { target: "0x0000000000000", method: "transfer" }, + ], + }, + messages: [], +}; diff --git a/packages/keychain/src/components/connect/CreateSession.tsx b/packages/keychain/src/components/connect/CreateSession.tsx index 645b2b612..e799c7210 100644 --- a/packages/keychain/src/components/connect/CreateSession.tsx +++ b/packages/keychain/src/components/connect/CreateSession.tsx @@ -11,6 +11,7 @@ import { SESSION_EXPIRATION } from "const"; import { Upgrade } from "./Upgrade"; import { TypedDataPolicy } from "@cartridge/controller"; import { ErrorCode } from "@cartridge/account-wasm"; +import { TransactionDetails } from "components/TransactionDetails"; export function CreateSession({ onConnect, @@ -82,6 +83,7 @@ export function CreateSession({ +