diff --git a/.gitignore b/.gitignore index fd3dbb5..27adf2a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel @@ -34,3 +35,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +account.json diff --git a/next.config.mjs b/next.config.mjs index 4678774..743fe22 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + eslint: { + ignoreDuringBuilds: true + } +}; export default nextConfig; diff --git a/package.json b/package.json index 77cdcc2..1628163 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@polkadot/api": "^11.3.1", "@polkadot/extension-dapp": "^0.47.6", "@polkadot/extension-inject": "^0.47.6", + "@polkadot/keyring": "^12.6.2", "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-slot": "^1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9477a45..64f272f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: '@polkadot/extension-inject': specifier: ^0.47.6 version: 0.47.6(@polkadot/api@11.3.1)(@polkadot/util@12.6.2) + '@polkadot/keyring': + specifier: ^12.6.2 + version: 12.6.2(@polkadot/util-crypto@12.6.2)(@polkadot/util@12.6.2) '@radix-ui/react-alert-dialog': specifier: ^1.1.1 version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) diff --git a/src/app/(dashboard)/home/_components/live-game-container.tsx b/src/app/(dashboard)/home/_components/live-game-container.tsx index 84bc028..a45855b 100644 --- a/src/app/(dashboard)/home/_components/live-game-container.tsx +++ b/src/app/(dashboard)/home/_components/live-game-container.tsx @@ -27,6 +27,8 @@ type GameType = 0 | 1; export default function LiveGamePlay({ type }: { type: GameType }) { const [openGameSheet, setOpenGameSheet] = useState(false); + + const [propertyDisplay, setPropertyDisplay] = useState(null); const [display, setDisplay] = useState<'start' | 'play' | 'success' | 'fail'>('start'); function closeGameSheet() { @@ -34,12 +36,21 @@ export default function LiveGamePlay({ type }: { type: GameType }) { } const game: IGamePlaySection = { - start: , + start: ( + + ), play: , success: , fail: }; + console.log(propertyDisplay); + return ( @@ -67,24 +78,34 @@ export default function LiveGamePlay({ type }: { type: GameType }) { interface GameProps { type: GameType; setDisplay: Dispatch>; + setPropertyDisplay: Dispatch>; close: () => void; } -function StartGame({ type, close, setDisplay }: GameProps) { +function StartGame({ type, close, setDisplay, setPropertyDisplay }: GameProps) { const { address } = useSubstrateContext(); - const [isPending, startTransition] = useTransition(); - - function onPlay() { - startTransition(async () => { - await playGame(type, address); - setDisplay('play'); - }); + const [isLoading, setIsLoading] = useState(false); + + // const [isPending, startTransition] = useTransition(); + + async function onPlay() { + try { + setIsLoading(true); + await playGame(type, address, data => { + setPropertyDisplay(data); + setDisplay('play'); + }); + setIsLoading(false); + } catch (error) { + console.log(error); + setIsLoading(false); + } } return (
- @@ -97,7 +118,7 @@ function StartGame({ type, close, setDisplay }: GameProps) { diff --git a/src/app/(dashboard)/profile/page.tsx b/src/app/(dashboard)/profile/page.tsx index 9035cb3..f238e03 100644 --- a/src/app/(dashboard)/profile/page.tsx +++ b/src/app/(dashboard)/profile/page.tsx @@ -124,7 +124,7 @@ export default function Page({
Rounded avatar
- +
@@ -160,7 +160,7 @@ export default function Page({ Nfts - + 23 @@ -168,7 +168,7 @@ export default function Page({ Listed - + 0 @@ -176,14 +176,14 @@ export default function Page({ Offers - + 4
  • Challenges{' '} - + 20 @@ -198,18 +198,18 @@ export default function Page({ key={collection} variant={'outline'} size={'sm'} - href={`${BASE_URL}?collection=${collection}`} className={ active ? 'border-primary-300 bg-primary-300/15 text-primary-300' : '' } + asChild > - {collection} + {collection} ); })}
  • -
    +
    {siteConfig.nfts.map((nft: any) => ( ([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); + const [propertyDisplay, setPropertyDisplay] = useState(null); - const handleGetImages = async () => { - setLoading(true); - setError(''); + // const handleGetImages = async () => { + // setLoading(true); + // setError(''); + // try { + // // const images = await processImages(); + // await checkResult(); + // // setImageSrcs(images); + // } catch (error) { + // console.log(error); + // setError('Failed to fetch images'); + // } finally { + // setLoading(false); + // } + // }; + + const handlePlayGame = async () => { + console.log('Button clicked'); try { - const images = await processImages(); - setImageSrcs(images); + await playGame(0, '5FEda1GYvjMYcBiuRE7rb85QbD5bQNHuZajhRvHYTxm4PPz5', data => { + setPropertyDisplay(data); + }); } catch (error) { - setError('Failed to fetch images'); - } finally { - setLoading(false); + console.log('error: ', error); } }; return (

    Home Page

    - + */} {error &&

    {error}

    }
    {imageSrcs.map((src, index) => ( {`Processed ))}
    + + + {propertyDisplay && ( +
    +

    Game Details:

    +

    {propertyDisplay}

    {' '} + {/* Adjust this to display actual data from propertyDisplay */} +
    + )}
    ); } diff --git a/src/app/actions.ts b/src/app/actions.ts index a56df38..c443e7a 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -1,18 +1,30 @@ 'use server'; import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; -import { DynamoDBDocumentClient, PutCommand, GetCommand } from '@aws-sdk/lib-dynamodb'; +import { + DynamoDBDocumentClient, + PutCommand, + GetCommand, + GetCommandOutput +} from '@aws-sdk/lib-dynamodb'; import fs from 'fs'; import path from 'path'; import sharp from 'sharp'; import { createCanvas } from 'canvas'; +import { getApi } from '@/lib/polkadot'; +import Keyring from '@polkadot/keyring'; +import { GameInfo, GameResultType } from '@/lib/extrinsic'; +import { getGameInfo, getUserData } from '@/lib/queries'; -const imageUrls = [ - 'https://media.rightmove.co.uk:443/dir/crop/10:9-16:9/32k/31572/148508522/31572_PAR130157_IMG_00_0000_max_476x317.jpeg', - 'https://media.rightmove.co.uk:443/dir/crop/10:9-16:9/257k/256565/146381138/256565_BCL15_IMG_01_0000_max_476x317.jpeg', - 'https://media.rightmove.co.uk:443/dir/crop/10:9-16:9/111k/110768/144527498/110768_101184001677_IMG_00_0000_max_476x317.jpeg', - 'https://media.rightmove.co.uk:443/dir/crop/10:9-16:9/63k/62293/147208034/62293_32892189_IMG_00_0000_max_476x317.jpeg' -]; +const client = new DynamoDBClient({ + region: 'eu-west-2', // specify your region + credentials: { + accessKeyId: process.env.DYNAMO_ACCESS_KEY!, + secretAccessKey: process.env.DYNAMO_SECRET_KEY! + } +}); + +const docClient = DynamoDBDocumentClient.from(client); const checkerboardSize = 2.5; @@ -52,12 +64,130 @@ async function applyCheckerboardOverlay(imageBuffer: ArrayBuffer) { return processedImage.toString('base64'); } -export async function processImages() { - const processedImagesPromises = imageUrls.map(async url => { - const imageBuffer = await fetchImage(url); - return applyCheckerboardOverlay(imageBuffer); +export async function checkResult( + data: { guess: number; gameId: number; address: string } + // secret: { scheme: string; key: string; iv: string } +) { + const gameInfo = (await getGameInfo(data.gameId)) as unknown as GameInfo; + // await fetchPropertyData(gameInfo.property.id); + const propertyData = await fetchPropertyData(139361966); + const realPrice = propertyData!.price; + const secret = { + scheme: 'aes-256-cbc', + keyBase64: propertyData!.key, + ivBase64: propertyData!.iv + }; + const api = await getApi(); + const keyring = new Keyring({ type: 'sr25519' }); + const account = keyring.createFromJson({ + encoded: process.env.ENCODED_SEED!, + encoding: { + content: ['pkcs8', 'sr25519'], + type: ['scrypt', 'xsalsa20-poly1305'], + version: '3' + }, + address: process.env.SUDO_ADDRESS!, + meta: { + genesisHash: '0x', + name: 'XCAV-SUDO', + whenCreated: 1702476542911 + } }); - const processedImages = await Promise.all(processedImagesPromises); - return processedImages.map(img => `data:image/jpeg;base64,${img}`); + account.unlock(process.env.PASSPHRASE!); + + const extrinsic = api.tx.gameModule.checkResult( + data.guess, + data.gameId, + realPrice, + JSON.stringify(secret) + ); + + // Sign and send the transaction + const unsub = await extrinsic.signAndSend(account, ({ status, events = [] }) => { + if (status.isInBlock) { + console.log('Transaction included at blockHash', status.asInBlock.toHex()); + const ResultChecked = events.find(({ event }) => + api.events.gameModule.ResultChecked.is(event) + ); + if (ResultChecked) { + const points = ResultChecked.event.data[2].toString(); + const won = ResultChecked.event.data[3].toString(); + console.log('Points:', points); + console.log('Won:', won); + } + } else if (status.isFinalized) { + console.log('Transaction finalized at blockHash', status.asFinalized.toHex()); + unsub(); + } + }); +} + +export async function fetchPropertyData(id: number) { + try { + const res = await docClient.send( + new GetCommand({ + TableName: 'realXDeal', + Key: { + id + } + }) + ); + return res.Item; + } catch (error) { + console.log('Error fetching property:', error); + } +} + +export async function fetchPropertyForDisplay(id: number) { + console.log('Logging on the server'); + + const data = await fetchPropertyData(id); + if (data) { + const displayData = await processPropertyData(data); + return displayData; + } else { + return undefined; + } +} + +async function processPropertyData(propertyData: Record) { + const { + mainImageSrc, + images, + summary, + location, + bedrooms, + bathrooms, + displaySize, + propertySubType + } = propertyData; + + try { + const processedMainImage = await processImage(mainImageSrc); + const processedImages = await Promise.all( + images.map((img: any) => { + return processImage(img.srcUrl); + }) + ); + + return { + cover_image: processedMainImage, + type: propertySubType, + location, + summary, + bedrooms, + bathrooms, + size: displaySize, + images: processedImages + }; + } catch (error) { + console.log('Error: ', error); + } +} + +export async function processImage(imageUrl: string) { + const imageBuffer = await fetchImage(imageUrl); + const imageString = await applyCheckerboardOverlay(imageBuffer); + return `data:image/jpeg;base64,${imageString}`; } diff --git a/src/app/gameplay/_components/play-guess.tsx b/src/app/gameplay/_components/play-guess.tsx index f76ea66..de2ce4e 100644 --- a/src/app/gameplay/_components/play-guess.tsx +++ b/src/app/gameplay/_components/play-guess.tsx @@ -1,3 +1,4 @@ +/* eslint-disable tailwindcss/no-custom-classname */ 'use client'; import { Button } from '@/components/ui/button'; @@ -58,7 +59,7 @@ export default function PlayGuess({ setDisplay }: GameProps) {
    {/*

    Return

    */} -

    @@ -67,11 +68,11 @@ export default function PlayGuess({ setDisplay }: GameProps) {

    -
    -
    -
    +
    +
    +
    property image
    -
    +
    property image
    -
    +
    Key Features

    - Private balcony. Communal roof terrace. Resident's concierge service. Close + Private balcony. Communal roof terrace. Residents concierge service. Close proximity to green spaces. 999 year lease with peppercorn ground rent

    diff --git a/src/context/polkadot-contex.tsx b/src/context/polkadot-contex.tsx index d4288a0..c0671ee 100644 --- a/src/context/polkadot-contex.tsx +++ b/src/context/polkadot-contex.tsx @@ -108,8 +108,7 @@ export default function SubstrateContextProvider({ children }: SubstrateProps) { // }; const handleConnect = useCallback(async (walletName: 'talisman' | 'subwallet-js') => { - - console.log(walletName) + console.log(walletName); const extensions = await web3Enable('RealXDeal'); if (extensions.length === 0) { @@ -117,16 +116,14 @@ export default function SubstrateContextProvider({ children }: SubstrateProps) { return; } - console.log(extensions) - const extension = extensions.find((value) => value.name === walletName) - + console.log(extensions); + const extension = extensions.find(value => value.name === walletName); if (!extension) { toast.error('No Polkadot wallet extensions found!'); return; } - console.log(extension.name === walletName) - + console.log(extension.name === walletName); const account = (await extension.accounts.get())[0]; diff --git a/src/lib/extrinsic.ts b/src/lib/extrinsic.ts index 63d4a22..9df9f70 100644 --- a/src/lib/extrinsic.ts +++ b/src/lib/extrinsic.ts @@ -1,27 +1,63 @@ import { web3Enable, web3FromAddress } from '@polkadot/extension-dapp'; import { getApi } from './polkadot'; import { toast } from 'sonner'; +import { getGameInfo } from './queries'; +import { checkResult, fetchPropertyForDisplay } from '@/app/actions'; -export async function playGame(gameType: 0 | 1 | 2, address: string) { +export interface GameInfo { + property: { + id: number; + [key: string]: any; + }; +} + +function getPropertyId(gameId: number) {} + +export async function playGame( + gameType: 0 | 1 | 2, + address: string, + handlePropertyDisplay: (data: any) => void +) { try { const api = await getApi(); - // const extensions = await web3Enable('RealXDEal'); + const extensions = await web3Enable('RealXDEal'); const injected = await web3FromAddress(address); const extrinsic = api.tx.gameModule.playGame(gameType); const signer = injected.signer; - const unsub = await extrinsic.signAndSend(address, { signer }, result => { - if (result.status.isInBlock) { - toast.success(result.status.asInBlock.toString()); - console.log(`Completed at block hash #${result.status.asInBlock.toString()}`); - } else if (result.status.isBroadcast) { - toast.warning('Broadcasting the game...'); - console.log('Broadcasting the game...'); - } - console.log('Play Result', result); - }); + let eventProcessed = false; + const unsub = await extrinsic.signAndSend( + address, + { signer }, + async ({ status, events = [], dispatchError }) => { + if (status.isInBlock && !eventProcessed) { + eventProcessed = true; + const gameStartedEvent = events.find(({ event }) => + api.events.gameModule.GameStarted.is(event) + ); - console.log('Transaction sent:', unsub); + if (gameStartedEvent) { + const gameId = gameStartedEvent.event.data[1].toString(); + console.log(`GameStarted event found with game_id: ${gameId}`); + const gameInfo = (await getGameInfo(parseInt(gameId))) as unknown as GameInfo; + console.log('The game info is: ', gameInfo); + + const propertyDisplay = await fetchPropertyForDisplay(139361966); + handlePropertyDisplay(propertyDisplay); + + // console.log(propertyDisplay); + + toast.success(status.asInBlock.toString()); + console.log(`Completed at block hash #${status.asInBlock.toString()}`); + } else if (dispatchError) { + // display a warning and prompt to retry + toast.warning('Broadcasting the game...'); + console.log('Broadcasting the game...'); + } + } + } + ); + // console.log('Transaction sent:', unsub); } catch (error) { console.error('Failed to submit guess:', error); } @@ -34,9 +70,11 @@ export async function submitGameAnswer(address: string, guess: any, gameId: numb const extrinsic = api.tx.gameModule.submitAnswer(guess, gameId); const signer = injected.signer; - const unsub = await extrinsic.signAndSend(address, { signer }, result => { + const unsub = await extrinsic.signAndSend(address, { signer }, async result => { if (result.status.isInBlock) { console.log(`Completed at block hash #${result.status.asInBlock.toString()}`); + // here we need to call the server action to trigger the check_result function passing in the guess and the game_id + await checkResult({ guess, gameId, address }); } else if (result.status.isBroadcast) { console.log('Broadcasting the guess...'); } @@ -49,37 +87,36 @@ export async function submitGameAnswer(address: string, guess: any, gameId: numb } } -type GameResultType = { +export type GameResultType = { guess: any; gameId: number; price: any; - secret: any; }; -export async function checkGameResult(address: string, value: GameResultType) { - try { - const api = await getApi(); - const injected = await web3FromAddress(address); - const extrinsic = api.tx.gameModule.checkResult( - value.guess, - value.gameId, - value.price, - value.secret - ); - const signer = injected.signer; +// export async function checkGameResult(address: string, value: GameResultType) { +// try { +// const api = await getApi(); +// const injected = await web3FromAddress(address); +// const extrinsic = api.tx.gameModule.checkResult( +// value.guess, +// value.gameId, +// value.price, +// value.secret +// ); +// const signer = injected.signer; - const unsub = await extrinsic.signAndSend(address, { signer }, result => { - if (result.status.isInBlock) { - console.log(`Completed at block hash #${result.status.asInBlock.toString()}`); - } else if (result.status.isBroadcast) { - console.log('Broadcasting the guess...'); - } - }); +// const unsub = await extrinsic.signAndSend(address, { signer }, result => { +// if (result.status.isInBlock) { +// console.log(`Completed at block hash #${result.status.asInBlock.toString()}`); +// } else if (result.status.isBroadcast) { +// console.log('Broadcasting the guess...'); +// } +// }); - console.log('Transaction sent:', unsub); - } catch (error) { - console.error('Failed to submit guess:', error); - } -} +// console.log('Transaction sent:', unsub); +// } catch (error) { +// console.error('Failed to submit guess:', error); +// } +// } export async function listNFT(senderAddress: string, collectionId: number, nftId: number) { try { diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 5151acb..92937cd 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -30,6 +30,10 @@ export async function getCurrentRoundID() { export async function getGameInfo(gameId: number) { const api = await getApi(); + // const apiAt = await api.at( + // '0x163029d36e699bbb6df2e449a5015efab1168092c9df385913ca7e6f5f0b3685' + // ); + const result = await api.query.gameModule.gameInfo(gameId); const output = result.toHuman(); return output;