create-fhevm-example 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/contracts/advanced/BlindAuction.sol +255 -0
  2. package/contracts/advanced/EncryptedEscrow.sol +315 -0
  3. package/contracts/advanced/HiddenVoting.sol +231 -0
  4. package/contracts/advanced/PrivateKYC.sol +309 -0
  5. package/contracts/advanced/PrivatePayroll.sol +285 -0
  6. package/contracts/basic/decryption/PublicDecryptMultipleValues.sol +160 -0
  7. package/contracts/basic/decryption/PublicDecryptSingleValue.sol +142 -0
  8. package/contracts/basic/decryption/UserDecryptMultipleValues.sol +61 -0
  9. package/contracts/basic/decryption/UserDecryptSingleValue.sol +59 -0
  10. package/contracts/basic/encryption/EncryptMultipleValues.sol +72 -0
  11. package/contracts/basic/encryption/EncryptSingleValue.sol +44 -0
  12. package/contracts/basic/encryption/FHECounter.sol +54 -0
  13. package/contracts/basic/fhe-operations/FHEAdd.sol +51 -0
  14. package/contracts/basic/fhe-operations/FHEArithmetic.sol +99 -0
  15. package/contracts/basic/fhe-operations/FHEComparison.sol +116 -0
  16. package/contracts/basic/fhe-operations/FHEIfThenElse.sol +53 -0
  17. package/contracts/concepts/FHEAccessControl.sol +94 -0
  18. package/contracts/concepts/FHEAntiPatterns.sol +329 -0
  19. package/contracts/concepts/FHEHandles.sol +128 -0
  20. package/contracts/concepts/FHEInputProof.sol +104 -0
  21. package/contracts/gaming/EncryptedLottery.sol +298 -0
  22. package/contracts/gaming/EncryptedPoker.sol +337 -0
  23. package/contracts/gaming/RockPaperScissors.sol +213 -0
  24. package/contracts/openzeppelin/ERC7984.sol +85 -0
  25. package/contracts/openzeppelin/ERC7984ERC20Wrapper.sol +43 -0
  26. package/contracts/openzeppelin/SwapERC7984ToERC20.sol +110 -0
  27. package/contracts/openzeppelin/SwapERC7984ToERC7984.sol +48 -0
  28. package/contracts/openzeppelin/VestingWallet.sol +147 -0
  29. package/contracts/openzeppelin/mocks/ERC20Mock.sol +31 -0
  30. package/dist/scripts/commands/add-mode.d.ts.map +1 -0
  31. package/dist/scripts/{add-mode.js → commands/add-mode.js} +27 -61
  32. package/dist/scripts/commands/doctor.d.ts.map +1 -0
  33. package/dist/scripts/{doctor.js → commands/doctor.js} +2 -2
  34. package/dist/scripts/commands/generate-config.d.ts.map +1 -0
  35. package/dist/scripts/{generate-config.js → commands/generate-config.js} +3 -10
  36. package/dist/scripts/commands/generate-docs.d.ts.map +1 -0
  37. package/dist/scripts/{generate-docs.js → commands/generate-docs.js} +4 -3
  38. package/dist/scripts/commands/maintenance.d.ts.map +1 -0
  39. package/dist/scripts/{maintenance.js → commands/maintenance.js} +11 -10
  40. package/dist/scripts/index.js +63 -59
  41. package/dist/scripts/{builders.d.ts → shared/builders.d.ts} +2 -2
  42. package/dist/scripts/shared/builders.d.ts.map +1 -0
  43. package/dist/scripts/{builders.js → shared/builders.js} +49 -30
  44. package/dist/scripts/{config.d.ts → shared/config.d.ts} +0 -2
  45. package/dist/scripts/shared/config.d.ts.map +1 -0
  46. package/dist/scripts/{config.js → shared/config.js} +48 -59
  47. package/dist/scripts/shared/generators.d.ts +42 -0
  48. package/dist/scripts/shared/generators.d.ts.map +1 -0
  49. package/dist/scripts/{utils.js → shared/generators.js} +34 -271
  50. package/dist/scripts/shared/ui.d.ts.map +1 -0
  51. package/dist/scripts/{ui.js → shared/ui.js} +3 -2
  52. package/dist/scripts/{utils.d.ts → shared/utils.d.ts} +4 -27
  53. package/dist/scripts/shared/utils.d.ts.map +1 -0
  54. package/dist/scripts/shared/utils.js +228 -0
  55. package/fhevm-hardhat-template/.eslintignore +26 -0
  56. package/fhevm-hardhat-template/.eslintrc.yml +21 -0
  57. package/fhevm-hardhat-template/.github/workflows/main.yml +47 -0
  58. package/fhevm-hardhat-template/.github/workflows/manual-windows.yml +28 -0
  59. package/fhevm-hardhat-template/.github/workflows/manual.yml +28 -0
  60. package/fhevm-hardhat-template/.prettierignore +25 -0
  61. package/fhevm-hardhat-template/.prettierrc.yml +15 -0
  62. package/fhevm-hardhat-template/.solcover.js +4 -0
  63. package/fhevm-hardhat-template/.solhint.json +12 -0
  64. package/fhevm-hardhat-template/.solhintignore +3 -0
  65. package/fhevm-hardhat-template/.vscode/extensions.json +3 -0
  66. package/fhevm-hardhat-template/.vscode/settings.json +9 -0
  67. package/fhevm-hardhat-template/LICENSE +33 -0
  68. package/fhevm-hardhat-template/README.md +110 -0
  69. package/fhevm-hardhat-template/contracts/FHECounter.sol +46 -0
  70. package/fhevm-hardhat-template/deploy/deploy.ts +17 -0
  71. package/fhevm-hardhat-template/hardhat.config.ts +90 -0
  72. package/fhevm-hardhat-template/package-lock.json +10405 -0
  73. package/fhevm-hardhat-template/package.json +104 -0
  74. package/fhevm-hardhat-template/tasks/FHECounter.ts +184 -0
  75. package/fhevm-hardhat-template/tasks/accounts.ts +9 -0
  76. package/fhevm-hardhat-template/test/FHECounter.ts +104 -0
  77. package/fhevm-hardhat-template/test/FHECounterSepolia.ts +104 -0
  78. package/fhevm-hardhat-template/tsconfig.json +23 -0
  79. package/package.json +13 -10
  80. package/test/advanced/BlindAuction.ts +246 -0
  81. package/test/advanced/EncryptedEscrow.ts +295 -0
  82. package/test/advanced/HiddenVoting.ts +268 -0
  83. package/test/advanced/PrivateKYC.ts +382 -0
  84. package/test/advanced/PrivatePayroll.ts +253 -0
  85. package/test/basic/decryption/PublicDecryptMultipleValues.ts +254 -0
  86. package/test/basic/decryption/PublicDecryptSingleValue.ts +264 -0
  87. package/test/basic/decryption/UserDecryptMultipleValues.ts +107 -0
  88. package/test/basic/decryption/UserDecryptSingleValue.ts +97 -0
  89. package/test/basic/encryption/EncryptMultipleValues.ts +110 -0
  90. package/test/basic/encryption/EncryptSingleValue.ts +124 -0
  91. package/test/basic/encryption/FHECounter.ts +112 -0
  92. package/test/basic/fhe-operations/FHEAdd.ts +97 -0
  93. package/test/basic/fhe-operations/FHEArithmetic.ts +161 -0
  94. package/test/basic/fhe-operations/FHEComparison.ts +167 -0
  95. package/test/basic/fhe-operations/FHEIfThenElse.ts +97 -0
  96. package/test/concepts/FHEAccessControl.ts +154 -0
  97. package/test/concepts/FHEAntiPatterns.ts +111 -0
  98. package/test/concepts/FHEHandles.ts +156 -0
  99. package/test/concepts/FHEInputProof.ts +151 -0
  100. package/test/gaming/EncryptedLottery.ts +214 -0
  101. package/test/gaming/EncryptedPoker.ts +349 -0
  102. package/test/gaming/RockPaperScissors.ts +205 -0
  103. package/test/openzeppelin/ERC7984.ts +142 -0
  104. package/test/openzeppelin/ERC7984ERC20Wrapper.ts +71 -0
  105. package/test/openzeppelin/SwapERC7984ToERC20.ts +76 -0
  106. package/test/openzeppelin/SwapERC7984ToERC7984.ts +113 -0
  107. package/test/openzeppelin/VestingWallet.ts +89 -0
  108. package/dist/scripts/add-mode.d.ts.map +0 -1
  109. package/dist/scripts/builders.d.ts.map +0 -1
  110. package/dist/scripts/config.d.ts.map +0 -1
  111. package/dist/scripts/doctor.d.ts.map +0 -1
  112. package/dist/scripts/generate-config.d.ts.map +0 -1
  113. package/dist/scripts/generate-docs.d.ts.map +0 -1
  114. package/dist/scripts/maintenance.d.ts.map +0 -1
  115. package/dist/scripts/ui.d.ts.map +0 -1
  116. package/dist/scripts/utils.d.ts.map +0 -1
  117. /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
  118. /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
  119. /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
  120. /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
  121. /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
  122. /package/dist/scripts/{ui.d.ts → shared/ui.d.ts} +0 -0
@@ -0,0 +1,255 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint64,
7
+ ebool,
8
+ externalEuint64
9
+ } from "@fhevm/solidity/lib/FHE.sol";
10
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
+
12
+ /**
13
+ * @notice Blind Auction with encrypted bids - only the winning price is revealed
14
+ *
15
+ * @dev Demonstrates advanced FHE patterns:
16
+ * - Encrypted bid storage and comparison
17
+ * - FHE.gt() and FHE.select() for finding maximum
18
+ * - FHE.makePubliclyDecryptable() for revealing results
19
+ * - FHE.checkSignatures() for proof verification
20
+ *
21
+ * Flow:
22
+ * 1. Owner creates auction with end time and minimum bid
23
+ * 2. Bidders submit encrypted bids (one per address)
24
+ * 3. Owner ends auction → winner computed via FHE.gt/select
25
+ * 4. Anyone can reveal winner after decryption proof is ready
26
+ *
27
+ * ⚠️ IMPORTANT: Losing bids remain encrypted forever!
28
+ */
29
+ contract BlindAuction is ZamaEthereumConfig {
30
+ // ==================== TYPES ====================
31
+
32
+ enum AuctionState {
33
+ Open, // Accepting bids
34
+ Closed, // Bidding ended, pending reveal
35
+ Revealed // Winner revealed on-chain
36
+ }
37
+
38
+ // ==================== STATE ====================
39
+
40
+ /// Auction owner (deployer)
41
+ address public owner;
42
+
43
+ /// Current auction state
44
+ AuctionState public auctionState;
45
+
46
+ /// Minimum bid in plaintext (for gas efficiency)
47
+ uint64 public minimumBid;
48
+
49
+ /// Auction end timestamp
50
+ uint256 public endTime;
51
+
52
+ /// All bidder addresses (for iteration)
53
+ address[] public bidders;
54
+
55
+ /// Mapping from bidder address to their encrypted bid
56
+ mapping(address => euint64) private _bids;
57
+
58
+ /// Whether an address has bid
59
+ mapping(address => bool) public hasBid;
60
+
61
+ /// Encrypted winning bid amount (set after auction ends)
62
+ euint64 private _winningBid;
63
+
64
+ /// Encrypted winner index in bidders array
65
+ euint64 private _winnerIndex;
66
+
67
+ /// Address of the winner (set after reveal)
68
+ address public winner;
69
+
70
+ /// Revealed winning amount (set after reveal)
71
+ uint64 public winningAmount;
72
+
73
+ // ==================== EVENTS ====================
74
+
75
+ /// @notice Emitted when a new bid is placed
76
+ /// @param bidder Address of the bidder
77
+ event BidPlaced(address indexed bidder);
78
+
79
+ /// @notice Emitted when auction is closed
80
+ /// @param encryptedWinningBid Handle for encrypted winning bid
81
+ /// @param encryptedWinnerIndex Handle for encrypted winner index
82
+ event AuctionEnded(
83
+ euint64 encryptedWinningBid,
84
+ euint64 encryptedWinnerIndex
85
+ );
86
+
87
+ /// @notice Emitted when winner is revealed
88
+ /// @param winner Address of the winner
89
+ /// @param amount Winning bid amount
90
+ event WinnerRevealed(address indexed winner, uint64 amount);
91
+
92
+ // ==================== MODIFIERS ====================
93
+
94
+ /// @dev Restricts function to owner only
95
+ modifier onlyOwner() {
96
+ require(msg.sender == owner, "Only owner can call");
97
+ _;
98
+ }
99
+
100
+ // ==================== CONSTRUCTOR ====================
101
+
102
+ /// @notice Creates a new blind auction
103
+ /// @param _endTime Unix timestamp when bidding ends
104
+ /// @param _minimumBid Minimum bid amount (plaintext)
105
+ constructor(uint256 _endTime, uint64 _minimumBid) {
106
+ require(_endTime > block.timestamp, "End time must be in future");
107
+ owner = msg.sender;
108
+ endTime = _endTime;
109
+ minimumBid = _minimumBid;
110
+ auctionState = AuctionState.Open;
111
+ }
112
+
113
+ // ==================== BIDDING ====================
114
+
115
+ /// @notice Submit an encrypted bid to the auction
116
+ /// @dev Each address can only bid once
117
+ /// @param encryptedBid The encrypted bid amount
118
+ /// @param inputProof Proof validating the encrypted input
119
+ function bid(
120
+ externalEuint64 encryptedBid,
121
+ bytes calldata inputProof
122
+ ) external {
123
+ require(auctionState == AuctionState.Open, "Auction not open");
124
+ require(block.timestamp < endTime, "Auction has ended");
125
+ require(!hasBid[msg.sender], "Already placed a bid");
126
+
127
+ // 🔐 Convert external encrypted input to internal euint64
128
+ euint64 bidAmount = FHE.fromExternal(encryptedBid, inputProof);
129
+
130
+ // ✅ Grant contract permission to operate on this value
131
+ FHE.allowThis(bidAmount);
132
+
133
+ // 📋 Store the bid
134
+ _bids[msg.sender] = bidAmount;
135
+ hasBid[msg.sender] = true;
136
+ bidders.push(msg.sender);
137
+
138
+ emit BidPlaced(msg.sender);
139
+ }
140
+
141
+ // ==================== END AUCTION ====================
142
+
143
+ /// @notice End the auction and compute the winner
144
+ /// @dev Only owner can call after end time
145
+ function endAuction() external onlyOwner {
146
+ require(auctionState == AuctionState.Open, "Auction not open");
147
+ require(block.timestamp >= endTime, "Auction not yet ended");
148
+ require(bidders.length > 0, "No bids placed");
149
+
150
+ // 🏆 Find the winning bid using encrypted comparisons
151
+
152
+ // Initialize with first bidder
153
+ euint64 currentMax = _bids[bidders[0]];
154
+ euint64 currentWinnerIdx = FHE.asEuint64(0);
155
+
156
+ // 🔄 Iterate through remaining bidders
157
+ for (uint256 i = 1; i < bidders.length; i++) {
158
+ euint64 candidateBid = _bids[bidders[i]];
159
+
160
+ // 🔍 Compare: is candidate > currentMax?
161
+ // Note: If equal, first bidder wins (no update)
162
+ ebool isGreater = FHE.gt(candidateBid, currentMax);
163
+
164
+ // 🔀 Select the higher bid and its index
165
+ currentMax = FHE.select(isGreater, candidateBid, currentMax);
166
+ currentWinnerIdx = FHE.select(
167
+ isGreater,
168
+ FHE.asEuint64(uint64(i)),
169
+ currentWinnerIdx
170
+ );
171
+ }
172
+
173
+ // 📊 Check minimum bid threshold
174
+ ebool meetsMinimum = FHE.ge(currentMax, FHE.asEuint64(minimumBid));
175
+
176
+ // If no one meets minimum, winner stays at index 0 but amount becomes 0
177
+ _winningBid = FHE.select(meetsMinimum, currentMax, FHE.asEuint64(0));
178
+ _winnerIndex = currentWinnerIdx;
179
+
180
+ // 🔓 Make results publicly decryptable
181
+ FHE.allowThis(_winningBid);
182
+ FHE.allowThis(_winnerIndex);
183
+ FHE.makePubliclyDecryptable(_winningBid);
184
+ FHE.makePubliclyDecryptable(_winnerIndex);
185
+
186
+ auctionState = AuctionState.Closed;
187
+
188
+ emit AuctionEnded(_winningBid, _winnerIndex);
189
+ }
190
+
191
+ /// @notice Reveal the winner with KMS decryption proof
192
+ /// @dev Anyone can call with valid decryption proof
193
+ /// @param abiEncodedResults ABI-encoded (uint64 amount, uint64 index)
194
+ /// @param decryptionProof KMS signature proving decryption
195
+ function revealWinner(
196
+ bytes memory abiEncodedResults,
197
+ bytes memory decryptionProof
198
+ ) external {
199
+ require(auctionState == AuctionState.Closed, "Auction not closed");
200
+
201
+ // 🔐 Build ciphertext list for verification
202
+ // Order MUST match abiEncodedResults encoding order!
203
+ bytes32[] memory cts = new bytes32[](2);
204
+ cts[0] = FHE.toBytes32(_winningBid);
205
+ cts[1] = FHE.toBytes32(_winnerIndex);
206
+
207
+ // 🔍 Verify the decryption proof (reverts if invalid)
208
+ FHE.checkSignatures(cts, abiEncodedResults, decryptionProof);
209
+
210
+ // 📤 Decode the verified results
211
+ (uint64 revealedAmount, uint64 winnerIdx) = abi.decode(
212
+ abiEncodedResults,
213
+ (uint64, uint64)
214
+ );
215
+
216
+ // 🏆 Look up winner address
217
+ if (revealedAmount >= minimumBid && winnerIdx < bidders.length) {
218
+ winner = bidders[winnerIdx];
219
+ winningAmount = revealedAmount;
220
+ } else {
221
+ // No valid winner (all bids below minimum)
222
+ winner = address(0);
223
+ winningAmount = 0;
224
+ }
225
+
226
+ auctionState = AuctionState.Revealed;
227
+
228
+ emit WinnerRevealed(winner, winningAmount);
229
+ }
230
+
231
+ // ==================== VIEW FUNCTIONS ====================
232
+
233
+ /// @notice Get the number of bidders
234
+ function getBidderCount() external view returns (uint256) {
235
+ return bidders.length;
236
+ }
237
+
238
+ /// @notice Get bidder address by index
239
+ function getBidder(uint256 index) external view returns (address) {
240
+ require(index < bidders.length, "Index out of bounds");
241
+ return bidders[index];
242
+ }
243
+
244
+ /// @notice Get encrypted winning bid handle (after auction ends)
245
+ function getEncryptedWinningBid() external view returns (euint64) {
246
+ require(auctionState != AuctionState.Open, "Auction still open");
247
+ return _winningBid;
248
+ }
249
+
250
+ /// @notice Get encrypted winner index handle (after auction ends)
251
+ function getEncryptedWinnerIndex() external view returns (euint64) {
252
+ require(auctionState != AuctionState.Open, "Auction still open");
253
+ return _winnerIndex;
254
+ }
255
+ }
@@ -0,0 +1,315 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint64,
7
+ ebool,
8
+ externalEuint64
9
+ } from "@fhevm/solidity/lib/FHE.sol";
10
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
+
12
+ /**
13
+ * @notice Encrypted Escrow service - amounts hidden until release!
14
+ *
15
+ * @dev Demonstrates secure escrow with FHE:
16
+ * - Escrow amounts remain encrypted
17
+ * - Conditions verified without revealing values
18
+ * - Multi-party agreement pattern
19
+ * - Dispute resolution with arbiter
20
+ *
21
+ * Flow:
22
+ * 1. Buyer creates escrow with encrypted amount
23
+ * 2. Buyer deposits funds
24
+ * 3. Seller delivers goods/services
25
+ * 4. Buyer releases funds OR disputes
26
+ * 5. Arbiter resolves disputes if needed
27
+ *
28
+ * ⚠️ IMPORTANT: Amount revealed only on release/refund
29
+ */
30
+ contract EncryptedEscrow is ZamaEthereumConfig {
31
+ // ==================== TYPES ====================
32
+
33
+ enum EscrowState {
34
+ Created, // Escrow created, awaiting deposit
35
+ Funded, // Funds deposited
36
+ Released, // Funds released to seller
37
+ Refunded, // Funds returned to buyer
38
+ Disputed // Under dispute resolution
39
+ }
40
+
41
+ struct Escrow {
42
+ address buyer;
43
+ address seller;
44
+ address arbiter;
45
+ euint64 encryptedAmount;
46
+ uint256 depositedAmount;
47
+ EscrowState state;
48
+ uint256 createdAt;
49
+ uint256 deadline;
50
+ }
51
+
52
+ // ==================== STATE ====================
53
+
54
+ /// Contract owner
55
+ address public owner;
56
+
57
+ /// Escrow ID counter
58
+ uint256 public escrowCount;
59
+
60
+ /// Mapping from escrow ID to Escrow data
61
+ mapping(uint256 => Escrow) private _escrows;
62
+
63
+ /// Arbiter fee percentage (default 1%)
64
+ uint256 public arbiterFeePercent;
65
+
66
+ // ==================== EVENTS ====================
67
+
68
+ /// @notice Emitted when escrow is created
69
+ /// @param escrowId ID of the escrow
70
+ /// @param buyer Address of buyer
71
+ /// @param seller Address of seller
72
+ event EscrowCreated(
73
+ uint256 indexed escrowId,
74
+ address indexed buyer,
75
+ address indexed seller
76
+ );
77
+
78
+ /// @notice Emitted when escrow is funded
79
+ /// @param escrowId ID of the escrow
80
+ /// @param amount Amount deposited
81
+ event EscrowFunded(uint256 indexed escrowId, uint256 amount);
82
+
83
+ /// @notice Emitted when funds are released
84
+ /// @param escrowId ID of the escrow
85
+ /// @param recipient Address receiving funds
86
+ event FundsReleased(uint256 indexed escrowId, address indexed recipient);
87
+
88
+ /// @notice Emitted when funds are refunded
89
+ /// @param escrowId ID of the escrow
90
+ /// @param recipient Address receiving refund
91
+ event FundsRefunded(uint256 indexed escrowId, address indexed recipient);
92
+
93
+ /// @notice Emitted when dispute is raised
94
+ /// @param escrowId ID of the escrow
95
+ /// @param raisedBy Address raising dispute
96
+ event DisputeRaised(uint256 indexed escrowId, address indexed raisedBy);
97
+
98
+ /// @notice Emitted when dispute is resolved
99
+ /// @param escrowId ID of the escrow
100
+ /// @param winner Address favored in resolution
101
+ event DisputeResolved(uint256 indexed escrowId, address indexed winner);
102
+
103
+ // ==================== CONSTRUCTOR ====================
104
+
105
+ /// @notice Creates the escrow contract
106
+ /// @param _arbiterFeePercent Fee percentage for arbiter (0-10)
107
+ constructor(uint256 _arbiterFeePercent) {
108
+ require(_arbiterFeePercent <= 10, "Fee too high");
109
+ owner = msg.sender;
110
+ arbiterFeePercent = _arbiterFeePercent;
111
+ }
112
+
113
+ // ==================== ESCROW CREATION ====================
114
+
115
+ /// @notice Create a new escrow with encrypted amount
116
+ /// @param seller Address of the seller
117
+ /// @param arbiter Address of dispute arbiter
118
+ /// @param encryptedAmount Encrypted escrow amount
119
+ /// @param inputProof Proof validating the encrypted input
120
+ /// @param deadline Deadline timestamp for delivery
121
+ /// @return escrowId The ID of created escrow
122
+ function createEscrow(
123
+ address seller,
124
+ address arbiter,
125
+ externalEuint64 encryptedAmount,
126
+ bytes calldata inputProof,
127
+ uint256 deadline
128
+ ) external returns (uint256 escrowId) {
129
+ require(seller != address(0), "Invalid seller");
130
+ require(seller != msg.sender, "Buyer cannot be seller");
131
+ require(arbiter != address(0), "Invalid arbiter");
132
+ require(arbiter != msg.sender && arbiter != seller, "Invalid arbiter");
133
+ require(deadline > block.timestamp, "Deadline must be future");
134
+
135
+ escrowId = ++escrowCount;
136
+
137
+ // 🔐 Convert external encrypted input
138
+ euint64 amount = FHE.fromExternal(encryptedAmount, inputProof);
139
+
140
+ // ✅ Grant permissions
141
+ FHE.allowThis(amount);
142
+ FHE.allow(amount, msg.sender); // Buyer can view
143
+ FHE.allow(amount, seller); // Seller can view
144
+ FHE.allow(amount, arbiter); // Arbiter can view
145
+
146
+ _escrows[escrowId] = Escrow({
147
+ buyer: msg.sender,
148
+ seller: seller,
149
+ arbiter: arbiter,
150
+ encryptedAmount: amount,
151
+ depositedAmount: 0,
152
+ state: EscrowState.Created,
153
+ createdAt: block.timestamp,
154
+ deadline: deadline
155
+ });
156
+
157
+ emit EscrowCreated(escrowId, msg.sender, seller);
158
+ }
159
+
160
+ /// @notice Fund the escrow
161
+ /// @dev Amount verified against encrypted value on release
162
+ /// @param escrowId ID of escrow to fund
163
+ function fundEscrow(uint256 escrowId) external payable {
164
+ Escrow storage escrow = _escrows[escrowId];
165
+ require(escrow.buyer == msg.sender, "Only buyer can fund");
166
+ require(escrow.state == EscrowState.Created, "Invalid state");
167
+ require(msg.value > 0, "Must send funds");
168
+
169
+ escrow.depositedAmount = msg.value;
170
+ escrow.state = EscrowState.Funded;
171
+
172
+ emit EscrowFunded(escrowId, msg.value);
173
+ }
174
+
175
+ // ==================== RELEASE / REFUND ====================
176
+
177
+ /// @notice Release funds to seller
178
+ /// @dev Only buyer can release after delivery
179
+ /// @param escrowId ID of escrow to release
180
+ function release(uint256 escrowId) external {
181
+ Escrow storage escrow = _escrows[escrowId];
182
+ require(escrow.buyer == msg.sender, "Only buyer can release");
183
+ require(escrow.state == EscrowState.Funded, "Not funded");
184
+
185
+ uint256 amount = escrow.depositedAmount;
186
+ escrow.depositedAmount = 0;
187
+ escrow.state = EscrowState.Released;
188
+
189
+ // Transfer to seller
190
+ (bool sent, ) = escrow.seller.call{value: amount}("");
191
+ require(sent, "Transfer failed");
192
+
193
+ emit FundsReleased(escrowId, escrow.seller);
194
+ }
195
+
196
+ /// @notice Request refund (before deadline or after timeout)
197
+ /// @param escrowId ID of escrow
198
+ function requestRefund(uint256 escrowId) external {
199
+ Escrow storage escrow = _escrows[escrowId];
200
+ require(escrow.buyer == msg.sender, "Only buyer");
201
+ require(escrow.state == EscrowState.Funded, "Not funded");
202
+ require(block.timestamp > escrow.deadline, "Deadline not passed");
203
+
204
+ uint256 amount = escrow.depositedAmount;
205
+ escrow.depositedAmount = 0;
206
+ escrow.state = EscrowState.Refunded;
207
+
208
+ (bool sent, ) = escrow.buyer.call{value: amount}("");
209
+ require(sent, "Transfer failed");
210
+
211
+ emit FundsRefunded(escrowId, escrow.buyer);
212
+ }
213
+
214
+ // ==================== DISPUTE RESOLUTION ====================
215
+
216
+ /// @notice Raise a dispute
217
+ /// @param escrowId ID of escrow
218
+ function raiseDispute(uint256 escrowId) external {
219
+ Escrow storage escrow = _escrows[escrowId];
220
+ require(
221
+ escrow.buyer == msg.sender || escrow.seller == msg.sender,
222
+ "Not a party"
223
+ );
224
+ require(escrow.state == EscrowState.Funded, "Not funded");
225
+
226
+ escrow.state = EscrowState.Disputed;
227
+
228
+ emit DisputeRaised(escrowId, msg.sender);
229
+ }
230
+
231
+ /// @notice Resolve dispute - arbiter decides winner
232
+ /// @param escrowId ID of escrow
233
+ /// @param favorBuyer True to refund buyer, false to release to seller
234
+ function resolveDispute(uint256 escrowId, bool favorBuyer) external {
235
+ Escrow storage escrow = _escrows[escrowId];
236
+ require(escrow.arbiter == msg.sender, "Only arbiter");
237
+ require(escrow.state == EscrowState.Disputed, "Not disputed");
238
+
239
+ uint256 amount = escrow.depositedAmount;
240
+ uint256 arbiterFee = (amount * arbiterFeePercent) / 100;
241
+ uint256 payout = amount - arbiterFee;
242
+
243
+ escrow.depositedAmount = 0;
244
+
245
+ address winner;
246
+ if (favorBuyer) {
247
+ escrow.state = EscrowState.Refunded;
248
+ winner = escrow.buyer;
249
+ } else {
250
+ escrow.state = EscrowState.Released;
251
+ winner = escrow.seller;
252
+ }
253
+
254
+ // Pay arbiter fee
255
+ if (arbiterFee > 0) {
256
+ (bool feeSent, ) = escrow.arbiter.call{value: arbiterFee}("");
257
+ require(feeSent, "Arbiter fee failed");
258
+ }
259
+
260
+ // Pay winner
261
+ (bool sent, ) = winner.call{value: payout}("");
262
+ require(sent, "Payout failed");
263
+
264
+ emit DisputeResolved(escrowId, winner);
265
+ }
266
+
267
+ // ==================== VIEW FUNCTIONS ====================
268
+
269
+ /// @notice Get escrow details
270
+ function getEscrow(
271
+ uint256 escrowId
272
+ )
273
+ external
274
+ view
275
+ returns (
276
+ address buyer,
277
+ address seller,
278
+ address arbiter,
279
+ uint256 depositedAmount,
280
+ EscrowState state,
281
+ uint256 createdAt,
282
+ uint256 deadline
283
+ )
284
+ {
285
+ Escrow storage escrow = _escrows[escrowId];
286
+ return (
287
+ escrow.buyer,
288
+ escrow.seller,
289
+ escrow.arbiter,
290
+ escrow.depositedAmount,
291
+ escrow.state,
292
+ escrow.createdAt,
293
+ escrow.deadline
294
+ );
295
+ }
296
+
297
+ /// @notice Get encrypted amount handle (for permitted parties)
298
+ function getEncryptedAmount(
299
+ uint256 escrowId
300
+ ) external view returns (euint64) {
301
+ Escrow storage escrow = _escrows[escrowId];
302
+ require(
303
+ msg.sender == escrow.buyer ||
304
+ msg.sender == escrow.seller ||
305
+ msg.sender == escrow.arbiter,
306
+ "Not authorized"
307
+ );
308
+ return escrow.encryptedAmount;
309
+ }
310
+
311
+ /// @notice Check if deadline has passed
312
+ function isDeadlinePassed(uint256 escrowId) external view returns (bool) {
313
+ return block.timestamp > _escrows[escrowId].deadline;
314
+ }
315
+ }