+ {/* Gradient Background Overlay */}
+
+
+
+
+
+ {t("buyTickets.nextDraw")}
+
+
+
-
- ${jackpot.toLocaleString()} USDC
-
+
+ ${jackpot.toLocaleString()} USDC
+
-
- {useBlockBasedCountdown && timeRemainingFromBlocks ? (
-
- ) : (
-
+
+ {useBlockBasedCountdown && timeRemainingFromBlocks ? (
+
+ ) : (
+
+ )}
+
+
+ {isConnected && (
+
+
+ {t("buyTickets.buyButton")}
+
)}
-
-
- {t("buyTickets.buyButton")}
-
-
+
);
}
}
diff --git a/packages/nextjs/components/sections/PrizeDistributionSection.tsx b/packages/nextjs/components/sections/PrizeDistributionSection.tsx
index 5d0283cb..1964ed3a 100644
--- a/packages/nextjs/components/sections/PrizeDistributionSection.tsx
+++ b/packages/nextjs/components/sections/PrizeDistributionSection.tsx
@@ -21,7 +21,7 @@ export function PrizeDistributionSection({
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
- className="text-3xl md:text-4xl font-bold text-white mb-4"
+ className="text-3xl md:text-4xl font-bold mb-4 bg-gradient-to-r from-starkYellow to-white bg-clip-text text-transparent"
>
Prize Distribution
@@ -29,7 +29,7 @@ export function PrizeDistributionSection({
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
- className="text-lg text-gray-300 max-w-2xl mx-auto"
+ className="text-lg text-white/80 max-w-2xl mx-auto"
>
Understand how prizes are distributed across different winning
tiers. Transparency is key to our platform.
@@ -40,9 +40,18 @@ export function PrizeDistributionSection({
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.4 }}
- className="max-w-4xl mx-auto"
+ className="max-w-4xl mx-auto relative overflow-hidden rounded-2xl border border-white/10 bg-white/5 backdrop-blur-md p-8"
+ style={{ boxShadow: "0 10px 25px rgba(255,214,0,0.1)" }}
>
-
+ {/* Gradient Background Overlay */}
+
+
+ {/* Animated Background Glow */}
+
+
+
-
+
Prize amounts are estimates based on current pool size. Actual
prizes may vary.
diff --git a/packages/nextjs/components/token-mint.tsx b/packages/nextjs/components/token-mint.tsx
index f7c29261..ac88bf3c 100644
--- a/packages/nextjs/components/token-mint.tsx
+++ b/packages/nextjs/components/token-mint.tsx
@@ -308,22 +308,31 @@ export default function TokenMint({
document.body,
)}
-
+
+ {/* Gradient Background Overlay */}
+
+
+ {/* Animated Background Glow */}
+
+
{/* Header */}
-
-
+
+
Mint $tarkPlay
{/* Body */}
-
+
{/* Input STRK */}
-
+
-
-
+
Balance: {strkBalance.toFixed(4)} STRK
MAX
-
+
💡 Max 95% of balance to leave room for transaction fees
{/* Arrow */}
-
{/* Output STRKP */}
-
+
-
-
-
+
{isValidInput ? mintedAmount.toFixed(6) : "0.0"}
-
+
You will receive
-
+
{isValidInput ? mintedAmount.toFixed(6) : "0.0"} $TRKP
-
+
Current Balance
-
+
{starkPlayBalanceFormatted.toFixed(6)} $TRKP
{/* Fee details */}
-
+
- 1 STRK = 1 $TRKP
+ 1 STRK = 1 $TRKP
-
+
Mint Fee (
{feePercent !== undefined
? (feePercent * 100).toFixed(1) + "%"
@@ -428,22 +437,22 @@ export default function TokenMint({
: "--"
}% fee is applied to all mint operations`}
>
-
+
-
+
{isValidInput ? feeAmount.toFixed(6) : "0.0"} STRK
{feeLoading && (
-
Loading commission…
+
Loading commission…
)}
{feeError && (
-
Error obtaining commission
+
Error obtaining commission
)}
- You will receive
-
+ You will receive
+
{isValidInput ? mintedAmount.toFixed(6) : "0.0"} $TRKP
@@ -452,15 +461,20 @@ export default function TokenMint({
{/* Mint button */}
-
+
{isLoading ? "Minting..." : "Mint $TRKP"}
diff --git a/packages/nextjs/components/token-unmint.tsx b/packages/nextjs/components/token-unmint.tsx
index 9d743f63..65a81fce 100644
--- a/packages/nextjs/components/token-unmint.tsx
+++ b/packages/nextjs/components/token-unmint.tsx
@@ -131,18 +131,27 @@ export default function TokenUnmint({
document.body,
)}
-
-
-
+
+ {/* Gradient Background Overlay */}
+
+
+ {/* Animated Background Glow */}
+
+
+
+
Unmint STRKP
-
+
{/* Warning about convertible tokens */}
-
+
-
+
Note: Only STRKP tokens earned as lottery prizes can be
converted to STRK. Tokens minted for gameplay are NOT
@@ -153,26 +162,26 @@ export default function TokenUnmint({
{/* Input STRKP - Token to spend */}
-
+
-
-
-
+
{isValidSelection ? selectedAmount.toFixed(6) : "0.0"}
-
+
Balance: {prizeBalance.toFixed(4)} STRKP
-
+
{isValidSelection ? selectedAmount.toFixed(6) : "0.0"} STRKP
@@ -185,8 +194,8 @@ export default function TokenUnmint({
key={percentage}
className={`py-2 px-3 text-sm font-medium rounded-md transition-all duration-200 ${
selectedPercentage === percentage
- ? "bg-purple-500 text-white shadow-lg scale-105"
- : "bg-purple-500/20 hover:bg-purple-500/30 text-purple-300 hover:scale-102"
+ ? "bg-starkYellow text-black shadow-lg scale-105 border border-starkYellow/30"
+ : "bg-starkYellow/20 hover:bg-starkYellow/30 text-starkYellow hover:scale-102 border border-starkYellow/30"
} ${
isProcessing || prizeBalance <= 0
? "opacity-50 cursor-not-allowed"
@@ -202,17 +211,17 @@ export default function TokenUnmint({
{/* Arrow indicator */}
-
{/* Output STRK - Token to receive */}
-
+
-
-
-
+
{isValidSelection ? netAmount.toFixed(6) : "0.0"}
-
+
You will receive
-
+
{isValidSelection ? netAmount.toFixed(6) : "0.0"} STRK
{/* Unmint details */}
-
+
- $P
- 1 STRKP = 1 STRK
+ $P
+ 1 STRKP = 1 STRK
- Unmint Fee (3%)
+ Unmint Fee (3%)
-
+
-
+
{isValidSelection ? feeAmount.toFixed(6) : "0.0"} STRKP
- Net amount
+ Net amount
-
+
{isValidSelection ? netAmount.toFixed(6) : "0.0"} STRK
@@ -271,15 +280,20 @@ export default function TokenUnmint({
{error &&
{error}
}
-
+
{isProcessing ? "Unminting..." : "Unmint STRKP"}
diff --git a/packages/nextjs/components/ui/StarkLottoLogo.tsx b/packages/nextjs/components/ui/StarkLottoLogo.tsx
new file mode 100644
index 00000000..9a6d7bdc
--- /dev/null
+++ b/packages/nextjs/components/ui/StarkLottoLogo.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import { motion } from "framer-motion";
+import Link from "next/link";
+
+interface StarkLottoLogoProps {
+ variant?: "landing" | "dapp";
+ onClick?: () => void;
+ href?: string;
+ className?: string;
+}
+
+export function StarkLottoLogo({
+ variant = "landing",
+ onClick,
+ href = "/",
+ className = "",
+}: StarkLottoLogoProps) {
+ const logoContent = (
+
+
+
+
+ );
+
+ if (onClick) {
+ return (
+
+ {logoContent}
+
+ );
+ }
+
+ return (
+
+
+ {logoContent}
+
+
+ );
+}
diff --git a/packages/snfoundry/contracts/src/Lottery.cairo b/packages/snfoundry/contracts/src/Lottery.cairo
index ebc27024..9845141c 100644
--- a/packages/snfoundry/contracts/src/Lottery.cairo
+++ b/packages/snfoundry/contracts/src/Lottery.cairo
@@ -77,7 +77,9 @@ pub trait ILottery
{
number5: u16,
) -> u8;
fn CreateNewDraw(ref self: TContractState, accumulatedPrize: u256);
- fn CreateNewDrawWithDuration(ref self: TContractState, accumulatedPrize: u256, duration_blocks: u64);
+ fn CreateNewDrawWithDuration(
+ ref self: TContractState, accumulatedPrize: u256, duration_blocks: u64,
+ );
fn GetCurrentActiveDraw(self: @TContractState) -> (u64, bool);
fn SetDrawInactive(ref self: TContractState, drawId: u64);
fn SetTicketPrice(ref self: TContractState, price: u256);
@@ -312,9 +314,7 @@ pub mod Lottery {
fixedPrize2Matches: u256,
accumulatedPrize: u256,
userTickets: Map<(ContractAddress, u64), felt252>,
- userTicketCount: Map<
- (ContractAddress, u64), u32,
- >, // (user, drawId) -> user ticket count
+ userTicketCount: Map<(ContractAddress, u64), u32>, // (user, drawId) -> user ticket count
// (user, drawId, index)-> ticketId
userTicketIds: Map<(ContractAddress, u64, u32), felt252>,
draws: Map,
@@ -659,7 +659,9 @@ pub mod Lottery {
}
//=======================================================================================
- fn CreateNewDrawWithDuration(ref self: ContractState, accumulatedPrize: u256, duration_blocks: u64) {
+ fn CreateNewDrawWithDuration(
+ ref self: ContractState, accumulatedPrize: u256, duration_blocks: u64,
+ ) {
// Validate that the accumulated prize is not negative
assert(accumulatedPrize >= 0, 'Invalid accumulated prize');
// Validate that duration is not zero
diff --git a/packages/snfoundry/contracts/src/StarkPlayERC20.cairo b/packages/snfoundry/contracts/src/StarkPlayERC20.cairo
index 26dbb79b..f72fc5fa 100644
--- a/packages/snfoundry/contracts/src/StarkPlayERC20.cairo
+++ b/packages/snfoundry/contracts/src/StarkPlayERC20.cairo
@@ -1,404 +1,404 @@
-use starknet::ContractAddress;
-#[starknet::interface]
-trait ITestingHelpers {
- fn getTotalSupply(self: @TContractState) -> u256;
- fn getAllAllowances(self: @TContractState, account: ContractAddress) -> (u256, u256);
-}
-
-// SPDX-License-Identifier: MIT
-// Compatible with OpenZeppelin Contracts for Cairo ^0.20.0
+use starknet::ContractAddress;
+#[starknet::interface]
+trait ITestingHelpers {
+ fn getTotalSupply(self: @TContractState) -> u256;
+ fn getAllAllowances(self: @TContractState, account: ContractAddress) -> (u256, u256);
+}
+
+// SPDX-License-Identifier: MIT
+// Compatible with OpenZeppelin Contracts for Cairo ^0.20.0
pub const INITIAL_SUPPLY: u256 = 1000; // initial supply added 1000 for testing purposes
-
-const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");
-const BURNER_ROLE: felt252 = selector!("BURNER_ROLE");
-const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");
-const PRIZE_ASSIGNER_ROLE: felt252 = selector!("PRIZE_ASSIGNER_ROLE");
-
-#[starknet::interface]
-pub trait IMintable {
- fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256);
- fn grant_minter_role(ref self: TContractState, minter: ContractAddress);
- fn revoke_minter_role(ref self: TContractState, minter: ContractAddress);
- fn set_minter_allowance(ref self: TContractState, minter: ContractAddress, allowance: u256);
- fn get_minter_allowance(self: @TContractState, minter: ContractAddress) -> u256;
- fn get_authorized_minters(self: @TContractState) -> Array;
-}
-
-#[starknet::interface]
-pub trait IBurnable {
- fn burn(ref self: TContractState, amount: u256);
- fn burn_from(ref self: TContractState, account: ContractAddress, amount: u256);
- fn grant_burner_role(ref self: TContractState, burner: ContractAddress);
- fn revoke_burner_role(ref self: TContractState, burner: ContractAddress);
- fn set_burner_allowance(ref self: TContractState, burner: ContractAddress, allowance: u256);
- fn get_burner_allowance(self: @TContractState, burner: ContractAddress) -> u256;
- fn get_authorized_burners(self: @TContractState) -> Array;
-}
-
-#[starknet::interface]
-pub trait IPrizeToken {
- fn assign_prize_tokens(ref self: TContractState, recipient: ContractAddress, amount: u256);
- fn get_prize_balance(self: @TContractState, account: ContractAddress) -> u256;
- fn grant_prize_assigner_role(ref self: TContractState, assigner: ContractAddress);
- fn revoke_prize_assigner_role(ref self: TContractState, assigner: ContractAddress);
-}
-
-#[starknet::contract]
-pub mod StarkPlayERC20 {
- use openzeppelin_access::accesscontrol::{AccessControlComponent, DEFAULT_ADMIN_ROLE};
- use openzeppelin_introspection::src5::SRC5Component;
- use openzeppelin_security::PausableComponent;
- use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
- use openzeppelin_upgrades::UpgradeableComponent;
- use openzeppelin_upgrades::interface::IUpgradeable;
- use starknet::storage::{
- Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePathEntry,
- StoragePointerReadAccess, StoragePointerWriteAccess,
- };
- use starknet::{ClassHash, ContractAddress, get_caller_address};
- use super::{
- BURNER_ROLE, IBurnable, IMintable, IPrizeToken, ITestingHelpers, MINTER_ROLE, PAUSER_ROLE,
- PRIZE_ASSIGNER_ROLE,
- };
-
- component!(path: ERC20Component, storage: erc20, event: ERC20Event);
- component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);
- component!(path: PausableComponent, storage: pausable, event: PausableEvent);
- component!(path: SRC5Component, storage: src5, event: SRC5Event);
- component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);
-
- #[abi(embed_v0)]
- impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;
- #[abi(embed_v0)]
- impl PausableImpl = PausableComponent::PausableImpl;
- #[abi(embed_v0)]
- impl AccessControlMixinImpl =
- AccessControlComponent::AccessControlMixinImpl;
-
- impl ERC20InternalImpl = ERC20Component::InternalImpl;
- impl PausableInternalImpl = PausableComponent::InternalImpl;
- impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;
- impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;
-
- #[storage]
- struct Storage {
- #[substorage(v0)]
- erc20: ERC20Component::Storage,
- #[substorage(v0)]
- pausable: PausableComponent::Storage,
- #[substorage(v0)]
- accesscontrol: AccessControlComponent::Storage,
- #[substorage(v0)]
- src5: SRC5Component::Storage,
- #[substorage(v0)]
- upgradeable: UpgradeableComponent::Storage,
- minter_allowance: Map,
- burner_allowance: Map,
- minters: Map,
- burners: Map,
- minters_count: u256,
- burners_count: u256,
- minter_index: Map,
- burner_index: Map,
- prize_balances: Map,
- prize_assigners: Map,
- prize_assigners_count: u256,
- prize_assigner_index: Map,
- }
-
- #[derive(Drop, starknet::Event)]
- pub struct Burn {
- #[key]
- pub burner: ContractAddress,
- #[key]
- pub amount: u256,
- }
-
- #[derive(Drop, starknet::Event)]
- pub struct Mint {
- #[key]
- pub recipient: ContractAddress,
- #[key]
- pub amount: u256,
- }
-
- #[derive(Drop, starknet::Event)]
- pub struct MinterAllowanceSet {
- #[key]
- pub minter: ContractAddress,
- #[key]
- pub allowance: u256,
- }
-
- #[derive(Drop, starknet::Event)]
- pub struct BurnerAllowanceSet {
- #[key]
- pub burner: ContractAddress,
- #[key]
- pub allowance: u256,
- }
-
- #[derive(Drop, starknet::Event)]
- pub struct PrizeTokensAssigned {
- #[key]
- pub recipient: ContractAddress,
- #[key]
- pub amount: u256,
- }
-
- #[event]
- #[derive(Drop, starknet::Event)]
- pub enum Event {
- #[flat]
- ERC20Event: ERC20Component::Event,
- #[flat]
- PausableEvent: PausableComponent::Event,
- #[flat]
- AccessControlEvent: AccessControlComponent::Event,
- #[flat]
- SRC5Event: SRC5Component::Event,
- #[flat]
- UpgradeableEvent: UpgradeableComponent::Event,
- Burn: Burn,
- Mint: Mint,
- MinterAllowanceSet: MinterAllowanceSet,
- BurnerAllowanceSet: BurnerAllowanceSet,
- PrizeTokensAssigned: PrizeTokensAssigned,
- }
-
- #[constructor]
- fn constructor(ref self: ContractState, recipient: ContractAddress, admin: ContractAddress) {
- self.erc20.initializer("$tarkPlay", "STARKP");
- self.accesscontrol.initializer();
-
- // Grant admin role to the specified admin
- self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, admin);
- self.accesscontrol._grant_role(PAUSER_ROLE, admin);
-
- // Always grant admin role temporarily to the caller for auto-configuration
- // This allows vault to configure itself during deployment
- let caller = get_caller_address();
- self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, caller);
-
- // Mint initial supply for testing
- self.erc20.mint(recipient, 1000);
- }
-
- #[abi(embed_v0)]
- impl MintableImpl of IMintable {
- fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) {
- self.pausable.assert_not_paused();
- let caller = get_caller_address();
- self.accesscontrol.assert_only_role(MINTER_ROLE);
- let allowance = self.minter_allowance.entry(caller).read();
- assert(allowance >= amount, 'Insufficient minter allowance');
- self.minter_allowance.entry(caller).write(allowance - amount);
- self.erc20.mint(recipient, amount);
- self.emit(Mint { recipient, amount });
- }
-
- fn grant_minter_role(ref self: ContractState, minter: ContractAddress) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- assert(is_contract(minter), 'Minter must be a contract');
- self.accesscontrol._grant_role(MINTER_ROLE, minter);
- let index = self.minters_count.read();
- self.minters.entry(index).write(minter);
- self.minter_index.entry(minter).write(index);
- self.minters_count.write(index + 1);
- }
-
- fn revoke_minter_role(ref self: ContractState, minter: ContractAddress) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- self.accesscontrol._revoke_role(MINTER_ROLE, minter);
- let index = self.minter_index.read(minter);
- let last_index = self.minters_count.read() - 1;
- if index != last_index {
- let last_minter = self.minters.entry(last_index).read();
- self.minters.entry(index).write(last_minter);
- self.minter_index.entry(last_minter).write(index);
- }
- self.minters.entry(last_index).write(zero_address_const());
- self.minter_index.write(minter, 0);
- self.minters_count.write(last_index);
- }
-
- fn set_minter_allowance(ref self: ContractState, minter: ContractAddress, allowance: u256) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- self.minter_allowance.entry(minter).write(allowance);
- self.emit(MinterAllowanceSet { minter, allowance });
- }
-
- fn get_minter_allowance(self: @ContractState, minter: ContractAddress) -> u256 {
- self.minter_allowance.entry(minter).read()
- }
-
- fn get_authorized_minters(self: @ContractState) -> Array {
- let mut minters = ArrayTrait::new();
- let count = self.minters_count.read();
- let mut i = 0;
- while i != count {
- minters.append(self.minters.entry(i).read());
- i += 1;
- }
- minters
- }
- }
-
- #[abi(embed_v0)]
- impl BurnableImpl of IBurnable {
- fn burn(ref self: ContractState, amount: u256) {
- self.pausable.assert_not_paused();
- let burner = get_caller_address();
- self.accesscontrol.assert_only_role(BURNER_ROLE);
- let allowance = self.burner_allowance.entry(burner).read();
- assert(allowance >= amount, 'Insufficient burner allowance');
- self.burner_allowance.entry(burner).write(allowance - amount);
- let prize_balance = self.prize_balances.entry(burner).read();
- if prize_balance >= amount {
- self.prize_balances.entry(burner).write(prize_balance - amount);
- } else {
- self.prize_balances.entry(burner).write(0);
- }
- self.erc20.burn(burner, amount);
- self.emit(Burn { burner, amount });
- }
-
- fn burn_from(ref self: ContractState, account: ContractAddress, amount: u256) {
- let caller = get_caller_address();
- self.accesscontrol.assert_only_role(BURNER_ROLE);
- let allowance = self.burner_allowance.entry(caller).read();
- assert(allowance >= amount, 'Insufficient burner allowance');
- self.burner_allowance.entry(caller).write(allowance - amount);
- let prize_balance = self.prize_balances.entry(account).read();
- if prize_balance >= amount {
- self.prize_balances.entry(account).write(prize_balance - amount);
- } else {
- self.prize_balances.entry(account).write(0);
- }
- self.erc20.burn(account, amount);
- self.emit(Burn { burner: account, amount });
- }
-
- fn grant_burner_role(ref self: ContractState, burner: ContractAddress) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- assert(is_contract(burner), 'Burner must be a contract');
- self.accesscontrol._grant_role(BURNER_ROLE, burner);
- let index = self.burners_count.read();
- self.burners.entry(index).write(burner);
- self.burner_index.entry(burner).write(index);
- self.burners_count.write(index + 1);
- }
-
- fn revoke_burner_role(ref self: ContractState, burner: ContractAddress) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- self.accesscontrol._revoke_role(BURNER_ROLE, burner);
- let index = self.burner_index.read(burner);
- let last_index = self.burners_count.read() - 1;
- if index != last_index {
- let last_burner = self.burners.entry(last_index).read();
- self.burners.entry(index).write(last_burner);
- self.burner_index.entry(last_burner).write(index);
- }
- self.burners.entry(last_index).write(zero_address_const());
- self.burner_index.write(burner, 0);
- self.burners_count.write(last_index);
- }
-
- fn set_burner_allowance(ref self: ContractState, burner: ContractAddress, allowance: u256) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- self.burner_allowance.entry(burner).write(allowance);
- self.emit(BurnerAllowanceSet { burner, allowance });
- }
-
- fn get_burner_allowance(self: @ContractState, burner: ContractAddress) -> u256 {
- self.burner_allowance.entry(burner).read()
- }
-
- fn get_authorized_burners(self: @ContractState) -> Array {
- let mut burners = ArrayTrait::new();
- let count = self.burners_count.read();
- let mut i = 0;
- while i != count {
- burners.append(self.burners.read(i));
- i += 1;
- }
- burners
- }
- }
-
- #[abi(embed_v0)]
- impl PrizeTokenImpl of IPrizeToken {
- fn assign_prize_tokens(ref self: ContractState, recipient: ContractAddress, amount: u256) {
- self.pausable.assert_not_paused();
- self.accesscontrol.assert_only_role(PRIZE_ASSIGNER_ROLE);
- let current_prize_balance = self.prize_balances.entry(recipient).read();
- self.prize_balances.entry(recipient).write(current_prize_balance + amount);
- self.erc20.mint(recipient, amount);
- self.emit(PrizeTokensAssigned { recipient, amount });
- }
-
- fn get_prize_balance(self: @ContractState, account: ContractAddress) -> u256 {
- self.prize_balances.entry(account).read()
- }
-
- fn grant_prize_assigner_role(ref self: ContractState, assigner: ContractAddress) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- assert(is_contract(assigner), 'Assigner must be a contract');
- self.accesscontrol._grant_role(PRIZE_ASSIGNER_ROLE, assigner);
- let index = self.prize_assigners_count.read();
- self.prize_assigners.entry(index).write(assigner);
- self.prize_assigner_index.entry(assigner).write(index);
- self.prize_assigners_count.write(index + 1);
- }
-
- fn revoke_prize_assigner_role(ref self: ContractState, assigner: ContractAddress) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- self.accesscontrol._revoke_role(PRIZE_ASSIGNER_ROLE, assigner);
- let index = self.prize_assigner_index.read(assigner);
- let last_index = self.prize_assigners_count.read() - 1;
- if index != last_index {
- let last_assigner = self.prize_assigners.entry(last_index).read();
- self.prize_assigners.entry(index).write(last_assigner);
- self.prize_assigner_index.entry(last_assigner).write(index);
- }
- self.prize_assigners.entry(last_index).write(zero_address_const());
- self.prize_assigner_index.entry(assigner).write(0);
- self.prize_assigners_count.write(last_index);
- }
- }
-
- #[abi(embed_v0)]
- impl UpgradeableImpl of IUpgradeable {
- fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
- self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
- self.upgradeable.upgrade(new_class_hash);
- }
- }
-
-
- fn is_contract(address: ContractAddress) -> bool {
- // Avoid zero address
- if address == zero_address_const() {
- return false;
- }
-
- true
- }
-
- fn zero_address_const() -> ContractAddress {
- '0x0'.try_into().unwrap()
- }
- #[abi(embed_v0)]
- impl TestingHelpers of ITestingHelpers {
- fn getTotalSupply(self: @ContractState) -> u256 {
- self.erc20.total_supply()
- }
-
- fn getAllAllowances(self: @ContractState, account: ContractAddress) -> (u256, u256) {
- let minter_allowance = self.minter_allowance.read(account);
- let burner_allowance = self.burner_allowance.read(account);
- (minter_allowance, burner_allowance)
- }
- }
-}
-
+
+const MINTER_ROLE: felt252 = selector!("MINTER_ROLE");
+const BURNER_ROLE: felt252 = selector!("BURNER_ROLE");
+const PAUSER_ROLE: felt252 = selector!("PAUSER_ROLE");
+const PRIZE_ASSIGNER_ROLE: felt252 = selector!("PRIZE_ASSIGNER_ROLE");
+
+#[starknet::interface]
+pub trait IMintable {
+ fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256);
+ fn grant_minter_role(ref self: TContractState, minter: ContractAddress);
+ fn revoke_minter_role(ref self: TContractState, minter: ContractAddress);
+ fn set_minter_allowance(ref self: TContractState, minter: ContractAddress, allowance: u256);
+ fn get_minter_allowance(self: @TContractState, minter: ContractAddress) -> u256;
+ fn get_authorized_minters(self: @TContractState) -> Array;
+}
+
+#[starknet::interface]
+pub trait IBurnable {
+ fn burn(ref self: TContractState, amount: u256);
+ fn burn_from(ref self: TContractState, account: ContractAddress, amount: u256);
+ fn grant_burner_role(ref self: TContractState, burner: ContractAddress);
+ fn revoke_burner_role(ref self: TContractState, burner: ContractAddress);
+ fn set_burner_allowance(ref self: TContractState, burner: ContractAddress, allowance: u256);
+ fn get_burner_allowance(self: @TContractState, burner: ContractAddress) -> u256;
+ fn get_authorized_burners(self: @TContractState) -> Array;
+}
+
+#[starknet::interface]
+pub trait IPrizeToken {
+ fn assign_prize_tokens(ref self: TContractState, recipient: ContractAddress, amount: u256);
+ fn get_prize_balance(self: @TContractState, account: ContractAddress) -> u256;
+ fn grant_prize_assigner_role(ref self: TContractState, assigner: ContractAddress);
+ fn revoke_prize_assigner_role(ref self: TContractState, assigner: ContractAddress);
+}
+
+#[starknet::contract]
+pub mod StarkPlayERC20 {
+ use openzeppelin_access::accesscontrol::{AccessControlComponent, DEFAULT_ADMIN_ROLE};
+ use openzeppelin_introspection::src5::SRC5Component;
+ use openzeppelin_security::PausableComponent;
+ use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
+ use openzeppelin_upgrades::UpgradeableComponent;
+ use openzeppelin_upgrades::interface::IUpgradeable;
+ use starknet::storage::{
+ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePathEntry,
+ StoragePointerReadAccess, StoragePointerWriteAccess,
+ };
+ use starknet::{ClassHash, ContractAddress, get_caller_address};
+ use super::{
+ BURNER_ROLE, IBurnable, IMintable, IPrizeToken, ITestingHelpers, MINTER_ROLE, PAUSER_ROLE,
+ PRIZE_ASSIGNER_ROLE,
+ };
+
+ component!(path: ERC20Component, storage: erc20, event: ERC20Event);
+ component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent);
+ component!(path: PausableComponent, storage: pausable, event: PausableEvent);
+ component!(path: SRC5Component, storage: src5, event: SRC5Event);
+ component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);
+
+ #[abi(embed_v0)]
+ impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl;
+ #[abi(embed_v0)]
+ impl PausableImpl = PausableComponent::PausableImpl;
+ #[abi(embed_v0)]
+ impl AccessControlMixinImpl =
+ AccessControlComponent::AccessControlMixinImpl;
+
+ impl ERC20InternalImpl = ERC20Component::InternalImpl;
+ impl PausableInternalImpl = PausableComponent::InternalImpl;
+ impl AccessControlInternalImpl = AccessControlComponent::InternalImpl;
+ impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl;
+
+ #[storage]
+ struct Storage {
+ #[substorage(v0)]
+ erc20: ERC20Component::Storage,
+ #[substorage(v0)]
+ pausable: PausableComponent::Storage,
+ #[substorage(v0)]
+ accesscontrol: AccessControlComponent::Storage,
+ #[substorage(v0)]
+ src5: SRC5Component::Storage,
+ #[substorage(v0)]
+ upgradeable: UpgradeableComponent::Storage,
+ minter_allowance: Map,
+ burner_allowance: Map,
+ minters: Map,
+ burners: Map,
+ minters_count: u256,
+ burners_count: u256,
+ minter_index: Map,
+ burner_index: Map,
+ prize_balances: Map,
+ prize_assigners: Map,
+ prize_assigners_count: u256,
+ prize_assigner_index: Map,
+ }
+
+ #[derive(Drop, starknet::Event)]
+ pub struct Burn {
+ #[key]
+ pub burner: ContractAddress,
+ #[key]
+ pub amount: u256,
+ }
+
+ #[derive(Drop, starknet::Event)]
+ pub struct Mint {
+ #[key]
+ pub recipient: ContractAddress,
+ #[key]
+ pub amount: u256,
+ }
+
+ #[derive(Drop, starknet::Event)]
+ pub struct MinterAllowanceSet {
+ #[key]
+ pub minter: ContractAddress,
+ #[key]
+ pub allowance: u256,
+ }
+
+ #[derive(Drop, starknet::Event)]
+ pub struct BurnerAllowanceSet {
+ #[key]
+ pub burner: ContractAddress,
+ #[key]
+ pub allowance: u256,
+ }
+
+ #[derive(Drop, starknet::Event)]
+ pub struct PrizeTokensAssigned {
+ #[key]
+ pub recipient: ContractAddress,
+ #[key]
+ pub amount: u256,
+ }
+
+ #[event]
+ #[derive(Drop, starknet::Event)]
+ pub enum Event {
+ #[flat]
+ ERC20Event: ERC20Component::Event,
+ #[flat]
+ PausableEvent: PausableComponent::Event,
+ #[flat]
+ AccessControlEvent: AccessControlComponent::Event,
+ #[flat]
+ SRC5Event: SRC5Component::Event,
+ #[flat]
+ UpgradeableEvent: UpgradeableComponent::Event,
+ Burn: Burn,
+ Mint: Mint,
+ MinterAllowanceSet: MinterAllowanceSet,
+ BurnerAllowanceSet: BurnerAllowanceSet,
+ PrizeTokensAssigned: PrizeTokensAssigned,
+ }
+
+ #[constructor]
+ fn constructor(ref self: ContractState, recipient: ContractAddress, admin: ContractAddress) {
+ self.erc20.initializer("$tarkPlay", "STARKP");
+ self.accesscontrol.initializer();
+
+ // Grant admin role to the specified admin
+ self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, admin);
+ self.accesscontrol._grant_role(PAUSER_ROLE, admin);
+
+ // Always grant admin role temporarily to the caller for auto-configuration
+ // This allows vault to configure itself during deployment
+ let caller = get_caller_address();
+ self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, caller);
+
+ // Mint initial supply for testing
+ self.erc20.mint(recipient, 1000);
+ }
+
+ #[abi(embed_v0)]
+ impl MintableImpl of IMintable {
+ fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) {
+ self.pausable.assert_not_paused();
+ let caller = get_caller_address();
+ self.accesscontrol.assert_only_role(MINTER_ROLE);
+ let allowance = self.minter_allowance.entry(caller).read();
+ assert(allowance >= amount, 'Insufficient minter allowance');
+ self.minter_allowance.entry(caller).write(allowance - amount);
+ self.erc20.mint(recipient, amount);
+ self.emit(Mint { recipient, amount });
+ }
+
+ fn grant_minter_role(ref self: ContractState, minter: ContractAddress) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ assert(is_contract(minter), 'Minter must be a contract');
+ self.accesscontrol._grant_role(MINTER_ROLE, minter);
+ let index = self.minters_count.read();
+ self.minters.entry(index).write(minter);
+ self.minter_index.entry(minter).write(index);
+ self.minters_count.write(index + 1);
+ }
+
+ fn revoke_minter_role(ref self: ContractState, minter: ContractAddress) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ self.accesscontrol._revoke_role(MINTER_ROLE, minter);
+ let index = self.minter_index.read(minter);
+ let last_index = self.minters_count.read() - 1;
+ if index != last_index {
+ let last_minter = self.minters.entry(last_index).read();
+ self.minters.entry(index).write(last_minter);
+ self.minter_index.entry(last_minter).write(index);
+ }
+ self.minters.entry(last_index).write(zero_address_const());
+ self.minter_index.write(minter, 0);
+ self.minters_count.write(last_index);
+ }
+
+ fn set_minter_allowance(ref self: ContractState, minter: ContractAddress, allowance: u256) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ self.minter_allowance.entry(minter).write(allowance);
+ self.emit(MinterAllowanceSet { minter, allowance });
+ }
+
+ fn get_minter_allowance(self: @ContractState, minter: ContractAddress) -> u256 {
+ self.minter_allowance.entry(minter).read()
+ }
+
+ fn get_authorized_minters(self: @ContractState) -> Array {
+ let mut minters = ArrayTrait::new();
+ let count = self.minters_count.read();
+ let mut i = 0;
+ while i != count {
+ minters.append(self.minters.entry(i).read());
+ i += 1;
+ }
+ minters
+ }
+ }
+
+ #[abi(embed_v0)]
+ impl BurnableImpl of IBurnable {
+ fn burn(ref self: ContractState, amount: u256) {
+ self.pausable.assert_not_paused();
+ let burner = get_caller_address();
+ self.accesscontrol.assert_only_role(BURNER_ROLE);
+ let allowance = self.burner_allowance.entry(burner).read();
+ assert(allowance >= amount, 'Insufficient burner allowance');
+ self.burner_allowance.entry(burner).write(allowance - amount);
+ let prize_balance = self.prize_balances.entry(burner).read();
+ if prize_balance >= amount {
+ self.prize_balances.entry(burner).write(prize_balance - amount);
+ } else {
+ self.prize_balances.entry(burner).write(0);
+ }
+ self.erc20.burn(burner, amount);
+ self.emit(Burn { burner, amount });
+ }
+
+ fn burn_from(ref self: ContractState, account: ContractAddress, amount: u256) {
+ let caller = get_caller_address();
+ self.accesscontrol.assert_only_role(BURNER_ROLE);
+ let allowance = self.burner_allowance.entry(caller).read();
+ assert(allowance >= amount, 'Insufficient burner allowance');
+ self.burner_allowance.entry(caller).write(allowance - amount);
+ let prize_balance = self.prize_balances.entry(account).read();
+ if prize_balance >= amount {
+ self.prize_balances.entry(account).write(prize_balance - amount);
+ } else {
+ self.prize_balances.entry(account).write(0);
+ }
+ self.erc20.burn(account, amount);
+ self.emit(Burn { burner: account, amount });
+ }
+
+ fn grant_burner_role(ref self: ContractState, burner: ContractAddress) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ assert(is_contract(burner), 'Burner must be a contract');
+ self.accesscontrol._grant_role(BURNER_ROLE, burner);
+ let index = self.burners_count.read();
+ self.burners.entry(index).write(burner);
+ self.burner_index.entry(burner).write(index);
+ self.burners_count.write(index + 1);
+ }
+
+ fn revoke_burner_role(ref self: ContractState, burner: ContractAddress) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ self.accesscontrol._revoke_role(BURNER_ROLE, burner);
+ let index = self.burner_index.read(burner);
+ let last_index = self.burners_count.read() - 1;
+ if index != last_index {
+ let last_burner = self.burners.entry(last_index).read();
+ self.burners.entry(index).write(last_burner);
+ self.burner_index.entry(last_burner).write(index);
+ }
+ self.burners.entry(last_index).write(zero_address_const());
+ self.burner_index.write(burner, 0);
+ self.burners_count.write(last_index);
+ }
+
+ fn set_burner_allowance(ref self: ContractState, burner: ContractAddress, allowance: u256) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ self.burner_allowance.entry(burner).write(allowance);
+ self.emit(BurnerAllowanceSet { burner, allowance });
+ }
+
+ fn get_burner_allowance(self: @ContractState, burner: ContractAddress) -> u256 {
+ self.burner_allowance.entry(burner).read()
+ }
+
+ fn get_authorized_burners(self: @ContractState) -> Array {
+ let mut burners = ArrayTrait::new();
+ let count = self.burners_count.read();
+ let mut i = 0;
+ while i != count {
+ burners.append(self.burners.read(i));
+ i += 1;
+ }
+ burners
+ }
+ }
+
+ #[abi(embed_v0)]
+ impl PrizeTokenImpl of IPrizeToken {
+ fn assign_prize_tokens(ref self: ContractState, recipient: ContractAddress, amount: u256) {
+ self.pausable.assert_not_paused();
+ self.accesscontrol.assert_only_role(PRIZE_ASSIGNER_ROLE);
+ let current_prize_balance = self.prize_balances.entry(recipient).read();
+ self.prize_balances.entry(recipient).write(current_prize_balance + amount);
+ self.erc20.mint(recipient, amount);
+ self.emit(PrizeTokensAssigned { recipient, amount });
+ }
+
+ fn get_prize_balance(self: @ContractState, account: ContractAddress) -> u256 {
+ self.prize_balances.entry(account).read()
+ }
+
+ fn grant_prize_assigner_role(ref self: ContractState, assigner: ContractAddress) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ assert(is_contract(assigner), 'Assigner must be a contract');
+ self.accesscontrol._grant_role(PRIZE_ASSIGNER_ROLE, assigner);
+ let index = self.prize_assigners_count.read();
+ self.prize_assigners.entry(index).write(assigner);
+ self.prize_assigner_index.entry(assigner).write(index);
+ self.prize_assigners_count.write(index + 1);
+ }
+
+ fn revoke_prize_assigner_role(ref self: ContractState, assigner: ContractAddress) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ self.accesscontrol._revoke_role(PRIZE_ASSIGNER_ROLE, assigner);
+ let index = self.prize_assigner_index.read(assigner);
+ let last_index = self.prize_assigners_count.read() - 1;
+ if index != last_index {
+ let last_assigner = self.prize_assigners.entry(last_index).read();
+ self.prize_assigners.entry(index).write(last_assigner);
+ self.prize_assigner_index.entry(last_assigner).write(index);
+ }
+ self.prize_assigners.entry(last_index).write(zero_address_const());
+ self.prize_assigner_index.entry(assigner).write(0);
+ self.prize_assigners_count.write(last_index);
+ }
+ }
+
+ #[abi(embed_v0)]
+ impl UpgradeableImpl of IUpgradeable {
+ fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
+ self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE);
+ self.upgradeable.upgrade(new_class_hash);
+ }
+ }
+
+
+ fn is_contract(address: ContractAddress) -> bool {
+ // Avoid zero address
+ if address == zero_address_const() {
+ return false;
+ }
+
+ true
+ }
+
+ fn zero_address_const() -> ContractAddress {
+ '0x0'.try_into().unwrap()
+ }
+ #[abi(embed_v0)]
+ impl TestingHelpers of ITestingHelpers {
+ fn getTotalSupply(self: @ContractState) -> u256 {
+ self.erc20.total_supply()
+ }
+
+ fn getAllAllowances(self: @ContractState, account: ContractAddress) -> (u256, u256) {
+ let minter_allowance = self.minter_allowance.read(account);
+ let burner_allowance = self.burner_allowance.read(account);
+ (minter_allowance, burner_allowance)
+ }
+ }
+}
+
diff --git a/packages/snfoundry/contracts/src/StarkPlayVault.cairo b/packages/snfoundry/contracts/src/StarkPlayVault.cairo
index 015c1087..34320c44 100644
--- a/packages/snfoundry/contracts/src/StarkPlayVault.cairo
+++ b/packages/snfoundry/contracts/src/StarkPlayVault.cairo
@@ -48,14 +48,14 @@ pub mod StarkPlayVault {
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//imports
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- use openzeppelin_access::ownable::OwnableComponent;
- use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
- use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
- use starknet::{ContractAddress, get_caller_address, get_contract_address};
use crate::StarkPlayERC20::{
IBurnableDispatcher, IBurnableDispatcherTrait, IMintableDispatcher,
IMintableDispatcherTrait, IPrizeTokenDispatcher, IPrizeTokenDispatcherTrait,
};
+ use openzeppelin_access::ownable::OwnableComponent;
+ use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
+ use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
+ use starknet::{ContractAddress, get_caller_address, get_contract_address};
use super::IStarkPlayVault;
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
diff --git a/packages/snfoundry/contracts/src/YourContract.cairo b/packages/snfoundry/contracts/src/YourContract.cairo
index 9b7f426a..7562231e 100644
--- a/packages/snfoundry/contracts/src/YourContract.cairo
+++ b/packages/snfoundry/contracts/src/YourContract.cairo
@@ -1,113 +1,113 @@
-#[starknet::interface]
-pub trait IYourContract {
- fn greeting(self: @TContractState) -> ByteArray;
- fn set_greeting(ref self: TContractState, new_greeting: ByteArray, amount_strk: Option);
- fn withdraw(ref self: TContractState);
- fn premium(self: @TContractState) -> bool;
-}
-
-#[starknet::contract]
-pub mod YourContract {
- use openzeppelin_access::ownable::OwnableComponent;
- use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
- use starknet::storage::{
- Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess,
- StoragePointerWriteAccess,
- };
- use starknet::{ContractAddress, get_caller_address, get_contract_address};
- use super::IYourContract;
-
- component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
-
- #[abi(embed_v0)]
- impl OwnableImpl = OwnableComponent::OwnableImpl;
- impl OwnableInternalImpl = OwnableComponent::InternalImpl;
-
- pub const FELT_STRK_CONTRACT: felt252 =
- 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d;
-
- #[event]
- #[derive(Drop, starknet::Event)]
- enum Event {
- #[flat]
- OwnableEvent: OwnableComponent::Event,
- GreetingChanged: GreetingChanged,
- }
-
- #[derive(Drop, starknet::Event)]
- struct GreetingChanged {
- #[key]
- greeting_setter: ContractAddress,
- #[key]
- new_greeting: ByteArray,
- premium: bool,
- value: Option,
- }
-
- #[storage]
- struct Storage {
- greeting: ByteArray,
- premium: bool,
- total_counter: u256,
- user_greeting_counter: Map,
- #[substorage(v0)]
- ownable: OwnableComponent::Storage,
- }
-
- #[constructor]
- fn constructor(ref self: ContractState, owner: ContractAddress) {
- self.greeting.write("Building Unstoppable Apps!!!");
- self.ownable.initializer(owner);
- }
-
- #[abi(embed_v0)]
- impl YourContractImpl of IYourContract {
- fn greeting(self: @ContractState) -> ByteArray {
- self.greeting.read()
- }
- fn set_greeting(
- ref self: ContractState, new_greeting: ByteArray, amount_strk: Option,
- ) {
- self.greeting.write(new_greeting);
- self.total_counter.write(self.total_counter.read() + 1);
- let user_counter = self.user_greeting_counter.read(get_caller_address());
- self.user_greeting_counter.write(get_caller_address(), user_counter + 1);
-
- match amount_strk {
- Option::Some(amount_strk) => {
- // In `Debug Contract` or UI implementation, call `approve` on STRK contract
- // before invoking fn set_greeting()
- let strk_contract_address: ContractAddress = FELT_STRK_CONTRACT
- .try_into()
- .unwrap();
- let strk_dispatcher = IERC20Dispatcher {
- contract_address: strk_contract_address,
- };
- strk_dispatcher
- .transfer_from(get_caller_address(), get_contract_address(), amount_strk);
- self.premium.write(true);
- },
- Option::None => { self.premium.write(false); },
- }
- self
- .emit(
- GreetingChanged {
- greeting_setter: get_caller_address(),
- new_greeting: self.greeting.read(),
- premium: true,
- value: amount_strk,
- },
- );
- }
- fn withdraw(ref self: ContractState) {
- self.ownable.assert_only_owner();
- let strk_contract_address = FELT_STRK_CONTRACT.try_into().unwrap();
- let strk_dispatcher = IERC20Dispatcher { contract_address: strk_contract_address };
- let balance = strk_dispatcher.balance_of(get_contract_address());
- strk_dispatcher.transfer(self.ownable.owner(), balance);
- }
- fn premium(self: @ContractState) -> bool {
- self.premium.read()
- }
- }
-}
+#[starknet::interface]
+pub trait IYourContract {
+ fn greeting(self: @TContractState) -> ByteArray;
+ fn set_greeting(ref self: TContractState, new_greeting: ByteArray, amount_strk: Option);
+ fn withdraw(ref self: TContractState);
+ fn premium(self: @TContractState) -> bool;
+}
+
+#[starknet::contract]
+pub mod YourContract {
+ use openzeppelin_access::ownable::OwnableComponent;
+ use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
+ use starknet::storage::{
+ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess,
+ StoragePointerWriteAccess,
+ };
+ use starknet::{ContractAddress, get_caller_address, get_contract_address};
+ use super::IYourContract;
+
+ component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
+
+ #[abi(embed_v0)]
+ impl OwnableImpl = OwnableComponent::OwnableImpl;
+ impl OwnableInternalImpl = OwnableComponent::InternalImpl;
+
+ pub const FELT_STRK_CONTRACT: felt252 =
+ 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d;
+
+ #[event]
+ #[derive(Drop, starknet::Event)]
+ enum Event {
+ #[flat]
+ OwnableEvent: OwnableComponent::Event,
+ GreetingChanged: GreetingChanged,
+ }
+
+ #[derive(Drop, starknet::Event)]
+ struct GreetingChanged {
+ #[key]
+ greeting_setter: ContractAddress,
+ #[key]
+ new_greeting: ByteArray,
+ premium: bool,
+ value: Option,
+ }
+
+ #[storage]
+ struct Storage {
+ greeting: ByteArray,
+ premium: bool,
+ total_counter: u256,
+ user_greeting_counter: Map,
+ #[substorage(v0)]
+ ownable: OwnableComponent::Storage,
+ }
+
+ #[constructor]
+ fn constructor(ref self: ContractState, owner: ContractAddress) {
+ self.greeting.write("Building Unstoppable Apps!!!");
+ self.ownable.initializer(owner);
+ }
+
+ #[abi(embed_v0)]
+ impl YourContractImpl of IYourContract {
+ fn greeting(self: @ContractState) -> ByteArray {
+ self.greeting.read()
+ }
+ fn set_greeting(
+ ref self: ContractState, new_greeting: ByteArray, amount_strk: Option,
+ ) {
+ self.greeting.write(new_greeting);
+ self.total_counter.write(self.total_counter.read() + 1);
+ let user_counter = self.user_greeting_counter.read(get_caller_address());
+ self.user_greeting_counter.write(get_caller_address(), user_counter + 1);
+
+ match amount_strk {
+ Option::Some(amount_strk) => {
+ // In `Debug Contract` or UI implementation, call `approve` on STRK contract
+ // before invoking fn set_greeting()
+ let strk_contract_address: ContractAddress = FELT_STRK_CONTRACT
+ .try_into()
+ .unwrap();
+ let strk_dispatcher = IERC20Dispatcher {
+ contract_address: strk_contract_address,
+ };
+ strk_dispatcher
+ .transfer_from(get_caller_address(), get_contract_address(), amount_strk);
+ self.premium.write(true);
+ },
+ Option::None => { self.premium.write(false); },
+ }
+ self
+ .emit(
+ GreetingChanged {
+ greeting_setter: get_caller_address(),
+ new_greeting: self.greeting.read(),
+ premium: true,
+ value: amount_strk,
+ },
+ );
+ }
+ fn withdraw(ref self: ContractState) {
+ self.ownable.assert_only_owner();
+ let strk_contract_address = FELT_STRK_CONTRACT.try_into().unwrap();
+ let strk_dispatcher = IERC20Dispatcher { contract_address: strk_contract_address };
+ let balance = strk_dispatcher.balance_of(get_contract_address());
+ strk_dispatcher.transfer(self.ownable.owner(), balance);
+ }
+ fn premium(self: @ContractState) -> bool {
+ self.premium.read()
+ }
+ }
+}
diff --git a/packages/snfoundry/contracts/src/lib.cairo b/packages/snfoundry/contracts/src/lib.cairo
index b322f331..920ddde6 100644
--- a/packages/snfoundry/contracts/src/lib.cairo
+++ b/packages/snfoundry/contracts/src/lib.cairo
@@ -1,6 +1,6 @@
-pub mod Lottery;
-pub mod LottoTicketNFT;
-pub mod StarkPlayERC20;
-pub mod StarkPlayVault;
-pub mod YourContract;
-
+pub mod Lottery;
+pub mod LottoTicketNFT;
+pub mod StarkPlayERC20;
+pub mod StarkPlayVault;
+pub mod YourContract;
+
diff --git a/packages/snfoundry/contracts/tests/TestContract.cairo b/packages/snfoundry/contracts/tests/TestContract.cairo
index 720636d7..56c8db7a 100644
--- a/packages/snfoundry/contracts/tests/TestContract.cairo
+++ b/packages/snfoundry/contracts/tests/TestContract.cairo
@@ -1,61 +1,61 @@
-use contracts::YourContract::YourContract::FELT_STRK_CONTRACT;
-use contracts::YourContract::{IYourContractDispatcher, IYourContractDispatcherTrait};
-use openzeppelin_testing::declare_and_deploy;
-use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
-use openzeppelin_utils::serde::SerializedAppend;
-use snforge_std::{CheatSpan, cheat_caller_address};
-use starknet::ContractAddress;
-
-// Real wallet address deployed on Sepolia
-const OWNER: ContractAddress = 0x02dA5254690b46B9C4059C25366D1778839BE63C142d899F0306fd5c312A5918
- .try_into()
- .unwrap();
-
-const STRK_TOKEN_CONTRACT_ADDRESS: ContractAddress = FELT_STRK_CONTRACT.try_into().unwrap();
-
-fn deploy_contract(name: ByteArray) -> ContractAddress {
- let mut calldata = array![];
- calldata.append_serde(OWNER);
- declare_and_deploy(name, calldata)
-}
-
-#[test]
-fn test_set_greetings() {
- let contract_address = deploy_contract("YourContract");
-
- let dispatcher = IYourContractDispatcher { contract_address };
-
- let current_greeting = dispatcher.greeting();
- let expected_greeting: ByteArray = "Building Unstoppable Apps!!!";
- assert(current_greeting == expected_greeting, 'Should have the right message');
-
- let new_greeting: ByteArray = "Learn Scaffold-Stark 2! :)";
+use contracts::YourContract::YourContract::FELT_STRK_CONTRACT;
+use contracts::YourContract::{IYourContractDispatcher, IYourContractDispatcherTrait};
+use openzeppelin_testing::declare_and_deploy;
+use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
+use openzeppelin_utils::serde::SerializedAppend;
+use snforge_std::{CheatSpan, cheat_caller_address};
+use starknet::ContractAddress;
+
+// Real wallet address deployed on Sepolia
+const OWNER: ContractAddress = 0x02dA5254690b46B9C4059C25366D1778839BE63C142d899F0306fd5c312A5918
+ .try_into()
+ .unwrap();
+
+const STRK_TOKEN_CONTRACT_ADDRESS: ContractAddress = FELT_STRK_CONTRACT.try_into().unwrap();
+
+fn deploy_contract(name: ByteArray) -> ContractAddress {
+ let mut calldata = array![];
+ calldata.append_serde(OWNER);
+ declare_and_deploy(name, calldata)
+}
+
+#[test]
+fn test_set_greetings() {
+ let contract_address = deploy_contract("YourContract");
+
+ let dispatcher = IYourContractDispatcher { contract_address };
+
+ let current_greeting = dispatcher.greeting();
+ let expected_greeting: ByteArray = "Building Unstoppable Apps!!!";
+ assert(current_greeting == expected_greeting, 'Should have the right message');
+
+ let new_greeting: ByteArray = "Learn Scaffold-Stark 2! :)";
dispatcher.set_greeting(new_greeting.clone(), Option::None); // we don't transfer any strk
- assert(dispatcher.greeting() == new_greeting, 'Should allow set new message');
-}
-
-#[test]
-#[fork("SEPOLIA_LATEST")]
-fn test_transfer() {
- let user = OWNER;
- let your_contract_address = deploy_contract("YourContract");
-
- let your_contract_dispatcher = IYourContractDispatcher {
- contract_address: your_contract_address,
- };
- let erc20_dispatcher = IERC20Dispatcher { contract_address: STRK_TOKEN_CONTRACT_ADDRESS };
- let amount_to_transfer: u256 = 500;
- cheat_caller_address(STRK_TOKEN_CONTRACT_ADDRESS, user, CheatSpan::TargetCalls(1));
- erc20_dispatcher.approve(your_contract_address, amount_to_transfer);
- let approved_amount = erc20_dispatcher.allowance(user, your_contract_address);
- assert(approved_amount == amount_to_transfer, 'Not the right amount approved');
-
- let new_greeting: ByteArray = "Learn Scaffold-Stark 2! :)";
-
- cheat_caller_address(your_contract_address, user, CheatSpan::TargetCalls(1));
- your_contract_dispatcher
- .set_greeting(
- new_greeting.clone(), Option::Some(amount_to_transfer),
+ assert(dispatcher.greeting() == new_greeting, 'Should allow set new message');
+}
+
+#[test]
+#[fork("SEPOLIA_LATEST")]
+fn test_transfer() {
+ let user = OWNER;
+ let your_contract_address = deploy_contract("YourContract");
+
+ let your_contract_dispatcher = IYourContractDispatcher {
+ contract_address: your_contract_address,
+ };
+ let erc20_dispatcher = IERC20Dispatcher { contract_address: STRK_TOKEN_CONTRACT_ADDRESS };
+ let amount_to_transfer: u256 = 500;
+ cheat_caller_address(STRK_TOKEN_CONTRACT_ADDRESS, user, CheatSpan::TargetCalls(1));
+ erc20_dispatcher.approve(your_contract_address, amount_to_transfer);
+ let approved_amount = erc20_dispatcher.allowance(user, your_contract_address);
+ assert(approved_amount == amount_to_transfer, 'Not the right amount approved');
+
+ let new_greeting: ByteArray = "Learn Scaffold-Stark 2! :)";
+
+ cheat_caller_address(your_contract_address, user, CheatSpan::TargetCalls(1));
+ your_contract_dispatcher
+ .set_greeting(
+ new_greeting.clone(), Option::Some(amount_to_transfer),
); // we transfer 500 wei
- assert(your_contract_dispatcher.greeting() == new_greeting, 'Should allow set new message');
-}
+ assert(your_contract_dispatcher.greeting() == new_greeting, 'Should allow set new message');
+}
diff --git a/packages/snfoundry/contracts/tests/test_CU05.cairo b/packages/snfoundry/contracts/tests/test_CU05.cairo
index bb00b75a..ea2ebf92 100644
--- a/packages/snfoundry/contracts/tests/test_CU05.cairo
+++ b/packages/snfoundry/contracts/tests/test_CU05.cairo
@@ -38,9 +38,7 @@ fn deploy_vault_contract() -> (IStarkPlayVaultDispatcher, IMintableDispatcher) {
let _strk_token = deploy_mock_strk_token();
let starkplay_contract = declare("StarkPlayERC20").unwrap().contract_class();
- let starkplay_constructor_calldata = array![
- owner_address().into(), owner_address().into(),
- ];
+ let starkplay_constructor_calldata = array![owner_address().into(), owner_address().into()];
let (starkplay_address, _) = starkplay_contract
.deploy(@starkplay_constructor_calldata)
.unwrap();
@@ -57,10 +55,8 @@ fn deploy_vault_contract() -> (IStarkPlayVaultDispatcher, IMintableDispatcher) {
start_cheat_caller_address(starkplay_token.contract_address, owner_address());
starkplay_token.grant_minter_role(vault_address);
starkplay_token_burn.grant_burner_role(vault_address);
- starkplay_token
- .set_minter_allowance(vault_address, 1000000000000000000000000000_u256);
- starkplay_token_burn
- .set_burner_allowance(vault_address, 1000000000000000000000000000_u256);
+ starkplay_token.set_minter_allowance(vault_address, 1000000000000000000000000000_u256);
+ starkplay_token_burn.set_burner_allowance(vault_address, 1000000000000000000000000000_u256);
stop_cheat_caller_address(starkplay_token.contract_address);
(vault, starkplay_token)
@@ -83,10 +79,7 @@ fn deploy_mock_strk_token() -> IMintableDispatcher {
start_cheat_caller_address(deployed_address, owner_address());
strk_token.grant_minter_role(owner_address());
- strk_token
- .set_minter_allowance(
- owner_address(), 1000000000000000000000000000_u256,
- );
+ strk_token.set_minter_allowance(owner_address(), 1000000000000000000000000000_u256);
strk_token.mint(USER(), 1000000000000000000000000000_u256);
@@ -178,7 +171,9 @@ fn test_create_new_draw_default_duration() {
let start_block = lottery.GetJackpotEntryStartBlock(current_draw_id);
let end_block = lottery.GetJackpotEntryEndBlock(current_draw_id);
- assert(end_block == start_block + STANDARD_DRAW_DURATION_BLOCKS, 'Should use standard duration');
+ assert(
+ end_block == start_block + STANDARD_DRAW_DURATION_BLOCKS, 'Should use standard duration',
+ );
}
#[test]
@@ -256,7 +251,9 @@ fn test_backward_compatibility_create_new_draw() {
let start_block = lottery.GetJackpotEntryStartBlock(current_draw_id);
let end_block = lottery.GetJackpotEntryEndBlock(current_draw_id);
- assert(end_block == start_block + STANDARD_DRAW_DURATION_BLOCKS, 'Should use standard duration');
+ assert(
+ end_block == start_block + STANDARD_DRAW_DURATION_BLOCKS, 'Should use standard duration',
+ );
}
#[should_panic(expected: 'Duration must be > 0')]
diff --git a/packages/snfoundry/contracts/tests/test_jackpot_history.cairo b/packages/snfoundry/contracts/tests/test_jackpot_history.cairo
index a2a09e9e..119b01c5 100644
--- a/packages/snfoundry/contracts/tests/test_jackpot_history.cairo
+++ b/packages/snfoundry/contracts/tests/test_jackpot_history.cairo
@@ -1,206 +1,206 @@
-use contracts::Lottery::{ILotteryDispatcher, ILotteryDispatcherTrait};
-use contracts::StarkPlayERC20::IMintableDispatcher;
-use contracts::StarkPlayVault::IStarkPlayVaultDispatcher;
-use core::array::ArrayTrait;
-use snforge_std::{
- ContractClassTrait, DeclareResultTrait, declare, start_cheat_caller_address,
- stop_cheat_caller_address,
-};
-use starknet::ContractAddress;
-
-// Helper constants
-pub fn OWNER() -> ContractAddress {
- 'OWNER'.try_into().unwrap()
-}
-
-pub fn USER() -> ContractAddress {
- 'USER'.try_into().unwrap()
-}
-
-pub fn deploy_lottery() -> ContractAddress {
- // Deploy mock contracts first
- let mock_strk_play = deploy_mock_strk_play();
- let mock_vault = deploy_mock_vault(mock_strk_play.contract_address);
-
- let mut constructor_calldata = array![];
- OWNER().serialize(ref constructor_calldata);
- mock_strk_play.contract_address.serialize(ref constructor_calldata);
- mock_vault.contract_address.serialize(ref constructor_calldata);
-
- let lottery_class = declare("Lottery").unwrap().contract_class();
- let (lottery_addr, _) = lottery_class.deploy(@constructor_calldata).unwrap();
-
- lottery_addr
-}
-
-fn deploy_mock_strk_play() -> IMintableDispatcher {
- let starkplay_contract = declare("StarkPlayERC20").unwrap().contract_class();
- let starkplay_constructor_calldata = array![
- OWNER().into(), OWNER().into(),
+use contracts::Lottery::{ILotteryDispatcher, ILotteryDispatcherTrait};
+use contracts::StarkPlayERC20::IMintableDispatcher;
+use contracts::StarkPlayVault::IStarkPlayVaultDispatcher;
+use core::array::ArrayTrait;
+use snforge_std::{
+ ContractClassTrait, DeclareResultTrait, declare, start_cheat_caller_address,
+ stop_cheat_caller_address,
+};
+use starknet::ContractAddress;
+
+// Helper constants
+pub fn OWNER() -> ContractAddress {
+ 'OWNER'.try_into().unwrap()
+}
+
+pub fn USER() -> ContractAddress {
+ 'USER'.try_into().unwrap()
+}
+
+pub fn deploy_lottery() -> ContractAddress {
+ // Deploy mock contracts first
+ let mock_strk_play = deploy_mock_strk_play();
+ let mock_vault = deploy_mock_vault(mock_strk_play.contract_address);
+
+ let mut constructor_calldata = array![];
+ OWNER().serialize(ref constructor_calldata);
+ mock_strk_play.contract_address.serialize(ref constructor_calldata);
+ mock_vault.contract_address.serialize(ref constructor_calldata);
+
+ let lottery_class = declare("Lottery").unwrap().contract_class();
+ let (lottery_addr, _) = lottery_class.deploy(@constructor_calldata).unwrap();
+
+ lottery_addr
+}
+
+fn deploy_mock_strk_play() -> IMintableDispatcher {
+ let starkplay_contract = declare("StarkPlayERC20").unwrap().contract_class();
+ let starkplay_constructor_calldata = array![
+ OWNER().into(), OWNER().into(),
]; // recipient and admin
- let (starkplay_address, _) = starkplay_contract
- .deploy(@starkplay_constructor_calldata)
- .unwrap();
- IMintableDispatcher { contract_address: starkplay_address }
-}
-
-fn deploy_mock_vault(strk_play_address: ContractAddress) -> IStarkPlayVaultDispatcher {
- let vault_contract = declare("StarkPlayVault").unwrap().contract_class();
- let vault_constructor_calldata = array![
- OWNER().into(), strk_play_address.into(), 50_u64.into(),
+ let (starkplay_address, _) = starkplay_contract
+ .deploy(@starkplay_constructor_calldata)
+ .unwrap();
+ IMintableDispatcher { contract_address: starkplay_address }
+}
+
+fn deploy_mock_vault(strk_play_address: ContractAddress) -> IStarkPlayVaultDispatcher {
+ let vault_contract = declare("StarkPlayVault").unwrap().contract_class();
+ let vault_constructor_calldata = array![
+ OWNER().into(), strk_play_address.into(), 50_u64.into(),
]; // owner, starkPlayToken, feePercentage
- let (vault_address, _) = vault_contract.deploy(@vault_constructor_calldata).unwrap();
- IStarkPlayVaultDispatcher { contract_address: vault_address }
-}
-
-// Helper functions to access JackpotEntry data through getter functions
-fn get_jackpot_entry_draw_id(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> u64 {
- lottery_dispatcher.GetJackpotEntryDrawId(draw_id)
-}
-
-fn get_jackpot_entry_amount(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> u256 {
- lottery_dispatcher.GetJackpotEntryAmount(draw_id)
-}
-
-fn get_jackpot_entry_start_time(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> u64 {
- lottery_dispatcher.GetJackpotEntryStartTime(draw_id)
-}
-
-fn get_jackpot_entry_end_time(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> u64 {
- lottery_dispatcher.GetJackpotEntryEndTime(draw_id)
-}
-
-fn get_jackpot_entry_is_active(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> bool {
- lottery_dispatcher.GetJackpotEntryIsActive(draw_id)
-}
-
-fn get_jackpot_entry_is_completed(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> bool {
- lottery_dispatcher.GetJackpotEntryIsCompleted(draw_id)
-}
-
-#[test]
-fn test_get_jackpot_history_basic() {
- let lottery_addr = deploy_lottery();
- let lottery_dispatcher = ILotteryDispatcher { contract_address: lottery_addr };
-
- start_cheat_caller_address(lottery_dispatcher.contract_address, OWNER());
- // Initialize the lottery
- lottery_dispatcher.Initialize(1000000000000000000_u256, 1000000000000000000000_u256);
- stop_cheat_caller_address(lottery_dispatcher.contract_address);
-
- // Get jackpot history - should return 1 entry for the initial draw
- let jackpot_history = lottery_dispatcher.get_jackpot_history();
- assert!(jackpot_history.len() == 1, "Should have 1 jackpot entry");
-
- // Use getter functions to access the data
- assert!(get_jackpot_entry_draw_id(lottery_dispatcher, 1) == 1, "First draw should have ID 1");
- assert!(get_jackpot_entry_is_active(lottery_dispatcher, 1), "First draw should be active");
- assert!(
- !get_jackpot_entry_is_completed(lottery_dispatcher, 1),
- "First draw should not be completed",
- );
-}
-
-#[test]
-fn test_get_jackpot_history_multiple_draws() {
- let lottery_addr = deploy_lottery();
- let lottery_dispatcher = ILotteryDispatcher { contract_address: lottery_addr };
-
- start_cheat_caller_address(lottery_dispatcher.contract_address, OWNER());
- // Initialize the lottery
- lottery_dispatcher.Initialize(1000000000000000000_u256, 1000000000000000000000_u256);
-
- // Create additional draws
- // close previous before next
- lottery_dispatcher.DrawNumbers(1);
- lottery_dispatcher.CreateNewDraw(2000000000000000000000_u256);
- lottery_dispatcher.DrawNumbers(2);
- lottery_dispatcher.CreateNewDraw(3000000000000000000000_u256);
- stop_cheat_caller_address(lottery_dispatcher.contract_address);
- // Get jackpot history - should return 3 entries
- let jackpot_history = lottery_dispatcher.get_jackpot_history();
- assert!(jackpot_history.len() == 3, "Should have 3 jackpot entries");
-
- // Verify each entry using getter functions
- assert!(
- get_jackpot_entry_draw_id(lottery_dispatcher, 1) == 1, "First entry should have drawId 1",
- );
- assert!(
- get_jackpot_entry_draw_id(lottery_dispatcher, 2) == 2, "Second entry should have drawId 2",
- );
- assert!(
- get_jackpot_entry_draw_id(lottery_dispatcher, 3) == 3, "Third entry should have drawId 3",
- );
-
- assert!(
- get_jackpot_entry_amount(lottery_dispatcher, 1) == 1000000000000000000000_u256,
- "First jackpot amount incorrect",
- );
- assert!(
- get_jackpot_entry_amount(lottery_dispatcher, 2) == 2000000000000000000000_u256,
- "Second jackpot amount incorrect",
- );
- assert!(
- get_jackpot_entry_amount(lottery_dispatcher, 3) == 3000000000000000000000_u256,
- "Third jackpot amount incorrect",
- );
-}
-
-#[test]
-fn test_get_jackpot_history_completed_draw() {
- let lottery_addr = deploy_lottery();
- let lottery_dispatcher = ILotteryDispatcher { contract_address: lottery_addr };
-
- start_cheat_caller_address(lottery_dispatcher.contract_address, OWNER());
- // Initialize the lottery
- lottery_dispatcher.Initialize(1000000000000000000_u256, 1000000000000000000000_u256);
-
- // Complete the draw
- lottery_dispatcher.DrawNumbers(1);
- stop_cheat_caller_address(lottery_dispatcher.contract_address);
- // Get jackpot history
- let jackpot_history = lottery_dispatcher.get_jackpot_history();
- assert!(jackpot_history.len() == 1, "Should have 1 jackpot entry");
-
- // Use getter functions to verify the completed draw
- assert!(get_jackpot_entry_draw_id(lottery_dispatcher, 1) == 1, "Entry should have drawId");
- assert!(
- !get_jackpot_entry_is_active(lottery_dispatcher, 1),
- "Draw should not be active after completion",
- );
- assert!(get_jackpot_entry_is_completed(lottery_dispatcher, 1), "Draw should be completed");
-}
-
-#[test]
-fn test_get_jackpot_history_performance() {
- let lottery_addr = deploy_lottery();
- let lottery_dispatcher = ILotteryDispatcher { contract_address: lottery_addr };
-
- start_cheat_caller_address(lottery_dispatcher.contract_address, OWNER());
- // Initialize the lottery
- lottery_dispatcher.Initialize(1000000000000000000_u256, 1000000000000000000000_u256);
-
- // Create many draws to test performance
- let mut i = 0;
+ let (vault_address, _) = vault_contract.deploy(@vault_constructor_calldata).unwrap();
+ IStarkPlayVaultDispatcher { contract_address: vault_address }
+}
+
+// Helper functions to access JackpotEntry data through getter functions
+fn get_jackpot_entry_draw_id(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> u64 {
+ lottery_dispatcher.GetJackpotEntryDrawId(draw_id)
+}
+
+fn get_jackpot_entry_amount(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> u256 {
+ lottery_dispatcher.GetJackpotEntryAmount(draw_id)
+}
+
+fn get_jackpot_entry_start_time(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> u64 {
+ lottery_dispatcher.GetJackpotEntryStartTime(draw_id)
+}
+
+fn get_jackpot_entry_end_time(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> u64 {
+ lottery_dispatcher.GetJackpotEntryEndTime(draw_id)
+}
+
+fn get_jackpot_entry_is_active(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> bool {
+ lottery_dispatcher.GetJackpotEntryIsActive(draw_id)
+}
+
+fn get_jackpot_entry_is_completed(lottery_dispatcher: ILotteryDispatcher, draw_id: u64) -> bool {
+ lottery_dispatcher.GetJackpotEntryIsCompleted(draw_id)
+}
+
+#[test]
+fn test_get_jackpot_history_basic() {
+ let lottery_addr = deploy_lottery();
+ let lottery_dispatcher = ILotteryDispatcher { contract_address: lottery_addr };
+
+ start_cheat_caller_address(lottery_dispatcher.contract_address, OWNER());
+ // Initialize the lottery
+ lottery_dispatcher.Initialize(1000000000000000000_u256, 1000000000000000000000_u256);
+ stop_cheat_caller_address(lottery_dispatcher.contract_address);
+
+ // Get jackpot history - should return 1 entry for the initial draw
+ let jackpot_history = lottery_dispatcher.get_jackpot_history();
+ assert!(jackpot_history.len() == 1, "Should have 1 jackpot entry");
+
+ // Use getter functions to access the data
+ assert!(get_jackpot_entry_draw_id(lottery_dispatcher, 1) == 1, "First draw should have ID 1");
+ assert!(get_jackpot_entry_is_active(lottery_dispatcher, 1), "First draw should be active");
+ assert!(
+ !get_jackpot_entry_is_completed(lottery_dispatcher, 1),
+ "First draw should not be completed",
+ );
+}
+
+#[test]
+fn test_get_jackpot_history_multiple_draws() {
+ let lottery_addr = deploy_lottery();
+ let lottery_dispatcher = ILotteryDispatcher { contract_address: lottery_addr };
+
+ start_cheat_caller_address(lottery_dispatcher.contract_address, OWNER());
+ // Initialize the lottery
+ lottery_dispatcher.Initialize(1000000000000000000_u256, 1000000000000000000000_u256);
+
+ // Create additional draws
+ // close previous before next
+ lottery_dispatcher.DrawNumbers(1);
+ lottery_dispatcher.CreateNewDraw(2000000000000000000000_u256);
+ lottery_dispatcher.DrawNumbers(2);
+ lottery_dispatcher.CreateNewDraw(3000000000000000000000_u256);
+ stop_cheat_caller_address(lottery_dispatcher.contract_address);
+ // Get jackpot history - should return 3 entries
+ let jackpot_history = lottery_dispatcher.get_jackpot_history();
+ assert!(jackpot_history.len() == 3, "Should have 3 jackpot entries");
+
+ // Verify each entry using getter functions
+ assert!(
+ get_jackpot_entry_draw_id(lottery_dispatcher, 1) == 1, "First entry should have drawId 1",
+ );
+ assert!(
+ get_jackpot_entry_draw_id(lottery_dispatcher, 2) == 2, "Second entry should have drawId 2",
+ );
+ assert!(
+ get_jackpot_entry_draw_id(lottery_dispatcher, 3) == 3, "Third entry should have drawId 3",
+ );
+
+ assert!(
+ get_jackpot_entry_amount(lottery_dispatcher, 1) == 1000000000000000000000_u256,
+ "First jackpot amount incorrect",
+ );
+ assert!(
+ get_jackpot_entry_amount(lottery_dispatcher, 2) == 2000000000000000000000_u256,
+ "Second jackpot amount incorrect",
+ );
+ assert!(
+ get_jackpot_entry_amount(lottery_dispatcher, 3) == 3000000000000000000000_u256,
+ "Third jackpot amount incorrect",
+ );
+}
+
+#[test]
+fn test_get_jackpot_history_completed_draw() {
+ let lottery_addr = deploy_lottery();
+ let lottery_dispatcher = ILotteryDispatcher { contract_address: lottery_addr };
+
+ start_cheat_caller_address(lottery_dispatcher.contract_address, OWNER());
+ // Initialize the lottery
+ lottery_dispatcher.Initialize(1000000000000000000_u256, 1000000000000000000000_u256);
+
+ // Complete the draw
+ lottery_dispatcher.DrawNumbers(1);
+ stop_cheat_caller_address(lottery_dispatcher.contract_address);
+ // Get jackpot history
+ let jackpot_history = lottery_dispatcher.get_jackpot_history();
+ assert!(jackpot_history.len() == 1, "Should have 1 jackpot entry");
+
+ // Use getter functions to verify the completed draw
+ assert!(get_jackpot_entry_draw_id(lottery_dispatcher, 1) == 1, "Entry should have drawId");
+ assert!(
+ !get_jackpot_entry_is_active(lottery_dispatcher, 1),
+ "Draw should not be active after completion",
+ );
+ assert!(get_jackpot_entry_is_completed(lottery_dispatcher, 1), "Draw should be completed");
+}
+
+#[test]
+fn test_get_jackpot_history_performance() {
+ let lottery_addr = deploy_lottery();
+ let lottery_dispatcher = ILotteryDispatcher { contract_address: lottery_addr };
+
+ start_cheat_caller_address(lottery_dispatcher.contract_address, OWNER());
+ // Initialize the lottery
+ lottery_dispatcher.Initialize(1000000000000000000_u256, 1000000000000000000000_u256);
+
+ // Create many draws to test performance
+ let mut i = 0;
let mut next_amount: u256 = 2000000000000000000000_u256; // starts at draw 2 amount
- while i != 10 {
- // Close last active then create next
- lottery_dispatcher.DrawNumbers(i + 1);
- lottery_dispatcher.CreateNewDraw(next_amount);
+ while i != 10 {
+ // Close last active then create next
+ lottery_dispatcher.DrawNumbers(i + 1);
+ lottery_dispatcher.CreateNewDraw(next_amount);
next_amount = next_amount + 1000000000000000000000_u256; // increment 1e18 each iteration
- i = i + 1;
- }
- stop_cheat_caller_address(lottery_dispatcher.contract_address);
- // Get jackpot history - should handle multiple entries efficiently
- let jackpot_history = lottery_dispatcher.get_jackpot_history();
- assert!(jackpot_history.len() == 11, "Should have 11 jackpot entries");
-
- // Verify the last entry using getter functions
- assert!(
- get_jackpot_entry_draw_id(lottery_dispatcher, 11) == 11, "Last entry should have drawId 11",
- );
- assert!(
- get_jackpot_entry_amount(lottery_dispatcher, 11) == 11000000000000000000000_u256,
- "Last jackpot amount incorrect",
- );
-}
+ i = i + 1;
+ }
+ stop_cheat_caller_address(lottery_dispatcher.contract_address);
+ // Get jackpot history - should handle multiple entries efficiently
+ let jackpot_history = lottery_dispatcher.get_jackpot_history();
+ assert!(jackpot_history.len() == 11, "Should have 11 jackpot entries");
+
+ // Verify the last entry using getter functions
+ assert!(
+ get_jackpot_entry_draw_id(lottery_dispatcher, 11) == 11, "Last entry should have drawId 11",
+ );
+ assert!(
+ get_jackpot_entry_amount(lottery_dispatcher, 11) == 11000000000000000000000_u256,
+ "Last jackpot amount incorrect",
+ );
+}
diff --git a/packages/snfoundry/scripts-ts/deploy-contract.ts b/packages/snfoundry/scripts-ts/deploy-contract.ts
index 376cce75..5cb88b36 100644
--- a/packages/snfoundry/scripts-ts/deploy-contract.ts
+++ b/packages/snfoundry/scripts-ts/deploy-contract.ts
@@ -1,330 +1,330 @@
-import fs from "fs";
-import path from "path";
-import { networks } from "./helpers/networks";
-import yargs from "yargs";
-import {
- CallData,
- stark,
- RawArgs,
- transaction,
- extractContractHashes,
- DeclareContractPayload,
- UniversalDetails,
- constants,
-} from "starknet";
-import { DeployContractParams, Network } from "./types";
-import { green, red, yellow } from "./helpers/colorize-log";
-
-interface Arguments {
- network: string;
- reset: boolean;
- [x: string]: unknown;
- _: (string | number)[];
- $0: string;
-}
-
-const argv = yargs(process.argv.slice(2))
- .option("network", {
- type: "string",
- description: "Specify the network",
- demandOption: true,
- })
- .option("reset", {
- type: "boolean",
- description: "Reset deployments (remove existing deployments)",
- default: true,
- })
- .parseSync() as Arguments;
-
-const networkName: string = argv.network;
-const resetDeployments: boolean = argv.reset;
-
-let deployments = {};
-let deployCalls = [];
-
-const { provider, deployer }: Network = networks[networkName];
-
-const declareIfNot_NotWait = async (
- payload: DeclareContractPayload,
- options?: UniversalDetails
-) => {
- const declareContractPayload = extractContractHashes(payload);
- try {
- await provider.getClassByHash(declareContractPayload.classHash);
- } catch (error) {
- try {
- const { transaction_hash } = await deployer.declare(payload, {
- ...options,
- version: constants.TRANSACTION_VERSION.V3,
- });
- if (networkName === "sepolia" || networkName === "mainnet") {
- console.log(
- yellow("Waiting for declaration transaction to be accepted...")
- );
- const receipt = await provider.waitForTransaction(transaction_hash);
- console.log(
- yellow("Declaration transaction receipt:"),
- JSON.stringify(
- receipt,
- (_, v) => (typeof v === "bigint" ? v.toString() : v),
- 2
- )
- );
-
- const receiptAny = receipt as any;
- if (receiptAny.execution_status !== "SUCCEEDED") {
- const revertReason = receiptAny.revert_reason || "Unknown reason";
- throw new Error(
- red(`Declaration failed or reverted. Reason: ${revertReason}`)
- );
- }
- console.log(green("Declaration successful"));
- }
- } catch (e) {
- console.error(red("Error declaring contract:"), e);
- throw e;
- }
- }
- return {
- classHash: declareContractPayload.classHash,
- };
-};
-
-const deployContract_NotWait = async (payload: {
- salt: string;
- classHash: string;
- constructorCalldata: RawArgs;
-}) => {
- try {
- const { calls, addresses } = transaction.buildUDCCall(
- payload,
- deployer.address
- );
- deployCalls.push(...calls);
- return {
- contractAddress: addresses[0],
- };
- } catch (error) {
- console.error(red("Error building UDC call:"), error);
- throw error;
- }
-};
-
-const findContractFile = (
- contract: string,
- fileType: "compiled_contract_class" | "contract_class"
-): string => {
- const targetDir = path.resolve(__dirname, "../contracts/target/dev");
- const files = fs.readdirSync(targetDir);
-
- const pattern = new RegExp(`.*${contract}\\.${fileType}\\.json$`);
- const matchingFile = files.find((file) => pattern.test(file));
-
- if (!matchingFile) {
- throw new Error(
- `Could not find ${fileType} file for contract "${contract}". ` +
- `Try removing snfoundry/contracts/target, then run 'yarn compile' and check if your contract name is correct inside the contracts/target/dev directory.`
- );
- }
-
- return path.join(targetDir, matchingFile);
-};
-
-/**
- * Deploy a contract using the specified parameters.
- *
- * @param {DeployContractParams} params - The parameters for deploying the contract.
- * @param {string} params.contract - The name of the contract to deploy.
- * @param {string} [params.contractName] - The name to export the contract as (optional).
- * @param {RawArgs} [params.constructorArgs] - The constructor arguments for the contract (optional).
- * @param {UniversalDetails} [params.options] - Additional deployment options (optional).
- *
- * @returns {Promise<{ classHash: string; address: string }>} The deployed contract's class hash and address.
- *
- * @example
- * ///Example usage of deployContract function
- * await deployContract({
- * contract: "YourContract",
- * contractName: "YourContractExportName",
- * constructorArgs: { owner: deployer.address },
- * options: { maxFee: BigInt(1000000000000) }
- * });
- */
-
-const deployContract = async (
- params: DeployContractParams
-): Promise<{
- classHash: string;
- address: string;
-}> => {
- const { contract, constructorArgs, contractName, options } = params;
-
- try {
- await deployer.getContractVersion(deployer.address);
- } catch (e) {
- if (e.toString().includes("Contract not found")) {
- const errorMessage = `The wallet you're using to deploy the contract is not deployed in the ${networkName} network.`;
- console.error(red(errorMessage));
- throw new Error(errorMessage);
- } else {
- console.error(red("Error getting contract version: "), e);
- throw e;
- }
- }
-
- let compiledContractCasm;
- let compiledContractSierra;
-
- try {
- compiledContractCasm = JSON.parse(
- fs
- .readFileSync(findContractFile(contract, "compiled_contract_class"))
- .toString("ascii")
- );
- } catch (error) {
- if (error.message.includes("Could not find")) {
- console.error(
- red(`The contract "${contract}" doesn't exist or is not compiled`)
- );
- } else {
- console.error(red("Error reading compiled contract class file: "), error);
- }
- return {
- classHash: "",
- address: "",
- };
- }
-
- try {
- compiledContractSierra = JSON.parse(
- fs
- .readFileSync(findContractFile(contract, "contract_class"))
- .toString("ascii")
- );
- } catch (error) {
- console.error(red("Error reading contract class file: "), error);
- return {
- classHash: "",
- address: "",
- };
- }
-
- const contractCalldata = new CallData(compiledContractSierra.abi);
- const constructorCalldata = constructorArgs
- ? contractCalldata.compile("constructor", constructorArgs)
- : [];
-
- console.log(yellow("Deploying Contract "), contractName || contract);
-
- let { classHash } = await declareIfNot_NotWait(
- {
- contract: compiledContractSierra,
- casm: compiledContractCasm,
- },
- options
- );
-
- let randomSalt = stark.randomAddress();
-
- let { contractAddress } = await deployContract_NotWait({
- salt: randomSalt,
- classHash,
- constructorCalldata,
- });
-
- console.log(green("Contract Deployed at "), contractAddress);
-
- let finalContractName = contractName || contract;
-
- deployments[finalContractName] = {
- classHash: classHash,
- address: contractAddress,
- contract: contract,
- };
-
- return {
- classHash: classHash,
- address: contractAddress,
- };
-};
-
-const executeDeployCalls = async (options?: UniversalDetails) => {
- if (deployCalls.length < 1) {
- throw new Error(
- red(
- "Aborted: No contract to deploy. Please prepare the contracts with `deployContract`"
- )
- );
- }
-
- try {
- let { transaction_hash } = await deployer.execute(deployCalls, {
- ...options,
- version: constants.TRANSACTION_VERSION.V3,
- });
- if (networkName === "sepolia" || networkName === "mainnet") {
- const receipt = await provider.waitForTransaction(transaction_hash);
- const receiptAny = receipt as any;
- if (receiptAny.execution_status !== "SUCCEEDED") {
- const revertReason = receiptAny.revert_reason;
- throw new Error(red(`Deploy Calls Failed: ${revertReason}`));
- }
- }
- console.log(green("Deploy Calls Executed at "), transaction_hash);
- } catch (error) {
- // split the calls in half and try again recursively
- if (deployCalls.length > 100) {
- let half = Math.ceil(deployCalls.length / 2);
- let firstHalf = deployCalls.slice(0, half);
- let secondHalf = deployCalls.slice(half);
- deployCalls = firstHalf;
- await executeDeployCalls(options);
- deployCalls = secondHalf;
- await executeDeployCalls(options);
- } else {
- throw error;
- }
- }
-};
-
-const loadExistingDeployments = () => {
- const networkPath = path.resolve(
- __dirname,
- `../deployments/${networkName}_latest.json`
- );
- if (fs.existsSync(networkPath)) {
- return JSON.parse(fs.readFileSync(networkPath, "utf8"));
- }
- return {};
-};
-
-const exportDeployments = () => {
- const networkPath = path.resolve(
- __dirname,
- `../deployments/${networkName}_latest.json`
- );
-
- if (!resetDeployments && fs.existsSync(networkPath)) {
- const currentTimestamp = new Date().getTime();
- fs.renameSync(
- networkPath,
- networkPath.replace("_latest.json", `_${currentTimestamp}.json`)
- );
- }
-
- if (resetDeployments && fs.existsSync(networkPath)) {
- fs.unlinkSync(networkPath);
- }
-
- fs.writeFileSync(networkPath, JSON.stringify(deployments, null, 2));
-};
-
-export {
- deployContract,
- provider,
- deployer,
- loadExistingDeployments,
- exportDeployments,
- executeDeployCalls,
- resetDeployments,
-};
+import fs from "fs";
+import path from "path";
+import { networks } from "./helpers/networks";
+import yargs from "yargs";
+import {
+ CallData,
+ stark,
+ RawArgs,
+ transaction,
+ extractContractHashes,
+ DeclareContractPayload,
+ UniversalDetails,
+ constants,
+} from "starknet";
+import { DeployContractParams, Network } from "./types";
+import { green, red, yellow } from "./helpers/colorize-log";
+
+interface Arguments {
+ network: string;
+ reset: boolean;
+ [x: string]: unknown;
+ _: (string | number)[];
+ $0: string;
+}
+
+const argv = yargs(process.argv.slice(2))
+ .option("network", {
+ type: "string",
+ description: "Specify the network",
+ demandOption: true,
+ })
+ .option("reset", {
+ type: "boolean",
+ description: "Reset deployments (remove existing deployments)",
+ default: true,
+ })
+ .parseSync() as Arguments;
+
+const networkName: string = argv.network;
+const resetDeployments: boolean = argv.reset;
+
+let deployments = {};
+let deployCalls = [];
+
+const { provider, deployer }: Network = networks[networkName];
+
+const declareIfNot_NotWait = async (
+ payload: DeclareContractPayload,
+ options?: UniversalDetails
+) => {
+ const declareContractPayload = extractContractHashes(payload);
+ try {
+ await provider.getClassByHash(declareContractPayload.classHash);
+ } catch (error) {
+ try {
+ const { transaction_hash } = await deployer.declare(payload, {
+ ...options,
+ version: constants.TRANSACTION_VERSION.V3,
+ });
+ if (networkName === "sepolia" || networkName === "mainnet") {
+ console.log(
+ yellow("Waiting for declaration transaction to be accepted...")
+ );
+ const receipt = await provider.waitForTransaction(transaction_hash);
+ console.log(
+ yellow("Declaration transaction receipt:"),
+ JSON.stringify(
+ receipt,
+ (_, v) => (typeof v === "bigint" ? v.toString() : v),
+ 2
+ )
+ );
+
+ const receiptAny = receipt as any;
+ if (receiptAny.execution_status !== "SUCCEEDED") {
+ const revertReason = receiptAny.revert_reason || "Unknown reason";
+ throw new Error(
+ red(`Declaration failed or reverted. Reason: ${revertReason}`)
+ );
+ }
+ console.log(green("Declaration successful"));
+ }
+ } catch (e) {
+ console.error(red("Error declaring contract:"), e);
+ throw e;
+ }
+ }
+ return {
+ classHash: declareContractPayload.classHash,
+ };
+};
+
+const deployContract_NotWait = async (payload: {
+ salt: string;
+ classHash: string;
+ constructorCalldata: RawArgs;
+}) => {
+ try {
+ const { calls, addresses } = transaction.buildUDCCall(
+ payload,
+ deployer.address
+ );
+ deployCalls.push(...calls);
+ return {
+ contractAddress: addresses[0],
+ };
+ } catch (error) {
+ console.error(red("Error building UDC call:"), error);
+ throw error;
+ }
+};
+
+const findContractFile = (
+ contract: string,
+ fileType: "compiled_contract_class" | "contract_class"
+): string => {
+ const targetDir = path.resolve(__dirname, "../contracts/target/dev");
+ const files = fs.readdirSync(targetDir);
+
+ const pattern = new RegExp(`.*${contract}\\.${fileType}\\.json$`);
+ const matchingFile = files.find((file) => pattern.test(file));
+
+ if (!matchingFile) {
+ throw new Error(
+ `Could not find ${fileType} file for contract "${contract}". ` +
+ `Try removing snfoundry/contracts/target, then run 'yarn compile' and check if your contract name is correct inside the contracts/target/dev directory.`
+ );
+ }
+
+ return path.join(targetDir, matchingFile);
+};
+
+/**
+ * Deploy a contract using the specified parameters.
+ *
+ * @param {DeployContractParams} params - The parameters for deploying the contract.
+ * @param {string} params.contract - The name of the contract to deploy.
+ * @param {string} [params.contractName] - The name to export the contract as (optional).
+ * @param {RawArgs} [params.constructorArgs] - The constructor arguments for the contract (optional).
+ * @param {UniversalDetails} [params.options] - Additional deployment options (optional).
+ *
+ * @returns {Promise<{ classHash: string; address: string }>} The deployed contract's class hash and address.
+ *
+ * @example
+ * ///Example usage of deployContract function
+ * await deployContract({
+ * contract: "YourContract",
+ * contractName: "YourContractExportName",
+ * constructorArgs: { owner: deployer.address },
+ * options: { maxFee: BigInt(1000000000000) }
+ * });
+ */
+
+const deployContract = async (
+ params: DeployContractParams
+): Promise<{
+ classHash: string;
+ address: string;
+}> => {
+ const { contract, constructorArgs, contractName, options } = params;
+
+ try {
+ await deployer.getContractVersion(deployer.address);
+ } catch (e) {
+ if (e.toString().includes("Contract not found")) {
+ const errorMessage = `The wallet you're using to deploy the contract is not deployed in the ${networkName} network.`;
+ console.error(red(errorMessage));
+ throw new Error(errorMessage);
+ } else {
+ console.error(red("Error getting contract version: "), e);
+ throw e;
+ }
+ }
+
+ let compiledContractCasm;
+ let compiledContractSierra;
+
+ try {
+ compiledContractCasm = JSON.parse(
+ fs
+ .readFileSync(findContractFile(contract, "compiled_contract_class"))
+ .toString("ascii")
+ );
+ } catch (error) {
+ if (error.message.includes("Could not find")) {
+ console.error(
+ red(`The contract "${contract}" doesn't exist or is not compiled`)
+ );
+ } else {
+ console.error(red("Error reading compiled contract class file: "), error);
+ }
+ return {
+ classHash: "",
+ address: "",
+ };
+ }
+
+ try {
+ compiledContractSierra = JSON.parse(
+ fs
+ .readFileSync(findContractFile(contract, "contract_class"))
+ .toString("ascii")
+ );
+ } catch (error) {
+ console.error(red("Error reading contract class file: "), error);
+ return {
+ classHash: "",
+ address: "",
+ };
+ }
+
+ const contractCalldata = new CallData(compiledContractSierra.abi);
+ const constructorCalldata = constructorArgs
+ ? contractCalldata.compile("constructor", constructorArgs)
+ : [];
+
+ console.log(yellow("Deploying Contract "), contractName || contract);
+
+ let { classHash } = await declareIfNot_NotWait(
+ {
+ contract: compiledContractSierra,
+ casm: compiledContractCasm,
+ },
+ options
+ );
+
+ let randomSalt = stark.randomAddress();
+
+ let { contractAddress } = await deployContract_NotWait({
+ salt: randomSalt,
+ classHash,
+ constructorCalldata,
+ });
+
+ console.log(green("Contract Deployed at "), contractAddress);
+
+ let finalContractName = contractName || contract;
+
+ deployments[finalContractName] = {
+ classHash: classHash,
+ address: contractAddress,
+ contract: contract,
+ };
+
+ return {
+ classHash: classHash,
+ address: contractAddress,
+ };
+};
+
+const executeDeployCalls = async (options?: UniversalDetails) => {
+ if (deployCalls.length < 1) {
+ throw new Error(
+ red(
+ "Aborted: No contract to deploy. Please prepare the contracts with `deployContract`"
+ )
+ );
+ }
+
+ try {
+ let { transaction_hash } = await deployer.execute(deployCalls, {
+ ...options,
+ version: constants.TRANSACTION_VERSION.V3,
+ });
+ if (networkName === "sepolia" || networkName === "mainnet") {
+ const receipt = await provider.waitForTransaction(transaction_hash);
+ const receiptAny = receipt as any;
+ if (receiptAny.execution_status !== "SUCCEEDED") {
+ const revertReason = receiptAny.revert_reason;
+ throw new Error(red(`Deploy Calls Failed: ${revertReason}`));
+ }
+ }
+ console.log(green("Deploy Calls Executed at "), transaction_hash);
+ } catch (error) {
+ // split the calls in half and try again recursively
+ if (deployCalls.length > 100) {
+ let half = Math.ceil(deployCalls.length / 2);
+ let firstHalf = deployCalls.slice(0, half);
+ let secondHalf = deployCalls.slice(half);
+ deployCalls = firstHalf;
+ await executeDeployCalls(options);
+ deployCalls = secondHalf;
+ await executeDeployCalls(options);
+ } else {
+ throw error;
+ }
+ }
+};
+
+const loadExistingDeployments = () => {
+ const networkPath = path.resolve(
+ __dirname,
+ `../deployments/${networkName}_latest.json`
+ );
+ if (fs.existsSync(networkPath)) {
+ return JSON.parse(fs.readFileSync(networkPath, "utf8"));
+ }
+ return {};
+};
+
+const exportDeployments = () => {
+ const networkPath = path.resolve(
+ __dirname,
+ `../deployments/${networkName}_latest.json`
+ );
+
+ if (!resetDeployments && fs.existsSync(networkPath)) {
+ const currentTimestamp = new Date().getTime();
+ fs.renameSync(
+ networkPath,
+ networkPath.replace("_latest.json", `_${currentTimestamp}.json`)
+ );
+ }
+
+ if (resetDeployments && fs.existsSync(networkPath)) {
+ fs.unlinkSync(networkPath);
+ }
+
+ fs.writeFileSync(networkPath, JSON.stringify(deployments, null, 2));
+};
+
+export {
+ deployContract,
+ provider,
+ deployer,
+ loadExistingDeployments,
+ exportDeployments,
+ executeDeployCalls,
+ resetDeployments,
+};
diff --git a/packages/snfoundry/scripts-ts/deploy.ts b/packages/snfoundry/scripts-ts/deploy.ts
index 67596480..b180c055 100644
--- a/packages/snfoundry/scripts-ts/deploy.ts
+++ b/packages/snfoundry/scripts-ts/deploy.ts
@@ -104,7 +104,7 @@ const deployScript = async (): Promise => {
constructorArgs: {
owner: deployer.address,
strkPlayContractAddress: starkPlayERC20Address,
- strkPlayVaultContractAddress: starkPlayVaultAddress
+ strkPlayVaultContractAddress: starkPlayVaultAddress,
},
});
const lotteryAddress = lotteryDeploymentResult.address;
@@ -133,11 +133,14 @@ const deployScript = async (): Promise => {
console.log("Assigning roles to StarkPlayVault...");
try {
const { Contract } = await import("starknet");
-
+
// Load StarkPlayERC20 contract ABI to interact with it
const starkPlayTokenCompiledContract = JSON.parse(
fs.readFileSync(
- path.join(__dirname, "../contracts/target/dev/contracts_StarkPlayERC20.contract_class.json"),
+ path.join(
+ __dirname,
+ "../contracts/target/dev/contracts_StarkPlayERC20.contract_class.json"
+ ),
"utf8"
)
);
@@ -151,18 +154,24 @@ const deployScript = async (): Promise => {
// Owner (deployer) assigns MINTER_ROLE to the vault
console.log("Granting MINTER_ROLE to vault...");
await starkPlayTokenContract.grant_minter_role(starkPlayVaultAddress);
-
+
// Set minter allowance for the vault
const mint_allowance = 1_000_000_000n * 1000000000000000000n; // 1B tokens with 18 decimals
- await starkPlayTokenContract.set_minter_allowance(starkPlayVaultAddress, mint_allowance);
-
+ await starkPlayTokenContract.set_minter_allowance(
+ starkPlayVaultAddress,
+ mint_allowance
+ );
+
// Owner (deployer) assigns BURNER_ROLE to the vault
console.log("Granting BURNER_ROLE to vault...");
await starkPlayTokenContract.grant_burner_role(starkPlayVaultAddress);
-
- // Set burner allowance for the vault
+
+ // Set burner allowance for the vault
const burn_allowance = 1_000_000_000n * 1000000000000000000n; // 1B tokens with 18 decimals
- await starkPlayTokenContract.set_burner_allowance(starkPlayVaultAddress, burn_allowance);
+ await starkPlayTokenContract.set_burner_allowance(
+ starkPlayVaultAddress,
+ burn_allowance
+ );
console.log("StarkPlayVault roles assigned successfully by owner");
} catch (error) {
diff --git a/packages/snfoundry/scripts-ts/helpers/colorize-log.ts b/packages/snfoundry/scripts-ts/helpers/colorize-log.ts
index 6dda5d9b..1e37792c 100644
--- a/packages/snfoundry/scripts-ts/helpers/colorize-log.ts
+++ b/packages/snfoundry/scripts-ts/helpers/colorize-log.ts
@@ -1,16 +1,16 @@
-const colors = {
- reset: "\x1b[0m",
- red: "\x1b[31m",
- green: "\x1b[32m",
- yellow: "\x1b[33m",
-};
-
-const colorize = (color: string, message: string): string => {
- return `${color}${message}${colors.reset}`;
-};
-
-export const red = (message: string): string => colorize(colors.red, message);
-export const green = (message: string): string =>
- colorize(colors.green, message);
-export const yellow = (message: string): string =>
- colorize(colors.yellow, message);
+const colors = {
+ reset: "\x1b[0m",
+ red: "\x1b[31m",
+ green: "\x1b[32m",
+ yellow: "\x1b[33m",
+};
+
+const colorize = (color: string, message: string): string => {
+ return `${color}${message}${colors.reset}`;
+};
+
+export const red = (message: string): string => colorize(colors.red, message);
+export const green = (message: string): string =>
+ colorize(colors.green, message);
+export const yellow = (message: string): string =>
+ colorize(colors.yellow, message);
diff --git a/packages/snfoundry/scripts-ts/helpers/deploy-wrapper.ts b/packages/snfoundry/scripts-ts/helpers/deploy-wrapper.ts
index ced16ec4..8843ab86 100644
--- a/packages/snfoundry/scripts-ts/helpers/deploy-wrapper.ts
+++ b/packages/snfoundry/scripts-ts/helpers/deploy-wrapper.ts
@@ -1,62 +1,62 @@
-#!/usr/bin/env node
-import yargs from "yargs";
-import { execSync } from "child_process";
-
-interface CommandLineOptions {
- _: string[]; // Non-hyphenated arguments are usually under the `_` key
- $0: string; // The script name or path is under the `$0` key
- network?: string; // The --network option
- reset?: boolean;
-}
-
-function main() {
- const argv = yargs(process.argv.slice(2))
- .option("network", {
- type: "string",
- choices: ["devnet", "sepolia", "mainnet"],
- default: "devnet",
- description: "Specify the network to deploy to",
- })
- .option("reset", {
- type: "boolean",
- description:
- "Reset deployments (overwrites the latest file only, previous generated files will remain)",
- default: true,
- hidden: true,
- })
- .option("no-reset", {
- type: "boolean",
- description: "Do not reset deployments (keep existing deployments)",
- default: false,
- })
- .demandOption(["network"])
- .strict() // This will make yargs throw an error for unknown options
- .help()
- .parseSync() as CommandLineOptions;
-
- if (argv._.length > 0) {
- console.error(
- `❌ Invalid arguments, only --network or --no-reset can be passed in`
- );
- return;
- }
-
- const resetFlag = argv.reset === false ? "--no-reset" : "";
-
- try {
- const command =
- `cd contracts && scarb build && ts-node ../scripts-ts/deploy.ts` +
- ` --network ${argv.network || "devnet"}` +
- `${resetFlag ? " " + resetFlag : ""}` +
- ` && ts-node ../scripts-ts/helpers/parse-deployments.ts && cd ..`;
-
- execSync(command, { stdio: "inherit" });
- } catch (error) {
- console.error("Error during deployment:", error);
- process.exit(1);
- }
-}
-
-if (require.main === module) {
- main();
-}
+#!/usr/bin/env node
+import yargs from "yargs";
+import { execSync } from "child_process";
+
+interface CommandLineOptions {
+ _: string[]; // Non-hyphenated arguments are usually under the `_` key
+ $0: string; // The script name or path is under the `$0` key
+ network?: string; // The --network option
+ reset?: boolean;
+}
+
+function main() {
+ const argv = yargs(process.argv.slice(2))
+ .option("network", {
+ type: "string",
+ choices: ["devnet", "sepolia", "mainnet"],
+ default: "devnet",
+ description: "Specify the network to deploy to",
+ })
+ .option("reset", {
+ type: "boolean",
+ description:
+ "Reset deployments (overwrites the latest file only, previous generated files will remain)",
+ default: true,
+ hidden: true,
+ })
+ .option("no-reset", {
+ type: "boolean",
+ description: "Do not reset deployments (keep existing deployments)",
+ default: false,
+ })
+ .demandOption(["network"])
+ .strict() // This will make yargs throw an error for unknown options
+ .help()
+ .parseSync() as CommandLineOptions;
+
+ if (argv._.length > 0) {
+ console.error(
+ `❌ Invalid arguments, only --network or --no-reset can be passed in`
+ );
+ return;
+ }
+
+ const resetFlag = argv.reset === false ? "--no-reset" : "";
+
+ try {
+ const command =
+ `cd contracts && scarb build && ts-node ../scripts-ts/deploy.ts` +
+ ` --network ${argv.network || "devnet"}` +
+ `${resetFlag ? " " + resetFlag : ""}` +
+ ` && ts-node ../scripts-ts/helpers/parse-deployments.ts && cd ..`;
+
+ execSync(command, { stdio: "inherit" });
+ } catch (error) {
+ console.error("Error during deployment:", error);
+ process.exit(1);
+ }
+}
+
+if (require.main === module) {
+ main();
+}
diff --git a/packages/snfoundry/scripts-ts/helpers/networks.ts b/packages/snfoundry/scripts-ts/helpers/networks.ts
index 4398b4b5..d08b0783 100644
--- a/packages/snfoundry/scripts-ts/helpers/networks.ts
+++ b/packages/snfoundry/scripts-ts/helpers/networks.ts
@@ -1,87 +1,87 @@
-import { RpcProvider, Account, constants } from "starknet";
-import path from "path";
-import dotenv from "dotenv";
-import { Networks } from "../types";
-
-dotenv.config({ path: path.resolve(__dirname, "../../.env") });
-
-// devnet
-const PRIVATE_KEY_DEVNET =
- process.env.PRIVATE_KEY_DEVNET || "0x71d7bb07b9a64f6f78ac4c816aff4da9";
-const RPC_URL_DEVNET = process.env.RPC_URL_DEVNET || "http://127.0.0.1:5050";
-const ACCOUNT_ADDRESS_DEVNET =
- process.env.ACCOUNT_ADDRESS_DEVNET ||
- "0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691";
-
-const providerDevnet =
- RPC_URL_DEVNET && new RpcProvider({ nodeUrl: RPC_URL_DEVNET });
-const deployerDevnet =
- ACCOUNT_ADDRESS_DEVNET &&
- PRIVATE_KEY_DEVNET &&
- new Account(
- providerDevnet,
- ACCOUNT_ADDRESS_DEVNET,
- PRIVATE_KEY_DEVNET,
- "1",
- constants.TRANSACTION_VERSION.V3
- );
-
-const STRK_TOKEN_ADDRESS_DEVNET =
- "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
-
-// sepolia
-const providerSepolia =
- process.env.RPC_URL_SEPOLIA &&
- new RpcProvider({ nodeUrl: process.env.RPC_URL_SEPOLIA });
-const deployerSepolia =
- process.env.ACCOUNT_ADDRESS_SEPOLIA &&
- process.env.PRIVATE_KEY_SEPOLIA &&
- new Account(
- providerSepolia,
- process.env.ACCOUNT_ADDRESS_SEPOLIA,
- process.env.PRIVATE_KEY_SEPOLIA,
- "1",
- constants.TRANSACTION_VERSION.V3
- );
-
-const STRK_TOKEN_ADDRESS =
- "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
-
-// mainnet
-const providerMainnet =
- process.env.RPC_URL_MAINNET &&
- new RpcProvider({ nodeUrl: process.env.RPC_URL_MAINNET });
-const deployerMainnet =
- process.env.ACCOUNT_ADDRESS_MAINNET &&
- process.env.PRIVATE_KEY_MAINNET &&
- new Account(
- providerMainnet,
- process.env.ACCOUNT_ADDRESS_MAINNET,
- process.env.PRIVATE_KEY_MAINNET,
- "1",
- constants.TRANSACTION_VERSION.V3
- );
-
-const feeTokenOptions = {
- devnet: [{ name: "strk", address: STRK_TOKEN_ADDRESS_DEVNET }],
- mainnet: [{ name: "strk", address: STRK_TOKEN_ADDRESS }],
- sepolia: [{ name: "strk", address: STRK_TOKEN_ADDRESS }],
-};
-
-export const networks: Networks = {
- devnet: {
- provider: providerDevnet,
- deployer: deployerDevnet,
- feeToken: feeTokenOptions.devnet,
- },
- sepolia: {
- provider: providerSepolia,
- deployer: deployerSepolia,
- feeToken: feeTokenOptions.sepolia,
- },
- mainnet: {
- provider: providerMainnet,
- deployer: deployerMainnet,
- feeToken: feeTokenOptions.mainnet,
- },
-};
+import { RpcProvider, Account, constants } from "starknet";
+import path from "path";
+import dotenv from "dotenv";
+import { Networks } from "../types";
+
+dotenv.config({ path: path.resolve(__dirname, "../../.env") });
+
+// devnet
+const PRIVATE_KEY_DEVNET =
+ process.env.PRIVATE_KEY_DEVNET || "0x71d7bb07b9a64f6f78ac4c816aff4da9";
+const RPC_URL_DEVNET = process.env.RPC_URL_DEVNET || "http://127.0.0.1:5050";
+const ACCOUNT_ADDRESS_DEVNET =
+ process.env.ACCOUNT_ADDRESS_DEVNET ||
+ "0x64b48806902a367c8598f4f95c305e8c1a1acba5f082d294a43793113115691";
+
+const providerDevnet =
+ RPC_URL_DEVNET && new RpcProvider({ nodeUrl: RPC_URL_DEVNET });
+const deployerDevnet =
+ ACCOUNT_ADDRESS_DEVNET &&
+ PRIVATE_KEY_DEVNET &&
+ new Account(
+ providerDevnet,
+ ACCOUNT_ADDRESS_DEVNET,
+ PRIVATE_KEY_DEVNET,
+ "1",
+ constants.TRANSACTION_VERSION.V3
+ );
+
+const STRK_TOKEN_ADDRESS_DEVNET =
+ "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
+
+// sepolia
+const providerSepolia =
+ process.env.RPC_URL_SEPOLIA &&
+ new RpcProvider({ nodeUrl: process.env.RPC_URL_SEPOLIA });
+const deployerSepolia =
+ process.env.ACCOUNT_ADDRESS_SEPOLIA &&
+ process.env.PRIVATE_KEY_SEPOLIA &&
+ new Account(
+ providerSepolia,
+ process.env.ACCOUNT_ADDRESS_SEPOLIA,
+ process.env.PRIVATE_KEY_SEPOLIA,
+ "1",
+ constants.TRANSACTION_VERSION.V3
+ );
+
+const STRK_TOKEN_ADDRESS =
+ "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
+
+// mainnet
+const providerMainnet =
+ process.env.RPC_URL_MAINNET &&
+ new RpcProvider({ nodeUrl: process.env.RPC_URL_MAINNET });
+const deployerMainnet =
+ process.env.ACCOUNT_ADDRESS_MAINNET &&
+ process.env.PRIVATE_KEY_MAINNET &&
+ new Account(
+ providerMainnet,
+ process.env.ACCOUNT_ADDRESS_MAINNET,
+ process.env.PRIVATE_KEY_MAINNET,
+ "1",
+ constants.TRANSACTION_VERSION.V3
+ );
+
+const feeTokenOptions = {
+ devnet: [{ name: "strk", address: STRK_TOKEN_ADDRESS_DEVNET }],
+ mainnet: [{ name: "strk", address: STRK_TOKEN_ADDRESS }],
+ sepolia: [{ name: "strk", address: STRK_TOKEN_ADDRESS }],
+};
+
+export const networks: Networks = {
+ devnet: {
+ provider: providerDevnet,
+ deployer: deployerDevnet,
+ feeToken: feeTokenOptions.devnet,
+ },
+ sepolia: {
+ provider: providerSepolia,
+ deployer: deployerSepolia,
+ feeToken: feeTokenOptions.sepolia,
+ },
+ mainnet: {
+ provider: providerMainnet,
+ deployer: deployerMainnet,
+ feeToken: feeTokenOptions.mainnet,
+ },
+};
diff --git a/packages/snfoundry/scripts-ts/helpers/parse-deployments.ts b/packages/snfoundry/scripts-ts/helpers/parse-deployments.ts
index 70440f4b..aa2490c5 100644
--- a/packages/snfoundry/scripts-ts/helpers/parse-deployments.ts
+++ b/packages/snfoundry/scripts-ts/helpers/parse-deployments.ts
@@ -1,93 +1,93 @@
-import fs from "fs";
-import path from "path";
-import prettier from "prettier";
-import { Abi, CompiledSierra } from "starknet";
-
-const TARGET_DIR = path.join(__dirname, "../../../nextjs/contracts");
-const deploymentsDir = path.join(__dirname, "../../deployments");
-const files = fs.readdirSync(deploymentsDir);
-
-const generatedContractComment = `/**
- * This file is autogenerated by Scaffold-Stark.
- * You should not edit it manually or your changes might be overwritten.
- */`;
-
-const getContractDataFromDeployments = (): Record<
- string,
- Record
-> => {
- const allContractsData: Record<
- string,
- Record
- > = {};
-
- files.forEach((file) => {
- if (path.extname(file) === ".json" && file.endsWith("_latest.json")) {
- const filePath = path.join(deploymentsDir, file);
- const content: Record<
- string,
- {
- contract: string;
- address: string;
- classHash: string;
- }
- > = JSON.parse(fs.readFileSync(filePath, "utf8"));
- const chainId = path.basename(file, "_latest.json");
-
- Object.entries(content).forEach(([contractName, contractData]) => {
- try {
- const abiFilePath = path.join(
- __dirname,
- `../../contracts/target/dev/contracts_${contractData.contract}.contract_class.json`
- );
- const abiContent: CompiledSierra = JSON.parse(
- fs.readFileSync(abiFilePath, "utf8")
- );
-
- allContractsData[chainId] = {
- ...allContractsData[chainId],
- [contractName]: {
- address: contractData.address,
- abi: abiContent.abi.filter((item) => item.type !== "l1_handler"),
- classHash: contractData.classHash,
- },
- };
- } catch (e) {}
- });
- }
- });
-
- return allContractsData;
-};
-
-const generateTsAbis = () => {
- const allContractsData = getContractDataFromDeployments();
-
- const fileContent = Object.entries(allContractsData).reduce(
- (content, [chainId, chainConfig]) => {
- // Use chainId directly as it is already a hex string
- return `${content}${chainId}:${JSON.stringify(chainConfig, null, 2)},`;
- },
- ""
- );
-
- if (!fs.existsSync(TARGET_DIR)) {
- fs.mkdirSync(TARGET_DIR);
- }
-
- fs.writeFileSync(
- path.join(TARGET_DIR, "deployedContracts.ts"),
- prettier.format(
- `${generatedContractComment}\n\nconst deployedContracts = {${fileContent}} as const;\n\nexport default deployedContracts;`,
- {
- parser: "typescript",
- }
- )
- );
-
- console.log(
- `📝 Updated TypeScript contract definition file on ${TARGET_DIR}/deployedContracts.ts`
- );
-};
-
-generateTsAbis();
+import fs from "fs";
+import path from "path";
+import prettier from "prettier";
+import { Abi, CompiledSierra } from "starknet";
+
+const TARGET_DIR = path.join(__dirname, "../../../nextjs/contracts");
+const deploymentsDir = path.join(__dirname, "../../deployments");
+const files = fs.readdirSync(deploymentsDir);
+
+const generatedContractComment = `/**
+ * This file is autogenerated by Scaffold-Stark.
+ * You should not edit it manually or your changes might be overwritten.
+ */`;
+
+const getContractDataFromDeployments = (): Record<
+ string,
+ Record
+> => {
+ const allContractsData: Record<
+ string,
+ Record
+ > = {};
+
+ files.forEach((file) => {
+ if (path.extname(file) === ".json" && file.endsWith("_latest.json")) {
+ const filePath = path.join(deploymentsDir, file);
+ const content: Record<
+ string,
+ {
+ contract: string;
+ address: string;
+ classHash: string;
+ }
+ > = JSON.parse(fs.readFileSync(filePath, "utf8"));
+ const chainId = path.basename(file, "_latest.json");
+
+ Object.entries(content).forEach(([contractName, contractData]) => {
+ try {
+ const abiFilePath = path.join(
+ __dirname,
+ `../../contracts/target/dev/contracts_${contractData.contract}.contract_class.json`
+ );
+ const abiContent: CompiledSierra = JSON.parse(
+ fs.readFileSync(abiFilePath, "utf8")
+ );
+
+ allContractsData[chainId] = {
+ ...allContractsData[chainId],
+ [contractName]: {
+ address: contractData.address,
+ abi: abiContent.abi.filter((item) => item.type !== "l1_handler"),
+ classHash: contractData.classHash,
+ },
+ };
+ } catch (e) {}
+ });
+ }
+ });
+
+ return allContractsData;
+};
+
+const generateTsAbis = () => {
+ const allContractsData = getContractDataFromDeployments();
+
+ const fileContent = Object.entries(allContractsData).reduce(
+ (content, [chainId, chainConfig]) => {
+ // Use chainId directly as it is already a hex string
+ return `${content}${chainId}:${JSON.stringify(chainConfig, null, 2)},`;
+ },
+ ""
+ );
+
+ if (!fs.existsSync(TARGET_DIR)) {
+ fs.mkdirSync(TARGET_DIR);
+ }
+
+ fs.writeFileSync(
+ path.join(TARGET_DIR, "deployedContracts.ts"),
+ prettier.format(
+ `${generatedContractComment}\n\nconst deployedContracts = {${fileContent}} as const;\n\nexport default deployedContracts;`,
+ {
+ parser: "typescript",
+ }
+ )
+ );
+
+ console.log(
+ `📝 Updated TypeScript contract definition file on ${TARGET_DIR}/deployedContracts.ts`
+ );
+};
+
+generateTsAbis();
diff --git a/packages/snfoundry/scripts-ts/types.ts b/packages/snfoundry/scripts-ts/types.ts
index 6272650c..0c98ef6a 100644
--- a/packages/snfoundry/scripts-ts/types.ts
+++ b/packages/snfoundry/scripts-ts/types.ts
@@ -1,16 +1,16 @@
-import { Account, RawArgs, RpcProvider, UniversalDetails } from "starknet";
-
-export type Networks = Record<"devnet" | "sepolia" | "mainnet", Network>;
-
-export type Network = {
- provider: RpcProvider;
- deployer: Account;
- feeToken: { name: string; address: string }[];
-};
-
-export type DeployContractParams = {
- contract: string;
- contractName?: string;
- constructorArgs?: RawArgs;
- options?: UniversalDetails;
-};
+import { Account, RawArgs, RpcProvider, UniversalDetails } from "starknet";
+
+export type Networks = Record<"devnet" | "sepolia" | "mainnet", Network>;
+
+export type Network = {
+ provider: RpcProvider;
+ deployer: Account;
+ feeToken: { name: string; address: string }[];
+};
+
+export type DeployContractParams = {
+ contract: string;
+ contractName?: string;
+ constructorArgs?: RawArgs;
+ options?: UniversalDetails;
+};
diff --git a/packages/snfoundry/scripts-ts/verify-contracts.ts b/packages/snfoundry/scripts-ts/verify-contracts.ts
index 5118f3c7..734743c9 100644
--- a/packages/snfoundry/scripts-ts/verify-contracts.ts
+++ b/packages/snfoundry/scripts-ts/verify-contracts.ts
@@ -1,68 +1,68 @@
-import path from "path";
-import { execSync } from "child_process";
-import yargs from "yargs";
-import { green, red, yellow } from "./helpers/colorize-log";
-import deployedContracts from "../../nextjs/contracts/deployedContracts";
-
-function main() {
- // Parse command line arguments
- const argv = yargs(process.argv.slice(2))
- .option("network", {
- type: "string",
- description: "Specify the network mainnet or sepolia",
- demandOption: true,
- })
- .parseSync();
-
- const network = argv.network;
-
- if (network !== "sepolia" && network !== "mainnet") {
- console.error(
- `Unsupported network: ${network}. Please use 'sepolia' or 'mainnet'.`
- );
- process.exit(1);
- }
-
- const contractsToVerify = deployedContracts[network];
-
- if (!contractsToVerify) {
- console.error(`No deployed contracts found for network: ${network}`);
- process.exit(1);
- }
-
- // Change to the contracts directory
- const contractsDir = path.resolve(__dirname, "../contracts");
- process.chdir(contractsDir);
-
- // Verify each contract
- Object.entries(contractsToVerify).forEach(
- ([contractName, contractInfo]: [string, any]) => {
- const { address, abi } = contractInfo;
- const interfaceNameItem = abi.find(
- (item) => item.type === "impl" && item.interface_name
- );
- if (!interfaceNameItem) {
- console.error(red(`Failed to find Contract for ${contractName}`));
- return;
- }
- const contractParts = interfaceNameItem.interface_name.split("::");
- const contract = contractParts[contractParts.length - 2];
-
- console.log(yellow(`Verifying ${contractName} on ${network}...`));
- try {
- execSync(
- `sncast verify --contract-address ${address} --contract-name ${contract} --network ${network} --verifier walnut --confirm-verification`,
- { stdio: "inherit" }
- );
- console.log(green("Successfully verified"), contractName);
- } catch (error) {
- console.error(red(`Failed to verify ${contractName}:`), error);
- }
- }
- );
- console.log(green("✅ Verification process completed."));
-}
-
-if (typeof module !== "undefined" && require.main === module) {
- main();
-}
+import path from "path";
+import { execSync } from "child_process";
+import yargs from "yargs";
+import { green, red, yellow } from "./helpers/colorize-log";
+import deployedContracts from "../../nextjs/contracts/deployedContracts";
+
+function main() {
+ // Parse command line arguments
+ const argv = yargs(process.argv.slice(2))
+ .option("network", {
+ type: "string",
+ description: "Specify the network mainnet or sepolia",
+ demandOption: true,
+ })
+ .parseSync();
+
+ const network = argv.network;
+
+ if (network !== "sepolia" && network !== "mainnet") {
+ console.error(
+ `Unsupported network: ${network}. Please use 'sepolia' or 'mainnet'.`
+ );
+ process.exit(1);
+ }
+
+ const contractsToVerify = deployedContracts[network];
+
+ if (!contractsToVerify) {
+ console.error(`No deployed contracts found for network: ${network}`);
+ process.exit(1);
+ }
+
+ // Change to the contracts directory
+ const contractsDir = path.resolve(__dirname, "../contracts");
+ process.chdir(contractsDir);
+
+ // Verify each contract
+ Object.entries(contractsToVerify).forEach(
+ ([contractName, contractInfo]: [string, any]) => {
+ const { address, abi } = contractInfo;
+ const interfaceNameItem = abi.find(
+ (item) => item.type === "impl" && item.interface_name
+ );
+ if (!interfaceNameItem) {
+ console.error(red(`Failed to find Contract for ${contractName}`));
+ return;
+ }
+ const contractParts = interfaceNameItem.interface_name.split("::");
+ const contract = contractParts[contractParts.length - 2];
+
+ console.log(yellow(`Verifying ${contractName} on ${network}...`));
+ try {
+ execSync(
+ `sncast verify --contract-address ${address} --contract-name ${contract} --network ${network} --verifier walnut --confirm-verification`,
+ { stdio: "inherit" }
+ );
+ console.log(green("Successfully verified"), contractName);
+ } catch (error) {
+ console.error(red(`Failed to verify ${contractName}:`), error);
+ }
+ }
+ );
+ console.log(green("✅ Verification process completed."));
+}
+
+if (typeof module !== "undefined" && require.main === module) {
+ main();
+}