Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions contracts/DEPLOYMENT_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Quick Deployment Guide for Spooky Score NFT

## ✅ Prerequisites Complete
- ✅ Scarb installed
- ✅ Starkli installed
- ✅ Contract compiled successfully

## Step 1: Set Up Starknet Account

You need a Starknet account to deploy. Choose one option:

### Option A: Use Existing Wallet (Recommended)
If you already have a Starknet wallet (Argent X or Braavos), export your account:

```bash
# For Argent X wallet
starkli signer keystore from-key ~/.starkli-wallets/keystore.json

# Follow prompts to enter your private key
# Then create account descriptor:
starkli account fetch <YOUR_WALLET_ADDRESS> \
--rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_7 \
--output ~/.starkli-wallets/account.json
```

### Option B: Create New Account
```bash
# Create keystore
starkli signer keystore new ~/.starkli-wallets/keystore.json

# Deploy account (requires ETH on Sepolia)
starkli account oz init ~/.starkli-wallets/account.json \
--keystore ~/.starkli-wallets/keystore.json \
--rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_7

# Fund the displayed address with Sepolia ETH from:
# https://starknet-faucet.vercel.app/

# Deploy the account:
starkli account deploy ~/.starkli-wallets/account.json \
--keystore ~/.starkli-wallets/keystore.json \
--rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_7
```

## Step 2: Declare the Contract

```bash
cd /Users/sharonkitavi/flapi/contracts

starkli declare target/dev/spooky_score_nft_SpookyScoreNFT.contract_class.json \
--rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_7 \
--account ~/.starkli-wallets/account.json \
--keystore ~/.starkli-wallets/keystore.json
```

Save the **class hash** from the output (looks like `0x...`).

## Step 3: Deploy the Contract

```bash
starkli deploy <CLASS_HASH_FROM_STEP_2> \
--rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_7 \
--account ~/.starkli-wallets/account.json \
--keystore ~/.starkli-wallets/keystore.json
```

Save the **contract address** from the output.

## Step 4: Update Your Game

Update the contract address in your game:

1. Open: `/Users/sharonkitavi/flapi/src/web3/StarknetWalletAdapter.js`
2. Find line 8: `var NFT_CONTRACT_ADDRESS = "0x..."`
3. Replace with your deployed contract address

## Step 5: Test It!

```bash
# Navigate to project root
cd /Users/sharonkitavi/flapi

# Start the game
npx serve

# Open http://localhost:3000
# Play, score 10+, connect wallet, and mint your NFT!
```

## Troubleshooting

### "Insufficient funds"
Get Sepolia ETH from: https://starknet-faucet.vercel.app/

### "Account not found"
Make sure you completed Step 1 properly and the account is deployed.

### "Invalid class hash"
Double-check you copied the full class hash from Step 2 (including `0x` prefix).

## Verify Your Deployment

Check your contract on Starkscan:
```
https://sepolia.starkscan.co/contract/<YOUR_CONTRACT_ADDRESS>
```

## Quick Commands Reference

```bash
# Check starkli version
starkli --version

# Check account
starkli account fetch <ADDRESS> --rpc https://starknet-sepolia.public.blastapi.io/rpc/v0_7

# Rebuild contract after changes
cd contracts && scarb build
```
6 changes: 6 additions & 0 deletions contracts/Scarb.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "spooky_score_nft"
version = "0.1.0"
9 changes: 9 additions & 0 deletions contracts/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "spooky_score_nft"
version = "0.1.0"
edition = "2024_07"

[dependencies]
starknet = ">=2.6.3"

[[target.starknet-contract]]
15 changes: 10 additions & 5 deletions contracts/SpookyScoreNFT.cairo → contracts/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Spooky Score NFT Contract for Flappy Haunt
// Deploy this contract to Starknet Sepolia and update the address in CartridgeControllerAdapter.js
// Deploy this contract to Starknet Sepolia and update the address in StarknetWalletAdapter.js

#[starknet::contract]
mod SpookyScoreNFT {
use starknet::{ContractAddress, get_caller_address, get_block_timestamp};
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, Map};
use starknet::ContractAddress;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess, Map, StorageMapReadAccess, StorageMapWriteAccess};
use core::num::traits::Zero;

#[storage]
struct Storage {
Expand All @@ -27,8 +28,11 @@ mod SpookyScoreNFT {

#[derive(Drop, starknet::Event)]
struct Transfer {
#[key]
from: ContractAddress,
#[key]
to: ContractAddress,
#[key]
token_id: u256,
}

Expand Down Expand Up @@ -72,8 +76,9 @@ mod SpookyScoreNFT {
self.next_token_id.write(token_id + 1);

// Emit events
let zero_address: ContractAddress = Zero::zero();
self.emit(Transfer {
from: starknet::contract_address_const::<0>(),
from: zero_address,
to: recipient,
token_id
});
Expand Down Expand Up @@ -115,7 +120,7 @@ mod SpookyScoreNFT {

fn token_uri(self: @ContractState, token_id: u256) -> ByteArray {
// Return base URI + token_id for metadata
let mut uri = self.token_uri_base.read();
let uri = self.token_uri_base.read();
// In production, append token_id and .json
uri
}
Expand Down
3 changes: 3 additions & 0 deletions contracts/target/CACHEDIR.TAG
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by scarb.
# For information about cache directory tags see https://bford.info/cachedir/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":1,"contracts":[{"id":"62vbes6m9i2p4","package_name":"spooky_score_nft","contract_name":"SpookyScoreNFT","module_path":"spooky_score_nft::SpookyScoreNFT","artifacts":{"sierra":"spooky_score_nft_SpookyScoreNFT.contract_class.json","casm":null}}]}

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ define([
state.currentScore = score;
overlayText.textContent = 'Game Over - Score: ' + score;

if(state.walletConnected && score >= 10){
if(state.walletConnected && score >= 5){
btnMint.style.display = 'block';
btnMint.disabled = false;
btnMint.textContent = 'Mint NFT 🎃';
Expand Down
133 changes: 66 additions & 67 deletions src/web3/StarknetWalletAdapter.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
define(["dojo/_base/lang"], function(lang){
var wallet = null;
var account = null;
define(["dojo/_base/lang"], function(lang) {
var wallet = null;
var account = null;
var constants = null;
var Contract = null;

// NFT Contract Configuration
var NFT_CONTRACT_ADDRESS = "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"; // Placeholder
var NFT_CONTRACT_ADDRESS = "0x01a670c9f5766759970aa71b9d754825490d29c35fedfd2cbd63d9b9f4175f2b"; // Deployed on Sepolia

function ensureLibraries(){
if(constants) return Promise.resolve();
function ensureLibraries() {
if (constants) return Promise.resolve();

// Lazy-load get-starknet and starknet.js via unpkg
return Promise.all([
import('https://unpkg.com/get-starknet-core@3.3.3/dist/index.mjs'),
import('https://unpkg.com/starknet@latest/dist/index.mjs')
]).then(function(mods){
]).then(function(mods) {
var getStarknet = mods[0];
constants = mods[1].constants;
Contract = mods[1].Contract;
Expand All @@ -25,101 +26,99 @@ define(["dojo/_base/lang"], function(lang){
}

return {
connect: function(){
return ensureLibraries().then(function(){
connect: function() {
return ensureLibraries().then(function() {
// Use get-starknet to connect to any Starknet wallet (Ready, Argent, Braavos...)
return window.getStarknet({
modalMode: "alwaysAsk",
modalTheme: "dark"
});
}).then(function(starknetWallet){
if(!starknetWallet) throw new Error('No Starknet wallet found');

wallet = starknetWallet;
const starknet = window.getStarknet();

// Enable the wallet and get account
return wallet.enable({
starknetVersion: "v5"
return starknet.enable({
modalMode: "alwaysAsk",
modalTheme: "dark"
}).then(function() {
if (!starknet.isConnected) {
throw new Error('Wallet connection failed');
}

wallet = starknet;
account = starknet.account;

if (!account || !account.address) {
throw new Error('Could not get account address');
}

console.log('Connected to Starknet wallet:', account.address);
return {
address: account.address,
account: account
};
});
}).then(function(addresses){
if(!addresses || addresses.length === 0) {
throw new Error('Wallet connection failed');
}

// Get the account object
account = wallet.account;

console.log('Connected to Starknet wallet:', addresses[0]);
return { address: addresses[0] };
}).catch(function(error) {
console.error('Wallet connection error:', error);
throw error;
});
},

isConnected: function(){
return !!account && wallet && wallet.isConnected;
isConnected: function() {
return !!(account && wallet && wallet.isConnected);
},

getAddress: function(){
getAddress: function() {
return account ? account.address : null;
},

disconnect: function(){
account = null;
disconnect: function() {
wallet = null;
account = null;
},

mintScoreNFT: function(score){
return ensureLibraries().then(function(){
if(!account) throw new Error('Wallet not connected');

console.log('Minting Spooky NFT for score:', score);
mintScoreNFT: function(score) {
return ensureLibraries().then(function() {
if (!account) throw new Error('Wallet not connected');

// Call the mint_score_nft function on the contract
// Parameters: recipient (address), score (u256), timestamp (u64)
var timestamp = Math.floor(Date.now() / 1000);

// Construct calldata for u256 score (split into low and high)
var scoreLow = score;
var scoreHigh = 0;
var scoreHigh = '0x0';

return account.execute([
{
contractAddress: NFT_CONTRACT_ADDRESS,
entrypoint: 'mint_score_nft',
calldata: [
account.address, // recipient
scoreLow, // score low (u128)
scoreHigh, // score high (u128) - u256 is split into two u128
timestamp // timestamp (u64)
]
}
]);
}).then(function(result){
console.log('NFT Minted! Transaction:', result.transaction_hash);
// Call the contract
return account.execute({
contractAddress: NFT_CONTRACT_ADDRESS,
entrypoint: 'mint_score_nft',
calldata: [
account.address, // recipient
scoreLow, // score low
scoreHigh, // score high (0 for u256)
timestamp // timestamp
]
});
}).then(function(result) {
console.log('NFT mint transaction:', result);
return {
success: true,
txHash: result.transaction_hash,
message: 'Spooky NFT minted successfully! 🎃'
message: 'NFT minted successfully!'
};
}).catch(function(err){
console.error('NFT Minting failed:', err);
}).catch(function(error) {
console.error('Error minting NFT:', error);
return {
success: false,
error: err.message || 'Minting failed'
error: error.message
};
});
},

submitScore: function(score){
// Submit score to leaderboard contract (if different from NFT contract)
return ensureLibraries().then(function(){
if(!account) throw new Error('Not connected');
console.log('Submitting score to leaderboard:', score);
// Implement leaderboard submission if needed
return { txHash: '0x0' };
});
submitScore: function(score) {
// For future leaderboard implementation
return Promise.resolve({ success: true });
},

fetchLeaderboard: function(){
fetchLeaderboard: function() {
// For future leaderboard implementation
return Promise.resolve([]);
return Promise.resolve([]);
}
};
Expand Down