Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/keychain/src/__mocks__/sharedMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ export const mockConnection = {
.fn()
.mockImplementation(() => constants.StarknetChainId.SN_MAIN),
estimateInvokeFee: vi.fn().mockImplementation(async () => ({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
})),
},
upgrade: {
Expand Down
1 change: 1 addition & 0 deletions packages/keychain/src/components/Execute.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ vi.mock("@/hooks/tokens", () => ({
error: null,
})),
convertTokenAmountToUSD: vi.fn(() => "$0.01"),
formatBalance: vi.fn(() => "0.01"),
}));

// Mock the connection hook
Expand Down
25 changes: 22 additions & 3 deletions packages/keychain/src/components/ExecutionContainer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ vi.mock("@/hooks/tokens", () => ({
error: null,
})),
convertTokenAmountToUSD: vi.fn(() => "$0.01"),
formatBalance: vi.fn(() => "0.01"),
}));

describe("ExecutionContainer", () => {
Expand All @@ -46,7 +47,13 @@ describe("ExecutionContainer", () => {

it("estimates fees when transactions are provided", async () => {
const estimateInvokeFee = vi.fn().mockImplementation(async () => ({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
}));

await act(async () => {
Expand Down Expand Up @@ -81,7 +88,13 @@ describe("ExecutionContainer", () => {
const onSubmit = vi.fn().mockResolvedValue(undefined);

const estimateInvokeFee = vi.fn().mockImplementation(async () => ({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
}));

await act(async () => {
Expand Down Expand Up @@ -139,7 +152,13 @@ describe("ExecutionContainer", () => {
message: "Insufficient balance",
});
const estimateInvokeFee = vi.fn().mockResolvedValue({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
});

await act(async () => {
Expand Down
39 changes: 39 additions & 0 deletions packages/keychain/src/components/Fees.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { FeeEstimate } from "starknet";
import { Fees } from "./Fees";

vi.mock("@/hooks/tokens", () => ({
useFeeToken: vi.fn(() => ({
token: {
address: "0x01",
symbol: "STRK",
decimals: 18,
price: { value: 1, currency: "USD" },
},
isLoading: false,
error: null,
})),
convertTokenAmountToUSD: vi.fn(() => "$0.01"),
formatBalance: vi.fn(() => "0.01"),
}));

describe("Fees", () => {
it("shows a partial paymaster label when subsidy is applied", () => {
const estimate = {
overall_fee: "0x1",
l1_gas_consumed: "0x0",
l1_gas_price: "0x0",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
} as FeeEstimate;

render(<Fees isLoading={false} maxFee={estimate} />);

expect(
screen.getByText("Partial paymaster subsidy applied"),
).toBeInTheDocument();
});
});
7 changes: 7 additions & 0 deletions packages/keychain/src/components/Fees.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
formatBalance,
useFeeToken,
} from "@/hooks/tokens";
import { isPartialPaymaster } from "@/utils/fee";
import { ErrorAlert } from "./ErrorAlert";
import { Total } from "./Total";

Expand All @@ -19,6 +20,7 @@ export function Fees({
const [feeValue, setFeeValue] = useState<number>();
const [usdFee, setUsdFee] = useState<string>();
const isLoading = isEstimating || isPriceLoading;
const partialPaymaster = isPartialPaymaster(maxFee);

useEffect(() => {
if (isLoading || error || !token) {
Expand Down Expand Up @@ -68,6 +70,11 @@ export function Fees({
usdValue={usdFee}
isLoading={isLoading}
/>
{partialPaymaster && !isLoading && (
<p className="mt-2 text-xs text-foreground-400">
Partial paymaster subsidy applied
</p>
)}
</div>
);
}
66 changes: 11 additions & 55 deletions packages/keychain/src/components/provider/upgrade.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,61 +11,17 @@ import { addAddressPadding, Call } from "starknet";
import { ControllerError } from "@/utils/connection";
import Controller from "@/utils/controller";
import { usePostHog } from "./posthog";

export enum OutsideExecutionVersion {
V2,
V3,
}

export type ControllerVersionInfo = {
version: string;
hash: string;
outsideExecutionVersion: OutsideExecutionVersion;
changes: string[];
};

export const CONTROLLER_VERSIONS: ControllerVersionInfo[] = [
{
version: "1.0.4",
hash: "0x24a9edbfa7082accfceabf6a92d7160086f346d622f28741bf1c651c412c9ab",
outsideExecutionVersion: OutsideExecutionVersion.V2,
changes: [],
},
{
version: "1.0.5",
hash: "0x32e17891b6cc89e0c3595a3df7cee760b5993744dc8dfef2bd4d443e65c0f40",
outsideExecutionVersion: OutsideExecutionVersion.V2,
changes: ["Improved session token implementation"],
},
{
version: "1.0.6",
hash: "0x59e4405accdf565112fe5bf9058b51ab0b0e63665d280b816f9fe4119554b77",
outsideExecutionVersion: OutsideExecutionVersion.V3,
changes: [
"Support session key message signing",
"Support session guardians",
"Improve paymaster nonce management",
],
},
{
version: "1.0.7",
hash: "0x3e0a04bab386eaa51a41abe93d8035dccc96bd9d216d44201266fe0b8ea1115",
outsideExecutionVersion: OutsideExecutionVersion.V3,
changes: ["Unified message signature verification"],
},
{
version: "1.0.8",
hash: "0x511dd75da368f5311134dee2356356ac4da1538d2ad18aa66d57c47e3757d59",
outsideExecutionVersion: OutsideExecutionVersion.V3,
changes: ["Improved session message signature"],
},
{
version: "1.0.9",
hash: "0x743c83c41ce99ad470aa308823f417b2141e02e04571f5c0004e743556e7faf",
outsideExecutionVersion: OutsideExecutionVersion.V3,
changes: ["Wildcard session support"],
},
];
import {
CONTROLLER_VERSIONS,
OutsideExecutionVersion,
} from "@/utils/controller-versions";
import type { ControllerVersionInfo } from "@/utils/controller-versions";

export {
CONTROLLER_VERSIONS,
OutsideExecutionVersion,
} from "@/utils/controller-versions";
export type { ControllerVersionInfo } from "@/utils/controller-versions";

export const STABLE_CONTROLLER = CONTROLLER_VERSIONS[5];
export const BETA_CONTROLLER = CONTROLLER_VERSIONS[5];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ const meta = {
connection: {
controller: {
estimateInvokeFee: () => ({
suggestedMaxFee: "100",
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
}),
hasSession: () => true,
session: () => true,
Expand Down Expand Up @@ -127,7 +133,13 @@ export const ValidationErrorFromProp: Story = {
controller: {
estimateInvokeFee: () =>
Promise.resolve({
suggestedMaxFee: BigInt(100),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
}),
hasSession: () => true,
session: () => true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ vi.mock("@/hooks/tokens", () => ({
error: null,
})),
convertTokenAmountToUSD: vi.fn(() => "$0.01"),
formatBalance: vi.fn(() => "0.01"),
}));

// Mock the upgrade provider hook
Expand Down Expand Up @@ -69,7 +70,13 @@ describe("ConfirmTransaction", () => {

const mockExecute = vi.fn().mockRejectedValue(validationError);
const estimateInvokeFee = vi.fn().mockResolvedValue({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
});

await act(async () => {
Expand Down Expand Up @@ -110,7 +117,13 @@ describe("ConfirmTransaction", () => {

const mockExecute = vi.fn().mockRejectedValue(validationError);
const estimateInvokeFee = vi.fn().mockResolvedValue({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
});

await act(async () => {
Expand Down Expand Up @@ -159,7 +172,13 @@ describe("ConfirmTransaction", () => {
transaction_hash: "0xabc123",
});
const estimateInvokeFee = vi.fn().mockResolvedValue({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
});

await act(async () => {
Expand Down Expand Up @@ -209,7 +228,13 @@ describe("ConfirmTransaction", () => {
controller: {
isRequestedSession: vi.fn().mockResolvedValue(true),
estimateInvokeFee: vi.fn().mockResolvedValue({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
}),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
Expand Down Expand Up @@ -242,7 +267,13 @@ describe("ConfirmTransaction", () => {
});

const estimateInvokeFee = vi.fn().mockResolvedValue({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
});

await act(async () => {
Expand Down Expand Up @@ -292,7 +323,13 @@ describe("ConfirmTransaction", () => {
transaction_hash: mockTransactionHash,
});
const estimateInvokeFee = vi.fn().mockResolvedValue({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
});

await act(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { executeCore } from "@/utils/connection/execute";
import { useEffect, useState } from "react";
import { PageLoading } from "../Loading";
import { ErrorCode } from "@cartridge/controller-wasm";
import { isPartialPaymaster } from "@/utils/fee";
import { buildPartialPaymasterCalls } from "@/utils/partial-paymaster";
import {
OutsideExecutionVersion,
resolveOutsideExecutionVersion,
} from "@/utils/controller-versions";

interface ConfirmTransactionProps {
onComplete: (transaction_hash: string) => void;
Expand Down Expand Up @@ -61,6 +67,24 @@ export function ConfirmTransaction({
}

try {
if (isPartialPaymaster(maxFee)) {
const callsWithFee = buildPartialPaymasterCalls(transactions, maxFee, {
order: "append",
});
const version = resolveOutsideExecutionVersion(
account.classHash?.(),
OutsideExecutionVersion.V3,
);

const { transaction_hash } =
version === OutsideExecutionVersion.V2
? await account.executeFromOutsideV2(callsWithFee)
: await account.executeFromOutsideV3(callsWithFee);

onComplete(transaction_hash);
return;
}

const { transaction_hash } = await account.execute(transactions, maxFee);
onComplete(transaction_hash);
} catch (e) {
Expand Down
8 changes: 7 additions & 1 deletion packages/keychain/src/test/mocks/connection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import { NavigationProvider } from "@/context/navigation";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const defaultMockController: any = {
estimateInvokeFee: vi.fn().mockImplementation(async () => ({
suggestedMaxFee: BigInt(1000),
overall_fee: "0x64",
l1_gas_consumed: "0x1",
l1_gas_price: "0x1",
l2_gas_consumed: "0x0",
l2_gas_price: "0x0",
l1_data_gas_consumed: "0x0",
l1_data_gas_price: "0x0",
})),
chainId: vi.fn().mockImplementation(() => constants.StarknetChainId.SN_MAIN),
} as const;
Expand Down
Loading
Loading