diff --git a/.gitignore b/.gitignore index e7a6e25d..54735cfd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # Sys files **/.DS_Store +.env +.env.main +.env.sep # Scarb and Starknet Foundry **/target diff --git a/backend/routes/indexer/pixel.go b/backend/routes/indexer/pixel.go index d660321a..55900b0e 100644 --- a/backend/routes/indexer/pixel.go +++ b/backend/routes/indexer/pixel.go @@ -234,3 +234,37 @@ func revertExtraPixelsPlacedEvent(event IndexerEvent) { return } } + +func processHostAwardedPixelsEvent(event IndexerEvent) { + user := event.Event.Keys[1][2:] // Remove 0x prefix + awardHex := event.Event.Data[0] + + award, err := strconv.ParseInt(awardHex, 0, 64) + if err != nil { + PrintIndexerError("processHostAwardedPixelsEvent", "Error converting award hex to int", user, awardHex) + return + } + + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "INSERT INTO ExtraPixels (address, available, used) VALUES ($1, $2, 0) ON CONFLICT (address) DO UPDATE SET available = ExtraPixels.available + $2", user, award) + if err != nil { + PrintIndexerError("processHostAwardedPixelsEvent", "Error updating extra pixels in postgres", user, awardHex) + return + } +} + +func revertHostAwardedPixelsEvent(event IndexerEvent) { + user := event.Event.Keys[1][2:] // Remove 0x prefix + awardHex := event.Event.Data[0] + + award, err := strconv.ParseInt(awardHex, 0, 64) + if err != nil { + PrintIndexerError("revertHostAwardedPixelsEvent", "Error converting award hex to int", user, awardHex) + return + } + + _, err = core.ArtPeaceBackend.Databases.Postgres.Exec(context.Background(), "UPDATE ExtraPixels SET available = ExtraPixels.available - $1 WHERE address = $2", award, user) + if err != nil { + PrintIndexerError("revertHostAwardedPixelsEvent", "Error updating extra pixels in postgres", user, awardHex) + return + } +} diff --git a/backend/routes/indexer/route.go b/backend/routes/indexer/route.go index 3687b1f0..7e001112 100644 --- a/backend/routes/indexer/route.go +++ b/backend/routes/indexer/route.go @@ -81,6 +81,7 @@ const ( factionTemplateRemovedEvent = "0x029a976c0074fc910f3a6a58f1351c48dab7b1c539f54ed930616292c806283f" chainFactionTemplateAddedEvent = "0x00476f35ea27024c89c1fc05dfad873e9e93419e452ee781e8207e435289a39b" chainFactionTemplateRemovedEvent = "0x0126718de7cb8b83dfa258eb095bc0ec7a3ef5a2258ebd1ed349551764856c6b" + hostAwardedPixelsEvent = "0x03cab98018a5e38e0cf717d8bed481983eb400f6a1d9ccd34f87050c0f36a32a" ) var eventProcessors = map[string](func(IndexerEvent)){ @@ -111,6 +112,7 @@ var eventProcessors = map[string](func(IndexerEvent)){ factionTemplateRemovedEvent: processFactionTemplateRemovedEvent, chainFactionTemplateAddedEvent: processChainFactionTemplateAddedEvent, chainFactionTemplateRemovedEvent: processChainFactionTemplateRemovedEvent, + hostAwardedPixelsEvent: processHostAwardedPixelsEvent, } var eventReverters = map[string](func(IndexerEvent)){ @@ -141,6 +143,7 @@ var eventReverters = map[string](func(IndexerEvent)){ factionTemplateRemovedEvent: revertFactionTemplateRemovedEvent, chainFactionTemplateAddedEvent: revertChainFactionTemplateAddedEvent, chainFactionTemplateRemovedEvent: revertChainFactionTemplateRemovedEvent, + hostAwardedPixelsEvent: revertHostAwardedPixelsEvent, } var eventRequiresOrdering = map[string]bool{ @@ -171,6 +174,7 @@ var eventRequiresOrdering = map[string]bool{ factionTemplateRemovedEvent: true, chainFactionTemplateAddedEvent: true, chainFactionTemplateRemovedEvent: true, + hostAwardedPixelsEvent: false, } const ( diff --git a/backend/routes/templates.go b/backend/routes/templates.go index e36a5d17..6d0512d1 100644 --- a/backend/routes/templates.go +++ b/backend/routes/templates.go @@ -23,6 +23,7 @@ func InitTemplateRoutes() { http.HandleFunc("/get-templates", getTemplates) http.HandleFunc("/get-faction-templates", getFactionTemplates) http.HandleFunc("/get-chain-faction-templates", getChainFactionTemplates) + http.HandleFunc("/build-template-img", buildTemplateImg) http.HandleFunc("/add-template-img", addTemplateImg) http.HandleFunc("/add-template-data", addTemplateData) if !core.ArtPeaceBackend.BackendConfig.Production { @@ -56,33 +57,44 @@ func hashTemplateImage(pixelData []byte) string { return "00" + hashStr[2:] } -func bytesToRGBA(colorBytes []byte) color.RGBA { - r := colorBytes[0] - g := colorBytes[1] - b := colorBytes[2] - return color.RGBA{r, g, b, 0xFF} +func hexToRGBA(colorBytes string) color.RGBA { + // Hex like "rrggbb" + r, err := strconv.ParseUint(colorBytes[0:2], 16, 8) + if err != nil { + return color.RGBA{} + } + g, err := strconv.ParseUint(colorBytes[2:4], 16, 8) + if err != nil { + return color.RGBA{} + } + b, err := strconv.ParseUint(colorBytes[4:6], 16, 8) + if err != nil { + return color.RGBA{} + } + return color.RGBA{uint8(r), uint8(g), uint8(b), 255} } -func imageToPixelData(imageData []byte) ([]byte, error) { +func imageToPixelData(imageData []byte) ([]int, error) { img, _, err := image.Decode(bytes.NewReader(imageData)) if err != nil { return nil, err } - colors, err := core.PostgresQueryJson[ColorType]("SELECT hex FROM colors ORDER BY key") + colors, err := core.PostgresQuery[ColorType]("SELECT hex FROM colors ORDER BY key") if err != nil { return nil, err } - colorCount := len(colors) / 3 + colorCount := len(colors) palette := make([]color.Color, colorCount) for i := 0; i < colorCount; i++ { - palette[i] = bytesToRGBA(colors[i*3 : i*3+3]) + colorHex := colors[i] + palette[i] = hexToRGBA(colorHex) } bounds := img.Bounds() width, height := bounds.Max.X, bounds.Max.Y - pixelData := make([]byte, width*height) + pixelData := make([]int, width*height) for y := 0; y < height; y++ { for x := 0; x < width; x++ { @@ -91,7 +103,7 @@ func imageToPixelData(imageData []byte) ([]byte, error) { pixelData[y*width+x] = 0xFF } else { closestIndex := findClosestColor(rgba, palette) - pixelData[y*width+x] = byte(closestIndex) + pixelData[y*width+x] = closestIndex } } } @@ -114,7 +126,10 @@ func findClosestColor(target color.RGBA, palette []color.Color) int { } func colorDistance(c1, c2 color.RGBA) float64 { - return math.Sqrt(float64((c1.R-c2.R)*(c1.R-c2.R) + (c1.G-c2.G)*(c1.G-c2.G) + (c1.B-c2.B)*(c1.B-c2.B))) + r_diff := float64(int(c1.R) - int(c2.R)) + g_diff := float64(int(c1.G) - int(c2.G)) + b_diff := float64(int(c1.B) - int(c2.B)) + return math.Sqrt(r_diff*r_diff + g_diff*g_diff + b_diff*b_diff) } type TemplateData struct { @@ -179,6 +194,92 @@ func getChainFactionTemplates(w http.ResponseWriter, r *http.Request) { routeutils.WriteDataJson(w, string(factionTemplates)) } +// curl -F "image=@" http://localhost:8080/build-template-img?start=0 +func buildTemplateImg(w http.ResponseWriter, r *http.Request) { + file, _, err := r.FormFile("image") + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Failed to read image") + return + } + defer file.Close() + + startStr := r.URL.Query().Get("start") + start, err := strconv.Atoi(startStr) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Invalid start position") + return + } + + // Decode the image to check dimensions + img, _, err := image.Decode(file) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusBadRequest, "Failed to decode image") + return + } + bounds := img.Bounds() + width, _ := bounds.Max.X-bounds.Min.X, bounds.Max.Y-bounds.Min.Y + file.Seek(0, 0) + + fileBytes, err := io.ReadAll(file) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to read image data") + + return + } + + imageData, err := imageToPixelData(fileBytes) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to convert image to pixel data") + return + } + + imageDataBytes := make([]byte, len(imageData)) + for idx, val := range imageData { + imageDataBytes[idx] = byte(val) + } + hash := hashTemplateImage(imageDataBytes) + + // Make file to store template data + if _, err := os.Stat("templates-build"); os.IsNotExist(err) { + err = os.Mkdir("templates-build", os.ModePerm) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create templates directory") + return + } + } + + filename := fmt.Sprintf("templates-build/template-%s.txt", hash) + newtemp, err := os.Create(filename) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to create image file") + return + } + defer file.Close() + + // TODO: Hardcoded width + x := start % 512 + y := start / 512 + start_x := x + end_x := x + width + // Write image data to file + for _, pixel := range imageData { + pos := y*512 + x + // Convert byte to integer representation + if pixel != 0xFF { + _, err := newtemp.WriteString(fmt.Sprintf("%d %d\n", pos, int(pixel))) + if err != nil { + routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to write image data") + return + } + } + x++ + if x >= end_x { + x = start_x + y++ + } + } +} + func addTemplateImg(w http.ResponseWriter, r *http.Request) { file, _, err := r.FormFile("image") if err != nil { @@ -216,7 +317,12 @@ func addTemplateImg(w http.ResponseWriter, r *http.Request) { routeutils.WriteErrorJson(w, http.StatusInternalServerError, "Failed to convert image to pixel data") return } - hash := hashTemplateImage(imageData) + + imageDataBytes := make([]byte, len(imageData)) + for idx, val := range imageData { + imageDataBytes[idx] = byte(val) + } + hash := hashTemplateImage(imageDataBytes) if _, err := os.Stat("templates"); os.IsNotExist(err) { err = os.Mkdir("templates", os.ModePerm) diff --git a/configs/factions.config.json b/configs/factions.config.json index dc210009..06ba1935 100644 --- a/configs/factions.config.json +++ b/configs/factions.config.json @@ -4,7 +4,7 @@ "id": 1, "name": "Ducks Everywhere", "icon": "$BACKEND_URL/faction-images/ducks-everywhere.png", - "leader": "0x05bd7adfE8AfaA58300aDC72bF5584b191E236987Fe16A217b1a3e067869A0Aa", + "leader": "0x5cf1e201ace78e436ec07748ddd7312db07a9d588e8b177ded3eb612daad80e", "joinable": true, "allocation": 1, "links": { @@ -18,80 +18,80 @@ }, { "id": 2, - "name": "WASD", - "icon": "$BACKEND_URL/faction-images/wasd.png", + "name": "Realms", + "icon": "$BACKEND_URL/faction-images/realms.png", "leader": "0x05bd7adfE8AfaA58300aDC72bF5584b191E236987Fe16A217b1a3e067869A0Aa", "joinable": true, "allocation": 1, "links": { - "telegram": "https://t.me/art_peace_game/3", - "twitter": "https://twitter.com/WASD_0x", - "github": "", - "site": "https://bento.me/wasd" + "telegram": "https://t.me/art_peace_game/25", + "twitter": "https://x.com/LootRealms", + "github": "https://github.com/bibliothecaDAO", + "site": "https://realms.world" }, "members": [ ] }, { "id": 3, - "name": "Influence", - "icon": "$BACKEND_URL/faction-images/influence.png", - "leader": "0x05bd7adfE8AfaA58300aDC72bF5584b191E236987Fe16A217b1a3e067869A0Aa", + "name": "Wolf Pack League", + "icon": "$BACKEND_URL/faction-images/wpl.png", + "leader": "0x0179caf1b35202a0a544109cc04caf07a813755d1a15927e025ad874108b0543", "joinable": true, "allocation": 1, "links": { - "telegram": "https://t.me/art_peace_game/4", - "twitter": "https://x.com/influenceth", + "telegram": "https://t.me/art_peace_game/26", + "twitter": "https://x.com/WPLSTARK", "github": "", - "site": "https://www.influenceth.io/" + "site": "https://www.thewpl.xyz" }, "members": [ ] }, { "id": 4, - "name": "Ark Project", - "icon": "$BACKEND_URL/faction-images/ark-project.png", - "leader": "0x05bd7adfE8AfaA58300aDC72bF5584b191E236987Fe16A217b1a3e067869A0Aa", + "name": "zkCampus", + "icon": "$BACKEND_URL/faction-images/zk-campus.png", + "leader": "0x00Cca69dDF0833Bd6De5d0074949dC45820113fd6F4124a1C380304EDC4d79E9", "joinable": true, "allocation": 1, "links": { - "telegram": "https://t.me/art_peace_game/5", - "twitter": "https://x.com/arkprojectnfts", + "telegram": "https://t.me/art_peace_game/27", + "twitter": "https://x.com/ZkCampus", "github": "", - "site": "https://www.arkproject.dev/" + "site": "https://medium.com/@ZkCampus" }, "members": [ ] }, { "id": 5, - "name": "Everai", - "icon": "$BACKEND_URL/faction-images/everai.png", - "leader": "0x05bd7adfE8AfaA58300aDC72bF5584b191E236987Fe16A217b1a3e067869A0Aa", + "name": "Starknet Africa", + "icon": "$BACKEND_URL/faction-images/sn_africa.png", + "leader": "0x0091549c2fe52f07266a21228406131df81b39a7f4793cf23c8cb30d4c667850", "joinable": true, "allocation": 1, "links": { - "telegram": "https://t.me/art_peace_game/6", - "twitter": "https://x.com/everai", + "telegram": "https://t.me/art_peace_game/12", + "twitter": "https://x.com/StarkNetAfrica", "github": "", - "site": "https://www.everai.xyz/" + "site": "https://www.starknet.africa" }, "members": [ ] }, { "id": 6, - "name": "2077 Collective", - "icon": "$BACKEND_URL/faction-images/2077.png", + "name": "Influence", + "icon": "$BACKEND_URL/faction-images/influence.png", "leader": "0x05bd7adfE8AfaA58300aDC72bF5584b191E236987Fe16A217b1a3e067869A0Aa", "joinable": true, "allocation": 1, "links": { - "telegram": "https://t.me/art_peace_game/7", - "twitter": "https://x.com/2077Collective", + "telegram": "https://t.me/art_peace_game/4", + "twitter": "https://x.com/influenceth", "github": "", - "site": "https://2077.xyz/" + "site": "https://www.influenceth.io/" }, "members": [ ] @@ -100,7 +100,7 @@ "id": 7, "name": "Argent", "icon": "$BACKEND_URL/faction-images/argent.png", - "leader": "0x05bd7adfE8AfaA58300aDC72bF5584b191E236987Fe16A217b1a3e067869A0Aa", + "leader": "0x02775831D81d35d30Cd842511331b482E4275Cd1c8a34AE832aDb0D282307AFd", "joinable": true, "allocation": 1, "links": { diff --git a/configs/mainnet-contracts.config.json b/configs/mainnet-contracts.config.json index 09f79a38..a5ac3668 100644 --- a/configs/mainnet-contracts.config.json +++ b/configs/mainnet-contracts.config.json @@ -1,5 +1,5 @@ { "usernameContract": "0x03d9ac2a08d83dcbcf7a05358dd77e05d2b094d3c13232611fe86ee957c34d02", - "artPeace": "0x0384e5fa826e1151715dbc3df0bd1b3aaec234dfc344394d02c39b670e354c48", - "canvasNFT": "0x009835b97a6756aca76edfd603aacb386d81fc5c34148912a306e9c22a61b748" + "artPeace": "0x067883deb1c1cb60756eb6e60d500081352441a040d5039d0e4ce9fed35d68c1", + "canvasNFT": "0x042dbc0bbdb0faaad99d0b116d0105f9e213ac0d2faf75c878f49d69b544befb" } diff --git a/configs/production-quests.config.json b/configs/production-quests.config.json index 64862885..7bc59712 100644 --- a/configs/production-quests.config.json +++ b/configs/production-quests.config.json @@ -6,15 +6,15 @@ "day": 1, "quests": [ { - "name": "Place 5 pixels", - "description": "Add 5 pixels on the canvas", - "reward": 3, + "name": "Place 10 pixels", + "description": "Place 10 pixels on the canvas today", + "reward": 10, "questContract": { "type": "PixelQuest", "initParams": [ "$ART_PEACE_CONTRACT", "$REWARD", - "5", + "10", "1", "$DAY_IDX", "0", @@ -26,7 +26,7 @@ { "name": "Create a username", "description": "Claim a username in the accounts tab", - "reward": 3, + "reward": 7, "questContract": { "type": "UsernameQuest", "initParams": [ @@ -38,16 +38,17 @@ } }, { - "name": "Represent your chain", - "description": "Join a faction to represent your favorite chain in the factions tab", - "reward": 3, + "name": "Cast your vote", + "description": "Vote to add a color to the palette in the vote tab", + "reward": 7, "questContract": { - "type": "ChainFactionQuest", + "type": "VoteQuest", "initParams": [ "$ART_PEACE_CONTRACT", - "$REWARD" + "$REWARD", + "$DAY_IDX" ], - "storeParams": [] + "storeParams": [2] } } ] @@ -57,14 +58,14 @@ "quests": [ { "name": "The Void", - "description": "Place 10 black pixels and spread the void", - "reward": 5, + "description": "Place 15 black pixels and spread the void", + "reward": 10, "questContract": { "type": "PixelQuest", "initParams": [ "$ART_PEACE_CONTRACT", "$REWARD", - "10", + "15", "1", "$DAY_IDX", "1", @@ -76,7 +77,7 @@ { "name": "Mint art/peace NFT", "description": "Mint your artwork from the canvas under the NFT tab", - "reward": 5, + "reward": 10, "questContract": { "type": "NFTMintQuest", "initParams": [ @@ -99,7 +100,7 @@ { "name": "Cast your vote", "description": "Vote to add a color to the palette in the vote tab", - "reward": 3, + "reward": 7, "questContract": { "type": "VoteQuest", "initParams": [ @@ -116,15 +117,15 @@ "day": 3, "quests": [ { - "name": "Place 5 pixels", - "description": "Add 5 pixels on the canvas", - "reward": 3, + "name": "Place 15 pixels", + "description": "Place 15 pixels on the canvas", + "reward": 13, "questContract": { "type": "PixelQuest", "initParams": [ "$ART_PEACE_CONTRACT", "$REWARD", - "5", + "15", "1", "$DAY_IDX", "0", @@ -135,8 +136,8 @@ }, { "name": "Last color vote", - "description": "Cast your vote in the last color vote in the vote tab", - "reward": 3, + "description": "Vote for the last color to addd to the palette in the vote tab", + "reward": 10, "questContract": { "type": "VoteQuest", "initParams": [ @@ -146,6 +147,24 @@ ], "storeParams": [2] } + }, + { + "name": "Become a Pixel Wizard", + "description": "Add 50 total pixels onto the canvas", + "reward": 15, + "questContract": { + "type": "PixelQuest", + "initParams": [ + "$ART_PEACE_CONTRACT", + "$REWARD", + "50", + "0", + "0", + "0", + "0" + ], + "storeParams": [2,3,4,5,6] + } } ] }, @@ -154,14 +173,14 @@ "quests": [ { "name": "Endgame Pixels", - "description": "Place 5 pixels on the canvas to try and appear on the final snapshot", - "reward": 3, + "description": "Place 10 pixels on the canvas to try and appear on the final snapshot", + "reward": 10, "questContract": { "type": "PixelQuest", "initParams": [ "$ART_PEACE_CONTRACT", "$REWARD", - "5", + "10", "1", "$DAY_IDX", "0", @@ -173,7 +192,7 @@ { "name": "Finalize your art piece", "description": "Mint an NFT of your artwork to keep it forever", - "reward": 5, + "reward": 10, "questContract": { "type": "NFTMintQuest", "initParams": [ @@ -202,7 +221,7 @@ { "name": "The Rainbow", "description": "Place at least 1 pixel of each color", - "reward": 10, + "reward": 13, "questContract": { "type": "RainbowQuest", "initParams": [ @@ -212,10 +231,23 @@ "storeParams": [] } }, + { + "name": "Represent your chain", + "description": "Join a faction to represent your favorite chain in the factions tab", + "reward": 7, + "questContract": { + "type": "ChainFactionQuest", + "initParams": [ + "$ART_PEACE_CONTRACT", + "$REWARD" + ], + "storeParams": [] + } + }, { "name": "Join a Faction", "description": "Represent a community by joining their faction on the factions tab", - "reward": 3, + "reward": 7, "questContract": { "type": "FactionQuest", "initParams": [ @@ -228,7 +260,7 @@ { "name": "HODL", "description": "Accumulate 10 extra pixels in your account", - "reward": 5, + "reward": 10, "questContract": { "type": "HodlQuest", "initParams": [ @@ -240,36 +272,15 @@ } }, { - "name": "Deploy a Memecoin", - "description": "Create your own [Unruggable memecoin](https://www.unruggable.meme/)", - "reward": 15, - "questContract": { - "type": "UnruggableQuest", - "initParams": [ - "$ART_PEACE_CONTRACT", - "$REWARD" - ], - "storeParams": [], - "claimParams": [ - { - "type": "address", - "name": "MemeCoin Address", - "example": "0x02D7B50EBF415606D77C7E7842546FC13F8ACFBFD16F7BCF2BC2D08F54114C23", - "input": true - } - ] - } - }, - { - "name": "Place 100 pixels", - "description": "Add 100 pixels on the canvas", - "reward": 15, + "name": "Become Alpha Wolf", + "description": "Add 500 pixels on the canvas", + "reward": 50, "questContract": { "type": "PixelQuest", "initParams": [ "$ART_PEACE_CONTRACT", "$REWARD", - "100", + "500", "0", "0", "0", diff --git a/frontend/src/App.js b/frontend/src/App.js index b6683c15..95a9c427 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -263,7 +263,7 @@ function App() { const updateInterval = 1000; // 1 second // TODO: make this a config - const timeBetweenPlacements = 30000; // 30 seconds + const timeBetweenPlacements = process.env.REACT_APP_BASE_PIXEL_TIMER; // Example: 30 * 1000; // 30 seconds const [basePixelTimer, setBasePixelTimer] = useState('XX:XX'); useEffect(() => { const updateBasePixelTimer = () => { diff --git a/frontend/src/tabs/account/Account.js b/frontend/src/tabs/account/Account.js index 7a654e87..3f0e547c 100644 --- a/frontend/src/tabs/account/Account.js +++ b/frontend/src/tabs/account/Account.js @@ -334,17 +334,17 @@ const Account = (props) => { }, [animatedRankColor, pixelCount]); useEffect(() => { - if (pixelCount >= 50) { + if (pixelCount >= 500) { setAccountRank('Alpha Wolf'); setAccountRankImg(WolfRankImg); - } else if (pixelCount >= 30) { + } else if (pixelCount >= 250) { setAccountRank('Degen Artist'); setRankBackground({ background: 'linear-gradient(45deg, rgba(255, 215, 0, 0.9), rgba(255, 215, 0, 0.6))' }); setAccountRankImg(CrownRankImg); - } else if (pixelCount >= 10) { + } else if (pixelCount >= 50) { setAccountRank('Pixel Wizard'); setRankBackground({ background: diff --git a/frontend/src/tabs/nfts/NFTItem.css b/frontend/src/tabs/nfts/NFTItem.css index 3fac2d52..b37446ac 100644 --- a/frontend/src/tabs/nfts/NFTItem.css +++ b/frontend/src/tabs/nfts/NFTItem.css @@ -200,4 +200,3 @@ transform: translateX(120%); transition: all 150ms; } - diff --git a/frontend/src/tabs/nfts/NFTItem.js b/frontend/src/tabs/nfts/NFTItem.js index d58ffb28..a961b413 100644 --- a/frontend/src/tabs/nfts/NFTItem.js +++ b/frontend/src/tabs/nfts/NFTItem.js @@ -142,7 +142,11 @@ const NFTItem = (props) => {

{props.name}

-
+
Share
ContractAddress { + self.host.read() + } + fn check_game_running(self: @ContractState) { let block_timestamp = starknet::get_block_timestamp(); assert(block_timestamp <= self.end_time.read(), 'ArtPeace game has ended'); @@ -821,11 +833,11 @@ pub mod ArtPeace { assert( starknet::get_caller_address() == self.host.read(), 'Quests are set by the host' ); - let mut i = self.main_quests_count.read(); + let quest_count = self.main_quests_count.read(); + let mut i = quest_count; let end = i + quests.len(); while i < end { - // TODO: This should be i - self.main_quests_count.read() - self.main_quests.write(i, *quests.at(i)); + self.main_quests.write(i, *quests.at(i - quest_count)); i += 1; }; self.main_quests_count.write(end); @@ -1008,6 +1020,23 @@ pub mod ArtPeace { ) -> u32 { self.user_pixels_placed.read((day, user, color)) } + + fn host_set_timer(ref self: ContractState, time: u64) { + assert(starknet::get_caller_address() == self.host.read(), 'Host sets timer'); + self.time_between_pixels.write(time); + self.time_between_member_pixels.write(time); + } + + fn host_award_user(ref self: ContractState, user: starknet::ContractAddress, amount: u32) { + assert(starknet::get_caller_address() == self.host.read(), 'Host awards user'); + self.extra_pixels.write(user, self.extra_pixels.read(user) + amount); + self.emit(HostAwardedUser { user, amount }); + } + + fn host_change_end_time(ref self: ContractState, new_end_time: u64) { + assert(starknet::get_caller_address() == self.host.read(), 'Host changes end time'); + self.end_time.write(new_end_time); + } } #[abi(embed_v0)] diff --git a/onchain/src/interfaces.cairo b/onchain/src/interfaces.cairo index 203c2ff6..be586289 100644 --- a/onchain/src/interfaces.cairo +++ b/onchain/src/interfaces.cairo @@ -37,6 +37,7 @@ pub trait IArtPeace { fn get_width(self: @TContractState) -> u128; fn get_height(self: @TContractState) -> u128; fn get_total_pixels(self: @TContractState) -> u128; + fn get_host(self: @TContractState) -> starknet::ContractAddress; // Assertion helpers fn check_game_running(self: @TContractState); @@ -149,4 +150,9 @@ pub trait IArtPeace { fn get_user_pixels_placed_day_color( self: @TContractState, user: starknet::ContractAddress, day: u32, color: u8 ) -> u32; + + // Host functions + fn host_set_timer(ref self: TContractState, time: u64); + fn host_award_user(ref self: TContractState, user: starknet::ContractAddress, amount: u32); + fn host_change_end_time(ref self: TContractState, new_end_time: u64); } diff --git a/resources/factions/realms.png b/resources/factions/realms.png new file mode 100644 index 00000000..1b52d664 Binary files /dev/null and b/resources/factions/realms.png differ diff --git a/resources/factions/wpl.png b/resources/factions/wpl.png new file mode 100644 index 00000000..968870b5 Binary files /dev/null and b/resources/factions/wpl.png differ diff --git a/resources/factions/zk-campus.png b/resources/factions/zk-campus.png new file mode 100644 index 00000000..aaebe509 Binary files /dev/null and b/resources/factions/zk-campus.png differ diff --git a/scripts/bot-builder.sh b/scripts/bot-builder.sh new file mode 100755 index 00000000..4bf467b7 --- /dev/null +++ b/scripts/bot-builder.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Use accounts to run builders + +echo "Running bot builder" + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +WORK_DIR=$SCRIPT_DIR/.. + +ACCOUNTS_DIR=$1 +TEMPLATE_FILE=$2 + +# loop thru all files of format art-peace-.json +for file in $ACCOUNTS_DIR/art-peace-*-signer.json; do + SIGNER=$(basename $file) + ACCOUNT=$(echo $SIGNER | sed 's/-signer.json/.json/') + $SCRIPT_DIR/builder-full.sh $TEMPLATE_FILE $ACCOUNTS_DIR/$ACCOUNT $ACCOUNTS_DIR/$SIGNER & + sleep 2 +done diff --git a/scripts/builder-full.sh b/scripts/builder-full.sh new file mode 100755 index 00000000..8b124851 --- /dev/null +++ b/scripts/builder-full.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# +# Draws something on the canvas + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +WORK_DIR=$SCRIPT_DIR/.. + +TEMPLATE_FILE=$1 +ACCOUNT_FILE=$2 +SIGNER_FILE=$3 + +FILE_LEN=$(cat $TEMPLATE_FILE | wc -l) +# loop till file empty +while [ $FILE_LEN -gt 0 ] +do + $SCRIPT_DIR/set-board-rand-builder4.sh $TEMPLATE_FILE $ACCOUNT_FILE $SIGNER_FILE + # Wait 30 seconds + sleep 30 + FILE_LEN=$(cat $TEMPLATE_FILE | wc -l) +done diff --git a/scripts/generate-accounts.sh b/scripts/generate-accounts.sh new file mode 100755 index 00000000..ba7c7ec4 --- /dev/null +++ b/scripts/generate-accounts.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Generate a list of accounts to play art/peace + +# Number of accounts to generate +NUM_ACCOUNTS=$1 +OUT_DIR=$2 +FUND_ACCOUNT=$3 +FUND_KEY=$4 + +# Generate accounts +for i in $(seq 1 $NUM_ACCOUNTS); do + echo "Creating account art-peace-$i" + starkli signer keystore new --password "" $OUT_DIR/art-peace-$i-signer.json + INIT_RES=$(starkli account oz init --keystore $OUT_DIR/art-peace-$i-signer.json --keystore-password "" $OUT_DIR/art-peace-$i.json 2>&1) + EXPECTED_ADDRESS=$(echo $INIT_RES | grep -o "0x[0-9a-fA-F]*") + echo "$EXPECTED_ADDRESS" > $OUT_DIR/art-peace-$i-address.txt + echo "Account art-peace-$i created with address $EXPECTED_ADDRESS" + # fund account + starkli invoke --network sepolia --keystore $FUND_KEY --account $FUND_ACCOUNT --watch 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7 transfer $EXPECTED_ADDRESS 200000000000000000 0 + starkli account deploy --keystore $OUT_DIR/art-peace-$i-signer.json --keystore-password "" --network sepolia $OUT_DIR/art-peace-$i.json +done diff --git a/scripts/generate-builder.sh b/scripts/generate-builder.sh new file mode 100755 index 00000000..a68ffa0b --- /dev/null +++ b/scripts/generate-builder.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# +# Generates a template file + +OUTPUT_FILE=$1 +rm -f $OUTPUT_FILE +touch $OUTPUT_FILE + +IDX=512 +END_IDX=1024 +while [ $IDX -lt $END_IDX ]; do + COLOR=$(shuf -i 1-17 -n 1) + echo "$IDX $COLOR" >> $OUTPUT_FILE + IDX=$((IDX+1)) +done diff --git a/scripts/how-to-bot.txt b/scripts/how-to-bot.txt new file mode 100644 index 00000000..d66b8998 --- /dev/null +++ b/scripts/how-to-bot.txt @@ -0,0 +1,11 @@ +curl -F "image=@/Users/brandonroberts/workspace/keep-starknet-strange/art-peace/templates/me.png" http://localhost:8080/build-template-img\?start\=2565 +#TODO: Diff from canvas + +rm -rf +mkdir +./scripts/generate-accounts.sh <#> +./scripts/setup-bots.sh + +./scripts/bot-builder.sh + +#TODO: Fund the bots script diff --git a/scripts/set-board-rand-builder.sh b/scripts/set-board-rand-builder.sh new file mode 100755 index 00000000..9718a3f9 --- /dev/null +++ b/scripts/set-board-rand-builder.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# +# Draws something on the canvas + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +WORK_DIR=$SCRIPT_DIR/.. + +# Load env variable from `.env` only if they're not already set +if [ -z "$STARKNET_KEYSTORE" ] || [ -z "$STARKNET_ACCOUNT" ]; then + source $WORK_DIR/.env +fi + +# Check if required env variables are set, if not exit +if [ -z "$STARKNET_KEYSTORE" ]; then + echo "Error: STARKNET_KEYSTORE is not set." + exit 1 +elif [ -z "$STARKNET_ACCOUNT" ]; then + echo "Error: STARKNET_ACCOUNT is not set." + exit 1 +fi + +GAME_CONTRACTS=$WORK_DIR/configs/sepolia-contracts.config.json +BOARD_CONTRACT=$(cat $GAME_CONTRACTS | jq -r '.artPeace') + +TEMPLATE_FILE=$1 +TEMPLATE_LEN=$(cat $TEMPLATE_FILE | wc -l) + +RAND_POS=$(shuf -i 0-$((TEMPLATE_LEN-1)) -n 1) +# Get the RAND_POS line in the template file +RAND_LINE=$(sed -n "$((RAND_POS+1))p" $TEMPLATE_FILE) +# Read pos and color from line formatted like: +POSITION=$(echo $RAND_LINE | awk '{print $1}') +COLOR=$(echo $RAND_LINE | awk '{print $2}') + +echo "Placing pixel at $POSITION with color $COLOR" + +TIME=$(date +%s) + +echo "starkli invoke --network sepolia --keystore $STARKNET_KEYSTORE --account $STARKNET_ACCOUNT --watch $BOARD_CONTRACT place_pixel $POSITION $COLOR $TIME" +starkli invoke --network sepolia --keystore $STARKNET_KEYSTORE --account $STARKNET_ACCOUNT --watch $BOARD_CONTRACT place_pixel $POSITION $COLOR $TIME + +# remove the line from the template file +sed -in "$((RAND_POS+1))d" $TEMPLATE_FILE diff --git a/scripts/set-board-rand-builder4.sh b/scripts/set-board-rand-builder4.sh new file mode 100755 index 00000000..1a45f0e5 --- /dev/null +++ b/scripts/set-board-rand-builder4.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# +# Draws something on the canvas + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +WORK_DIR=$SCRIPT_DIR/.. +STARKNET_ACCOUNT=$2 +STARKNET_KEYSTORE=$3 + +# Load env variable from `.env` only if they're not already set +if [ -z "$STARKNET_KEYSTORE" ] || [ -z "$STARKNET_ACCOUNT" ]; then + source $WORK_DIR/.env +fi + +# Check if required env variables are set, if not exit +if [ -z "$STARKNET_KEYSTORE" ]; then + echo "Error: STARKNET_KEYSTORE is not set." + exit 1 +elif [ -z "$STARKNET_ACCOUNT" ]; then + echo "Error: STARKNET_ACCOUNT is not set." + exit 1 +fi + +GAME_CONTRACTS=$WORK_DIR/configs/sepolia-contracts.config.json +BOARD_CONTRACT=$(cat $GAME_CONTRACTS | jq -r '.artPeace') + +TEMPLATE_FILE=$1 +TEMPLATE_LEN=$(cat $TEMPLATE_FILE | wc -l) + +RAND_POS1=$(shuf -i 0-$((TEMPLATE_LEN-1)) -n 1) +RAND_POS2=$(shuf -i 0-$((TEMPLATE_LEN-1)) -n 1) +RAND_POS3=$(shuf -i 0-$((TEMPLATE_LEN-1)) -n 1) +RAND_POS4=$(shuf -i 0-$((TEMPLATE_LEN-1)) -n 1) +# Ensure that the random position is not the same as the previous one +if [ $TEMPLATE_LEN -gt 4 ]; then + while [ $RAND_POS2 -eq $RAND_POS1 ]; do + RAND_POS2=$(shuf -i 0-$((TEMPLATE_LEN-1)) -n 1) + done + while [ $RAND_POS3 -eq $RAND_POS1 ] || [ $RAND_POS3 -eq $RAND_POS2 ]; do + RAND_POS3=$(shuf -i 0-$((TEMPLATE_LEN-1)) -n 1) + done + while [ $RAND_POS4 -eq $RAND_POS1 ] || [ $RAND_POS4 -eq $RAND_POS2 ] || [ $RAND_POS4 -eq $RAND_POS3 ]; do + RAND_POS4=$(shuf -i 0-$((TEMPLATE_LEN-1)) -n 1) + done +fi +# Get the RAND_POS line in the template file +RAND_LINE1=$(sed -n "$((RAND_POS1+1))p" $TEMPLATE_FILE) +RAND_LINE2=$(sed -n "$((RAND_POS2+1))p" $TEMPLATE_FILE) +RAND_LINE3=$(sed -n "$((RAND_POS3+1))p" $TEMPLATE_FILE) +RAND_LINE4=$(sed -n "$((RAND_POS4+1))p" $TEMPLATE_FILE) +# Read pos and color from line formatted like: +POSITION1=$(echo $RAND_LINE1 | awk '{print $1}') +POSITION2=$(echo $RAND_LINE2 | awk '{print $1}') +POSITION3=$(echo $RAND_LINE3 | awk '{print $1}') +POSITION4=$(echo $RAND_LINE4 | awk '{print $1}') +COLOR1=$(echo $RAND_LINE1 | awk '{print $2}') +COLOR2=$(echo $RAND_LINE2 | awk '{print $2}') +COLOR3=$(echo $RAND_LINE3 | awk '{print $2}') +COLOR4=$(echo $RAND_LINE4 | awk '{print $2}') + +echo "Placing pixels at positions $POSITION1, $POSITION2, $POSITION3, $POSITION4 with colors $COLOR1, $COLOR2, $COLOR3, $COLOR4" + +TIME=$(date +%s) +sed -in "$((RAND_POS1+1))d;$((RAND_POS2+1))d;$((RAND_POS3+1))d;$((RAND_POS4+1))d" $TEMPLATE_FILE + +echo "starkli invoke --network sepolia --keystore $STARKNET_KEYSTORE --account $STARKNET_ACCOUNT --watch $BOARD_CONTRACT place_extra_pixels 4 $POSITION1 $POSITION2 $POSITION3 $POSITION4 4 $COLOR1 $COLOR2 $COLOR3 $COLOR4 $TIME" +starkli invoke --network sepolia --keystore $STARKNET_KEYSTORE --keystore-password "" --account $STARKNET_ACCOUNT --watch $BOARD_CONTRACT place_extra_pixels 4 $POSITION1 $POSITION2 $POSITION3 $POSITION4 4 $COLOR1 $COLOR2 $COLOR3 $COLOR4 $TIME + +# remove the lines from the template file diff --git a/scripts/setup-bots.sh b/scripts/setup-bots.sh new file mode 100755 index 00000000..da4638d3 --- /dev/null +++ b/scripts/setup-bots.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# Use accounts to run builders + +echo "Setting up bots" + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +WORK_DIR=$SCRIPT_DIR/.. + +ACCOUNTS_DIR=$1 +ART_PEACE_CONTRACT=0x06fde2e43914b859e7e554585d3bc0dbf93d3b0187096a7bdb2c4fe1d4e1547d + +# loop thru all files of format art-peace-.json +for file in $ACCOUNTS_DIR/art-peace-*-signer.json; do + SIGNER=$(basename $file) + ACCOUNT=$(echo $SIGNER | sed 's/-signer.json/.json/') + #$SCRIPT_DIR/builder-full.sh $TEMPLATE_FILE $ACCOUNTS_DIR/$ACCOUNT $ACCOUNTS_DIR/$SIGNER + #TODO: randomize the faction + starkli invoke --network sepolia --keystore $ACCOUNTS_DIR/$SIGNER --account $ACCOUNTS_DIR/$ACCOUNT --watch $ART_PEACE_CONTRACT join_faction 1 + starkli invoke --network sepolia --keystore $ACCOUNTS_DIR/$SIGNER --account $ACCOUNTS_DIR/$ACCOUNT --watch $ART_PEACE_CONTRACT join_chain_faction 1 +done diff --git a/tests/integration/mainnet/deploy-canvas-nft.sh b/tests/integration/mainnet/deploy-canvas-nft.sh index 0913dbed..ff2fcf8e 100755 --- a/tests/integration/mainnet/deploy-canvas-nft.sh +++ b/tests/integration/mainnet/deploy-canvas-nft.sh @@ -78,7 +78,7 @@ CANVAS_NFT_CONTRACT_CLASSHASH=$(echo $CANVAS_NFT_DECLARE_OUTPUT | tail -n 1 | aw echo "Contract class hash: $CANVAS_NFT_CONTRACT_CLASSHASH" # Deploying the contract -CALLDATA=$(echo 0 318195848183955342120051 10 0 4271952 3) +CALLDATA=$(echo 0 81458137135092567582733106 11 0 4271952 3) echo "Deploying the contract..." echo "starkli deploy --network mainnet --keystore $STARKNET_KEYSTORE --account $STARKNET_ACCOUNT --watch $CANVAS_NFT_CONTRACT_CLASSHASH $CALLDATA" starkli deploy --network mainnet --keystore $STARKNET_KEYSTORE --account $STARKNET_ACCOUNT --watch $CANVAS_NFT_CONTRACT_CLASSHASH $CALLDATA diff --git a/tests/integration/mainnet/deploy.sh b/tests/integration/mainnet/deploy.sh index b1bb80a9..ae7279e9 100755 --- a/tests/integration/mainnet/deploy.sh +++ b/tests/integration/mainnet/deploy.sh @@ -104,8 +104,8 @@ COLORS=$(jq -r '.colors[]' $CANVAS_CONFIG | sed 's/^/0x/') VOTABLE_COLOR_COUNT=$(jq -r '.votableColors[]' $CANVAS_CONFIG | wc -l | tr -d ' ') VOTABLE_COLORS=$(jq -r '.votableColors[]' $CANVAS_CONFIG | sed 's/^/0x/') DAILY_NEW_COLORS_COUNT=3 -START_TIME=1720430751 -END_TIME=1720776600 +START_TIME=1727424000 +END_TIME=1727769600 DEVNET_MODE=0 DAILY_QUESTS_COUNT=$(jq -r '.daily.dailyQuestsCount' $QUESTS_CONFIG)