diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 5e425885f..6b36694fa 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -5,6 +5,7 @@ on: branches: [ main, develop ] tags: - '[0-9]+.[0-9]+.[0-9]+*' + - '[0-9]+.[0-9]+.[0-9]+' pull_request: types: [ opened, synchronize ] workflow_dispatch: @@ -53,17 +54,6 @@ jobs: --DOCKER_REGISTRIES="${{ secrets.DOCKER_REGISTRIES }}" \ --DOCKER_IMAGES_EXTRA_TAGS="${EARTHLY_DOCKER_IMAGES_EXTRA_TAGS}" - - name: Dispatch successful build event to private repo - uses: cardano-foundation/cf-gha-workflows/./actions/cf-gha-dispatch-event@main - with: - EVENT_TYPE: "${{ github.event_name }}-${{ steps.cf-gha-baseline.outputs.TRIGGERING_REF }}-${{ steps.cf-gha-baseline.outputs.BRANCH_NAME }}" - GITHUB_TOKEN: ${{ secrets.PRIVATE_REPO_PAT }} - GITHUB_REPO: ${{ secrets.PRIVATE_REPO }} - TRIGGERING_EVENT: ${{ github.event_name }} - TRIGGERING_REF: ${{ steps.cf-gha-baseline.outputs.TRIGGERING_REF }} - TRIGGERING_BRANCH: ${{ steps.cf-gha-baseline.outputs.BRANCH_NAME }} - GIT_SHORT_COMMIT: ${{ steps.cf-gha-baseline.outputs.GIT_SHORT_COMMIT }} - ui-summit-2024: runs-on: ${{ fromJson(vars.RUNS_ON) }} env: @@ -88,10 +78,31 @@ jobs: - name: 🌍 earthly (docker build and push) run: | earthly +${{ env.APP_NAME }} \ + --VITE_VERSION=${{ steps.cf-gha-baseline.outputs.GIT_SHORT_COMMIT }} \ --PUSH=${DOCKER_PUSH} \ --DOCKER_REGISTRIES="${{ secrets.DOCKER_REGISTRIES }}" \ --DOCKER_IMAGES_EXTRA_TAGS="${EARTHLY_DOCKER_IMAGES_EXTRA_TAGS}" + dispatch-build-success-event: + runs-on: ${{ fromJson(vars.RUNS_ON) }} + needs: + - ui-summit-2024 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: ⛮ cf-gha-baseline + uses: cardano-foundation/cf-gha-workflows/./actions/cf-gha-baseline@main + id: cf-gha-baseline + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PRIVATE_DOCKER_REGISTRY_URL: ${{ env.PRIVATE_DOCKER_REGISTRY_URL }} + PRIVATE_DOCKER_REGISTRY_USER: ${{ env.PRIVATE_DOCKER_REGISTRY_USER }} + PRIVATE_DOCKER_REGISTRY_PASS: ${{ env.PRIVATE_DOCKER_REGISTRY_PASS }} + HUB_DOCKER_COM_USER: ${{ secrets.HUB_DOCKER_COM_USER }} + HUB_DOCKER_COM_PASS: ${{ secrets.HUB_DOCKER_COM_PASS }} + DOCKER_REGISTRIES: "${{ secrets.DOCKER_REGISTRIES }}" + - name: Dispatch successful build event to private repo uses: cardano-foundation/cf-gha-workflows/./actions/cf-gha-dispatch-event@main with: @@ -101,4 +112,5 @@ jobs: TRIGGERING_EVENT: ${{ github.event_name }} TRIGGERING_REF: ${{ steps.cf-gha-baseline.outputs.TRIGGERING_REF }} TRIGGERING_BRANCH: ${{ steps.cf-gha-baseline.outputs.BRANCH_NAME }} + TRIGGERING_GHRUNID: ${{ github.run_id }} GIT_SHORT_COMMIT: ${{ steps.cf-gha-baseline.outputs.GIT_SHORT_COMMIT }} diff --git a/Earthfile b/Earthfile index 3a28eb5ff..fcb7c2a78 100644 --- a/Earthfile +++ b/Earthfile @@ -115,10 +115,11 @@ user-verification-service: ui-summit-2024: ARG EARTHLY_TARGET_NAME + ARG VITE_VERSION=0.1.0 LET DOCKER_IMAGE_NAME=${DOCKER_IMAGES_PREFIX}-${EARTHLY_TARGET_NAME} WAIT - FROM DOCKERFILE ./ui/summit-2024 + FROM DOCKERFILE --build-arg VITE_VERSION=${VITE_VERSION} ./ui/summit-2024 END WAIT SAVE IMAGE ${DOCKER_IMAGE_NAME} diff --git a/backend-services/keri-ballot-verifier/src/verifier/controllers.py b/backend-services/keri-ballot-verifier/src/verifier/controllers.py index afdb3e7bd..f655901a7 100644 --- a/backend-services/keri-ballot-verifier/src/verifier/controllers.py +++ b/backend-services/keri-ballot-verifier/src/verifier/controllers.py @@ -33,10 +33,12 @@ def __init__(self, hby): def on_get(self, req, resp): # This should be a path param but is causing issues, query will do. oobi = req.params.get('url') + if oobi is None or oobi == "": raise falcon.HTTPBadRequest(description=f"required field url missing from request") result = self.hby.db.roobi.get(keys=(oobi,)) + if result: resp.status = falcon.HTTP_200 resp.text = result.cid diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/client/KeriVerificationClient.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/client/KeriVerificationClient.java index ea221aa1a..2182e5ab3 100644 --- a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/client/KeriVerificationClient.java +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/client/KeriVerificationClient.java @@ -20,6 +20,7 @@ import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; @Component @Slf4j @@ -114,12 +115,14 @@ public Either getOOBI(String oobi, Integer maxAttempts) { while (attempt < attempts) { try { val response = restTemplate.exchange(url, GET, entity, String.class); - if (response.getStatusCode().is2xxSuccessful()) { + log.info("OOBI successfully retrieved after {} attempts", attempt+1); return Either.right(response.getBody()); } } catch (HttpClientErrorException e) { - if (e.getStatusCode() != BAD_REQUEST) { + if (e.getStatusCode() == NOT_FOUND) { + log.info("OOBI not found, continuing attempts... "+attempt); + } else { return Either.left(Problem.builder() .withTitle("OOBI_FETCH_ERROR") .withDetail("Unable to fetch OOBI, reason: " + e.getMessage()) @@ -146,7 +149,91 @@ public Either getOOBI(String oobi, Integer maxAttempts) { return Either.left(Problem.builder() .withTitle("OOBI_NOT_FOUND") .withDetail("The OOBI was not found after " + attempts + " attempts.") - .withStatus(new HttpStatusAdapter(BAD_REQUEST)) + .withStatus(new HttpStatusAdapter(NOT_FOUND)) + .build()); + } + + public Either updateAndVerifyKeyState(String aid, Integer maxAttempts) { + val updateUrl = String.format("%s/keystate", keriVerifierBaseUrl); + val verifyUrl = String.format("%s/keystate/%s", keriVerifierBaseUrl, aid); + + val headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + + val requestBody = new HashMap(); + requestBody.put("pre", aid); + + val entity = new HttpEntity>(requestBody, headers); + + // Attempt to update the key state + try { + val response = restTemplate.exchange(updateUrl, POST, entity, String.class); + if (response.getStatusCode().is2xxSuccessful()) { + log.info("Key state updated successfully for aid: {}", aid); + return verifyKeyState(verifyUrl, maxAttempts); + } else { + return Either.left(Problem.builder() + .withTitle("KEY_STATE_UPDATE_FAILED") + .withDetail("Failed to update key state.") + .withStatus(new HttpStatusAdapter(response.getStatusCode())) + .build()); + } + } catch (HttpClientErrorException e) { + log.error("Unable to update key state, reason: {}", e.getMessage()); + return Either.left(Problem.builder() + .withTitle("KEY_STATE_UPDATE_ERROR") + .withDetail("Unable to update key state, reason: " + e.getMessage()) + .withStatus(new HttpStatusAdapter(e.getStatusCode())) + .build()); + } + } + + private Either verifyKeyState(String url, Integer maxAttempts) { + val headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + val entity = new HttpEntity(headers); + + int attempts = (maxAttempts == null) ? 1 : maxAttempts; + int attempt = 0; + + while (attempt < attempts) { + try { + val response = restTemplate.exchange(url, GET, entity, String.class); + if (response.getStatusCode().is2xxSuccessful()) { + log.info("Key state verified successfully after {} attempts", attempt + 1); + return Either.right(true); + } + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == NOT_FOUND) { + log.info("Key state not found, continuing attempts... " + (attempt + 1)); + } else { + return Either.left(Problem.builder() + .withTitle("KEY_STATE_VERIFICATION_ERROR") + .withDetail("Unable to verify key state, reason: " + e.getMessage()) + .withStatus(new HttpStatusAdapter(e.getStatusCode())) + .build()); + } + } + + attempt++; + if (attempt < attempts) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return Either.left(Problem.builder() + .withTitle("INTERRUPTED_ERROR") + .withDetail("Thread was interrupted while waiting to retry.") + .withStatus(new HttpStatusAdapter(NOT_FOUND)) + .build()); + } + } + } + + return Either.left(Problem.builder() + .withTitle("KEY_STATE_VERIFICATION_FAILED") + .withDetail("The key state verification failed after " + attempts + " attempts.") + .withStatus(new HttpStatusAdapter(NOT_FOUND)) .build()); } diff --git a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/discord/DefaultDiscordUserVerificationService.java b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/discord/DefaultDiscordUserVerificationService.java index d01d6831c..a70ce97f8 100644 --- a/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/discord/DefaultDiscordUserVerificationService.java +++ b/backend-services/user-verification-service/src/main/java/org/cardano/foundation/voting/service/discord/DefaultDiscordUserVerificationService.java @@ -428,9 +428,17 @@ private Either handleKeriVerification(DiscordCheckV // Step 1: Check if OOBI is already registered Either oobiCheckResult = keriVerificationClient.getOOBI(oobi, 1); - if (oobiCheckResult.isRight()) { - log.info("OOBI already registered: {}", oobiCheckResult); + log.info("OOBI already registered: {}", oobiCheckResult.get()); + + // TODO: Review this implementation once the KERI watchers are operational. + // This solution is temporary and might need adjustments to integrate with the new KERI components + // Step 1.1:Update key state + Either keyStateUpdateResult = keriVerificationClient.updateAndVerifyKeyState(walletId, 60); + if (keyStateUpdateResult.isLeft()) { + return Either.left(keyStateUpdateResult.getLeft()); + } + Either verificationResult = keriVerificationClient.verifySignature(walletId, signature, payload); if (verificationResult.isLeft()) { @@ -465,13 +473,18 @@ private Either handleKeriVerification(DiscordCheckV log.info("OOBI registered successfully: {}", oobiM); - // Step 3: Attempt to verify OOBI registration up to 10 times + // Step 3: Attempt to verify OOBI registration up to 60 times val oobiFetchResultE = keriVerificationClient.getOOBI(oobi, 60); if (oobiFetchResultE.isLeft()) { return Either.left(oobiFetchResultE.getLeft()); } - // Step 4: Verify signature after OOBI registration + // Step 4: Update key state + Either keyStateUpdateResult = keriVerificationClient.updateAndVerifyKeyState(walletId, 60); + if (keyStateUpdateResult.isLeft()) { + return Either.left(keyStateUpdateResult.getLeft()); + } + // Step 5: Verify signature after OOBI registration val verificationResultE = keriVerificationClient.verifySignature(walletId, signature, payload); if (verificationResultE.isLeft()) { return Either.left(verificationResultE.getLeft()); diff --git a/backend-services/user-verification-service/src/main/resources/application.properties b/backend-services/user-verification-service/src/main/resources/application.properties index f45f01154..1204577fc 100644 --- a/backend-services/user-verification-service/src/main/resources/application.properties +++ b/backend-services/user-verification-service/src/main/resources/application.properties @@ -67,7 +67,7 @@ max.pending.verification.attempts=${MAX_PENDING_VERIFICATION_ATTEMPTS:5} spring.h2.console.enabled=${H2_CONSOLE_ENABLED:true} phone.number.salt=${SALT:67274569c9671a4ae3f753b9647ca719} -discord.bot.eventId.binding=${DISCORD_BOT_EVENT_ID_BINDING:CF_SUMMIT_2024_10BCC} +discord.bot.eventId.binding=${DISCORD_BOT_EVENT_ID_BINDING:CF_SUMMIT_2024_15BCC} discord.bot.username=${DISCORD_BOT_USERNAME:discord_bot} discord.bot.password=${DISCORD_BOT_PASSWORD:test} diff --git a/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/config/BlockchainDataConfig.java b/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/config/BlockchainDataConfig.java index 9208d633f..e53b741d4 100644 --- a/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/config/BlockchainDataConfig.java +++ b/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/config/BlockchainDataConfig.java @@ -29,8 +29,9 @@ public BlockchainTransactionSubmissionService backendServiceTransactionSubmissio @Bean @Profile( value = { "prod", "dev--preprod" } ) public BlockchainTransactionSubmissionService cardanoSummitTransactionSubmissionService(HttpClient httpClient, - @Value("${cardano.tx.submit.api.url}") String cardanoSubmitApiUrl) { - return new CardanoSubmitApiBlockchainTransactionSubmissionService(cardanoSubmitApiUrl, httpClient); + @Value("${cardano.tx.submit.api.url}") String cardanoSubmitApiUrl, + @Value("${blockfrost.api.key}") String blockfrostApiKey) { + return new CardanoSubmitApiBlockchainTransactionSubmissionService(cardanoSubmitApiUrl, httpClient, blockfrostApiKey); } } diff --git a/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/domain/VoteSerialisations.java b/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/domain/VoteSerialisations.java index 6566671ae..b86dad19a 100644 --- a/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/domain/VoteSerialisations.java +++ b/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/domain/VoteSerialisations.java @@ -4,6 +4,7 @@ import org.cardano.foundation.voting.repository.VoteRepository; import org.cardanofoundation.cip30.CIP30Verifier; +import java.nio.ByteBuffer; import java.util.Optional; import java.util.function.Function; @@ -26,12 +27,13 @@ public static Function createSerialiserFunct case KERI -> { val message = vote.getSignature().getBytes(); val payload = vote.getPayload().map(String::getBytes).orElse(new byte[0]); + val totalLength = message.length + payload.length; - val result = new byte[message.length + payload.length]; + val buffer = ByteBuffer.allocate(totalLength); + buffer.put(message); + buffer.put(payload); - System.arraycopy(message, 0, result, 0, payload.length); - - yield blake2bHash256(result); + yield blake2bHash256(buffer.array()); } }; } diff --git a/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/service/blockchain_state/cardano_submit_api/CardanoSubmitApiBlockchainTransactionSubmissionService.java b/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/service/blockchain_state/cardano_submit_api/CardanoSubmitApiBlockchainTransactionSubmissionService.java index f85d00c76..314d5cd2e 100644 --- a/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/service/blockchain_state/cardano_submit_api/CardanoSubmitApiBlockchainTransactionSubmissionService.java +++ b/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/service/blockchain_state/cardano_submit_api/CardanoSubmitApiBlockchainTransactionSubmissionService.java @@ -19,6 +19,8 @@ public class CardanoSubmitApiBlockchainTransactionSubmissionService implements B private final HttpClient httpClient; + private final String blockfrostApiKey; + @Override @SneakyThrows public String submitTransaction(byte[] txData) { @@ -26,6 +28,7 @@ public String submitTransaction(byte[] txData) { .uri(URI.create(cardanoSubmitApiUrl)) .POST(HttpRequest.BodyPublishers.ofByteArray(txData)) .header("Content-Type", "application/cbor") + .header("project_id", blockfrostApiKey) .build(); var r = httpClient.send(txTransactionSubmitPostRequest, HttpResponse.BodyHandlers.ofString()); diff --git a/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/service/yaci/RollbackHandler.java b/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/service/yaci/RollbackHandler.java index 3f99ace76..0709958f5 100644 --- a/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/service/yaci/RollbackHandler.java +++ b/backend-services/vote-commitment-app/src/main/java/org/cardano/foundation/voting/service/yaci/RollbackHandler.java @@ -1,126 +1,126 @@ -package org.cardano.foundation.voting.service.yaci; - -import com.bloxbean.cardano.yaci.core.model.Block; -import com.bloxbean.cardano.yaci.core.model.Era; -import com.bloxbean.cardano.yaci.core.protocol.chainsync.messages.Point; -import com.bloxbean.cardano.yaci.helper.BlockSync; -import com.bloxbean.cardano.yaci.helper.listener.BlockChainDataListener; -import com.bloxbean.cardano.yaci.helper.model.Transaction; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; -import lombok.extern.slf4j.Slf4j; -import org.cardano.foundation.voting.client.ChainFollowerClient; -import org.cardano.foundation.voting.domain.ChainNetwork; -import org.cardano.foundation.voting.domain.WellKnownPointWithProtocolMagic; -import org.cardano.foundation.voting.service.merkle_tree.VoteCommitmentService; -import org.cardano.foundation.voting.service.merkle_tree.VoteMerkleProofService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Optional; - -@Component -@Slf4j -@ConditionalOnProperty(prefix = "rollback.handling", value = "enabled", havingValue = "true") -public class RollbackHandler { - - @Value("${cardano.node.ip}") - private String cardanoNodeIp; - - @Value("${cardano.node.port}") - private int cardanoNodePort; - - @Autowired - private ChainNetwork chainNetwork; - - @Autowired - private ChainFollowerClient chainFollowerClient; - - @Autowired - private VoteMerkleProofService voteMerkleProofService; - - @Autowired - private VoteCommitmentService voteCommitmentService; - - @Autowired - private WellKnownPointWithProtocolMagic wellKnownPointWithProtocolMagic; - - private Optional blockSync = Optional.empty(); - - @PostConstruct - public void init() { - log.info("Starting cardano block sync on network: {}...", chainNetwork); - - if (wellKnownPointWithProtocolMagic.wellKnownPointForNetwork().isEmpty()) { - log.warn("Well known point is not known. Skipping rollback handler / sync..."); - return; - } - - var wellKnownPoint = wellKnownPointWithProtocolMagic.wellKnownPointForNetwork().orElseThrow(); - - var protocolMagic = wellKnownPointWithProtocolMagic.protocolMagic(); - var blockSync = startBlockSync(protocolMagic, wellKnownPoint); - - this.blockSync = Optional.of(blockSync); - } - - private BlockSync startBlockSync(long protocolMagic, Point wellKnownPoint) { - var blockSync = new BlockSync(cardanoNodeIp, cardanoNodePort, protocolMagic, wellKnownPoint); - blockSync.startSyncFromTip(new BlockChainDataListener() { - - @Override - public void onBlock(Era era, Block block, List transactions) { - var headerBody = block.getHeader().getHeaderBody(); - - log.info("Block's slot:{}, hash:{}, blockNo:{}", headerBody.getSlot(), headerBody.getBlockHash(), headerBody.getBlockNumber()); - } - - @Override - public void onRollback(Point point) { - var allCommitmentWindowOpenEventsE = chainFollowerClient.findAllCommitmentWindowOpenEvents(); - - if (allCommitmentWindowOpenEventsE.isEmpty()) { - var issue = allCommitmentWindowOpenEventsE.swap().get(); - log.warn("Failed to get eventSummaries issue: {}, will try again in some time (on next rollback)...", issue.toString()); - - return; - } - - var allCommitmentWindowOpenEvents = allCommitmentWindowOpenEventsE.get(); - - if (allCommitmentWindowOpenEvents.isEmpty()) { - log.info("No commitment window open events found. Skipping rollback handler..."); - - return; - } - - var absoluteSlot = point.getSlot(); - - for (var eventSummary : allCommitmentWindowOpenEvents) { - String eventId = eventSummary.id(); - - log.info("Processing rollback for eventId: {}, absoluteSlot: {}", eventId, absoluteSlot); - - int updatedVoteProofs = voteMerkleProofService.softDeleteAllProofsAfterSlot(eventId, absoluteSlot); - - log.info("Soft deleted {} vote proofs after slot: {} for eventId: {}", updatedVoteProofs, absoluteSlot, eventId); - } - - } - - }); - - return blockSync; - } - - @PreDestroy - public void destroy() { - log.info("Stopping block sync..."); - - blockSync.ifPresent(BlockSync::stop); - } - -} +//package org.cardano.foundation.voting.service.yaci; +// +//import com.bloxbean.cardano.yaci.core.model.Block; +//import com.bloxbean.cardano.yaci.core.model.Era; +//import com.bloxbean.cardano.yaci.core.protocol.chainsync.messages.Point; +//import com.bloxbean.cardano.yaci.helper.BlockSync; +//import com.bloxbean.cardano.yaci.helper.listener.BlockChainDataListener; +//import com.bloxbean.cardano.yaci.helper.model.Transaction; +//import jakarta.annotation.PostConstruct; +//import jakarta.annotation.PreDestroy; +//import lombok.extern.slf4j.Slf4j; +//import org.cardano.foundation.voting.client.ChainFollowerClient; +//import org.cardano.foundation.voting.domain.ChainNetwork; +//import org.cardano.foundation.voting.domain.WellKnownPointWithProtocolMagic; +//import org.cardano.foundation.voting.service.merkle_tree.VoteCommitmentService; +//import org.cardano.foundation.voting.service.merkle_tree.VoteMerkleProofService; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +//import org.springframework.stereotype.Component; +// +//import java.util.List; +//import java.util.Optional; +// +//@Component +//@Slf4j +//@ConditionalOnProperty(prefix = "rollback.handling", value = "enabled", havingValue = "true") +//public class RollbackHandler { +// +// @Value("${cardano.node.ip}") +// private String cardanoNodeIp; +// +// @Value("${cardano.node.port}") +// private int cardanoNodePort; +// +// @Autowired +// private ChainNetwork chainNetwork; +// +// @Autowired +// private ChainFollowerClient chainFollowerClient; +// +// @Autowired +// private VoteMerkleProofService voteMerkleProofService; +// +// @Autowired +// private VoteCommitmentService voteCommitmentService; +// +// @Autowired +// private WellKnownPointWithProtocolMagic wellKnownPointWithProtocolMagic; +// +// private Optional blockSync = Optional.empty(); +// +// @PostConstruct +// public void init() { +// log.info("Starting cardano block sync on network: {}...", chainNetwork); +// +// if (wellKnownPointWithProtocolMagic.wellKnownPointForNetwork().isEmpty()) { +// log.warn("Well known point is not known. Skipping rollback handler / sync..."); +// return; +// } +// +// var wellKnownPoint = wellKnownPointWithProtocolMagic.wellKnownPointForNetwork().orElseThrow(); +// +// var protocolMagic = wellKnownPointWithProtocolMagic.protocolMagic(); +// var blockSync = startBlockSync(protocolMagic, wellKnownPoint); +// +// this.blockSync = Optional.of(blockSync); +// } +// +// private BlockSync startBlockSync(long protocolMagic, Point wellKnownPoint) { +// var blockSync = new BlockSync(cardanoNodeIp, cardanoNodePort, protocolMagic, wellKnownPoint); +// blockSync.startSyncFromTip(new BlockChainDataListener() { +// +// @Override +// public void onBlock(Era era, Block block, List transactions) { +// var headerBody = block.getHeader().getHeaderBody(); +// +// log.info("Block's slot:{}, hash:{}, blockNo:{}", headerBody.getSlot(), headerBody.getBlockHash(), headerBody.getBlockNumber()); +// } +// +// @Override +// public void onRollback(Point point) { +// var allCommitmentWindowOpenEventsE = chainFollowerClient.findAllCommitmentWindowOpenEvents(); +// +// if (allCommitmentWindowOpenEventsE.isEmpty()) { +// var issue = allCommitmentWindowOpenEventsE.swap().get(); +// log.warn("Failed to get eventSummaries issue: {}, will try again in some time (on next rollback)...", issue.toString()); +// +// return; +// } +// +// var allCommitmentWindowOpenEvents = allCommitmentWindowOpenEventsE.get(); +// +// if (allCommitmentWindowOpenEvents.isEmpty()) { +// log.info("No commitment window open events found. Skipping rollback handler..."); +// +// return; +// } +// +// var absoluteSlot = point.getSlot(); +// +// for (var eventSummary : allCommitmentWindowOpenEvents) { +// String eventId = eventSummary.id(); +// +// log.info("Processing rollback for eventId: {}, absoluteSlot: {}", eventId, absoluteSlot); +// +// int updatedVoteProofs = voteMerkleProofService.softDeleteAllProofsAfterSlot(eventId, absoluteSlot); +// +// log.info("Soft deleted {} vote proofs after slot: {} for eventId: {}", updatedVoteProofs, absoluteSlot, eventId); +// } +// +// } +// +// }); +// +// return blockSync; +// } +// +// @PreDestroy +// public void destroy() { +// log.info("Stopping block sync..."); +// +// blockSync.ifPresent(BlockSync::stop); +// } +// +//} diff --git a/backend-services/vote-commitment-app/src/test/java/org/cardano/foundation/voting/domain/VoteSerialisationsTest.java b/backend-services/vote-commitment-app/src/test/java/org/cardano/foundation/voting/domain/VoteSerialisationsTest.java new file mode 100644 index 000000000..f43b4ecb3 --- /dev/null +++ b/backend-services/vote-commitment-app/src/test/java/org/cardano/foundation/voting/domain/VoteSerialisationsTest.java @@ -0,0 +1,152 @@ +package org.cardano.foundation.voting.domain; + +import org.cardano.foundation.voting.repository.VoteRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class VoteSerialisationsTest { + + @Test + @DisplayName("Test CARDANO wallet type with valid verification result") + public void testCardanoWalletTypeWithValidVerificationResult() { + // Arrange + VoteRepository.CompactVote vote = mock(VoteRepository.CompactVote.class); + Mockito.when(vote.getWalletType()).thenReturn(WalletType.CARDANO); + Mockito.when(vote.getSignature()).thenReturn("cafebabe"); + Mockito.when(vote.getPublicKey()).thenReturn(Optional.of("deadbeef")); + + // Act + byte[] result = VoteSerialisations.VOTE_SERIALISER.apply(vote); + + // Assert + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("Test CARDANO wallet type with null verification message") + public void testCardanoWalletTypeWithNullVerificationMessage() { + // Arrange + VoteRepository.CompactVote vote = mock(VoteRepository.CompactVote.class); + Mockito.when(vote.getWalletType()).thenReturn(WalletType.CARDANO); + Mockito.when(vote.getSignature()).thenReturn("cafebabe"); + Mockito.when(vote.getPublicKey()).thenReturn(Optional.of("cafebabecafebabecafebabecafebabe")); + + // Act + byte[] result = VoteSerialisations.VOTE_SERIALISER.apply(vote); + + // Assert + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("Test KERI wallet type with non-empty payload") + public void testKeriWalletTypeWithPayload() { + // Arrange + String signature = "signature"; + String payload = "payload"; + VoteRepository.CompactVote vote = mock(VoteRepository.CompactVote.class); + Mockito.when(vote.getWalletType()).thenReturn(WalletType.KERI); + Mockito.when(vote.getSignature()).thenReturn(signature); + Mockito.when(vote.getPayload()).thenReturn(Optional.of(payload)); + + // Act + byte[] result = VoteSerialisations.VOTE_SERIALISER.apply(vote); + + // Assert + assertThat(result).isNotNull(); + // Optionally, verify the content of the result + } + + @Test + @DisplayName("Test KERI wallet type with empty payload") + public void testKeriWalletTypeWithEmptyPayload() { + // Arrange + String signature = "signature"; + VoteRepository.CompactVote vote = mock(VoteRepository.CompactVote.class); + Mockito.when(vote.getWalletType()).thenReturn(WalletType.KERI); + Mockito.when(vote.getSignature()).thenReturn(signature); + Mockito.when(vote.getPayload()).thenReturn(Optional.empty()); + + // Act + byte[] result = VoteSerialisations.VOTE_SERIALISER.apply(vote); + + // Assert + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("Test KERI wallet type with large payload") + public void testKeriWalletTypeWithLargePayload() { + // Arrange + String signature = "signature"; + StringBuilder largePayloadBuilder = new StringBuilder(); + for (int i = 0; i < 10000; i++) { + largePayloadBuilder.append("a"); + } + String largePayload = largePayloadBuilder.toString(); + VoteRepository.CompactVote vote = mock(VoteRepository.CompactVote.class); + Mockito.when(vote.getWalletType()).thenReturn(WalletType.KERI); + Mockito.when(vote.getSignature()).thenReturn(signature); + Mockito.when(vote.getPayload()).thenReturn(Optional.of(largePayload)); + + // Act + byte[] result = VoteSerialisations.VOTE_SERIALISER.apply(vote); + + // Assert + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("Test KERI wallet type with null signature") + public void testKeriWalletTypeWithNullSignature() { + // Arrange + VoteRepository.CompactVote vote = mock(VoteRepository.CompactVote.class); + Mockito.when(vote.getWalletType()).thenReturn(WalletType.KERI); + Mockito.when(vote.getSignature()).thenReturn(null); + Mockito.when(vote.getPayload()).thenReturn(Optional.of("payload")); + + // Act & Assert + assertThrows(NullPointerException.class, () -> { + VoteSerialisations.VOTE_SERIALISER.apply(vote); + }, "Applying the serialiser with null signature should throw NullPointerException"); + } + + @Test + @DisplayName("Test KERI wallet type with null payload") + public void testKeriWalletTypeWithNullPayload() { + // Arrange + String signature = "signature"; + VoteRepository.CompactVote vote = mock(VoteRepository.CompactVote.class); + when(vote.getWalletType()).thenReturn(WalletType.KERI); + when(vote.getSignature()).thenReturn(signature); + when(vote.getPayload()).thenReturn(Optional.empty()); + + // Act + byte[] result = VoteSerialisations.VOTE_SERIALISER.apply(vote); + + // Assert + assertThat(result).isNotNull(); + } + + @Test + @DisplayName("Test unknown wallet type") + public void testUnknownWalletType() { + // Arrange + VoteRepository.CompactVote vote = mock(VoteRepository.CompactVote.class); + when(vote.getWalletType()).thenReturn(null); // Unknown or unsupported wallet type + + // Act & Assert + assertThrows(NullPointerException.class, () -> { + VoteSerialisations.VOTE_SERIALISER.apply(vote); + }, "Applying the serialiser with unknown wallet type should throw NullPointerException"); + } + +} \ No newline at end of file diff --git a/backend-services/voting-admin-app/src/main/java/org/cardano/foundation/voting/service/blockchain_state/CardanoSubmitApiBlockchainTransactionSubmissionService.java b/backend-services/voting-admin-app/src/main/java/org/cardano/foundation/voting/service/blockchain_state/CardanoSubmitApiBlockchainTransactionSubmissionService.java index 2b4182453..6ba3dd95f 100644 --- a/backend-services/voting-admin-app/src/main/java/org/cardano/foundation/voting/service/blockchain_state/CardanoSubmitApiBlockchainTransactionSubmissionService.java +++ b/backend-services/voting-admin-app/src/main/java/org/cardano/foundation/voting/service/blockchain_state/CardanoSubmitApiBlockchainTransactionSubmissionService.java @@ -19,6 +19,9 @@ public class CardanoSubmitApiBlockchainTransactionSubmissionService implements B @Value("${cardano.tx.submit.api.url}") private String cardanoSubmitApiUrl; + @Value("${blockfrost.api.key}") + private String blockfrostApiKey; + @Override @SneakyThrows public String submitTransaction(byte[] txData) { @@ -26,6 +29,7 @@ public String submitTransaction(byte[] txData) { .uri(URI.create(cardanoSubmitApiUrl)) .POST(HttpRequest.BodyPublishers.ofByteArray(txData)) .header("Content-Type", "application/cbor") + .header("project_id", blockfrostApiKey) .build(); var r = httpClient.send(txTransactionSubmitPostRequest, HttpResponse.BodyHandlers.ofString()); diff --git a/backend-services/voting-admin-app/src/main/java/org/cardano/foundation/voting/shell/CardanoSummit2024ProdCommands.java b/backend-services/voting-admin-app/src/main/java/org/cardano/foundation/voting/shell/CardanoSummit2024ProdCommands.java new file mode 100644 index 000000000..3e8b2453d --- /dev/null +++ b/backend-services/voting-admin-app/src/main/java/org/cardano/foundation/voting/shell/CardanoSummit2024ProdCommands.java @@ -0,0 +1,675 @@ +package org.cardano.foundation.voting.shell; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.cardano.foundation.voting.domain.CardanoNetwork; +import org.cardano.foundation.voting.domain.CreateCategoryCommand; +import org.cardano.foundation.voting.domain.CreateEventCommand; +import org.cardano.foundation.voting.domain.Proposal; +import org.cardano.foundation.voting.service.transaction_submit.L1SubmissionService; +import org.springframework.core.annotation.Order; +import org.springframework.shell.standard.ShellComponent; +import org.springframework.shell.standard.ShellMethod; +import org.springframework.shell.standard.ShellOption; + +import java.util.HashSet; +import java.util.List; +import java.util.Optional; + +import static org.cardano.foundation.voting.domain.CardanoNetwork.MAIN; +import static org.cardano.foundation.voting.domain.SchemaVersion.V11; +import static org.cardano.foundation.voting.domain.VotingEventType.USER_BASED; + +@ShellComponent +@Slf4j +@RequiredArgsConstructor +public class CardanoSummit2024ProdCommands { + + private final static String EVENT_NAME = "CARDANO_SUMMIT_AWARDS_2024"; + + private final L1SubmissionService l1SubmissionService; + + private final CardanoNetwork network; + + @ShellMethod(key = "01_create-cf-summit04-event-prod", value = "Create a CF-Summit 2024 voting event on a PROD network.") + @Order(1) + public String createCFSummit2024Event() { + if (network != MAIN) { + return "This command can only be run on MAIN network!"; + } + + log.info("Creating CF-Summit 2024 on a MAIN network..."); + + long startSlot = 135591309; // 24/09/2024 06:00:00 UTC + long endSlot = 136858508; // 08/10/2024 21:59:59 UTC + long proposalsRevealSlot = 138233708; // 24/10/2024 19:59:59 UTC + + var createEventCommand = CreateEventCommand.builder() + .id(EVENT_NAME) + .startSlot(Optional.of(startSlot)) + .endSlot(Optional.of(endSlot)) + .votingPowerAsset(Optional.empty()) + .organisers("Cardano Foundation") + .votingEventType(USER_BASED) + .schemaVersion(V11) + .allowVoteChanging(false) + .highLevelEventResultsWhileVoting(true) + .highLevelCategoryResultsWhileVoting(true) + .categoryResultsWhileVoting(false) + .proposalsRevealSlot(Optional.of(proposalsRevealSlot)) + .build(); + + l1SubmissionService.submitEvent(createEventCommand); + + return "Created CF-Summit 2024 event: " + createEventCommand; + } + + @ShellMethod(key = "02_create-ambassador-category-prod", value = "Create a CF-Summit 2024 Ambassador category on a PROD network.") + @Order(2) + public String createAmbassadorCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 Ambassador category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("E4EB1F0C-427A-4B94-B965-C6F46CA09845") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("7D2B42A7-B28B-463D-A673-C992CC462413") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("A50DF74D-C89D-46A1-B90F-DA073CCD5CA3") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("538FBA40-E852-417A-9716-7D794D4BE8DB") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("21910627-D2E5-4429-89EA-102484C1925D") + .name("Option 5") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5); + + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("AMBASSADOR") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "03_create-blockchain-for-good-category-prod", value = "Create a CF-Summit 2024 Best Blockchain for Good category on a PROD network.") + @Order(3) + public String createBlockchainForGoodCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 Blockchain For Good category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("72F103B0-5DBB-44F9-BC56-AB991B784FCC") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("2976E410-916C-4564-A158-78E04EE03B4C") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("092C7ED2-40EC-4544-AFDB-BCF2A0B9DE64") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("C95EF71B-CA9F-40A5-BF9D-789C78EB8D57") + .name("Option 4") + .build(); + + List allProposals = List.of(n1, n2, n3, n4); + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("BLOCKCHAIN_FOR_GOOD") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "04_create-innovation-standards-category-prod", value = "Create a CF-Summit 2024 Innovation & Standards category on a PROD network.") + @Order(4) + public String createInnovationStandarsCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 Innovation & Standards category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("2E2676E5-5D72-400C-AF66-ED53A957A86E") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("923437B0-75BA-4AF0-9EB8-43AABBF696C5") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("00DAE5F3-ABCF-41A3-9E5B-70B41A2958BC") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("0E724D70-BBBD-4310-B6E5-EF2D7F5BF154") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("B1727049-7F0F-421C-B1CE-A0CEB29AD189") + .name("Option 5") + .build(); + + Proposal n6 = Proposal.builder() + .id("5332AD46-5317-4E02-9069-4908F56881E4") + .name("Option 6") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5, n6); + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("INNOVATION_STANDARDS") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "05_create-dex-category-prod", value = "Create a CF-Summit 2024 DEX category on a PROD network.") + @Order(5) + public String createDEXCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 Best DeFi / DEX category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("882F424F-D628-4C27-B072-076A615C8CB7") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("EB3756EE-4DE6-499F-AD97-24AD799A181A") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("7225D86F-0AE2-4AEF-8678-19F658794D0A") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("BC20C916-BFAA-4347-8EC6-2B6F1E699EB0") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("AE33E741-1E1A-4681-9B11-0949D511F502") + .name("Option 5") + .build(); + + Proposal n6 = Proposal.builder() + .id("7AEC510B-FA86-472E-8E92-66C8E71F1BED") + .name("Option 6") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5, n6); + + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("DEX") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "06_create-developer-tooling-category-prod", value = "Create a CF-Summit 2024 Developer & Tooling category on a PROD network.") + @Order(6) + public String createDeveloperToolingCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 Developer & Tooling category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("FB8501FE-0D1C-4D9D-8219-62881819C277") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("040A201A-0E1D-4607-921A-E7FC154DEE0D") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("8B260B9A-5928-487B-91B6-21EBE484A956") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("6A0AEC7B-85AD-45B2-98B8-2E436A718F45") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("6F4D3D8B-531A-4FB3-AC41-CB0937AFE059") + .name("Option 5") + .build(); + + Proposal n6 = Proposal.builder() + .id("3F10573E-6564-48BB-851F-A40C9BC6E248") + .name("Option 6") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5, n6); + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("DEVELOPER_TOOLING") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "07_create-educational-influencer-category-prod", value = "Create a CF-Summit 2024 Educational Influencer category on a PROD network.") + @Order(7) + public String createEducationalInfluencerCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 Educational Influence category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("968AEB62-DADD-4D79-B6D6-B2A013982874") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("E3531D81-1F35-42D7-A175-EBC353E0E365") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("C16D8E47-F220-4B51-8024-EA184D867898") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("8ED763AF-39BA-4D88-B933-1E3320DB179A") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("2CE60EE3-BF09-45FC-9DE1-970785C14B5F") + .name("Option 5") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5); + + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("EDUCATIONAL_INFLUENCER") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "08_create-nft-digital-collectibles-category-prod", value = "Create a CF-Summit 2024 NFT & Digital Collectibles category on a PROD network.") + @Order(8) + public String createNFTDigitalCollectiblesCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 NFT & Digital Collectibles category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("EDCB0046-E576-4CD4-965C-D442086B2EC5") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("2967D6F9-AE00-4B2D-94DA-82CBDE18FC88") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("68E4D3AB-FA69-4DA5-850F-290A8DC21C89") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("0E39800C-C9AB-4543-B9F8-12BD8ED3808F") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("5CEC4D99-886C-4BE7-B6C8-BEED71682F75") + .name("Option 5") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5); + + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("NFT_DIGITAL_COLLECTIBLES") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "09_create-infrastructure-platform-category-prod", value = "Create a CF-Summit 2024 Infrastructure Platform category on a PROD network.") + @Order(9) + public String createInfrastructurePlatformCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 Infrastructure Platform category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("86A61885-556E-4320-AD16-79D87793544A") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("6C6092EB-D04D-411F-BDDE-87DDF71C375A") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("49C9BFC5-70F1-43AB-AC7F-15265F6A20F5") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("CFBDA700-ABB9-420C-9828-798A9BC7B6A9") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("85D2E32F-BD87-4A8A-9736-46E9A64EEC8A") + .name("Option 5") + .build(); + + Proposal n6 = Proposal.builder() + .id("C975CC1D-28A3-47F1-8FFE-9E0C897079DA") + .name("Option 6") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5, n6); + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("INFRASTRUCTURE_PLATFORM") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "10_create-dao-tooling-governance-category-prod", value = "Create a CF-Summit 2024 DAO Tooling & Governance category on a PROD network.") + @Order(10) + public String createDAOToolingGovernanceCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 DAO Tooling & Governance category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("64C38356-E099-40CF-9F5D-30EED973B89A") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("55C832E9-C3EE-4029-8DC2-F73C29379316") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("B18E350C-D561-41D9-9C72-C42C7BE6DB8D") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("D791ECA4-19C2-45B9-9A3F-DEE509D5ADA0") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("36384CB0-3517-483D-8AA5-AA8F9E51159B") + .name("Option 5") + .build(); + + Proposal n6 = Proposal.builder() + .id("ECFA5481-3B31-4DAE-B3B7-145ED3525FEE") + .name("Option 6") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5, n6); + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("DAO_TOOLING_GOVERNANCE") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "11_create-defi-platform-category-prod", value = "Create a CF-Summit 2024 DeFi Platform category on a PROD network.") + @Order(11) + public String createDEFICategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 DeFi Platform category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("A37B8E3B-99ED-4596-9349-E31015DCFA43") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("FDB103DC-EA4F-48BD-85EF-877F21C3FEC8") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("9E1B84B1-1F5F-4CD9-92A5-264E30C69BA9") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("5DF10439-EDBE-4AD3-A033-833906220AD8") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("26364D11-DD33-42FA-8AA9-48DB223B660E") + .name("Option 5") + .build(); + + Proposal n6 = Proposal.builder() + .id("B1F3D716-D10F-43F2-9462-D5D1445324E6") + .name("Option 6") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5, n6); + + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("DEFI") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + + @ShellMethod(key = "12_create-sspo-category-prod", value = "Create a CF-Summit 2024 SSPO category on a PROD network.") + @Order(12) + public String createSSPOCategory(@ShellOption String event) { + if (network != MAIN) { + return "This command can only be run on a PROD network!"; + } + + log.info("Creating CF-Summit 2024 SSPO category on a PROD network..."); + + Proposal n1 = Proposal.builder() + .id("10C26123-3E3F-4503-861B-3EE0E3ADA21A") + .name("Option 1") + .build(); + + Proposal n2 = Proposal.builder() + .id("5C1F4FE1-8126-40BB-9C65-CBB9286CC1F0") + .name("Option 2") + .build(); + + Proposal n3 = Proposal.builder() + .id("42BBA522-2222-4671-930A-59FC5F1940CB") + .name("Option 3") + .build(); + + Proposal n4 = Proposal.builder() + .id("F34DAB95-603B-4F92-A70C-48016C13D28F") + .name("Option 4") + .build(); + + Proposal n5 = Proposal.builder() + .id("A3CA5296-A6B3-45BF-B047-11D685B1DDEF") + .name("Option 5") + .build(); + + Proposal n6 = Proposal.builder() + .id("D7C37AFD-13A7-4AE3-8312-3888F9A3DB77") + .name("Option 6") + .build(); + + List allProposals = List.of(n1, n2, n3, n4, n5, n6); + + CreateCategoryCommand createCategoryCommand = CreateCategoryCommand.builder() + .id("SSPO") + .event(event) + .gdprProtection(true) + .schemaVersion(V11) + .proposals(allProposals) + .build(); + + if (allProposals.size() != new HashSet<>(allProposals).size()) { + throw new RuntimeException("Duplicate proposals detected!"); + } + + l1SubmissionService.submitCategory(createCategoryCommand); + + return "Created CF-Summit 2024 category: " + createCategoryCommand; + } + +} diff --git a/backend-services/voting-app/build.gradle.kts b/backend-services/voting-app/build.gradle.kts index 1843b3dc6..d30b1f97e 100644 --- a/backend-services/voting-app/build.gradle.kts +++ b/backend-services/voting-app/build.gradle.kts @@ -78,7 +78,7 @@ dependencies { runtimeOnly("org.postgresql:postgresql") implementation("org.cardanofoundation:merkle-tree-java:0.0.7") - implementation("org.cardanofoundation:cip30-data-signature-parser:0.0.11") + implementation("org.cardanofoundation:cip30-data-signature-parser:0.0.12") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0") diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/client/KeriVerificationClient.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/client/KeriVerificationClient.java index 463f91c9d..dd13efa1b 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/client/KeriVerificationClient.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/client/KeriVerificationClient.java @@ -19,6 +19,7 @@ import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; @RequiredArgsConstructor @Component @@ -47,7 +48,6 @@ public Either verifySignature(String aid, try { val response = restTemplate.exchange(url, POST, entity, String.class); - if (response.getStatusCode().is2xxSuccessful()) { return Either.right(true); } @@ -78,6 +78,7 @@ public Either registerOOBI(String oobi) { requestBody.put("oobi", oobi); val entity = new HttpEntity>(requestBody, headers); + try { val response = restTemplate.exchange(url, POST, entity, String.class); @@ -113,12 +114,14 @@ public Either getOOBI(String oobi, Integer maxAttempts) { while (attempt < attempts) { try { val response = restTemplate.exchange(url, GET, entity, String.class); - if (response.getStatusCode().is2xxSuccessful()) { + log.info("OOBI successfully retrieved after {} attempts", attempt+1); return Either.right(response.getBody()); } } catch (HttpClientErrorException e) { - if (e.getStatusCode() != BAD_REQUEST) { + if (e.getStatusCode() == NOT_FOUND) { + log.info("OOBI not found, continuing attempts..."); + } else { return Either.left(Problem.builder() .withTitle("OOBI_FETCH_ERROR") .withDetail("Unable to fetch OOBI, reason: " + e.getMessage()) @@ -145,8 +148,91 @@ public Either getOOBI(String oobi, Integer maxAttempts) { return Either.left(Problem.builder() .withTitle("OOBI_NOT_FOUND") .withDetail("The OOBI was not found after " + attempts + " attempts.") - .withStatus(new HttpStatusAdapter(BAD_REQUEST)) + .withStatus(new HttpStatusAdapter(NOT_FOUND)) .build()); } + public Either updateAndVerifyKeyState(String aid, Integer maxAttempts) { + val updateUrl = String.format("%s/keystate", keriVerifierBaseUrl); + val verifyUrl = String.format("%s/keystate/%s", keriVerifierBaseUrl, aid); + + val headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + + val requestBody = new HashMap(); + requestBody.put("pre", aid); + + val entity = new HttpEntity>(requestBody, headers); + + // Attempt to update the key state + try { + val response = restTemplate.exchange(updateUrl, POST, entity, String.class); + if (response.getStatusCode().is2xxSuccessful()) { + log.info("Key state updated successfully for aid: {}", aid); + return verifyKeyState(verifyUrl, maxAttempts); + } else { + return Either.left(Problem.builder() + .withTitle("KEY_STATE_UPDATE_FAILED") + .withDetail("Failed to update key state.") + .withStatus(new HttpStatusAdapter(response.getStatusCode())) + .build()); + } + } catch (HttpClientErrorException e) { + log.error("Unable to update key state, reason: {}", e.getMessage()); + return Either.left(Problem.builder() + .withTitle("KEY_STATE_UPDATE_ERROR") + .withDetail("Unable to update key state, reason: " + e.getMessage()) + .withStatus(new HttpStatusAdapter(e.getStatusCode())) + .build()); + } + } + + private Either verifyKeyState(String url, Integer maxAttempts) { + val headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + val entity = new HttpEntity(headers); + + int attempts = (maxAttempts == null) ? 1 : maxAttempts; + int attempt = 0; + + while (attempt < attempts) { + try { + val response = restTemplate.exchange(url, GET, entity, String.class); + if (response.getStatusCode().is2xxSuccessful()) { + log.info("Key state verified successfully after {} attempts", attempt + 1); + return Either.right(true); + } + } catch (HttpClientErrorException e) { + if (e.getStatusCode() == NOT_FOUND) { + log.info("Key state not found, continuing attempts... " + (attempt + 1)); + } else { + return Either.left(Problem.builder() + .withTitle("KEY_STATE_VERIFICATION_ERROR") + .withDetail("Unable to verify key state, reason: " + e.getMessage()) + .withStatus(new HttpStatusAdapter(e.getStatusCode())) + .build()); + } + } + + attempt++; + if (attempt < attempts) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return Either.left(Problem.builder() + .withTitle("INTERRUPTED_ERROR") + .withDetail("Thread was interrupted while waiting to retry.") + .withStatus(new HttpStatusAdapter(NOT_FOUND)) + .build()); + } + } + } + + return Either.left(Problem.builder() + .withTitle("KEY_STATE_VERIFICATION_FAILED") + .withDetail("The key state verification failed after " + attempts + " attempts.") + .withStatus(new HttpStatusAdapter(NOT_FOUND)) + .build()); + } } diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/domain/entity/Vote.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/domain/entity/Vote.java index 8e9d6938b..aaee8f24c 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/domain/entity/Vote.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/domain/entity/Vote.java @@ -60,7 +60,7 @@ public class Vote extends AbstractTimestampEntity { private String signature; @Column(name = "payload", nullable = false, columnDefinition = "text", length = 2048) - @Nullable + @Nullable // TODO remove nullable since payload is now always required private String payload; @Column(name = "public_key") diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/DefaultLoginService.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/DefaultLoginService.java index 830a9a264..e8ce3711f 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/DefaultLoginService.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/DefaultLoginService.java @@ -81,7 +81,7 @@ public Either login(Web3AuthenticationToken web3Authentica } private Either unwrapLoginVoteEnvelope(Web3ConcreteDetails concreteDetails) { - val jsonBody = concreteDetails.getSignedJson(); + val jsonBody = concreteDetails.getPayload(); val jsonPayloadE = jsonService.decodeCIP93LoginEnvelope(jsonBody); if (jsonPayloadE.isLeft()) { diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3Details.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3Details.java index 46f37eb0a..dd3913f51 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3Details.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3Details.java @@ -21,6 +21,7 @@ public class CardanoWeb3Details implements Web3ConcreteDetails { private Cip30VerificationResult cip30VerificationResult; private CIP93Envelope> envelope; private SignedCIP30 signedCIP30; + private String payload; public String getUri() { return envelope.getUri(); @@ -40,8 +41,12 @@ public String getSignature() { return signedCIP30.getSignature(); } - public Optional getPayload() { - return Optional.empty(); + public String getPayload() { + if (cip30VerificationResult.isHashed()) { + return payload; + } + + return cip30VerificationResult.getMessage(MessageFormat.TEXT); } public Optional getPublicKey() { diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3Filter.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3Filter.java index 0cddb4908..94dc5fb58 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3Filter.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3Filter.java @@ -30,10 +30,12 @@ import java.util.List; import java.util.Optional; +import static com.bloxbean.cardano.client.crypto.Blake2bUtil.blake2bHash224; +import static com.bloxbean.cardano.client.util.HexUtil.decodeHexString; +import static com.bloxbean.cardano.client.util.HexUtil.encodeHexString; import static org.cardano.foundation.voting.domain.Role.VOTER; import static org.cardano.foundation.voting.domain.web3.WalletType.CARDANO; -import static org.cardano.foundation.voting.resource.Headers.X_Ballot_PublicKey; -import static org.cardano.foundation.voting.resource.Headers.X_Ballot_Signature; +import static org.cardano.foundation.voting.resource.Headers.*; import static org.cardano.foundation.voting.service.auth.LoginSystem.CARDANO_CIP93; import static org.cardano.foundation.voting.service.auth.web3.MoreFilters.sendBackProblem; import static org.cardano.foundation.voting.utils.MoreNumber.isNumeric; @@ -77,6 +79,7 @@ protected void doFilterInternal(HttpServletRequest req, val signatureM = Optional.ofNullable(req.getHeader(X_Ballot_Signature)); val publicKey = req.getHeader(X_Ballot_PublicKey); + val payloadM = Optional.ofNullable(req.getHeader(X_Ballot_Payload)); if (signatureM.isEmpty()) { val problem = Problem.builder() @@ -122,7 +125,37 @@ protected void doFilterInternal(HttpServletRequest req, val walletId = maybeAddress.orElseThrow(); - val cipBody = cipVerificationResult.getMessage(MessageFormat.TEXT); + var cipBody = cipVerificationResult.getMessage(MessageFormat.TEXT); + if (cipVerificationResult.isHashed() && payloadM.isEmpty()) { + val problem = Problem.builder() + .withTitle("HASHED_CONTENT_NO_PAYLOAD") + .withDetail("Payload was not sent along with the request and CIP-30 signature contains is hashed!") + .withStatus(BAD_REQUEST) + .build(); + + sendBackProblem(objectMapper, res, problem); + return; + } + + if (cipVerificationResult.isHashed()) { + val cipBodyHash = cipVerificationResult.getMessage(MessageFormat.HEX); + val payload = payloadM.orElseThrow(); + + val payloadHash = encodeHexString(blake2bHash224(decodeHexString(payload))); + + if (!cipBodyHash.equals(payloadHash)) { + val problem = Problem.builder() + .withTitle("CIP_30_HASH_MISMATCH") + .withDetail("Signed hash does not match our precalculated hash!") + .withStatus(BAD_REQUEST) + .build(); + + sendBackProblem(objectMapper, res, problem); + return; + } + + cipBody = new String(decodeHexString(payload)); // flip cipBody to be payload for further processing + } val cip93EnvelopeE = jsonService.decodeGenericCIP93(cipBody); if (cip93EnvelopeE.isEmpty()) { @@ -318,6 +351,7 @@ protected void doFilterInternal(HttpServletRequest req, .web3CommonDetails(commonWeb3Details) .envelope(genericEnvelope) .signedCIP30(signedWeb3Request) + .payload(cipBody) .cip30VerificationResult(cipVerificationResult) .build(); diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3Details.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3Details.java index 02168f0e1..0cec0c966 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3Details.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3Details.java @@ -40,8 +40,8 @@ public String getSignature() { } @Override - public Optional getPayload() { - return Optional.of(signedKERI.getPayload()); + public String getPayload() { + return signedKERI.getPayload(); } @Override diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3Filter.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3Filter.java index 168c0bb1d..dfe426b8e 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3Filter.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3Filter.java @@ -59,6 +59,7 @@ public class KeriWeb3Filter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws ServletException, IOException { + val logonSystemM = loginSystemDetector.detect(req); if (logonSystemM.isEmpty()) { chain.doFilter(req, res); @@ -125,53 +126,40 @@ protected void doFilterInternal(HttpServletRequest req, // Step 1: Check if OOBI is already registered Either oobiCheckResult = keriVerificationClient.getOOBI(headerOobi, 1); - if (oobiCheckResult.isLeft()) { - sendBackProblem(objectMapper, res, oobiCheckResult.getLeft()); - return; - } - // Log if OOBI is registered or not - log.info("OOBI status: {}", oobiCheckResult.get()); + if (oobiCheckResult.isLeft()) { + log.info("OOBI not registered yet: {}", headerOobi); + // Step 2: Register OOBI if not already registered + val oobiRegistrationResultE = keriVerificationClient.registerOOBI(headerOobi); - if (oobiCheckResult.isRight()) { - log.info("OOBI already registered: {}", oobiCheckResult); - Either verificationResult = keriVerificationClient.verifySignature(headerAid, headerSignature, headerSignedJson); + if (oobiRegistrationResultE.isLeft()) { + sendBackProblem(objectMapper, res, oobiRegistrationResultE.getLeft()); + return; + } - if (verificationResult.isEmpty()) { - val problem = Problem.builder() - .withTitle("KERI_SIGNATURE_VERIFICATION_FAILED") - .withDetail("Unable to verify KERI header signature, reason: " + verificationResult.swap().get().getDetail()) - .withStatus(BAD_REQUEST) - .build(); + log.info("OOBI registered successfully: {}", headerOobi); - sendBackProblem(objectMapper, res, problem); + // Step 3: Attempt to verify OOBI registration up to 60 times + val oobiFetchResultE = keriVerificationClient.getOOBI(headerOobi, 60); + if (oobiFetchResultE.isLeft()) { + sendBackProblem(objectMapper, res, oobiFetchResultE.getLeft()); return; } } - log.info("OOBI not registered yet: {}", headerOobi); - // Step 2: Register OOBI if not already registered - val oobiRegistrationResultE = keriVerificationClient.registerOOBI(headerOobi); - - if (oobiRegistrationResultE.isLeft()) { - sendBackProblem(objectMapper, res, oobiRegistrationResultE.getLeft()); + // Step 1.1:Update key state + Either keyStateUpdateResult = keriVerificationClient.updateAndVerifyKeyState(headerAid, 60); + if (keyStateUpdateResult.isLeft()) { + sendBackProblem(objectMapper, res, keyStateUpdateResult.getLeft()); return; } - log.info("OOBI registered successfully: {}", headerOobi); - - // Step 3: Attempt to verify OOBI registration up to 60 times - val oobiFetchResultE = keriVerificationClient.getOOBI(headerOobi, 60); - if (oobiFetchResultE.isLeft()) { - sendBackProblem(objectMapper, res, oobiFetchResultE.getLeft()); - return; - } + Either verificationResult = keriVerificationClient.verifySignature(headerAid, headerSignature, headerSignedJson); - val keriVerificationResultE = keriVerificationClient.verifySignature(headerAid, headerSignature, headerSignedJson); - if (keriVerificationResultE.isEmpty()) { + if (verificationResult.isEmpty()) { val problem = Problem.builder() .withTitle("KERI_SIGNATURE_VERIFICATION_FAILED") - .withDetail("Unable to verify KERI header signature, reason: " + keriVerificationResultE.swap().get().getDetail()) + .withDetail("Unable to verify KERI header signature, reason: " + verificationResult.swap().get().getDetail()) .withStatus(BAD_REQUEST) .build(); diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/Web3ConcreteDetails.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/Web3ConcreteDetails.java index 290ab3401..f041fa279 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/Web3ConcreteDetails.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/auth/web3/Web3ConcreteDetails.java @@ -16,7 +16,7 @@ public interface Web3ConcreteDetails { String getSignature(); - Optional getPayload(); + String getPayload(); Optional getPublicKey(); diff --git a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/vote/DefaultVoteService.java b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/vote/DefaultVoteService.java index 416870d06..11b8f816c 100644 --- a/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/vote/DefaultVoteService.java +++ b/backend-services/voting-app/src/main/java/org/cardano/foundation/voting/service/vote/DefaultVoteService.java @@ -28,9 +28,9 @@ import org.zalando.problem.Problem; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.UUID; -import java.util.Objects; import static com.bloxbean.cardano.client.util.HexUtil.encodeHexString; import static org.cardano.foundation.voting.domain.VoteReceipt.Status.*; @@ -306,7 +306,7 @@ public Either castVote(Web3AuthenticationToken web3Authentication existingVote.setVotedAtSlot(castVote.getVotedAtSlot()); existingVote.setWalletType(walletType); existingVote.setSignature(concreteDetails.getSignature()); - existingVote.setPayload(concreteDetails.getPayload()); + existingVote.setPayload(Optional.of(concreteDetails.getPayload())); existingVote.setPublicKey(concreteDetails.getPublicKey()); return Either.right(voteRepository.saveAndFlush(existingVote)); @@ -321,7 +321,7 @@ public Either castVote(Web3AuthenticationToken web3Authentication vote.setWalletType(walletType); vote.setVotedAtSlot(castVote.getVotedAtSlot()); vote.setSignature(concreteDetails.getSignature()); - vote.setPayload(concreteDetails.getPayload()); + vote.setPayload(Optional.of(concreteDetails.getPayload())); vote.setPublicKey(concreteDetails.getPublicKey()); vote.setIdNumericHash(UUID.fromString(voteId).hashCode() & 0xFFFFFFF); @@ -412,7 +412,7 @@ public Either castVote(Web3AuthenticationToken web3Authentication } private Either unwrapViewVoteReceiptEnvelope(Web3ConcreteDetails concreteDetails) { - val signedJson = concreteDetails.getSignedJson(); + val signedJson = concreteDetails.getPayload(); switch (concreteDetails) { case CardanoWeb3Details cardanoWeb3Details -> { @@ -455,7 +455,7 @@ private Either unwrapViewVoteReceiptEnvelope(W } private Either unwrapCastCoteEnvelope(Web3ConcreteDetails concreteDetails) { - val signedJson = concreteDetails.getSignedJson(); + val signedJson = concreteDetails.getPayload(); switch (concreteDetails) { case CardanoWeb3Details cardanoWeb3Details -> { diff --git a/backend-services/voting-app/src/test/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3FilterTest.java b/backend-services/voting-app/src/test/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3FilterTest.java index bab5c501b..b7df15f98 100644 --- a/backend-services/voting-app/src/test/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3FilterTest.java +++ b/backend-services/voting-app/src/test/java/org/cardano/foundation/voting/service/auth/web3/CardanoWeb3FilterTest.java @@ -1,5 +1,6 @@ package org.cardano.foundation.voting.service.auth.web3; +import com.bloxbean.cardano.client.util.HexUtil; import com.fasterxml.jackson.databind.ObjectMapper; import io.vavr.control.Either; import jakarta.servlet.FilterChain; @@ -30,8 +31,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.cardano.foundation.voting.domain.ChainNetwork.*; -import static org.cardano.foundation.voting.resource.Headers.X_Ballot_PublicKey; -import static org.cardano.foundation.voting.resource.Headers.X_Ballot_Signature; +import static org.cardano.foundation.voting.resource.Headers.*; import static org.cardano.foundation.voting.service.auth.LoginSystem.CARDANO_CIP93; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -48,8 +48,7 @@ class CardanoWeb3FilterTest { private ExpirationService expirationService; private ChainFollowerClient chainFollowerClient; private LoginSystemDetector loginSystemDetector; - - private final ChainNetwork chainNetworkStartedOn = PREPROD; + private ChainNetwork chainNetworkStartedOn = PREPROD; @BeforeEach void setUp() throws IOException { @@ -579,4 +578,156 @@ void doFilterInternal_shouldAuthenticate_whenAllConditionsMet() throws ServletEx assertThat(cardanoDetails.getEnvelope()).isNotNull(); } + @Test + void doFilterInternal_shouldAuthenticate_whenAllConditionsMetWithHashedContent() throws ServletException, IOException { + chainNetworkStartedOn = MAIN; + filter = new CardanoWeb3Filter(jsonService, expirationService, objectMapper, chainFollowerClient, chainNetworkStartedOn, loginSystemDetector); + + val payloadAsHex = "7b22616374696f6e223a224c4f47494e222c22616374696f6e54657874223a224c6f67696e222c2264617461223a7b226576656e74223a2243415244414e4f5f53554d4d49545f4157415244535f32303234222c226e6574776f726b223a224d41494e222c22726f6c65223a22564f544552222c2277616c6c65744964223a227374616b6531757970617970326e797a793636746d637a36796a757468353970796d3064663833726a706b30373538666871726e6371387663647a222c2277616c6c657454797065223a2243415244414e4f227d2c22736c6f74223a22313336303638393432227d"; + //{"action":"LOGIN","actionText":"Login","data":{"event":"CARDANO_SUMMIT_AWARDS_2024","network":"MAIN","role":"VOTER","walletId":"stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz","walletType":"CARDANO"},"slot":"136068942"} + + val genericEnvelope = CIP93Envelope.>builder() + .action("LOGIN") + .slot("136068942") + .data(Map.of( + "event", "CARDANO_SUMMIT_AWARDS_2024", + "walletId", "stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz", + "walletType", WalletType.CARDANO.name(), + "network", "MAIN", + "role", "VOTER" + ) + ) + .build(); + + when(loginSystemDetector.detect(request)).thenReturn(Optional.of(CARDANO_CIP93)); + when(request.getHeader(X_Ballot_Signature)).thenReturn("84582aa201276761646472657373581de103d205532089ad2f7816892e2ef42849b7b52788e41b3fd43a6e01cfa166686173686564f5581c1c1afc33a1ed48205eadcbbda2fc8e61442af2e04673616f21b7d0385840954858f672e9ca51975655452d79a8f106011e9535a2ebfb909f7bbcce5d10d246ae62df2da3a7790edd8f93723cbdfdffc5341d08135b1a40e7a998e8b2ed06"); + when(request.getHeader(X_Ballot_Payload)).thenReturn(payloadAsHex); + when(request.getHeader(X_Ballot_PublicKey)).thenReturn("a4010103272006215820c13745be35c2dfc3fa9523140030dda5b5346634e405662b1aae5c61389c55b3"); + + val cip30VerificationResult = mock(Cip30VerificationResult.class); + when(cip30VerificationResult.isValid()).thenReturn(true); + when(cip30VerificationResult.getAddress(any())).thenReturn(Optional.of("stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz")); + + when(chainFollowerClient.getChainTip()).thenReturn(Either.right( + new ChainFollowerClient.ChainTipResponse("hash", 512, 136068942, true, MAIN)) + ); + + when(chainFollowerClient.getEventDetails(any())).thenReturn(Either.right(Optional.of(mock(ChainFollowerClient.EventDetailsResponse.class)))); + + when(jsonService.decodeGenericCIP93(any())).thenReturn(Either.right(genericEnvelope)); + + filter.doFilterInternal(request, response, chain); + + verify(chain, times(1)).doFilter(request, response); + + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull(); + assertThat(SecurityContextHolder.getContext().getAuthentication().getPrincipal()).isEqualTo("stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz"); + + val cardanoDetails = (CardanoWeb3Details) SecurityContextHolder.getContext().getAuthentication().getDetails(); + + assertThat(cardanoDetails.getSignedCIP30().getSignature()).isEqualTo("84582aa201276761646472657373581de103d205532089ad2f7816892e2ef42849b7b52788e41b3fd43a6e01cfa166686173686564f5581c1c1afc33a1ed48205eadcbbda2fc8e61442af2e04673616f21b7d0385840954858f672e9ca51975655452d79a8f106011e9535a2ebfb909f7bbcce5d10d246ae62df2da3a7790edd8f93723cbdfdffc5341d08135b1a40e7a998e8b2ed06"); + assertThat(cardanoDetails.getSignedCIP30().getPublicKey()).isEqualTo(Optional.of("a4010103272006215820c13745be35c2dfc3fa9523140030dda5b5346634e405662b1aae5c61389c55b3")); + assertThat(cardanoDetails.getWeb3CommonDetails().getAction()).isEqualTo(Web3Action.LOGIN); + assertThat(cardanoDetails.getWeb3CommonDetails().getNetwork()).isEqualTo(MAIN); + assertThat(cardanoDetails.getPayload()).isEqualTo(new String(HexUtil.decodeHexString(payloadAsHex))); + assertThat(cardanoDetails.getEnvelope()).isNotNull(); + } + + // CIP30 is a data sign with hash only but hashes do not properly match + @Test + void doFilterInternal_shouldNotAuthenticate_whenHashesMismatch() throws IOException, ServletException { + chainNetworkStartedOn = MAIN; + filter = new CardanoWeb3Filter(jsonService, expirationService, objectMapper, chainFollowerClient, chainNetworkStartedOn, loginSystemDetector); + + //{"action":"LOGIN","actionText":"Login","data":{"event":"CARDANO_SUMMIT_AWARDS_2024","network":"MAIN","role":"VOTER","walletId":"stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz","walletType":"CARDANO"},"slot":"136068943"} + + val genericEnvelope = CIP93Envelope.>builder() + .action("LOGIN") + .slot("136068943") + .data(Map.of( + "event", "CARDANO_SUMMIT_AWARDS_2024", + "walletId", "stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz", + "walletType", WalletType.CARDANO.name(), + "network", "MAIN", + "role", "VOTER" + ) + ) + .build(); + + when(loginSystemDetector.detect(request)).thenReturn(Optional.of(CARDANO_CIP93)); + when(request.getHeader(X_Ballot_Signature)).thenReturn("84582aa201276761646472657373581de103d205532089ad2f7816892e2ef42849b7b52788e41b3fd43a6e01cfa166686173686564f5581c1c1afc33a1ed48205eadcbbda2fc8e61442af2e04673616f21b7d0385840954858f672e9ca51975655452d79a8f106011e9535a2ebfb909f7bbcce5d10d246ae62df2da3a7790edd8f93723cbdfdffc5341d08135b1a40e7a998e8b2ed06"); + when(request.getHeader(X_Ballot_Payload)).thenReturn("7B22616374696F6E223A224C4F47494E222C22616374696F6E54657874223A224C6F67696E222C2264617461223A7B226576656E74223A2243415244414E4F5F53554D4D49545F4157415244535F32303234222C226E6574776F726B223A224D41494E222C22726F6C65223A22564F544552222C2277616C6C65744964223A227374616B6531757970617970326E797A793636746D637A36796A757468353970796D3064663833726A706B30373538666871726E6371387663647A222C2277616C6C657454797065223A2243415244414E4F227D2C22736C6F74223A22313336303638393433227D"); + when(request.getHeader(X_Ballot_PublicKey)).thenReturn("a4010103272006215820c13745be35c2dfc3fa9523140030dda5b5346634e405662b1aae5c61389c55b3"); + + val cip30VerificationResult = mock(Cip30VerificationResult.class); + when(cip30VerificationResult.isValid()).thenReturn(true); + when(cip30VerificationResult.getAddress(any())).thenReturn(Optional.of("stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz")); + + when(chainFollowerClient.getChainTip()).thenReturn(Either.right( + new ChainFollowerClient.ChainTipResponse("hash", 512, 136068943, true, MAIN)) + ); + + when(chainFollowerClient.getEventDetails(any())).thenReturn(Either.right(Optional.of(mock(ChainFollowerClient.EventDetailsResponse.class)))); + + when(jsonService.decodeGenericCIP93(any())).thenReturn(Either.right(genericEnvelope)); + + filter.doFilterInternal(request, response, chain); + + val problemCaptor = ArgumentCaptor.forClass(Problem.class); + + verify(objectMapper, times(1)).writeValueAsString(problemCaptor.capture()); + val capturedProblem = problemCaptor.getValue(); + + assertThat(capturedProblem.getTitle()).isEqualTo("CIP_30_HASH_MISMATCH"); + assertThat(capturedProblem.getStatus()).isEqualTo(BAD_REQUEST); + } + + // CIP30 is a data sign with hash only but lets say we forgot to send the payload... + @Test + void doFilterInternal_shouldNotAuthenticate_whenPayloadNotSent() throws IOException, ServletException { + chainNetworkStartedOn = MAIN; + filter = new CardanoWeb3Filter(jsonService, expirationService, objectMapper, chainFollowerClient, chainNetworkStartedOn, loginSystemDetector); + + //{"action":"LOGIN","actionText":"Login","data":{"event":"CARDANO_SUMMIT_AWARDS_2024","network":"MAIN","role":"VOTER","walletId":"stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz","walletType":"CARDANO"},"slot":"136068943"} + + val genericEnvelope = CIP93Envelope.>builder() + .action("LOGIN") + .slot("136068943") + .data(Map.of( + "event", "CARDANO_SUMMIT_AWARDS_2024", + "walletId", "stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz", + "walletType", WalletType.CARDANO.name(), + "network", "MAIN", + "role", "VOTER" + ) + ) + .build(); + + when(loginSystemDetector.detect(request)).thenReturn(Optional.of(CARDANO_CIP93)); + when(request.getHeader(X_Ballot_Signature)).thenReturn("84582aa201276761646472657373581de103d205532089ad2f7816892e2ef42849b7b52788e41b3fd43a6e01cfa166686173686564f5581c1c1afc33a1ed48205eadcbbda2fc8e61442af2e04673616f21b7d0385840954858f672e9ca51975655452d79a8f106011e9535a2ebfb909f7bbcce5d10d246ae62df2da3a7790edd8f93723cbdfdffc5341d08135b1a40e7a998e8b2ed06"); + when(request.getHeader(X_Ballot_PublicKey)).thenReturn("a4010103272006215820c13745be35c2dfc3fa9523140030dda5b5346634e405662b1aae5c61389c55b3"); + + val cip30VerificationResult = mock(Cip30VerificationResult.class); + when(cip30VerificationResult.isValid()).thenReturn(true); + when(cip30VerificationResult.getAddress(any())).thenReturn(Optional.of("stake1uypayp2nyzy66tmcz6yjuth59pym0df83rjpk0758fhqrncq8vcdz")); + + when(chainFollowerClient.getChainTip()).thenReturn(Either.right( + new ChainFollowerClient.ChainTipResponse("hash", 512, 136068943, true, MAIN)) + ); + + when(chainFollowerClient.getEventDetails(any())).thenReturn(Either.right(Optional.of(mock(ChainFollowerClient.EventDetailsResponse.class)))); + + when(jsonService.decodeGenericCIP93(any())).thenReturn(Either.right(genericEnvelope)); + + filter.doFilterInternal(request, response, chain); + + val problemCaptor = ArgumentCaptor.forClass(Problem.class); + + verify(objectMapper, times(1)).writeValueAsString(problemCaptor.capture()); + val capturedProblem = problemCaptor.getValue(); + + assertThat(capturedProblem.getTitle()).isEqualTo("HASHED_CONTENT_NO_PAYLOAD"); + assertThat(capturedProblem.getStatus()).isEqualTo(BAD_REQUEST); + } + } diff --git a/backend-services/voting-app/src/test/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3FilterTest.java b/backend-services/voting-app/src/test/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3FilterTest.java index 068e9cf84..b98d35779 100644 --- a/backend-services/voting-app/src/test/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3FilterTest.java +++ b/backend-services/voting-app/src/test/java/org/cardano/foundation/voting/service/auth/web3/KeriWeb3FilterTest.java @@ -127,6 +127,7 @@ void doFilterInternal_shouldReturnBadRequest_whenKeriVerificationFails() throws when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.left(mock(Problem.class))); filter.doFilterInternal(request, response, chain); @@ -144,6 +145,7 @@ void doFilterInternal_shouldReturnBadRequest_whenKeriEnvelopeDecodingFails() thr when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(jsonService.decodeGenericKeri(any())).thenReturn(Either.left(mock(Problem.class))); @@ -168,6 +170,7 @@ void doFilterInternal_shouldReturnBadRequest_whenWalletIdIsMissingInEnvelope() t when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); val genericEnvelope = KERIEnvelope.>builder() @@ -198,6 +201,7 @@ void doFilterInternal_shouldReturnBadRequest_whenInvalidWalletType() throws Serv when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); val genericEnvelope = KERIEnvelope.>builder() @@ -231,6 +235,7 @@ void doFilterInternal_shouldReturnBadRequest_whenSlotIsNotNumeric() throws Servl when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.right( @@ -270,6 +275,7 @@ void doFilterInternal_shouldReturnInternalServerError_whenChainTipFails() throws when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.left(Problem.builder() @@ -312,6 +318,7 @@ void doFilterInternal_shouldReturnBadRequest_whenSlotIsExpired() throws ServletE when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(expirationService.isSlotExpired(any(), anyLong())).thenReturn(true); @@ -354,6 +361,7 @@ void doFilterInternal_shouldReturnBadRequest_whenNetworkIsInvalid() throws Servl when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.right( @@ -394,6 +402,7 @@ void doFilterInternal_shouldReturnBadRequest_whenChainNetworkMismatch() throws S when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.right( @@ -434,6 +443,7 @@ void doFilterInternal_shouldReturnBadRequest_whenAidCheckFails() throws ServletE when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.right( @@ -474,6 +484,7 @@ void doFilterInternal_shouldReturnBadRequest_whenAidMismatch() throws ServletExc when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.right( @@ -514,6 +525,7 @@ void doFilterInternal_shouldReturnInternalServerError_whenEventDetailsFails() th when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.right( @@ -561,6 +573,7 @@ void doFilterInternal_shouldReturnBadRequest_whenEventDetailsNotFound() throws S when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.right( @@ -603,6 +616,7 @@ void doFilterInternal_shouldAuthenticate_whenAllConditionsMet() throws ServletEx when(keriVerificationClient.getOOBI(anyString(), anyInt())).thenReturn(Either.right("http://localhost:3902/oobi/EBfYGHqQUk-iHVRDW5r3LKb2y5VpF1id8OF-RGXfcyRm/agent/EBfZKfhCQEssQXFUoIMk0otn3OB1DcCCxss6dBNWu2FZ")); when(keriVerificationClient.registerOOBI(anyString())).thenReturn(Either.right(true)); + when(keriVerificationClient.updateAndVerifyKeyState(anyString(), anyInt())).thenReturn(Either.right(true)); when(keriVerificationClient.verifySignature(any(), any(), any())).thenReturn(Either.right(true)); when(chainFollowerClient.getChainTip()).thenReturn(Either.right( diff --git a/backend-services/voting-ledger-follower-app/src/main/resources/application.properties b/backend-services/voting-ledger-follower-app/src/main/resources/application.properties index 3e9ed05bc..18655304b 100644 --- a/backend-services/voting-ledger-follower-app/src/main/resources/application.properties +++ b/backend-services/voting-ledger-follower-app/src/main/resources/application.properties @@ -49,7 +49,7 @@ cardano-client-lib.backend.type=${CLI_BACKEND:BLOCKFROST} organiser.account.stakeAddress=${ORGANISER_STAKE_ADDRESS} # comma separated list of event ids that this app will be binding / serving -bind.on.event.ids=${BIND_ON_EVENT_IDS:CF_SUMMIT_2024_10BCC} +bind.on.event.ids=${BIND_ON_EVENT_IDS:CF_SUMMIT_2024_15BCC} # yaci store props store.cardano.host=${CARDANO_NODE_HOST:preprod-node.world.dev.cardano.org} @@ -57,8 +57,8 @@ store.cardano.port=${CARDANO_NODE_PORT:30000} # protocol magic 1 = Cardano PreProd network store.cardano.protocol-magic=${CARDANO_NODE_PROTOCOL_MAGIC:1} -store.cardano.sync-start-blockhash=${YACI_STORE_CARDANO_SYNC_START_BLOCK_HASH:274218b4101de63d02dd38fa0ff9be75a1c146667f4aa67fe8a04e462c8c55b1} -store.cardano.sync-start-slot=${YACI_STORE_CARDANO_SYNC_START_SLOT:67868386} +store.cardano.sync-start-blockhash=${YACI_STORE_CARDANO_SYNC_START_BLOCK_HASH:be8c117c4e5ecbfbb233ab282eaf370764972585f0edbd6c3b195d532ef5eca8} +store.cardano.sync-start-slot=${YACI_STORE_CARDANO_SYNC_START_SLOT:69312000} # 1 day store.blocks.epoch-calculation-interval=86400 diff --git a/backend-services/voting-verification-app/src/main/java/org/cardano/foundation/voting/domain/VoteVerificationRequest.java b/backend-services/voting-verification-app/src/main/java/org/cardano/foundation/voting/domain/VoteVerificationRequest.java index aba3c92b2..aed4c90f3 100644 --- a/backend-services/voting-verification-app/src/main/java/org/cardano/foundation/voting/domain/VoteVerificationRequest.java +++ b/backend-services/voting-verification-app/src/main/java/org/cardano/foundation/voting/domain/VoteVerificationRequest.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.*; import java.util.List; @@ -18,7 +19,7 @@ public class VoteVerificationRequest { @Schema(description = "Root hash of the merkle tree", required = true) private String rootHash; - @NotBlank + @NotNull @Schema(description = "Cardano or KERI", required = true) private WalletType walletType; diff --git a/backend-services/voting-verification-app/src/main/java/org/cardano/foundation/voting/utils/VoteSerialisations.java b/backend-services/voting-verification-app/src/main/java/org/cardano/foundation/voting/utils/VoteSerialisations.java index 924fb1dd7..d30bca2c6 100644 --- a/backend-services/voting-verification-app/src/main/java/org/cardano/foundation/voting/utils/VoteSerialisations.java +++ b/backend-services/voting-verification-app/src/main/java/org/cardano/foundation/voting/utils/VoteSerialisations.java @@ -4,6 +4,7 @@ import org.cardano.foundation.voting.domain.WrappedVote; import org.cardanofoundation.cip30.CIP30Verifier; +import java.nio.ByteBuffer; import java.util.Optional; import java.util.function.Function; @@ -19,12 +20,13 @@ private static Function createSerialiserFunction() { case KERI -> { val message = vote.getSignature().getBytes(); val payload = vote.getPayload().map(String::getBytes).orElse(new byte[0]); + val totalLength = message.length + payload.length; - val result = new byte[message.length + payload.length]; + val buffer = ByteBuffer.allocate(totalLength); + buffer.put(message); + buffer.put(payload); - System.arraycopy(message, 0, result, 0, payload.length); - - yield blake2bHash256(result); + yield blake2bHash256(buffer.array()); } case CARDANO -> { val cip30Verifier = new CIP30Verifier(vote.getSignature(), vote.getPublicKey()); diff --git a/deploy/cf-cardano-ballot-ledger-follower-api/templates/cardano-node-service.yaml b/deploy/cf-cardano-ballot-ledger-follower-api/templates/cardano-node-service.yaml new file mode 100644 index 000000000..69b93a167 --- /dev/null +++ b/deploy/cf-cardano-ballot-ledger-follower-api/templates/cardano-node-service.yaml @@ -0,0 +1,17 @@ +{{ with .Values.localCardanoNode }} +{{ if .enabled }} +apiVersion: v1 +kind: Service +metadata: + labels: + app: cardano-node + name: cardano-node +spec: + ports: + - name: "3001" + port: 3001 + targetPort: 3001 + selector: + app: cardano-node +{{ end }} +{{ end }} diff --git a/deploy/cf-cardano-ballot-ledger-follower-api/templates/cardano-node-statefulset.yaml b/deploy/cf-cardano-ballot-ledger-follower-api/templates/cardano-node-statefulset.yaml new file mode 100644 index 000000000..d16799ce6 --- /dev/null +++ b/deploy/cf-cardano-ballot-ledger-follower-api/templates/cardano-node-statefulset.yaml @@ -0,0 +1,70 @@ +{{ with .Values.localCardanoNode }} +{{ if .enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + app: cardano-node + name: cardano-node +spec: + replicas: 1 + selector: + matchLabels: + app: cardano-node + template: + metadata: + labels: + app: cardano-node + spec: + {{- if .tolerations }} + tolerations: + {{ with index .tolerations }} +{{ toYaml . | nindent 8 }} + {{- end }} + {{- end }} + {{- if .affinity }} + affinity: + {{ with index .affinity }} +{{ toYaml . | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: cardano-node + image: "{{ .image.repository }}:{{ .image.tag }}" + {{- if .sleep | default false }} + command: ["bash", "-c", "sleep infinity"] + {{- end }} + env: + - name: RESTORE_SNAPSHOT + value: "{{ .mithrilRestore | default true }}" + - name: NETWORK + value: {{ .network | default "preprod" | quote }} + ports: + - containerPort: 3001 + hostPort: 3001 + protocol: TCP + resources: {} + volumeMounts: + - mountPath: /data + name: cardano-node-db + - mountPath: /ipc + name: cardano-node-ipc + restartPolicy: Always + volumes: + - name: cardano-node-db + persistentVolumeClaim: + claimName: cardano-node-db + - name: cardano-node-ipc + emptyDir: + sizeLimit: 1Mi + + volumeClaimTemplates: + - metadata: + name: cardano-node-db + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: {{ .volumeSize | default "10Gi" }} +{{ end }} +{{ end }} diff --git a/deploy/cf-cardano-ballot-ledger-follower-api/values.yaml b/deploy/cf-cardano-ballot-ledger-follower-api/values.yaml index 0c15e13cd..9452346ae 100644 --- a/deploy/cf-cardano-ballot-ledger-follower-api/values.yaml +++ b/deploy/cf-cardano-ballot-ledger-follower-api/values.yaml @@ -7,6 +7,14 @@ imagePullSecrets: [] nameOverride: "" fullnameOverride: "" +localCardanoNode: + enabled: false + image: + repository: ghcr.io/blinklabs-io/cardano-node + tag: main-amd64 + mithrilRestore: "true" + tolerations: null + affinity: null service: type: ClusterIP diff --git a/deploy/cf-cardano-ballot-voting-api/templates/deployment.yaml b/deploy/cf-cardano-ballot-voting-api/templates/deployment.yaml index 4c712a04f..5d5f32f21 100644 --- a/deploy/cf-cardano-ballot-voting-api/templates/deployment.yaml +++ b/deploy/cf-cardano-ballot-voting-api/templates/deployment.yaml @@ -33,17 +33,6 @@ spec: - name: CARDANO_NETWORK value: {{ .Values.network | default "preprod" | upper}} - - name: CARDANO_NODE_IP - value: {{ .Values.yaci.cardanoNodeHost | default "preprod-node.world.dev.cardano.org" }} - - name: CARDANO_NODE_PORT - value: {{ .Values.yaci.cardanoNodePort | default 30000 | quote }} - - name: CARDANO_NODE_PROTOCOL_MAGIC - value: {{ .Values.yaci.cardanoNodeProtocolMagic | default 1 | quote }} - - name: YACI_STORE_CARDANO_SYNC_START_BLOCK_HASH - value: {{ .Values.yaci.startBlockHash }} - - name: YACI_STORE_CARDANO_SYNC_START_SLOT - value: {{ .Values.yaci.startSlotNumber | quote }} - - name: CARDANO_NODE_HOST value: {{ $.Values.yaci.cardanoNodeHost | default "preprod-node.world.dev.cardano.org" }} - name: CARDANO_NODE_PORT diff --git a/deploy/cf-cardano-summit-2024-ui/templates/deployment.yaml b/deploy/cf-cardano-summit-2024-ui/templates/deployment.yaml index c042ff964..6e85e2521 100644 --- a/deploy/cf-cardano-summit-2024-ui/templates/deployment.yaml +++ b/deploy/cf-cardano-summit-2024-ui/templates/deployment.yaml @@ -39,12 +39,14 @@ spec: value: {{ .Values.discordBotUrl | default "https://discord.com/channels/example" }} - name: VITE_MATOMO_BASE_URL value: {{ .Values.matomoBaseUrl | default "https://example.com" }} + - name: VITE_MATOMO_PROJECT_ID + value: {{ .Values.matomoProjectId | default "1" | quote }} - name: VITE_TARGET_NETWORK value: {{ .Values.targetNetwork | default "PREPROD" }} - name: VITE_EVENT_ID value: {{ .Values.eventId | default "CF_SUMMIT_2024_8BCC" }} - name: VITE_SUPPORTED_WALLETS - value: {{ .Values.supportedWallets | default "flint,eternl,nami,typhon,yoroi,nufi,gerowallet,lace" }} + value: {{ .Values.supportedWallets | default "eternl,nami,typhon,yoroi,nufi,gerowallet,lace" }} - name: VITE_SHOW_WINNERS value: {{ .Values.showWinners | default "false" | quote }} - name: VITE_SHOW_HYDRA_TALLY diff --git a/deploy/cf-vote-commitment/templates/deployment.yaml b/deploy/cf-vote-commitment/templates/deployment.yaml index 212dcd0b4..87d2b0e58 100644 --- a/deploy/cf-vote-commitment/templates/deployment.yaml +++ b/deploy/cf-vote-commitment/templates/deployment.yaml @@ -110,6 +110,14 @@ spec: - name: LEDGER_FOLLOWER_APP_URL value: {{ $.Values.ledgerFollowerAppUrl }} + - name: ROLLBACK_HANDLING_ENABLED + value: {{ $.Values.rollbackHandlingEnabled | default "true" | quote }} + + - name: CARDANO_NODE_IP + value: {{ $.Values.cardanoNodeIp | default "preprod-node.world.dev.cardano.org" | quote }} + - name: CARDANO_NODE_PORT + value: {{ $.Values.cardanoNodePort | default "30000" | quote }} + ports: - name: http containerPort: {{ $.Values.service.port }} diff --git a/ui/summit-2023/src/components/common/Header/Header.tsx b/ui/summit-2023/src/components/common/Header/Header.tsx index acb304005..e534496bb 100644 --- a/ui/summit-2023/src/components/common/Header/Header.tsx +++ b/ui/summit-2023/src/components/common/Header/Header.tsx @@ -544,7 +544,8 @@ const Header: React.FC = () => { {' '} diff --git a/ui/summit-2023/src/pages/Categories/Categories.tsx b/ui/summit-2023/src/pages/Categories/Categories.tsx index 5dd8b97a2..4d55a3817 100644 --- a/ui/summit-2023/src/pages/Categories/Categories.tsx +++ b/ui/summit-2023/src/pages/Categories/Categories.tsx @@ -86,242 +86,375 @@ const Categories = () => { spacing={3} justifyContent="center" > - {items.map((category, index) => { - const voted = categoryAlreadyVoted(category.id, userVotes); - return ( - - -
- - {!isMobile ? ( - { + const voted = categoryAlreadyVoted(category.id, userVotes); + return ( + + +
+ - {isHoveredId == category.id ? ( - + {!isMobile ? ( + + {isHoveredId == category.id ? ( + + + + {voted ? ( + + {i18n.t('categories.alreadyVoted')} + + ) : null} + + + } + /> + + + {category.presentationName} + + + + + {category.desc} + + + + + + + + ) : ( + + + {voted ? ( + + {i18n.t('categories.alreadyVoted')} + + ) : null} + + + + + {category.presentationName} + + + + + + + )} + + ) : ( {voted ? ( - - {i18n.t('categories.alreadyVoted')} - + + {i18n.t('categories.alreadyVoted')} + ) : null} - } + avatar={ + + } /> {category.presentationName} {category.desc} - - ) : ( - - - {voted ? ( - - {i18n.t('categories.alreadyVoted')} - - ) : null} - - - - - {category.presentationName} - - - - - - )} - - ) : ( - - - {voted ? ( + +
+
+
+ ); + }) : null + } + +
+ ); + }; + const renderResponsiveList = (items): ReactElement => { + return ( +
+ + { + items.length ? items.map((category, index) => { + const voted = categoryAlreadyVoted(category.id, userVotes); + return ( + + + + + {voted ? ( {i18n.t('categories.alreadyVoted')} - ) : null} - + ) : null} + + - } + avatar={ + + } /> - + {category.presentationName} - - {category.desc} - + + + + + - - - )} - -
-
-
- ); - })} - - - ); - }; - const renderResponsiveList = (items): ReactElement => { - return ( -
- - {items.map((category, index) => { - const voted = categoryAlreadyVoted(category.id, userVotes); - return ( - - - - - {voted ? ( - - {i18n.t('categories.alreadyVoted')} - - ) : null} - - - - } - /> - - - {category.presentationName} - - - {category.desc} - - - - - - - - - - - - - ); - })} + > + {i18n.t('button.viewNominees')} + + + + + + ); + }) : null + }
); diff --git a/ui/summit-2024/.env.example b/ui/summit-2024/.env.example index dac154daf..4c5781869 100644 --- a/ui/summit-2024/.env.example +++ b/ui/summit-2024/.env.example @@ -14,18 +14,19 @@ VITE_DISCORD_CHANNEL_URL=https://discord.gg/WyB6QSVh ## Discord bot URL for Wallet verification VITE_DISCORD_BOT_URL=https://discord.com/channels/1255818615886843945/1255820482180284448 -## Matomo Base URL +## Matomo VITE_MATOMO_BASE_URL=http://localhost:8080 +VITE_MATOMO_PROJECT_ID=1 # Config vars ## Cardano Network Target. Possible values: MAINNET or PREPROD VITE_TARGET_NETWORK=PREPROD ## ID of the running event on chain -VITE_EVENT_ID="CF_SUMMIT_2024_8BCC" +VITE_EVENT_ID=CF_SUMMIT_2024_12BAF-LOCAL ## List of supported wallets -VITE_SUPPORTED_WALLETS=flint,eternl,nami,typhon,yoroi,nufi,gerowallet,lace +VITE_SUPPORTED_WALLETS=eternl,nami,typhon,yoroi,nufi,gerowallet,lace ## Controls the visibility of the winners of the voting VITE_SHOW_WINNERS=false @@ -34,4 +35,4 @@ VITE_SHOW_WINNERS=false VITE_SHOW_HYDRA_TALLY=false ## Using dummy data -VITE_USING_FIXTURES=false +VITE_USING_FIXTURES=true diff --git a/ui/summit-2024/Dockerfile b/ui/summit-2024/Dockerfile index e943eb40b..214636b2e 100644 --- a/ui/summit-2024/Dockerfile +++ b/ui/summit-2024/Dockerfile @@ -1,5 +1,5 @@ FROM node:18 AS build - +ARG VITE_VERSION=0.0.1 # Add a work directory WORKDIR /app # Cache and Install dependencies diff --git a/ui/summit-2024/docker-assets/bin/entrypoint.sh b/ui/summit-2024/docker-assets/bin/entrypoint.sh index ef91d0a49..f3143701f 100755 --- a/ui/summit-2024/docker-assets/bin/entrypoint.sh +++ b/ui/summit-2024/docker-assets/bin/entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/sh ENV_FILE_PATH=/usr/share/nginx/html/envfile.js -export VITE_VERSION=$(grep -m 1 "version" package.json | awk '{print $2}' | sed 's/[",]//g') +#export VITE_VERSION=$(grep -m 1 "version" package.json | awk '{print $2}' | sed 's/[",]//g') echo "window._env_ = {" > $ENV_FILE_PATH for var in $(env | grep ^VITE_); do diff --git a/ui/summit-2024/index.html b/ui/summit-2024/index.html index feaf61dd4..66e0ad4c9 100644 --- a/ui/summit-2024/index.html +++ b/ui/summit-2024/index.html @@ -10,13 +10,13 @@ Cardano Ballot - Cardano Summit Awards Voting
- + diff --git a/ui/summit-2024/package.json b/ui/summit-2024/package.json index 61cff037c..96fd1e4a3 100644 --- a/ui/summit-2024/package.json +++ b/ui/summit-2024/package.json @@ -1,7 +1,7 @@ { - "name": "vite-project", + "name": "cardano-ballot", "private": true, - "version": "0.0.0", + "version": "0.1.0", "type": "module", "scripts": { "dev": "vite", @@ -11,6 +11,7 @@ "prettier": "npx prettier --write 'src/**/*.{ts,tsx,scss}'" }, "dependencies": { + "buffer": "^6.0.3", "@cardano-foundation/cardano-connect-with-wallet": "^0.2.9", "@commitlint/cli": "^17.6.6", "@commitlint/config-conventional": "^17.6.6", diff --git a/ui/summit-2024/public/award24-3d.glb b/ui/summit-2024/public/award24-3d.glb new file mode 100644 index 000000000..7f316a681 Binary files /dev/null and b/ui/summit-2024/public/award24-3d.glb differ diff --git a/ui/summit-2024/public/compressed.glb b/ui/summit-2024/public/compressed.glb deleted file mode 100644 index e9af4801f..000000000 Binary files a/ui/summit-2024/public/compressed.glb and /dev/null differ diff --git a/ui/summit-2024/src/__fixtures__/event.ts b/ui/summit-2024/src/__fixtures__/event.ts index b549ae74e..99bb34e05 100644 --- a/ui/summit-2024/src/__fixtures__/event.ts +++ b/ui/summit-2024/src/__fixtures__/event.ts @@ -1,16 +1,16 @@ import { EventCacheProps } from "../store/reducers/eventCache/eventCache.types"; const eventDataFixture: EventCacheProps = { - id: "CF_SUMMIT_2024_4BCC", + id: "CF_SUMMIT_2024_15BCC", organisers: "CF", votingEventType: "USER_BASED", - startSlot: 41338775, - endSlot: 41360375, - proposalsRevealSlot: 41360375, + startSlot: 69948001, + endSlot: 70185600, + proposalsRevealSlot: 70189200, startEpoch: null, - eventStartDate: "2024-10-11T10:59:35", - eventEndDate: "2024-10-11T16:59:35", - proposalsRevealDate: "2024-10-11T16:59:35", + eventStartDate: "2024-09-26T14:00:01", + eventEndDate: "2024-09-29T08:00:00", + proposalsRevealDate: "2024-09-29T09:00:00", snapshotTime: null, endEpoch: null, snapshotEpoch: null, @@ -21,269 +21,529 @@ const eventDataFixture: EventCacheProps = { gdprProtection: true, proposals: [ { - id: "63123e7f-dfc3-481e-bb9d-fed1d9f6e9b9", - name: null, + id: "EC30C7AA-70BB-4036-A353-7C306FF31613", + name: "Andreas Sosilo", + x: "https://x.com/andreassosilo", + linkedin: "", + url: "", }, { - id: "0299d93e-93f2-4bc8-9b40-6dd09343c443", - name: null, + id: "4582D74F-81D0-4F28-8B18-E70E2A346813", + name: "Mauro Andreoli", + x: "https://x.com/MauroAndreoliA ", + linkedin: "", + url: "", }, { - id: "fd477fac-ad16-4d2a-91a4-0a4288d3d7aa", - name: null, + id: "7FA87520-0AD7-4797-83C7-4C7625AA58AA", + name: "Ha Nguyen", + x: "https://x.com/Hahero7", + linkedin: "", + url: "", }, { - id: "0b755eaf-a588-441f-a9dd-50c4aa478a90", - name: null, + id: "135DF392-D603-41B1-8BE1-C103E23816AD", + name: "Felix Weber", + x: "https://x.com/CatalystSwarm", + linkedin: "", + url: "", }, { - id: "2c94cd2e-2ad9-4425-af01-27210afca1e3", - name: null, + id: "AA8C39C4-0795-4F88-A867-3019098D5289", + name: "Guillermo Moratorio", + x: "https://x.com/WoodlandPools", + linkedin: "", + url: "", }, ], + name: "Ambassador", + desc: "Recognizes an individual who has significantly contributed to promoting and expanding the Cardano ecosystem.", }, { id: "BLOCKCHAIN_FOR_GOOD", gdprProtection: true, proposals: [ { - id: "633199b6-ab4c-49bc-afd8-e8c675d145d0", - name: null, + id: "150C277B-22D7-47F4-B626-AA57C338C479", + name: "Empowa", + x: "https://x.com/empowa_io", + linkedin: "https://www.linkedin.com/company/empowa/", + url: "https://empowa.io/", }, { - id: "d4d33796-7372-410a-b640-7dde093f20e5", - name: null, + id: "7BB2EF27-9BED-4A24-A79B-C79F530D27D2", + name: "Socious", + x: "https://x.com/SociousDAO", + linkedin: "https://www.linkedin.com/company/socious-io/", + url: "socious.io", }, { - id: "2e24b92f-1a34-4799-9eb4-a489be2b63c6", - name: null, + id: "7B9888E9-E5BD-42A1-BCF2-E997FE878E8A", + name: "ADA4Good SPO", + x: "https://x.com/Ada4goodP", + linkedin: "", + url: "https://www.ada4good.com", }, { - id: "4cbeb976-20ba-4c20-bdc1-f21bf28c17fd", - name: null, + id: "FD858845-4C44-423C-9898-62836D246E83", + name: "Landano International", + x: "https://x.com/landanodapp", + linkedin: "https://www.linkedin.com/company/landano/", + url: "https://www.landano.io/", + }, + ], + name: "Blockchain for Good", + desc: "Recognizes a project or stake pool operator (SPO) doing exceptional environmental or humanitarian work.", + }, + { + id: "INNOVATION_STANDARDS", + gdprProtection: true, + proposals: [ + { + id: "5D72F48B-8285-4BAC-8846-595B455A3084", + name: "ZKFold", + x: "https://x.com/zkFold", + linkedin: "https://www.linkedin.com/company/zkfold/", + url: "https://zkfold.io/", + }, + { + id: "BAD03444-C2EF-4E76-B502-807A69713F19", + name: "Unbox", + x: "https://x.com/Unbox_Universe", + linkedin: "https://www.linkedin.com/company/unboxuniverse/", + url: "https://www.unboxuniverse.com/", + }, + { + id: "AC5A90ED-A2C0-4775-86B4-C47EC39A083B", + name: "Vacuumlabs", + x: "https://x.com/vacuumlabs", + linkedin: "https://www.linkedin.com/company/vacuumlabs/", + url: "https://vacuumlabs.com/", + }, + { + id: "6D538F80-F617-402F-9F6C-0A2E7260D33E", + name: "Harmonic Labs", + x: "https://x.com/hlabs_tech", + linkedin: "", + url: "https://www.harmoniclabs.tech/", + }, + { + id: "F4192CB9-76B8-4C95-96A8-7B1822625877", + name: "NEWM", + x: "https://x.com/projectNEWM", + linkedin: "https://www.linkedin.com/company/projectnewm/", + url: "https://newm.io/", }, { - id: "71f4f082-4512-4e7f-adc6-6092d1b3aa14", - name: null, + id: "23D9536B-0ECE-4FE5-90D4-FBF05F7A85DD", + name: "Anastasia Labs", + x: "https://x.com/AnastasiaLabs", + linkedin: "https://www.linkedin.com/company/anastasialabs/", + url: "https://anastasialabs.com/", }, ], + name: "Innovation & Standards", + desc: 'Honors and expands the former "Standards (CIPs)" category to recognize an innovative project pushing the boundaries of what is possible on Cardano.', }, { - id: "CIPS", + id: "DEX", gdprProtection: true, proposals: [ { - id: "4cf1ea70-87dd-45ee-85a0-2d29720725f7", - name: null, + id: "CFA2481E-937C-433E-98BD-1AAC9F0FA2B9", + name: "Minswap", + x: "https://x.com/MinswapDEX", + linkedin: "https://www.linkedin.com/company/minswap", + url: "https://minswap.org/analytics/dao-treasury", }, { - id: "d5116b52-1e82-4c7f-95e5-2a74a9f75492", - name: null, + id: "74404AF7-466B-4679-977C-5F5BDE6446CD", + name: "Sundae Labs", + x: "https://x.com/SundaeSwap", + linkedin: "https://www.linkedin.com/company/sundaeswap-labs/", + url: "https://sundaeswap.finance", }, { - id: "69ee264f-9d02-48ef-98f1-541ffb28756f", - name: null, + id: "AD29AA23-00CA-4A4F-B08C-7D4FC8CE69E9", + name: "DexHunter", + x: "https://x.com/DexHunterIO", + linkedin: "https://www.linkedin.com/company/dexhunter/", + url: "https://app.dexhunter.io/", }, { - id: "70a88fb6-a87f-4ebd-8719-31c461118f3d", - name: null, + id: "58806B5D-4AD5-4C76-9CF4-2C90D068F6BA", + name: "WingRiders", + x: "https://x.com/wingriderscom", + linkedin: "https://www.linkedin.com/company/wingriders/", + url: "https://www.wingriders.com/", }, { - id: "eaafc7a1-8944-4faf-91dd-5ceefa51e8db", - name: null, + id: "802936AE-8114-4EB9-B1D9-C1780811CCBC", + name: "Splash Labs Inc.", + x: "https://x.com/splashprotocol", + linkedin: "", + url: "https://www.splash.trade/", + }, + { + id: "C3897902-424D-4F42-939C-9C37DE7137C5", + name: "MuesliSwap", + x: "https://x.com/MuesliSwapTeam", + linkedin: "", + url: "https://www.muesliswap.com/", }, ], + name: "DEX", + desc: "Recognizes a decentralized exchange (DEX) or project that contributes to liquidity provision on Cardano.", }, { - id: "BEST_DEFI_DEX", + id: "DEVELOPER_TOOLING", gdprProtection: true, proposals: [ { - id: "f71c438d-8247-4064-a5aa-4d21b54c2a5d", - name: null, + id: "865CE0B4-ABF9-4700-A53A-C8E84AD53995", + name: "MeshJS", + x: "https://x.com/meshsdk", + linkedin: "https://www.linkedin.com/company/meshjs", + url: "https://meshjs.dev/", }, { - id: "a5f92606-6e15-4f91-8443-7030eb02a274", - name: null, + id: "E6DBAF3C-5DA1-4AEA-9A14-FA81C14CF3C3", + name: "Aiken", + x: "https://x.com/aiken_eng", + linkedin: "", + url: "https://aiken-lang.org/", }, { - id: "e9d72191-bda4-437e-af4b-2f979bad5c7f", - name: null, + id: "3DE5D697-2FF5-4197-9694-78B35EA1CCC3", + name: "Zhaata - DexHunter", + x: "https://x.com/ZhaataC", + linkedin: "", + url: "https://app.dexhunter.io/", }, { - id: "8cf20a27-8bc8-49f3-8133-ef76e899e1c1", - name: null, + id: "6F604592-9908-45B3-BCE2-5C63F183AFFF", + name: "Pi Lanningham - SundaeSwap", + x: "https://x.com/Quantumplation", + linkedin: "", + url: "https://www.314pool.com/", }, { - id: "91871e20-f9aa-422f-9213-3722ac47c1c6", - name: null, + id: "7CB41A46-AC83-4F5F-B9D2-DC023209D8EE", + name: "Dennis Mittmann - IAMX", + x: "https://x.com/DennisMittmann", + linkedin: "", + url: "https://iamx.id/", + }, + { + id: "86A96F3F-FE17-42E1-9415-9A02DFEFAF66", + name: "Raul Padilla - FluidTokens", + x: "https://x.com/ElRaulito_cnft", + linkedin: "", + url: "https://linktr.ee/fluidtokens", }, ], + name: "Developer & Tooling", + desc: "Recognizes an individual or team and the tools they create to enhance the Cardano development experience.", }, { - id: "BEST_DEVELOPER_OR_DEVELOPER_TOOLS", + id: "EDUCATIONAL_INFLUENCER", gdprProtection: true, proposals: [ { - id: "f4d6055f-964e-43b4-bc23-83141ca04f9f", - name: null, + id: "46187CD7-07BD-4B6B-B225-DECA839E87C4", + name: "Lido Nation", + x: "https://x.com/LidoNation", + linkedin: "https://www.linkedin.com/company/lido-nation", + url: "https://lidonation.com", }, { - id: "a00e6d3e-1b06-48f0-b2a0-4f3784e6226c", - name: null, + id: "9605C945-845B-4616-B98C-3807263187F7", + name: "Gimbalabs", + x: "https://x.com/gimbalabs", + linkedin: "https://www.linkedin.com/company/gimbalabs", + url: "https://plutuspbl.io/", }, { - id: "58bb4e29-5124-473b-80e1-c5c8ffa57dbb", - name: null, + id: "E81164D1-550A-47B4-8726-2412E4DF42D0", + name: "Farid - Dapp Central", + x: "https://x.com/dapp_central", + linkedin: "", + url: "https://www.dappcentral.net/", }, { - id: "a30267e1-314c-4801-aa95-b03dd4d6856e", - name: null, + id: "9DFF4520-79C1-491A-95F1-C79B583355D7", + name: "Cardano Whale", + x: "https://x.com/cardano_whale", + linkedin: "", + url: "", }, { - id: "ec34567c-2012-4e3b-94ee-8778a6e33a04", - name: null, + id: "BBCE94A0-3FDB-4EDA-B0B7-EE256E8B157F", + name: "Peter Bui", + x: "https://x.com/petebui", + linkedin: "", + url: "https://linktr.ee/peterbui", }, ], + name: "Educational Influencer", + desc: "Recognizes an individual or account vastly contributing to blockchain education by creating content that helps others learn about Cardano.", }, { - id: "EDUCATIONAL_INFLUENCER", + id: "NFT_DIGITAL_COLLECTIBLES", gdprProtection: true, proposals: [ { - id: "88af463f-0d9c-4738-baef-bdb80f2c374e", - name: null, + id: "A31E6D75-2E84-4862-8032-B2BF7BB1887D", + name: "Hosky", + x: "https://x.com/hoskytoken", + linkedin: "", + url: "https://hosky.io", }, { - id: "65cf347e-129a-459b-b192-55ae37e03160", - name: null, + id: "7D6EF0EB-8067-4B3A-84F4-1947029E9700", + name: "GOAT Tribe", + x: "https://x.com/adaGOATS", + linkedin: "", + url: "https://www.goattribe.io/", }, { - id: "1e609753-a83e-4ff6-9cf8-dd90803f0368", - name: null, + id: "B70BD431-3C20-4680-8F79-D3A049CD154B", + name: "OREMOB by OreOreOre", + x: "https://x.com/ore_times_3", + linkedin: "", + url: "https://oremob.io/", }, { - id: "9b91f3ed-42be-4650-8e43-6d7a416f9591", - name: null, + id: "23B353B8-AB9E-46EA-B8E3-3F7B88E2E461", + name: "Clay Nation", + x: "https://x.com/claymates", + linkedin: "https://www.linkedin.com/company/claynation/", + url: "https://www.clayspace.io/", }, { - id: "702efda6-ceec-413e-8f33-aa206962850c", - name: null, + id: "B3BF6FBB-2838-4C39-850D-19F40E19F1AD", + name: "Book.io", + x: "https://x.com/book_io", + linkedin: "https://www.linkedin.com/company/book-io/", + url: "https://book.io/", }, ], + name: "NFT & Digital Collectibles", + desc: "Recognizes a project related to non-fungible tokens (NFTs) or digital collectibles, encompassing categories as diverse as marketplaces, art, music, gaming, or others.", }, { - id: "MARKETPLACE", + id: "INFRASTRUCTURE_PLATFORM", gdprProtection: true, proposals: [ { - id: "33038f64-fff9-44cc-a8e5-d4f5896c8ff6", - name: null, + id: "4C43B867-335E-4B75-A06F-D90F675D6B7C", + name: "Demeter by TxPipe", + x: "https://x.com/DemeterRun", + linkedin: "", + url: "https://demeter.run/", + }, + { + id: "C9C434CA-8DC8-462A-8E4D-1200B388F585", + name: "Maestro", + x: "https://x.com/GoMaestroOrg", + linkedin: "https://www.linkedin.com/company/gomaestro/", + url: "https://www.gomaestro.org/", }, { - id: "953c7970-6f9d-41a0-8556-b83ff7b481fe", - name: null, + id: "70C455C2-7DCF-48A0-92F1-61B88B8EB0CE", + name: "Blockfrost", + x: "https://x.com/blockfrost_io", + linkedin: "", + url: "https://blockfrost.io/", }, { - id: "107fc947-85f0-442e-b56f-9c10e8b5631a", - name: null, + id: "7B5CB884-74E5-4B01-A591-4FAA5768F143", + name: "Ada Anvil", + x: "https://x.com/ada_anvil", + linkedin: + "https://www.linkedin.com/company/anvil-development-agency-inc/about/", + url: "https://ada-anvil.io/", }, { - id: "0752dc99-19fa-4f4c-96c4-25ca3a66a12f", - name: null, + id: "A4D294D8-C40D-401C-949C-6DBCD0627759", + name: "dcSpark", + x: "https://x.com/dcspark_io", + linkedin: "https://www.linkedin.com/company/dcspark/", + url: "https://www.dcspark.io", }, { - id: "5b2145cd-8740-4254-942f-889eb3671640", - name: null, + id: "6B26B538-9E16-4EA5-8AA2-0120FDBA30F2", + name: "NMKR", + x: "https://x.com/nmkr_io", + linkedin: "https://www.linkedin.com/company/nmkrio/", + url: "https://www.nmkr.io/", }, ], + name: "Infrastructure Platform", + desc: "Recognizes an essential infrastructure project that supports the Cardano ecosystem.", }, { - id: "MOST_IMPACTFUL_SSPO", + id: "DAO_TOOLING_GOVERNANCE", gdprProtection: true, proposals: [ { - id: "6e16cdae-7696-4c41-a5f2-de373a17f488", - name: null, + id: "4D831658-C079-4A99-B08C-6180F18652A1", + name: "Clarity", + x: "https://x.com/Clarity_DAO", + linkedin: "https://www.linkedin.com/company/clarityprotocol/", + url: "https://www.clarity.vote/", + }, + { + id: "1846F0B9-1616-43B2-A919-CF70AEDAB356", + name: "Liqwid", + x: "https://x.com/liqwidfinance", + linkedin: "https://www.linkedin.com/company/liqwid-labs/", + url: "https://liqwid.finance/", }, { - id: "46ea36b8-15b6-4d31-8c29-946342595756", - name: null, + id: "0D81F908-2C90-4811-A4DE-ED2E61302AE1", + name: "Summon Platform", + x: "https://x.com/SummonPlatform", + linkedin: "https://www.linkedin.com/company/summonplatform/", + url: "https://summonplatform.io/", }, { - id: "e3a130e7-45c9-47c5-a121-fbdebc6c3e9f", - name: null, + id: "5278A5CC-AF3D-41A4-837E-10DFDD0CAB63", + name: "Minswap", + x: "https://x.com/MinswapDEX", + linkedin: "https://www.linkedin.com/company/minswap", + url: "https://minswap.org/", }, { - id: "cfe477d2-e7eb-46a7-a8ee-f721da2de399", - name: null, + id: "D1C06574-6619-4563-BD6B-6FE297A3E777", + name: "Indigo", + x: "https://x.com/Indigo_protocol", + linkedin: "https://www.linkedin.com/company/indigolabsinc/", + url: "https://indigoprotocol.io", }, { - id: "6ee41116-b60c-41d2-974c-c3de31b71a83", - name: null, + id: "1A75AED2-2A10-4558-9BE2-31F559242A43", + name: "Optim Finance", + x: "https://x.com/optimfi", + linkedin: "", + url: "https://dao.optim.finance/", }, ], + name: "DAO Tooling & Governance", + desc: "Recognizes a decentralized autonomous organization (DAO) for the importance of their tooling and governance solutions within the Cardano ecosystem.", }, { - id: "NFT_PROJECT", + id: "DEFI", gdprProtection: true, proposals: [ { - id: "623405b4-a845-4130-b406-b4cf4a1a985d", - name: null, + id: "8FE32CE4-8B75-4815-BDE5-DCC197945671", + name: "Indigo", + x: "https://x.com/Indigo_protocol", + linkedin: "https://www.linkedin.com/company/indigolabsinc/", + url: "https://indigoprotocol.io", }, { - id: "9074cf60-d413-4c20-a344-a3f894d2e6c0", - name: null, + id: "00CE32E2-60B3-4052-9C83-8A1547C10A63", + name: "Lenfi", + x: "https://x.com/LenfiOfficial", + linkedin: "https://www.linkedin.com/company/lenfi/", + url: "https://lenfi.io/", }, { - id: "1bae816a-b943-4148-ac58-c4081ef8cac5", - name: null, + id: "648E39DB-AB35-4072-9BAD-E39450961DD8", + name: "Liqwid", + x: "https://x.com/liqwidfinance", + linkedin: "https://www.linkedin.com/company/liqwid-labs/", + url: "https://liqwid.finance/", }, { - id: "c0f06200-b04c-4e08-b00b-e050cdcc205c", - name: null, + id: "33D87F23-7D57-41B2-86E3-D5C91BFAEC35", + name: "Optim Finance", + x: "https://x.com/optimfi", + linkedin: "", + url: "https://dao.optim.finance/", }, { - id: "02bd8150-91cd-499d-b94c-c0e7b5fd5dc4", - name: null, + id: "D4C62649-9CC3-4288-9C21-62EA8F5899F1", + name: "VyFinance", + x: "https://x.com/VyFiOfficial", + linkedin: "https://www.linkedin.com/company/vyfinance/", + url: "https://app.vyfi.io/", + }, + { + id: "EE456364-EA59-4F06-BC37-922572B79404", + name: "FluidTokens", + x: "https://x.com/FluidTokens", + linkedin: "https://www.linkedin.com/company/fluidtokens/", + url: "https://fluidtokens.com/", }, ], + name: "DeFi Platform", + desc: "Specifically recognizes a decentralized finance (DeFi) project offering superlative solutions in the form of lending platforms, yield aggregators, or other options.", }, { - id: "SSI", + id: "SSPO", gdprProtection: true, proposals: [ { - id: "a910a8af-f63b-4190-90fb-1409fd110526", - name: null, + id: "470FFE2B-18B6-4ACD-8DBE-0E8B7B2B761C", + name: "PRIDE", + x: "https://x.com/StakeWithPride", + linkedin: "", + url: "https://www.stakewithpride.org", }, { - id: "0a70b72d-1394-4bdd-bf93-e79ceb0c40a6", - name: null, + id: "B56B2068-7FC6-4ECB-B339-8BB6F1DCA6F9", + name: "Cardanistas", + x: "https://x.com/cardanistas", + linkedin: "", + url: "https://www.youtube.com/cardanistas", }, { - id: "f37bf063-15fc-4959-a6a4-0349a7613ede", - name: null, + id: "3FB24A88-424F-4D73-A96C-617533D7DB1C", + name: "Malu", + x: "https://x.com/Cryptofly777", + linkedin: "", + url: "https://www.youtube.com/channel/UCP_US45irp84HHE1j5E8SCg", }, { - id: "57f93799-5123-4ad0-a13f-a7c70387a756", - name: null, + id: "A19FF565-895D-46A4-A86D-2FF16FE796E2", + name: "Vietnam Cardano Community", + x: "https://x.com/VietnamCardano", + linkedin: "", + url: "https://linktr.ee/cardanoadavietnam", + }, + { + id: "82A8C4C1-0B86-4682-A9B0-6B88E75AC0E7", + name: "Dapp Central", + x: "https://x.com/dapp_central", + linkedin: "", + url: "https://www.dappcentral.net/", + }, + { + id: "D83833A3-9F99-4859-B0B7-545E3827FD09", + name: "DRMZ Pool", + x: "https://x.com/rodg_drmz", + linkedin: "", + url: "https://www.web3ineducation.com/", }, ], + name: "SSPO", + desc: "Recognizes the remarkable work done by a single stake pool operator (SSPO) and their vital contributions to the Cardano ecosystem.", }, ], tallies: [], + started: false, active: true, - started: true, - finished: false, - proposalsReveal: true, - commitmentsWindowOpen: false, - notStarted: false, - allowVoteChanging: false, highLevelEventResultsWhileVoting: true, highLevelCategoryResultsWhileVoting: true, categoryResultsWhileVoting: false, + commitmentsWindowOpen: false, + proposalsReveal: false, + allowVoteChanging: false, + notStarted: false, + finished: false, }; export { eventDataFixture }; diff --git a/ui/summit-2024/src/assets/bg/archBg.png b/ui/summit-2024/src/assets/archBg.png similarity index 100% rename from ui/summit-2024/src/assets/bg/archBg.png rename to ui/summit-2024/src/assets/archBg.png diff --git a/ui/summit-2024/src/assets/bg/guideCard.svg b/ui/summit-2024/src/assets/guideCard.svg similarity index 100% rename from ui/summit-2024/src/assets/bg/guideCard.svg rename to ui/summit-2024/src/assets/guideCard.svg diff --git a/ui/summit-2024/src/assets/bg/leaderboard1.svg b/ui/summit-2024/src/assets/leaderboard1.svg similarity index 100% rename from ui/summit-2024/src/assets/bg/leaderboard1.svg rename to ui/summit-2024/src/assets/leaderboard1.svg diff --git a/ui/summit-2024/src/assets/bg/leaderboard2.svg b/ui/summit-2024/src/assets/leaderboard2.svg similarity index 100% rename from ui/summit-2024/src/assets/bg/leaderboard2.svg rename to ui/summit-2024/src/assets/leaderboard2.svg diff --git a/ui/summit-2024/src/assets/bg/nomineeCard.svg b/ui/summit-2024/src/assets/nomineeCard.svg similarity index 100% rename from ui/summit-2024/src/assets/bg/nomineeCard.svg rename to ui/summit-2024/src/assets/nomineeCard.svg diff --git a/ui/summit-2024/src/assets/bg/notFoundBg.svg b/ui/summit-2024/src/assets/notFoundBg.svg similarity index 100% rename from ui/summit-2024/src/assets/bg/notFoundBg.svg rename to ui/summit-2024/src/assets/notFoundBg.svg diff --git a/ui/summit-2024/src/assets/share.svg b/ui/summit-2024/src/assets/share.svg new file mode 100644 index 000000000..44715287e --- /dev/null +++ b/ui/summit-2024/src/assets/share.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/ui/summit-2024/src/assets/bg/winnerBg.svg b/ui/summit-2024/src/assets/winnerBg.svg similarity index 100% rename from ui/summit-2024/src/assets/bg/winnerBg.svg rename to ui/summit-2024/src/assets/winnerBg.svg diff --git a/ui/summit-2024/src/common/api/loginService.ts b/ui/summit-2024/src/common/api/loginService.ts index d1407c43a..62e22c7bb 100644 --- a/ui/summit-2024/src/common/api/loginService.ts +++ b/ui/summit-2024/src/common/api/loginService.ts @@ -10,12 +10,14 @@ import { env } from "../constants/env"; export const LOGIN_URL = `${env.VOTING_APP_SERVER_URL}/api/auth/login`; type LoginInput = { - stakeAddress: string; + walletId: string; + walletType: string; slotNumber: string; }; const buildCanonicalLoginJson = ({ - stakeAddress, + walletId, + walletType, slotNumber, }: LoginInput): ReturnType => { return canonicalize({ @@ -23,20 +25,27 @@ const buildCanonicalLoginJson = ({ actionText: "Login", slot: slotNumber, data: { - address: stakeAddress, event: env.EVENT_ID, network: env.TARGET_NETWORK, role: "VOTER", + walletId: walletId, + walletType: walletType, }, }); }; -const submitLogin = async (jsonRequest: SignedWeb3Request) => { +const submitLogin = async ( + jsonRequest: SignedWeb3Request, + walletType: string, +) => { return await doRequest<{ accessToken: string; expiresAt: string }>( HttpMethods.GET, LOGIN_URL, DEFAULT_CONTENT_TYPE_HEADERS, - JSON.stringify(jsonRequest), + JSON.stringify({ + ...jsonRequest, + walletType, + }), undefined, true, ); diff --git a/ui/summit-2024/src/common/api/utils.ts b/ui/summit-2024/src/common/api/utils.ts index a7a649cd8..8a11c4103 100644 --- a/ui/summit-2024/src/common/api/utils.ts +++ b/ui/summit-2024/src/common/api/utils.ts @@ -4,7 +4,7 @@ enum WalletIdentifierType { } const resolveWalletType = (walletIdentifier: string): WalletIdentifierType => { - const regex = /^stake_[a-zA-Z0-9]+$/; + const regex = /^stake[_]?[a-zA-Z0-9]+$/; if (regex.test(walletIdentifier)) { return WalletIdentifierType.CARDANO; diff --git a/ui/summit-2024/src/common/api/verificationService.ts b/ui/summit-2024/src/common/api/verificationService.ts index 2db93b95c..28af0673d 100644 --- a/ui/summit-2024/src/common/api/verificationService.ts +++ b/ui/summit-2024/src/common/api/verificationService.ts @@ -23,16 +23,22 @@ export const DISCORD_VERIFICATION_URL = `${env.VOTING_USER_VERIFICATION_SERVER_U export const verifyVote = async (payload: { rootHash: string; steps: MerkleProofItem[]; - voteCoseSignature: string; - voteCosePublicKey: string; -}) => - await doRequest( + payload: string; + walletId: string; + signature: string; + publicKey: string | undefined; +}) => { + return await doRequest( HttpMethods.POST, `${VERIFICATION_URL}`, DEFAULT_CONTENT_TYPE_HEADERS, - JSON.stringify(payload), + JSON.stringify({ + ...payload, + eventId: env.EVENT_ID, + walletType: resolveWalletType(payload.walletId), + }), ); - +}; export const getIsVerified = async (walletIdentifier: string) => { return await doRequest<{ verified: boolean }>( HttpMethods.GET, diff --git a/ui/summit-2024/src/common/api/voteService.ts b/ui/summit-2024/src/common/api/voteService.ts index 2bbb8ead3..6b33336fb 100644 --- a/ui/summit-2024/src/common/api/voteService.ts +++ b/ui/summit-2024/src/common/api/voteService.ts @@ -8,7 +8,7 @@ import { env } from "../constants/env"; import { Problem, SignedWeb3Request, - UserVotes, + UserVote, Vote, VoteReceipt, } from "../../types/voting-app-types"; @@ -17,6 +17,7 @@ import { WalletIdentifierType } from "./utils"; export const CAST_VOTE_URL = `${env.VOTING_APP_SERVER_URL}/api/vote/cast`; export const VOTE_RECEIPT_URL = `${env.VOTING_APP_SERVER_URL}/api/vote/receipt`; +export const VOTE_RECEIPTS_URL = `${env.VOTING_APP_SERVER_URL}/api/vote/receipts`; export const BLOCKCHAIN_TIP_URL = `${env.VOTING_LEDGER_FOLLOWER_APP_SERVER_URL}/api/blockchain/tip`; export const USER_VOTES_URL = `${env.VOTING_APP_SERVER_URL}/api/vote/votes`; @@ -93,8 +94,19 @@ const getVoteReceipt = async (categoryId: string, token: string) => token, ); -const getUserVotes = async (token: string) => - await doRequest( +const getVoteReceipts = async (token: string) => + await doRequest( + HttpMethods.GET, + `${VOTE_RECEIPTS_URL}`, + { + ...DEFAULT_CONTENT_TYPE_HEADERS, + }, + undefined, + token, + ); + +const submitGetUserVotes = async (token: string) => + await doRequest( HttpMethods.GET, `${USER_VOTES_URL}/${env.EVENT_ID}`, { @@ -108,5 +120,6 @@ export { submitVoteWithDigitalSignature, getSlotNumber, getVoteReceipt, - getUserVotes, + getVoteReceipts, + submitGetUserVotes, }; diff --git a/ui/summit-2024/src/common/constants/env.ts b/ui/summit-2024/src/common/constants/env.ts index 4370ca3e5..148ac4c32 100644 --- a/ui/summit-2024/src/common/constants/env.ts +++ b/ui/summit-2024/src/common/constants/env.ts @@ -15,6 +15,10 @@ const MATOMO_BASE_URL = window._env_?.VITE_MATOMO_BASE_URL || import.meta.env.VITE_MATOMO_BASE_URL || "none"; +const MATOMO_PROJECT_ID = + window._env_?.VITE_MATOMO_PROJECT_ID || + import.meta.env.VITE_MATOMO_PROJECT_ID || + "1"; const DISCORD_CHANNEL_URL = window._env_?.VITE_DISCORD_CHANNEL_URL || import.meta.env.VITE_DISCORD_CHANNEL_URL; @@ -55,6 +59,7 @@ export const env = { APP_VERSION, SUPPORTED_WALLETS, MATOMO_BASE_URL, + MATOMO_PROJECT_ID, FRONTEND_URL, COMMIT_HASH, DISCORD_CHANNEL_URL, diff --git a/ui/summit-2024/src/common/handlers/httpHandler.ts b/ui/summit-2024/src/common/handlers/httpHandler.ts index fe2bed672..f9c1e346d 100644 --- a/ui/summit-2024/src/common/handlers/httpHandler.ts +++ b/ui/summit-2024/src/common/handlers/httpHandler.ts @@ -244,19 +244,17 @@ export const doRequest = async ( ) => { const allHeaders = { ...headers, ...DEFAULT_CONTENT_TYPE_HEADERS }; - console.log("body"); - console.log(body); if (body && bodyInHeader) { - allHeaders["X-Login-Signature"] = JSON.parse(body).signature; - allHeaders["X-Login-Public-Key"] = JSON.parse(body).publicKey; - allHeaders["X-Login-Payload"] = JSON.parse(body).payload; - allHeaders["X-Wallet-Type"] = JSON.parse(body).walletType; + allHeaders["X-Ballot-Signature"] = JSON.parse(body).signature; + allHeaders["X-Ballot-Public-Key"] = JSON.parse(body).publicKey; + allHeaders["X-Ballot-Payload"] = JSON.parse(body).payload; + allHeaders["X-Ballot-Wallet-Type"] = JSON.parse(body).walletType; + if (JSON.parse(body).oobi?.length) { + allHeaders["X-Ballot-Oobi"] = JSON.parse(body).oobi; + } body = undefined; } - console.log("allHeaders"); - console.log(allHeaders); - if (token) { // @ts-ignore allHeaders["Authorization"] = `Bearer ${token}`; diff --git a/ui/summit-2024/src/common/hooks/useSignatures.ts b/ui/summit-2024/src/common/hooks/useSignatures.ts deleted file mode 100644 index e3123ac44..000000000 --- a/ui/summit-2024/src/common/hooks/useSignatures.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { useCallback, useMemo, useState } from "react"; -import { useCardano } from "@cardano-foundation/cardano-connect-with-wallet"; -import { WalletIdentifierType } from "../api/utils"; -import { Buffer } from "buffer"; -import { - SignedKeriRequest, - SignedWeb3Request, -} from "../../types/voting-app-types"; -import { getSignedMessagePromise } from "../../utils/utils"; - -interface SignResponse { - success: boolean; - result?: SignedKeriRequest | SignedWeb3Request; - error?: string; -} - -export const useSignatures = () => { - const { signMessage } = useCardano(); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const signMessagePromisified = useMemo( - () => getSignedMessagePromise(signMessage), - [signMessage], - ); - - const signWithWallet = useCallback( - async ( - message: string, - walletIdentifier: string, - messageType?: WalletIdentifierType, - ): Promise => { - setIsLoading(true); - setError(null); - - try { - if ( - messageType && - messageType === WalletIdentifierType.KERI && - window.cardano && - window.cardano["idw_p2p"] - ) { - const api = window.cardano["idw_p2p"]; - const enabledApi = await api.enable(); - const keriIdentifier = - await enabledApi.experimental.getKeriIdentifier(); - - const signedMessage: string = await enabledApi.experimental.signKeri( - walletIdentifier, - message, - ); - return { - success: true, - result: { - signature: signedMessage, - publicKey: keriIdentifier.id, - payload: Buffer.from(message, "utf8").toString("hex"), - oobi: keriIdentifier.oobi, - }, - }; - } else { - const signedMessage = await signMessagePromisified(message); - return { - success: true, - - result: { - signature: signedMessage.signature, - publicKey: signedMessage.publicKey, - payload: Buffer.from(message, "utf8").toString("hex"), - }, - }; - } - } catch (e) { - // @ts-ignore - setError(`Error signing message: ${e.message}`); - setIsLoading(false); - // @ts-ignore - return { success: false, error: e.message }; - } finally { - setIsLoading(false); - } - }, - [], - ); - - return { - signWithWallet, - isLoading, - setIsLoading, - error, - }; -}; diff --git a/ui/summit-2024/src/common/resources/data/privacyPolicy.json b/ui/summit-2024/src/common/resources/data/privacyPolicy.json index 664c25578..4b17f4b35 100644 --- a/ui/summit-2024/src/common/resources/data/privacyPolicy.json +++ b/ui/summit-2024/src/common/resources/data/privacyPolicy.json @@ -1,6 +1,6 @@ { "title": "Privacy Policy", - "date": "Last updated: July, 2023", + "date": "Last updated: July, 2024", "description": [ "This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.", "We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy." diff --git a/ui/summit-2024/src/common/resources/data/summit2023Content.json b/ui/summit-2024/src/common/resources/data/summit2023Content.json deleted file mode 100644 index 1c129c78b..000000000 --- a/ui/summit-2024/src/common/resources/data/summit2023Content.json +++ /dev/null @@ -1,422 +0,0 @@ -{ - "categories": [ - { - "id": "AMBASSADOR", - "presentationName": "Ambassador", - "desc": "Special commitment and work of a Cardano Ambassador.", - "proposals": [ - { - "id": "63123e7f-dfc3-481e-bb9d-fed1d9f6e9b9", - "presentationName": "Brian Dill", - "urls": ["https://twitter.com/HEROsPool"], - "desc": "As a Cardano Ambassador, Brian creates premium content that educates but provides comprehensive perspectives on emerging industry developments." - }, - { - "id": "0299d93e-93f2-4bc8-9b40-6dd09343c443", - "presentationName": "Guillermo Moratorio", - "urls": ["https://twitter.com/WoodlandPools"], - "desc": "Guillermo - Woodland Pools is a Cardano Ambassador and Stake Pool Operator from Colorado. In addition to working as a full-stack developer for @book_io, Guillermo hosts a YouTube channel called Woodland Pools, where he covers the latest Cardano news, tutorials, and blockchain education." - }, - { - "id": "fd477fac-ad16-4d2a-91a4-0a4288d3d7aa", - "presentationName": "Thiago Nunes", - "urls": ["https://www.linkedin.com/in/thiago-nunes-72b95327/?originalSubdomain=br"], - "desc": "Thiago is a Cardano Ambassador based in Brazil. In addition to serving as a pioneering Cardano Stakepool Operator at OUROS, he's a founding member of Intersect and Director at Cardano Warriors LATAM." - }, - { - "id": "0b755eaf-a588-441f-a9dd-50c4aa478a90", - "presentationName": "Tien Nguyen Anh", - "urls": ["https://www.linkedin.com/in/tienna/?originalSubdomain=vn"], - "desc": "Tien is a dedicated Solution Architect and Enterprise Technical Specialist from Vietnam. As an active Cardano Ambassador, he's at the forefront of blockchain education in Vietnam and holds a key position in the Vietnam Blockchain Association." - }, - { - "id": "2c94cd2e-2ad9-4425-af01-27210afca1e3", - "presentationName": "Martin Lang", - "urls": ["https://forum.cardano.org/u/atada"], - "desc": "Martin is a Cardano Ambassador and Stake Pool Operator (SPO) advocate from Austria. He offers ongoing assistance by sharing scripts, guides, and support to aid fellow SPOs in establishing and managing their Stake Pools." - }, - { - "id": "e7d4df4a-8305-4ed8-9e42-6f67442d796e", - "presentationName": "Yuta Oishi", - "urls": ["https://forum.cardano.org/u/yuta_oishi/summary"], - "desc": "Yuta is a Cardano Ambassador and Stake Pool Operator (SPO) from Japan. In addition to working as a crypto-based CPA and tax accountant, Yuta is a Catalyst Proposer and ZZZ Co. (Ltd.) Community Lead." - } - ] - }, - { - "id": "BLOCKCHAIN_FOR_GOOD", - "presentationName": "Blockchain for Good", - "desc": "Project or stake pool operator (SPO) doing exceptional environmental or humanitarian work.", - "proposals": [ - { - "id": "633199b6-ab4c-49bc-afd8-e8c675d145d0", - "presentationName": "Cardano Impact Report 2023 - Sustainable ADA", - "urls": ["https://sustainableada.com/cardano-impact-report/"], - "desc": "The Cardano Impact Report 2023 explores how blockchain is reshaping perspectives on value, identity, and positive social or environmental changes. It envisions a future where Cardano's decentralized mechanisms can replace environmentally damaging practices, promoting cooperation and consensus for a sustainable world." - }, - { - "id": "d4d33796-7372-410a-b640-7dde093f20e5", - "presentationName": "Climate Neutral Cardano", - "urls": ["https://climateneutralcardano.org/"], - "desc": "Climate Neutral Cardano is an alliance of stake pools dedicated to achieving carbon-neutrality for the Cardano blockchain. It aims to mitigate the environmental impact of blockchain technology, striving to reduce carbon emissions to zero and promote sustainability within the Cardano ecosystem." - }, - { - "id": "2e24b92f-1a34-4799-9eb4-a489be2b63c6", - "presentationName": "DirectEd Development Foundation", - "urls": ["https://directed.dev/"], - "desc": "DirectEd Development Foundation (DirectEd) is a non-profit organization dedicated to providing evidence-based, scalable, and affordable training in coding, soft skills, and entrepreneurship. They focus on empowering under-resourced, high-potential students in Kenya and Ethiopia between high school and university." - }, - { - "id": "4cbeb976-20ba-4c20-bdc1-f21bf28c17fd", - "presentationName": "Kind Stake Pool [KIND]", - "urls": ["https://kindstakepool.com/"], - "desc": "The Kind Stake Pool [KIND] is motivated by a mission to transform Cardano into a platform for charitable contributions. By participating in [KIND], you not only earn attractive staking rewards, but also actively support charitable endeavors." - }, - { - "id": "71f4f082-4512-4e7f-adc6-6092d1b3aa14", - "presentationName": "Stake Pool for Refugees [WRFGS]", - "urls": ["https://unrefugees.ch/en/blockchain-refugees"], - "desc": "Switzerland for UNHCR employs the Cardano blockchain to aid displaced populations, offering a novel fundraising method that bridges the blockchain and humanitarian sectors to provide meaningful assistance. By embracing Cardano, they pioneer innovative fundraising through staking to support refugees and displaced individuals." - }, - { - "id": "07d4a8b3-dbfc-412c-9931-a5252db9082d", - "presentationName": "World Mobile", - "urls": ["https://worldmobile.io/"], - "desc": "World Mobile is the world's first community-driven mobile network, harnessing blockchain technology to achieve global connectivity by 2030 even in remote areas normally deemed expensive and challenging to serve. Their core values prioritize social, economic, and environmental sustainability, highlighted by their use of off-the-shelf equipment and renewable solar energy." - } - ] - }, - { - "id": "CIPS", - "presentationName": "CIPs", - "desc": "Original author or contributor to a Cardano Improvement Proposal (CIP) that seeks to significantly enhance the Cardano protocol.", - "proposals": [ - { - "id": "4cf1ea70-87dd-45ee-85a0-2d29720725f7", - "presentationName": "Adam Dean", - "urls": ["https://github.com/cardano-foundation/CIPs/tree/master/CIP-0027"], - "desc": "Adam Dean wrote CIP-0027 and contributed to CPS-0007 as well as CIP-0083. He is also a CIP editor." - }, - { - "id": "d5116b52-1e82-4c7f-95e5-2a74a9f75492", - "presentationName": "Andrew Westberg", - "urls": ["https://github.com/cardano-foundation/CIPs/tree/master/CIP-0060"], - "desc": "Andrew Westberg wrote CIP-0060 as well as CIP-0020, CIP-0022 and CIP-0083. He has also contributed to CIP-1694." - }, - { - "id": "69ee264f-9d02-48ef-98f1-541ffb28756f", - "presentationName": "Dr. Michael Liesenfelt", - "urls": ["https://github.com/cardano-foundation/CIPs/tree/master/CIP-0050"], - "desc": "Dr. Liesenfelt wrote CIP-0050 and contributed to many discussions regarding rewards and incentives." - }, - { - "id": "70a88fb6-a87f-4ebd-8719-31c461118f3d", - "presentationName": "Mike Hornan", - "urls": ["https://github.com/cardano-foundation/CIPs/tree/master"], - "desc": "Mike Hornan actively collaborated on CIP-1694, translated the proposal into French, and led efforts to educate the community about it." - }, - { - "id": "eaafc7a1-8944-4faf-91dd-5ceefa51e8db", - "presentationName": "Robert Phair", - "urls": ["https://github.com/cardano-foundation/CIPs/tree/master"], - "desc": "Robert Phair has been a CIP editor for more than 2 years." - }, - { - "id": "9cdbde0e-ffd4-4d22-ae3c-17cb63dc89fd", - "presentationName": "Ryan Williams", - "urls": ["https://github.com/cardano-foundation/CIPs/pull/509", "https://github.com/cardano-foundation/CIPs/pull/462/"], - "desc": "Ryan Williams is a CIP editor and also author of CIP-0090 and CIP-0095." - } - ] - }, - { - "id": "BEST_DEFI_DEX", - "presentationName": "DeFi / DEX", - "desc": "Decentralized finance (DeFi) or decentralized exchange (DEX) platform offering superlative solutions.", - "proposals": [ - { - "id": "f71c438d-8247-4064-a5aa-4d21b54c2a5d", - "presentationName": "Indigo", - "urls": ["https://indigoprotocol.io/"], - "desc": "Indigo, a Cardano-based protocol, offers autonomous synthetics (iAssets) linking the real world to blockchain. iAssets mimic real asset price movements, enabling everyone to access financial opportunities without owning the actual assets." - }, - { - "id": "a5f92606-6e15-4f91-8443-7030eb02a274", - "presentationName": "Lenfi", - "urls": ["https://lenfi.io/"], - "desc": "Lenfi enables decentralized lending and borrowing on the Cardano blockchain. Users engage as depositors or borrowers in peer-to-peer or peer-to-pool transactions, unlocking financial potential." - }, - { - "id": "e9d72191-bda4-437e-af4b-2f979bad5c7f", - "presentationName": "Minswap", - "urls": ["https://minswap.org/"], - "desc": "Minswap is a community-focused DEX, distributing $MIN tokens equitably without private or VC backing. The project introduced innovative concepts like the FISO model and Protocol Owned Liquidity to the Cardano ecosystem, delivering a multi-pool decentralized exchange." - }, - { - "id": "8cf20a27-8bc8-49f3-8133-ef76e899e1c1", - "presentationName": "Optim", - "urls": ["https://www.optim.finance/"], - "desc": "Optim Finance is a Cardano-based DeFi protocol that empowers users with innovative yield products like ada staking optimizing capital allocation and facilitating participation." - }, - { - "id": "91871e20-f9aa-422f-9213-3722ac47c1c6", - "presentationName": "Spectrum Finance", - "urls": ["https://spectrum.fi/"], - "desc": "Spectrum Finance is a multi-chain DEX that launched on Cardano in July 2023. In addition to multi-chain capability and security, it offers fast, trustless cross-chain swaps, liquidity provision, and mining on an open-source platform." - }, - { - "id": "c7c7ca58-af7e-4f27-a170-070d76707580", - "presentationName": "Wingriders", - "urls": ["https://www.wingriders.com/"], - "desc": "WingRiders, a Cardano-based DEX utilizing the eUTxO model, provides DeFi services encompassing ada swaps, staking, and yield farming within its Automated Market Maker (AMM) ecosystem." - } - ] - }, - { - "id": "BEST_DEVELOPER_OR_DEVELOPER_TOOLS", - "presentationName": "Developer or Developer Tools", - "desc": "Individual or team providing technical solutions to the Cardano ecosystem.", - "proposals": [ - { - "id": "f4d6055f-964e-43b4-bc23-83141ca04f9f", - "presentationName": "Andrew Westberg", - "urls": ["https://twitter.com/amw7?lang=en-GB"], - "desc": "Andrew is a Stake Pool Operator (SPO) for Blue Cheese St\u20b3ke House. As an active member of the Cardano community, he also runs an educational YouTube Channel called NerdOut, where he delves into technical Cardano topics." - }, - { - "id": "a00e6d3e-1b06-48f0-b2a0-4f3784e6226c", - "presentationName": "Helios", - "urls": ["http://github.com/hyperion-bt/helios"], - "desc": "Helios is a Domain Specific Language (DSL) that compiles to Plutus-Core (i.e., Cardano on-chain validator scripts). Helios is a non-Haskell alternative to Plutus and is purely functional, strongly typed, and has a simple curlsy braces syntax." - }, - { - "id": "58bb4e29-5124-473b-80e1-c5c8ffa57dbb", - "presentationName": "OpShin", - "urls": ["https://opshin.dev/"], - "desc": "OpShin is a programming language and toolchain designed for Cardano smart contract development. Opshin is a strict subset of Python, meaning developers with Python experience can get up to speed quickly, eliminating barriers to entry." - }, - { - "id": "a30267e1-314c-4801-aa95-b03dd4d6856e", - "presentationName": "Pi Lanningham", - "urls": ["https://www.314pool.com/"], - "desc": "Pi (\u03c0) is a mathematician turned software engineer, best known as the CTO of SundaeSwap Labs. With a passion for education, \u03c0 operates 314pool and contributes regularly to the Cardano ecosystem via thought leadership." - }, - { - "id": "ec34567c-2012-4e3b-94ee-8778a6e33a04", - "presentationName": "Strica", - "urls": ["https://strica.io/"], - "desc": "Strica develops open-source developer tools for Cardano, including Cardanoscan, Typhon Wallet, and Flac Finance. They've also introduced Warp Transactions, which aim to transform ada token transfers on the Cardano network." - }, - { - "id": "204a8d71-adb1-4fff-b59d-5fb391d0078d", - "presentationName": "TxPipe", - "urls": ["https://txpipe.io/"], - "desc": "TxPipe is an open-source software project dedicated to enhancing the developer experience in the Cardano blockchain ecosystem. The project focuses on developing blockchain tools that support the open-source community, accelerating blockchain adoption." - } - ] - }, - { - "id": "EDUCATIONAL_INFLUENCER", - "presentationName": "Educational Influencer", - "desc": "Individual or account vastly contributing to blockchain education.", - "proposals": [ - { - "id": "88af463f-0d9c-4738-baef-bdb80f2c374e", - "presentationName": "Army of Spies", - "urls": ["https://www.youtube.com/@ArmyofSpies/videos"], - "desc": "Army of Spies is a YouTube channel that provides an (almost) daily rundown of all the news and rumors circulating in the Cardano ecosystem." - }, - { - "id": "65cf347e-129a-459b-b192-55ae37e03160", - "presentationName": "Cardano over Coffee", - "urls": ["https://linktr.ee/CardanoOverCoffee"], - "desc": "Cardano over Coffee is a laid-back, deep-dive conversational program (show) that delves into everything Cardano. The platform also serves as a launchpad for new project and company announcements. Active ecosystem members are also regularly interview as guests." - }, - { - "id": "1e609753-a83e-4ff6-9cf8-dd90803f0368", - "presentationName": "Cardano With Paul", - "urls": ["https://www.youtube.com/@CardanoWithPaul/about"], - "desc": "Paul, a crypto enthusiast based in Ireland, has actively participated in the ecosystem since 2016. He manages the \"Cardano with Paul\" YouTube channel and blog, exclusively focusing on Cardano updates, insights, tutorials, occasional price analysis, and any valuable content he deems beneficial." - }, - { - "id": "9b91f3ed-42be-4650-8e43-6d7a416f9591", - "presentationName": "Farid - Dapp Central", - "urls": ["https://twitter.com/dapp_central?lang=en"], - "desc": "Farid is the founder of DappCentral, a Stake Pool Operator [DAPP] and educational YouTube channel that aims to on-board new crypto enthusiasts into the blockchain and crypto space." - }, - { - "id": "702efda6-ceec-413e-8f33-aa206962850c", - "presentationName": "Peter Bui", - "urls": ["https://learncardano.io/"], - "desc": "Peter is a Cardano Ambassador, Stake Pool Operator, and \"Learn Cardano\" podcast host where anyone, from beginners to experts, can learn more about Cardano and blockchain technology." - }, - { - "id": "e0b5f280-95f4-42aa-8ecb-00b95c2896a4", - "presentationName": "The Cardano Times", - "urls": ["https://linktr.ee/thecardanotimes"], - "desc": "The Cardano Times offers free education and news on Cardano, crypto, and Web3. The YouTube channel covers a wide range of Cardano-related topics, including NFTs, smart contracts, decentralized apps, exchanges, staking, and more." - } - ] - }, - { - "id": "MARKETPLACE", - "presentationName": "Marketplace", - "desc": "Marketplace with proven maturity and excellence.", - "proposals": [ - { - "id": "33038f64-fff9-44cc-a8e5-d4f5896c8ff6", - "presentationName": "Artano", - "urls": ["https://artano.io/home/"], - "desc": "Artano's mission is to create a community-driven marketplace for artists, emphasizing diversity and high-quality art. Their Council of Curators, representing global talent, hails from 16 countries with diverse backgrounds. The $ARTA governance token empowers users to shape Artano's future direction through voting." - }, - { - "id": "953c7970-6f9d-41a0-8556-b83ff7b481fe", - "presentationName": "Dropspot", - "urls": ["https://dropspot.io/"], - "desc": "Dropspot is a curated NFT experience on Cardano that empowers creators to regain artistic sovereignty, allowing them to take complete control of their work and connecting them with new markets, global buyers, and collaborators." - }, - { - "id": "107fc947-85f0-442e-b56f-9c10e8b5631a", - "presentationName": "JamOnBread", - "urls": ["https://jamonbread.io/"], - "desc": "JamOnBread is a decentralized NFT marketplace on Cardano, featuring a groundbreaking smart contract for equitable profit sharing among users, projects, and marketplaces. Its transparent approach includes on-chain affiliate rewards, NFT rarity scoring, and user profiles, aiming to attract newcomers to Cardano's NFT ecosystem." - }, - { - "id": "0752dc99-19fa-4f4c-96c4-25ca3a66a12f", - "presentationName": "JPG Store", - "urls": ["https://www.jpg.store/"], - "desc": "JPG Store is the largest NFT marketplace on Cardano and supports various file formats, including JPGs, PNGs, and SVGs. The JPG Store mission aligns with Cardano's goal of delivering financial infrastructure for the unbanked." - }, - { - "id": "5b2145cd-8740-4254-942f-889eb3671640", - "presentationName": "Kreate Platform", - "urls": ["https://kreate.art/"], - "desc": "Kreate is a comprehensive art platform on Cardano, fostering a vibrant arts culture. It empowers artists with portfolio management, social features, and innovative marketplace tools like edition support, royalties, auctions, and provenance exploration. With years of Cardano experience, Kreate delivers user-friendly, decentralized solutions, including non-custodial on-demand minting." - }, - { - "id": "01991af2-3bc9-4818-a745-db7683c8fe37", - "presentationName": "Mutant Labs", - "urls": ["https://labs.mutant-nft.com/"], - "desc": "Mutant Labs provides diverse NFT project utilities on Cardano, including raffles, staking, and whitelist management. Their core mission is to inject enjoyment into the cNFT ecosystem." - } - ] - }, - { - "id": "MOST_IMPACTFUL_SSPO", - "presentationName": "Most Impactful SSPO", - "desc": "Remarkable single stake pool operator (SSPO).", - "proposals": [ - { - "id": "6e16cdae-7696-4c41-a5f2-de373a17f488", - "presentationName": "ADA4Good [A4G]", - "urls": ["https://www.ada4good.com/"], - "desc": "Run by Vahid, ADA4Good [A4G] rewards its delegators with World Mobile Tokens (WMT) and ada, while also donating 50% of margin fees to Save The Children. As a Mission Drive Stake Pool, [A4G] is part of a collective that donates a portion of block rewards to charitable causes using ada where accepted, or fiat currency." - }, - { - "id": "46ea36b8-15b6-4d31-8c29-946342595756", - "presentationName": "Blade [BLADE]", - "urls": ["https://bladepool.com"], - "desc": "The Blade Pool [BLADE] is operated by Conrad, an InfoSec and IT infrastructure engineer from New York City. In addition to his 20 years of corporate experience, Conrad is a $adahandle co-founder and a well-respected, active community member on various social networks where he engages in discussions about Cardano and blockchain-related topics." - }, - { - "id": "e3a130e7-45c9-47c5-a121-fbdebc6c3e9f", - "presentationName": "Clay Nation [CLAY]", - "urls": ["https://claynation.gitbook.io/claypaper/tokenonomy/cardano-staking"], - "desc": "The independent, community-driven Clay Nation [CLAY] Pool has been operating for over 18 months, with over 1,000 blocks minted. CLAY offers staking benefits to encourage ada staking and long-term holding. Clay Nation stands out for its high-impact collaborations that blend technology, art, and entertainment. Through the forging of creative partnerships, the team is dedicated to accelerating adoption and amplifying the impact of Cardano projects across various markets." - }, - { - "id": "cfe477d2-e7eb-46a7-a8ee-f721da2de399", - "presentationName": "GimbalPool [GMBL]", - "urls": ["https://gimbalabs.com/"], - "desc": "[GMBL] Stake Pool represents the community behind Gimbalabs. Dedicated to empowering the Cardano ecosystem, Gimbalabs creates community spaces, establishes robust technical infrastructure, offers educational programs, and contributes open-source code all with the ambition of ensuring Cardano's transformative impact on the world. Their emphasis on education and collaboration shines through in their Plutus Project-Based Learning program, which onboards developers into the ecosystem." - }, - { - "id": "6ee41116-b60c-41d2-974c-c3de31b71a83", - "presentationName": "LEAD Stake Pool [LEAD]", - "urls": ["https://leadstakepool.com/"], - "desc": "The LEAD Stake Pool [LEAD] is run by a wealth manager in Sydney, Australia, with a decade of experience in financial services, specializing in tax engine programming. LEAD is dedicated to fostering communication with delegators, prioritizing long-term commitment and minimizing the need for constant delegation oversight. As a strong supporter of decentralization, LEAD participates in the Cardano Bare Metal and Single Pool Alliances." - }, - { - "id": "e4895e4b-b25a-43d5-9bd0-1dcd23954faa", - "presentationName": "SM\u20b3UG [SMAUG]", - "urls": ["https://smaug.pool.pm/"], - "desc": "SMAUG is the creator of pool.pm, one of the most popular and widely used block explorers in the Cardano ecosystem. With the development of this block explorer, SMAUG has not only demonstrated technical excellence but also the ability to provide a solution that helps visualize transaction movements and wallet content." - } - ] - }, - { - "id": "NFT_PROJECT", - "presentationName": "NFT Project", - "desc": "Most impactful art, music, metaverse, gaming, or collectible NFT project in the Cardano community.", - "proposals": [ - { - "id": "623405b4-a845-4130-b406-b4cf4a1a985d", - "presentationName": "aeoniumsky", - "urls": ["https://www.aeoniumsky.io/"], - "desc": "aeoniumsky crafts surreal animated artworks that blur reality and imagination, offering viewers a sensory journey through fantastical alternate realms." - }, - { - "id": "9074cf60-d413-4c20-a344-a3f894d2e6c0", - "presentationName": "Book.io", - "urls": ["https://book.io/"], - "desc": "Book.io brings digital reading to Web3 technology, allowing readers to own eBooks and Audiobooks as Decentralized Encrypted Assets (DEAs). Unlike other licenses, these DEAs are full books living on-chain and enabling resale on secondary markets. Authors and publishers receive perpetual royalties, rewarding their creative efforts" - }, - { - "id": "1bae816a-b943-4148-ac58-c4081ef8cac5", - "presentationName": "Clay Nation", - "urls": ["https://www.clayspace.io/"], - "desc": "A collection of 10,000 characters created from hand-crafted, randomly-assembled clay traits. Each is a one-of-a-kind digital collectible, stored on the Cardano blockchain." - }, - { - "id": "c0f06200-b04c-4e08-b00b-e050cdcc205c", - "presentationName": "HOSKY", - "urls": ["https://hosky.io/"], - "desc": "HOSKY is Cardano's inaugural meme token. Without intrinsic value, it provides a speculative thrill for crypto enthusiasts riding the dog-themed mascot trend." - }, - { - "id": "02bd8150-91cd-499d-b94c-c0e7b5fd5dc4", - "presentationName": "OREMOB", - "urls": ["https://oremob.io/"], - "desc": "OREMOB, by Berlin artist ORE ORE ORE, is an expansion of his 2021 Web3 anime project. It's a community-driven space blending captivating narratives, unique visual identities, and pop-culture references into an artful experience." - }, - { - "id": "50d31468-a915-4284-9e3f-e0c6f5c1c90c", - "presentationName": "The Ape Society", - "urls": ["https://www.theapesociety.io/"], - "desc": "The Ape Society is a Cardano project and community featuring 7,000 unique NFTs. Anyone can join this exclusive community by minting an ape or purchasing one on secondary markets." - } - ] - }, - { - "id": "SSI", - "presentationName": "Self Sovereign Identity (SSI)", - "desc": "SSI solution providing phenomenal management and governance of credentials, identity, tokens, or others.", - "proposals": [ - { - "id": "a910a8af-f63b-4190-90fb-1409fd110526", - "presentationName": "Atala PRISM", - "urls": ["https://atalaprism.io/"], - "desc": "Atala PRISM is a self-sovereign identity (SSI) platform and service suite for verifiable data and digital identity. Built on Cardano, it offers core infrastructure for issuing DIDs (Decentralized identifiers) and verifiable credentials, alongside other tools and frameworks." - }, - { - "id": "0a70b72d-1394-4bdd-bf93-e79ceb0c40a6", - "presentationName": "Blocktrust", - "urls": ["https://blocktrust.dev/"], - "desc": "Blocktrust is building on Atala PRISM, an SSI ecosystem platform for DID creation, credential issuance, identity verification, and more. With Atala PRISM, IOG has created a technical platform in the SSI ecosystem on which Blocktrust can create DIDs and issue credentials." - }, - { - "id": "f37bf063-15fc-4959-a6a4-0349a7613ede", - "presentationName": "IAMX", - "urls": ["https://iamx.id/"], - "desc": "IAMX's self-sovereign identity (SSI) solutions blend consumer incentives and top-tier security. Their platform shapes identity management in the decentralized, consumer-driven landscape of Web3." - }, - { - "id": "57f93799-5123-4ad0-a13f-a7c70387a756", - "presentationName": "ProofSpace", - "urls": ["https://www.proofspace.id/"], - "desc": "ProofSpace is an interoperable, decentralized identity network with no-code capabilities. Their platform facilitates the issuance and verification of reusable identity credentials, empowering ecosystems." - } - ] - } - ] -} \ No newline at end of file diff --git a/ui/summit-2024/src/common/resources/data/summit2024MainnetContent.json b/ui/summit-2024/src/common/resources/data/summit2024MainnetContent.json new file mode 100644 index 000000000..92904746a --- /dev/null +++ b/ui/summit-2024/src/common/resources/data/summit2024MainnetContent.json @@ -0,0 +1,508 @@ +{ + "categories": [ + { + "id": "AMBASSADOR", + "presentationName": "Ambassador", + "desc": "Recognizes an individual who has significantly contributed to promoting and expanding the Cardano ecosystem.", + "proposals": [ + { + "id": "E4EB1F0C-427A-4B94-B965-C6F46CA09845", + "presentationName": "Andreas Sosilo", + "x": "https://x.com/andreassosilo", + "linkedin": "", + "url": "" + }, + { + "id": "7D2B42A7-B28B-463D-A673-C992CC462413", + "presentationName": "Mauro Andreoli", + "x": "https://x.com/MauroAndreoliA ", + "linkedin": "", + "url": "" + }, + { + "id": "A50DF74D-C89D-46A1-B90F-DA073CCD5CA3", + "presentationName": "Ha Nguyen", + "x": "https://x.com/Hahero7", + "linkedin": "", + "url": "" + }, + { + "id": "538FBA40-E852-417A-9716-7D794D4BE8DB", + "presentationName": "Felix Weber", + "x": "https://x.com/CatalystSwarm", + "linkedin": "", + "url": "" + }, + { + "id": "21910627-D2E5-4429-89EA-102484C1925D", + "presentationName": "Guillermo Moratorio", + "x": "https://x.com/WoodlandPools", + "linkedin": "", + "url": "" + } + ] + }, + { + "id": "BLOCKCHAIN_FOR_GOOD", + "presentationName": "Blockchain for Good", + "desc": "Recognizes a project or stake pool operator (SPO) doing exceptional environmental or humanitarian work.", + "proposals": [ + { + "id": "72F103B0-5DBB-44F9-BC56-AB991B784FCC", + "presentationName": "Empowa", + "x": "https://x.com/empowa_io", + "linkedin": "https://www.linkedin.com/company/empowa/", + "url": "https://empowa.io/" + }, + { + "id": "2976E410-916C-4564-A158-78E04EE03B4C", + "presentationName": "Socious", + "x": "https://x.com/SociousDAO", + "linkedin": "https://www.linkedin.com/company/socious-io/", + "url": "https://socious.io/" + }, + { + "id": "092C7ED2-40EC-4544-AFDB-BCF2A0B9DE64", + "presentationName": "ADA4Good SPO", + "x": "https://x.com/Ada4goodP", + "linkedin": "", + "url": "https://www.ada4good.com" + }, + { + "id": "C95EF71B-CA9F-40A5-BF9D-789C78EB8D57", + "presentationName": "Landano International", + "x": "https://x.com/landanodapp", + "linkedin": "https://www.linkedin.com/company/landano/", + "url": "https://www.landano.io/" + } + ] + }, + { + "id": "INNOVATION_STANDARDS", + "presentationName": "Innovation & Standards", + "desc": "Honors and expands the former \"Standards (CIPs)\" category to recognize an innovative project pushing the boundaries of what is possible on Cardano.", + "proposals": [ + { + "id": "2E2676E5-5D72-400C-AF66-ED53A957A86E", + "presentationName": "ZKFold", + "x": "https://x.com/zkFold", + "linkedin": "https://www.linkedin.com/company/zkfold/", + "url": "https://zkfold.io/" + }, + { + "id": "923437B0-75BA-4AF0-9EB8-43AABBF696C5", + "presentationName": "Unbox", + "x": "https://x.com/Unbox_Universe", + "linkedin": "https://www.linkedin.com/company/unboxuniverse/", + "url": "https://www.unboxuniverse.com/" + }, + { + "id": "00DAE5F3-ABCF-41A3-9E5B-70B41A2958BC", + "presentationName": "Vacuumlabs", + "x": "https://x.com/vacuumlabs", + "linkedin": "https://www.linkedin.com/company/vacuumlabs/", + "url": "https://vacuumlabs.com/" + }, + { + "id": "0E724D70-BBBD-4310-B6E5-EF2D7F5BF154", + "presentationName": "Harmonic Labs", + "x": "https://x.com/hlabs_tech", + "linkedin": "", + "url": "https://www.harmoniclabs.tech/" + }, + { + "id": "B1727049-7F0F-421C-B1CE-A0CEB29AD189", + "presentationName": "NEWM", + "x": "https://x.com/projectNEWM", + "linkedin": "https://www.linkedin.com/company/projectnewm/", + "url": "https://newm.io/" + }, + { + "id": "5332AD46-5317-4E02-9069-4908F56881E4", + "presentationName": "Anastasia Labs", + "x": "https://x.com/AnastasiaLabs", + "linkedin": "https://www.linkedin.com/company/anastasialabs/", + "url": "https://anastasialabs.com/" + } + ] + }, + { + "id": "DEX", + "presentationName": "DEX", + "desc": "Recognizes a decentralized exchange (DEX) or project that contributes to liquidity provision on Cardano.", + "proposals": [ + { + "id": "882F424F-D628-4C27-B072-076A615C8CB7", + "presentationName": "Minswap", + "x": "https://x.com/MinswapDEX", + "linkedin": "https://www.linkedin.com/company/minswap", + "url": "https://minswap.org/analytics/dao-treasury" + }, + { + "id": "EB3756EE-4DE6-499F-AD97-24AD799A181A", + "presentationName": "Sundae Labs", + "x": "https://x.com/SundaeSwap", + "linkedin": "https://www.linkedin.com/company/sundaeswap-labs/", + "url": "https://sundaeswap.finance" + }, + { + "id": "7225D86F-0AE2-4AEF-8678-19F658794D0A", + "presentationName": "DexHunter", + "x": "https://x.com/DexHunterIO", + "linkedin": "https://www.linkedin.com/company/dexhunter/", + "url": "https://app.dexhunter.io/" + }, + { + "id": "BC20C916-BFAA-4347-8EC6-2B6F1E699EB0", + "presentationName": "WingRiders", + "x": "https://x.com/wingriderscom", + "linkedin": "https://www.linkedin.com/company/wingriders/", + "url": "https://www.wingriders.com/" + }, + { + "id": "AE33E741-1E1A-4681-9B11-0949D511F502", + "presentationName": "Splash Labs Inc.", + "x": "https://x.com/splashprotocol", + "linkedin": "", + "url": "https://www.splash.trade/" + }, + { + "id": "7AEC510B-FA86-472E-8E92-66C8E71F1BED", + "presentationName": "MuesliSwap", + "x": "https://x.com/MuesliSwapTeam", + "linkedin": "", + "url": "https://www.muesliswap.com/" + } + ] + }, + { + "id": "DEVELOPER_TOOLING", + "presentationName": "Developer & Tooling", + "desc": "Recognizes an individual or team and the tools they create to enhance the Cardano development experience.", + "proposals": [ + { + "id": "FB8501FE-0D1C-4D9D-8219-62881819C277", + "presentationName": "MeshJS", + "x": "https://x.com/meshsdk", + "linkedin": "https://www.linkedin.com/company/meshjs", + "url": "https://meshjs.dev/" + }, + { + "id": "040A201A-0E1D-4607-921A-E7FC154DEE0D", + "presentationName": "Aiken", + "x": "https://x.com/aiken_eng", + "linkedin": "", + "url": "https://aiken-lang.org/" + }, + { + "id": "8B260B9A-5928-487B-91B6-21EBE484A956", + "presentationName": "Zhaata - DexHunter", + "x": "https://x.com/ZhaataC", + "linkedin": "", + "url": "https://app.dexhunter.io/" + }, + { + "id": "6A0AEC7B-85AD-45B2-98B8-2E436A718F45", + "presentationName": "Pi Lanningham - SundaeSwap", + "x": "https://x.com/Quantumplation", + "linkedin": "", + "url": "https://www.314pool.com/" + }, + { + "id": "6F4D3D8B-531A-4FB3-AC41-CB0937AFE059", + "presentationName": "Dennis Mittmann - IAMX", + "x": "https://x.com/DennisMittmann", + "linkedin": "", + "url": "https://iamx.id/" + }, + { + "id": "3F10573E-6564-48BB-851F-A40C9BC6E248", + "presentationName": "Raul Padilla - FluidTokens", + "x": "https://x.com/ElRaulito_cnft", + "linkedin": "", + "url": "https://linktr.ee/fluidtokens" + } + ] + }, + { + "id": "EDUCATIONAL_INFLUENCER", + "presentationName": "Educational Influencer", + "desc": "Recognizes an individual or account vastly contributing to blockchain education by creating content that helps others learn about Cardano.", + "proposals": [ + { + "id": "968AEB62-DADD-4D79-B6D6-B2A013982874", + "presentationName": "Lido Nation", + "x": "https://x.com/LidoNation", + "linkedin": "https://www.linkedin.com/company/lido-nation", + "url": "https://lidonation.com" + }, + { + "id": "E3531D81-1F35-42D7-A175-EBC353E0E365", + "presentationName": "Gimbalabs", + "x": "https://x.com/gimbalabs", + "linkedin": "https://www.linkedin.com/company/gimbalabs", + "url": "https://plutuspbl.io/" + }, + { + "id": "C16D8E47-F220-4B51-8024-EA184D867898", + "presentationName": "Farid - Dapp Central", + "x": "https://x.com/dapp_central", + "linkedin": "", + "url": "https://www.dappcentral.net/" + }, + { + "id": "8ED763AF-39BA-4D88-B933-1E3320DB179A", + "presentationName": "Cardano Whale", + "x": "https://x.com/cardano_whale", + "linkedin": "", + "url": "" + }, + { + "id": "2CE60EE3-BF09-45FC-9DE1-970785C14B5F", + "presentationName": "Peter Bui", + "x": "https://x.com/petebui", + "linkedin": "", + "url": "https://linktr.ee/peterbui" + } + ] + }, + { + "id": "NFT_DIGITAL_COLLECTIBLES", + "presentationName": "NFT & Digital Collectibles", + "desc": "Recognizes a project related to non-fungible tokens (NFTs) or digital collectibles, encompassing categories as diverse as marketplaces, art, music, gaming, or others.", + "proposals": [ + { + "id": "EDCB0046-E576-4CD4-965C-D442086B2EC5", + "presentationName": "Hosky", + "x": "https://x.com/hoskytoken", + "linkedin": "", + "url": "https://hosky.io" + }, + { + "id": "2967D6F9-AE00-4B2D-94DA-82CBDE18FC88", + "presentationName": "GOAT Tribe", + "x": "https://x.com/adaGOATS", + "linkedin": "", + "url": "https://www.goattribe.io/" + }, + { + "id": "68E4D3AB-FA69-4DA5-850F-290A8DC21C89", + "presentationName": "OREMOB by OreOreOre", + "x": "https://x.com/ore_times_3", + "linkedin": "", + "url": "https://oremob.io/" + }, + { + "id": "0E39800C-C9AB-4543-B9F8-12BD8ED3808F", + "presentationName": "Clay Nation", + "x": "https://x.com/claymates", + "linkedin": "https://www.linkedin.com/company/claynation/", + "url": "https://www.clayspace.io/" + }, + { + "id": "5CEC4D99-886C-4BE7-B6C8-BEED71682F75", + "presentationName": "Book.io", + "x": "https://x.com/book_io", + "linkedin": "https://www.linkedin.com/company/book-io/", + "url": "https://book.io/" + } + ] + }, + { + "id": "INFRASTRUCTURE_PLATFORM", + "presentationName": "Infrastructure Platform", + "desc": "Recognizes an essential infrastructure project that supports the Cardano ecosystem.", + "proposals": [ + { + "id": "86A61885-556E-4320-AD16-79D87793544A", + "presentationName": "Demeter by TxPipe", + "x": "https://x.com/DemeterRun", + "linkedin": "", + "url": "https://demeter.run/" + }, + { + "id": "6C6092EB-D04D-411F-BDDE-87DDF71C375A", + "presentationName": "Maestro", + "x": "https://x.com/GoMaestroOrg", + "linkedin": "https://www.linkedin.com/company/gomaestro/", + "url": "https://www.gomaestro.org/" + }, + { + "id": "49C9BFC5-70F1-43AB-AC7F-15265F6A20F5", + "presentationName": "Blockfrost", + "x": "https://x.com/blockfrost_io", + "linkedin": "", + "url": "https://blockfrost.io/" + }, + { + "id": "CFBDA700-ABB9-420C-9828-798A9BC7B6A9", + "presentationName": "Ada Anvil", + "x": "https://x.com/ada_anvil", + "linkedin": "https://www.linkedin.com/company/anvil-development-agency-inc/about/", + "url": "https://ada-anvil.io/" + }, + { + "id": "85D2E32F-BD87-4A8A-9736-46E9A64EEC8A", + "presentationName": "dcSpark", + "x": "https://x.com/dcspark_io", + "linkedin": "https://www.linkedin.com/company/dcspark/", + "url": "https://www.dcspark.io" + }, + { + "id": "C975CC1D-28A3-47F1-8FFE-9E0C897079DA", + "presentationName": "NMKR", + "x": "https://x.com/nmkr_io", + "linkedin": "https://www.linkedin.com/company/nmkrio/", + "url": "https://www.nmkr.io/" + } + ] + }, + { + "id": "DAO_TOOLING_GOVERNANCE", + "presentationName": "DAO Tooling & Governance", + "desc": "Recognizes a decentralized autonomous organization (DAO) for the importance of their tooling and governance solutions within the Cardano ecosystem.", + "proposals": [ + { + "id": "64C38356-E099-40CF-9F5D-30EED973B89A", + "presentationName": "Clarity", + "x": "https://x.com/Clarity_DAO", + "linkedin": "https://www.linkedin.com/company/clarityprotocol/", + "url": "https://www.clarity.vote/" + }, + { + "id": "55C832E9-C3EE-4029-8DC2-F73C29379316", + "presentationName": "Liqwid", + "x": "https://x.com/liqwidfinance", + "linkedin": "https://www.linkedin.com/company/liqwid-labs/", + "url": "https://liqwid.finance/" + }, + { + "id": "B18E350C-D561-41D9-9C72-C42C7BE6DB8D", + "presentationName": "Summon Platform", + "x": "https://x.com/SummonPlatform", + "linkedin": "https://www.linkedin.com/company/summonplatform/", + "url": "https://summonplatform.io/" + }, + { + "id": "D791ECA4-19C2-45B9-9A3F-DEE509D5ADA0", + "presentationName": "Minswap", + "x": "https://x.com/MinswapDEX", + "linkedin": "https://www.linkedin.com/company/minswap", + "url": "https://minswap.org/" + }, + { + "id": "36384CB0-3517-483D-8AA5-AA8F9E51159B", + "presentationName": "Indigo", + "x": "https://x.com/Indigo_protocol", + "linkedin": "https://www.linkedin.com/company/indigolabsinc/", + "url": "https://indigoprotocol.io" + }, + { + "id": "ECFA5481-3B31-4DAE-B3B7-145ED3525FEE", + "presentationName": "Optim Finance", + "x": "https://x.com/optimfi", + "linkedin": "", + "url": "https://optim.finance/" + } + ] + }, + { + "id": "DEFI", + "presentationName": "DeFi Platform", + "desc": "Specifically recognizes a decentralized finance (DeFi) project offering superlative solutions in the form of lending platforms, yield aggregators, or other options.", + "proposals": [ + { + "id": "A37B8E3B-99ED-4596-9349-E31015DCFA43", + "presentationName": "Indigo", + "x": "https://x.com/Indigo_protocol", + "linkedin": "https://www.linkedin.com/company/indigolabsinc/", + "url": "https://indigoprotocol.io" + }, + { + "id": "FDB103DC-EA4F-48BD-85EF-877F21C3FEC8", + "presentationName": "Lenfi", + "x": "https://x.com/LenfiOfficial", + "linkedin": "https://www.linkedin.com/company/lenfi/", + "url": "https://lenfi.io/" + }, + { + "id": "9E1B84B1-1F5F-4CD9-92A5-264E30C69BA9", + "presentationName": "Liqwid", + "x": "https://x.com/liqwidfinance", + "linkedin": "https://www.linkedin.com/company/liqwid-labs/", + "url": "https://liqwid.finance/" + }, + { + "id": "5DF10439-EDBE-4AD3-A033-833906220AD8", + "presentationName": "Optim Finance", + "x": "https://x.com/optimfi", + "linkedin": "", + "url": "https://dao.optim.finance/" + }, + { + "id": "26364D11-DD33-42FA-8AA9-48DB223B660E", + "presentationName": "VyFinance", + "x": "https://x.com/VyFiOfficial", + "linkedin": "https://www.linkedin.com/company/vyfinance/", + "url": "https://app.vyfi.io/" + }, + { + "id": "B1F3D716-D10F-43F2-9462-D5D1445324E6", + "presentationName": "FluidTokens", + "x": "https://x.com/FluidTokens", + "linkedin": "https://www.linkedin.com/company/fluidtokens/", + "url": "https://fluidtokens.com/" + } + ] + }, + { + "id": "SSPO", + "presentationName": "SSPO", + "desc": "Recognizes the remarkable work done by a single stake pool operator (SSPO) and their vital contributions to the Cardano ecosystem.", + "proposals": [ + { + "id": "10C26123-3E3F-4503-861B-3EE0E3ADA21A", + "presentationName": "PRIDE", + "x": "https://x.com/StakeWithPride", + "linkedin": "", + "url": "https://www.stakewithpride.org" + }, + { + "id": "5C1F4FE1-8126-40BB-9C65-CBB9286CC1F0", + "presentationName": "Cardanistas", + "x": "https://x.com/cardanistas", + "linkedin": "", + "url": "https://www.youtube.com/cardanistas" + }, + { + "id": "42BBA522-2222-4671-930A-59FC5F1940CB", + "presentationName": "Malu", + "x": "https://x.com/Cryptofly777", + "linkedin": "", + "url": "https://www.youtube.com/channel/UCP_US45irp84HHE1j5E8SCg" + }, + { + "id": "F34DAB95-603B-4F92-A70C-48016C13D28F", + "presentationName": "Vietnam Cardano Community", + "x": "https://x.com/VietnamCardano", + "linkedin": "", + "url": "https://linktr.ee/cardanoadavietnam" + }, + { + "id": "A3CA5296-A6B3-45BF-B047-11D685B1DDEF", + "presentationName": "Dapp Central", + "x": "https://x.com/dapp_central", + "linkedin": "", + "url": "https://www.dappcentral.net/" + }, + { + "id": "D7C37AFD-13A7-4AE3-8312-3888F9A3DB77", + "presentationName": "DRMZ Pool", + "x": "https://x.com/rodg_drmz", + "linkedin": "", + "url": "https://www.web3ineducation.com/" + } + ] + } + ] +} \ No newline at end of file diff --git a/ui/summit-2024/src/common/resources/data/summit2024PreProdContent.json b/ui/summit-2024/src/common/resources/data/summit2024PreProdContent.json new file mode 100644 index 000000000..8068aa0ed --- /dev/null +++ b/ui/summit-2024/src/common/resources/data/summit2024PreProdContent.json @@ -0,0 +1,508 @@ +{ + "categories": [ + { + "id": "AMBASSADOR", + "presentationName": "Ambassador", + "desc": "Recognizes an individual who has significantly contributed to promoting and expanding the Cardano ecosystem.", + "proposals": [ + { + "id": "EC30C7AA-70BB-4036-A353-7C306FF31613", + "presentationName": "Andreas Sosilo", + "x": "https://x.com/andreassosilo", + "linkedin": "", + "url": "" + }, + { + "id": "4582D74F-81D0-4F28-8B18-E70E2A346813", + "presentationName": "Mauro Andreoli", + "x": "https://x.com/MauroAndreoliA ", + "linkedin": "", + "url": "" + }, + { + "id": "7FA87520-0AD7-4797-83C7-4C7625AA58AA", + "presentationName": "Ha Nguyen", + "x": "https://x.com/Hahero7", + "linkedin": "", + "url": "" + }, + { + "id": "135DF392-D603-41B1-8BE1-C103E23816AD", + "presentationName": "Felix Weber", + "x": "https://x.com/CatalystSwarm", + "linkedin": "", + "url": "" + }, + { + "id": "AA8C39C4-0795-4F88-A867-3019098D5289", + "presentationName": "Guillermo Moratorio", + "x": "https://x.com/WoodlandPools", + "linkedin": "", + "url": "" + } + ] + }, + { + "id": "BLOCKCHAIN_FOR_GOOD", + "presentationName": "Blockchain for Good", + "desc": "Recognizes a project or stake pool operator (SPO) doing exceptional environmental or humanitarian work.", + "proposals": [ + { + "id": "150C277B-22D7-47F4-B626-AA57C338C479", + "presentationName": "Empowa", + "x": "https://x.com/empowa_io", + "linkedin": "https://www.linkedin.com/company/empowa/", + "url": "https://empowa.io/" + }, + { + "id": "7BB2EF27-9BED-4A24-A79B-C79F530D27D2", + "presentationName": "Socious", + "x": "https://x.com/SociousDAO", + "linkedin": "https://www.linkedin.com/company/socious-io/", + "url": "socious.io" + }, + { + "id": "7B9888E9-E5BD-42A1-BCF2-E997FE878E8A", + "presentationName": "ADA4Good SPO", + "x": "https://x.com/Ada4goodP", + "linkedin": "", + "url": "https://www.ada4good.com" + }, + { + "id": "FD858845-4C44-423C-9898-62836D246E83", + "presentationName": "Landano International", + "x": "https://x.com/landanodapp", + "linkedin": "https://www.linkedin.com/company/landano/", + "url": "https://www.landano.io/" + } + ] + }, + { + "id": "INNOVATION_STANDARDS", + "presentationName": "Innovation & Standards", + "desc": "Honors and expands the former \"Standards (CIPs)\" category to recognize an innovative project pushing the boundaries of what is possible on Cardano.", + "proposals": [ + { + "id": "5D72F48B-8285-4BAC-8846-595B455A3084", + "presentationName": "ZKFold", + "x": "https://x.com/zkFold", + "linkedin": "https://www.linkedin.com/company/zkfold/", + "url": "https://zkfold.io/" + }, + { + "id": "BAD03444-C2EF-4E76-B502-807A69713F19", + "presentationName": "Unbox", + "x": "https://x.com/Unbox_Universe", + "linkedin": "https://www.linkedin.com/company/unboxuniverse/", + "url": "https://www.unboxuniverse.com/" + }, + { + "id": "AC5A90ED-A2C0-4775-86B4-C47EC39A083B", + "presentationName": "Vacuumlabs", + "x": "https://x.com/vacuumlabs", + "linkedin": "https://www.linkedin.com/company/vacuumlabs/", + "url": "https://vacuumlabs.com/" + }, + { + "id": "6D538F80-F617-402F-9F6C-0A2E7260D33E", + "presentationName": "Harmonic Labs", + "x": "https://x.com/hlabs_tech", + "linkedin": "", + "url": "https://www.harmoniclabs.tech/" + }, + { + "id": "F4192CB9-76B8-4C95-96A8-7B1822625877", + "presentationName": "NEWM", + "x": "https://x.com/projectNEWM", + "linkedin": "https://www.linkedin.com/company/projectnewm/", + "url": "https://newm.io/" + }, + { + "id": "23D9536B-0ECE-4FE5-90D4-FBF05F7A85DD", + "presentationName": "Anastasia Labs", + "x": "https://x.com/AnastasiaLabs", + "linkedin": "https://www.linkedin.com/company/anastasialabs/", + "url": "https://anastasialabs.com/" + } + ] + }, + { + "id": "DEX", + "presentationName": "DEX", + "desc": "Recognizes a decentralized exchange (DEX) or project that contributes to liquidity provision on Cardano.", + "proposals": [ + { + "id": "CFA2481E-937C-433E-98BD-1AAC9F0FA2B9", + "presentationName": "Minswap", + "x": "https://x.com/MinswapDEX", + "linkedin": "https://www.linkedin.com/company/minswap", + "url": "https://minswap.org/analytics/dao-treasury" + }, + { + "id": "74404AF7-466B-4679-977C-5F5BDE6446CD", + "presentationName": "Sundae Labs", + "x": "https://x.com/SundaeSwap", + "linkedin": "https://www.linkedin.com/company/sundaeswap-labs/", + "url": "https://sundaeswap.finance" + }, + { + "id": "AD29AA23-00CA-4A4F-B08C-7D4FC8CE69E9", + "presentationName": "DexHunter", + "x": "https://x.com/DexHunterIO", + "linkedin": "https://www.linkedin.com/company/dexhunter/", + "url": "https://app.dexhunter.io/" + }, + { + "id": "58806B5D-4AD5-4C76-9CF4-2C90D068F6BA", + "presentationName": "WingRiders", + "x": "https://x.com/wingriderscom", + "linkedin": "https://www.linkedin.com/company/wingriders/", + "url": "https://www.wingriders.com/" + }, + { + "id": "802936AE-8114-4EB9-B1D9-C1780811CCBC", + "presentationName": "Splash Labs Inc.", + "x": "https://x.com/splashprotocol", + "linkedin": "", + "url": "https://www.splash.trade/" + }, + { + "id": "C3897902-424D-4F42-939C-9C37DE7137C5", + "presentationName": "MuesliSwap", + "x": "https://x.com/MuesliSwapTeam", + "linkedin": "", + "url": "https://www.muesliswap.com/" + } + ] + }, + { + "id": "DEVELOPER_TOOLING", + "presentationName": "Developer & Tooling", + "desc": "Recognizes an individual or team and the tools they create to enhance the Cardano development experience.", + "proposals": [ + { + "id": "865CE0B4-ABF9-4700-A53A-C8E84AD53995", + "presentationName": "MeshJS", + "x": "https://x.com/meshsdk", + "linkedin": "https://www.linkedin.com/company/meshjs", + "url": "https://meshjs.dev/" + }, + { + "id": "E6DBAF3C-5DA1-4AEA-9A14-FA81C14CF3C3", + "presentationName": "Aiken", + "x": "https://x.com/aiken_eng", + "linkedin": "", + "url": "https://aiken-lang.org/" + }, + { + "id": "3DE5D697-2FF5-4197-9694-78B35EA1CCC3", + "presentationName": "Zhaata - DexHunter", + "x": "https://x.com/ZhaataC", + "linkedin": "", + "url": "https://app.dexhunter.io/" + }, + { + "id": "6F604592-9908-45B3-BCE2-5C63F183AFFF", + "presentationName": "Pi Lanningham - SundaeSwap", + "x": "https://x.com/Quantumplation", + "linkedin": "", + "url": "https://www.314pool.com/" + }, + { + "id": "7CB41A46-AC83-4F5F-B9D2-DC023209D8EE", + "presentationName": "Dennis Mittmann - IAMX", + "x": "https://x.com/DennisMittmann", + "linkedin": "", + "url": "https://iamx.id/" + }, + { + "id": "86A96F3F-FE17-42E1-9415-9A02DFEFAF66", + "presentationName": "Raul Padilla - FluidTokens", + "x": "https://x.com/ElRaulito_cnft", + "linkedin": "", + "url": "https://linktr.ee/fluidtokens" + } + ] + }, + { + "id": "EDUCATIONAL_INFLUENCER", + "presentationName": "Educational Influencer", + "desc": "Recognizes an individual or account vastly contributing to blockchain education by creating content that helps others learn about Cardano.", + "proposals": [ + { + "id": "46187CD7-07BD-4B6B-B225-DECA839E87C4", + "presentationName": "Lido Nation", + "x": "https://x.com/LidoNation", + "linkedin": "https://www.linkedin.com/company/lido-nation", + "url": "https://lidonation.com" + }, + { + "id": "9605C945-845B-4616-B98C-3807263187F7", + "presentationName": "Gimbalabs", + "x": "https://x.com/gimbalabs", + "linkedin": "https://www.linkedin.com/company/gimbalabs", + "url": "https://plutuspbl.io/" + }, + { + "id": "E81164D1-550A-47B4-8726-2412E4DF42D0", + "presentationName": "Farid - Dapp Central", + "x": "https://x.com/dapp_central", + "linkedin": "", + "url": "https://www.dappcentral.net/" + }, + { + "id": "9DFF4520-79C1-491A-95F1-C79B583355D7", + "presentationName": "Cardano Whale", + "x": "https://x.com/cardano_whale", + "linkedin": "", + "url": "" + }, + { + "id": "BBCE94A0-3FDB-4EDA-B0B7-EE256E8B157F", + "presentationName": "Peter Bui", + "x": "https://x.com/petebui", + "linkedin": "", + "url": "https://linktr.ee/peterbui" + } + ] + }, + { + "id": "NFT_DIGITAL_COLLECTIBLES", + "presentationName": "NFT & Digital Collectibles", + "desc": "Recognizes a project related to non-fungible tokens (NFTs) or digital collectibles, encompassing categories as diverse as marketplaces, art, music, gaming, or others.", + "proposals": [ + { + "id": "A31E6D75-2E84-4862-8032-B2BF7BB1887D", + "presentationName": "Hosky", + "x": "https://x.com/hoskytoken", + "linkedin": "", + "url": "https://hosky.io" + }, + { + "id": "7D6EF0EB-8067-4B3A-84F4-1947029E9700", + "presentationName": "GOAT Tribe", + "x": "https://x.com/adaGOATS", + "linkedin": "", + "url": "https://www.goattribe.io/" + }, + { + "id": "B70BD431-3C20-4680-8F79-D3A049CD154B", + "presentationName": "OREMOB by OreOreOre", + "x": "https://x.com/ore_times_3", + "linkedin": "", + "url": "https://oremob.io/" + }, + { + "id": "23B353B8-AB9E-46EA-B8E3-3F7B88E2E461", + "presentationName": "Clay Nation", + "x": "https://x.com/claymates", + "linkedin": "https://www.linkedin.com/company/claynation/", + "url": "https://www.clayspace.io/" + }, + { + "id": "B3BF6FBB-2838-4C39-850D-19F40E19F1AD", + "presentationName": "Book.io", + "x": "https://x.com/book_io", + "linkedin": "https://www.linkedin.com/company/book-io/", + "url": "https://book.io/" + } + ] + }, + { + "id": "INFRASTRUCTURE_PLATFORM", + "presentationName": "Infrastructure Platform", + "desc": "Recognizes an essential infrastructure project that supports the Cardano ecosystem.", + "proposals": [ + { + "id": "4C43B867-335E-4B75-A06F-D90F675D6B7C", + "presentationName": "Demeter by TxPipe", + "x": "https://x.com/DemeterRun", + "linkedin": "", + "url": "https://demeter.run/" + }, + { + "id": "C9C434CA-8DC8-462A-8E4D-1200B388F585", + "presentationName": "Maestro", + "x": "https://x.com/GoMaestroOrg", + "linkedin": "https://www.linkedin.com/company/gomaestro/", + "url": "https://www.gomaestro.org/" + }, + { + "id": "70C455C2-7DCF-48A0-92F1-61B88B8EB0CE", + "presentationName": "Blockfrost", + "x": "https://x.com/blockfrost_io", + "linkedin": "", + "url": "https://blockfrost.io/" + }, + { + "id": "7B5CB884-74E5-4B01-A591-4FAA5768F143", + "presentationName": "Ada Anvil", + "x": "https://x.com/ada_anvil", + "linkedin": "https://www.linkedin.com/company/anvil-development-agency-inc/about/", + "url": "https://ada-anvil.io/" + }, + { + "id": "A4D294D8-C40D-401C-949C-6DBCD0627759", + "presentationName": "dcSpark", + "x": "https://x.com/dcspark_io", + "linkedin": "https://www.linkedin.com/company/dcspark/", + "url": "https://www.dcspark.io" + }, + { + "id": "6B26B538-9E16-4EA5-8AA2-0120FDBA30F2", + "presentationName": "NMKR", + "x": "https://x.com/nmkr_io", + "linkedin": "https://www.linkedin.com/company/nmkrio/", + "url": "https://www.nmkr.io/" + } + ] + }, + { + "id": "DAO_TOOLING_GOVERNANCE", + "presentationName": "DAO Tooling & Governance", + "desc": "Recognizes a decentralized autonomous organization (DAO) for the importance of their tooling and governance solutions within the Cardano ecosystem.", + "proposals": [ + { + "id": "4D831658-C079-4A99-B08C-6180F18652A1", + "presentationName": "Clarity", + "x": "https://x.com/Clarity_DAO", + "linkedin": "https://www.linkedin.com/company/clarityprotocol/", + "url": "https://www.clarity.vote/" + }, + { + "id": "1846F0B9-1616-43B2-A919-CF70AEDAB356", + "presentationName": "Liqwid", + "x": "https://x.com/liqwidfinance", + "linkedin": "https://www.linkedin.com/company/liqwid-labs/", + "url": "https://liqwid.finance/" + }, + { + "id": "0D81F908-2C90-4811-A4DE-ED2E61302AE1", + "presentationName": "Summon Platform", + "x": "https://x.com/SummonPlatform", + "linkedin": "https://www.linkedin.com/company/summonplatform/", + "url": "https://summonplatform.io/" + }, + { + "id": "5278A5CC-AF3D-41A4-837E-10DFDD0CAB63", + "presentationName": "Minswap", + "x": "https://x.com/MinswapDEX", + "linkedin": "https://www.linkedin.com/company/minswap", + "url": "https://minswap.org/" + }, + { + "id": "D1C06574-6619-4563-BD6B-6FE297A3E777", + "presentationName": "Indigo", + "x": "https://x.com/Indigo_protocol", + "linkedin": "https://www.linkedin.com/company/indigolabsinc/", + "url": "https://indigoprotocol.io" + }, + { + "id": "1A75AED2-2A10-4558-9BE2-31F559242A43", + "presentationName": "Optim Finance", + "x": "https://x.com/optimfi", + "linkedin": "", + "url": "https://dao.optim.finance/" + } + ] + }, + { + "id": "DEFI", + "presentationName": "DeFi Platform", + "desc": "Specifically recognizes a decentralized finance (DeFi) project offering superlative solutions in the form of lending platforms, yield aggregators, or other options.", + "proposals": [ + { + "id": "8FE32CE4-8B75-4815-BDE5-DCC197945671", + "presentationName": "Indigo", + "x": "https://x.com/Indigo_protocol", + "linkedin": "https://www.linkedin.com/company/indigolabsinc/", + "url": "https://indigoprotocol.io" + }, + { + "id": "00CE32E2-60B3-4052-9C83-8A1547C10A63", + "presentationName": "Lenfi", + "x": "https://x.com/LenfiOfficial", + "linkedin": "https://www.linkedin.com/company/lenfi/", + "url": "https://lenfi.io/" + }, + { + "id": "648E39DB-AB35-4072-9BAD-E39450961DD8", + "presentationName": "Liqwid", + "x": "https://x.com/liqwidfinance", + "linkedin": "https://www.linkedin.com/company/liqwid-labs/", + "url": "https://liqwid.finance/" + }, + { + "id": "33D87F23-7D57-41B2-86E3-D5C91BFAEC35", + "presentationName": "Optim Finance", + "x": "https://x.com/optimfi", + "linkedin": "", + "url": "https://dao.optim.finance/" + }, + { + "id": "D4C62649-9CC3-4288-9C21-62EA8F5899F1", + "presentationName": "VyFinance", + "x": "https://x.com/VyFiOfficial", + "linkedin": "https://www.linkedin.com/company/vyfinance/", + "url": "https://app.vyfi.io/" + }, + { + "id": "EE456364-EA59-4F06-BC37-922572B79404", + "presentationName": "FluidTokens", + "x": "https://x.com/FluidTokens", + "linkedin": "https://www.linkedin.com/company/fluidtokens/", + "url": "https://fluidtokens.com/" + } + ] + }, + { + "id": "SSPO", + "presentationName": "SSPO", + "desc": "Recognizes the remarkable work done by a single stake pool operator (SSPO) and their vital contributions to the Cardano ecosystem.", + "proposals": [ + { + "id": "470FFE2B-18B6-4ACD-8DBE-0E8B7B2B761C", + "presentationName": "PRIDE", + "x": "https://x.com/StakeWithPride", + "linkedin": "", + "url": "https://www.stakewithpride.org" + }, + { + "id": "B56B2068-7FC6-4ECB-B339-8BB6F1DCA6F9", + "presentationName": "Cardanistas", + "x": "https://x.com/cardanistas", + "linkedin": "", + "url": "https://www.youtube.com/cardanistas" + }, + { + "id": "3FB24A88-424F-4D73-A96C-617533D7DB1C", + "presentationName": "Malu", + "x": "https://x.com/Cryptofly777", + "linkedin": "", + "url": "https://www.youtube.com/channel/UCP_US45irp84HHE1j5E8SCg" + }, + { + "id": "A19FF565-895D-46A4-A86D-2FF16FE796E2", + "presentationName": "Vietnam Cardano Community", + "x": "https://x.com/VietnamCardano", + "linkedin": "", + "url": "https://linktr.ee/cardanoadavietnam" + }, + { + "id": "82A8C4C1-0B86-4682-A9B0-6B88E75AC0E7", + "presentationName": "Dapp Central", + "x": "https://x.com/dapp_central", + "linkedin": "", + "url": "https://www.dappcentral.net/" + }, + { + "id": "D83833A3-9F99-4859-B0B7-545E3827FD09", + "presentationName": "DRMZ Pool", + "x": "https://x.com/rodg_drmz", + "linkedin": "", + "url": "https://www.web3ineducation.com/" + } + ] + } + ] +} \ No newline at end of file diff --git a/ui/summit-2024/src/common/resources/data/termsAndConditions.json b/ui/summit-2024/src/common/resources/data/termsAndConditions.json index f94be280c..96b006e5a 100644 --- a/ui/summit-2024/src/common/resources/data/termsAndConditions.json +++ b/ui/summit-2024/src/common/resources/data/termsAndConditions.json @@ -1,6 +1,6 @@ { "title": "Terms and Conditions", - "date": "Last updated: August, 2023", + "date": "Last updated: August, 2024", "sections": [ { "title": "Interpretation and Definitions", @@ -13,7 +13,7 @@ "title": "Definitions", "content": ["For the purposes of these Terms and Conditions:"], "definitions": { - "Website": "Website means the server provided by the Company to You on any electronic device, named Cardano Summit 2023.", + "Website": "Website means the server provided by the Company to You on any electronic device, named Cardano Summit 2024.", "Country": "Country refers to Switzerland.", "Company": "Company refers to Cardano Foundation, Dammstrasse 16, 6300 Zug, Switzerland, and is referred to as either 'the Company,' 'We,' 'Us,' or 'Our' in this Agreement.", "Device": "Device means any device that can access the Service such as a computer, a mobile phone, or a digital tablet.", @@ -133,7 +133,7 @@ "list": [ { "number": "2.1", - "content": ["2.1 The Web Application is a platform that offers a verified e-voting platform. The Web Application Cardano Ballot is only being made available for the Cardano Summit 2023 as is designed only for the Summit use case. This is a platform where users can choose between two methods of registration and authentication. One method is via email and SMS, the other is via a Cardano Wallet and Discord bot. (User Discord account required). The site provides a facility for users to generate ballots and submit votes for the Cardano Summit 2023. The process will be anonymously recorded on the Cardano blockchain."] + "content": ["2.1 The Web Application is a platform that offers a verified e-voting platform. The Web Application Cardano Ballot is only being made available for the Cardano Summit 2024 as is designed only for the Summit use case. This is a platform where users can choose between two methods of registration and authentication. One method is via email and SMS, the other is via a Cardano Wallet and Discord bot. (User Discord account required). The site provides a facility for users to generate ballots and submit votes for the Cardano Summit 2024. The process will be anonymously recorded on the Cardano blockchain."] }, { "number": "2.2", diff --git a/ui/summit-2024/src/common/resources/images/leaderboard11.svg b/ui/summit-2024/src/common/resources/images/leaderboard11.svg new file mode 100644 index 000000000..a8e63128e --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/leaderboard11.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/eternl.svg b/ui/summit-2024/src/common/resources/images/wallets/eternl.svg new file mode 100644 index 000000000..d6587c468 --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/eternl.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/flint.svg b/ui/summit-2024/src/common/resources/images/wallets/flint.svg new file mode 100644 index 000000000..b8616f339 --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/flint.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/gero.svg b/ui/summit-2024/src/common/resources/images/wallets/gero.svg new file mode 100644 index 000000000..96be4aa43 --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/gero.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/idw.svg b/ui/summit-2024/src/common/resources/images/wallets/idw.svg new file mode 100644 index 000000000..35cea0d8e --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/idw.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/lace.svg b/ui/summit-2024/src/common/resources/images/wallets/lace.svg new file mode 100644 index 000000000..93d65b50e --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/lace.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/nami.svg b/ui/summit-2024/src/common/resources/images/wallets/nami.svg new file mode 100644 index 000000000..0eaf74c6e --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/nami.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/nufi.svg b/ui/summit-2024/src/common/resources/images/wallets/nufi.svg new file mode 100644 index 000000000..fa9636d2b --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/nufi.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/typhon.svg b/ui/summit-2024/src/common/resources/images/wallets/typhon.svg new file mode 100644 index 000000000..1d13bab79 --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/typhon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/summit-2024/src/common/resources/images/wallets/yoroi.svg b/ui/summit-2024/src/common/resources/images/wallets/yoroi.svg new file mode 100644 index 000000000..0f1a1de55 --- /dev/null +++ b/ui/summit-2024/src/common/resources/images/wallets/yoroi.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/summit-2024/src/components/AnimatedSwitch/AnimatedSwitch.tsx b/ui/summit-2024/src/components/AnimatedSwitch/AnimatedSwitch.tsx index bc2ea2cfd..36b4dc576 100644 --- a/ui/summit-2024/src/components/AnimatedSwitch/AnimatedSwitch.tsx +++ b/ui/summit-2024/src/components/AnimatedSwitch/AnimatedSwitch.tsx @@ -1,9 +1,10 @@ import React, { useState } from "react"; import { Box, Typography, styled } from "@mui/material"; -import theme from "../../common/styles/theme"; interface AnimatedSwitchProps { defaultValue: string; + optionA: string; + optionB: string; onClickOption: (option: string) => void; } @@ -15,12 +16,21 @@ const SwitchContainer = styled(Box)(({ theme }) => ({ cursor: "pointer", userSelect: "none", position: "relative", - width: "388px", + width: "100%", + maxWidth: "388px", height: "48px", background: theme.palette.background.neutralDark, + "@media (max-width: 600px)": { + height: "40px", + maxWidth: "300px", + }, + "@media (max-width: 400px)": { + height: "32px", + maxWidth: "250px", + }, })); -const Option = styled(Box)<{ selected: boolean }>(() => ({ +const Option = styled(Box)<{ selected: boolean }>(({ theme, selected }) => ({ flex: 1, padding: "12px 24px", textAlign: "center", @@ -30,10 +40,19 @@ const Option = styled(Box)<{ selected: boolean }>(() => ({ display: "flex", alignItems: "center", justifyContent: "center", + color: selected ? theme.palette.background.default : "inherit", + "@media (max-width: 600px)": { + padding: "8px 16px", + fontSize: "14px", + }, + "@media (max-width: 400px)": { + padding: "6px 12px", + fontSize: "12px", + }, })); const AnimatedRectangle = styled(Box)<{ selected: boolean }>( - ({ selected }) => ({ + ({ theme, selected }) => ({ position: "absolute", top: 0, left: 0, @@ -48,6 +67,8 @@ const AnimatedRectangle = styled(Box)<{ selected: boolean }>( const AnimatedSwitch: React.FC = ({ defaultValue, + optionA, + optionB, onClickOption, }) => { const [selected, setSelected] = useState(defaultValue); @@ -59,10 +80,10 @@ const AnimatedSwitch: React.FC = ({ return ( - + diff --git a/ui/summit-2024/src/components/AppWrapper/AppWrapper.tsx b/ui/summit-2024/src/components/AppWrapper/AppWrapper.tsx index 70fa3f9db..90b34fa53 100644 --- a/ui/summit-2024/src/components/AppWrapper/AppWrapper.tsx +++ b/ui/summit-2024/src/components/AppWrapper/AppWrapper.tsx @@ -1,7 +1,7 @@ import { ReactNode, useEffect } from "react"; import { useAppDispatch, useAppSelector } from "../../store/hooks"; import { env } from "../../common/constants/env"; -import { setEventCache } from "../../store/reducers/eventCache"; +import { getEventCache, setEventCache } from "../../store/reducers/eventCache"; import { getEventData } from "../../common/api/eventDataService"; import { eventBus, EventName } from "../../utils/EventBus"; import { eventDataFixture } from "../../__fixtures__/event"; @@ -9,14 +9,29 @@ import { ToastType } from "../common/Toast/Toast.types"; import { getIsVerified } from "../../common/api/verificationService"; import { getConnectedWallet, + getWalletIsVerified, setConnectedWallet, setWalletIsVerified, } from "../../store/reducers/userCache"; import { useCardano } from "@cardano-foundation/cardano-connect-with-wallet"; import { resolveCardanoNetwork } from "../../utils/utils"; +import { getUserInSession, tokenIsExpired } from "../../utils/session"; +import { + getVoteReceipts, + submitGetUserVotes, +} from "../../common/api/voteService"; +import { setVoteReceipts, setVotes } from "../../store/reducers/votesCache"; +import { parseError } from "../../common/constants/errors"; +import event2024PreProdExtended from "../../common/resources/data/summit2024PreProdContent.json"; +import event2024MainnetExtended from "../../common/resources/data/summit2024MainnetContent.json"; +import { NetworkType } from "../ConnectWalletList/ConnectWalletList.types"; const AppWrapper = (props: { children: ReactNode }) => { const dispatch = useAppDispatch(); + const session = getUserInSession(); + const isExpired = tokenIsExpired(session?.expiresAt); + const eventCache = useAppSelector(getEventCache); + const walletIsVerified = useAppSelector(getWalletIsVerified); const connectedWallet = useAppSelector(getConnectedWallet); const { stakeAddress, enabledWallet } = useCardano({ limitNetwork: resolveCardanoNetwork(env.TARGET_NETWORK), @@ -26,13 +41,43 @@ const AppWrapper = (props: { children: ReactNode }) => { initApp(); }, []); + useEffect(() => { + const updateUserVotes = async () => { + submitGetUserVotes(session.accessToken) + .then((response) => { + if (Array.isArray(response) && response.length) { + dispatch(setVotes(response)); + getVoteReceipts(session.accessToken).then((receipts) => { + // @ts-ignore + dispatch(setVoteReceipts(receipts)); + }); + } + }) + .catch((e) => { + if (process.env.NODE_ENV === "development") { + console.log(`Failed to fetch user votes, ${parseError(e.message)}`); + } + }); + }; + + const walletIsConnected = connectedWallet.address.length; + if (walletIsConnected && !isExpired) { + updateUserVotes(); + } + }, [connectedWallet.address, walletIsVerified, isExpired]); + useEffect(() => { const checkWalletVerification = async () => { const isVerifiedResult = await getIsVerified(connectedWallet.address); // @ts-ignore - if (!isVerifiedResult?.error) { + if (isVerifiedResult?.verified) { // @ts-ignore dispatch(setWalletIsVerified(isVerifiedResult.verified)); + if (!session || tokenIsExpired(session?.expiresAt)) { + eventBus.publish(EventName.OpenLoginModal); + } + } else if (eventCache.active) { + eventBus.publish(EventName.OpenVerifyWalletModal); } }; if (connectedWallet.address?.length) { @@ -53,15 +98,64 @@ const AppWrapper = (props: { children: ReactNode }) => { } }, [stakeAddress, connectedWallet.address]); + const mergeEventData = (eventData, staticData) => { + const mergedCategories = eventData.categories.map((category) => { + const staticCategory = staticData.categories.find( + (cat) => cat.id === category.id, + ); + + if (staticCategory) { + const mergedProposals = category.proposals.map((proposal) => { + const staticProposal = staticCategory.proposals.find( + (p) => p.id === proposal.id, + ); + + if (staticProposal) { + // TODO: update reducer types + return { + ...proposal, + name: staticProposal.presentationName || proposal.name, + x: staticProposal.x || null, + linkedin: staticProposal.linkedin || null, + url: staticProposal.url || null, + }; + } + return proposal; + }); + + return { + ...category, + name: staticCategory.presentationName?.length + ? staticCategory.presentationName + : category.id, + desc: staticCategory.desc, + proposals: mergedProposals, + }; + } + return category; + }); + + return { + ...eventData, + categories: mergedCategories, + }; + }; + const initApp = async () => { if (env.USING_FIXTURES) { dispatch(setEventCache(eventDataFixture)); } else { const eventData = await getEventData(env.EVENT_ID); + const eventDataExtended = + resolveCardanoNetwork(env.TARGET_NETWORK) == NetworkType.MAINNET + ? event2024MainnetExtended + : event2024PreProdExtended; + // @ts-ignore if (!eventData?.error) { + const mergedEventData = mergeEventData(eventData, eventDataExtended); // @ts-ignore - dispatch(setEventCache(eventData)); + dispatch(setEventCache(mergedEventData)); } else { eventBus.publish( EventName.ShowToast, diff --git a/ui/summit-2024/src/components/CheckWalletModal/CheckWalletModal.tsx b/ui/summit-2024/src/components/CheckWalletModal/CheckWalletModal.tsx new file mode 100644 index 000000000..7233f42eb --- /dev/null +++ b/ui/summit-2024/src/components/CheckWalletModal/CheckWalletModal.tsx @@ -0,0 +1,105 @@ +import React, {useEffect} from "react"; +import {Avatar, Box, Typography} from "@mui/material"; +import { CustomButton } from "../common/CustomButton/CustomButton"; +import Modal from "../common/Modal/Modal"; +import { useIsPortrait } from "../../common/hooks/useIsPortrait"; +import theme from "../../common/styles/theme"; +import {useAppSelector} from "../../store/hooks"; +import {getConnectedWallet} from "../../store/reducers/userCache"; +import {eventBus, EventName} from "../../utils/EventBus"; + +interface CheckWalletModalProps { + isOpen: boolean; + handleOpenModal: () => void; + handleCloseModal: () => void; +} + +const CheckWalletModal: React.FC = ({ + isOpen, + handleOpenModal, + handleCloseModal, +}) => { + const isMobile = useIsPortrait(); + const connectedWallet = useAppSelector(getConnectedWallet); + + useEffect(() => { + + const closeCheckWalletModal = () => { + handleCloseModal(); + }; + const openCheckWalletModal = () => { + handleOpenModal(); + }; + + eventBus.subscribe(EventName.CloseCheckWalletModal, closeCheckWalletModal); + eventBus.subscribe(EventName.OpenCheckWalletModal, openCheckWalletModal); + + return () => { + eventBus.unsubscribe( + EventName.CloseCheckWalletModal, + closeCheckWalletModal + ); + eventBus.unsubscribe( + EventName.OpenCheckWalletModal, + openCheckWalletModal + ); + }; + }, []); + + return ( + <> + handleCloseModal()} + width={isMobile ? "100%" : "450px"} + > + + + + Please refer to your wallet application to verify your vote. + + handleCloseModal()} + colorVariant="primary" + sx={{ + minWidth: "100%", + mt: "24px", + mb: "28px", + }} + > + Ok + + + + + ); +}; + +export { CheckWalletModal }; diff --git a/ui/summit-2024/src/components/ConnectWalletButton/ConnectWalletButton.scss b/ui/summit-2024/src/components/ConnectWalletButton/ConnectWalletButton.scss index f72d7bb9a..81908b21f 100644 --- a/ui/summit-2024/src/components/ConnectWalletButton/ConnectWalletButton.scss +++ b/ui/summit-2024/src/components/ConnectWalletButton/ConnectWalletButton.scss @@ -1,7 +1,9 @@ .button-container { position: relative; - + z-index: 1300; .main-button { + position: relative; + z-index: 1301; border-radius: 12px; border: 1px solid var(--orange, #ee9766); display: inline-flex; @@ -72,6 +74,10 @@ line-height: 20px; height: 44px; + &:hover { + background-color: #282828; + } + .MuiTypography-root { font-size: 12px; } @@ -102,7 +108,6 @@ border: 1px solid #d2d2d9; background: var(--color-light-blue); display: inline-flex; - padding: 16px 20px; justify-content: center; align-items: center; gap: 10px; diff --git a/ui/summit-2024/src/components/ConnectWalletButton/ConnectWalletButton.tsx b/ui/summit-2024/src/components/ConnectWalletButton/ConnectWalletButton.tsx index e0883b17a..c0c6ea074 100644 --- a/ui/summit-2024/src/components/ConnectWalletButton/ConnectWalletButton.tsx +++ b/ui/summit-2024/src/components/ConnectWalletButton/ConnectWalletButton.tsx @@ -14,7 +14,7 @@ import VerifiedIcon from "@mui/icons-material/Verified"; import "./ConnectWalletButton.scss"; import { getUserInSession, tokenIsExpired } from "../../utils/session"; import { addressSlice } from "../../utils/utils"; -import { eventBus } from "../../utils/EventBus"; +import { eventBus, EventName } from "../../utils/EventBus"; import { useIsPortrait } from "../../common/hooks/useIsPortrait"; import { useAppSelector } from "../../store/hooks"; import { getEventCache } from "../../store/reducers/eventCache"; @@ -28,21 +28,21 @@ import { ROUTES } from "../../routes"; type ConnectWalletButtonProps = { label: string; disableBackdropClick?: boolean; + showAddress?: boolean; onOpenConnectWalletModal: () => void; onOpenVerifyWalletModal: () => void; onDisconnectWallet: () => void; - onLogin: () => void; }; const ConnectWalletButton = (props: ConnectWalletButtonProps) => { - const { onOpenConnectWalletModal, onLogin, onDisconnectWallet } = props; + const { onOpenConnectWalletModal, onDisconnectWallet, showAddress } = props; const navigate = useNavigate(); + const session = getUserInSession(); const isMobile = useIsPortrait(); const eventCache = useAppSelector(getEventCache); const walletIsVerified = useAppSelector(getWalletIsVerified); const connectedWallet = useAppSelector(getConnectedWallet); - const session = getUserInSession(); const isExpired = tokenIsExpired(session?.expiresAt); const handleConnectWallet = () => { @@ -52,7 +52,11 @@ const ConnectWalletButton = (props: ConnectWalletButtonProps) => { }; const handleVerifyWallet = () => { - eventBus.publish("openVerifyWalletModal"); + eventBus.publish(EventName.OpenVerifyWalletModal); + }; + + const handleOpenLoginModal = () => { + eventBus.publish(EventName.OpenLoginModal); }; const handleOpenVoteReceipts = () => { @@ -60,9 +64,20 @@ const ConnectWalletButton = (props: ConnectWalletButtonProps) => { }; return ( - +