# Smart Contract Change Requests > Extracted from [[Vaki Use Cases - Technical Reference#6. Smart Contract Change Requests]] and [[Vaki Use Cases - Technical Reference#5. Open Design Decisions]]. These are changes that require modifications to the smart contract code — not just deployment configuration. ## 1. P1: Prevent `disburseFees()` on Cancelled Treasuries **Status:** Requested **Affected cases:** Case 12 (Fraud Vaki), Case 12b (Fraud Vaki — Closed Unverified), Case 16 (Fraudulent Contribution — fee overcollection) **Contract:** `KeepWhatsRaised` (and potentially `AllOrNothing`) **Priority 1** **Problem:** Fraud vakis should not disburse fees to the protocol. Currently, `disburseFees()` and `cancelTreasury()` are independent functions — nothing prevents calling `disburseFees()` after the treasury has been cancelled. Vaki is paying protocol + platform fees on contributions that are all being reversed via payment gateways. **Case 12b adds a race condition:** When a vaki closes naturally (due date passed) and is later flagged as fraud during `En verificación`, `disburseFees()` is Public and callable as soon as the deadline passes — potentially **before** fraud is even detected. The admin must call `cancelTreasury()` as early as possible, but if `disburseFees()` was already called, the fees are already gone. **Required change:** `disburseFees()` should revert if the treasury is in a cancelled state. Add a check like `require(!cancelled, "Treasury is cancelled")` at the top of the function. This prevents fee disbursement **after** cancellation, but does not solve the Case 12b race where fees are disbursed **before** fraud is detected. **Current workaround:** Vaki is paying the protocol fees on `FRAUD` Vakis. For Case 12b, the only mitigation is operational speed — flag fraud and cancel the treasury before anyone calls `disburseFees()`. ## 2. P1: Per-Pledge Cancel/Void Function **Status:** Requested **Affected cases:** Case 16 (Fraudulent Contribution), Case 18 (Lost Dispute) **Contract:** `BaseTreasury` / `KeepWhatsRaised` **Priority 1** **Problem:** No mechanism exists to reverse a single pledge on-chain. When an individual contribution is flagged as fraud or a dispute is lost, the COPM from that pledge remains locked in the treasury and the NFT remains in the vaker's wallet. The adjustment is handled entirely off-chain (excluded from fiat off-ramp calculation), creating a permanent on-chain/off-chain discrepancy. `getRaisedAmount()` on-chain will always be higher than the real Firestore totals. **Disputes make this especially urgent:** Stripe disputes can take up to 75 days to resolve. A dispute opened on an active vaki (`Publicada`) will almost certainly resolve **after** the vaki has closed, the refund window has expired, and potentially after the creator has already withdrawn. Without `voidPledge`, there is no clean on-chain recovery path for late-resolving disputes — the COPM sits as surplus, fees are overcollected, and if the creator was already paid, Vaki absorbs the fiat loss from Stripe's clawback. **Why this is critical — timing dependency:** Without `voidPledge`, the on-chain recovery of a single fraudulent pledge depends entirely on when fraud is detected relative to the KeepWhatsRaised timeline: | Timing | Recovery option | Problem | | --- | --- | --- | | Before deadline (`Publicada`) | None | COPM trapped, no refund/withdraw available | | Refund window open (deadline → deadline + `refundDelay`) | `claimRefund(tokenId)` via Privy embedded wallet | Only available during a narrow window | | Refund window closed, before `withdrawalDelay` | None without cancelling entire treasury | COPM stuck as surplus indefinitely | | After `withdrawalDelay` (`Conf Aportes`) | `claimFund()` sweeps everything | Not per-pledge — mixes surplus with other unclaimed funds | **Required change:** Add a `voidPledge(tokenId)` function (Platform Admin only) that: - Burns the ERC721 NFT receipt for the specified token - Releases the COPM back to the platform admin address (W1) - Decrements `raisedAmount` by the pledge amount - Emits a `PledgeVoided(tokenId, amount)` event - **Must exclude voided pledges from `disburseFees()` calculations** — if fees haven't been disbursed yet, the voided amount should not be included in the fee base. This prevents protocol/platform fee overcollection on reversed contributions. **Current workaround:** Excess COPM stays in the treasury. If detected during the refund window, `claimRefund(tokenId)` can be used (backend signs via Privy). Otherwise, eventually claimed by `claimFund()` or absorbed as surplus during withdraw. Firestore totals are the authoritative source. ## 3. P0: Role Separation: Operator vs Fee Recipient **Status:** Needs investigation **Affected cases:** All cases **Contract:** `GlobalParams`, `BaseTreasury` **Priority 0** **Problem:** The current contract ties the platform admin address to both (a) who can call admin functions (transaction signer) and (b) who receives platform fees. The multi-wallet plan needs: - **W2 (vaki.eth)** as the authorized caller — signs all on-chain transactions - **W1 (Vaki Safe)** as the fee recipient — receives all COPM If the contract uses `platformAdmin` for both access control checks AND fee transfers, setting W1 would mean W1 (multi-sig) must sign every transaction (defeats hot wallet purpose), and setting W2 would mean fees go to the hot wallet (breaks the zero-balance invariant). **Required change (if needed):** One of: - Add a separate `platformOperator` role that can execute admin functions on behalf of the platform admin - Add a separate `feeRecipient` address field that overrides where fees are sent - Or: verify that the existing contract already supports this separation (e.g., if `platformAdmin` controls access but fees go to a configurable address) **Action:** Review `GlobalParams.enlistPlatform()` and `BaseTreasury` access control implementation to determine if role separation is possible without contract changes. ## 4. P2: Tip Forwarding in `setFeeAndPledge()` **Status:** Proposed — pending feasibility review **Affected cases:** All cases with tips (pledge via `setFeeAndPledge` with `tip > 0`) **Contract:** `KeepWhatsRaised` (and potentially `BaseTreasury`) **Priority 2** **Problem:** The `tip` parameter in `setFeeAndPledge()` is always sent as `0` on-chain, creating a data discrepancy with the fiat gateway. The vaker pays a tip in fiat, but the tip is invisible on-chain. A separate `claimTip()` call exists but is never invoked in any use case flow, and tips should never sit in the treasury balance. **Desired behavior:** `setFeeAndPledge()` should accept the real tip amount, but instead of storing it in the treasury, immediately forward it to the platform admin address (W1 / Vaki Safe) within the same transaction. **Proposed change:** Modify `setFeeAndPledge()` so that when `tip > 0`, the function calls `COPM.transferFrom(caller, platformAdmin, tip)` (or equivalent internal transfer) directly, emitting a tip event linked to the pledge — but NOT adding the tip to the treasury balance. **Benefits:** - Single transaction — no separate `claimTip()` call needed - Tip recorded on-chain linked to pledge ID - Tip never stored in treasury - On-chain data matches fiat gateway (vaker tip amount visible) - Eliminates the unused `claimTip()` flow entirely **Questions for SC team:** 1. Is this feasible within the current `setFeeAndPledge()` architecture? 2. Does it impact gas costs significantly? 3. Would this require changes to `BaseTreasury` or just `KeepWhatsRaised`? --- ## Open Design Decisions > These are not SC code changes but architectural decisions that affect how the backend interacts with the contracts. ### `claimRefund()` Caller Mechanism **Affected cases:** 7, 8, 10, 11, 12, 12b, 14, 15, 19 (all refund cases) `claimRefund(tokenId)` requires the NFT holder (`msg.sender` must be token owner). Since vakers have Privy embedded wallets custodied by Vaki, the backend must execute this call from the vaker's wallet. | Option | Mechanism | Tradeoff | | ------------------------------ | ------------------------------------------------------------------ | -------------------------------------------------------------------------- | | Embedded wallet + session keys | Backend uses Privy session keys to sign from the vaker's wallet | Simplest if Privy supports persistent session keys for server-side signing | | Meta-transaction relay | Vaker signs a meta-tx, relayer submits it | Adds complexity; vaker may not be available to sign | | NFT transfer to W2 first | Transfer NFT from vaker wallet → W2, then W2 calls `claimRefund()` | Extra on-chain tx; W2 temporarily holds the NFT | ### `claimFund()` vs `disburseFees()` Interaction **Affected cases:** 2, 4, 5, 6, 9, 13, 24 (all timeout/fund claim cases) The use cases state `claimFund()` "sweeps everything" without needing `disburseFees()` first. Need to verify: - Does `claimFund()` transfer the entire treasury balance to the platform admin? - Or does it only transfer the "remaining" amount after fees should have been disbursed? Since all addresses (protocolAdmin, platformAdmin, campaignOwner) resolve to W1, the practical impact is zero. But the on-chain event trail and accounting would differ.