From 37f9204c92a0d4bd5f8704d57e5d6c51f30adf44 Mon Sep 17 00:00:00 2001 From: RM3 Date: Wed, 25 Feb 2026 18:05:00 -0300 Subject: [PATCH 01/15] feat: swap review screen WIP --- examples/next/src/app/page.tsx | 2 + examples/next/src/components/Swap.tsx | 282 ++++++++++ .../src/components/ExecutionContainer.tsx | 2 +- .../src/components/swap/ConfirmSwap.tsx | 105 ++++ .../src/components/swap/ekuboRouterAbi.json | 482 ++++++++++++++++++ packages/keychain/src/components/swap/swap.ts | 157 ++++++ .../transaction/ConfirmTransaction.tsx | 22 +- 7 files changed, 1048 insertions(+), 4 deletions(-) create mode 100644 examples/next/src/components/Swap.tsx create mode 100644 packages/keychain/src/components/swap/ConfirmSwap.tsx create mode 100644 packages/keychain/src/components/swap/ekuboRouterAbi.json create mode 100644 packages/keychain/src/components/swap/swap.ts diff --git a/examples/next/src/app/page.tsx b/examples/next/src/app/page.tsx index 7efd511f81..f047d8f196 100644 --- a/examples/next/src/app/page.tsx +++ b/examples/next/src/app/page.tsx @@ -12,6 +12,7 @@ import { PlayButton } from "components/PlayButton"; import { Profile } from "components/Profile"; import { SignMessage } from "components/SignMessage"; import { Transfer } from "components/Transfer"; +import { Swap } from "components/Swap"; import { Starterpack } from "components/Starterpack"; import { UpdateSession } from "components/UpdateSession"; import { ControllerToaster } from "@cartridge/ui"; @@ -29,6 +30,7 @@ const Home: FC = () => { + diff --git a/examples/next/src/components/Swap.tsx b/examples/next/src/components/Swap.tsx new file mode 100644 index 0000000000..c9c80c9514 --- /dev/null +++ b/examples/next/src/components/Swap.tsx @@ -0,0 +1,282 @@ +"use client"; + +import { useAccount } from "@starknet-react/core"; +import ControllerConnector from "@cartridge/connector/controller"; +import { Button } from "@cartridge/ui"; + +export function Swap() { + const { account, connector } = useAccount(); + const ctrlConnector = connector as unknown as ControllerConnector; + + if (!account) { + return null; + } + + return ( +
+

Token Swaps

+
+ + + +
+
+ ); +} + +const SWAP_SINGLE = [ + { + contractAddress: + "0x0124aeb495b947201f5faC96fD1138E326AD86195B98df6DEc9009158A533B49", + entrypoint: "transfer", + calldata: [ + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + "0x32a03ab37fef8ba51", + "0x0", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "multihop_swap", + calldata: [ + "0x2", + "0x016dea82a6588ca9fb7200125fa05631b1c1735a313e24afe9c90301e441a796", + "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "17014118346046924117642026945517453312", + "0x56a4c", + "0x043e4f09c32d13d43a880e85f69f7de93ceda62d6cf2581a582c6db635548fdc", + "0x6f3528fe26840249f4b191ef6dff7928", + "0xfffffc080ed7b455", + 0, + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "3402823669209384634633746074317682114", + "0x56a4c", + "0x043e4f09c32d13d43a880e85f69f7de93ceda62d6cf2581a582c6db635548fdc", + "0x1000003f7f1380b75", + "0x0", + 0, + "0x016dea82a6588ca9fb7200125fa05631b1c1735a313e24afe9c90301e441a796", + "0xa688906bd8b00000", + "0x1", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "clear_minimum", + calldata: [ + "0x016dea82a6588ca9fb7200125fa05631b1c1735a313e24afe9c90301e441a796", + "0xa4de3d0e9ba40000", + "0x0", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "clear", + calldata: [ + "0x0124aeb495b947201f5faC96fD1138E326AD86195B98df6DEc9009158A533B49", + ], + }, +]; + +const SWAP_MULTIPLE = [ + { + contractAddress: + "0x0124aeb495b947201f5faC96fD1138E326AD86195B98df6DEc9009158A533B49", + entrypoint: "transfer", + calldata: [ + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + "0x176e9649d99dd740a", + "0x0", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "multihop_swap", + calldata: [ + "0x2", + "0x01c3c8284d7eed443b42f47e764032a56eaf50a9079d67993b633930e3689814", + "0x075afe6402ad5a5c20dd25e10ec3b3986acaa647b77e4ae24b0cbc9a54a27a87", + "0", + "0x56a4c", + "0x005e470ff654d834983a46b8f29dfa99963d5044b993cb7b9c92243a69dab38f", + "0x6f3528fe26840249f4b191ef6dff7928", + "0xfffffc080ed7b455", + 0, + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + "0x075afe6402ad5a5c20dd25e10ec3b3986acaa647b77e4ae24b0cbc9a54a27a87", + "1020847100762815411640772995208708096", + "0x56a4c", + "0x043e4f09c32d13d43a880e85f69f7de93ceda62d6cf2581a582c6db635548fdc", + "0x1000003f7f1380b75", + "0x0", + 0, + "0x01c3c8284d7eed443b42f47e764032a56eaf50a9079d67993b633930e3689814", + "0xde0b6b3a7640000", + "0x1", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "clear_minimum", + calldata: [ + "0x01c3c8284d7eed443b42f47e764032a56eaf50a9079d67993b633930e3689814", + "0xdbd2fc137a30000", + "0x0", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "clear", + calldata: [ + "0x0124aeb495b947201f5faC96fD1138E326AD86195B98df6DEc9009158A533B49", + ], + }, + { + contractAddress: + "0x0124aeb495b947201f5faC96fD1138E326AD86195B98df6DEc9009158A533B49", + entrypoint: "transfer", + calldata: [ + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + "0x4f1eba34861ddd0", + "0x0", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "multihop_swap", + calldata: [ + "0x2", + "0x0103eafe79f8631932530cc687dfcdeb013c883a82619ebf81be393e2953a87a", + "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "17014118346046923173168730371588410572", + "0x56a4c", + "0x043e4f09c32d13d43a880e85f69f7de93ceda62d6cf2581a582c6db635548fdc", + "0x6f3528fe26840249f4b191ef6dff7928", + "0xfffffc080ed7b455", + 0, + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + "0x042dd777885ad2c116be96d4d634abc90a26a790ffb5871e037dd5ae7d2ec86b", + "17014118346046923173168730371588410572", + "0x1744e", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xf7c31547edfb13af0071dfd6ffe", + "0x0", + 0, + "0x0103eafe79f8631932530cc687dfcdeb013c883a82619ebf81be393e2953a87a", + "0xde0b6b3a7640000", + "0x1", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "clear_minimum", + calldata: [ + "0x0103eafe79f8631932530cc687dfcdeb013c883a82619ebf81be393e2953a87a", + "0xdbd2fc137a30000", + "0x0", + ], + }, + { + contractAddress: + "0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066", + entrypoint: "clear", + calldata: [ + "0x0124aeb495b947201f5faC96fD1138E326AD86195B98df6DEc9009158A533B49", + ], + }, +]; + +const LS2_PURCHASE_GAME = [ + { + contractAddress: + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + entrypoint: "transfer", + calldata: [ + "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e", + "0x5bbb37da193af4ba9", + "0x0", + ], + }, + { + contractAddress: + "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e", + entrypoint: "multihop_swap", + calldata: [ + "0x1", + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + "0x0452810188c4cb3aebd63711a3b445755bc0d6c4f27b923fdd99b1a118858136", + "0", + "0x56a4c", + "0x043e4f09c32d13d43a880e85f69f7de93ceda62d6cf2581a582c6db635548fdc", + "0x1000003f7f1380b75", + "0x0", + 0, + "0x0452810188C4Cb3AEbD63711a3b445755BC0D6C4f27B923fDd99B1A118858136", + "0xde0b6b3a7640000", + "0x1", + ], + }, + { + contractAddress: + "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e", + entrypoint: "clear_minimum", + calldata: [ + "1955023220287003686908448593668771782622329060199208410425295899940041883958", + "1000000000000000000", + "0", + ], + }, + { + contractAddress: + "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e", + entrypoint: "clear", + calldata: [ + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + ], + }, + { + contractAddress: + "0x0452810188C4Cb3AEbD63711a3b445755BC0D6C4f27B923fDd99B1A118858136", + entrypoint: "approve", + calldata: [ + "294172758298611957878874535440244936028848058202724233951972339591192112194", + "1000000000000000000", + "0", + ], + }, + { + contractAddress: + "0x00a67ef20b61a9846e1c82b411175e6ab167ea9f8632bd6c2091823c3629ec42", + entrypoint: "buy_game", + calldata: [ + "0", + "0", + "2017717448871504735845", + "2403140985568399978641699320335980224292375691718886561247325844102368719999", + "0", + ], + }, +]; diff --git a/packages/keychain/src/components/ExecutionContainer.tsx b/packages/keychain/src/components/ExecutionContainer.tsx index 2428d03f48..b47a398204 100644 --- a/packages/keychain/src/components/ExecutionContainer.tsx +++ b/packages/keychain/src/components/ExecutionContainer.tsx @@ -157,7 +157,7 @@ export function ExecutionContainer({ description={description} icon={icon} right={right} - hideIcon + hideIcon={!icon} /> {children} diff --git a/packages/keychain/src/components/swap/ConfirmSwap.tsx b/packages/keychain/src/components/swap/ConfirmSwap.tsx new file mode 100644 index 0000000000..daf099711c --- /dev/null +++ b/packages/keychain/src/components/swap/ConfirmSwap.tsx @@ -0,0 +1,105 @@ +import { useMemo } from "react"; +import { + LayoutContent, + TokenCard, + TokenSummary, + TransferIcon, +} from "@cartridge/ui"; +import { TransactionSummary } from "@/components/transaction/TransactionSummary"; +import { ControllerError } from "@/utils/connection"; +import { Call, FeeEstimate, getChecksumAddress } from "starknet"; +import { ExecutionContainer } from "@/components/ExecutionContainer"; +import { useSwapTransaction } from "@/components/swap/swap"; +import { useTokens } from "@/hooks/token"; +import placeholder from "/placeholder.svg?url"; + +interface ConfirmSwapProps { + onSubmit: (maxFee?: FeeEstimate) => Promise; + onError?: (error: ControllerError) => void; + transactions: Call[]; + error?: ControllerError; + origin: string; +} + +export function ConfirmSwap({ + onSubmit, + onError, + transactions, + error, + origin, +}: ConfirmSwapProps) { + const { tokens } = useTokens(); + const { isSwap, swapTransaction } = useSwapTransaction(transactions); + // console.log("swapTransaction:", swapTransaction); + + const sellTokens = useMemo( + () => + tokens + .filter( + (token) => + swapTransaction.sellAddress == + getChecksumAddress(token.metadata.address), + ) + .map((token) => ({ ...token, decreasing: true, increasing: false })), + [tokens, swapTransaction], + ); + + const buyTokens = useMemo( + () => + tokens + .filter( + (token) => + swapTransaction.buyAddress == + getChecksumAddress(token.metadata.address), + ) + .map((token) => ({ ...token, increasing: true, decreasing: false })), + [tokens, swapTransaction], + ); + + console.log( + swapTransaction.sellAddress, + swapTransaction.buyAddress, + tokens, + sellTokens, + buyTokens, + ); + + return ( + } + title={"Review Swap"} + description={origin} + executionError={error} + transactions={transactions} + onSubmit={onSubmit} + onError={onError} + > + + {!isSwap ? ( + + ) : ( + <> + + {[...sellTokens, ...buyTokens].map((token) => ( + + ))} + + + )} + + + ); +} diff --git a/packages/keychain/src/components/swap/ekuboRouterAbi.json b/packages/keychain/src/components/swap/ekuboRouterAbi.json new file mode 100644 index 0000000000..ac84ff3f06 --- /dev/null +++ b/packages/keychain/src/components/swap/ekuboRouterAbi.json @@ -0,0 +1,482 @@ +[ + { + "type": "impl", + "name": "LockerImpl", + "interface_name": "ekubo::interfaces::core::ILocker" + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "interface", + "name": "ekubo::interfaces::core::ILocker", + "items": [ + { + "type": "function", + "name": "locked", + "inputs": [ + { + "name": "id", + "type": "core::integer::u32" + }, + { + "name": "data", + "type": "core::array::Span::" + } + ], + "outputs": [ + { + "type": "core::array::Span::" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "RouterImpl", + "interface_name": "ekubo::router::IRouter" + }, + { + "type": "struct", + "name": "ekubo::types::keys::PoolKey", + "members": [ + { + "name": "token0", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token1", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "fee", + "type": "core::integer::u128" + }, + { + "name": "tick_spacing", + "type": "core::integer::u128" + }, + { + "name": "extension", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "struct", + "name": "ekubo::router::RouteNode", + "members": [ + { + "name": "pool_key", + "type": "ekubo::types::keys::PoolKey" + }, + { + "name": "sqrt_ratio_limit", + "type": "core::integer::u256" + }, + { + "name": "skip_ahead", + "type": "core::integer::u128" + } + ] + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "ekubo::types::i129::i129", + "members": [ + { + "name": "mag", + "type": "core::integer::u128" + }, + { + "name": "sign", + "type": "core::bool" + } + ] + }, + { + "type": "struct", + "name": "ekubo::router::TokenAmount", + "members": [ + { + "name": "token", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "amount", + "type": "ekubo::types::i129::i129" + } + ] + }, + { + "type": "struct", + "name": "ekubo::types::delta::Delta", + "members": [ + { + "name": "amount0", + "type": "ekubo::types::i129::i129" + }, + { + "name": "amount1", + "type": "ekubo::types::i129::i129" + } + ] + }, + { + "type": "struct", + "name": "ekubo::router::Swap", + "members": [ + { + "name": "route", + "type": "core::array::Array::" + }, + { + "name": "token_amount", + "type": "ekubo::router::TokenAmount" + } + ] + }, + { + "type": "struct", + "name": "ekubo::router::Depth", + "members": [ + { + "name": "token0", + "type": "core::integer::u128" + }, + { + "name": "token1", + "type": "core::integer::u128" + } + ] + }, + { + "type": "interface", + "name": "ekubo::router::IRouter", + "items": [ + { + "type": "function", + "name": "swap", + "inputs": [ + { + "name": "node", + "type": "ekubo::router::RouteNode" + }, + { + "name": "token_amount", + "type": "ekubo::router::TokenAmount" + } + ], + "outputs": [ + { + "type": "ekubo::types::delta::Delta" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "multihop_swap", + "inputs": [ + { + "name": "route", + "type": "core::array::Array::" + }, + { + "name": "token_amount", + "type": "ekubo::router::TokenAmount" + } + ], + "outputs": [ + { + "type": "core::array::Array::" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "multi_multihop_swap", + "inputs": [ + { + "name": "swaps", + "type": "core::array::Array::" + } + ], + "outputs": [ + { + "type": "core::array::Array::>" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "quote", + "inputs": [ + { + "name": "swaps", + "type": "core::array::Array::" + } + ], + "outputs": [ + { + "type": "core::array::Array::>" + } + ], + "state_mutability": "external" + }, + { + "type": "function", + "name": "get_delta_to_sqrt_ratio", + "inputs": [ + { + "name": "pool_key", + "type": "ekubo::types::keys::PoolKey" + }, + { + "name": "sqrt_ratio", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "ekubo::types::delta::Delta" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_market_depth", + "inputs": [ + { + "name": "pool_key", + "type": "ekubo::types::keys::PoolKey" + }, + { + "name": "sqrt_percent", + "type": "core::integer::u128" + } + ], + "outputs": [ + { + "type": "ekubo::router::Depth" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_market_depth_v2", + "inputs": [ + { + "name": "pool_key", + "type": "ekubo::types::keys::PoolKey" + }, + { + "name": "percent_64x64", + "type": "core::integer::u128" + } + ], + "outputs": [ + { + "type": "ekubo::router::Depth" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "get_market_depth_at_sqrt_ratio", + "inputs": [ + { + "name": "pool_key", + "type": "ekubo::types::keys::PoolKey" + }, + { + "name": "sqrt_ratio", + "type": "core::integer::u256" + }, + { + "name": "percent_64x64", + "type": "core::integer::u128" + } + ], + "outputs": [ + { + "type": "ekubo::router::Depth" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "Clear", + "interface_name": "ekubo::components::clear::IClear" + }, + { + "type": "struct", + "name": "ekubo::interfaces::erc20::IERC20Dispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "interface", + "name": "ekubo::components::clear::IClear", + "items": [ + { + "type": "function", + "name": "clear", + "inputs": [ + { + "name": "token", + "type": "ekubo::interfaces::erc20::IERC20Dispatcher" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "clear_minimum", + "inputs": [ + { + "name": "token", + "type": "ekubo::interfaces::erc20::IERC20Dispatcher" + }, + { + "name": "minimum", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "clear_minimum_to_recipient", + "inputs": [ + { + "name": "token", + "type": "ekubo::interfaces::erc20::IERC20Dispatcher" + }, + { + "name": "minimum", + "type": "core::integer::u256" + }, + { + "name": "recipient", + "type": "core::starknet::contract_address::ContractAddress" + } + ], + "outputs": [ + { + "type": "core::integer::u256" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "impl", + "name": "Expires", + "interface_name": "ekubo::components::expires::IExpires" + }, + { + "type": "interface", + "name": "ekubo::components::expires::IExpires", + "items": [ + { + "type": "function", + "name": "expires", + "inputs": [ + { + "name": "at", + "type": "core::integer::u64" + } + ], + "outputs": [], + "state_mutability": "view" + } + ] + }, + { + "type": "struct", + "name": "ekubo::interfaces::core::ICoreDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "core", + "type": "ekubo::interfaces::core::ICoreDispatcher" + } + ] + }, + { + "type": "event", + "name": "ekubo::router::Router::Event", + "kind": "enum", + "variants": [] + } +] diff --git a/packages/keychain/src/components/swap/swap.ts b/packages/keychain/src/components/swap/swap.ts new file mode 100644 index 0000000000..dcb03286b3 --- /dev/null +++ b/packages/keychain/src/components/swap/swap.ts @@ -0,0 +1,157 @@ +import { useMemo } from "react"; +import { + Abi, + CallData, + CallResult, + FunctionAbi, + getChecksumAddress, + InterfaceAbi, + type Call, +} from "starknet"; + +// abi from: https://voyager.online/contract/0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066#code +import ekuboRouterAbi from "./ekuboRouterAbi.json" assert { type: "json" }; +import { erc20Abi } from "viem"; + +// detect swap transaction +// swap example in /examples/next/src/components/Profile.tsx +export const useIsSwapTransaction = ( + transactions: Call[], +): { + isSwap: boolean; +} => { + const isSwap = useMemo( + () => + transactions.length === 4 && + transactions[0].entrypoint === "transfer" && + transactions[1].entrypoint === "multihop_swap" && + transactions[2].entrypoint === "clear_minimum" && + transactions[3].entrypoint === "clear", + [transactions], + ); + + return { isSwap }; +}; + +type TransferInputs = { + amount: bigint; + recipient: string; +}; + +// type SwapInputs = { +// route: [ +// { +// pool_key: { +// token0: bigint; +// token1: bigint; +// fee: bigint; +// tick_spacing: bigint; +// extension: bigint; +// }; +// sqrt_ratio_limit: bigint; +// skip_ahead: bigint; +// }, +// ]; +// token_amount: { +// token: bigint; +// amount: { +// mag: bigint; +// sign: boolean; +// }; +// }; +// }; + +type ClearMinimumInputs = { + minimum: bigint; + token: { + contract_address: bigint; + }; +}; + +export type SwapTransaction = { + sellAddress: string; + sellAmount: string; + buyAddress: string; + buyAmount: string; +}; + +export const useSwapTransaction = ( + transactions: Call[], +): { + isSwap: boolean; + swapTransaction: SwapTransaction; +} => { + const { isSwap } = useIsSwapTransaction(transactions); + const swapTransaction = useMemo(() => { + if (!isSwap) return {} as SwapTransaction; + + const transfer = transactions.find((t) => t.entrypoint === "transfer"); + const multihop_swap = transactions.find( + (t) => t.entrypoint === "multihop_swap", + ); + const clear_minimum = transactions.find( + (t) => t.entrypoint === "clear_minimum", + ); + const clear = transactions.find((t) => t.entrypoint === "clear"); + if (!transfer || !multihop_swap || !clear_minimum || !clear) { + return {} as SwapTransaction; + } + + const parseInputs = ( + abi: Abi, + interfaceName: string, + method: string, + args: string[], + ) => { + const interfaceAbi = abi.find( + (a) => a.name === interfaceName, + ) as InterfaceAbi; + const { inputs } = (interfaceAbi?.items ?? abi).find( + (a) => a.name === method, + ) as FunctionAbi; + const callData = new CallData(abi); + const decoded = callData.decodeParameters( + inputs.map((i) => i.type), + args, + ); + const result = inputs.reduce( + (acc, input, index) => { + acc[input.name] = Array.isArray(decoded) ? decoded[index] : decoded; + return acc; + }, + {} as { [key: string]: CallResult }, + ); + return result; + }; + + // CallResult is typed, but we can't get the typescript types from them... + const transferInputs = parseInputs( + erc20Abi, + "IERC20", + "transfer", + transfer.calldata as string[], + ) as TransferInputs; + // const swapInputs = parseInputs( + // ekuboRouterAbi, + // "ekubo::router::IRouter", + // "multihop_swap", + // multihop_swap.calldata as string[], + // ) as SwapInputs; + const clearMinimumInputs = parseInputs( + ekuboRouterAbi, + "ekubo::components::clear::IClear", + "clear_minimum", + clear_minimum.calldata as string[], + ) as ClearMinimumInputs; + + const swapTransaction: SwapTransaction = { + sellAddress: getChecksumAddress(transfer.contractAddress), + sellAmount: `0x${BigInt(transferInputs.amount).toString(16)}`, + buyAddress: getChecksumAddress(clearMinimumInputs.token.contract_address), + buyAmount: `0x${BigInt(clearMinimumInputs.minimum).toString(16)}`, + }; + return swapTransaction; + }, [isSwap, transactions]); + + return { isSwap, swapTransaction }; +}; diff --git a/packages/keychain/src/components/transaction/ConfirmTransaction.tsx b/packages/keychain/src/components/transaction/ConfirmTransaction.tsx index 7d31c1bb70..bd135cefa3 100644 --- a/packages/keychain/src/components/transaction/ConfirmTransaction.tsx +++ b/packages/keychain/src/components/transaction/ConfirmTransaction.tsx @@ -1,16 +1,18 @@ +import { useEffect, useState } from "react"; import { LayoutContent } from "@cartridge/ui"; import { useConnection } from "@/hooks/connection"; import { TransactionSummary } from "@/components/transaction/TransactionSummary"; import { ControllerError } from "@/utils/connection"; import { Call, FeeEstimate } from "starknet"; import { ExecutionContainer } from "@/components/ExecutionContainer"; -import { CreateSession } from "../connect"; import { executeCore } from "@/utils/connection/execute"; -import { useEffect, useState } from "react"; -import { PageLoading } from "../Loading"; +import { CreateSession } from "@/components/connect"; +import { PageLoading } from "@/components/Loading"; import { ErrorCode } from "@cartridge/controller-wasm"; import { useToast } from "@/context/toast"; import { humanizeString } from "@cartridge/controller"; +import { useIsSwapTransaction } from "@/components/swap/swap"; +import { ConfirmSwap } from "@/components/swap/ConfirmSwap"; interface ConfirmTransactionProps { onComplete: (transaction_hash: string) => void; @@ -89,10 +91,24 @@ export function ConfirmTransaction({ } }; + const { isSwap } = useIsSwapTransaction(transactions); + if (loading) { return ; } + if (isSwap) { + return ( + + ); + } + // Show session refresh UI if SessionRefreshRequired error occurred if (needsSessionRefresh && policies && !skipSession) { return ( From 2fd2acbed9eb9101d5a859b8b7b3d05d7fcb9a46 Mon Sep 17 00:00:00 2001 From: RM3 Date: Thu, 26 Feb 2026 18:40:35 -0300 Subject: [PATCH 02/15] feat: fetch token value with useTokenSwapData() --- .../src/components/swap/ConfirmSwap.tsx | 63 +++++++------------ packages/keychain/src/hooks/token.ts | 60 ++++++++++++++++++ 2 files changed, 81 insertions(+), 42 deletions(-) diff --git a/packages/keychain/src/components/swap/ConfirmSwap.tsx b/packages/keychain/src/components/swap/ConfirmSwap.tsx index daf099711c..a910d5e4a9 100644 --- a/packages/keychain/src/components/swap/ConfirmSwap.tsx +++ b/packages/keychain/src/components/swap/ConfirmSwap.tsx @@ -1,4 +1,3 @@ -import { useMemo } from "react"; import { LayoutContent, TokenCard, @@ -7,10 +6,10 @@ import { } from "@cartridge/ui"; import { TransactionSummary } from "@/components/transaction/TransactionSummary"; import { ControllerError } from "@/utils/connection"; -import { Call, FeeEstimate, getChecksumAddress } from "starknet"; +import { Call, FeeEstimate } from "starknet"; import { ExecutionContainer } from "@/components/ExecutionContainer"; import { useSwapTransaction } from "@/components/swap/swap"; -import { useTokens } from "@/hooks/token"; +import { useTokenSwapData } from "@/hooks/token"; import placeholder from "/placeholder.svg?url"; interface ConfirmSwapProps { @@ -28,40 +27,23 @@ export function ConfirmSwap({ error, origin, }: ConfirmSwapProps) { - const { tokens } = useTokens(); const { isSwap, swapTransaction } = useSwapTransaction(transactions); + const { tokenSwapData } = useTokenSwapData([ + { + address: swapTransaction.sellAddress, + amount: swapTransaction.sellAmount, + }, + { + address: swapTransaction.buyAddress, + amount: swapTransaction.buyAmount, + }, + ]); // console.log("swapTransaction:", swapTransaction); - const sellTokens = useMemo( - () => - tokens - .filter( - (token) => - swapTransaction.sellAddress == - getChecksumAddress(token.metadata.address), - ) - .map((token) => ({ ...token, decreasing: true, increasing: false })), - [tokens, swapTransaction], - ); - - const buyTokens = useMemo( - () => - tokens - .filter( - (token) => - swapTransaction.buyAddress == - getChecksumAddress(token.metadata.address), - ) - .map((token) => ({ ...token, increasing: true, decreasing: false })), - [tokens, swapTransaction], - ); - console.log( swapTransaction.sellAddress, swapTransaction.buyAddress, - tokens, - sellTokens, - buyTokens, + tokenSwapData, ); return ( @@ -80,18 +62,15 @@ export function ConfirmSwap({ ) : ( <> - {[...sellTokens, ...buyTokens].map((token) => ( + {tokenSwapData.map((token) => ( diff --git a/packages/keychain/src/hooks/token.ts b/packages/keychain/src/hooks/token.ts index 33e9b80041..6debc3b6b3 100644 --- a/packages/keychain/src/hooks/token.ts +++ b/packages/keychain/src/hooks/token.ts @@ -8,6 +8,7 @@ import { import { useBalanceQuery, useBalancesQuery, + usePriceByAddressesQuery, } from "@cartridge/ui/utils/api/cartridge"; import makeBlockie from "ethereum-blockies-base64"; import { useAccount } from "./account"; @@ -390,3 +391,62 @@ export function useToken({ status, }; } + +export type TokenSwap = { + address: string; + amount: string; +}; + +export type TokenSwapData = Metadata & { + amount: number; + value: number | null | undefined; +}; + +export type UseTokenSwapDataResponse = { + tokenSwapData: TokenSwapData[]; + status: "success" | "error" | "idle" | "loading"; +}; + +export function useTokenSwapData( + tokens: TokenSwap[], +): UseTokenSwapDataResponse { + const { data: priceData, ...restPriceData } = usePriceByAddressesQuery({ + addresses: tokens.map((token) => token.address), + }); + + const tokenSwapData = useMemo(() => { + return tokens.map((token) => { + const metadata = erc20Metadata.find( + (m) => + getChecksumAddress(m.l2_token_address) === + getChecksumAddress(token.address), + ); + const price = priceData?.priceByAddresses.find( + (p) => getChecksumAddress(p.base) === getChecksumAddress(token.address), + ); + const amount = Number(token.amount) / 10 ** (metadata?.decimals || 18); + const tokenData: TokenSwapData = { + amount, + name: metadata?.name || "Unknown", + symbol: metadata?.symbol || "UNKNOWN", + decimals: metadata?.decimals || 18, + address: getChecksumAddress(token.address), + image: + metadata?.logo_url || makeBlockie(getChecksumAddress(token.address)), + value: price + ? (Number(price.amount) / 10 ** (price.decimals || 18)) * amount + : undefined, + }; + return tokenData; + }); + }, [tokens, priceData]); + + const status = useMemo(() => { + if (restPriceData.isLoading) return "loading"; + if (restPriceData.isError) return "error"; + if (restPriceData.isSuccess) return "success"; + return "idle"; + }, [restPriceData]); + + return { tokenSwapData, status }; +} From fdd37d503941d8a7a1780beaca1a40baf1e4724b Mon Sep 17 00:00:00 2001 From: RM3 Date: Fri, 27 Feb 2026 16:01:31 -0300 Subject: [PATCH 03/15] chore: detect multi swaps --- .../src/components/swap/ConfirmSwap.tsx | 39 ++-- packages/keychain/src/components/swap/swap.ts | 196 +++++++----------- .../keychain/src/hooks/calldata-decode.ts | 131 ++++++++++++ packages/keychain/src/hooks/token.ts | 8 +- 4 files changed, 227 insertions(+), 147 deletions(-) create mode 100644 packages/keychain/src/hooks/calldata-decode.ts diff --git a/packages/keychain/src/components/swap/ConfirmSwap.tsx b/packages/keychain/src/components/swap/ConfirmSwap.tsx index a910d5e4a9..b50f031337 100644 --- a/packages/keychain/src/components/swap/ConfirmSwap.tsx +++ b/packages/keychain/src/components/swap/ConfirmSwap.tsx @@ -8,7 +8,7 @@ import { TransactionSummary } from "@/components/transaction/TransactionSummary" import { ControllerError } from "@/utils/connection"; import { Call, FeeEstimate } from "starknet"; import { ExecutionContainer } from "@/components/ExecutionContainer"; -import { useSwapTransaction } from "@/components/swap/swap"; +import { useSwapTransactions } from "@/components/swap/swap"; import { useTokenSwapData } from "@/hooks/token"; import placeholder from "/placeholder.svg?url"; @@ -27,24 +27,15 @@ export function ConfirmSwap({ error, origin, }: ConfirmSwapProps) { - const { isSwap, swapTransaction } = useSwapTransaction(transactions); + const { isSwap, swapTransactions, additionalMethodCount } = + useSwapTransactions(transactions); const { tokenSwapData } = useTokenSwapData([ - { - address: swapTransaction.sellAddress, - amount: swapTransaction.sellAmount, - }, - { - address: swapTransaction.buyAddress, - amount: swapTransaction.buyAmount, - }, + ...swapTransactions.selling, + ...swapTransactions.buying, ]); // console.log("swapTransaction:", swapTransaction); - console.log( - swapTransaction.sellAddress, - swapTransaction.buyAddress, - tokenSwapData, - ); + console.log(`SWAPS:`, swapTransactions, tokenSwapData); return ( 0 ? `+ ${additionalMethodCount}` : ""}`} > {!isSwap ? ( @@ -68,10 +60,19 @@ export function ConfirmSwap({ image={token.image || placeholder} title={token.name} amount={`${token.amount.toLocaleString(undefined, { maximumFractionDigits: 5 })} ${token.symbol}`} - value={token.value ? `$${token.value.toFixed(2)}` : ""} - increasing={token.address === swapTransaction.buyAddress} - decreasing={token.address === swapTransaction.sellAddress} - approximately + value={ + !token.value + ? "$0.00" + : token.value < 0.01 + ? "<$0.01" + : `~$${token.value.toFixed(2)}` + } + increasing={swapTransactions.buying.some( + (t) => t.address === token.address, + )} + decreasing={swapTransactions.selling.some( + (t) => t.address === token.address, + )} clickable={false} /> ))} diff --git a/packages/keychain/src/components/swap/swap.ts b/packages/keychain/src/components/swap/swap.ts index dcb03286b3..1d9fe958be 100644 --- a/packages/keychain/src/components/swap/swap.ts +++ b/packages/keychain/src/components/swap/swap.ts @@ -1,17 +1,11 @@ import { useMemo } from "react"; -import { - Abi, - CallData, - CallResult, - FunctionAbi, - getChecksumAddress, - InterfaceAbi, - type Call, -} from "starknet"; +import { getChecksumAddress, type Call } from "starknet"; +import { useDecodeTransactionInputs } from "@/hooks/calldata-decode"; +import { TokenSwap } from "@/hooks/token"; -// abi from: https://voyager.online/contract/0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066#code -import ekuboRouterAbi from "./ekuboRouterAbi.json" assert { type: "json" }; -import { erc20Abi } from "viem"; +const findTransactions = (transactions: Call[], entrypoint: string) => { + return transactions.filter((t) => t.entrypoint === entrypoint); +}; // detect swap transaction // swap example in /examples/next/src/components/Profile.tsx @@ -22,136 +16,92 @@ export const useIsSwapTransaction = ( } => { const isSwap = useMemo( () => - transactions.length === 4 && - transactions[0].entrypoint === "transfer" && - transactions[1].entrypoint === "multihop_swap" && - transactions[2].entrypoint === "clear_minimum" && - transactions[3].entrypoint === "clear", + transactions.length >= 4 && + findTransactions(transactions, "transfer").length > 0 && + findTransactions(transactions, "multihop_swap").length > 0 && + findTransactions(transactions, "clear_minimum").length > 0 && + findTransactions(transactions, "clear").length > 0, [transactions], ); - return { isSwap }; }; -type TransferInputs = { - amount: bigint; - recipient: string; +export type SwapTransactions = { + selling: TokenSwap[]; + buying: TokenSwap[]; }; -// type SwapInputs = { -// route: [ -// { -// pool_key: { -// token0: bigint; -// token1: bigint; -// fee: bigint; -// tick_spacing: bigint; -// extension: bigint; -// }; -// sqrt_ratio_limit: bigint; -// skip_ahead: bigint; -// }, -// ]; -// token_amount: { -// token: bigint; -// amount: { -// mag: bigint; -// sign: boolean; -// }; -// }; -// }; - -type ClearMinimumInputs = { - minimum: bigint; - token: { - contract_address: bigint; - }; -}; - -export type SwapTransaction = { - sellAddress: string; - sellAmount: string; - buyAddress: string; - buyAmount: string; -}; - -export const useSwapTransaction = ( +export const useSwapTransactions = ( transactions: Call[], ): { isSwap: boolean; - swapTransaction: SwapTransaction; + swapTransactions: SwapTransactions; + swapMethodCount: number; + additionalMethodCount: number; } => { const { isSwap } = useIsSwapTransaction(transactions); - const swapTransaction = useMemo(() => { - if (!isSwap) return {} as SwapTransaction; - const transfer = transactions.find((t) => t.entrypoint === "transfer"); - const multihop_swap = transactions.find( - (t) => t.entrypoint === "multihop_swap", + const { decodeTransferInputs, decodeClearMinimumInputs } = + useDecodeTransactionInputs(); + + const [swapTransactions, swapMethodCount] = useMemo(() => { + const swapTransactions: SwapTransactions = { + selling: [], + buying: [], + }; + if (!isSwap) return [swapTransactions, 0]; + + const transfers = findTransactions(transactions, "transfer"); + const multihop_swaps = findTransactions(transactions, "multihop_swap"); + const clear_minimuns = findTransactions(transactions, "clear_minimum"); + const clears = findTransactions(transactions, "clear"); + + const transferInputs = transfers.map((transfer) => + decodeTransferInputs(transfer.calldata as string[]), ); - const clear_minimum = transactions.find( - (t) => t.entrypoint === "clear_minimum", + const clearInputs = clear_minimuns.map((clear_minimum) => + decodeClearMinimumInputs(clear_minimum.calldata as string[]), ); - const clear = transactions.find((t) => t.entrypoint === "clear"); - if (!transfer || !multihop_swap || !clear_minimum || !clear) { - return {} as SwapTransaction; - } - const parseInputs = ( - abi: Abi, - interfaceName: string, - method: string, - args: string[], - ) => { - const interfaceAbi = abi.find( - (a) => a.name === interfaceName, - ) as InterfaceAbi; - const { inputs } = (interfaceAbi?.items ?? abi).find( - (a) => a.name === method, - ) as FunctionAbi; - const callData = new CallData(abi); - const decoded = callData.decodeParameters( - inputs.map((i) => i.type), - args, + const addToken = (acc: TokenSwap[], address: string, amount: bigint) => { + const token = acc.find((t) => t.address === address); + if (token) { + token.amount += amount; + } else { + acc.push({ address, amount }); + } + return acc; + }; + + swapTransactions.selling = transferInputs.reduce((acc, input, index) => { + return addToken( + acc, + getChecksumAddress(transfers[index].contractAddress), + input.amount, ); - const result = inputs.reduce( - (acc, input, index) => { - acc[input.name] = Array.isArray(decoded) ? decoded[index] : decoded; - return acc; - }, - {} as { [key: string]: CallResult }, + }, [] as TokenSwap[]); + + swapTransactions.buying = clearInputs.reduce((acc, input) => { + return addToken( + acc, + getChecksumAddress(input.token.contract_address), + input.minimum, ); - return result; - }; + }, [] as TokenSwap[]); - // CallResult is typed, but we can't get the typescript types from them... - const transferInputs = parseInputs( - erc20Abi, - "IERC20", - "transfer", - transfer.calldata as string[], - ) as TransferInputs; - // const swapInputs = parseInputs( - // ekuboRouterAbi, - // "ekubo::router::IRouter", - // "multihop_swap", - // multihop_swap.calldata as string[], - // ) as SwapInputs; - const clearMinimumInputs = parseInputs( - ekuboRouterAbi, - "ekubo::components::clear::IClear", - "clear_minimum", - clear_minimum.calldata as string[], - ) as ClearMinimumInputs; + const count = + transfers.length + + multihop_swaps.length + + clear_minimuns.length + + clears.length; - const swapTransaction: SwapTransaction = { - sellAddress: getChecksumAddress(transfer.contractAddress), - sellAmount: `0x${BigInt(transferInputs.amount).toString(16)}`, - buyAddress: getChecksumAddress(clearMinimumInputs.token.contract_address), - buyAmount: `0x${BigInt(clearMinimumInputs.minimum).toString(16)}`, - }; - return swapTransaction; - }, [isSwap, transactions]); + return [swapTransactions, count]; + }, [isSwap, transactions, decodeClearMinimumInputs, decodeTransferInputs]); - return { isSwap, swapTransaction }; + return { + isSwap, + swapTransactions, + swapMethodCount, + additionalMethodCount: transactions.length - swapMethodCount, + }; }; diff --git a/packages/keychain/src/hooks/calldata-decode.ts b/packages/keychain/src/hooks/calldata-decode.ts new file mode 100644 index 0000000000..63352bd2ee --- /dev/null +++ b/packages/keychain/src/hooks/calldata-decode.ts @@ -0,0 +1,131 @@ +import { useCallback } from "react"; +import { Abi, CallData, CallResult, FunctionAbi, InterfaceAbi } from "starknet"; +import { erc20Abi } from "viem"; + +// ekubo abi from: +// https://voyager.online/contract/0x04505a9f06f2bd639b6601f37a4dc0908bb70e8e0e0c34b1220827d64f4fc066#code +import ekuboRouterAbi from "@/components/swap/ekuboRouterAbi.json" assert { type: "json" }; + +const useDecodeCallbackInputs = () => { + const decodeInputs = useCallback( + (abi: Abi, interfaceName: string, method: string, args: string[]) => { + const interfaceAbi = abi.find( + (a) => a.name === interfaceName, + ) as InterfaceAbi; + const { inputs } = (interfaceAbi?.items ?? abi).find( + (a) => a.name === method, + ) as FunctionAbi; + const callData = new CallData(abi); + const decoded = callData.decodeParameters( + inputs.map((i) => i.type), + args, + ); + const result = inputs.reduce( + (acc, input, index) => { + acc[input.name] = Array.isArray(decoded) ? decoded[index] : decoded; + return acc; + }, + {} as { [key: string]: CallResult }, + ); + return result as T; + }, + [], + ); + return { decodeInputs }; +}; + +export type TransferInputs = { + amount: bigint; + recipient: string; +}; + +export type MultihopSwapInputs = { + route: [ + { + pool_key: { + token0: bigint; + token1: bigint; + fee: bigint; + tick_spacing: bigint; + extension: bigint; + }; + sqrt_ratio_limit: bigint; + skip_ahead: bigint; + }, + ]; + token_amount: { + token: bigint; + amount: { + mag: bigint; + sign: boolean; + }; + }; +}; + +export interface ClearMinimumInputs { + minimum: bigint; + token: { + contract_address: bigint; + }; +} + +export interface ClearInputs { + minimum: bigint; + token: { + contract_address: bigint; + }; +} + +export const useDecodeTransactionInputs = () => { + const { decodeInputs } = useDecodeCallbackInputs(); + + const decodeTransferInputs = useCallback( + (args: string[]) => { + return decodeInputs(erc20Abi, "ERC20", "transfer", args); + }, + [decodeInputs], + ); + + const decodeMultihopSwapInputs = useCallback( + (args: string[]) => { + return decodeInputs( + ekuboRouterAbi, + "ekubo::router::IRouter", + "multihop_swap", + args, + ); + }, + [decodeInputs], + ); + + const decodeClearMinimumInputs = useCallback( + (args: string[]) => { + return decodeInputs( + ekuboRouterAbi, + "ekubo::components::clear::IClear", + "clear_minimum", + args, + ); + }, + [decodeInputs], + ); + + const decodeClearInputs = useCallback( + (args: string[]) => { + return decodeInputs( + ekuboRouterAbi, + "ekubo::components::clear::IClear", + "clear", + args, + ); + }, + [decodeInputs], + ); + + return { + decodeTransferInputs, + decodeMultihopSwapInputs, + decodeClearMinimumInputs, + decodeClearInputs, + }; +}; diff --git a/packages/keychain/src/hooks/token.ts b/packages/keychain/src/hooks/token.ts index 6debc3b6b3..076ac45624 100644 --- a/packages/keychain/src/hooks/token.ts +++ b/packages/keychain/src/hooks/token.ts @@ -394,7 +394,7 @@ export function useToken({ export type TokenSwap = { address: string; - amount: string; + amount: bigint; }; export type TokenSwapData = Metadata & { @@ -417,12 +417,10 @@ export function useTokenSwapData( const tokenSwapData = useMemo(() => { return tokens.map((token) => { const metadata = erc20Metadata.find( - (m) => - getChecksumAddress(m.l2_token_address) === - getChecksumAddress(token.address), + (m) => BigInt(m.l2_token_address) === BigInt(token.address), ); const price = priceData?.priceByAddresses.find( - (p) => getChecksumAddress(p.base) === getChecksumAddress(token.address), + (p) => BigInt(p.base) === BigInt(token.address), ); const amount = Number(token.amount) / 10 ** (metadata?.decimals || 18); const tokenData: TokenSwapData = { From 068196f5d6bd997fc357ce59b87c91f62cdb77b7 Mon Sep 17 00:00:00 2001 From: RM3 Date: Fri, 27 Feb 2026 16:59:38 -0300 Subject: [PATCH 04/15] chore: updated presets dependency --- package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 7defb07110..08f4e21fd5 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "prepare": "husky" }, "dependencies": { - "@cartridge/presets": "github:cartridge-gg/presets#c064e82", + "@cartridge/presets": "github:cartridge-gg/presets#476cc4f", "@cartridge/ui": "catalog:", "tailwindcss": "catalog:", "@graphql-codegen/cli": "^2.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cab1b5034e..5229010135 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,8 +162,8 @@ importers: .: dependencies: '@cartridge/presets': - specifier: github:cartridge-gg/presets#c064e82 - version: https://codeload.github.com/cartridge-gg/presets/tar.gz/c064e82 + specifier: github:cartridge-gg/presets#476cc4f + version: https://codeload.github.com/cartridge-gg/presets/tar.gz/476cc4f '@cartridge/ui': specifier: 'catalog:' version: https://codeload.github.com/cartridge-gg/ui/tar.gz/b4646934(@types/react-dom@18.3.7(@types/react@18.3.20))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sonner@2.0.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(starknet@8.5.4)(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.24(@swc/helpers@0.5.17))(@types/node@16.18.11)(typescript@5.8.3)))(viem@2.28.1(bufferutil@4.0.9)(typescript@5.8.3)(utf-8-validate@5.0.10)(zod@3.24.4)) @@ -1261,12 +1261,12 @@ packages: '@cartridge/penpal@6.2.4': resolution: {integrity: sha512-tdpOnSJJBFMlgLZ1+z9Ho5e6cG5EgMAb1Cmmh1lGT2tmplogU/XPMjLE6CwvKAPDoe6a38iMnbH+ySTAWWIOKA==} - '@cartridge/presets@https://codeload.github.com/cartridge-gg/presets/tar.gz/90a5fe0': - resolution: {tarball: https://codeload.github.com/cartridge-gg/presets/tar.gz/90a5fe0} + '@cartridge/presets@https://codeload.github.com/cartridge-gg/presets/tar.gz/476cc4f': + resolution: {tarball: https://codeload.github.com/cartridge-gg/presets/tar.gz/476cc4f} version: 0.0.1 - '@cartridge/presets@https://codeload.github.com/cartridge-gg/presets/tar.gz/c064e82': - resolution: {tarball: https://codeload.github.com/cartridge-gg/presets/tar.gz/c064e82} + '@cartridge/presets@https://codeload.github.com/cartridge-gg/presets/tar.gz/90a5fe0': + resolution: {tarball: https://codeload.github.com/cartridge-gg/presets/tar.gz/90a5fe0} version: 0.0.1 '@cartridge/ui@https://codeload.github.com/cartridge-gg/ui/tar.gz/b4646934': @@ -11208,11 +11208,11 @@ snapshots: '@cartridge/penpal@6.2.4': {} - '@cartridge/presets@https://codeload.github.com/cartridge-gg/presets/tar.gz/90a5fe0': + '@cartridge/presets@https://codeload.github.com/cartridge-gg/presets/tar.gz/476cc4f': dependencies: '@starknet-io/types-js': 0.8.4 - '@cartridge/presets@https://codeload.github.com/cartridge-gg/presets/tar.gz/c064e82': + '@cartridge/presets@https://codeload.github.com/cartridge-gg/presets/tar.gz/90a5fe0': dependencies: '@starknet-io/types-js': 0.8.4 From a9bc76372ef231d8fa5860207a79821028a2b2f5 Mon Sep 17 00:00:00 2001 From: RM3 Date: Fri, 27 Feb 2026 17:59:21 -0300 Subject: [PATCH 05/15] feat: swap advanced view --- .../keychain/src/components/ContractLink.tsx | 38 ++++++ .../keychain/src/components/ErrorAlert.tsx | 13 +- .../src/components/session/MessageCard.tsx | 6 +- .../components/swap/AdvancedTransactions.tsx | 119 ++++++++++++++++++ .../src/components/swap/ConfirmSwap.tsx | 26 +++- .../transaction/CallCard.stories.tsx | 15 --- .../src/components/transaction/CallCard.tsx | 115 ++++++----------- .../transaction/TransactionSummary.tsx | 1 - 8 files changed, 222 insertions(+), 111 deletions(-) create mode 100644 packages/keychain/src/components/ContractLink.tsx create mode 100644 packages/keychain/src/components/swap/AdvancedTransactions.tsx diff --git a/packages/keychain/src/components/ContractLink.tsx b/packages/keychain/src/components/ContractLink.tsx new file mode 100644 index 0000000000..1532c7eae1 --- /dev/null +++ b/packages/keychain/src/components/ContractLink.tsx @@ -0,0 +1,38 @@ +import { useConnection } from "@/hooks/connection"; +import { Address, cn } from "@cartridge/ui"; +import { useExplorer } from "@starknet-react/core"; +import { constants } from "starknet"; + +export function ContractLink({ + contractAddress, + className, +}: { + contractAddress: string; + className?: string; +}) { + const { controller } = useConnection(); + const explorer = useExplorer(); + return ( + +
+ + ); +} diff --git a/packages/keychain/src/components/ErrorAlert.tsx b/packages/keychain/src/components/ErrorAlert.tsx index a199c6ee03..6b7c3034cb 100644 --- a/packages/keychain/src/components/ErrorAlert.tsx +++ b/packages/keychain/src/components/ErrorAlert.tsx @@ -7,6 +7,7 @@ import { type GraphQLErrorDetails, type ErrorWithGraphQL, } from "@/utils/errors"; +import { humanizeString } from "@cartridge/controller"; import { ErrorCode } from "@cartridge/controller-wasm/controller"; import { Accordion, @@ -486,15 +487,3 @@ export function isControllerError( ): error is ControllerError { return !!(error as ControllerError).code; } - -export function humanizeString(str: string): string { - return ( - str - // Convert from camelCase or snake_case - .replace(/([a-z])([A-Z])/g, "$1 $2") // camelCase to spaces - .replace(/_/g, " ") // snake_case to spaces - .toLowerCase() - // Capitalize first letter - .replace(/^\w/, (c) => c.toUpperCase()) - ); -} diff --git a/packages/keychain/src/components/session/MessageCard.tsx b/packages/keychain/src/components/session/MessageCard.tsx index 12c904e25f..e0f64f2b0f 100644 --- a/packages/keychain/src/components/session/MessageCard.tsx +++ b/packages/keychain/src/components/session/MessageCard.tsx @@ -1,5 +1,4 @@ -import { useCreateSession } from "@/hooks/session"; -import type { SignMessagePolicy } from "@cartridge/presets"; +import { type PropsWithChildren, useState, useEffect } from "react"; import { Accordion, AccordionContent, @@ -11,12 +10,13 @@ import { Thumbnail, } from "@cartridge/ui"; import { cn } from "@cartridge/ui/utils"; +import { useCreateSession } from "@/hooks/session"; import { ArrowTurnDownIcon, Badge } from "@cartridge/ui"; +import type { SignMessagePolicy } from "@cartridge/presets"; import type { StarknetEnumType, StarknetMerkleType, } from "@starknet-io/types-js"; -import { type PropsWithChildren, useState, useEffect } from "react"; interface MessageCardProps { messages: SignMessagePolicyWithEnabled[]; diff --git a/packages/keychain/src/components/swap/AdvancedTransactions.tsx b/packages/keychain/src/components/swap/AdvancedTransactions.tsx new file mode 100644 index 0000000000..a2c7b8a188 --- /dev/null +++ b/packages/keychain/src/components/swap/AdvancedTransactions.tsx @@ -0,0 +1,119 @@ +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + CheckboxIcon, + GearIcon, + Thumbnail, +} from "@cartridge/ui"; +import { cn } from "@cartridge/ui/utils"; +import { Call } from "starknet"; +import { humanizeString } from "@cartridge/controller"; +import { useState, PropsWithChildren } from "react"; +import { ContractLink } from "@/components/ContractLink"; +import { CallCardContents } from "../transaction/CallCard"; + +interface AdvancedTransactionsProps { + transactions: Call[]; + isExpanded?: boolean; + className?: string; +} + +export function AdvancedTransactions({ + transactions, + isExpanded = false, + className, +}: AdvancedTransactionsProps) { + const [isOpened, setisOpened] = useState(isExpanded); + + return ( + setisOpened(value === "item")} + > + + + } + centered={true} + /> +

Advanced

+
+ + + {transactions.map((call) => ( + + + {/*
+
+
+
+

+ {humanizeString(call.entrypoint)} +

+
+
+
+
*/} +
+ ))} +
+
+
+ ); +} + +interface CollapsibleTransactionProps extends PropsWithChildren { + transaction: Call; + enabled: boolean; +} + +export function CollapsibleTransactionRow({ + transaction, + children, +}: CollapsibleTransactionProps) { + const [value, setValue] = useState(""); + + return ( + + + +
+ +

+ {humanizeString(transaction.entrypoint)} +

+ +
+
+ + + {children} + +
+
+ ); +} diff --git a/packages/keychain/src/components/swap/ConfirmSwap.tsx b/packages/keychain/src/components/swap/ConfirmSwap.tsx index b50f031337..513dade4f7 100644 --- a/packages/keychain/src/components/swap/ConfirmSwap.tsx +++ b/packages/keychain/src/components/swap/ConfirmSwap.tsx @@ -1,4 +1,7 @@ +import { useState } from "react"; import { + Button, + GearIcon, LayoutContent, TokenCard, TokenSummary, @@ -10,6 +13,7 @@ import { Call, FeeEstimate } from "starknet"; import { ExecutionContainer } from "@/components/ExecutionContainer"; import { useSwapTransactions } from "@/components/swap/swap"; import { useTokenSwapData } from "@/hooks/token"; +import { AdvancedTransactions } from "./AdvancedTransactions"; import placeholder from "/placeholder.svg?url"; interface ConfirmSwapProps { @@ -27,15 +31,14 @@ export function ConfirmSwap({ error, origin, }: ConfirmSwapProps) { + const [advancedVisible, setAdvancedVisible] = useState(false); + const { isSwap, swapTransactions, additionalMethodCount } = useSwapTransactions(transactions); const { tokenSwapData } = useTokenSwapData([ ...swapTransactions.selling, ...swapTransactions.buying, ]); - // console.log("swapTransaction:", swapTransaction); - - console.log(`SWAPS:`, swapTransactions, tokenSwapData); return ( 0 ? `+ ${additionalMethodCount}` : ""}`} + right={ + !advancedVisible ? ( + + ) : undefined + } > {!isSwap ? ( ) : ( <> - + {tokenSwapData.map((token) => ( ))} + {advancedVisible && ( + + )} )} diff --git a/packages/keychain/src/components/transaction/CallCard.stories.tsx b/packages/keychain/src/components/transaction/CallCard.stories.tsx index 9fd90592b6..69437a59c3 100644 --- a/packages/keychain/src/components/transaction/CallCard.stories.tsx +++ b/packages/keychain/src/components/transaction/CallCard.stories.tsx @@ -15,7 +15,6 @@ const meta: Meta = { }, }, argTypes: { - address: { control: "text" }, title: { control: "text" }, call: { control: "object" }, }, @@ -34,8 +33,6 @@ type Story = StoryObj; export const SimpleCalldata: Story = { args: { - address: - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", title: "Transfer", call: { contractAddress: @@ -53,8 +50,6 @@ export const SimpleCalldata: Story = { export const ObjectCalldata: Story = { args: { - address: - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", title: "Swap", call: { contractAddress: @@ -77,8 +72,6 @@ export const ObjectCalldata: Story = { export const ComplexTypes: Story = { args: { - address: - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", title: "Complex Transaction", call: { contractAddress: @@ -113,8 +106,6 @@ export const ComplexTypes: Story = { export const WithUint256: Story = { args: { - address: - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", title: "With Uint256", call: { contractAddress: @@ -132,8 +123,6 @@ export const WithUint256: Story = { export const WithEnum: Story = { args: { - address: - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", title: "With Enum", call: { contractAddress: @@ -150,8 +139,6 @@ export const WithEnum: Story = { export const NestedObjects: Story = { args: { - address: - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", title: "Nested Objects", call: { contractAddress: @@ -184,8 +171,6 @@ export const NestedObjects: Story = { export const LongArrays: Story = { args: { - address: - "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", title: "Long Arrays", call: { contractAddress: diff --git a/packages/keychain/src/components/transaction/CallCard.tsx b/packages/keychain/src/components/transaction/CallCard.tsx index 217ed22930..c31969c4c6 100644 --- a/packages/keychain/src/components/transaction/CallCard.tsx +++ b/packages/keychain/src/components/transaction/CallCard.tsx @@ -1,4 +1,5 @@ -import { useConnection } from "@/hooks/connection"; +import { useState, useEffect } from "react"; +import { Call } from "starknet"; import { Card, CardContent, @@ -9,12 +10,10 @@ import { Badge, Address, } from "@cartridge/ui"; -import { useExplorer } from "@starknet-react/core"; -import { constants, Call } from "starknet"; -import { useState, useEffect } from "react"; +import { humanizeString } from "@cartridge/controller"; +import { ContractLink } from "@/components/ContractLink"; interface CallCardProps { - address: string; title: string; call: Call; icon?: React.ReactNode; @@ -223,13 +222,7 @@ function CalldataKeyValue({ keyName, value }: { keyName: string; value: any }) { ); } -export function CallCard({ - address, - call, - defaultExpanded = false, -}: CallCardProps) { - const { controller } = useConnection(); - const explorer = useExplorer(); +export function CallCard({ call, defaultExpanded = false }: CallCardProps) { const [isExpanded, setIsExpanded] = useState(defaultExpanded); // Update expansion state when defaultExpanded prop changes @@ -237,22 +230,6 @@ export function CallCard({ setIsExpanded(defaultExpanded); }, [defaultExpanded]); - const explorerLink = ( - -
- - ); - return ( @@ -272,48 +249,7 @@ export function CallCard({ -
-
-
- Contract -
- {explorerLink} -
- -
-
- Entrypoint -
-
- - {call.entrypoint} - -
-
- - {call.calldata && ( -
-
- Calldata -
-
- {Array.isArray(call.calldata) - ? call.calldata.map((data, i) => ( - - )) - : Object.entries(call.calldata).map( - ([key, value], i) => ( - - ), - )} -
-
- )} -
+
@@ -322,10 +258,37 @@ export function CallCard({ ); } -function humanizeString(str: string): string { - return str - .replace(/([a-z])([A-Z])/g, "$1 $2") - .replace(/_/g, " ") - .toLowerCase() - .replace(/^\w/, (c) => c.toUpperCase()); +export function CallCardContents({ call }: { call: Call }) { + return ( +
+
+
Contract
+ +
+ +
+
Entrypoint
+
+ + {call.entrypoint} + +
+
+ + {call.calldata && ( + <> +
Calldata
+
+ {Array.isArray(call.calldata) + ? call.calldata.map((data, i) => ( + + )) + : Object.entries(call.calldata).map(([key, value], i) => ( + + ))} +
+ + )} +
+ ); } diff --git a/packages/keychain/src/components/transaction/TransactionSummary.tsx b/packages/keychain/src/components/transaction/TransactionSummary.tsx index fdb01d8134..6fb5e5c97f 100644 --- a/packages/keychain/src/components/transaction/TransactionSummary.tsx +++ b/packages/keychain/src/components/transaction/TransactionSummary.tsx @@ -8,7 +8,6 @@ export function TransactionSummary({ calls }: { calls: Call[] }) { return ( Date: Mon, 2 Mar 2026 17:07:02 -0300 Subject: [PATCH 06/15] feat: consolidated fees footer --- examples/next/src/components/Profile.tsx | 23 +++- examples/next/src/components/Swap.tsx | 79 ++++++++++++ .../components/ExecutionContainer.test.tsx | 27 ++-- .../src/components/ExecutionContainer.tsx | 40 ++++-- packages/keychain/src/components/Fees.tsx | 122 +++++++++++++----- packages/keychain/src/components/FeesRow.tsx | 108 ++++++++++++++++ packages/keychain/src/components/Total.tsx | 89 ------------- .../collection/collectible-purchase.tsx | 25 ++-- .../collection/collection-purchase.tsx | 25 ++-- .../inventory/collection/footer.tsx | 114 ++++++++-------- .../src/components/swap/ConfirmSwap.tsx | 61 ++++++--- .../transaction/ConfirmTransaction.test.tsx | 4 + .../keychain/src/test/mocks/connection.tsx | 2 + 13 files changed, 467 insertions(+), 252 deletions(-) create mode 100644 packages/keychain/src/components/FeesRow.tsx delete mode 100644 packages/keychain/src/components/Total.tsx diff --git a/examples/next/src/components/Profile.tsx b/examples/next/src/components/Profile.tsx index bd47fbc4eb..5167484473 100644 --- a/examples/next/src/components/Profile.tsx +++ b/examples/next/src/components/Profile.tsx @@ -171,12 +171,29 @@ export function Profile() { ctrlConnector.controller.openProfileAt( // "account/bal7hazar/inventory/collection/0x046dA8955829ADF2bDa310099A0063451923f02E648cF25A1203aac6335CF0e4/token/0x000000000000000000000000000000000000000000000000000000000000c527?ps=arcade-main&preset=loot-survivor&address=0x027917d3084dC0dcd3C4ED5189733d14b0c4C13E762829BD3D1D761aa36201AB&purchaseView=true&tokenIds=0x000000000000000000000000000000000000000000000000000000000000c527", // "account/mataleone/inventory/collection/0x046dA8955829ADF2bDa310099A0063451923f02E648cF25A1203aac6335CF0e4/purchase?ps=arcade-main&preset=loot-survivor&orders=2674", - // "account/mataleone/inventory/collection/0x046dA8955829ADF2bDa310099A0063451923f02E648cF25A1203aac6335CF0e4/purchase?ps=arcade-main&preset=loot-survivor&orders=520", - "account/mataleone/inventory/collection/0x07aAa9866750A0db82a54bA8674c38620Fa2F967D2FBb31133DEF48E0527c87f/purchase?ps=arcade-main&preset=pistols&orders=2867", + "account/mataleone/inventory/collection/0x046dA8955829ADF2bDa310099A0063451923f02E648cF25A1203aac6335CF0e4/purchase?ps=arcade-main&preset=loot-survivor&orders=8772", ) } > - Open at Purchase + Purchase 1 + + + + ); @@ -280,3 +287,75 @@ const LS2_PURCHASE_GAME = [ ], }, ]; + +const LS2_PURCHASE_GAME_ERROR = [ + { + contractAddress: + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + entrypoint: "transfer", + calldata: [ + "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e", + "0x5bbb37da193af4ba90000000", + "0x0", + ], + }, + { + contractAddress: + "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e", + entrypoint: "multihop_swap", + calldata: [ + "0x1", + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + "0x0452810188c4cb3aebd63711a3b445755bc0d6c4f27b923fdd99b1a118858136", + "0", + "0x56a4c", + "0x043e4f09c32d13d43a880e85f69f7de93ceda62d6cf2581a582c6db635548fdc", + "0x1000003f7f1380b75", + "0x0", + 0, + "0x0452810188C4Cb3AEbD63711a3b445755BC0D6C4f27B923fDd99B1A118858136", + "0xde0b6b3a7640000", + "0x1", + ], + }, + { + contractAddress: + "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e", + entrypoint: "clear_minimum", + calldata: [ + "1955023220287003686908448593668771782622329060199208410425295899940041883958", + "1000000000000000000", + "0", + ], + }, + { + contractAddress: + "0x0199741822c2dc722f6f605204f35e56dbc23bceed54818168c4c49e4fb8737e", + entrypoint: "clear", + calldata: [ + "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", + ], + }, + { + contractAddress: + "0x0452810188C4Cb3AEbD63711a3b445755BC0D6C4f27B923fDd99B1A118858136", + entrypoint: "approve", + calldata: [ + "294172758298611957878874535440244936028848058202724233951972339591192112194", + "1000000000000000000", + "0", + ], + }, + { + contractAddress: + "0x00a67ef20b61a9846e1c82b411175e6ab167ea9f8632bd6c2091823c3629ec42", + entrypoint: "buy_game", + calldata: [ + "0", + "0", + "2017717448871504735845", + "2403140985568399978641699320335980224292375691718886561247325844102368719999", + "0", + ], + }, +]; diff --git a/packages/keychain/src/components/ExecutionContainer.test.tsx b/packages/keychain/src/components/ExecutionContainer.test.tsx index 9d1674cd0d..1b3b0fecaa 100644 --- a/packages/keychain/src/components/ExecutionContainer.test.tsx +++ b/packages/keychain/src/components/ExecutionContainer.test.tsx @@ -22,6 +22,12 @@ vi.mock("@/hooks/tokens", () => ({ convertTokenAmountToUSD: vi.fn(() => "$0.01"), })); +const estimateInvokeFee = vi.fn().mockImplementation(async () => ({ + suggestedMaxFee: BigInt(1000), +})); +const address = vi.fn().mockImplementation(async () => "0x123456789abcdef"); +const username = vi.fn().mockImplementation(async () => "testuser"); + describe("ExecutionContainer", () => { const defaultProps = { transactions: [], @@ -45,10 +51,6 @@ describe("ExecutionContainer", () => { }); it("estimates fees when transactions are provided", async () => { - const estimateInvokeFee = vi.fn().mockImplementation(async () => ({ - suggestedMaxFee: BigInt(1000), - })); - await act(async () => { renderWithProviders( { connection: { controller: { estimateInvokeFee, + address, + username, // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any, }, @@ -80,10 +84,6 @@ describe("ExecutionContainer", () => { it("handles submit action correctly", async () => { const onSubmit = vi.fn().mockResolvedValue(undefined); - const estimateInvokeFee = vi.fn().mockImplementation(async () => ({ - suggestedMaxFee: BigInt(1000), - })); - await act(async () => { renderWithProviders( { connection: { controller: { estimateInvokeFee, + address, + username, // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any, }, @@ -138,9 +140,6 @@ describe("ExecutionContainer", () => { code: 113, // ErrorCode.InsufficientBalance, message: "Insufficient balance", }); - const estimateInvokeFee = vi.fn().mockResolvedValue({ - suggestedMaxFee: BigInt(1000), - }); await act(async () => { renderWithProviders( @@ -160,6 +159,8 @@ describe("ExecutionContainer", () => { connection: { controller: { estimateInvokeFee, + address, + username, // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any, }, @@ -197,7 +198,7 @@ describe("ExecutionContainer", () => { it("shows deploy view when controller is not deployed", async () => { await act(async () => { - renderWithProviders( + renderWithConnection( { it("shows funding view when balance is insufficient", async () => { await act(async () => { - renderWithConnection( + renderWithProviders( ) { @@ -175,11 +176,14 @@ export function ExecutionContainer({ case ErrorCode.InsufficientBalance: return ( <> - {ctrlError ? ( - - ) : ( - - )} + + } + additionalFees={additionalFees} + /> ); @@ -232,7 +236,11 @@ export function ExecutionContainer({ // Paymaster not available, fallback to user pays flow return ( <> - + - - + + - diff --git a/packages/keychain/src/components/swap/ConfirmSwap.tsx b/packages/keychain/src/components/swap/ConfirmSwap.tsx index a23eed2e0c..296f0ae8af 100644 --- a/packages/keychain/src/components/swap/ConfirmSwap.tsx +++ b/packages/keychain/src/components/swap/ConfirmSwap.tsx @@ -19,7 +19,7 @@ interface ConfirmSwapProps { onSubmit: (maxFee?: FeeEstimate) => Promise; onError?: (error: ControllerError) => void; transactions: Call[]; - error?: ControllerError; + executionError?: ControllerError; origin: string; } @@ -27,7 +27,7 @@ export function ConfirmSwap({ onSubmit, onError, transactions, - error, + executionError, origin, }: ConfirmSwapProps) { const [advancedVisible, setAdvancedVisible] = useState(false); @@ -58,8 +58,8 @@ export function ConfirmSwap({ icon={} title={"Review Swap"} description={origin} - executionError={error} transactions={transactions} + executionError={executionError} onSubmit={onSubmit} onError={onError} buttonText={`Swap ${additionalMethodCount > 0 ? `+ ${additionalMethodCount}` : ""}`} diff --git a/packages/keychain/src/components/transaction/ConfirmTransaction.tsx b/packages/keychain/src/components/transaction/ConfirmTransaction.tsx index 0e141ee580..6dcd5b62e6 100644 --- a/packages/keychain/src/components/transaction/ConfirmTransaction.tsx +++ b/packages/keychain/src/components/transaction/ConfirmTransaction.tsx @@ -103,7 +103,7 @@ export function ConfirmTransaction({ onSubmit={onSubmit} onError={onError} transactions={transactions} - error={error || executionError} + executionError={error || executionError} origin={origin} /> );