Skip to main content

Interacting with smart contracts

Reading contract state​

Reads go straight to Arc and need no wallet:

const total = await publicClient.readContract({
address: CONTRACT,
abi,
functionName: "totalSupply",
});

Batch several reads with multicall:

const results = await publicClient.multicall({
contracts: [
{ address: CONTRACT, abi, functionName: "price" },
{ address: CONTRACT, abi, functionName: "stock" },
],
});

Writing to contracts​

const hash = await walletClient.writeContract({
account,
chain: walletClient.chain,
address: CONTRACT,
abi,
functionName: "pay",
args: [orderId],
value: parseEther("1.50"), // native USDC, 18 decimals
});

The user sees a confirmation in CrackPay and approves with their passkey. The promise resolves once the transaction is final; a revert rejects it. Gas is sponsored, so pass no gas options.

Design payments for one confirmation​

Prefer a payable function that takes native USDC:

function pay(bytes32 orderId) external payable {
// msg.value is USDC with 18 decimals
}

One transaction, one confirmation, and CrackPay shows the exact amount leaving the user's balance.

Taking tokens​

For EURC, or anything built on transferFrom, send approve then your call, and await each. Approve the exact amount, never an unlimited one. A listed app may only ask a token to approve one of its own listed contracts. See Send a transaction.

Contract events​

Your events are in the receipt's logs:

import { parseEventLogs } from "viem";

const receipt = await publicClient.getTransactionReceipt({ hash });
const events = parseEventLogs({ abi, logs: receipt.logs, eventName: "Paid" });

The receipt's from is a bundler and its to is the ERC-4337 EntryPoint, not your contract. That is how account abstraction works.

Contracts that accept CrackPay users​

Your contract's caller is a smart contract account. It must:

  • Not require tx.origin == msg.sender.
  • Not reject callers that have code (extcodesize, isContract).
  • Not depend on an ECDSA signature from the user. Use msg.sender.
  • Treat msg.value as 18-decimal USDC and never mix it with a 6-decimal balanceOf in arithmetic.
  • Expect sends to the zero address to revert on Arc.

Contract address management​

Keep addresses in one config module, per network:

export const contracts = {
5042: { shop: "0x…" }, // Arc mainnet
5042002: { shop: "0x…" }, // Arc Testnet
} as const;

For listing, every contract your app sends transactions to must be named in your listing and verified on the Arc explorer (mainnet, testnet) for the network you list on.

Gas estimation​

You do not need it. CrackPay sponsors gas and ignores gas fields.