# 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.