create-fhevm-example 1.3.2 → 1.4.1

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 (125) 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 +14 -33
  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 +236 -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 +11 -8
  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/help.d.ts +0 -9
  115. package/dist/scripts/help.d.ts.map +0 -1
  116. package/dist/scripts/help.js +0 -73
  117. package/dist/scripts/maintenance.d.ts.map +0 -1
  118. package/dist/scripts/ui.d.ts.map +0 -1
  119. package/dist/scripts/utils.d.ts.map +0 -1
  120. /package/dist/scripts/{add-mode.d.ts → commands/add-mode.d.ts} +0 -0
  121. /package/dist/scripts/{doctor.d.ts → commands/doctor.d.ts} +0 -0
  122. /package/dist/scripts/{generate-config.d.ts → commands/generate-config.d.ts} +0 -0
  123. /package/dist/scripts/{generate-docs.d.ts → commands/generate-docs.d.ts} +0 -0
  124. /package/dist/scripts/{maintenance.d.ts → commands/maintenance.d.ts} +0 -0
  125. /package/dist/scripts/{ui.d.ts → shared/ui.d.ts} +0 -0
@@ -0,0 +1,298 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint64,
7
+ euint8,
8
+ ebool,
9
+ externalEuint64
10
+ } from "@fhevm/solidity/lib/FHE.sol";
11
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
12
+
13
+ /**
14
+ * @notice Encrypted Lottery with private ticket numbers - fair and verifiable!
15
+ *
16
+ * @dev Demonstrates FHE random selection and encrypted matching:
17
+ * - Players purchase tickets with encrypted numbers
18
+ * - Winning number generated using block randomness + FHE
19
+ * - Winners identified via FHE.eq without revealing ticket numbers
20
+ * - Only winner's ticket revealed at end
21
+ *
22
+ * Flow:
23
+ * 1. Lottery created with ticket price and duration
24
+ * 2. Players buy tickets with encrypted numbers
25
+ * 3. Owner draws winning number at end
26
+ * 4. Winners can claim prizes
27
+ *
28
+ * ⚠️ IMPORTANT: Block randomness is predictable - use VRF in production!
29
+ */
30
+ contract EncryptedLottery is ZamaEthereumConfig {
31
+ // ==================== TYPES ====================
32
+
33
+ enum LotteryState {
34
+ Open, // Accepting tickets
35
+ Drawing, // Drawing in progress
36
+ Completed // Winner revealed
37
+ }
38
+
39
+ struct Ticket {
40
+ address owner;
41
+ euint64 number;
42
+ }
43
+
44
+ // ==================== STATE ====================
45
+
46
+ /// Lottery owner
47
+ address public owner;
48
+
49
+ /// Current lottery state
50
+ LotteryState public state;
51
+
52
+ /// Ticket price in wei
53
+ uint256 public ticketPrice;
54
+
55
+ /// Lottery end time
56
+ uint256 public endTime;
57
+
58
+ /// All tickets
59
+ Ticket[] private _tickets;
60
+
61
+ /// Mapping from address to ticket indices
62
+ mapping(address => uint256[]) private _playerTickets;
63
+
64
+ /// Encrypted winning number
65
+ euint64 private _winningNumber;
66
+
67
+ /// Winner address (if found)
68
+ address public winner;
69
+
70
+ /// Prize pool
71
+ uint256 public prizePool;
72
+
73
+ /// Lottery round number
74
+ uint256 public roundNumber;
75
+
76
+ // ==================== EVENTS ====================
77
+
78
+ /// @notice Emitted when a ticket is purchased
79
+ /// @param buyer Address of ticket buyer
80
+ /// @param ticketIndex Index of the ticket
81
+ event TicketPurchased(address indexed buyer, uint256 indexed ticketIndex);
82
+
83
+ /// @notice Emitted when drawing starts
84
+ /// @param roundNumber Current round
85
+ event DrawingStarted(uint256 indexed roundNumber);
86
+
87
+ /// @notice Emitted when winner is found
88
+ /// @param winner Address of winner
89
+ /// @param prize Amount won
90
+ event WinnerFound(address indexed winner, uint256 prize);
91
+
92
+ /// @notice Emitted when no winner found
93
+ /// @param roundNumber Current round
94
+ /// @param rollover Amount rolled to next round
95
+ event NoWinner(uint256 indexed roundNumber, uint256 rollover);
96
+
97
+ // ==================== MODIFIERS ====================
98
+
99
+ modifier onlyOwner() {
100
+ require(msg.sender == owner, "Only owner");
101
+ _;
102
+ }
103
+
104
+ // ==================== CONSTRUCTOR ====================
105
+
106
+ /// @notice Creates a new encrypted lottery
107
+ /// @param _ticketPrice Price per ticket in wei
108
+ /// @param _duration Duration in seconds
109
+ constructor(uint256 _ticketPrice, uint256 _duration) {
110
+ require(_ticketPrice > 0, "Ticket price must be > 0");
111
+ require(_duration > 0, "Duration must be > 0");
112
+
113
+ owner = msg.sender;
114
+ ticketPrice = _ticketPrice;
115
+ endTime = block.timestamp + _duration;
116
+ state = LotteryState.Open;
117
+ roundNumber = 1;
118
+ }
119
+
120
+ // ==================== TICKET PURCHASE ====================
121
+
122
+ /// @notice Purchase a lottery ticket with an encrypted number
123
+ /// @param encryptedNumber Your encrypted ticket number
124
+ /// @param inputProof Proof validating the encrypted input
125
+ function buyTicket(
126
+ externalEuint64 encryptedNumber,
127
+ bytes calldata inputProof
128
+ ) external payable {
129
+ require(state == LotteryState.Open, "Lottery not open");
130
+ require(block.timestamp < endTime, "Lottery ended");
131
+ require(msg.value >= ticketPrice, "Insufficient payment");
132
+
133
+ // 🔐 Convert external encrypted input
134
+ euint64 ticketNumber = FHE.fromExternal(encryptedNumber, inputProof);
135
+
136
+ // ✅ Grant contract permission
137
+ FHE.allowThis(ticketNumber);
138
+
139
+ // 📋 Store ticket
140
+ uint256 ticketIndex = _tickets.length;
141
+ _tickets.push(Ticket({owner: msg.sender, number: ticketNumber}));
142
+
143
+ _playerTickets[msg.sender].push(ticketIndex);
144
+ prizePool += msg.value;
145
+
146
+ emit TicketPurchased(msg.sender, ticketIndex);
147
+ }
148
+
149
+ // ==================== DRAWING ====================
150
+
151
+ /// @notice Start the drawing process
152
+ /// @dev Only owner can call after lottery ends
153
+ function startDrawing() external onlyOwner {
154
+ require(state == LotteryState.Open, "Wrong state");
155
+ require(block.timestamp >= endTime, "Lottery not ended");
156
+ require(_tickets.length > 0, "No tickets sold");
157
+
158
+ // 🎲 Generate "random" winning number using block data
159
+ // ⚠️ WARNING: This is predictable! Use Chainlink VRF in production
160
+ uint64 randomSeed = uint64(
161
+ uint256(
162
+ keccak256(
163
+ abi.encodePacked(
164
+ block.prevrandao,
165
+ block.timestamp,
166
+ _tickets.length,
167
+ msg.sender
168
+ )
169
+ )
170
+ )
171
+ );
172
+
173
+ // 🔐 Encrypt the winning number
174
+ _winningNumber = FHE.asEuint64(randomSeed);
175
+ FHE.allowThis(_winningNumber);
176
+
177
+ state = LotteryState.Drawing;
178
+
179
+ emit DrawingStarted(roundNumber);
180
+ }
181
+
182
+ /// @notice Check if a ticket is a winner and claim prize
183
+ /// @param ticketIndex Index of the ticket to check
184
+ function checkAndClaim(uint256 ticketIndex) external {
185
+ require(state == LotteryState.Drawing, "Not in drawing phase");
186
+ require(ticketIndex < _tickets.length, "Invalid ticket");
187
+ require(_tickets[ticketIndex].owner == msg.sender, "Not your ticket");
188
+
189
+ // 🔍 Check if ticket number matches winning number
190
+ // This comparison happens in encrypted space!
191
+ ebool isWinner = FHE.eq(_tickets[ticketIndex].number, _winningNumber);
192
+
193
+ // 🔓 Make result publicly decryptable
194
+ FHE.allowThis(isWinner);
195
+ FHE.makePubliclyDecryptable(isWinner);
196
+
197
+ // Store for later reveal
198
+ // In production, use callback pattern
199
+ }
200
+
201
+ /// @notice Reveal winner with decryption proof
202
+ /// @param ticketIndex Ticket being checked
203
+ /// @param abiEncodedResult ABI-encoded bool result
204
+ /// @param decryptionProof KMS signature proving decryption
205
+ function revealWinner(
206
+ uint256 ticketIndex,
207
+ bytes memory abiEncodedResult,
208
+ bytes memory decryptionProof
209
+ ) external {
210
+ require(state == LotteryState.Drawing, "Not in drawing phase");
211
+ require(ticketIndex < _tickets.length, "Invalid ticket");
212
+
213
+ // Rebuild the comparison for verification
214
+ ebool isWinner = FHE.eq(_tickets[ticketIndex].number, _winningNumber);
215
+
216
+ bytes32[] memory cts = new bytes32[](1);
217
+ cts[0] = FHE.toBytes32(isWinner);
218
+
219
+ // Verify decryption proof
220
+ FHE.checkSignatures(cts, abiEncodedResult, decryptionProof);
221
+
222
+ bool won = abi.decode(abiEncodedResult, (bool));
223
+
224
+ if (won) {
225
+ winner = _tickets[ticketIndex].owner;
226
+ state = LotteryState.Completed;
227
+
228
+ uint256 prize = prizePool;
229
+ prizePool = 0;
230
+
231
+ // Transfer prize
232
+ (bool sent, ) = winner.call{value: prize}("");
233
+ require(sent, "Prize transfer failed");
234
+
235
+ emit WinnerFound(winner, prize);
236
+ }
237
+ }
238
+
239
+ /// @notice End drawing with no winner (rollover)
240
+ /// @dev Called if all tickets checked with no match
241
+ function endDrawingNoWinner() external onlyOwner {
242
+ require(state == LotteryState.Drawing, "Not in drawing phase");
243
+
244
+ state = LotteryState.Completed;
245
+
246
+ emit NoWinner(roundNumber, prizePool);
247
+ }
248
+
249
+ /// @notice Start a new lottery round
250
+ /// @param _duration Duration for next round
251
+ function startNewRound(uint256 _duration) external onlyOwner {
252
+ require(state == LotteryState.Completed, "Current round not complete");
253
+ require(_duration > 0, "Duration must be > 0");
254
+
255
+ // Reset for new round
256
+ delete _tickets;
257
+ winner = address(0);
258
+ endTime = block.timestamp + _duration;
259
+ state = LotteryState.Open;
260
+ roundNumber++;
261
+ // prizePool carries over if no winner
262
+ }
263
+
264
+ // ==================== VIEW FUNCTIONS ====================
265
+
266
+ /// @notice Get total number of tickets sold
267
+ function getTicketCount() external view returns (uint256) {
268
+ return _tickets.length;
269
+ }
270
+
271
+ /// @notice Get ticket indices for a player
272
+ function getPlayerTickets(
273
+ address player
274
+ ) external view returns (uint256[] memory) {
275
+ return _playerTickets[player];
276
+ }
277
+
278
+ /// @notice Check time remaining
279
+ function timeRemaining() external view returns (uint256) {
280
+ if (block.timestamp >= endTime) return 0;
281
+ return endTime - block.timestamp;
282
+ }
283
+
284
+ /// @notice Get lottery info
285
+ function getLotteryInfo()
286
+ external
287
+ view
288
+ returns (
289
+ LotteryState currentState,
290
+ uint256 currentPrizePool,
291
+ uint256 currentEndTime,
292
+ uint256 currentRound,
293
+ uint256 totalTickets
294
+ )
295
+ {
296
+ return (state, prizePool, endTime, roundNumber, _tickets.length);
297
+ }
298
+ }
@@ -0,0 +1,337 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint8,
7
+ euint16,
8
+ ebool,
9
+ externalEuint8
10
+ } from "@fhevm/solidity/lib/FHE.sol";
11
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
12
+
13
+ /**
14
+ * @notice Encrypted Poker - Texas Hold'em with hidden hole cards!
15
+ *
16
+ * @dev Demonstrates multi-player FHE game mechanics:
17
+ * - Each player's hole cards remain encrypted until showdown
18
+ * - Hand strength computed via FHE without revealing cards
19
+ * - Winner determined by encrypted comparison
20
+ * - Only winning hand revealed, loser's cards stay private
21
+ *
22
+ * Simplified rules for demo:
23
+ * - 2 players (heads-up)
24
+ * - Cards encoded as 1-13 (Ace=1, King=13)
25
+ * - Hand strength = card1 + card2 (simplified for demo)
26
+ * - Higher total wins
27
+ *
28
+ * ⚠️ IMPORTANT: Production poker would need proper hand rankings!
29
+ */
30
+ contract EncryptedPoker is ZamaEthereumConfig {
31
+ // ==================== TYPES ====================
32
+
33
+ enum GameState {
34
+ WaitingForPlayers, // Waiting for 2 players
35
+ CardsDealt, // Both players have cards
36
+ BettingRound, // Players can bet
37
+ Showdown, // Comparing hands
38
+ Finished // Winner determined
39
+ }
40
+
41
+ struct Player {
42
+ address addr;
43
+ euint8 card1;
44
+ euint8 card2;
45
+ euint16 handStrength; // card1 + card2 for simplified ranking
46
+ uint256 bet;
47
+ bool folded;
48
+ }
49
+
50
+ // ==================== STATE ====================
51
+
52
+ /// Game state
53
+ GameState public state;
54
+
55
+ /// Players (0 and 1)
56
+ Player[2] private _players;
57
+
58
+ /// Current pot
59
+ uint256 public pot;
60
+
61
+ /// Minimum bet
62
+ uint256 public minBet;
63
+
64
+ /// Winner address (set after showdown)
65
+ address public winner;
66
+
67
+ /// Game ID
68
+ uint256 public gameId;
69
+
70
+ /// Encrypted comparison result (true if player 0 wins)
71
+ ebool private _player0Wins;
72
+
73
+ // ==================== EVENTS ====================
74
+
75
+ /// @notice Emitted when a player joins
76
+ /// @param player Address of joining player
77
+ /// @param seat Seat number (0 or 1)
78
+ event PlayerJoined(address indexed player, uint8 seat);
79
+
80
+ /// @notice Emitted when cards are dealt
81
+ event CardsDealt();
82
+
83
+ /// @notice Emitted when a bet is placed
84
+ /// @param player Address of betting player
85
+ /// @param amount Bet amount
86
+ event BetPlaced(address indexed player, uint256 amount);
87
+
88
+ /// @notice Emitted when a player folds
89
+ /// @param player Address of folding player
90
+ event PlayerFolded(address indexed player);
91
+
92
+ /// @notice Emitted when showdown begins
93
+ event ShowdownStarted();
94
+
95
+ /// @notice Emitted when winner is determined
96
+ /// @param winner Address of winner
97
+ /// @param pot Total pot won
98
+ event GameWon(address indexed winner, uint256 pot);
99
+
100
+ // ==================== CONSTRUCTOR ====================
101
+
102
+ /// @notice Creates a new poker game
103
+ /// @param _minBet Minimum bet amount in wei
104
+ constructor(uint256 _minBet) {
105
+ require(_minBet > 0, "Min bet must be > 0");
106
+ minBet = _minBet;
107
+ gameId = 1;
108
+ state = GameState.WaitingForPlayers;
109
+ }
110
+
111
+ // ==================== JOIN GAME ====================
112
+
113
+ /// @notice Join the game with encrypted hole cards
114
+ /// @param encCard1 First encrypted card (1-13)
115
+ /// @param encCard2 Second encrypted card (1-13)
116
+ /// @param inputProof Proof validating encrypted inputs
117
+ function joinGame(
118
+ externalEuint8 encCard1,
119
+ externalEuint8 encCard2,
120
+ bytes calldata inputProof
121
+ ) external payable {
122
+ require(
123
+ state == GameState.WaitingForPlayers,
124
+ "Game not accepting players"
125
+ );
126
+ require(msg.value >= minBet, "Must pay min bet to join");
127
+ require(
128
+ _players[0].addr != msg.sender && _players[1].addr != msg.sender,
129
+ "Already in game"
130
+ );
131
+
132
+ // 🔐 Convert encrypted cards
133
+ euint8 card1 = FHE.fromExternal(encCard1, inputProof);
134
+ euint8 card2 = FHE.fromExternal(encCard2, inputProof);
135
+
136
+ // ✅ Grant permissions
137
+ FHE.allowThis(card1);
138
+ FHE.allowThis(card2);
139
+ FHE.allow(card1, msg.sender); // Player can see own cards
140
+ FHE.allow(card2, msg.sender);
141
+
142
+ // 🧮 Compute hand strength (simplified: sum of cards)
143
+ euint16 strength = FHE.add(FHE.asEuint16(card1), FHE.asEuint16(card2));
144
+ FHE.allowThis(strength);
145
+
146
+ uint8 seat;
147
+ if (_players[0].addr == address(0)) {
148
+ seat = 0;
149
+ _players[0] = Player({
150
+ addr: msg.sender,
151
+ card1: card1,
152
+ card2: card2,
153
+ handStrength: strength,
154
+ bet: msg.value,
155
+ folded: false
156
+ });
157
+ } else {
158
+ seat = 1;
159
+ _players[1] = Player({
160
+ addr: msg.sender,
161
+ card1: card1,
162
+ card2: card2,
163
+ handStrength: strength,
164
+ bet: msg.value,
165
+ folded: false
166
+ });
167
+ // Both players joined
168
+ state = GameState.CardsDealt;
169
+ emit CardsDealt();
170
+ }
171
+
172
+ pot += msg.value;
173
+ emit PlayerJoined(msg.sender, seat);
174
+ }
175
+
176
+ // ==================== BETTING ====================
177
+
178
+ /// @notice Place a bet
179
+ function bet() external payable {
180
+ require(
181
+ state == GameState.CardsDealt || state == GameState.BettingRound,
182
+ "Not betting phase"
183
+ );
184
+ require(msg.value > 0, "Must bet something");
185
+
186
+ uint8 seat = _getSeat(msg.sender);
187
+ require(!_players[seat].folded, "Already folded");
188
+
189
+ _players[seat].bet += msg.value;
190
+ pot += msg.value;
191
+ state = GameState.BettingRound;
192
+
193
+ emit BetPlaced(msg.sender, msg.value);
194
+ }
195
+
196
+ /// @notice Fold your hand
197
+ function fold() external {
198
+ require(
199
+ state == GameState.CardsDealt || state == GameState.BettingRound,
200
+ "Not betting phase"
201
+ );
202
+
203
+ uint8 seat = _getSeat(msg.sender);
204
+ require(!_players[seat].folded, "Already folded");
205
+
206
+ _players[seat].folded = true;
207
+
208
+ // Other player wins by default
209
+ uint8 winnerSeat = seat == 0 ? 1 : 0;
210
+ winner = _players[winnerSeat].addr;
211
+ state = GameState.Finished;
212
+
213
+ // Transfer pot to winner
214
+ uint256 winnings = pot;
215
+ pot = 0;
216
+ (bool sent, ) = winner.call{value: winnings}("");
217
+ require(sent, "Transfer failed");
218
+
219
+ emit PlayerFolded(msg.sender);
220
+ emit GameWon(winner, winnings);
221
+ }
222
+
223
+ // ==================== SHOWDOWN ====================
224
+
225
+ /// @notice Initiate showdown - compare hands using FHE
226
+ function showdown() external {
227
+ require(
228
+ state == GameState.CardsDealt || state == GameState.BettingRound,
229
+ "Not ready for showdown"
230
+ );
231
+ require(!_players[0].folded && !_players[1].folded, "Someone folded");
232
+
233
+ // 🎯 Compare hand strengths using FHE
234
+ _player0Wins = FHE.gt(
235
+ _players[0].handStrength,
236
+ _players[1].handStrength
237
+ );
238
+
239
+ FHE.allowThis(_player0Wins);
240
+ FHE.makePubliclyDecryptable(_player0Wins);
241
+
242
+ state = GameState.Showdown;
243
+ emit ShowdownStarted();
244
+ }
245
+
246
+ /// @notice Reveal winner with decryption proof
247
+ /// @param abiEncodedResult ABI-encoded bool result
248
+ /// @param decryptionProof KMS signature proving decryption
249
+ function revealWinner(
250
+ bytes memory abiEncodedResult,
251
+ bytes memory decryptionProof
252
+ ) external {
253
+ require(state == GameState.Showdown, "Not showdown phase");
254
+
255
+ // Verify decryption
256
+ bytes32[] memory cts = new bytes32[](1);
257
+ cts[0] = FHE.toBytes32(_player0Wins);
258
+ FHE.checkSignatures(cts, abiEncodedResult, decryptionProof);
259
+
260
+ bool player0Wins = abi.decode(abiEncodedResult, (bool));
261
+
262
+ if (player0Wins) {
263
+ winner = _players[0].addr;
264
+ } else {
265
+ winner = _players[1].addr;
266
+ }
267
+
268
+ state = GameState.Finished;
269
+
270
+ // Transfer pot
271
+ uint256 winnings = pot;
272
+ pot = 0;
273
+ (bool sent, ) = winner.call{value: winnings}("");
274
+ require(sent, "Transfer failed");
275
+
276
+ emit GameWon(winner, winnings);
277
+ }
278
+
279
+ // ==================== RESET ====================
280
+
281
+ /// @notice Reset for a new game
282
+ function resetGame() external {
283
+ require(state == GameState.Finished, "Game not finished");
284
+
285
+ delete _players[0];
286
+ delete _players[1];
287
+ pot = 0;
288
+ winner = address(0);
289
+ state = GameState.WaitingForPlayers;
290
+ gameId++;
291
+ }
292
+
293
+ // ==================== VIEW FUNCTIONS ====================
294
+
295
+ /// @notice Get game info
296
+ function getGameInfo()
297
+ external
298
+ view
299
+ returns (
300
+ GameState currentState,
301
+ address player0,
302
+ address player1,
303
+ uint256 currentPot,
304
+ address currentWinner,
305
+ uint256 currentGameId
306
+ )
307
+ {
308
+ return (state, _players[0].addr, _players[1].addr, pot, winner, gameId);
309
+ }
310
+
311
+ /// @notice Get player bet
312
+ function getPlayerBet(address player) external view returns (uint256) {
313
+ if (_players[0].addr == player) return _players[0].bet;
314
+ if (_players[1].addr == player) return _players[1].bet;
315
+ return 0;
316
+ }
317
+
318
+ /// @notice Check if address is in game
319
+ function isPlayer(address addr) external view returns (bool) {
320
+ return _players[0].addr == addr || _players[1].addr == addr;
321
+ }
322
+
323
+ /// @notice Get own cards (only callable by the player themselves)
324
+ /// @dev Returns encrypted handles that only the caller can decrypt
325
+ function getMyCards() external view returns (euint8 card1, euint8 card2) {
326
+ uint8 seat = _getSeat(msg.sender);
327
+ return (_players[seat].card1, _players[seat].card2);
328
+ }
329
+
330
+ // ==================== INTERNAL ====================
331
+
332
+ function _getSeat(address player) internal view returns (uint8) {
333
+ if (_players[0].addr == player) return 0;
334
+ if (_players[1].addr == player) return 1;
335
+ revert("Not a player");
336
+ }
337
+ }