From eff43ab69e9530888fb9a2683a6dbaeea975dc74 Mon Sep 17 00:00:00 2001 From: grumbach Date: Fri, 3 Apr 2026 11:37:51 +0900 Subject: [PATCH 1/2] feat: add merkle payment calldata generation for external signers - Make pay_for_merkle_tree_calldata() public in the unified PaymentVault handler - Add pay_for_merkle_tree_calldata() to external_signer.rs with MerklePaymentCalldataReturn type for external wallet signing - Uses the unified PaymentVault API (post-V2 refactor) --- src/contract/payment_vault/handler.rs | 6 ++-- src/external_signer.rs | 51 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/contract/payment_vault/handler.rs b/src/contract/payment_vault/handler.rs index 846e476..d68b562 100644 --- a/src/contract/payment_vault/handler.rs +++ b/src/contract/payment_vault/handler.rs @@ -114,8 +114,10 @@ where Ok((winner_pool_hash, total_amount, gas_info)) } - /// Get calldata for payForMerkleTree - fn pay_for_merkle_tree_calldata( + /// Get calldata for payForMerkleTree. + /// + /// Public so external signers can generate calldata without a wallet. + pub fn pay_for_merkle_tree_calldata( &self, depth: u8, pool_commitments: I, diff --git a/src/external_signer.rs b/src/external_signer.rs index 26ffe04..d5bf49f 100644 --- a/src/external_signer.rs +++ b/src/external_signer.rs @@ -10,6 +10,8 @@ use crate::Network; use crate::common::{Address, Amount, Calldata, QuoteHash, QuotePayment, U256}; use crate::contract::network_token::{self, NetworkToken}; use crate::contract::payment_vault::MAX_TRANSFERS_PER_TRANSACTION; +use crate::contract::payment_vault::handler::PaymentVaultHandler; +use crate::merkle_batch_payment::PoolCommitment; use crate::utils::http_provider; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -96,3 +98,52 @@ pub fn pay_for_quotes_calldata>( approve_amount, }) } + +/// Return type for merkle tree payment calldata generation. +#[derive(Debug, Serialize, Deserialize)] +pub struct MerklePaymentCalldataReturn { + /// Transaction calldata for `payForMerkleTree()`. + pub calldata: Calldata, + /// Contract address to send the transaction to. + pub to: Address, + /// Address to approve for token spending. + pub approve_spender: Address, + /// Estimated total cost for approval. + pub approve_amount: Amount, + /// Tree depth. + pub depth: u8, + /// Payment timestamp committed by all candidate nodes. + pub merkle_payment_timestamp: u64, +} + +/// Generate calldata for a merkle batch payment without executing it. +/// +/// This is the external-signer counterpart to `Wallet::pay_for_merkle_tree`. +/// Returns the raw calldata that an external wallet (MetaMask, WalletConnect, etc.) +/// should sign and submit as a single transaction. +/// +/// The `approve_amount` is set to `U256::MAX` since the exact cost depends on +/// the contract's winner pool selection (which includes `msg.sender` as entropy). +pub fn pay_for_merkle_tree_calldata( + network: &Network, + depth: u8, + pool_commitments: Vec, + merkle_payment_timestamp: u64, +) -> Result { + let vault_address = *network.payment_vault_address(); + + let provider = http_provider(network.rpc_url().clone()); + let handler = PaymentVaultHandler::new(vault_address, provider); + + let (calldata, to) = + handler.pay_for_merkle_tree_calldata(depth, pool_commitments, merkle_payment_timestamp)?; + + Ok(MerklePaymentCalldataReturn { + calldata, + to, + approve_spender: vault_address, + approve_amount: Amount::MAX, + depth, + merkle_payment_timestamp, + }) +} From f8fa0898b3b31874125762441cbd7cb3a77eae8b Mon Sep 17 00:00:00 2001 From: grumbach Date: Fri, 3 Apr 2026 12:06:57 +0900 Subject: [PATCH 2/2] fix: use imported PaymentVaultHandler instead of inline path --- src/external_signer.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/external_signer.rs b/src/external_signer.rs index d5bf49f..55b0bed 100644 --- a/src/external_signer.rs +++ b/src/external_signer.rs @@ -74,10 +74,7 @@ pub fn pay_for_quotes_calldata>( let approve_amount = total_amount; let provider = http_provider(network.rpc_url().clone()); - let data_payments = crate::contract::payment_vault::handler::PaymentVaultHandler::new( - *network.payment_vault_address(), - provider, - ); + let data_payments = PaymentVaultHandler::new(*network.payment_vault_address(), provider); // Divide transfers over multiple transactions if they exceed the max per transaction. let chunks = payments.chunks(MAX_TRANSFERS_PER_TRANSACTION);