Skip to main content

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.ethereum being missing at load.
  • null from 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.isCrackPay is true.
  • window.ethereum is 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.

crackpay-config.ts
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() },
});
}
useAutoConnect.ts
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_sign and eth_signTypedData* return error 4200. Do not build login, permits or off-chain orders on signatures. The account address is the user's identity.
  • msg.sender is the user's account. tx.origin is a bundler, so a contract that requires tx.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​

  1. Await the provider; never read window.ethereum at load.
  2. Never show a connect button.
  3. Show a loading state while connecting.
  4. Show a clear message when opened outside CrackPay.
  5. Identify users by account, greet them by handle.