create-stylus 0.1.10 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/templates/base/.lintstagedrc.js +2 -2
  3. package/templates/base/package.json +1 -1
  4. package/templates/base/packages/nextjs/components/Header.tsx +1 -2
  5. package/templates/base/packages/nextjs/components/ThemeProvider.tsx +1 -2
  6. package/templates/base/packages/nextjs/components/scaffold-eth/Faucet.tsx +0 -1
  7. package/templates/base/packages/nextjs/components/scaffold-eth/FaucetButton.tsx +0 -1
  8. package/templates/base/packages/nextjs/components/scaffold-eth/Input/EtherInput.tsx +1 -0
  9. package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/AddressInfoDropdown.tsx +8 -7
  10. package/templates/base/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/WrongNetworkDropdown.tsx +8 -2
  11. package/templates/base/packages/nextjs/eslint.config.mjs +8 -18
  12. package/templates/base/packages/nextjs/hooks/scaffold-eth/useScaffoldWriteContract.ts +48 -17
  13. package/templates/base/packages/nextjs/next-env.d.ts +1 -1
  14. package/templates/base/packages/nextjs/next.config.js +0 -8
  15. package/templates/base/packages/nextjs/package.json +29 -30
  16. package/templates/base/packages/nextjs/postcss.config.js +1 -2
  17. package/templates/base/packages/nextjs/scaffold.config.ts +10 -0
  18. package/templates/base/packages/nextjs/styles/globals.css +128 -4
  19. package/templates/base/packages/nextjs/tsconfig.json +3 -3
  20. package/templates/base/packages/stylus/scripts/utils/command.ts +38 -0
  21. package/templates/base/readme.md +2 -2
  22. package/templates/base/yarn.lock +6035 -3448
  23. package/templates/base/packages/nextjs/.eslintignore +0 -11
  24. package/templates/base/packages/nextjs/.eslintrc.json +0 -15
  25. package/templates/base/packages/nextjs/tailwind.config.js +0 -102
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-stylus",
3
- "version": "0.1.10",
3
+ "version": "0.1.14",
4
4
  "author": "Q3 Labs",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,9 +1,9 @@
1
1
  const path = require("path");
2
2
 
3
3
  const buildNextEslintCommand = (filenames) =>
4
- `yarn next:lint --fix --file ${filenames
4
+ `yarn workspace @ss/nextjs eslint --fix ${filenames
5
5
  .map((f) => path.relative(path.join("packages", "nextjs"), f))
6
- .join(" --file ")}`;
6
+ .join(" ")}`;
7
7
 
8
8
  const checkTypesNextCommand = () => "yarn next:check-types";
9
9
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ss",
3
- "version": "0.1.10",
3
+ "version": "0.1.14",
4
4
  "private": true,
5
5
  "workspaces": {
6
6
  "packages": [
@@ -105,9 +105,8 @@ export const Header = () => {
105
105
  }}
106
106
  >
107
107
  <div className="navbar-start w-auto lg:w-1/2">
108
- <div className="lg:hidden dropdown" ref={burgerMenuRef}>
108
+ <div className={`lg:hidden dropdown ${isDrawerOpen ? "dropdown-open" : ""}`} ref={burgerMenuRef}>
109
109
  <label
110
- tabIndex={0}
111
110
  className={`ml-1 btn btn-ghost ${isDrawerOpen ? "hover:bg-secondary" : "hover:bg-transparent"}`}
112
111
  onClick={() => {
113
112
  setIsDrawerOpen(prevIsOpenState => !prevIsOpenState);
@@ -1,8 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import * as React from "react";
4
- import { ThemeProvider as NextThemesProvider } from "next-themes";
5
- import { type ThemeProviderProps } from "next-themes/dist/types";
4
+ import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from "next-themes";
6
5
 
7
6
  export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => {
8
7
  const [mounted, setMounted] = React.useState(false);
@@ -72,7 +72,6 @@ export const Faucet = () => {
72
72
  await faucetTxn({
73
73
  to: inputAddress,
74
74
  value: parseEther(sendValue as `${number}`),
75
- account: faucetAddress,
76
75
  });
77
76
  setLoading(false);
78
77
  setInputAddress(undefined);
@@ -36,7 +36,6 @@ export const FaucetButton = () => {
36
36
  try {
37
37
  setLoading(true);
38
38
  await faucetTxn({
39
- account: FAUCET_ADDRESS,
40
39
  to: address,
41
40
  value: parseEther(NUM_OF_ETH),
42
41
  });
@@ -66,6 +66,7 @@ export const EtherInput = ({
66
66
  return transitoryDisplayValue;
67
67
  }
68
68
  // Clear any transitory display values that might be set
69
+ // eslint-disable-next-line react-hooks/set-state-in-render
69
70
  setTransitoryDisplayValue(undefined);
70
71
  return newDisplayValue;
71
72
  }, [nativeCurrencyPrice, transitoryDisplayValue, displayUsdMode, value]);
@@ -40,21 +40,22 @@ export const AddressInfoDropdown = ({ address, ensAvatar, displayName, onSwitchA
40
40
  const { copyToClipboard: copyAddressToClipboard, isCopiedToClipboard: isAddressCopiedToClipboard } =
41
41
  useCopyToClipboard();
42
42
  const [selectingNetwork, setSelectingNetwork] = useState(false);
43
- const dropdownRef = useRef<HTMLDetailsElement>(null);
43
+ const [isOpen, setIsOpen] = useState(false);
44
+ const dropdownRef = useRef<HTMLDivElement>(null);
44
45
 
45
46
  const closeDropdown = () => {
46
47
  setSelectingNetwork(false);
47
- dropdownRef.current?.removeAttribute("open");
48
+ setIsOpen(false);
48
49
  };
49
50
 
50
51
  useOutsideClick(dropdownRef, closeDropdown);
51
52
 
52
53
  return (
53
54
  <>
54
- <details ref={dropdownRef} className="dropdown dropdown-end leading-3">
55
- <summary
56
- tabIndex={0}
55
+ <div ref={dropdownRef} className={`dropdown dropdown-end leading-3 ${isOpen ? "dropdown-open" : ""}`}>
56
+ <label
57
57
  className="dropdown-toggle gap-0 !h-auto"
58
+ onClick={() => setIsOpen(prev => !prev)}
58
59
  style={{
59
60
  position: "relative",
60
61
  width: "220px",
@@ -126,7 +127,7 @@ export const AddressInfoDropdown = ({ address, ensAvatar, displayName, onSwitchA
126
127
  }}
127
128
  />
128
129
  </div>
129
- </summary>
130
+ </label>
130
131
  <ul
131
132
  tabIndex={0}
132
133
  className="dropdown-content menu z-[2] p-2 mt-4 gap-1"
@@ -229,7 +230,7 @@ export const AddressInfoDropdown = ({ address, ensAvatar, displayName, onSwitchA
229
230
  </button>
230
231
  </li>
231
232
  </ul>
232
- </details>
233
+ </div>
233
234
  </>
234
235
  );
235
236
  };
@@ -1,13 +1,19 @@
1
+ import { useRef, useState } from "react";
1
2
  import { NetworkOptions } from "./NetworkOptions";
2
3
  import { useDisconnect } from "wagmi";
3
4
  import { ArrowLeftEndOnRectangleIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
5
+ import { useOutsideClick } from "~~/hooks/scaffold-eth";
4
6
 
5
7
  export const WrongNetworkDropdown = () => {
6
8
  const { disconnect } = useDisconnect();
9
+ const [isOpen, setIsOpen] = useState(false);
10
+ const dropdownRef = useRef<HTMLDivElement>(null);
11
+
12
+ useOutsideClick(dropdownRef, () => setIsOpen(false));
7
13
 
8
14
  return (
9
- <div className="dropdown dropdown-end mr-2">
10
- <label tabIndex={0} className="btn btn-error btn-sm dropdown-toggle gap-1">
15
+ <div ref={dropdownRef} className={`dropdown dropdown-end mr-2 ${isOpen ? "dropdown-open" : ""}`}>
16
+ <label className="btn btn-error btn-sm dropdown-toggle gap-1" onClick={() => setIsOpen(prev => !prev)}>
11
17
  <span>Wrong network</span>
12
18
  <ChevronDownIcon className="h-6 w-4 ml-2 sm:ml-0" />
13
19
  </label>
@@ -1,26 +1,21 @@
1
- import { FlatCompat } from "@eslint/eslintrc";
1
+ import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
2
+ import nextTypescript from "eslint-config-next/typescript";
3
+ import prettierConfig from "eslint-config-prettier";
2
4
  import prettierPlugin from "eslint-plugin-prettier";
3
5
  import { defineConfig } from "eslint/config";
4
- import path from "node:path";
5
- import { fileURLToPath } from "node:url";
6
-
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
- const compat = new FlatCompat({
10
- baseDirectory: __dirname,
11
- });
12
6
 
13
7
  export default defineConfig([
8
+ ...nextCoreWebVitals,
9
+ ...nextTypescript,
10
+ prettierConfig,
14
11
  {
15
12
  plugins: {
16
13
  prettier: prettierPlugin,
17
14
  },
18
- extends: compat.extends("next/core-web-vitals", "next/typescript", "prettier"),
19
-
20
15
  rules: {
21
16
  "@typescript-eslint/no-explicit-any": "off",
22
17
  "@typescript-eslint/ban-ts-comment": "off",
23
-
18
+ "react-hooks/set-state-in-effect": "off",
24
19
  "prettier/prettier": [
25
20
  "warn",
26
21
  {
@@ -28,11 +23,6 @@ export default defineConfig([
28
23
  },
29
24
  ],
30
25
  },
31
- },
32
- {
33
- files: ["next-env.d.ts"],
34
- rules: {
35
- "@typescript-eslint/triple-slash-reference": "off",
36
- },
26
+ ignores: [".next", "next-env.d.ts"],
37
27
  },
38
28
  ]);
@@ -2,12 +2,14 @@ import { useEffect, useState } from "react";
2
2
  import { MutateOptions } from "@tanstack/react-query";
3
3
  import { Abi, ExtractAbiFunctionNames } from "abitype";
4
4
  import { Config, UseWriteContractParameters, useAccount, useConfig, useWriteContract } from "wagmi";
5
- import { WriteContractErrorType, WriteContractReturnType } from "wagmi/actions";
5
+ import { estimateFeesPerGas, WriteContractErrorType, WriteContractReturnType } from "wagmi/actions";
6
6
  import { WriteContractVariables } from "wagmi/query";
7
7
  import { useSelectedNetwork } from "~~/hooks/scaffold-eth";
8
8
  import { useDeployedContractInfo, useTransactor } from "~~/hooks/scaffold-eth";
9
9
  import { notification } from "~~/utils/scaffold-eth";
10
10
  import { AllowedChainIds } from "~~/utils/scaffold-stylus";
11
+ import scaffoldConfig from "~~/scaffold.config";
12
+ import { wagmiConfig } from "~~/services/web3/wagmiConfig";
11
13
  import {
12
14
  ContractAbi,
13
15
  ContractName,
@@ -34,6 +36,30 @@ type ScaffoldWriteContractReturnType<TContractName extends ContractName> = Omit<
34
36
  ) => void;
35
37
  };
36
38
 
39
+ /**
40
+ * Optionally bump maxFeePerGas on the write args to protect against base fee spikes.
41
+ * Since maxFeePerGas is a ceiling (not the actual fee), a generous multiplier is safe.
42
+ */
43
+ async function applyGasFeeMultiplier(
44
+ args: WriteContractVariables<Abi, string, any[], Config, number>,
45
+ chainId: number,
46
+ ): Promise<WriteContractVariables<Abi, string, any[], Config, number>> {
47
+ const multiplier = scaffoldConfig.gasFeeMultiplier;
48
+ if (!multiplier || multiplier <= 1) return args;
49
+ try {
50
+ const fees = await estimateFeesPerGas(wagmiConfig, { chainId: chainId as any });
51
+ const scaledMultiplier = BigInt(Math.round(multiplier * 100));
52
+ return {
53
+ ...args,
54
+ maxFeePerGas: (fees.maxFeePerGas * scaledMultiplier) / 100n,
55
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
56
+ } as WriteContractVariables<Abi, string, any[], Config, number>;
57
+ } catch (e) {
58
+ console.warn("⚡️ ~ useScaffoldWriteContract ~ estimateFeesPerGas failed:", e);
59
+ return args;
60
+ }
61
+ }
62
+
37
63
  export function useScaffoldWriteContract<TContractName extends ContractName>(
38
64
  config: UseScaffoldWriteConfig<TContractName>,
39
65
  ): ScaffoldWriteContractReturnType<TContractName>;
@@ -110,12 +136,14 @@ export function useScaffoldWriteContract<TContractName extends ContractName>(
110
136
  setIsMining(true);
111
137
  const { blockConfirmations, onBlockConfirmation, ...mutateOptions } = options || {};
112
138
 
113
- const writeContractObject = {
139
+ let writeContractObject = {
114
140
  abi: deployedContractData.abi as Abi,
115
141
  address: deployedContractData.address,
116
142
  ...variables,
117
143
  } as WriteContractVariables<Abi, string, any[], Config, number>;
118
144
 
145
+ writeContractObject = await applyGasFeeMultiplier(writeContractObject, selectedNetwork.id as AllowedChainIds);
146
+
119
147
  if (!finalConfig?.disableSimulate) {
120
148
  await simulateContractWriteAndNotifyError({
121
149
  wagmiConfig,
@@ -167,21 +195,24 @@ export function useScaffoldWriteContract<TContractName extends ContractName>(
167
195
  return;
168
196
  }
169
197
 
170
- wagmiContractWrite.writeContract(
171
- {
172
- abi: deployedContractData.abi as Abi,
173
- address: deployedContractData.address,
174
- ...variables,
175
- } as WriteContractVariables<Abi, string, any[], Config, number>,
176
- options as
177
- | MutateOptions<
178
- WriteContractReturnType,
179
- WriteContractErrorType,
180
- WriteContractVariables<Abi, string, any[], Config, number>,
181
- unknown
182
- >
183
- | undefined,
184
- );
198
+ const writeContractObject = {
199
+ abi: deployedContractData.abi as Abi,
200
+ address: deployedContractData.address,
201
+ ...variables,
202
+ } as WriteContractVariables<Abi, string, any[], Config, number>;
203
+ applyGasFeeMultiplier(writeContractObject, selectedNetwork.id as AllowedChainIds).then(buffered => {
204
+ wagmiContractWrite.writeContract(
205
+ buffered,
206
+ options as
207
+ | MutateOptions<
208
+ WriteContractReturnType,
209
+ WriteContractErrorType,
210
+ WriteContractVariables<Abi, string, any[], Config, number>,
211
+ unknown
212
+ >
213
+ | undefined,
214
+ );
215
+ });
185
216
  };
186
217
 
187
218
  return {
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- /// <reference path="./.next/types/routes.d.ts" />
3
+ import "./.next/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -6,14 +6,6 @@ const nextConfig = {
6
6
  typescript: {
7
7
  ignoreBuildErrors: process.env.NEXT_PUBLIC_IGNORE_BUILD_ERROR === "true",
8
8
  },
9
- eslint: {
10
- ignoreDuringBuilds: process.env.NEXT_PUBLIC_IGNORE_BUILD_ERROR === "true",
11
- },
12
- webpack: config => {
13
- config.resolve.fallback = { fs: false, net: false, tls: false };
14
- config.externals.push("pino-pretty", "lokijs", "encoding");
15
- return config;
16
- },
17
9
  };
18
10
 
19
11
  module.exports = nextConfig;
@@ -7,7 +7,7 @@
7
7
  "start": "next dev",
8
8
  "build": "next build",
9
9
  "serve": "next start",
10
- "lint": "next lint",
10
+ "lint": "eslint .",
11
11
  "format": "prettier --write . '!(node_modules|.next|contracts)/**/*'",
12
12
  "check-types": "tsc --noEmit --incremental",
13
13
  "vercel": "vercel",
@@ -23,44 +23,43 @@
23
23
  "has-ansi": "5.0.1"
24
24
  },
25
25
  "dependencies": {
26
- "@heroicons/react": "^2.1.5",
27
- "@rainbow-me/rainbowkit": "2.2.8",
26
+ "@heroicons/react": "^2.2.0",
27
+ "@rainbow-me/rainbowkit": "2.2.9",
28
28
  "@tanstack/react-query": "^5.59.15",
29
29
  "@uniswap/sdk-core": "^5.8.2",
30
30
  "@uniswap/v2-sdk": "^4.6.1",
31
- "blo": "^1.2.0",
32
- "burner-connector": "0.0.18",
33
- "daisyui": "4.5.0",
34
- "kubo-rpc-client": "^5.0.2",
35
- "next": "^15.2.3",
36
- "next-nprogress-bar": "^2.3.13",
37
- "next-themes": "^0.3.0",
38
- "qrcode.react": "^4.0.1",
39
- "react": "^19.0.0",
40
- "react-dom": "^19.0.0",
41
- "react-hot-toast": "^2.4.0",
42
- "usehooks-ts": "^3.1.0",
43
- "viem": "2.34.0",
44
- "wagmi": "2.16.4",
45
- "zustand": "^5.0.0"
31
+ "blo": "^2.0.0",
32
+ "burner-connector": "0.0.20",
33
+ "daisyui": "^5.5.19",
34
+ "kubo-rpc-client": "^6.1.0",
35
+ "next": "^16.2.4",
36
+ "next-nprogress-bar": "^2.4.7",
37
+ "next-themes": "^0.4.6",
38
+ "qrcode.react": "^4.2.0",
39
+ "react": "^19.2.5",
40
+ "react-dom": "^19.2.5",
41
+ "react-hot-toast": "^2.6.0",
42
+ "usehooks-ts": "^3.1.1",
43
+ "viem": "2.39.0",
44
+ "wagmi": "2.19.5",
45
+ "zustand": "^5.0.12"
46
46
  },
47
47
  "devDependencies": {
48
- "@tailwindcss/postcss": "latest",
48
+ "@tailwindcss/postcss": "^4.2.4",
49
49
  "@trivago/prettier-plugin-sort-imports": "^4.3.0",
50
- "@types/node": "^18.19.50",
51
- "@types/react": "^19.0.7",
50
+ "@types/node": "^18.19.130",
51
+ "@types/react": "^19.2.14",
52
52
  "abitype": "1.0.6",
53
- "autoprefixer": "^10.4.12",
54
53
  "bgipfs": "^0.0.12",
55
- "eslint": "^9.23.0",
56
- "eslint-config-next": "^15.2.3",
57
- "eslint-config-prettier": "^10.1.1",
58
- "eslint-plugin-prettier": "^5.2.4",
59
- "postcss": "^8.4.45",
60
- "prettier": "^3.5.3",
61
- "tailwindcss": "^3.3.3",
54
+ "eslint": "^9.39.0",
55
+ "eslint-config-next": "^16.2.4",
56
+ "eslint-config-prettier": "^10.1.8",
57
+ "eslint-plugin-prettier": "^5.5.5",
58
+ "postcss": "^8.5.10",
59
+ "prettier": "^3.8.3",
60
+ "tailwindcss": "^4.2.4",
62
61
  "type-fest": "^4.26.1",
63
62
  "typescript": "^5.8.2",
64
- "vercel": "^39.1.3"
63
+ "vercel": "~52.2.1"
65
64
  }
66
65
  }
@@ -1,6 +1,5 @@
1
1
  module.exports = {
2
2
  plugins: {
3
- tailwindcss: {},
4
- autoprefixer: {},
3
+ "@tailwindcss/postcss": {},
5
4
  },
6
5
  };
@@ -11,6 +11,13 @@ export type ScaffoldConfig = {
11
11
  walletConnectProjectId: string;
12
12
  onlyLocalBurnerWallet: boolean;
13
13
  walletAutoConnect: boolean;
14
+ /**
15
+ * Multiplier applied to maxFeePerGas for contract writes.
16
+ * Since maxFeePerGas is a CEILING (actual fee = baseFee + priorityFee capped at this),
17
+ * values >1 protect against block-to-block base fee spikes without increasing real cost.
18
+ * Set to 1 or omit to disable.
19
+ */
20
+ gasFeeMultiplier?: number;
14
21
  };
15
22
 
16
23
  export const DEFAULT_ALCHEMY_API_KEY = "oKxs-03sij-U_N0iOlrSsZFr29-IqbuF";
@@ -51,6 +58,9 @@ const scaffoldConfig = {
51
58
  * 2. If user is not connected to any wallet: On reload, connect to burner wallet if burnerWallet.enabled is true && burnerWallet.onlyLocal is false
52
59
  */
53
60
  walletAutoConnect: true,
61
+
62
+ // Gas fee multiplier for contract writes (ceiling, not actual cost — safe to be generous)
63
+ gasFeeMultiplier: 2,
54
64
  } as const satisfies ScaffoldConfig;
55
65
 
56
66
  export default scaffoldConfig;
@@ -1,10 +1,134 @@
1
- @import "tailwindcss/base";
2
- @import "tailwindcss/components";
3
- @import "tailwindcss/utilities";
1
+ @import "tailwindcss";
2
+
3
+ @source "../app";
4
+ @source "../components";
5
+ @source "../utils";
6
+
7
+ @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
8
+
9
+ @theme {
10
+ --font-sans: var(--font-inter), system-ui, sans-serif;
11
+ --shadow-center: 0 0 12px -2px rgb(0 0 0 / 0.05);
12
+ --animate-pulse-fast: pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;
13
+ }
14
+
15
+ /* Orbitron utility — defined as a standalone @utility (not @theme) to avoid a
16
+ self-referential --font-orbitron, since next/font already emits that var. */
17
+ @utility font-orbitron {
18
+ font-family: var(--font-orbitron), sans-serif;
19
+ }
20
+
21
+ @plugin "daisyui" {
22
+ themes:
23
+ light,
24
+ dark --prefersdark;
25
+ }
26
+
27
+ @plugin "daisyui/theme" {
28
+ name: "light";
29
+
30
+ --color-primary: #93bbfb;
31
+ --color-primary-content: #212638;
32
+ --color-secondary: #dae8ff;
33
+ --color-secondary-content: #212638;
34
+ --color-accent: #93bbfb;
35
+ --color-accent-content: #212638;
36
+ --color-neutral: #212638;
37
+ --color-neutral-content: #ffffff;
38
+ --color-base-100: #ffffff;
39
+ --color-base-200: #f4f8ff;
40
+ --color-base-300: #dae8ff;
41
+ --color-base-content: #212638;
42
+ --color-info: #93bbfb;
43
+ --color-success: #34eeb6;
44
+ --color-warning: #ffcf72;
45
+ --color-error: #ff8863;
46
+
47
+ /* ported from --rounded-btn: 9999rem (daisyui v4) -> v5 radius family */
48
+ --radius-field: 9999rem;
49
+ --radius-box: 1rem;
50
+
51
+ /* ported from .tooltip { --tooltip-tail: 6px } */
52
+ --tt-tailw: 6px;
53
+ }
54
+
55
+ @plugin "daisyui/theme" {
56
+ name: "dark";
57
+
58
+ --color-primary: #212638;
59
+ --color-primary-content: #f9fbff;
60
+ --color-secondary: #323f61;
61
+ --color-secondary-content: #f9fbff;
62
+ --color-accent: #4969a6;
63
+ --color-accent-content: #f9fbff;
64
+ --color-neutral: #f9fbff;
65
+ --color-neutral-content: #385183;
66
+ --color-base-100: #000000;
67
+ --color-base-200: #2a3655;
68
+ --color-base-300: #212638;
69
+ --color-base-content: #f9fbff;
70
+ --color-info: #385183;
71
+ --color-success: #34eeb6;
72
+ --color-warning: #ffcf72;
73
+ --color-error: #ff8863;
74
+
75
+ /* ported from --rounded-btn: 9999rem (daisyui v4) -> v5 radius family */
76
+ --radius-field: 9999rem;
77
+ --radius-box: 1rem;
78
+
79
+ /* ported from .tooltip { --tooltip-tail: 6px; --tooltip-color: oklch(var(--p)) } */
80
+ --tt-tailw: 6px;
81
+ --tt-bg: var(--color-primary);
82
+ }
83
+
84
+ /* Per-theme custom variables ported verbatim from tailwind.config.js daisyui themes.
85
+ Defined with explicit [data-theme] selectors (not inside the daisyui theme blocks,
86
+ which only accept daisyui's own tokens). */
87
+ [data-theme="light"] {
88
+ --surface: rgba(0, 0, 0, 0.04);
89
+ --bg-border: #ffffff;
90
+ --round-color: #000;
91
+ --gradient-start: #3283eb;
92
+ --gradient-end: #e3066e;
93
+ --border-color: rgba(0, 0, 0, 0.07);
94
+ }
95
+
96
+ [data-theme="dark"] {
97
+ --surface: rgba(255, 255, 255, 0.04);
98
+ --bg-border: #000000;
99
+ --round-color: #e3066e;
100
+ --gradient-start: #3283eb;
101
+ --gradient-end: #e3066e;
102
+ --border-color: rgba(255, 255, 255, 0.2);
103
+ }
104
+
105
+ /*
106
+ Tailwind CSS v4 changed the default border color to `currentColor`.
107
+ Restore the v3 default (gray-200) so existing borders look identical.
108
+ */
109
+ @layer base {
110
+ *,
111
+ ::after,
112
+ ::before,
113
+ ::backdrop,
114
+ ::file-selector-button {
115
+ border-color: var(--color-gray-200, currentColor);
116
+ }
117
+ }
4
118
 
5
119
  :root,
6
120
  [data-theme] {
7
- background: oklch(var(--b1));
121
+ background: var(--color-base-100);
122
+ }
123
+
124
+ /* ported from .link / .link:hover (defined per-theme in tailwind.config.js;
125
+ identical across light & dark, so defined once globally) */
126
+ .link {
127
+ text-underline-offset: 2px;
128
+ }
129
+
130
+ .link:hover {
131
+ opacity: 80%;
8
132
  }
9
133
 
10
134
  body {
@@ -9,10 +9,10 @@
9
9
  "noEmit": true,
10
10
  "esModuleInterop": true,
11
11
  "module": "esnext",
12
- "moduleResolution": "node",
12
+ "moduleResolution": "bundler",
13
13
  "resolveJsonModule": true,
14
14
  "isolatedModules": true,
15
- "jsx": "preserve",
15
+ "jsx": "react-jsx",
16
16
  "incremental": true,
17
17
  "paths": {
18
18
  "~~/*": ["./*"]
@@ -23,6 +23,6 @@
23
23
  }
24
24
  ]
25
25
  },
26
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
27
27
  "exclude": ["node_modules"]
28
28
  }
@@ -5,6 +5,38 @@ import {
5
5
  isContractHasConstructor,
6
6
  } from "./contract";
7
7
  import { getRpcUrlFromChain } from "./network";
8
+ import { createPublicClient, http, formatUnits } from "viem";
9
+
10
+ const DEFAULT_GAS_FEE_MULTIPLIER = 3;
11
+ const MIN_FEE_GWEI = 0.1;
12
+
13
+ function getGasFeeMultiplier(): number {
14
+ const envVal = process.env["DEPLOY_GAS_FEE_MULTIPLIER"];
15
+ if (envVal) {
16
+ const parsed = parseFloat(envVal);
17
+ if (!isNaN(parsed) && parsed > 0) {
18
+ return parsed;
19
+ }
20
+ }
21
+ return DEFAULT_GAS_FEE_MULTIPLIER;
22
+ }
23
+
24
+ async function getBufferedMaxFeeGwei(rpcUrl: string): Promise<number> {
25
+ try {
26
+ const publicClient = createPublicClient({
27
+ transport: http(rpcUrl),
28
+ });
29
+ const block = await publicClient.getBlock({ blockTag: "latest" });
30
+ if (block.baseFeePerGas === null) {
31
+ return MIN_FEE_GWEI;
32
+ }
33
+ const baseFeeGwei = Number(formatUnits(block.baseFeePerGas, 9));
34
+ const buffered = baseFeeGwei * getGasFeeMultiplier();
35
+ return Math.max(buffered, MIN_FEE_GWEI);
36
+ } catch {
37
+ return MIN_FEE_GWEI;
38
+ }
39
+ }
8
40
 
9
41
  export async function buildDeployCommand(
10
42
  config: DeploymentConfig,
@@ -18,6 +50,12 @@ export async function buildDeployCommand(
18
50
 
19
51
  if (deployOptions.maxFee) {
20
52
  baseCommand += ` --max-fee-per-gas-gwei=${deployOptions.maxFee}`;
53
+ } else {
54
+ // maxFeePerGas is a CEILING (actual charge = base fee), so over-provisioning is safe and free.
55
+ // Cargo stylus without this flag uses a tight estimate that the base fee can creep past.
56
+ const rpcUrl = getRpcUrlFromChain(config.chain);
57
+ const maxFeeGwei = await getBufferedMaxFeeGwei(rpcUrl);
58
+ baseCommand += ` --max-fee-per-gas-gwei=${maxFeeGwei}`;
21
59
  }
22
60
 
23
61
  if (!deployOptions.verify) {