feat: support batch txs#24
Conversation
|
failing ci |
|
@claude review |
We'll also need an efficient way in general to handle possibly under-funding nodes for gas costs. Depending on how many nodes individually work on signals, determines how many extra expensive collect calls there will be. Maybe weight the reward automatically in the escrow side by transfer amount percentages (the same logic as the weights, but using the single number). |
|
It would be nice to consider incorporating the salt-based commitments from #23 to prevent leaking data if a transaction is never solved |
|
@claude review |
|
@claude review |
|
Claude finished @ozwaldorf's task in 3m 12s —— View job PR Review: feat/support-batch-tx
|
|
@ozwaldorf any ideas on reward fee calculations? |
Yes, lets do the initial take where reward percentage is based on tx volumes. Basically use the amount in the current formula, and remove the user-side argument for each tx in the batch. |
already implemented that. this only works cleanly when the amounts are comparable. i mean for same token batch tx it's fine, but for mixed assets, raw amounts aren't comparable |
|
I wonder if native token should be a separate contract, similar to the single tx contracts, and avoid mixing assets entirely, along with avoiding requiring an oracle or something. Stables are interchangeable (USDC USDT etc) |
|
one way i can think of right now is to pass normalized per-row value. frontend calculates: what do you think @ozwaldorf |
yeah true, but we aren't "avoiding mixed assets" |
|
We should have e2e tests using proofs generated from nomad. You can write a nomad e2e test and have the test dump the proofs it would submit, and plug them into the contract side |
okay |
@ozwaldorf did you see this |
|
I think reward weight is very confusing to use at the moment and changes drastically depending on ETH amounts. Lets make it rewardRate and computed in the enclave per 1 unit, basically what you had put in the comment previously USD = rate of 1 The node should check the rate and ensure it's acceptable within slippage based on current pricing |
we are no longer using rewardweight though |
ozwaldorf
left a comment
There was a problem hiding this comment.
We should fine tune the reward mechanism in the next pr
|
@ozwaldorf i cant merge???! |

Escrow Batch Implementation Summary
What This Implements
EscrowBatchis the batch version of the escrow contract. It lets a deployer define many expected transfers in one escrow, lets one or more nodes reserve different parts of that batch, and lets each node prove that the transfers they reserved were executed before they can collect.The batch now supports mixed payment assets. A single batch can contain ERC20 transfers and native ETH transfers, for example USDC, USDT, and ETH in the same escrow.
The reward and bond token is still one ERC20 token. That token is set by
tokenContract. The payment assets are defined per transfer row.Transfer Model
Each row in the batch is represented by
BatchTransfer:assetTypesays whether the payment is ERC20 or native ETH.assetis the ERC20 token address for ERC20 transfers. For native ETH, it must beaddress(0).recipientandamountdescribe the transfer the node must execute.rewardWeightis the common value used to split rewards and calculate bond requirements. This is needed because raw token amounts across USDC, USDT, and ETH cannot be compared directly. For example,1 etherand1 USDCare not the same value just because they are both represented as integers on-chain.So the contract does not use
amountto split rewards. It usesrewardWeight.The contract does not calculate
rewardWeightby itself. The deployer or frontend supplies it for each transfer row.For a batch where every payment uses the same token,
rewardWeightcan simply match the transfer amount:For mixed-asset batches,
rewardWeightshould be normalized to a common value, usually USD value or reward-token value:The total weight is:
If the total reward is
35 USDC, then the reward split is:If the deployer wants every transfer row to receive the same reward regardless of payment size, every row can use the same weight:
Funding
The deployer funds the batch with:
tokenContract;msg.valuefor rows whereassetType == NATIVE.Native funding must exactly equal the sum of all native ETH transfer amounts.
ERC20 payment assets are transferred from the deployer into the escrow contract.
The contract tracks payment balances per asset, so it knows how much USDC, USDT, ETH, or any other supported asset is still locked in the batch.
Funding is one-shot. Once the batch has been funded, it cannot be re-funded with stale state.
Multi-Node Reservations
Nodess do not have to execute the whole batch.
Each node can call
bond()with the transfer indexes they want to execute. For example:The contract prevents overlap. If a transfer index is already reserved or already completed, another node cannot reserve it.
Each reservation stores:
The minimum bond is calculated from the executor's share of the reward:
The reward share is based on
rewardWeight, not raw token amounts.If a node fails to collect before the deadline, their reservation expires. The reserved transfer indexes become available again, and the forfeited bond is added to the reward pool.
Proof Model
Collection uses one interface:
The array is important because one node may need to prove transfers that landed in different receipts, different transactions, or different blocks.
Each proof has a
proofType:For ERC20 transfers, the proof validates receipt logs.
For native ETH transfers, the proof validates both the transaction and the receipt.
ERC20 Proofs
An ERC20 proof can prove multiple transfer rows from one receipt.
For each ERC20 row, the node provides:
The contract checks that each log proves the exact expected transfer:
asset;fromaddress must be the collecting Nomad;toaddress must match the expected recipient;This means a node cannot claim an old transfer, another sender's transfer, or a transfer from the wrong token contract.
Native ETH Proofs
Native ETH does not emit a standard ERC20
Transferlog, so it needs a different proof shape.For native ETH rows, the contract validates: