重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
controller-react by cartridge-gg/docs
npx skills add https://github.com/cartridge-gg/docs --skill controller-react使用 starknet-react 将 Cartridge Controller 与 React 集成。
pnpm add @cartridge/connector @cartridge/controller @starknet-react/core @starknet-react/chains starknet
pnpm add -D vite-plugin-mkcert
重要提示:在 React 组件外部创建连接器。
import { sepolia, mainnet, Chain } from "@starknet-react/chains";
import { StarknetConfig, jsonRpcProvider, cartridge } from "@starknet-react/core";
import { ControllerConnector } from "@cartridge/connector";
import { SessionPolicies } from "@cartridge/controller";
// 定义合约地址
const ETH_TOKEN_ADDRESS =
"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
const policies: SessionPolicies = {
contracts: {
[ETH_TOKEN_ADDRESS]: {
methods: [
{
name: "approve",
entrypoint: "approve",
spender: "0x1234567890abcdef1234567890abcdef12345678",
amount: "0xffffffffffffffffffffffffffffffff",
description: "Approve spending of tokens",
},
{ name: "transfer", entrypoint: "transfer" },
],
},
},
};
// 在组件外部创建
const connector = new ControllerConnector({ policies });
// 用于本地开发的 Katana 链定义
// 需要 katana.toml 包含 [cartridge] paymaster = true
const KATANA_CHAIN_ID = "0x4b4154414e41"; // "KATANA" 的十六进制 ASCII 编码
const KATANA_URL = "http://localhost:5050";
const katana: Chain = {
id: BigInt(KATANA_CHAIN_ID),
name: "Katana",
network: "katana",
testnet: true,
nativeCurrency: {
address: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
name: "Stark",
symbol: "STRK",
decimals: 18,
},
rpcUrls: {
default: { http: [KATANA_URL] },
public: { http: [KATANA_URL] },
},
// Controller 账户在 Katana 上自动部署所需
paymasterRpcUrls: {
avnu: { http: [KATANA_URL] },
},
};
const provider = jsonRpcProvider({
rpc: (chain: Chain) => {
switch (chain) {
case mainnet:
return { nodeUrl: "https://api.cartridge.gg/x/starknet/mainnet" };
case sepolia:
return { nodeUrl: "https://api.cartridge.gg/x/starknet/sepolia" };
default:
return { nodeUrl: KATANA_URL };
}
},
});
export function StarknetProvider({ children }: { children: React.ReactNode }) {
return (
<StarknetConfig
autoConnect
defaultChainId={katana.id}
chains={[katana, mainnet, sepolia]}
provider={provider}
connectors={[connector]}
explorer={cartridge}
>
{children}
</StarknetConfig>
);
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
import { useAccount, useConnect, useDisconnect } from "@starknet-react/core";
import { ControllerConnector } from "@cartridge/connector";
import { useEffect, useState } from "react";
export function ConnectWallet() {
const { connect, connectors } = useConnect();
const { disconnect } = useDisconnect();
const { address } = useAccount();
const controller = connectors[0] as ControllerConnector;
const [username, setUsername] = useState<string>();
useEffect(() => {
if (!address) return;
controller.username()?.then(setUsername);
}, [address, controller]);
if (address) {
return (
<div>
<p>账户: {address}</p>
{username && <p>用户名: {username}</p>}
<button onClick={() => disconnect()}>断开连接</button>
</div>
);
}
return (
<div>
<button onClick={() => connect({ connector: controller })}>
连接
</button>
</div>
);
}
const handleSpecificAuth = async (signupOptions: string[]) => {
try {
// 为特定认证选项直接连接控制器
await controller.connect({ signupOptions });
// 手动触发 starknet-react 状态更新
connect({ connector: controller });
} catch (error) {
console.error("连接失败:", error);
}
};
<button onClick={() => handleSpecificAuth(["phantom-evm"])}>
使用 Phantom 继续
</button>
<button onClick={() => handleSpecificAuth(["google"])}>
使用 Google 继续
</button>
import { useAccount, useExplorer } from "@starknet-react/core";
import { useCallback, useState } from "react";
const ETH = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
export function TransferEth() {
const [submitted, setSubmitted] = useState<boolean>(false);
const { account } = useAccount();
const explorer = useExplorer();
const [txnHash, setTxnHash] = useState<string>();
const execute = useCallback(
async (amount: string) => {
if (!account) return;
setSubmitted(true);
setTxnHash(undefined);
try {
const result = await account.execute([
{
contractAddress: ETH,
entrypoint: "approve",
calldata: [account.address, amount, "0x0"],
},
{
contractAddress: ETH,
entrypoint: "transfer",
calldata: [account.address, amount, "0x0"],
},
]);
setTxnHash(result.transaction_hash);
} catch (e) {
console.error(e);
} finally {
setSubmitted(false);
}
},
[account]
);
if (!account) return null;
return (
<div>
<button onClick={() => execute("0x1C6BF52634000")} disabled={submitted}>
转账 0.005 ETH
</button>
{txnHash && (
<a href={explorer.transaction(txnHash)} target="_blank" rel="noreferrer">
查看交易
</a>
)}
</div>
);
}
// 等待交易确认
const response = await controller.externalWaitForTransaction(
"metamask",
txHash,
30000 // 超时时间(毫秒)
);
if (response.success) {
console.log("收据:", response.result);
} else {
console.error("错误:", response.error);
}
// 切换链
const success = await controller.externalSwitchChain("metamask", chainId);
支持的钱包类型:metamask、rabby、phantom、argent、walletconnect。
为本地开发启用 HTTPS:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import mkcert from "vite-plugin-mkcert";
export default defineConfig({
plugins: [react(), mkcert()],
});
# 使用本地 API 进行本地开发
pnpm dev
# 使用生产 API 进行测试(混合模式)
pnpm dev:live
dev:live 模式在本地运行密钥链,同时连接到生产 API。
import { StarknetProvider } from "./StarknetProvider";
import { ConnectWallet } from "./ConnectWallet";
import { TransferEth } from "./TransferEth";
function App() {
return (
<StarknetProvider>
<ConnectWallet />
<TransferEth />
</StarknetProvider>
);
}
每周安装量
62
代码仓库
GitHub 星标数
4
首次出现
2026年2月4日
安全审计
安装于
opencode60
codex59
github-copilot58
kimi-cli57
gemini-cli57
amp57
Integrate Cartridge Controller with React using starknet-react.
pnpm add @cartridge/connector @cartridge/controller @starknet-react/core @starknet-react/chains starknet
pnpm add -D vite-plugin-mkcert
Important : Create connector outside React components.
import { sepolia, mainnet, Chain } from "@starknet-react/chains";
import { StarknetConfig, jsonRpcProvider, cartridge } from "@starknet-react/core";
import { ControllerConnector } from "@cartridge/connector";
import { SessionPolicies } from "@cartridge/controller";
// Define contract addresses
const ETH_TOKEN_ADDRESS =
"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
const policies: SessionPolicies = {
contracts: {
[ETH_TOKEN_ADDRESS]: {
methods: [
{
name: "approve",
entrypoint: "approve",
spender: "0x1234567890abcdef1234567890abcdef12345678",
amount: "0xffffffffffffffffffffffffffffffff",
description: "Approve spending of tokens",
},
{ name: "transfer", entrypoint: "transfer" },
],
},
},
};
// Create OUTSIDE component
const connector = new ControllerConnector({ policies });
// Katana chain definition for local development
// Requires katana.toml with [cartridge] paymaster = true
const KATANA_CHAIN_ID = "0x4b4154414e41"; // "KATANA" hex-encoded ASCII
const KATANA_URL = "http://localhost:5050";
const katana: Chain = {
id: BigInt(KATANA_CHAIN_ID),
name: "Katana",
network: "katana",
testnet: true,
nativeCurrency: {
address: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
name: "Stark",
symbol: "STRK",
decimals: 18,
},
rpcUrls: {
default: { http: [KATANA_URL] },
public: { http: [KATANA_URL] },
},
// Required for Controller account auto-deployment on Katana
paymasterRpcUrls: {
avnu: { http: [KATANA_URL] },
},
};
const provider = jsonRpcProvider({
rpc: (chain: Chain) => {
switch (chain) {
case mainnet:
return { nodeUrl: "https://api.cartridge.gg/x/starknet/mainnet" };
case sepolia:
return { nodeUrl: "https://api.cartridge.gg/x/starknet/sepolia" };
default:
return { nodeUrl: KATANA_URL };
}
},
});
export function StarknetProvider({ children }: { children: React.ReactNode }) {
return (
<StarknetConfig
autoConnect
defaultChainId={katana.id}
chains={[katana, mainnet, sepolia]}
provider={provider}
connectors={[connector]}
explorer={cartridge}
>
{children}
</StarknetConfig>
);
}
import { useAccount, useConnect, useDisconnect } from "@starknet-react/core";
import { ControllerConnector } from "@cartridge/connector";
import { useEffect, useState } from "react";
export function ConnectWallet() {
const { connect, connectors } = useConnect();
const { disconnect } = useDisconnect();
const { address } = useAccount();
const controller = connectors[0] as ControllerConnector;
const [username, setUsername] = useState<string>();
useEffect(() => {
if (!address) return;
controller.username()?.then(setUsername);
}, [address, controller]);
if (address) {
return (
<div>
<p>Account: {address}</p>
{username && <p>Username: {username}</p>}
<button onClick={() => disconnect()}>Disconnect</button>
</div>
);
}
return (
<div>
<button onClick={() => connect({ connector: controller })}>
Connect
</button>
</div>
);
}
const handleSpecificAuth = async (signupOptions: string[]) => {
try {
// Direct controller connection for specific auth options
await controller.connect({ signupOptions });
// Manually trigger starknet-react state update
connect({ connector: controller });
} catch (error) {
console.error("Connection failed:", error);
}
};
<button onClick={() => handleSpecificAuth(["phantom-evm"])}>
Continue with Phantom
</button>
<button onClick={() => handleSpecificAuth(["google"])}>
Continue with Google
</button>
import { useAccount, useExplorer } from "@starknet-react/core";
import { useCallback, useState } from "react";
const ETH = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
export function TransferEth() {
const [submitted, setSubmitted] = useState<boolean>(false);
const { account } = useAccount();
const explorer = useExplorer();
const [txnHash, setTxnHash] = useState<string>();
const execute = useCallback(
async (amount: string) => {
if (!account) return;
setSubmitted(true);
setTxnHash(undefined);
try {
const result = await account.execute([
{
contractAddress: ETH,
entrypoint: "approve",
calldata: [account.address, amount, "0x0"],
},
{
contractAddress: ETH,
entrypoint: "transfer",
calldata: [account.address, amount, "0x0"],
},
]);
setTxnHash(result.transaction_hash);
} catch (e) {
console.error(e);
} finally {
setSubmitted(false);
}
},
[account]
);
if (!account) return null;
return (
<div>
<button onClick={() => execute("0x1C6BF52634000")} disabled={submitted}>
Transfer 0.005 ETH
</button>
{txnHash && (
<a href={explorer.transaction(txnHash)} target="_blank" rel="noreferrer">
View Transaction
</a>
)}
</div>
);
}
// Wait for transaction confirmation
const response = await controller.externalWaitForTransaction(
"metamask",
txHash,
30000 // timeout ms
);
if (response.success) {
console.log("Receipt:", response.result);
} else {
console.error("Error:", response.error);
}
// Switch chains
const success = await controller.externalSwitchChain("metamask", chainId);
Supported wallet types: metamask, rabby, phantom, argent, walletconnect.
Enable HTTPS for local development:
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import mkcert from "vite-plugin-mkcert";
export default defineConfig({
plugins: [react(), mkcert()],
});
# Local development with local APIs
pnpm dev
# Testing with production APIs (hybrid mode)
pnpm dev:live
The dev:live mode runs keychain locally while connecting to production APIs.
import { StarknetProvider } from "./StarknetProvider";
import { ConnectWallet } from "./ConnectWallet";
import { TransferEth } from "./TransferEth";
function App() {
return (
<StarknetProvider>
<ConnectWallet />
<TransferEth />
</StarknetProvider>
);
}
Weekly Installs
62
Repository
GitHub Stars
4
First Seen
Feb 4, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
opencode60
codex59
github-copilot58
kimi-cli57
gemini-cli57
amp57
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
123,700 周安装
docs-sync:OpenAI Agents Python 文档同步工具,自动检测代码与文档差异
91 周安装
gcloud CLI 技能:Google Cloud Platform 命令行工具安装、配置与最佳实践指南
92 周安装
724-office-ai-agent:7x24小时运行的生产级AI代理系统,纯Python无框架依赖
57 周安装
RAG Agent Builder:构建检索增强生成AI应用,集成外部知识源提升LLM准确性
92 周安装
Biome开发指南:Rust静态分析工具最佳实践与常见陷阱解析
95 周安装
PHP 8.2+ 专家指南:现代PHP开发、性能优化与企业级应用架构
91 周安装