Clear Macro Forwarder
API reference for the Clear Macro Forwarder (ClearMacroForwarderV1WithPermit2)
The Clear Macro Forwarder executes user-defined "macro" contracts as an atomic batch of protocol operations, using clear (human-readable) EIP-712 signing. Unlike the blind forwarder, the signer approves a structured, displayable payload, and the signed macro can be relayed by a third party — runMacro takes the signer and their signature as explicit arguments, so the relayer's wallet is not the operator. It also supports Permit2 for token approvals (runPermit2AndMacro).
The on-chain contract is ClearMacroForwarderV1WithPermit2; its address is sourced from the metadata contractsV1.clearMacroForwarderV1WithPermit2 key and is the same deterministic address across all supported networks.
Contract Addresses
import { clearMacroForwarderAddress } from "@sfpro/sdk/abi"
import { mainnet } from "viem/chains"
// Get Clear Macro Forwarder address for a specific chain
const forwarderAddress = clearMacroForwarderAddress[mainnet.id]Read Functions
getNonce
Get the current nonce for a signer (and nonce key), used to build a replay-protected EIP-712 payload.
import { useReadClearMacroForwarder } from "@sfpro/sdk/hook"
const { data: nonce } = useReadClearMacroForwarder({
functionName: "getNonce",
args: ["0x...", 0n] // Signer address, nonce key
}) import { clearMacroForwarderAbi, clearMacroForwarderAddress } from "@sfpro/sdk/abi"
import { createPublicClient, http } from "viem"
import { mainnet } from "viem/chains"
const client = createPublicClient({
chain: mainnet,
transport: http()
})
const nonce = await client.readContract({
address: clearMacroForwarderAddress[mainnet.id],
abi: clearMacroForwarderAbi,
functionName: "getNonce",
args: [
"0x...", // Signer address
0n // Nonce key
]
}) import { readClearMacroForwarder } from "@sfpro/sdk/action"
import { createConfig } from "@wagmi/core"
import { http } from "viem"
import { mainnet } from "viem/chains"
const config = createConfig({
chains: [mainnet],
transports: { [mainnet.id]: http() }
})
const nonce = await readClearMacroForwarder(config, {
chainId: mainnet.id,
functionName: "getNonce",
args: ["0x...", 0n] // Signer address, nonce key
}) getDigest
Get the EIP-712 digest for a macro and its encoded payload. The signer signs this digest to authorize the macro.
import { useReadClearMacroForwarder } from "@sfpro/sdk/hook"
const { data: digest } = useReadClearMacroForwarder({
functionName: "getDigest",
args: ["0x...", "0x..."] // Macro contract address, encoded payload
}) import { clearMacroForwarderAbi, clearMacroForwarderAddress } from "@sfpro/sdk/abi"
import { createPublicClient, http } from "viem"
import { mainnet } from "viem/chains"
const client = createPublicClient({
chain: mainnet,
transport: http()
})
const digest = await client.readContract({
address: clearMacroForwarderAddress[mainnet.id],
abi: clearMacroForwarderAbi,
functionName: "getDigest",
args: [
"0x...", // Macro contract address
"0x..." // Encoded payload
]
}) import { readClearMacroForwarder } from "@sfpro/sdk/action"
import { createConfig } from "@wagmi/core"
import { http } from "viem"
import { mainnet } from "viem/chains"
const config = createConfig({
chains: [mainnet],
transports: { [mainnet.id]: http() }
})
const digest = await readClearMacroForwarder(config, {
chainId: mainnet.id,
functionName: "getDigest",
args: ["0x...", "0x..."] // Macro contract address, encoded payload
}) Write Functions
runMacro
Run a clear-signed macro on behalf of signer. The macro contract builds the batch operations from encodedPayload, the forwarder verifies the signer's EIP-712 signature, and forwards the operations to the Host atomically. The caller (relayer) does not need to be the signer.
import { useWriteClearMacroForwarder } from "@sfpro/sdk/hook"
const { writeContract: runMacro } = useWriteClearMacroForwarder({
functionName: "runMacro",
args: ["0x...", "0x...", "0x...", "0x..."] // Macro, encoded payload, signer, signature
}) import { clearMacroForwarderAbi, clearMacroForwarderAddress } from "@sfpro/sdk/abi"
import { createWalletClient, http } from "viem"
import { mainnet } from "viem/chains"
import { privateKeyToAccount } from "viem/accounts"
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http()
})
const hash = await walletClient.writeContract({
address: clearMacroForwarderAddress[mainnet.id],
abi: clearMacroForwarderAbi,
functionName: "runMacro",
args: [
"0x...", // Macro contract address
"0x...", // Encoded payload
"0x...", // Signer address
"0x..." // EIP-712 signature
]
}) import { writeClearMacroForwarder } from "@sfpro/sdk/action"
import { createConfig } from "@wagmi/core"
import { createWalletClient, http } from "viem"
import { mainnet } from "viem/chains"
import { privateKeyToAccount } from "viem/accounts"
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const config = createConfig({
chains: [mainnet],
client({ chain }) {
return createWalletClient({ account, chain, transport: http() })
},
})
const hash = await writeClearMacroForwarder(config, {
chainId: mainnet.id,
functionName: "runMacro",
args: ["0x...", "0x...", "0x...", "0x..."] // Macro, encoded payload, signer, signature
}) Key Notes
- Clear vs blind signing: The signer approves a human-readable EIP-712 payload (the "clear" forwarder), rather than opaque calldata. Use
getDigest/getNonce/encodeParamsto build the payload to sign. - Third-party relaying:
runMacrotakessignerandsignatureexplicitly, so any relayer can submit a macro that was authorized by another account. - Permit2:
runPermit2AndMacrobundles a Permit2 token approval with the macro execution in a single transaction. - Events: The contract emits
MacroExecuted,EIP712DomainChanged, andPermit2UpgradeExecuted— subscribe withuseWatchClearMacroForwarderEvent(/hook) orwatchClearMacroForwarderEvent(/action). - Address source: Powered by metadata
contractsV1.clearMacroForwarderV1WithPermit2, deployed at the same deterministic address on every supported network.