Send a transaction
Basic payment
import { parseEther } from "viem";
const hash = await walletClient.sendTransaction({
account,
chain: walletClient.chain,
to: recipient,
value: parseEther("1.50"), // native USDC: 18 decimals
});
Calling your contract
const hash = await walletClient.writeContract({
account,
chain: walletClient.chain,
address: CONTRACT,
abi,
functionName: "pay",
args: [orderId],
value: parseEther("1.50"),
});
What happens
- CrackPay checks the call against your app's listing. If it is not allowed, the
call rejects with
4100and the user sees nothing. - CrackPay shows the user the amount and where it goes.
- The user approves with their passkey.
- CrackPay sends it with gas sponsored and waits for it to land.
- The promise resolves with the transaction hash. The payment is final.
Request fields
| Field | Behaviour |
|---|---|
to | Required. No contract creation. The zero address is refused. |
data | Optional |
value | Optional. Native USDC, 18 decimals. |
gas, gasPrice, maxFeePerGas, maxPriorityFeePerGas, nonce | Ignored |
One call per transaction. There is no batching.
Tokens
For EURC, or any contract that pulls tokens with transferFrom, send two
transactions and await each:
import { tokens } from "@crackpay/miniapp-sdk";
import { erc20Abi, parseUnits } from "viem";
const amount = parseUnits("5", tokens.EURC.decimals);
await walletClient.writeContract({
account,
chain: walletClient.chain,
address: tokens.EURC.address,
abi: erc20Abi,
functionName: "approve",
args: [CONTRACT, amount], // the exact amount, never unlimited
});
await walletClient.writeContract({
account,
chain: walletClient.chain,
address: CONTRACT,
abi,
functionName: "payWithToken",
args: [tokens.EURC.address, amount],
});
What a listed app may call
| Call | Listed app | Test app |
|---|---|---|
| A contract in its listing | Allowed | Allowed |
| Any other contract or address | Refused, 4100 | Allowed |
approve on a listed token, spender in the listing | Allowed | Allowed |
approve to any other spender | Refused, 4100 | Allowed |
transfer or transferFrom on a token | Refused, 4100 | Allowed |
Move tokens through your own contract, never with a direct token transfer.
Gas
CrackPay sponsors every transaction. Do not estimate gas, set fee fields, or keep a gas reserve in the user's balance.
:::note While Circle's sponsorship is paused When Circle Gas Station is unavailable, CrackPay falls back to the user's account paying its own fee, about a cent in USDC, and shows it in the confirmation. Your app still sets nothing. A payment of the user's entire balance can then fail for lack of the fee; CrackPay checks this before the passkey prompt. :::
Errors
See Error codes. Treat 4001 as a cancel.