genlayer 0.12.3 → 0.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -0
- package/CHANGELOG.md +8 -0
- package/dist/index.js +409 -80
- package/esbuild.config.dev.js +1 -2
- package/esbuild.config.prod.js +1 -1
- package/eslint.config.js +2 -1
- package/package.json +5 -3
- package/src/commands/contracts/call.ts +16 -20
- package/src/commands/contracts/deploy.ts +107 -25
- package/src/commands/contracts/index.ts +14 -3
- package/src/commands/general/init.ts +0 -1
- package/src/commands/scaffold/index.ts +16 -0
- package/src/commands/scaffold/new.ts +34 -0
- package/src/index.ts +2 -0
- package/src/lib/actions/BaseAction.ts +11 -6
- package/src/lib/config/simulator.ts +2 -2
- package/templates/default/LICENSE +21 -0
- package/templates/default/README.md +101 -0
- package/templates/default/__init__.py +0 -0
- package/templates/default/app/.env.example +2 -0
- package/templates/default/app/.vscode/extensions.json +3 -0
- package/templates/default/app/README.md +5 -0
- package/templates/default/app/index.html +17 -0
- package/templates/default/app/package-lock.json +4920 -0
- package/templates/default/app/package.json +23 -0
- package/templates/default/app/postcss.config.js +6 -0
- package/templates/default/app/public/favicon.png +0 -0
- package/templates/default/app/src/App.vue +16 -0
- package/templates/default/app/src/components/Address.vue +38 -0
- package/templates/default/app/src/components/BetsScreen.vue +329 -0
- package/templates/default/app/src/logic/FootballBets.js +100 -0
- package/templates/default/app/src/main.js +5 -0
- package/templates/default/app/src/services/genlayer.js +19 -0
- package/templates/default/app/src/style.css +3 -0
- package/templates/default/app/tailwind.config.js +8 -0
- package/templates/default/app/vite.config.js +7 -0
- package/templates/default/config/__init__.py +0 -0
- package/templates/default/config/genlayer_config.py +14 -0
- package/templates/default/contracts/__init__.py +0 -0
- package/templates/default/contracts/football_bets.py +119 -0
- package/templates/default/deploy/deployScript.ts +31 -0
- package/templates/default/package-lock.json +3231 -0
- package/templates/default/package.json +7 -0
- package/templates/default/requirements.txt +6 -0
- package/templates/default/test/__init__.py +0 -0
- package/templates/default/test/football_bets_get_contract_schema_for_code.py +124 -0
- package/templates/default/test/test_football_bet_success_draw.py +108 -0
- package/templates/default/test/test_football_bet_success_win.py +106 -0
- package/templates/default/test/test_football_bet_unsuccess.py +107 -0
- package/templates/default/tools/__init__.py +0 -0
- package/templates/default/tools/accounts.py +5 -0
- package/templates/default/tools/calldata.py +224 -0
- package/templates/default/tools/request.py +134 -0
- package/templates/default/tools/response.py +52 -0
- package/templates/default/tools/structure.py +39 -0
- package/templates/default/tools/transactions.py +28 -0
- package/templates/default/tools/types.py +214 -0
- package/templates/default/tsconfig.json +7 -0
- package/tests/actions/call.test.ts +38 -76
- package/tests/actions/deploy.test.ts +200 -30
- package/tests/actions/new.test.ts +80 -0
- package/tests/commands/call.test.ts +6 -1
- package/tests/commands/deploy.test.ts +12 -1
- package/tests/commands/new.test.ts +68 -0
- package/tests/index.test.ts +4 -0
- package/tests/libs/baseAction.test.ts +17 -0
- package/vitest.config.ts +1 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"genlayer-js": "^0.6.4",
|
|
13
|
+
"lucide-vue-next": "^0.446.0",
|
|
14
|
+
"vue": "^3.4.37"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@vitejs/plugin-vue": "^5.1.2",
|
|
18
|
+
"autoprefixer": "^10.4.20",
|
|
19
|
+
"postcss": "^8.4.47",
|
|
20
|
+
"tailwindcss": "^3.4.13",
|
|
21
|
+
"vite": "^5.4.1"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Suspense>
|
|
3
|
+
<template #default>
|
|
4
|
+
<BetsScreen />
|
|
5
|
+
</template>
|
|
6
|
+
<template #fallback>
|
|
7
|
+
<div class="flex items-center justify-center h-screen">
|
|
8
|
+
<div class="spinner">Loading...</div>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
</Suspense>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup>
|
|
15
|
+
import BetsScreen from "./components/BetsScreen.vue";
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span :title="fullAddress">{{ shortenedAddress }}</span>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script>
|
|
6
|
+
export default {
|
|
7
|
+
name: "Address",
|
|
8
|
+
props: {
|
|
9
|
+
address: {
|
|
10
|
+
type: String,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
maxLength: {
|
|
14
|
+
type: Number,
|
|
15
|
+
default: 12,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
computed: {
|
|
19
|
+
fullAddress() {
|
|
20
|
+
return this.address;
|
|
21
|
+
},
|
|
22
|
+
shortenedAddress() {
|
|
23
|
+
if (!this.address) {
|
|
24
|
+
return "";
|
|
25
|
+
}
|
|
26
|
+
if (this.address.length <= this.maxLength) {
|
|
27
|
+
return this.address;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const halfLength = Math.floor((this.maxLength - 3) / 2);
|
|
31
|
+
const start = this.address.slice(0, halfLength);
|
|
32
|
+
const end = this.address.slice(-halfLength);
|
|
33
|
+
|
|
34
|
+
return `${start}...${end}`;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="min-h-screen bg-gray-100 text-gray-900">
|
|
3
|
+
<header class="bg-white shadow flex justify-between">
|
|
4
|
+
<div class="max-w-7xl py-6 px-4 sm:px-6 lg:px-8">
|
|
5
|
+
<h1 class="text-3xl font-bold text-gray-900">GenLayer Football Bets</h1>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="max-w-7xl py-6 px-4 sm:px-6 lg:px-8 text-right">
|
|
8
|
+
<div v-if="!userAddress">
|
|
9
|
+
<button
|
|
10
|
+
@click="createUserAccount"
|
|
11
|
+
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
|
12
|
+
>
|
|
13
|
+
Create Account
|
|
14
|
+
</button>
|
|
15
|
+
</div>
|
|
16
|
+
<div v-else>
|
|
17
|
+
<p class="text-lg">Your address: <Address :address="userAddress" /></p>
|
|
18
|
+
<p class="text-lg">Your points: {{ userPoints }}</p>
|
|
19
|
+
<button
|
|
20
|
+
@click="disconnectUserAccount"
|
|
21
|
+
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
|
22
|
+
>
|
|
23
|
+
Disconnect
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</header>
|
|
28
|
+
<main class="mx-auto py-6 sm:px-6 lg:px-8">
|
|
29
|
+
<!-- Account Section -->
|
|
30
|
+
|
|
31
|
+
<div class="grid grid-cols-1 md:grid-cols-10 gap-8">
|
|
32
|
+
<!-- Bets List -->
|
|
33
|
+
<div class="bg-white shadow overflow-hidden sm:rounded-lg col-span-7">
|
|
34
|
+
<div class="px-4 py-5 sm:px-6 flex justify-between items-center">
|
|
35
|
+
<h2 class="text-lg leading-6 font-medium text-gray-900">Bets</h2>
|
|
36
|
+
<button
|
|
37
|
+
@click="openCreateModal"
|
|
38
|
+
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-sm"
|
|
39
|
+
>
|
|
40
|
+
Create Bet
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="border-t border-gray-200">
|
|
44
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
45
|
+
<thead class="bg-gray-50">
|
|
46
|
+
<tr>
|
|
47
|
+
<th
|
|
48
|
+
scope="col"
|
|
49
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
50
|
+
>
|
|
51
|
+
User
|
|
52
|
+
</th>
|
|
53
|
+
<th
|
|
54
|
+
scope="col"
|
|
55
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
56
|
+
>
|
|
57
|
+
Game Date
|
|
58
|
+
</th>
|
|
59
|
+
<th
|
|
60
|
+
scope="col"
|
|
61
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
62
|
+
>
|
|
63
|
+
Team 1
|
|
64
|
+
</th>
|
|
65
|
+
<th
|
|
66
|
+
scope="col"
|
|
67
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
68
|
+
>
|
|
69
|
+
Team 2
|
|
70
|
+
</th>
|
|
71
|
+
<th
|
|
72
|
+
scope="col"
|
|
73
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
74
|
+
>
|
|
75
|
+
Predicted Winner
|
|
76
|
+
</th>
|
|
77
|
+
<th
|
|
78
|
+
scope="col"
|
|
79
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
80
|
+
>
|
|
81
|
+
Status
|
|
82
|
+
</th>
|
|
83
|
+
<th
|
|
84
|
+
scope="col"
|
|
85
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
86
|
+
>
|
|
87
|
+
Result
|
|
88
|
+
</th>
|
|
89
|
+
<th
|
|
90
|
+
scope="col"
|
|
91
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
92
|
+
>
|
|
93
|
+
Action
|
|
94
|
+
</th>
|
|
95
|
+
</tr>
|
|
96
|
+
</thead>
|
|
97
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
98
|
+
<tr v-for="bet in bets" :key="bet.id">
|
|
99
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
100
|
+
<Address :address="bet.owner" />
|
|
101
|
+
</td>
|
|
102
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
103
|
+
{{ bet.game_date }}
|
|
104
|
+
</td>
|
|
105
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
106
|
+
{{ bet.team1 }}
|
|
107
|
+
</td>
|
|
108
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
109
|
+
{{ bet.team2 }}
|
|
110
|
+
</td>
|
|
111
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
112
|
+
{{ bet.predicted_winner === "0" ? "Draw" : (bet.predicted_winner === "1" ? bet.team1 : bet.team2) }}
|
|
113
|
+
</td>
|
|
114
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
115
|
+
<span :class="bet.has_resolved ? 'text-green-600' : 'text-yellow-600'">
|
|
116
|
+
{{ bet.has_resolved ? "Resolved" : "Unresolved" }}
|
|
117
|
+
</span>
|
|
118
|
+
</td>
|
|
119
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
120
|
+
<span v-if="bet.has_resolved" :class="bet.predicted_winner === String(bet.real_winner) ? 'text-green-600' : 'text-red-600'">
|
|
121
|
+
{{ bet.predicted_winner === String(bet.real_winner) ? "Success" : "Failure" }}
|
|
122
|
+
</span>
|
|
123
|
+
</td>
|
|
124
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
125
|
+
<div v-if="resolvingBet !== bet.id">
|
|
126
|
+
<button
|
|
127
|
+
v-if="bet.owner === userAddress && !bet.has_resolved"
|
|
128
|
+
@click="resolveBet(bet.id)"
|
|
129
|
+
class="text-indigo-600 hover:text-indigo-900"
|
|
130
|
+
>
|
|
131
|
+
Resolve
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
<div v-else>Resolving bet</div>
|
|
135
|
+
</td>
|
|
136
|
+
</tr>
|
|
137
|
+
</tbody>
|
|
138
|
+
</table>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Leaderboard -->
|
|
143
|
+
<div class="bg-white shadow overflow-hidden sm:rounded-lg col-span-3">
|
|
144
|
+
<div class="px-4 py-5 sm:px-6">
|
|
145
|
+
<h2 class="text-lg leading-6 font-medium text-gray-900">Leaderboard</h2>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="border-t border-gray-200">
|
|
148
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
149
|
+
<thead class="bg-gray-50">
|
|
150
|
+
<tr>
|
|
151
|
+
<th
|
|
152
|
+
scope="col"
|
|
153
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
154
|
+
>
|
|
155
|
+
Rank
|
|
156
|
+
</th>
|
|
157
|
+
<th
|
|
158
|
+
scope="col"
|
|
159
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
160
|
+
>
|
|
161
|
+
Address
|
|
162
|
+
</th>
|
|
163
|
+
<th
|
|
164
|
+
scope="col"
|
|
165
|
+
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
|
166
|
+
>
|
|
167
|
+
Points
|
|
168
|
+
</th>
|
|
169
|
+
</tr>
|
|
170
|
+
</thead>
|
|
171
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
172
|
+
<tr v-for="(user, index) in leaderboard" :key="user.address">
|
|
173
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ index + 1 }}</td>
|
|
174
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
175
|
+
<Address :address="user.address" />
|
|
176
|
+
</td>
|
|
177
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ user.points }}</td>
|
|
178
|
+
</tr>
|
|
179
|
+
</tbody>
|
|
180
|
+
</table>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<!-- Create Bet Modal -->
|
|
186
|
+
<div
|
|
187
|
+
v-if="showCreateModal"
|
|
188
|
+
class="fixed inset-0 bg-gray-600 bg-opacity-75 overflow-y-auto h-full w-full flex items-center justify-center"
|
|
189
|
+
>
|
|
190
|
+
<div class="relative p-5 border w-96 shadow-lg rounded-md bg-white">
|
|
191
|
+
<h3 class="text-lg font-medium leading-6 text-gray-900 mb-2">Create Bet</h3>
|
|
192
|
+
<input
|
|
193
|
+
v-model="gameDate"
|
|
194
|
+
type="date"
|
|
195
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-2"
|
|
196
|
+
placeholder="Game Date (YYYY-MM-DD)"
|
|
197
|
+
/>
|
|
198
|
+
<input
|
|
199
|
+
v-model="team1"
|
|
200
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-2"
|
|
201
|
+
placeholder="Team 1"
|
|
202
|
+
/>
|
|
203
|
+
<input
|
|
204
|
+
v-model="team2"
|
|
205
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-2"
|
|
206
|
+
placeholder="Team 2"
|
|
207
|
+
/>
|
|
208
|
+
<select
|
|
209
|
+
v-model="predictedWinner"
|
|
210
|
+
class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 mb-2"
|
|
211
|
+
>
|
|
212
|
+
<option value="" disabled selected>Select Predicted Winner</option>
|
|
213
|
+
<option value="0">Draw</option>
|
|
214
|
+
<option value="1">Team 1</option>
|
|
215
|
+
<option value="2">Team 2</option>
|
|
216
|
+
</select>
|
|
217
|
+
<div class="mt-4">
|
|
218
|
+
<div v-if="!creatingBet">
|
|
219
|
+
<button
|
|
220
|
+
@click="createBet"
|
|
221
|
+
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-2"
|
|
222
|
+
>
|
|
223
|
+
Create
|
|
224
|
+
</button>
|
|
225
|
+
<button
|
|
226
|
+
@click="showCreateModal = false"
|
|
227
|
+
class="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded"
|
|
228
|
+
>
|
|
229
|
+
Cancel
|
|
230
|
+
</button>
|
|
231
|
+
</div>
|
|
232
|
+
<div v-else>
|
|
233
|
+
<div class="spinner">Creating...</div>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</main>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="flex items-center justify-center h-screen">
|
|
242
|
+
<div class="spinner">Loading...</div>
|
|
243
|
+
</div>
|
|
244
|
+
</template>
|
|
245
|
+
|
|
246
|
+
<script setup>
|
|
247
|
+
import { ref, computed, onMounted } from "vue";
|
|
248
|
+
import { account, createAccount, removeAccount } from "../services/genlayer";
|
|
249
|
+
import FootballBets from "../logic/FootballBets";
|
|
250
|
+
import Address from "./Address.vue";
|
|
251
|
+
// State
|
|
252
|
+
const gameDate = ref("");
|
|
253
|
+
const team1 = ref("");
|
|
254
|
+
const team2 = ref("");
|
|
255
|
+
const creatingBet = ref(false);
|
|
256
|
+
const resolvingBet = ref(0);
|
|
257
|
+
const predictedWinner = ref("");
|
|
258
|
+
const contractAddress = import.meta.env.VITE_CONTRACT_ADDRESS;
|
|
259
|
+
const studioUrl = import.meta.env.VITE_STUDIO_URL;
|
|
260
|
+
const footballBets = new FootballBets(contractAddress, account, studioUrl);
|
|
261
|
+
const userAccount = ref(account);
|
|
262
|
+
const userPoints = ref(0);
|
|
263
|
+
const userAddress = computed(() => userAccount.value?.address);
|
|
264
|
+
const bets = ref([]);
|
|
265
|
+
const leaderboard = ref([]);
|
|
266
|
+
const showCreateModal = ref(false);
|
|
267
|
+
|
|
268
|
+
// Methods
|
|
269
|
+
const createUserAccount = async () => {
|
|
270
|
+
userAccount.value = createAccount();
|
|
271
|
+
footballBets.updateAccount(userAccount.value);
|
|
272
|
+
userPoints.value = 0;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const disconnectUserAccount = async () => {
|
|
276
|
+
userAccount.value = null;
|
|
277
|
+
removeAccount();
|
|
278
|
+
userPoints.value = 0;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const openCreateModal = () => {
|
|
282
|
+
showCreateModal.value = true;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const loadBets = async () => {
|
|
286
|
+
const allBets = await footballBets.getBets();
|
|
287
|
+
bets.value = allBets;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const loadLeaderboard = async () => {
|
|
291
|
+
leaderboard.value = await footballBets.getLeaderboard();
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const refreshPlayerPoints = async () => {
|
|
295
|
+
userPoints.value = await footballBets.getPlayerPoints(userAddress.value);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const createBet = async () => {
|
|
299
|
+
if (gameDate.value && team1.value && team2.value && predictedWinner.value) {
|
|
300
|
+
creatingBet.value = true;
|
|
301
|
+
await footballBets.createBet(gameDate.value, team1.value, team2.value, predictedWinner.value);
|
|
302
|
+
await loadBets();
|
|
303
|
+
// Reset form fields
|
|
304
|
+
creatingBet.value = false;
|
|
305
|
+
gameDate.value = "";
|
|
306
|
+
team1.value = "";
|
|
307
|
+
team2.value = "";
|
|
308
|
+
predictedWinner.value = "";
|
|
309
|
+
showCreateModal.value = false;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const resolveBet = async (betId) => {
|
|
314
|
+
resolvingBet.value = betId;
|
|
315
|
+
await footballBets.resolveBet(betId);
|
|
316
|
+
resolvingBet.value = 0;
|
|
317
|
+
await loadBets();
|
|
318
|
+
await loadLeaderboard();
|
|
319
|
+
await refreshPlayerPoints();
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
// Initialize with some sample data
|
|
324
|
+
onMounted(async () => {
|
|
325
|
+
await loadBets();
|
|
326
|
+
await loadLeaderboard();
|
|
327
|
+
await refreshPlayerPoints();
|
|
328
|
+
});
|
|
329
|
+
</script>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createClient } from "genlayer-js";
|
|
2
|
+
import { simulator } from "genlayer-js/chains";
|
|
3
|
+
|
|
4
|
+
class FootballBets {
|
|
5
|
+
contractAddress;
|
|
6
|
+
client;
|
|
7
|
+
|
|
8
|
+
constructor(contractAddress, account = null, studioUrl = null) {
|
|
9
|
+
this.contractAddress = contractAddress;
|
|
10
|
+
const config = {
|
|
11
|
+
chain: simulator,
|
|
12
|
+
...(account ? { account } : {}),
|
|
13
|
+
...(studioUrl ? { endpoint: studioUrl } : {}),
|
|
14
|
+
};
|
|
15
|
+
this.client = createClient(config);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
updateAccount(account) {
|
|
19
|
+
this.client = createClient({ chain: simulator, account });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async getBets() {
|
|
23
|
+
const bets = await this.client.readContract({
|
|
24
|
+
address: this.contractAddress,
|
|
25
|
+
functionName: "get_bets",
|
|
26
|
+
args: [],
|
|
27
|
+
});
|
|
28
|
+
return Array.from(bets.entries()).flatMap(([owner, bet]) => {
|
|
29
|
+
return Array.from(bet.entries()).map(([id, betData]) => {
|
|
30
|
+
const betObj = Array.from(betData.entries()).reduce((obj, [key, value]) => {
|
|
31
|
+
obj[key] = value;
|
|
32
|
+
return obj;
|
|
33
|
+
}, {});
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
id,
|
|
37
|
+
...betObj,
|
|
38
|
+
owner,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getPlayerPoints(address) {
|
|
45
|
+
if (!address) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
const points = await this.client.readContract({
|
|
49
|
+
address: this.contractAddress,
|
|
50
|
+
functionName: "get_player_points",
|
|
51
|
+
args: [address],
|
|
52
|
+
});
|
|
53
|
+
return points;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getLeaderboard() {
|
|
57
|
+
const points = await this.client.readContract({
|
|
58
|
+
address: this.contractAddress,
|
|
59
|
+
functionName: "get_points",
|
|
60
|
+
args: [],
|
|
61
|
+
});
|
|
62
|
+
return Array.from(points.entries())
|
|
63
|
+
.map(([address, points]) => ({
|
|
64
|
+
address,
|
|
65
|
+
points: Number(points),
|
|
66
|
+
}))
|
|
67
|
+
.sort((a, b) => b.points - a.points);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async createBet(gameDate, team1, team2, predictedWinner) {
|
|
71
|
+
const txHash = await this.client.writeContract({
|
|
72
|
+
address: this.contractAddress,
|
|
73
|
+
functionName: "create_bet",
|
|
74
|
+
args: [gameDate, team1, team2, predictedWinner],
|
|
75
|
+
});
|
|
76
|
+
const receipt = await this.client.waitForTransactionReceipt({
|
|
77
|
+
hash: txHash,
|
|
78
|
+
status: "FINALIZED",
|
|
79
|
+
interval: 10000,
|
|
80
|
+
});
|
|
81
|
+
return receipt;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async resolveBet(betId) {
|
|
85
|
+
const txHash = await this.client.writeContract({
|
|
86
|
+
address: this.contractAddress,
|
|
87
|
+
functionName: "resolve_bet",
|
|
88
|
+
args: [betId],
|
|
89
|
+
});
|
|
90
|
+
const receipt = await this.client.waitForTransactionReceipt({
|
|
91
|
+
hash: txHash,
|
|
92
|
+
status: "FINALIZED",
|
|
93
|
+
interval: 10000,
|
|
94
|
+
retries: 20,
|
|
95
|
+
});
|
|
96
|
+
return receipt;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default FootballBets;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createClient, createAccount as createGenLayerAccount, generatePrivateKey } from "genlayer-js";
|
|
2
|
+
import { simulator } from "genlayer-js/chains";
|
|
3
|
+
|
|
4
|
+
const accountPrivateKey = localStorage.getItem("accountPrivateKey")
|
|
5
|
+
? localStorage.getItem("accountPrivateKey")
|
|
6
|
+
: null;
|
|
7
|
+
export const account = accountPrivateKey ? createGenLayerAccount(accountPrivateKey) : null;
|
|
8
|
+
|
|
9
|
+
export const createAccount = () => {
|
|
10
|
+
const newAccountPrivateKey = generatePrivateKey();
|
|
11
|
+
localStorage.setItem("accountPrivateKey", newAccountPrivateKey);
|
|
12
|
+
return createGenLayerAccount(newAccountPrivateKey);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const removeAccount = () => {
|
|
16
|
+
localStorage.removeItem("accountPrivateKey");
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const client = createClient({ chain: simulator, account });
|
|
File without changes
|
|
File without changes
|