Smart wallets
If you just need to interact with smart contracts, the easiest option is one of our smart wallets. You’ll be able to see all of your wallets in the dashboard athttps://app.goldsky.com/{projectId}/dashboard/compose/{appName}.
Create a smart wallet
Using wallets
Once you have a wallet created you can use it to write to smart contracts, see the full smart contract docs for more detailssendTransaction
For lower-level control, usesendTransaction to send a transaction with pre-encoded calldata. This is useful when you need to encode the transaction data yourself, pass specific gas parameters, or interact with contracts in ways that writeContract doesn’t cover.
The
nonce parameter is only supported with EOA (private key) wallets. Smart wallets manage nonces internally.writeContractBatch
writeContractBatch sends several contract calls in one gas-sponsored transaction. The batch is all or nothing: if any call reverts, none of them take effect. Inside each call, msg.sender is your wallet, so it works for methods that check the caller.
address, a functionSig, an args array, and an optional value in wei. The second argument to writeContractBatch is a list of these calls. The optional third argument is the same confirmation object as writeContract, and the optional fourth is a retry config. It returns { hash, receipt, userOpHash } for the one transaction that carried the whole batch.
writeContractBatch throws when:
- The wallet is not gas-sponsored, or the app is running locally or with
--fork-chains. It only runs in deployed apps. - The list of calls is empty.
- You pass
onReorgin the confirmation config. Batches do not support reorg handling.confirmationsworks. - A call would revert, or the transaction reverts on-chain. None of the calls take effect.
Use
writeContractBatch when calls must land together or not at all. If your calls are independent, send them as separate writeContract calls at the same time instead. Compose packs those automatically.getBalance
Check the native token balance of any wallet:Wallet properties
Every wallet exposesname and address as read-only properties:
EOA wallets
You can also use EOAs that you already own. With an EOA you can self-fund gas, send and receive tokens in your tasks, and interact with smart contracts where your EOA has privileges on particular contract methods. Currently we support storing your EOA private key in Compose’s secret management system, but in the future you’ll be able to use private keys that are secured within TEEs. EOA wallets never pass their private keys outside of the task process and they sign requests passed in unsigned from the host process. When tasks run in TEEs, the private key will stay inside the TEE, never exposed to any other part of the stack. First, you’ll need to store the private key in Goldsky’s secret management system and reference it in your compose.yaml file. You can see details on how to do that in the Secrets docs. Once you have your private key secret stored, you can use it to create a wallet:Webhook wallets
evm.webhookWallet sends gas-sponsored transactions from an address you already hold. Compose never sees the private key. It builds the ERC-4337 UserOperation, including paymaster data, POSTs that payload to a signing URL you host, then submits the signed operation.
Create a webhook wallet
url must be http:// or https://. address must be a 20-byte hex address. headers are copied onto every POST. Store the URL, address, and any auth token as secrets.
Compose saves the address on the app, so the wallet shows up in the dashboard.
Webhook wallets implement the same IWallet as smart wallets and EOAs (writeContract, writeContractBatch, sendTransaction, readContract, simulate, getBalance). Writes are always gas-sponsored.
Cloud only
Webhook wallets run in deployed apps. Creating one in local dev throws:--fork-chains still POSTs to your webhook so you can test signing. Submit fails after that because there is no local bundler.
Request bodies
Compose POSTs JSON tourl with Content-Type: application/json plus any headers you set. The request times out after 120 seconds. Bigint fields in the body are sent as decimal strings.
If the wallet is not yet EIP-7702 delegated on the target chain, Compose asks the webhook to sign the authorization first:
r and s as 32-byte hex strings, and either yParity (0 or 1) or v (27, 28, 0, or 1). If you also return address, chainId, or nonce, they must match the request. contractAddress is the EIP-7702 implementation, not your wallet.
Every sponsored write then asks the webhook to sign the UserOperation’s EIP-712 typed data:
{ "signature": "0x..." }. A non-JSON body, a missing signature, or a non-2xx status fails the task. Do not broadcast from the webhook. Return the signature; Compose submits the sponsored UserOperation.
nonce on sendTransaction is ignored, as with other gas-sponsored wallets. A task retry prepares a new UserOperation, so Compose does not reuse a previous webhook signature.
Signing server
The other side ofwebhookWallet is one POST handler on a server you host. sign7702Authorization and signTypedData are whatever holds the key (Turnkey, Fireblocks, an HSM, a local key). Sign and return. Do not broadcast.
Impersonated wallets
When testing locally with--fork-chains, you may need to call contract methods that are restricted to a specific address, for example an owner-only admin function or a method guarded by an access control list. Normally you’d need the private key for that address, but with the --impersonate flag you can act as any address on the local TEVM fork using only its public address.
This is useful when:
- You want to test privileged contract methods without giving your local environment access to the private key
- You need to debug interactions with a contract where only a specific address has permission to call certain methods
- You’re testing against a forked mainnet contract and want to simulate actions from an address you control on-chain but don’t want to expose the key locally
Usage
Pass--impersonate alongside --fork-chains when starting your app. The flag takes a comma-separated list of walletName=address mappings:
.address will resolve to the impersonated address, and all contract interactions (reads, writes, and simulations) will execute as that address on the local fork.
name that matches an entry in the impersonate map, the impersonated address takes precedence and the private key is effectively ignored on the fork:
--impersonate requires --fork-chains and only works in local development. All impersonated transactions execute on your local TEVM fork. Nothing is sent to the actual chain. When you deploy to cloud, wallets resolve normally.Gas sponsoring
By default, smart wallets (wallets created without a private key) use gas sponsoring so you don’t have to manage gas funding. Smart wallets use EIP-7702 delegation for account abstraction, with gas costs handled through ERC-4337 UserOperations. Webhook wallets are always gas-sponsored. You pay the gas bill as part of your normal monthly Goldsky bill, avoiding the complex budgetary and tax issues of purchasing gas tokens. When you don’t use gas sponsoring, you’ll need to get your wallet address from the compose dashboard athttps://app.goldsky.com/{projectId}/dashboard/compose/{appName}
and then transfer gas tokens to that wallet through a wallet or an exchange.
Gas sponsoring with EOA (private key) wallets
You can also opt into gas sponsoring for EOA wallets by passingsponsorGas: true when creating the wallet. Compose delegates the EOA via EIP-7702 on first use and routes transactions through ERC-4337 UserOperations, just like smart wallets: you keep full control of the key and signing, but you don’t have to fund the wallet with native gas tokens.
Sponsored EOA transactions only run in deployed apps. Running with
sponsorGas: true locally will throw a clear error that tells you to either remove sponsorGas, fund the wallet manually, or re-run with --fork-chains to exercise the sponsored flow against a forked chain. In --fork-chains mode the transaction is executed against the local fork (where gas is free), and you’ll see a warning reminding you the sponsored flow only takes effect once deployed to cloud.Concurrent writes
In a deployed app, gas-sponsoredwriteContract and sendTransaction calls from the same wallet are queued and packed together into fewer transactions. You don’t change any code. A task can start many writes at once and they finish much faster than if you await them one by one:
- Calls must not depend on each other. Compose decides how calls are packed, so do not rely on their order. Use
writeContractBatchwhen they must land together or not at all. - Reverting calls fail fast. Before packing, Compose checks each call. A call that would revert throws with the revert reason and is never sent, so it costs no gas. The other calls in the same burst still go through.
- Packed calls share a transaction. Calls that land together return the same
hash,userOpHash, andreceipt. - Confirmation settings split batches. Writes with different
confirmationsoronReorgsettings are packed separately, and each call resolves after its own confirmation depth. - Do follow-up work after the writes settle. If each concurrent branch makes more context calls after its write, those calls can happen in a different order when the run replays. Await all the writes first, then do the follow-up calls.
Retries and restarts
A gas-sponsored write never lands twice when a task retries or an app restarts. If a retry finds that the original write already landed, it returns the originalhash and receipt instead of failing.
Backup bundler
If the main bundler for a chain is down, Compose switches gas-sponsored writes on that chain to a backup bundler for 15 minutes, then tries the main one again. Your task code does not change. This applies on chains where a backup bundler is available.Gas usage in the dashboard
Every sponsored or self-paid transaction emits a run event withevm.gas_used and evm.total_cost_wei attributes (both decimal strings in wei) so you can audit per-transaction gas spend from the Compose dashboard at https://app.goldsky.com/{projectId}/dashboard/compose/{appName}. On OP Stack L2s (Base, Optimism, and friends) evm.total_cost_wei includes the L1 data fee, which typically dominates the total cost. Reading gasUsed × effectiveGasPrice from the receipt alone will under-report by roughly two orders of magnitude.
Gas pricing
For non-sponsored transactions, Compose uses automatic gas estimation with sensible defaults. If you need precise control over gas parameters, usesendTransaction instead of writeContract and specify maxFeePerGas, maxPriorityFeePerGas, and gas explicitly.
writeContract automatically simulates the transaction before submitting it, catching revert errors early. Gas-sponsored sendTransaction calls in a deployed app are also checked before they are sent, and a call that would revert throws without spending gas. Self-funded sendTransaction calls are not simulated: they submit directly.