Skip to Content

Web3 Plugin

Interact with EVM-compatible blockchain networks and Solana. Read-only actions work without credentials. Write actions require a connected Turnkey wallet.

Actions

ActionCategoryCredentialsDescription
Get Native Token BalanceWeb3NoCheck ETH/MATIC/etc. balance of any address
Get ERC20 Token BalanceWeb3NoCheck ERC20 token balance of any address
Get SPL Token BalanceWeb3NoCheck SPL token balance of any Solana address
Read ContractWeb3NoCall view/pure functions on smart contracts
Batch Read ContractWeb3NoBatch multiple contract reads into one RPC call via Multicall3
Get TransactionWeb3NoFetch full transaction details by hash
Get Address NonceWeb3NoRead an address’s mined and pending transaction counts
Write ContractWeb3WalletExecute state-changing contract functions
Transfer Native TokenWeb3WalletSend native tokens (ETH/MATIC on EVM, SOL on Solana) to a recipient
Transfer ERC20 TokenWeb3WalletSend ERC20 tokens to a recipient
Transfer SPL TokenWeb3WalletSend SPL tokens on Solana to a recipient
Approve ERC20 TokenWeb3WalletApprove a spender contract to spend tokens on your behalf
Check ERC20 AllowanceWeb3NoCheck the current token spending allowance granted to a spender
Query Contract EventsWeb3NoQuery historical smart contract events across a block range, for one event or for several events across several contracts, optionally filtered by indexed argument values
Query Transaction HistoryWeb3NoQuery historical transactions by function call with optional argument filtering
Sign Typed Data (EIP-712)Web3WalletProduce an EIP-712 signature over a typed-data payload for off-chain signed intents
Decode CalldataSecurityNoDecode raw calldata into human-readable function calls
Assess Transaction RiskSecurityNoAI-powered risk scoring with built-in DeFi rules

Get Native Token Balance

Check the native token balance (ETH, MATIC, etc.) of any address on any supported EVM chain.

Inputs: Network, Address

Outputs: success, balance (human-readable), balanceWei, address, error

When to use: Monitor wallet balances, trigger refills when balance drops below a threshold, track treasury holdings.

Example workflow:

Schedule (every hour) -> Get Native Token Balance (bot wallet) -> Condition: balance < 0.1 ETH -> Transfer Native Token (refill from treasury) -> Discord: "Bot wallet refilled"

Get ERC20 Token Balance

Check the balance of any ERC20 token for a given address on EVM chains. For SPL tokens on Solana, use Get SPL Token Balance.

Inputs: Network, Address, Token (select from supported tokens or enter custom contract)

Outputs: success, balance.balance, balance.symbol, balance.decimals, balance.name, address, error

When to use: Track token holdings, monitor protocol positions, alert on balance changes.


Get SPL Token Balance

Check the balance of any SPL token for a given Solana address. Supports Token-2022 mints and program-owned (off-curve) wallet addresses. A wallet with no associated token account for the mint reports a zero balance. Token symbol and name resolve from the mint’s on-chain metadata when the token is not in the supported list.

Inputs: Network (Solana), Address, Token (select from supported tokens or enter a mint address)

Outputs: success, balance.balance, balance.symbol, balance.decimals, balance.name, address, error

When to use: Track SPL token holdings, monitor treasury balances on Solana, alert on balance changes.


Read Contract

Call view or pure functions on any verified smart contract. Automatically fetches the ABI from block explorers and supports proxy contracts.

Inputs: Network, Contract Address, ABI (auto-fetched), Function, Function Arguments

Outputs: success, result (structured based on ABI outputs), error

When to use: Read on-chain state (prices, positions, governance proposals), check protocol health factors, monitor contract parameters.

Example workflow:

Schedule (every 5 min) -> Read Contract: Aave getLiquidationThreshold() -> Condition: health factor < 1.5 -> Discord: "Liquidation risk alert"

Batch Read Contract

Call the same contract function with multiple argument sets — or different functions across different contracts — in a single RPC call using Multicall3. Reduces dozens of individual read-contract nodes into one.

No credentials required — this is a read-only operation using Multicall3 at 0xcA11bde05977b3631167028862bE2a173976CA11.

Input Modes

ModeDescriptionBest For
UniformOne contract, one function, multiple arg sets on a single networkSame function across many inputs: balanceOf(addr) for 50 pools
MixedEach call has its own network, contract address, ABI, function, and argsHeterogeneous reads: hat() + approvals(addr) on different chains

Inputs

Uniform Mode:

InputRequiredDescription
inputModeYesuniform (default) or mixed
networkYesEVM chain to execute on
contractAddressYesTarget contract address (0x... or template variable)
abiYesContract ABI (auto-fetched from block explorer or pasted manually)
abiFunctionYesFunction to call (selected from ABI)
argsListNoArgument sets for each call (see Args List Builder below)
batchSizeNoMax calls per Multicall3 request (default: 100, range: 1-500)

Args List Builder (Uniform Mode):

The Args List field uses a dynamic builder instead of raw JSON. After selecting a function, the UI shows labeled inputs for each parameter based on the ABI signature. For example, selecting allowance(address owner, address spender) shows two labeled inputs per row: “owner (address)” and “spender (address)”. Click “Add Arg Set” to add more rows, each representing one call. Template variables like {{NodeName.value}} are supported in all inputs.

Mixed Mode:

InputRequiredDescription
inputModeYesMust be mixed
callsYesList of call objects, each with its own network, contract address, ABI, function, and arguments
batchSizeNoMax calls per Multicall3 request per network (default: 100, range: 1-500)

Each call in mixed mode is fully self-contained with:

  • Network — can mix calls across different chains (grouped and batched per network)
  • Contract Address — with address book integration for saved addresses
  • ABI — auto-fetched from block explorer per contract
  • Function — selected from the contract’s ABI
  • Arguments — auto-populated input fields based on function signature

Outputs

OutputDescription
successWhether the batch operation succeeded overall
resultsArray of { success, result, error? } in original call order
totalCallsTotal number of calls executed
errorError message if the entire batch failed (validation, RPC, or encoding error)

Each entry in results has:

  • success — whether this specific call succeeded
  • result — decoded return value (structured with named fields when ABI provides output names)
  • error — revert reason if the call failed (decoded from revert data when possible)

Partial Failure Handling

Uses Multicall3’s aggregate3 with allowFailure: true. If one call reverts, the others still return successfully. Each result has its own success flag. Revert reasons are decoded when possible (custom errors, Error(string) pattern, or raw bytes).

Cross-Chain Execution (Mixed Mode)

When mixed mode calls target different networks, calls are automatically grouped by network. Each network group is executed in parallel via Promise.all, and results are merged back in the original call order. This means you can batch reads across Ethereum, Polygon, and Arbitrum in a single node with minimal latency overhead.

Batch Size

The batchSize parameter (default: 100, max: 500) controls how many calls are included in each Multicall3 RPC request. If you have 250 calls with a batch size of 100, the node sends 3 sequential RPC requests (100 + 100 + 50). Lower values reduce payload size and are useful when RPC providers have response size limits. Maximum total calls per execution is 5000.

Result Structure

Return values are structured based on ABI output definitions:

  • Single unnamed output: Returns the value directly (e.g., "1000000000000000000")
  • Single named output: Returns { outputName: value } (e.g., { "balance": "1000000000000000000" })
  • Multiple outputs: Returns an object with all named fields (e.g., { "reserve0": "...", "reserve1": "...", "blockTimestamp": "..." })
  • BigInt values: Serialized as strings to preserve precision

Example Workflows

DEX Pool Liquidity Monitor (Uniform Mode)

Monitor a token’s balance across multiple DEX pool contracts. One function (balanceOf), many addresses. Each arg set shows a labeled “account (address)” input.

Schedule (every 5 min) -> Batch Read Contract: inputMode: uniform network: ethereum contractAddress: 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2 (MKR) function: balanceOf(address) argsList: Call 1: account = 0xPool1 Call 2: account = 0xPool2 Call 3: account = 0xPool3 ... Call 14: account = 0xPool14 -> Aggregate: operation: sum inputMode: array arrayInput: {{@batch:Batch Read Contract.results}} fieldPath: result.balance -> Condition: {{@total:Aggregate.result}} < 1000000000000000000000 -> Discord: "Low MKR liquidity: {{@total:Aggregate.result}} across {{@batch:Batch Read Contract.totalCalls}} pools"

Multi-Contract Governance Dashboard (Mixed Mode)

Read different parameters from multiple governance contracts on the same chain.

Schedule (every hour) -> Batch Read Contract: inputMode: mixed calls: [ { network: "ethereum", contractAddress: "0xGovA", abiFunction: "hat", args: [] }, { network: "ethereum", contractAddress: "0xGovA", abiFunction: "approvals", args: ["0xSpell1"] }, { network: "ethereum", contractAddress: "0xGovB", abiFunction: "proposalCount", args: [] }, { network: "ethereum", contractAddress: "0xGovB", abiFunction: "state", args: [42] } ] -> Discord: "hat={{@batch.results[0].result}}, approvals={{@batch.results[1].result}}, proposals={{@batch.results[2].result}}"

Cross-Chain Balance Check (Mixed Mode)

Check the same token’s balance on multiple chains in a single node.

Schedule (every 15 min) -> Batch Read Contract: inputMode: mixed calls: [ { network: "ethereum", contractAddress: "0xUSDC_ETH", abiFunction: "balanceOf", args: ["0xTreasury"], abi: "..." }, { network: "polygon", contractAddress: "0xUSDC_POLY", abiFunction: "balanceOf", args: ["0xTreasury"], abi: "..." }, { network: "arbitrum", contractAddress: "0xUSDC_ARB", abiFunction: "balanceOf", args: ["0xTreasury"], abi: "..." } ] -> Aggregate: operation: sum inputMode: array arrayInput: {{@batch:Batch Read Contract.results}} fieldPath: result -> Discord: "Total USDC across 3 chains: {{@total:Aggregate.result}}"

Keeper Network Job Scanner (Uniform Mode)

Check which jobs are workable across a keeper network using a single batched read. Each arg set shows a labeled “keeper (address)” input.

Schedule (every block) -> Batch Read Contract: inputMode: uniform network: ethereum contractAddress: 0xKeeperRegistry function: workable(address) argsList: Call 1: keeper = 0xJob1 Call 2: keeper = 0xJob2 Call 3: keeper = 0xJob3 Call 4: keeper = 0xJob4 -> Condition: any result.success && result.result == true -> Write Contract: execute the workable job

Uniswap V2 Pair Reserves (Uniform Mode)

Read reserves from multiple Uniswap V2 pairs. getReserves() takes no parameters, so each arg set is empty — just click “Add Arg Set” for each pair contract you want to query. Returns (reserve0, reserve1, blockTimestampLast) per call.

Schedule (every 5 min) -> Batch Read Contract: inputMode: uniform network: ethereum contractAddress: 0xPairAddress function: getReserves() argsList: (no parameters -- function has no inputs, single arg set added) -> Discord: "Pair reserves: {{@batch:Batch Read Contract.results}}"

Multi-Param Function: Allowance Check (Uniform Mode)

Check token allowances for multiple owner/spender pairs. The allowance(address,address) function has two parameters, so each arg set shows labeled inputs for both “owner” and “spender”.

Schedule (every hour) -> Batch Read Contract: inputMode: uniform network: ethereum contractAddress: 0xUSDC function: allowance(address,address) argsList: Call 1: owner = 0xTreasury, spender = 0xRouter Call 2: owner = 0xTreasury, spender = 0xBridge Call 3: owner = 0xVault, spender = 0xRouter -> Condition: any result where result < threshold -> Discord: "Low allowance detected"

Loop + Batch Read (Dynamic Address List)

Use a database query to get a dynamic list of addresses, then batch-read their balances. Each arg set’s “account” field uses a template variable.

Schedule (daily) -> Database Query: SELECT address FROM monitored_wallets -> Batch Read Contract: inputMode: uniform network: ethereum contractAddress: 0xUSDC function: balanceOf(address) argsList: Call 1: account = {{@db:Database Query.rows[0].address}} Call 2: account = {{@db:Database Query.rows[1].address}} ... -> Aggregate: operation: sum inputMode: array arrayInput: {{@batch:Batch Read Contract.results}} fieldPath: result -> Discord: "Total USDC across {{@batch:Batch Read Contract.totalCalls}} wallets: {{@total:Aggregate.result}}"

Partial Failure Detection

Handle calls where some succeed and some revert, such as checking allowances on contracts that may not implement the interface.

Schedule (every hour) -> Batch Read Contract: inputMode: mixed calls: [ { network: "ethereum", contractAddress: "0xTokenA", abiFunction: "allowance", args: ["0xOwner", "0xSpender"], abi: "..." }, { network: "ethereum", contractAddress: "0xTokenB", abiFunction: "allowance", args: ["0xOwner", "0xSpender"], abi: "..." }, { network: "ethereum", contractAddress: "0xNonERC20", abiFunction: "allowance", args: ["0xOwner", "0xSpender"], abi: "..." } ] -> Condition: any result where success == false -> Discord: "Failed calls: {{failed results with error messages}}"

Get Transaction

Fetch full transaction details by hash via eth_getTransactionByHash. Returns sender, recipient, value, calldata, nonce, gas, and block explorer links.

Inputs: Network, Transaction Hash

Outputs: success, hash, from, to, value (ETH), input (calldata), nonce, gasLimit, blockNumber, transactionLink, fromLink, toLink, error

When to use: Enrich event-triggered workflows with full transaction context, inspect pending transactions, feed transaction data into Decode Calldata or Assess Risk steps.

Example workflow:

Event (new transaction on monitored contract) -> Get Transaction: {{Trigger.transactionHash}} -> Decode Calldata: {{GetTransaction.input}} -> Assess Transaction Risk: calldata={{GetTransaction.input}}, value={{GetTransaction.value}} -> Condition: riskScore >= 51 -> Discord: "HIGH RISK TX from {{GetTransaction.from}}: {{AssessRisk.reasoning}}"

Get Address Nonce

Read the transaction count (nonce) of any address on EVM chains via eth_getTransactionCount. Every run reads both the mined count and the pending count in one step, so a single node can tell when transactions from a wallet are waiting in the mempool and not being mined.

Inputs: Network, Address, Block Tag (latest or pending, defaults to latest)

Outputs: success, nonce (count at the selected block tag), blockTag, address, latestNonce (mined transactions), pendingNonce (mined plus mempool transactions), pendingCount (pendingNonce minus latestNonce, never negative), error

When to use: Watch a keeper or bot wallet for stuck transactions, confirm a submitted transaction was mined before sending the next one, or compare the nonce against your own records to spot transactions that were mined but did not have the expected effect.

Example workflow:

Schedule (every 5 min) -> Get Address Nonce (keeper wallet) -> Condition: pendingCount > 0 -> Discord: "{{GetAddressNonce.pendingCount}} transaction(s) waiting from {{GetAddressNonce.address}}"

Query Contract Events

Query historical smart contract events (logs) across a block range with automatic batching. Supports any EVM-compatible chain. The step internally batches queries in 2,000-block chunks to stay within RPC provider limits.

One node can watch a single event on one contract, or several events across several contracts on the same network.

Query modes:

ModeDescriptionBest For
Single event (default)One contract, one event, optional indexed argument filterWatching one event, such as Transfer on a token
Multiple events or contractsA list of contract and event pairs on one network, each with its own ABI and optional filter, returned as one merged listWatching a set of permission or emergency events across related contracts from one node

Inputs:

  • Query Mode (default: Single event)
  • Network (required)
  • Contract Address (required)
  • Contract ABI (required, auto-fetched from block explorer)
  • Event Name (required, selected from ABI)
  • Filter by Indexed Arguments (optional) — a value for any indexed parameter of the event. Omit a parameter to match any value for it; a parameter given an empty value fails the step. One value per parameter: eth_getLogs also accepts a list of alternatives per topic, but that OR form is not supported here
  • Events (required in Multiple mode): up to 20 entries, each with its own Contract Address, Contract ABI (auto-fetched from block explorer), Event and optional Filter by Indexed Arguments. In Multiple mode these replace the single Contract Address, Contract ABI, Event Name and filter fields above.
  • Block Lookback — number of blocks to scan back from To Block (default: 6500, ~1 day on Ethereum). Ignored if From Block is set
  • From Block — explicit start block (overrides Block Lookback)
  • To Block — end block number (default: latest)

Outputs: success, events (array of decoded event objects with blockNumber, transactionHash, logIndex, args), fromBlock, toBlock, eventCount, error

In Multiple mode each event also carries contractAddress and eventName, and events is one list ordered by block number, then log index, across every entry.

How it works:

  1. Resolves the block range from inputs (either explicit From/To or lookback from latest)
  2. Compiles any indexed argument filter into log topics, failing the step on a filter that could never match
  3. Splits the range into 2,000-block batches to avoid RPC provider limits
  4. Queries each batch via eth_getLogs and decodes events using the ABI
  5. Concatenates all results and returns the full event list

Filtering by indexed arguments

The filter is applied by the RPC node, not after the fact, so a narrow filter changes what the query costs rather than only what it returns. On a busy contract that can be the difference between a query that completes and one that times out or hits the provider’s response limit.

Only indexed parameters can be filtered. Whether a parameter is indexed is fixed by the contract, so the configuration panel lists the ones available and disables the rest.

Two limits are worth knowing before you rely on it:

  • An indexed string or bytes is stored as a hash of its contents rather than the contents. Filtering one matches the whole value exactly; there is no partial or prefix matching, and the original value cannot be read back out of the log.
  • An indexed array or tuple cannot be filtered at all, for the same reason: its topic is a hash of the encoded contents. Those parameters are shown but disabled, and filtering them in a later node is the way to do it.

A filter naming a parameter that is not indexed, or a value that does not fit its type, fails the step before any query runs rather than scanning the range and returning nothing.

Querying multiple events or contracts

Set Query Mode to Multiple events or contracts and add one entry per contract and event pair. Each entry has its own contract address, ABI and event, and can carry its own indexed argument filter. Every entry runs on the node’s network over one shared block range, so fromBlock, toBlock and eventCount describe the whole node.

The node makes as few eth_getLogs calls as the entries allow:

  • Entries without a filter on the same contract share one call, and contracts watching the same set of events share one call through a list of addresses.
  • An entry with a filter gets a call of its own, because its filter narrows topics that the other entries leave open.

Watching ten event types across three related contracts without filters takes at most three calls per 2,000-block batch, not one per contract and event pair. A log that two entries both match, such as a Transfer filtered on from in one entry and on to in another, is returned once.

Every entry is checked before any query runs. An invalid address, an ABI that does not parse, an event missing from its ABI, or a filter that could never match fails the step with a message naming the entry, for example Event 3 (RoleGranted): .... One node holds at most 20 entries.

When To Block is empty or latest, a Multiple query ends 5 blocks behind the latest block. The calls for different entries can reach different nodes behind the same RPC provider, and a fixed end block lets every call cover exactly the same range. Block Lookback counts back from that end block, so a schedule whose lookback matches its interval still covers every block, and the most recent blocks are picked up on the next run.

When to use: Index historical events, monitor contract activity over time, aggregate on-chain data for analytics, trigger downstream actions based on past events, and watch one address, pool or token ID on a contract busy enough that an unfiltered scan is impractical. Use Multiple mode to watch a set of related events across several contracts from one node instead of one node per contract and event.

Example workflow — DEX Swap Monitor:

Schedule (every hour) -> Query Contract Events: Uniswap Router, event "Swap", Block Lookback 300 -> Condition: eventCount > 0 -> Discord: "{{QueryEvents.eventCount}} swaps in the last hour"

Example workflow — Governance Vote Tracker:

Schedule (daily) -> Query Contract Events: Governor contract, event "VoteCast", Block Lookback 7200 -> SendGrid: "{{QueryEvents.eventCount}} votes cast today"

Example workflow — Treasury Deposit Watch:

Schedule (every 15 minutes) -> Query Contract Events: USDC, event "Transfer", to = treasury address, Block Lookback 75 -> Condition: eventCount > 0 -> Discord: "{{QueryEvents.eventCount}} incoming transfers"

Without the to filter this query fetches every USDC transfer in the window and the node is doing the filtering; with it, the RPC returns only the treasury’s.

Example workflow (Protocol Emergency Watch):

Schedule (every 15 minutes) -> Query Contract Events: Multiple events or contracts, Block Lookback 75 Vault: "Paused", "Unpaused", "RoleGranted", "RoleRevoked" Price oracle: "Paused", "Unpaused" Governance token: "OwnershipTransferred" -> Condition: eventCount > 0 -> Discord: "{{QueryEvents.eventCount}} admin events across the protocol"

One node replaces seven, and downstream nodes read one list in which each event names its contract and event.


Query Transaction History

Query historical transactions sent to a contract address, filtered by function call and optionally by argument values. Uses block explorer APIs (Etherscan/Blockscout) to find transactions, then decodes their calldata using the provided ABI. Useful when the target contract does not emit events for certain function calls.

Inputs:

InputRequiredDescription
NetworkYesEVM chain to query
Contract AddressYesTarget contract address
ABIYesContract ABI (auto-fetched from block explorer)
FunctionYesFunction name to filter by (selected from ABI)
Function ArgumentsNoArgument values to match. Empty args act as wildcards — leave all empty to return all calls to the selected function
Block LookbackNoNumber of blocks to scan back from To Block (default: 6500). Ignored if From Block is set
From BlockNoExplicit start block (overrides Block Lookback)
To BlockNoEnd block number (default: latest)

Outputs: success, transactions (array of decoded transaction objects), fromBlock, toBlock, totalFetched, matchCount, contractAddressLink, error

Each transaction in the transactions array contains:

  • hash — transaction hash
  • from — sender address
  • to — contract address
  • value — ETH value sent
  • blockNumber — block number
  • timestamp — block timestamp
  • functionName — decoded function name
  • functionSignature — full function signature
  • args — decoded named arguments as key-value pairs
  • transactionLink — block explorer link

How it works:

  1. Resolves the block range from inputs (either explicit From/To or lookback from latest)
  2. Fetches all transactions to the contract via block explorer API (Etherscan txlist or Blockscout equivalent)
  3. Decodes each transaction’s calldata using the provided ABI
  4. Filters to only transactions calling the selected function
  5. If argument values are provided, matches decoded args against filter values (case-insensitive, empty = wildcard)

When to use: Query on-chain activity for contracts that don’t emit events for certain operations, audit historical function calls, monitor specific contract interactions over a block range.

Example workflow — Monitor specific function calls:

Schedule (every hour) -> Query Transaction History: contract 0xVault, function "deposit", Block Lookback 300 -> Condition: matchCount > 0 -> Discord: "{{QueryTx.matchCount}} deposits in the last hour"

Example workflow — Filter by argument value:

Schedule (every 15 min) -> Query Transaction History: contract 0xEngine, function "lock", args: ["", "0xSpecificUrn"] -> Condition: matchCount > 0 -> SendGrid: "{{QueryTx.matchCount}} lock() calls to urn 0xSpecificUrn"

Write Contract

Execute state-changing functions on smart contracts using your Turnkey wallet. Requires a connected wallet.

Inputs: Network, Contract Address, ABI (auto-fetched), Function, Function Arguments, Fail workflow on error (optional, on by default), Gas Limit Multiplier (optional, in Advanced section)

Outputs: success, transactionHash, transactionLink, gasUsed (total gas cost in wei), result, error

When to use: Execute DeFi operations (harvest, compound, rebalance), respond to on-chain events, automate protocol maintenance.

Gas Configuration: Optionally set a custom Gas Limit Multiplier in the Advanced section to override the chain default. See Gas Management for details.

Note: The gasUsed output represents the total transaction cost in wei (gas units × effective gas price), not just the number of gas units consumed.

When a write fails

Fail workflow on error is on by default, so a write that fails stops the run and the failure is reported on the node.

Turn it off and the failure is softened instead: the workflow continues, and the node reports success: true with error and rejection set. The softened result carries only those three fields, so transactionHash, transactionLink and the receipt fields are absent from it. Read error to tell a softened failure from a real success, because at the node the two are otherwise identical.

The two halves of a softened failure are reported in different places, which is the part that surprises people:

  • On the node: success: true, error set, no transactionHash.
  • On the execution: status: success, transactionHashes: [], and errorContext: null, because that field is only populated for a failed status. The run reads as a success in /api/analytics/runs with no hash against it.

Not every failure softens. A failure with no class, or one classified as a third-party dependency, is softened. One classified as this workflow’s own configuration or as a platform problem still fails the run whatever the toggle says, and so does a write whose outcome the platform cannot yet determine: a send that is still in flight is never reported as a success.

Direct-API field names

If you’re building this action via the Workflows API rather than the editor, the UI labels above map to these config keys — the strict-mode validator will reject the natural-language versions (function, method, contract).

functionName and args are a trap. The save-time validator accepts them so that workflows persisted before a field rename stay re-savable, but the runtime never translates them to abiFunction / functionArgs. A workflow built with them saves cleanly and then fails at execution with Missing `abiFunction` in the step config. Send the canonical keys.

UI labelAPI config fieldShape
FunctionabiFunctionplain function name string (e.g. "release"). Not functionName — see the note above.
Function ArgumentsfunctionArgsJSON-encoded array string — e.g. "[\"0xabc…\"]". Templates inside the string are resolved before JSON.parse, so wrap template tokens in the array’s string quotes.
Contract ABIabiJSON-encoded string (same convention as functionArgs)
Web3 Connectionweb3ConnectionSender routing: "default" (org policy), "eoa" (force the Turnkey EOA), or "safe:<safeWalletId>". The signing wallet is your org’s Turnkey wallet, resolved automatically.
Networknetworknumeric chain id as a string (e.g. "11155111")
Fail workflow on errorfailOnErrorboolean, on by default. false, or the string "false", softens a failed write into success: true with error set; any other value stays on.

See the generic web3 write-contract example in the Workflows API docs for a full working request body.


Transfer Native Token

Send native tokens from your Turnkey wallet to a recipient address. On EVM networks this sends ETH, MATIC, or the chain’s native asset. On Solana networks this sends SOL.

Inputs: Network, Amount, Recipient Address, Gas Limit Multiplier (optional, in Advanced section; EVM only)

Outputs: success, transactionHash, transactionLink, gasUsed (total fee in wei on EVM, lamports on Solana), gasUsedUnits and effectiveGasPrice on Solana (compute-unit metadata), error

When to use: Refill bot wallets, distribute funds, automate payroll.

Gas Configuration: On EVM networks you can optionally set a custom Gas Limit Multiplier in the Advanced section. See Gas Management for details. Solana uses lamport fees and compute units instead of EVM gas limits.

Note: On EVM, gasUsed is the total transaction cost in wei (gas units x effective gas price). On Solana, gasUsed is the total lamport fee (base signature fee plus priority fee from consumed compute units).


Transfer ERC20 Token

Send ERC20 tokens from your Turnkey wallet to a recipient address.

Inputs: Network, Token, Amount, Recipient Address, Gas Limit Multiplier (optional, in Advanced section)

Outputs: success, transactionHash, transactionLink, gasUsed (total gas cost in wei), amount, symbol, recipient, error

When to use: Distribute tokens, move funds between wallets, automate token transfers based on conditions.

Gas Configuration: Optionally set a custom Gas Limit Multiplier in the Advanced section to override the chain default. See Gas Management for details.

Note: The gasUsed output represents the total transaction cost in wei (gas units × effective gas price), not just the number of gas units consumed.


Transfer SPL Token

Send SPL tokens on Solana from your Turnkey wallet to a recipient address. If the recipient does not yet have an associated token account for the mint, KeeperHub creates one and reserves the rent-exempt minimum from your SOL balance.

Inputs: Network (Solana mainnet or devnet), Mint address, Amount, Recipient address

Outputs: success, transactionHash, transactionLink, gasUsed (total lamport fee), gasUsedUnits, effectiveGasPrice, amount, mint, decimals, recipient, recipientTokenAccount, createdRecipientAccount, error

When to use: Distribute SPL tokens, automate treasury payouts on Solana, or move tokens between wallets on Solana networks.


Approve ERC20 Token

Grant spending permission to a spender contract, allowing it to transfer ERC20 tokens on behalf of your Turnkey wallet. This is required before using DEX aggregators, DeFi protocols, or any contract that needs to move tokens from your wallet.

Inputs: Network, Token, Spender Address, Amount, Gas Limit Multiplier (optional, in Advanced section)

Outputs: success, transactionHash, transactionLink, gasUsed (total gas cost in wei), approvedAmount (human-readable or “unlimited”), spender, symbol, error

When to use: Enable DeFi interactions (swaps, deposits, lending), automate approval management, set up bot wallets for protocol operations.

Amount Options:

  • Exact amount (e.g., “100.50”) - approve only the specified amount
  • “max” or “unlimited” - approve unlimited spending (max uint256)

Gas Configuration: Optionally set a custom Gas Limit Multiplier in the Advanced section to override the chain default. See Gas Management for details.

Note: The gasUsed output represents the total transaction cost in wei (gas units × effective gas price), not just the number of gas units consumed.

Example workflow:

Schedule (daily) -> Check ERC20 Allowance: owner={{walletAddress}}, spender={{routerAddress}} -> Condition: allowanceRaw < 1000000000 -> Approve ERC20 Token: spender={{routerAddress}}, amount="max" -> Discord: "Router approval refreshed"

Sign Typed Data (EIP-712)

Produce an EIP-712 signature over a typed-data payload using your organization’s Turnkey wallet, for off-chain signed intents. Fund-moving authorizations (permits, transfer authorizations, delegations) are refused on this action.

Inputs: EIP-712 Typed Data (JSON with domain, types, primaryType, and message)

Outputs: success, signature (65-byte 0x-prefixed signature), signer (checksummed signer address), error, code (VALIDATION, NO_WALLET, POLICY_BLOCKED, UPSTREAM, or UNKNOWN)

When to use: Sign off-chain intents such as order commitments or attestations that a downstream service verifies. Not for fund-moving permits or transfer authorizations, which are refused.


Check ERC20 Allowance

Check the current ERC20 token spending allowance that an owner has granted to a spender address. This is a read-only operation that does not require credentials.

Inputs: Network, Token, Owner Address, Spender Address

Outputs: success, allowance (human-readable), allowanceRaw (wei string), symbol, error

When to use: Monitor approval status before operations, audit token permissions, trigger approval renewals when allowance drops below threshold.

Example workflow:

Schedule (every hour) -> Check ERC20 Allowance: owner={{treasuryWallet}}, spender={{dexRouter}} -> Condition: allowanceRaw < 100000000000000000000 -> Approve ERC20 Token: amount="1000" -> SendGrid: "DEX router approval renewed to 1000 tokens"

Decode Calldata

Decode raw transaction calldata into human-readable function calls with parameter names and values. Uses a cascading strategy: manual ABI, block explorer lookup, 4byte.directory, then selector-only fallback.

Inputs: Calldata (hex string), Contract Address (optional), Network (optional), ABI Override (optional, advanced)

Outputs: success, selector, functionName, functionSignature, parameters (array with name/type/value), decodingSource, error

When to use: Analyze pending transactions before execution, audit governance proposals, inspect multisig queue items, feed decoded data into risk assessment.

This is a security-critical action (maxRetries = 0). On error, it fails rather than retrying.

Example workflow:

Webhook (receives pending tx) -> Decode Calldata: {{Trigger.calldata}} -> Condition: functionName == "transferOwnership" -> Discord: "ALERT: Ownership transfer detected"

Assess Transaction Risk

AI-powered risk assessment that combines built-in DeFi security rules with OpenAI analysis. Produces a risk score (0-100) and detailed risk factors.

Inputs: Transaction Calldata, Contract Address (optional), Transaction Value in ETH (optional), Chain (optional), Sender Address (optional)

Outputs: success, riskLevel (low/medium/high/critical), riskScore (0-100), factors (array of risk descriptions), decodedFunction, reasoning, error

How it works:

  1. Auto-decodes calldata internally (no need for a separate Decode Calldata step)
  2. Runs built-in rules across 4 categories:
    • Approval risks — Unlimited approvals (MAX_UINT256), setApprovalForAll
    • Privileged operations — transferOwnership, upgradeTo, selfdestruct
    • Value risks — Large ETH transfers (>10 ETH)
    • Interaction risks — Zero address targets, unknown selectors, generic execution patterns
  3. Critical rule match short-circuits without AI call
  4. Otherwise, enhances assessment with GPT-4o Mini (3s timeout)
  5. Combines rule-based and AI findings, taking the higher risk level

Fail-closed policy: If the AI call fails or times out, the risk level is elevated (not lowered). This is a security-critical action (maxRetries = 0).

When to use: Guard transactions before execution, score governance proposals, audit multisig queues, build security monitoring workflows.

Example workflow — Transaction Guardian:

Webhook (receives pending tx from multisig) -> Assess Transaction Risk: {{Trigger.calldata}} -> Condition: riskLevel == "critical" OR riskLevel == "high" -> YES: Discord #security-alerts: "HIGH RISK TX: {{AssessRisk.reasoning}}" -> NO: Discord #operations: "TX approved ({{AssessRisk.riskLevel}})"

Example workflow — Approval Monitor:

Schedule (every 15 min) -> Read Contract: getApprovalQueue() -> Assess Transaction Risk: {{ReadContract.result.calldata}} -> Condition: riskScore > 50 -> SendGrid: "Review required: risk score {{AssessRisk.riskScore}}"