create-fhevm-example 1.3.2 → 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 (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 +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 +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,213 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {
5
+ FHE,
6
+ euint8,
7
+ ebool,
8
+ externalEuint8
9
+ } from "@fhevm/solidity/lib/FHE.sol";
10
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
11
+
12
+ /**
13
+ * @notice Rock-Paper-Scissors game with encrypted moves - fair play guaranteed!
14
+ *
15
+ * @dev Demonstrates FHE commit-reveal pattern without trusted third party:
16
+ * - Players submit encrypted moves (0=Rock, 1=Paper, 2=Scissors)
17
+ * - FHE comparison determines winner without revealing moves
18
+ * - Loser's move revealed only after game concludes
19
+ *
20
+ * Move encoding: 0 = Rock, 1 = Paper, 2 = Scissors
21
+ * Win logic: (move1 - move2 + 3) % 3 → 1 = player1 wins, 2 = player2 wins
22
+ *
23
+ * ⚠️ IMPORTANT: Uses FHE.rem for modulo - computationally expensive but secure
24
+ */
25
+ contract RockPaperScissors is ZamaEthereumConfig {
26
+ // ==================== TYPES ====================
27
+
28
+ enum GameState {
29
+ WaitingForPlayers, // 0 or 1 player joined
30
+ BothMoved, // Both players submitted moves
31
+ Revealed // Winner determined and revealed
32
+ }
33
+
34
+ // ==================== STATE ====================
35
+
36
+ /// Player 1 address
37
+ address public player1;
38
+
39
+ /// Player 2 address
40
+ address public player2;
41
+
42
+ /// Game state
43
+ GameState public state;
44
+
45
+ /// Encrypted moves
46
+ euint8 private _move1;
47
+ euint8 private _move2;
48
+
49
+ /// Winner address (set after reveal)
50
+ address public winner;
51
+
52
+ /// Game ID for tracking multiple games
53
+ uint256 public gameId;
54
+
55
+ // ==================== EVENTS ====================
56
+
57
+ /// @notice Emitted when a player joins
58
+ /// @param player Address of joining player
59
+ /// @param playerNumber 1 or 2
60
+ event PlayerJoined(address indexed player, uint8 playerNumber);
61
+
62
+ /// @notice Emitted when both moves are submitted
63
+ event BothMovesMade();
64
+
65
+ /// @notice Emitted when winner is revealed
66
+ /// @param winner Address of winner (address(0) for tie)
67
+ /// @param gameId Current game ID
68
+ event GameResult(address indexed winner, uint256 indexed gameId);
69
+
70
+ // ==================== CONSTRUCTOR ====================
71
+
72
+ /// @notice Creates a new Rock-Paper-Scissors game
73
+ constructor() {
74
+ gameId = 1;
75
+ state = GameState.WaitingForPlayers;
76
+ }
77
+
78
+ // ==================== PLAY FUNCTIONS ====================
79
+
80
+ /// @notice Submit an encrypted move to play
81
+ /// @dev Move must be 0 (Rock), 1 (Paper), or 2 (Scissors)
82
+ /// @param encryptedMove Encrypted move value (0-2)
83
+ /// @param inputProof Proof validating the encrypted input
84
+ function play(
85
+ externalEuint8 encryptedMove,
86
+ bytes calldata inputProof
87
+ ) external {
88
+ require(state == GameState.WaitingForPlayers, "Game not accepting moves");
89
+ require(
90
+ msg.sender != player1 && msg.sender != player2,
91
+ "Already in this game"
92
+ );
93
+
94
+ // 🔐 Convert external encrypted input to internal euint8
95
+ euint8 move = FHE.fromExternal(encryptedMove, inputProof);
96
+
97
+ // ✅ Grant contract permission to operate on this value
98
+ FHE.allowThis(move);
99
+
100
+ if (player1 == address(0)) {
101
+ // First player joins
102
+ player1 = msg.sender;
103
+ _move1 = move;
104
+ emit PlayerJoined(msg.sender, 1);
105
+ } else {
106
+ // Second player joins
107
+ player2 = msg.sender;
108
+ _move2 = move;
109
+ state = GameState.BothMoved;
110
+ emit PlayerJoined(msg.sender, 2);
111
+ emit BothMovesMade();
112
+ }
113
+ }
114
+
115
+ /// @notice Determine the winner using FHE computation
116
+ /// @dev Anyone can call after both players have moved
117
+ function determineWinner() external {
118
+ require(state == GameState.BothMoved, "Not ready to determine winner");
119
+
120
+ // 🎯 Winner calculation using FHE
121
+ // Formula: (move1 - move2 + 3) % 3
122
+ // Result: 0 = tie, 1 = player1 wins, 2 = player2 wins
123
+
124
+ // Convert moves to computation: add 3 to avoid negative numbers
125
+ // move1 + 3 - move2 = (move1 - move2 + 3)
126
+ euint8 diff = FHE.add(FHE.add(_move1, FHE.asEuint8(3)), FHE.neg(_move2));
127
+
128
+ // Modulo 3 to get result (0, 1, or 2)
129
+ euint8 result = FHE.rem(diff, 3);
130
+
131
+ // 🔍 Check outcomes
132
+ ebool isTie = FHE.eq(result, FHE.asEuint8(0));
133
+ ebool player1Wins = FHE.eq(result, FHE.asEuint8(1));
134
+
135
+ // 📊 Encode winner as address bits (simplified for demo)
136
+ // In production, use public decryption pattern
137
+
138
+ // Make result publicly decryptable
139
+ FHE.allowThis(result);
140
+ FHE.makePubliclyDecryptable(result);
141
+
142
+ // Store encrypted result for later reveal
143
+ // For this demo, we'll use select to pick winner
144
+
145
+ state = GameState.Revealed;
146
+
147
+ emit GameResult(address(0), gameId); // Placeholder - real winner after decryption
148
+ }
149
+
150
+ /// @notice Reveal winner with decryption proof
151
+ /// @param abiEncodedResult ABI-encoded uint8 result
152
+ /// @param decryptionProof KMS signature proving decryption
153
+ function revealWinner(
154
+ bytes memory abiEncodedResult,
155
+ bytes memory decryptionProof
156
+ ) external {
157
+ require(state == GameState.Revealed, "Game not ready for reveal");
158
+
159
+ // Build ciphertext list for verification
160
+ euint8 diff = FHE.add(FHE.add(_move1, FHE.asEuint8(3)), FHE.neg(_move2));
161
+ euint8 result = FHE.rem(diff, 3);
162
+
163
+ bytes32[] memory cts = new bytes32[](1);
164
+ cts[0] = FHE.toBytes32(result);
165
+
166
+ // Verify the decryption proof
167
+ FHE.checkSignatures(cts, abiEncodedResult, decryptionProof);
168
+
169
+ // Decode result
170
+ uint8 resultValue = abi.decode(abiEncodedResult, (uint8));
171
+
172
+ if (resultValue == 0) {
173
+ winner = address(0); // Tie
174
+ } else if (resultValue == 1) {
175
+ winner = player1;
176
+ } else {
177
+ winner = player2;
178
+ }
179
+
180
+ emit GameResult(winner, gameId);
181
+ }
182
+
183
+ /// @notice Reset for a new game
184
+ /// @dev Only callable after game is revealed
185
+ function resetGame() external {
186
+ require(state == GameState.Revealed, "Current game not finished");
187
+
188
+ // Reset state
189
+ player1 = address(0);
190
+ player2 = address(0);
191
+ winner = address(0);
192
+ state = GameState.WaitingForPlayers;
193
+ gameId++;
194
+ }
195
+
196
+ // ==================== VIEW FUNCTIONS ====================
197
+
198
+ /// @notice Get current game state
199
+ function getGameState() external view returns (
200
+ address p1,
201
+ address p2,
202
+ GameState currentState,
203
+ address currentWinner,
204
+ uint256 currentGameId
205
+ ) {
206
+ return (player1, player2, state, winner, gameId);
207
+ }
208
+
209
+ /// @notice Check if an address is in the current game
210
+ function isPlayer(address addr) external view returns (bool) {
211
+ return addr == player1 || addr == player2;
212
+ }
213
+ }
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.27;
3
+
4
+ import {
5
+ Ownable2Step,
6
+ Ownable
7
+ } from "@openzeppelin/contracts/access/Ownable2Step.sol";
8
+ import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
9
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
10
+ import {
11
+ ERC7984 as ERC7984Base
12
+ } from "@openzeppelin/confidential-contracts/token/ERC7984/ERC7984.sol";
13
+
14
+ /**
15
+ * @notice Confidential token using OpenZeppelin's ERC7984 standard
16
+ *
17
+ * @dev Demonstrates minting and burning with both visible and encrypted amounts.
18
+ * Shows how to integrate FHE with standard token operations.
19
+ */
20
+ contract ERC7984Example is ZamaEthereumConfig, ERC7984Base, Ownable2Step {
21
+ /// @notice Creates a new confidential ERC7984 token with initial supply
22
+ /// @param owner Address that will own the token and receive initial supply
23
+ /// @param amount Initial supply amount (visible on-chain but stored encrypted)
24
+ /// @param name_ Token name
25
+ /// @param symbol_ Token symbol
26
+ /// @param tokenURI_ Token metadata URI
27
+ constructor(
28
+ address owner,
29
+ uint64 amount,
30
+ string memory name_,
31
+ string memory symbol_,
32
+ string memory tokenURI_
33
+ ) ERC7984Base(name_, symbol_, tokenURI_) Ownable(owner) {
34
+ // 🔐 Mint initial supply as encrypted amount
35
+ euint64 encryptedAmount = FHE.asEuint64(amount);
36
+ _mint(owner, encryptedAmount);
37
+ }
38
+
39
+ // ==================== MINTING ====================
40
+
41
+ /// @notice Mint with visible amount (owner knows the amount)
42
+ /// @dev Amount is visible on-chain but stored encrypted
43
+ function mint(address to, uint64 amount) external onlyOwner {
44
+ _mint(to, FHE.asEuint64(amount));
45
+ }
46
+
47
+ /// @notice Mint with encrypted amount (full privacy)
48
+ /// @dev Even the contract doesn't know the minted amount
49
+ function confidentialMint(
50
+ address to,
51
+ externalEuint64 encryptedAmount,
52
+ bytes calldata inputProof
53
+ ) external onlyOwner returns (euint64 transferred) {
54
+ return _mint(to, FHE.fromExternal(encryptedAmount, inputProof));
55
+ }
56
+
57
+ // ==================== BURNING ====================
58
+
59
+ /// @notice Burn with visible amount
60
+ function burn(address from, uint64 amount) external onlyOwner {
61
+ _burn(from, FHE.asEuint64(amount));
62
+ }
63
+
64
+ /// @notice Burn with encrypted amount (full privacy)
65
+ function confidentialBurn(
66
+ address from,
67
+ externalEuint64 encryptedAmount,
68
+ bytes calldata inputProof
69
+ ) external onlyOwner returns (euint64 transferred) {
70
+ return _burn(from, FHE.fromExternal(encryptedAmount, inputProof));
71
+ }
72
+
73
+ // ==================== INTERNAL ====================
74
+
75
+ /// @dev Grant owner access to total supply on every transfer
76
+ function _update(
77
+ address from,
78
+ address to,
79
+ euint64 amount
80
+ ) internal virtual override returns (euint64 transferred) {
81
+ transferred = super._update(from, to, amount);
82
+ // Allow owner to decrypt total supply
83
+ FHE.allow(confidentialTotalSupply(), owner());
84
+ }
85
+ }
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.27;
3
+
4
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
5
+ import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
6
+ import {
7
+ ERC7984ERC20Wrapper as ERC7984ERC20WrapperBase,
8
+ ERC7984
9
+ } from "@openzeppelin/confidential-contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol";
10
+
11
+ /**
12
+ * @notice Wraps ERC20 tokens into confidential ERC7984 tokens
13
+ *
14
+ * @dev WRAP: ERC20 → ERC7984 (public → private)
15
+ * UNWRAP: ERC7984 → ERC20 (private → public, requires decryption)
16
+ */
17
+ contract ERC7984ERC20WrapperExample is
18
+ ERC7984ERC20WrapperBase,
19
+ ZamaEthereumConfig
20
+ {
21
+ /// @notice Creates a new ERC20-to-ERC7984 wrapper
22
+ /// @param token The ERC20 token to wrap
23
+ /// @param name Name for the wrapped ERC7984 token
24
+ /// @param symbol Symbol for the wrapped ERC7984 token
25
+ /// @param uri Metadata URI for the wrapped token
26
+ constructor(
27
+ IERC20 token,
28
+ string memory name,
29
+ string memory symbol,
30
+ string memory uri
31
+ ) ERC7984ERC20WrapperBase(token) ERC7984(name, symbol, uri) {}
32
+
33
+ // 📦 Inherited from ERC7984ERC20Wrapper:
34
+ //
35
+ // wrap(address to, uint256 amount)
36
+ // - User approves this contract for ERC20
37
+ // - ERC20 is escrowed, ERC7984 is minted
38
+ //
39
+ // unwrap(address from, address to, euint64 amount)
40
+ // - ERC7984 is burned
41
+ // - Decryption is requested
42
+ // - After proof, ERC20 is released
43
+ }
@@ -0,0 +1,110 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.27;
3
+
4
+ import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
5
+ import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
6
+ import {
7
+ SafeERC20
8
+ } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
9
+ import {
10
+ IERC7984
11
+ } from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";
12
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
13
+
14
+ /**
15
+ * @notice Swap confidential ERC7984 tokens to regular ERC20 tokens
16
+ *
17
+ * @dev Uses FHEVM v0.9 decryption flow:
18
+ * FHE.makePubliclyDecryptable() + FHE.checkSignatures()
19
+ */
20
+ contract SwapERC7984ToERC20Example is ZamaEthereumConfig {
21
+ using SafeERC20 for IERC20;
22
+
23
+ error InvalidSwap(euint64 encryptedAmount);
24
+
25
+ struct PendingSwap {
26
+ address receiver;
27
+ bool pending;
28
+ }
29
+
30
+ mapping(euint64 => PendingSwap) private _pendingSwaps;
31
+ IERC7984 private _fromToken;
32
+ IERC20 private _toToken;
33
+
34
+ event SwapInitiated(
35
+ euint64 indexed encryptedAmount,
36
+ address indexed receiver
37
+ );
38
+ event SwapFinalized(address indexed receiver, uint64 amount);
39
+
40
+ constructor(IERC7984 fromToken, IERC20 toToken) {
41
+ _fromToken = fromToken;
42
+ _toToken = toToken;
43
+ }
44
+
45
+ // ==================== STEP 1: INITIATE ====================
46
+
47
+ /// @notice Start the swap - transfers ERC7984 and requests decryption
48
+ function initiateSwap(
49
+ externalEuint64 encryptedInput,
50
+ bytes calldata inputProof
51
+ ) public {
52
+ euint64 amount = FHE.fromExternal(encryptedInput, inputProof);
53
+
54
+ // Transfer ERC7984 from user to this contract
55
+ FHE.allowTransient(amount, address(_fromToken));
56
+ euint64 amountTransferred = _fromToken.confidentialTransferFrom(
57
+ msg.sender,
58
+ address(this),
59
+ amount
60
+ );
61
+
62
+ // 🔓 FHEVM v0.9: Request public decryption
63
+ // KMS will provide proof that this value decrypts to X
64
+ FHE.makePubliclyDecryptable(amountTransferred);
65
+ FHE.allowThis(amountTransferred);
66
+
67
+ // Register pending swap
68
+ _pendingSwaps[amountTransferred] = PendingSwap({
69
+ receiver: msg.sender,
70
+ pending: true
71
+ });
72
+
73
+ emit SwapInitiated(amountTransferred, msg.sender);
74
+ }
75
+
76
+ // ==================== STEP 2: FINALIZE ====================
77
+
78
+ /// @notice Complete the swap with decryption proof from KMS
79
+ /// @dev encryptedAmount: The handle from initiateSwap
80
+ /// cleartextAmount: The decrypted value
81
+ /// decryptionProof: Proof from KMS that decryption is valid
82
+ function finalizeSwap(
83
+ euint64 encryptedAmount,
84
+ uint64 cleartextAmount,
85
+ bytes calldata decryptionProof
86
+ ) public {
87
+ PendingSwap storage pending = _pendingSwaps[encryptedAmount];
88
+ require(pending.pending, InvalidSwap(encryptedAmount));
89
+
90
+ // 🔐 FHEVM v0.9: Verify decryption proof
91
+ // This ensures cleartextAmount is the TRUE decryption of encryptedAmount
92
+ bytes32[] memory handles = new bytes32[](1);
93
+ handles[0] = euint64.unwrap(encryptedAmount);
94
+ FHE.checkSignatures(
95
+ handles,
96
+ abi.encode(cleartextAmount),
97
+ decryptionProof
98
+ );
99
+
100
+ address receiver = pending.receiver;
101
+ delete _pendingSwaps[encryptedAmount];
102
+
103
+ // Release ERC20 to user
104
+ if (cleartextAmount != 0) {
105
+ _toToken.safeTransfer(receiver, cleartextAmount);
106
+ }
107
+
108
+ emit SwapFinalized(receiver, cleartextAmount);
109
+ }
110
+ }
@@ -0,0 +1,48 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.27;
3
+
4
+ import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
5
+ import {
6
+ IERC7984
7
+ } from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";
8
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
9
+
10
+ /**
11
+ * @notice Fully confidential swap between two ERC7984 tokens
12
+ *
13
+ * @dev Both input and output amounts remain encrypted throughout the swap.
14
+ */
15
+ contract SwapERC7984ToERC7984Example is ZamaEthereumConfig {
16
+ /// @notice Swap confidential token for another confidential token
17
+ /// @dev fromToken: The token to swap from
18
+ /// toToken: The token to receive
19
+ /// amountInput: Encrypted amount to swap
20
+ /// inputProof: Proof for the encrypted input
21
+ function swapConfidentialForConfidential(
22
+ IERC7984 fromToken,
23
+ IERC7984 toToken,
24
+ externalEuint64 amountInput,
25
+ bytes calldata inputProof
26
+ ) public virtual {
27
+ // 🔍 Check caller is operator for this contract
28
+ require(
29
+ fromToken.isOperator(msg.sender, address(this)),
30
+ "Not authorized operator"
31
+ );
32
+
33
+ euint64 amount = FHE.fromExternal(amountInput, inputProof);
34
+
35
+ // 📥 Transfer fromToken: user → this contract
36
+ FHE.allowTransient(amount, address(fromToken));
37
+ euint64 amountTransferred = fromToken.confidentialTransferFrom(
38
+ msg.sender,
39
+ address(this),
40
+ amount
41
+ );
42
+
43
+ // 📤 Transfer toToken: this contract → user
44
+ // Amount stays encrypted throughout!
45
+ FHE.allowTransient(amountTransferred, address(toToken));
46
+ toToken.confidentialTransfer(msg.sender, amountTransferred);
47
+ }
48
+ }
@@ -0,0 +1,147 @@
1
+ // SPDX-License-Identifier: BSD-3-Clause-Clear
2
+ pragma solidity ^0.8.27;
3
+
4
+ import {FHE, ebool, euint64, euint128} from "@fhevm/solidity/lib/FHE.sol";
5
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
6
+ import {
7
+ ReentrancyGuardTransient
8
+ } from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
9
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
10
+ import {
11
+ IERC7984
12
+ } from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";
13
+
14
+ /**
15
+ * @notice Linear vesting wallet for ERC7984 tokens - amounts stay encrypted!
16
+ *
17
+ * @dev Timeline: |--START--|---VESTING---|--END--|
18
+ * 0% linear 100%
19
+ *
20
+ * All vesting calculations are performed on encrypted values using FHE operations.
21
+ */
22
+ contract VestingWalletExample is
23
+ Ownable,
24
+ ReentrancyGuardTransient,
25
+ ZamaEthereumConfig
26
+ {
27
+ // 🔐 Track released amounts per token (encrypted)
28
+ mapping(address token => euint128) private _tokenReleased;
29
+ uint64 private _start;
30
+ uint64 private _duration;
31
+
32
+ /// @notice Emitted when vested tokens are released to beneficiary
33
+ /// @param token The ERC7984 token address
34
+ /// @param amount The encrypted amount released
35
+ event VestingWalletConfidentialTokenReleased(
36
+ address indexed token,
37
+ euint64 amount
38
+ );
39
+
40
+ /// @notice Creates a new vesting wallet for a beneficiary
41
+ /// @param beneficiary Address that will receive vested tokens
42
+ /// @param startTimestamp Unix timestamp when vesting begins
43
+ /// @param durationSeconds Duration of the vesting period in seconds
44
+ constructor(
45
+ address beneficiary,
46
+ uint48 startTimestamp,
47
+ uint48 durationSeconds
48
+ ) Ownable(beneficiary) {
49
+ _start = startTimestamp;
50
+ _duration = durationSeconds;
51
+ }
52
+
53
+ // ==================== VIEW FUNCTIONS ====================
54
+
55
+ function start() public view virtual returns (uint64) {
56
+ return _start;
57
+ }
58
+
59
+ function duration() public view virtual returns (uint64) {
60
+ return _duration;
61
+ }
62
+
63
+ function end() public view virtual returns (uint64) {
64
+ return start() + duration();
65
+ }
66
+
67
+ /// @notice Encrypted amount already released for token
68
+ function released(address token) public view virtual returns (euint128) {
69
+ return _tokenReleased[token];
70
+ }
71
+
72
+ // ==================== CORE LOGIC ====================
73
+
74
+ /// @notice Calculate how much can be released now
75
+ /// @dev Returns encrypted amount - no one knows the actual value
76
+ function releasable(address token) public virtual returns (euint64) {
77
+ euint128 vestedAmount_ = vestedAmount(token, uint48(block.timestamp));
78
+ euint128 releasedAmount = released(token);
79
+
80
+ // 🔀 FHE.select: encrypted if-else
81
+ // If vested >= released: return (vested - released)
82
+ // Else: return 0
83
+ ebool canRelease = FHE.ge(vestedAmount_, releasedAmount);
84
+ return
85
+ FHE.select(
86
+ canRelease,
87
+ FHE.asEuint64(FHE.sub(vestedAmount_, releasedAmount)),
88
+ FHE.asEuint64(0)
89
+ );
90
+ }
91
+
92
+ /// @notice Release vested tokens to beneficiary
93
+ function release(address token) public virtual nonReentrant {
94
+ euint64 amount = releasable(token);
95
+
96
+ // Transfer encrypted amount to owner
97
+ FHE.allowTransient(amount, token);
98
+ euint64 amountSent = IERC7984(token).confidentialTransfer(
99
+ owner(),
100
+ amount
101
+ );
102
+
103
+ // Update released amount (encrypted)
104
+ euint128 newReleasedAmount = FHE.add(released(token), amountSent);
105
+ FHE.allow(newReleasedAmount, owner());
106
+ FHE.allowThis(newReleasedAmount);
107
+ _tokenReleased[token] = newReleasedAmount;
108
+
109
+ emit VestingWalletConfidentialTokenReleased(token, amountSent);
110
+ }
111
+
112
+ /// @notice Calculate vested amount at timestamp
113
+ function vestedAmount(
114
+ address token,
115
+ uint48 timestamp
116
+ ) public virtual returns (euint128) {
117
+ // Total = released + current balance
118
+ euint128 totalAllocation = FHE.add(
119
+ released(token),
120
+ IERC7984(token).confidentialBalanceOf(address(this))
121
+ );
122
+ return _vestingSchedule(totalAllocation, timestamp);
123
+ }
124
+
125
+ // ==================== INTERNAL ====================
126
+
127
+ /// @dev Linear vesting: (total * elapsed) / duration
128
+ function _vestingSchedule(
129
+ euint128 totalAllocation,
130
+ uint48 timestamp
131
+ ) internal virtual returns (euint128) {
132
+ if (timestamp < start()) {
133
+ // Before start: 0% vested
134
+ return euint128.wrap(0);
135
+ } else if (timestamp >= end()) {
136
+ // After end: 100% vested
137
+ return totalAllocation;
138
+ } else {
139
+ // During vesting: linear unlock
140
+ return
141
+ FHE.div(
142
+ FHE.mul(totalAllocation, (timestamp - start())),
143
+ duration()
144
+ );
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,31 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.27;
3
+
4
+ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5
+
6
+ /**
7
+ * @notice Simple ERC20 mock for testing purposes
8
+ */
9
+ contract ERC20Mock is ERC20 {
10
+ uint8 private _decimals;
11
+
12
+ constructor(
13
+ string memory name_,
14
+ string memory symbol_,
15
+ uint8 decimals_
16
+ ) ERC20(name_, symbol_) {
17
+ _decimals = decimals_;
18
+ }
19
+
20
+ function decimals() public view override returns (uint8) {
21
+ return _decimals;
22
+ }
23
+
24
+ function mint(address to, uint256 amount) external {
25
+ _mint(to, amount);
26
+ }
27
+
28
+ function burn(address from, uint256 amount) external {
29
+ _burn(from, amount);
30
+ }
31
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-mode.d.ts","sourceRoot":"","sources":["../../../scripts/commands/add-mode.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2OH;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ElE"}