create-kozalak-l1 0.1.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 (61) hide show
  1. package/README.md +87 -0
  2. package/dist/deploy.js +63 -0
  3. package/dist/forge.js +27 -0
  4. package/dist/index.js +176 -0
  5. package/dist/prompts.js +23 -0
  6. package/dist/scaffold.js +33 -0
  7. package/dist/templates.js +108 -0
  8. package/package.json +29 -0
  9. package/templates/erc20-gas/.env.example +19 -0
  10. package/templates/erc20-gas/.gitmodules +6 -0
  11. package/templates/erc20-gas/README.md +48 -0
  12. package/templates/erc20-gas/foundry.toml +35 -0
  13. package/templates/erc20-gas/gitignore +5 -0
  14. package/templates/erc20-gas/remappings.txt +2 -0
  15. package/templates/erc20-gas/script/DeployERC20Gas.s.sol +131 -0
  16. package/templates/erc20-gas/src/KozaGasToken.sol +105 -0
  17. package/templates/erc20-gas/test/DeployERC20Gas.t.sol +60 -0
  18. package/templates/erc20-gas/test/ERC20Gas.invariants.t.sol +144 -0
  19. package/templates/erc20-gas/test/ERC20Gas.t.sol +354 -0
  20. package/templates/erc721-collection/.env.example +37 -0
  21. package/templates/erc721-collection/.gitmodules +6 -0
  22. package/templates/erc721-collection/README.md +48 -0
  23. package/templates/erc721-collection/foundry.toml +35 -0
  24. package/templates/erc721-collection/gitignore +5 -0
  25. package/templates/erc721-collection/remappings.txt +2 -0
  26. package/templates/erc721-collection/script/DeployERC721Collection.s.sol +151 -0
  27. package/templates/erc721-collection/src/KozaCollection.sol +281 -0
  28. package/templates/erc721-collection/test/DeployERC721Collection.t.sol +76 -0
  29. package/templates/erc721-collection/test/ERC721Collection.invariants.t.sol +175 -0
  30. package/templates/erc721-collection/test/ERC721Collection.t.sol +501 -0
  31. package/templates/ictt-bridge/.env.example +19 -0
  32. package/templates/ictt-bridge/.gitmodules +9 -0
  33. package/templates/ictt-bridge/README.md +49 -0
  34. package/templates/ictt-bridge/foundry.toml +41 -0
  35. package/templates/ictt-bridge/gitignore +5 -0
  36. package/templates/ictt-bridge/remappings.txt +8 -0
  37. package/templates/ictt-bridge/script/DeployTokenHome.s.sol +139 -0
  38. package/templates/ictt-bridge/src/KozaTokenHome.sol +57 -0
  39. package/templates/ictt-bridge/src/KozaTokenRemote.sol +65 -0
  40. package/templates/ictt-bridge/test/ICTTBridge.t.sol +157 -0
  41. package/templates/soulbound-credential/.env.example +19 -0
  42. package/templates/soulbound-credential/.gitmodules +6 -0
  43. package/templates/soulbound-credential/README.md +48 -0
  44. package/templates/soulbound-credential/foundry.toml +35 -0
  45. package/templates/soulbound-credential/gitignore +5 -0
  46. package/templates/soulbound-credential/remappings.txt +2 -0
  47. package/templates/soulbound-credential/script/DeployCredential.s.sol +126 -0
  48. package/templates/soulbound-credential/src/KozaCredential.sol +201 -0
  49. package/templates/soulbound-credential/test/DeployCredential.t.sol +46 -0
  50. package/templates/soulbound-credential/test/Soulbound.invariants.t.sol +133 -0
  51. package/templates/soulbound-credential/test/Soulbound.t.sol +319 -0
  52. package/templates/treasury-multisig/.env.example +19 -0
  53. package/templates/treasury-multisig/.gitmodules +6 -0
  54. package/templates/treasury-multisig/README.md +48 -0
  55. package/templates/treasury-multisig/foundry.toml +35 -0
  56. package/templates/treasury-multisig/gitignore +5 -0
  57. package/templates/treasury-multisig/remappings.txt +2 -0
  58. package/templates/treasury-multisig/script/DeployTreasury.s.sol +128 -0
  59. package/templates/treasury-multisig/src/KozaTreasury.sol +55 -0
  60. package/templates/treasury-multisig/test/DeployTreasury.t.sol +50 -0
  61. package/templates/treasury-multisig/test/Treasury.t.sol +154 -0
@@ -0,0 +1,131 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.34;
3
+
4
+ import {Script} from "forge-std/Script.sol";
5
+ import {console2} from "forge-std/console2.sol";
6
+ import {KozaGasToken} from "../src/KozaGasToken.sol";
7
+
8
+ /**
9
+ * @title DeployERC20Gas
10
+ * @notice Foundry deployment script for KozaGasToken (Phase 1, Template 1).
11
+ *
12
+ * Usage (Fuji testnet, with .env populated):
13
+ *
14
+ * forge script script/deploy/DeployERC20Gas.s.sol \
15
+ * --rpc-url fuji \
16
+ * --broadcast \
17
+ * --verify
18
+ *
19
+ * Required env (loaded by `forge` from .env):
20
+ * - PRIVATE_KEY — deployer private key (testnet only!)
21
+ *
22
+ * Optional env (defaults below if unset):
23
+ * - ERC20_NAME ("Koza Gas Token")
24
+ * - ERC20_SYMBOL ("KGAS")
25
+ * - ERC20_CAP (1_000_000 ether) — total supply hard cap
26
+ * - ERC20_INITIAL_MINT (100_000 ether) — minted to owner at deploy
27
+ * - ERC20_OWNER (msg.sender) — production: pass a multisig
28
+ *
29
+ * Production checklist:
30
+ * - PRIVATE_KEY must be testnet-only OR replaced with cast-wallet/keystore
31
+ * - ERC20_OWNER must point to a Safe (Gnosis Safe) multisig, never an EOA
32
+ * - SNOWTRACE_API_KEY in .env (set to "verifyContract" for free Routescan tier)
33
+ */
34
+ contract DeployERC20Gas is Script {
35
+ /*//////////////////////////////////////////////////////////////
36
+ DEFAULTS
37
+ //////////////////////////////////////////////////////////////*/
38
+
39
+ string internal constant DEFAULT_NAME = "Koza Gas Token";
40
+ string internal constant DEFAULT_SYMBOL = "KGAS";
41
+ uint256 internal constant DEFAULT_CAP = 1_000_000 ether;
42
+ uint256 internal constant DEFAULT_INITIAL_MINT = 100_000 ether;
43
+
44
+ /*//////////////////////////////////////////////////////////////
45
+ RUN
46
+ //////////////////////////////////////////////////////////////*/
47
+
48
+ /// @notice Entry point invoked by `forge script`. Reads parameters from env.
49
+ function run() external returns (KozaGasToken token, address deployer) {
50
+ string memory name = vm.envOr("ERC20_NAME", DEFAULT_NAME);
51
+ string memory symbol = vm.envOr("ERC20_SYMBOL", DEFAULT_SYMBOL);
52
+ uint256 cap = vm.envOr("ERC20_CAP", DEFAULT_CAP);
53
+ uint256 initialMint = vm.envOr("ERC20_INITIAL_MINT", DEFAULT_INITIAL_MINT);
54
+
55
+ address broadcaster = _resolveBroadcaster();
56
+ address owner = vm.envOr("ERC20_OWNER", broadcaster);
57
+
58
+ return deploy(name, symbol, cap, initialMint, owner, broadcaster);
59
+ }
60
+
61
+ /// @notice Test-friendly entry point. Takes explicit parameters instead of env.
62
+ /// @dev Prefer this from Foundry tests so env state does not leak between cases.
63
+ function deploy(
64
+ string memory name,
65
+ string memory symbol,
66
+ uint256 cap,
67
+ uint256 initialMint,
68
+ address owner,
69
+ address broadcaster
70
+ )
71
+ public
72
+ returns (KozaGasToken token, address deployer)
73
+ {
74
+ _logPreDeploy(name, symbol, cap, initialMint, owner, broadcaster);
75
+
76
+ vm.startBroadcast();
77
+ token = new KozaGasToken(name, symbol, cap, initialMint, owner);
78
+ vm.stopBroadcast();
79
+
80
+ deployer = broadcaster;
81
+
82
+ _logPostDeploy(token, owner);
83
+ }
84
+
85
+ /*//////////////////////////////////////////////////////////////
86
+ INTERNALS
87
+ //////////////////////////////////////////////////////////////*/
88
+
89
+ /// @dev Returns the address that will broadcast txs for this run.
90
+ /// Order: explicit DEPLOYER_ADDRESS > address derived from PRIVATE_KEY > tx.origin.
91
+ function _resolveBroadcaster() internal view returns (address) {
92
+ address explicitDeployer = vm.envOr("DEPLOYER_ADDRESS", address(0));
93
+ if (explicitDeployer != address(0)) return explicitDeployer;
94
+
95
+ uint256 pk = vm.envOr("PRIVATE_KEY", uint256(0));
96
+ if (pk != 0) return vm.addr(pk);
97
+
98
+ return tx.origin;
99
+ }
100
+
101
+ function _logPreDeploy(
102
+ string memory name,
103
+ string memory symbol,
104
+ uint256 cap,
105
+ uint256 initialMint,
106
+ address owner,
107
+ address broadcaster
108
+ )
109
+ internal
110
+ pure
111
+ {
112
+ console2.log("=== Deploying KozaGasToken ===");
113
+ console2.log(" Broadcaster: ", broadcaster);
114
+ console2.log(" Owner: ", owner);
115
+ console2.log(" Name: ", name);
116
+ console2.log(" Symbol: ", symbol);
117
+ console2.log(" Cap (wei): ", cap);
118
+ console2.log(" Initial mint: ", initialMint);
119
+ }
120
+
121
+ function _logPostDeploy(KozaGasToken token, address owner) internal view {
122
+ console2.log("=== Deployed ===");
123
+ console2.log(" Address: ", address(token));
124
+ console2.log(" Total supply: ", token.totalSupply());
125
+ console2.log(" Cap: ", token.cap());
126
+ console2.log(" Owner balance: ", token.balanceOf(owner));
127
+ console2.log("");
128
+ console2.log("Verify on Snowtrace:");
129
+ console2.log(" forge verify-contract <address> KozaGasToken --rpc-url fuji");
130
+ }
131
+ }
@@ -0,0 +1,105 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.34;
3
+
4
+ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5
+ import {ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
6
+ import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
7
+ import {Ownable, Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
8
+
9
+ /**
10
+ * @title KozaGasToken
11
+ * @author kozalak-L1 contributors
12
+ * @notice ERC-20 + Capped + Permit + Ownable2Step token. Subnet-EVM L1'de native gas
13
+ * token olarak veya C-Chain üzerinde yardımcı token olarak kullanılabilir.
14
+ * @dev OpenZeppelin v5.3+ pattern'leri ile audit-grade boilerplate. Custom logic minimum.
15
+ *
16
+ * Güvenlik:
17
+ * - Ownable2Step: tek-adımlı ownership transfer riski sıfırlanır (yanlış adres koruması)
18
+ * - ERC20Capped: total supply hard-cap, sınırsız enflasyon yok
19
+ * - ERC20Permit (EIP-2612): gasless approve, smart wallet UX
20
+ * - Custom errors: gas + audit kalitesi (require string yerine)
21
+ *
22
+ * Constructor input validation OpenZeppelin parent kontratlarına bırakıldı:
23
+ * - cap_ == 0 → ERC20Capped.ERC20InvalidCap(0)
24
+ * - initialOwner_ 0 → Ownable.OwnableInvalidOwner(0)
25
+ * Bu sayede çift kontrol (gas waste) ve hata mesajı çakışması olmaz.
26
+ */
27
+ contract KozaGasToken is ERC20Capped, ERC20Permit, Ownable2Step {
28
+ /*//////////////////////////////////////////////////////////////
29
+ ERRORS
30
+ //////////////////////////////////////////////////////////////*/
31
+
32
+ /// @notice Sıfır miktar parametresi geçerli değil (mint, burn için).
33
+ error ZeroAmount();
34
+
35
+ /// @notice Constructor'daki ilk mint cap'i aşıyor.
36
+ /// @param cap Maksimum total supply
37
+ /// @param attempted İstenen ilk mint miktarı
38
+ error InitialMintExceedsCap(uint256 cap, uint256 attempted);
39
+
40
+ /*//////////////////////////////////////////////////////////////
41
+ CONSTRUCTOR
42
+ //////////////////////////////////////////////////////////////*/
43
+
44
+ /**
45
+ * @param name_ Token adı (örn. "Koza Gas Token")
46
+ * @param symbol_ Token sembolü (örn. "KGAS")
47
+ * @param cap_ Maksimum total supply (wei cinsinden, decimal'i içerir)
48
+ * @param initialMint_ Constructor'da `initialOwner_`'a mint edilecek miktar (0 olabilir)
49
+ * @param initialOwner_ Owner adresi (production: multisig, asla EOA değil)
50
+ */
51
+ constructor(
52
+ string memory name_,
53
+ string memory symbol_,
54
+ uint256 cap_,
55
+ uint256 initialMint_,
56
+ address initialOwner_
57
+ )
58
+ ERC20(name_, symbol_)
59
+ ERC20Capped(cap_)
60
+ ERC20Permit(name_)
61
+ Ownable(initialOwner_)
62
+ {
63
+ if (initialMint_ > cap_) {
64
+ revert InitialMintExceedsCap(cap_, initialMint_);
65
+ }
66
+
67
+ if (initialMint_ > 0) {
68
+ _mint(initialOwner_, initialMint_);
69
+ }
70
+ }
71
+
72
+ /*//////////////////////////////////////////////////////////////
73
+ EXTERNAL ACTIONS
74
+ //////////////////////////////////////////////////////////////*/
75
+
76
+ /**
77
+ * @notice Yeni token mint et. Sadece owner çağırabilir.
78
+ * @dev Cap kontrolü ERC20Capped tarafından `_update` içinde otomatik yapılır.
79
+ * Sıfır adres kontrolü ERC20 `_mint` içinde yapılır (ERC20InvalidReceiver).
80
+ * @param to Mint edilecek adres
81
+ * @param amount Mint miktarı (wei cinsinden)
82
+ */
83
+ function mint(address to, uint256 amount) external onlyOwner {
84
+ if (amount == 0) revert ZeroAmount();
85
+ _mint(to, amount);
86
+ }
87
+
88
+ /**
89
+ * @notice Çağıran kişi kendi token'larını yakar.
90
+ * @param amount Yakılacak miktar (wei cinsinden)
91
+ */
92
+ function burn(uint256 amount) external {
93
+ if (amount == 0) revert ZeroAmount();
94
+ _burn(msg.sender, amount);
95
+ }
96
+
97
+ /*//////////////////////////////////////////////////////////////
98
+ OVERRIDES
99
+ //////////////////////////////////////////////////////////////*/
100
+
101
+ /// @dev ERC20 ve ERC20Capped'in `_update` fonksiyonları çakışıyor; üst sınıfa delegate et.
102
+ function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Capped) {
103
+ super._update(from, to, value);
104
+ }
105
+ }
@@ -0,0 +1,60 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.34;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {DeployERC20Gas} from "../script/DeployERC20Gas.s.sol";
6
+ import {KozaGasToken} from "../src/KozaGasToken.sol";
7
+
8
+ /**
9
+ * @title DeployERC20GasTest
10
+ * @notice Smoke tests for the deploy script. Calls `deploy(...)` directly with
11
+ * explicit parameters so each test is order-independent and immune to
12
+ * leaking env state between Foundry cases.
13
+ *
14
+ * The env-driven `run()` entry point is intentionally not unit-tested here;
15
+ * it is exercised via the actual `forge script ... --broadcast` flow on
16
+ * Fuji testnet (integration test).
17
+ */
18
+ contract DeployERC20GasTest is Test {
19
+ function test_Deploy_WithDefaultParameters() public {
20
+ DeployERC20Gas deployer = new DeployERC20Gas();
21
+ address broadcaster = makeAddr("broadcaster");
22
+
23
+ (KozaGasToken token, address returnedDeployer) =
24
+ deployer.deploy("Koza Gas Token", "KGAS", 1_000_000 ether, 100_000 ether, broadcaster, broadcaster);
25
+
26
+ assertEq(token.name(), "Koza Gas Token");
27
+ assertEq(token.symbol(), "KGAS");
28
+ assertEq(token.cap(), 1_000_000 ether);
29
+ assertEq(token.totalSupply(), 100_000 ether);
30
+ assertEq(token.owner(), broadcaster);
31
+ assertEq(token.balanceOf(broadcaster), 100_000 ether);
32
+ assertEq(returnedDeployer, broadcaster);
33
+ }
34
+
35
+ function test_Deploy_WithCustomParameters() public {
36
+ DeployERC20Gas deployer = new DeployERC20Gas();
37
+ address customOwner = makeAddr("customOwner");
38
+ address customBroadcaster = makeAddr("customBroadcaster");
39
+
40
+ (KozaGasToken token,) =
41
+ deployer.deploy("Custom Token", "CUST", 2_000_000 ether, 50_000 ether, customOwner, customBroadcaster);
42
+
43
+ assertEq(token.name(), "Custom Token");
44
+ assertEq(token.symbol(), "CUST");
45
+ assertEq(token.cap(), 2_000_000 ether);
46
+ assertEq(token.totalSupply(), 50_000 ether);
47
+ assertEq(token.owner(), customOwner);
48
+ assertEq(token.balanceOf(customOwner), 50_000 ether);
49
+ }
50
+
51
+ function test_Deploy_NoInitialMint() public {
52
+ DeployERC20Gas deployer = new DeployERC20Gas();
53
+ address broadcaster = makeAddr("broadcaster");
54
+
55
+ (KozaGasToken token,) = deployer.deploy("Koza Gas Token", "KGAS", 1_000_000 ether, 0, broadcaster, broadcaster);
56
+
57
+ assertEq(token.totalSupply(), 0);
58
+ assertEq(token.balanceOf(broadcaster), 0);
59
+ }
60
+ }
@@ -0,0 +1,144 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.34;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {KozaGasToken} from "../src/KozaGasToken.sol";
6
+
7
+ /**
8
+ * @title ERC20GasHandler
9
+ * @notice Foundry invariant testing handler. Bounded random calls into KozaGasToken.
10
+ * @dev Handler pattern (Foundry stateful fuzzing) — invariant'ların ihlal edilmediği
11
+ * her tx sırasında doğrulanır. Owner pranking ile yönetim fonksiyonlarına erişim
12
+ * sağlanır.
13
+ */
14
+ contract ERC20GasHandler is Test {
15
+ KozaGasToken public token;
16
+ address public owner;
17
+ address[] public actors;
18
+
19
+ // Mevcut tüm transfer/mint/burn aksiyon sayıları (debug için)
20
+ uint256 public callCount;
21
+
22
+ constructor(KozaGasToken _token, address _owner, address[] memory _actors) {
23
+ token = _token;
24
+ owner = _owner;
25
+ actors = _actors;
26
+ }
27
+
28
+ function _pickActor(uint256 seed) internal view returns (address) {
29
+ return actors[seed % actors.length];
30
+ }
31
+
32
+ /// @notice Bounded random transfer between actors.
33
+ function transfer(uint256 fromSeed, uint256 toSeed, uint256 amount) external {
34
+ callCount++;
35
+ address from = _pickActor(fromSeed);
36
+ address to = _pickActor(toSeed);
37
+
38
+ uint256 fromBalance = token.balanceOf(from);
39
+ if (fromBalance == 0) return;
40
+
41
+ amount = bound(amount, 1, fromBalance);
42
+
43
+ vm.prank(from);
44
+ try token.transfer(to, amount) returns (bool) {} catch {}
45
+ }
46
+
47
+ /// @notice Bounded random mint by owner.
48
+ function mint(uint256 toSeed, uint256 amount) external {
49
+ callCount++;
50
+ address to = _pickActor(toSeed);
51
+
52
+ uint256 cap = token.cap();
53
+ uint256 totalSupply = token.totalSupply();
54
+ if (totalSupply >= cap) return;
55
+
56
+ // Avoid uint256 overflow by capping at type(uint128).max
57
+ amount = bound(amount, 1, cap - totalSupply);
58
+
59
+ vm.prank(owner);
60
+ try token.mint(to, amount) {} catch {}
61
+ }
62
+
63
+ /// @notice Bounded random burn by an actor.
64
+ function burn(uint256 actorSeed, uint256 amount) external {
65
+ callCount++;
66
+ address actor = _pickActor(actorSeed);
67
+
68
+ uint256 balance = token.balanceOf(actor);
69
+ if (balance == 0) return;
70
+
71
+ amount = bound(amount, 1, balance);
72
+
73
+ vm.prank(actor);
74
+ try token.burn(amount) {} catch {}
75
+ }
76
+
77
+ /// @notice Returns the current actor list (used by invariant test).
78
+ function getActors() external view returns (address[] memory) {
79
+ return actors;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * @title ERC20GasInvariantTest
85
+ * @notice Stateful fuzzing invariants for KozaGasToken. Foundry's invariant runner
86
+ * randomly calls handler functions and asserts these invariants hold after
87
+ * every call.
88
+ *
89
+ * Tested invariants:
90
+ * 1. totalSupply() <= cap() — never exceeds the cap
91
+ * 2. sum(balanceOf(actors)) == totalSupply() — balance accounting consistent
92
+ */
93
+ contract ERC20GasInvariantTest is Test {
94
+ KozaGasToken internal token;
95
+ ERC20GasHandler internal handler;
96
+ address internal owner;
97
+ address[] internal actors;
98
+
99
+ uint256 internal constant CAP = 10_000_000 ether;
100
+ uint256 internal constant INITIAL_MINT = 1_000_000 ether;
101
+
102
+ function setUp() public {
103
+ owner = makeAddr("owner");
104
+
105
+ // Set up 5 actors and seed first one with the initial mint
106
+ actors.push(owner);
107
+ actors.push(makeAddr("alice"));
108
+ actors.push(makeAddr("bob"));
109
+ actors.push(makeAddr("charlie"));
110
+ actors.push(makeAddr("dave"));
111
+
112
+ token = new KozaGasToken("Koza Gas Token", "KGAS", CAP, INITIAL_MINT, owner);
113
+ handler = new ERC20GasHandler(token, owner, actors);
114
+
115
+ // Configure Foundry invariant runner to only call our handler
116
+ targetContract(address(handler));
117
+
118
+ // Restrict the function selectors that can be invoked
119
+ bytes4[] memory selectors = new bytes4[](3);
120
+ selectors[0] = handler.transfer.selector;
121
+ selectors[1] = handler.mint.selector;
122
+ selectors[2] = handler.burn.selector;
123
+ targetSelector(FuzzSelector({addr: address(handler), selectors: selectors}));
124
+ }
125
+
126
+ /// @notice totalSupply must never exceed cap.
127
+ function invariant_TotalSupplyDoesNotExceedCap() public view {
128
+ assertLe(token.totalSupply(), token.cap(), "totalSupply > cap");
129
+ }
130
+
131
+ /// @notice Sum of balances across all actors must equal totalSupply.
132
+ function invariant_SumOfBalancesEqualsTotalSupply() public view {
133
+ uint256 sum;
134
+ for (uint256 i = 0; i < actors.length; i++) {
135
+ sum += token.balanceOf(actors[i]);
136
+ }
137
+ assertEq(sum, token.totalSupply(), "sum(balances) != totalSupply");
138
+ }
139
+
140
+ /// @notice Owner remains constant during invariant runs (no ownership change exposed).
141
+ function invariant_OwnerDoesNotChange() public view {
142
+ assertEq(token.owner(), owner, "owner mutated unexpectedly");
143
+ }
144
+ }