Wallet connection
A Mini App is connected from the moment it loads. Never show a "Connect wallet" button, and never ask the user to sign a message to prove who they are.
The provider arrives asynchronously
CrackPay shows your app in a frame. Your app gets the wallet through a short
handshake with the CrackPay page, a few milliseconds after it starts. So there is
no window.ethereum at the moment your page loads.
- Always await the SDK before using the wallet.
- Never decide "not in CrackPay" from
window.ethereumbeing missing at load. nullfrom the SDK means "not inside CrackPay". It is not an error.
Get the provider
npm install @crackpay/miniapp-sdk
import { getCrackPayProvider } from "@crackpay/miniapp-sdk";
const provider = await getCrackPayProvider();
if (!provider) {
showMessage("Open this app from CrackPay to use it.");
}
It resolves within three seconds at most, and immediately with null when the
page is not in a frame. It is safe to call from several places; the connection is
made once.
Once it resolves with a provider:
provider.isCrackPayistrue.window.ethereumis the same provider, unless something else already set it.- It is announced through EIP-6963 as
CrackPay(rdns: "app.crackpay").
Auto-connect
const [account] = await provider.request({ method: "eth_requestAccounts" });
This never prompts. eth_accounts returns the same account, and the account does
not change during a session.
With viem
import { connectCrackPay } from "@crackpay/miniapp-sdk/viem";
const crackpay = await connectCrackPay();
if (crackpay) {
const { account, handle, walletClient, publicClient, provider } = crackpay;
}
walletClient sends through CrackPay; publicClient reads from Arc directly.
With React
import { useCrackPay } from "@crackpay/miniapp-sdk/react";
function App() {
const crackpay = useCrackPay();
if (crackpay.status === "connecting") return <Loading />;
if (crackpay.status === "unavailable") return <p>Open this app from CrackPay.</p>;
if (crackpay.status === "error") return <p>Something went wrong.</p>;
return <Main account={crackpay.account} handle={crackpay.handle} provider={crackpay.provider} />;
}
If you pass options, pass a stable object (module level or memoised). A new object on every render reconnects on every render.
With wagmi
Create the wagmi config after the provider resolves, with CrackPay as the only connector, and connect once on mount.
import { getCrackPayProvider } from "@crackpay/miniapp-sdk";
import { createConfig, http, injected } from "wagmi";
import { arc, arcTestnet } from "wagmi/chains";
export async function createCrackPayConfig() {
const provider = await getCrackPayProvider();
if (!provider) return null;
// CrackPay runs on one Arc network; configure wagmi for that one.
const chainId = Number.parseInt((await provider.request({ method: "eth_chainId" })) as string, 16);
const chain = chainId === arcTestnet.id ? arcTestnet : arc;
return createConfig({
chains: [chain],
connectors: [
injected({
target: { id: "crackpay", name: "CrackPay", provider },
// CrackPay has no permissions API; skip wagmi's disconnect shim.
shimDisconnect: false,
}),
],
transports: { [chain.id]: http() },
});
}
import { useEffect, useRef } from "react";
import { useConnect, useConnectors } from "wagmi";
export function useAutoConnect() {
const connectors = useConnectors();
const { connect } = useConnect();
const attempted = useRef(false);
useEffect(() => {
const connector = connectors[0];
if (attempted.current || !connector) return;
attempted.current = true;
connect({ connector });
}, [connectors, connect]);
}
Render your app only once createCrackPayConfig() has resolved, and show the
"open in CrackPay" message when it returns null.
Avoid useSignMessage, useSignTypedData and useSendCalls; see below.
Without a bundler
<script src="https://www.crackpay.xyz/miniapp-sdk.js"></script>
<script type="module">
const provider = await window.crackpay.ready; // the same provider, or null
</script>
Apps that also run outside CrackPay
const crackpay = await connectCrackPay();
const wallet = crackpay ?? (await connectWithYourUsualWalletFlow());
isFramed() tells you synchronously whether the page is in a frame at all. Not
framed means certainly not in CrackPay.
The account is a smart account
A CrackPay account is a passkey-controlled smart contract account (ERC-4337), not a private-key wallet.
- No message signing.
personal_sign,eth_signandeth_signTypedData*return error4200. Do not build login, permits or off-chain orders on signatures. The account address is the user's identity. msg.senderis the user's account.tx.originis a bundler, so a contract that requirestx.origin == msg.sender, or rejects callers that have code, rejects CrackPay users.- A new account has no code until its first transaction.
Testing against your own CrackPay
The SDK trusts every CrackPay host (CRACKPAY_ORIGINS). To test against a CrackPay you run
yourself:
getCrackPayProvider({ hostOrigins: ["http://localhost:3000"] });
Never add a host you do not control: whichever page is trusted acts as the wallet.
Best practices
- Await the provider; never read
window.ethereumat load. - Never show a connect button.
- Show a loading state while connecting.
- Show a clear message when opened outside CrackPay.
- Identify users by account, greet them by handle.