Skip to content

Commit

Permalink
init mini game
Browse files Browse the repository at this point in the history
  • Loading branch information
Claudio La Barbera committed Sep 9, 2024
1 parent 99bfa50 commit f315636
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 82 deletions.
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
VITE_ENV="development"
VITE_TELEGRAM_BOT_URL=""
VITE_TELEGRAM_MANIFEST_URL=""
VITE_JETTON_SYMBOL=""
VITE_TON_CONTRACT_ADDRESS=""
VITE_API_BASE_URL="https://example-api.com/api"
VITE_TON_CONTRACT_ADDRESS=""
8 changes: 3 additions & 5 deletions .env.production
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
VITE_ENV="development"
VITE_TELEGRAM_BOT_URL="https://t.me/DisclosureGameBot"
VITE_TELEGRAM_MANIFEST_URL="https://thebatclaudio.github.io/ton-dapp-vue-template/tonconnect-manifest.json"
VITE_JETTON_SYMBOL="COIN"
VITE_TON_CONTRACT_ADDRESS="UQCxB9fUfl5s2s2J8IClW8cvSPoCpp3G1EXcu_j3kfjUhO0K"
VITE_API_BASE_URL="http://localhost/api"
VITE_TELEGRAM_BOT_URL="https://t.me/x2lotterybot"
VITE_TELEGRAM_MANIFEST_URL="https://gradoally.github.io/x2lottery/tonconnect-manifest.json"
VITE_TON_CONTRACT_ADDRESS="kQB0_W4oxy-FUhQAxH8wSgay0XB3BXciGJ7z0TSjo0qel3D3"
3 changes: 0 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ services:
dockerfile: Dockerfile
expose:
- 80
environment:
VIRTUAL_HOST: disclosuregame.thebatclaud.io
LETSENCRYPT_HOST: disclosuregame.thebatclaud.io
volumes:
- .:/app
- /app/node_modules
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<title>TON DApp Vue Template</title>
<title>x2Lottery</title>
</head>
<body class="bg-gray-950 text-white h-screen select-none">
<div id="app" class="h-full"></div>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "ton-dapp-vue-template",
"name": "x2lottery",
"private": true,
"version": "0.1.0",
"type": "module",
Expand Down
Binary file modified public/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/loser.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions public/tonconnect-manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"url": "https://thebatclaudio.github.io/ton-dapp-vue-template",
"name": "TON DApp Vue Template",
"iconUrl": "https://thebatclaudio.github.io/ton-dapp-vue-template/icon.png"
"url": "https://gradoally.github.io/x2lottery",
"name": "x2lottery",
"iconUrl": "https://gradoally.github.io/x2lottery/icon.png"
}
21 changes: 1 addition & 20 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { useRouter } from 'vue-router'
import { store } from './common/store'
import Navbar from './components/common/Navbar.vue'
const router = useRouter()
const loading = ref(true);
Expand Down Expand Up @@ -33,23 +31,6 @@ onMounted(async () => {
</keep-alive>
</router-view>
</div>
<div id="navbar-container" v-if="!loading" class="relative p-3 bg-black">
<Navbar class="relative z-20"></Navbar>
</div>
</div>
</div>
</template>

<style>
#navbar-container::before {
content: "";
position: absolute;
height: 40px;
top: -40px;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%);
z-index: 10;
}
</style>
</template>
43 changes: 22 additions & 21 deletions src/common/store/telegram.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Reactive, reactive } from "vue";
import { TonConnectUI, TonConnectUiOptions, Account } from "@tonconnect/ui";
import { SendTransactionRequest } from "@tonconnect/ui";
import { InitDataParsed, retrieveLaunchParams } from '@telegram-apps/sdk';
import { beginCell, toNano } from "@ton/ton";
import { InitDataParsed, retrieveLaunchParams } from "@telegram-apps/sdk";
import { Address, toNano, TonClient } from "@ton/ton";
import { HeadsOrTails } from "../../wrappers/HeadsOrTails";
import { TonConnectSender } from "../../wrappers/TonConnectSender";

interface Telegram {
walletAccount: Account | null;
Expand All @@ -13,7 +14,7 @@ interface Telegram {
initTelegramData: () => void;
initWallet: () => Promise<void>;
initConnectWalletButton: (buttonRootId: string | null) => Promise<void>;
sendTransaction: (transaction: SendTransactionRequest) => Promise<void>;
playGame: (amount: number) => Promise<void>;
}

export const Telegram: Reactive<Telegram> = reactive<Telegram>({
Expand Down Expand Up @@ -48,23 +49,23 @@ export const Telegram: Reactive<Telegram> = reactive<Telegram>({
twaReturnUrl: import.meta.env.VITE_TELEGRAM_BOT_URL,
} as TonConnectUiOptions;
},
async sendTransaction() {
const body = beginCell()
.storeUint(0, 32) // write 32 zero bits to indicate that a text comment will follow
.storeStringTail("Test transaction") // write our text comment
.endCell();
async playGame(amount: number) {
const collectionAddress = import.meta.env.VITE_TON_CONTRACT_ADDRESS;
const address = Address.parse(collectionAddress);

const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: import.meta.env.VITE_TON_CONTRACT_ADDRESS,
amount: toNano(0.05).toString(),
payload: body.toBoc().toString("base64"),
},
],
};
const tonClient = new TonClient({
endpoint: "https://toncenter.com/api/v2/jsonRPC",
});

const contractProvider = tonClient.open(
HeadsOrTails.createFromAddress(address)
);

await this.tonConnectUI?.sendTransaction(transaction);
if (this.tonConnectUI) {
return await contractProvider.sendBet(
new TonConnectSender(this.tonConnectUI),
toNano(amount)
);
}
},
});
});
99 changes: 74 additions & 25 deletions src/components/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { ref, computed, onMounted } from 'vue'
import { initHapticFeedback } from '@telegram-apps/sdk';
import { FaceSmileIcon } from '@heroicons/vue/24/solid';
import { HeadsOrTails } from '../../wrappers/HeadsOrTails';
import Modal from '../modals/Modal.vue';
import Button from '../common/Button.vue';
import { store } from '../../common/store';
Expand All @@ -10,10 +11,7 @@ const jettonSymbol = ref(import.meta.env.VITE_JETTON_SYMBOL);
const hapticFeedback = initHapticFeedback();
const handleClick = (event) => {
hapticFeedback.impactOccurred('heavy');
store.incrementAmount();
};
const amount = ref(1);
const modalOpen = ref(false);
const modalTitle = ref('');
Expand All @@ -25,8 +23,27 @@ const openModal = () => {
modalDescription.value = "Modal description";
}
const sendTransaction = () => {
store.telegram.sendTransaction();
let isFlipping = false;
let currentDegrees = 0;
const playGame = async () => {
if (isFlipping) return;
const betResult = await store.telegram.playGame(amount.value);
coin.classList.add("flipping");
isFlipping = true;
const random = Math.floor(Math.random() * 4 + parseInt(betResult));
currentDegrees += 180 * random;
coin.style.transform = `rotateY(${currentDegrees}deg)`;
setTimeout(() => {
isFlipping = false;
coin.classList.remove("flipping");
}, 5000);
}
onMounted(() => {
Expand All @@ -36,38 +53,70 @@ onMounted(() => {
<template>
<div class="text-center flex flex-col h-full pt-10">
<div class="font-mono text-4xl sm:text-6xl pt-10">{{ store.currentAmount }}<small class="text-sm">{{ jettonSymbol }}</small>
</div>
<div class="flex-1 flex flex-col justify-center content-center">
<div class="flex justify-center">
<div class="cursor-pointer" @click="handleClick">
<FaceSmileIcon class="size-20" />
<div id="coin">
<div id="front"><img src="/icon.png" class="pulse w-[60vw]"></div>
<div id="back"><img src="/loser.png" class="pulse w-[60vw]"></div>
</div>
</div>
<div class="mt-12">
<Button @click="playGame()">
Play
</Button>
</div>
</div>
<div class="text-center text-3xl uppercase p-5">
<button id="ton-connect-button" type="button"></button>
</div>
<div class="flex justify-center">
<Button @click="sendTransaction()">
Send test transaction
</Button>
</div>
<div class="flex justify-center mt-2">
<Button @click="openModal()">
Open modal
</Button>
</div>
<div class="flex flex-col mt-5">
<div class="uppercase text-xs">Daily taps</div>
<div class="font-mono font-bold">{{ store.user?.daily_taps }}/{{ store.maxDailyTaps }}</div>
<!-- footer -->
</div>
</div>
<Modal :isOpen="modalOpen" @close="modalOpen = false" :confirmButtonEnabled="false"
:cancelButtonText="'Ok'" :title="modalTitle">
<Modal :isOpen="modalOpen" @close="modalOpen = false" :confirmButtonEnabled="false" :cancelButtonText="'Ok'"
:title="modalTitle">
<p class="text-center">
{{ modalDescription }}
</p>
</Modal>
</template>
</template>
<style>
#coin {
position: relative;
width: 200px;
height: 200px;
perspective: 1000px;
transition: transform 5s ease, box-shadow 0.2s ease;
transform-style: preserve-3d;
background-color: #020b13;
box-sizing: border-box;
border-radius: 50%;
align-self: center;
box-shadow: 0 10px 10px rgba(0, 0, 0, 0.2);
}
#coin.flipping {
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2);
}
#coin>* {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
backface-visibility: hidden;
border-radius: 50%;
margin: 16px;
}
#front {
z-index: 2;
}
#back {
transform: rotateY(180deg);
}
</style>
32 changes: 32 additions & 0 deletions src/wrappers/HeadsOrTails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';
export const Op = { Setup: 0, Deploy: 1, TopUp: 2, Withdraw: 3, };
export const Result = { ValueError: 0, Loss: 1, };
export type HeadsOrTailsConfig = { owner: Address, min_bet: number | bigint, max_bet: number | bigint, mul_num: number | bigint, mul_denom: number | bigint, };
export function headsOrTailsConfigToCell(config: HeadsOrTailsConfig): Cell { return beginCell()
.storeAddress(config.owner)
.storeCoins(config.min_bet)
.storeCoins(config.max_bet)
.storeUint(config.mul_num, 16)
.storeUint(config.mul_denom, 16)
.endCell(); }
export class HeadsOrTails implements Contract {
constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {}
static createFromAddress(address: Address) { return new HeadsOrTails(address); }
static createFromConfig(config: HeadsOrTailsConfig, code: Cell, workchain = 0) {
const data = headsOrTailsConfigToCell(config);
const init = { code, data };
return new HeadsOrTails(contractAddress(workchain, init), init); }
async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) { await provider.internal(via, {
value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: beginCell().storeUint(Op.Deploy, 32).endCell(), }); }
async sendBet(provider: ContractProvider, via: Sender, value: bigint) {
await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: beginCell().endCell(),}); }
async sendTopUp(provider: ContractProvider, via: Sender, value: bigint) {
await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: beginCell().storeUint(Op.TopUp, 32).endCell(),}); }
async sendSetup(provider: ContractProvider, via: Sender, value: bigint, owner: Address, min_bet: number | bigint, max_bet: number | bigint, mul_num: number | bigint, mul_denom: number | bigint, ) {
await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: beginCell().storeUint(Op.Setup, 32).storeUint(0, 64)
.storeAddress(owner).storeCoins(min_bet).storeCoins(max_bet).storeUint(mul_num, 16).storeUint(mul_denom, 16).endCell(),}); }
async sendWithdraw(provider: ContractProvider, via: Sender, value: bigint, w_amount: bigint) {
await provider.internal(via, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: beginCell().storeUint(Op.Withdraw, 32).storeUint(0, 64).storeCoins(w_amount).endCell(),}); }
async getData(provider: ContractProvider) {
const result = await provider.get('get_smc_data', []);
return result.stack.readCell(); } }
33 changes: 33 additions & 0 deletions src/wrappers/TonConnectSender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Address, beginCell, Sender, SenderArguments, SendMode, storeStateInit } from "@ton/ton";
import { TonConnectUI } from "@tonconnect/ui";

export class TonConnectSender implements Sender {
#provider: TonConnectUI;
readonly address?: Address;

constructor(provider: TonConnectUI) {
this.#provider = provider;
if (provider.wallet) this.address = Address.parseRaw(provider.wallet?.account.address);
else this.address = undefined;
}

async send(args: SenderArguments): Promise<void> {
if (!(args.sendMode === undefined || args.sendMode == SendMode.PAY_GAS_SEPARATELY)) {
throw new Error("Deployer sender does not support `sendMode` other than `PAY_GAS_SEPARATELY`");
}

await this.#provider.sendTransaction({
validUntil: Date.now() + 5 * 60 * 1000,
messages: [
{
address: args.to.toString(),
amount: args.value.toString(),
payload: args.body?.toBoc().toString("base64"),
stateInit: args.init
? beginCell().storeWritable(storeStateInit(args.init)).endCell().toBoc().toString("base64")
: undefined,
},
],
});
}
}

0 comments on commit f315636

Please sign in to comment.