init
This commit is contained in:
commit
8e3b63d0df
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
lib-cov
|
||||||
|
*.seed
|
||||||
|
*.log
|
||||||
|
*.csv
|
||||||
|
*.dat
|
||||||
|
*.out
|
||||||
|
*.pid
|
||||||
|
*.gz
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
pids
|
||||||
|
logs
|
||||||
|
results
|
||||||
|
tmp
|
||||||
|
|
||||||
|
# Build
|
||||||
|
public/css/main.css
|
||||||
|
|
||||||
|
# Coverage reports
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# API keys and secrets
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Dependency directory
|
||||||
|
node_modules
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# Editors
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# OS metadata
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Ignore built ts files
|
||||||
|
dist/**/*
|
||||||
|
|
||||||
|
# ignore yarn.lock
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
duelfi_validator_logs/*
|
||||||
11
eslint.config.mjs
Normal file
11
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import globals from "globals";
|
||||||
|
import pluginJs from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{files: ["**/*.{js,mjs,cjs,ts}"]},
|
||||||
|
{languageOptions: { globals: globals.browser }},
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
];
|
||||||
3435
package-lock.json
generated
Normal file
3435
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "duelfi_reward_distributor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"server": "ts-node src/index.ts",
|
||||||
|
"server:devnet": "USE_DEVNET=true ts-node src/index.ts",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.13.0",
|
||||||
|
"eslint": "^9.13.0",
|
||||||
|
"globals": "^15.11.0",
|
||||||
|
"typescript": "^5.6.3",
|
||||||
|
"typescript-eslint": "^8.11.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@coral-xyz/anchor": "^0.30.1",
|
||||||
|
"@solana/spl-token": "^0.4.9",
|
||||||
|
"@solana/web3.js": "^1.98.0",
|
||||||
|
"bs58": "^6.0.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"express": "^5.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
371
src/bets.json
Normal file
371
src/bets.json
Normal file
|
|
@ -0,0 +1,371 @@
|
||||||
|
{
|
||||||
|
"address": "Haj94DF925qNRgcoRwQfNsVLKgSmFhG4bjgtvusMkkpD",
|
||||||
|
"metadata": {
|
||||||
|
"name": "bets",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"spec": "0.1.0",
|
||||||
|
"description": "Created with Anchor"
|
||||||
|
},
|
||||||
|
"instructions": [
|
||||||
|
{
|
||||||
|
"name": "close_bet",
|
||||||
|
"discriminator": [
|
||||||
|
185,
|
||||||
|
206,
|
||||||
|
13,
|
||||||
|
184,
|
||||||
|
176,
|
||||||
|
108,
|
||||||
|
140,
|
||||||
|
107
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "bets_list",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bet_vault",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fee_wallet",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "winner",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner_referrer",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joiner_referrer",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_program",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "winner",
|
||||||
|
"type": "pubkey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userid",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "create_bet",
|
||||||
|
"discriminator": [
|
||||||
|
197,
|
||||||
|
42,
|
||||||
|
153,
|
||||||
|
2,
|
||||||
|
59,
|
||||||
|
63,
|
||||||
|
143,
|
||||||
|
246
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bets_list",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bet_vault",
|
||||||
|
"writable": true,
|
||||||
|
"pda": {
|
||||||
|
"seeds": [
|
||||||
|
{
|
||||||
|
"kind": "const",
|
||||||
|
"value": [
|
||||||
|
98,
|
||||||
|
101,
|
||||||
|
116,
|
||||||
|
95,
|
||||||
|
118,
|
||||||
|
97,
|
||||||
|
117,
|
||||||
|
108,
|
||||||
|
116
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "account",
|
||||||
|
"path": "payer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "arg",
|
||||||
|
"path": "game_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "arg",
|
||||||
|
"path": "_nonce"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_program",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "wager",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "game_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nonce",
|
||||||
|
"type": "u64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "initialize",
|
||||||
|
"discriminator": [
|
||||||
|
175,
|
||||||
|
175,
|
||||||
|
109,
|
||||||
|
31,
|
||||||
|
13,
|
||||||
|
152,
|
||||||
|
155,
|
||||||
|
237
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "bets_list",
|
||||||
|
"writable": true,
|
||||||
|
"pda": {
|
||||||
|
"seeds": [
|
||||||
|
{
|
||||||
|
"kind": "const",
|
||||||
|
"value": [
|
||||||
|
98,
|
||||||
|
101,
|
||||||
|
116,
|
||||||
|
115,
|
||||||
|
95,
|
||||||
|
108,
|
||||||
|
105,
|
||||||
|
115,
|
||||||
|
116
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_program",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "join_bet",
|
||||||
|
"discriminator": [
|
||||||
|
69,
|
||||||
|
116,
|
||||||
|
82,
|
||||||
|
26,
|
||||||
|
144,
|
||||||
|
192,
|
||||||
|
58,
|
||||||
|
238
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "bet_vault",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_program",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "game_id",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "refund_bet",
|
||||||
|
"discriminator": [
|
||||||
|
209,
|
||||||
|
182,
|
||||||
|
226,
|
||||||
|
96,
|
||||||
|
55,
|
||||||
|
121,
|
||||||
|
83,
|
||||||
|
183
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "bets_list",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "bet_vault",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "system_program",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "pubkey"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "BetVault",
|
||||||
|
"discriminator": [
|
||||||
|
103,
|
||||||
|
78,
|
||||||
|
21,
|
||||||
|
234,
|
||||||
|
18,
|
||||||
|
250,
|
||||||
|
230,
|
||||||
|
209
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BetsList",
|
||||||
|
"discriminator": [
|
||||||
|
231,
|
||||||
|
234,
|
||||||
|
50,
|
||||||
|
58,
|
||||||
|
81,
|
||||||
|
179,
|
||||||
|
239,
|
||||||
|
117
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": 6000,
|
||||||
|
"name": "CustomError",
|
||||||
|
"msg": "Custom error message"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "BetVault",
|
||||||
|
"type": {
|
||||||
|
"kind": "struct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "game_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "pubkey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joiner",
|
||||||
|
"type": "pubkey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joiner_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wager",
|
||||||
|
"type": "u64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BetsList",
|
||||||
|
"type": {
|
||||||
|
"kind": "struct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "bets",
|
||||||
|
"type": {
|
||||||
|
"vec": "pubkey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"name": "FEE_COLLECTOR",
|
||||||
|
"type": "string",
|
||||||
|
"value": "\"9esrj2X33pr5og6fdkDMjaW6fdnnb9hT1cWshamxTdL4\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SEED",
|
||||||
|
"type": "string",
|
||||||
|
"value": "\"anchor\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
377
src/bets.ts
Normal file
377
src/bets.ts
Normal file
|
|
@ -0,0 +1,377 @@
|
||||||
|
/**
|
||||||
|
* Program IDL in camelCase format in order to be used in JS/TS.
|
||||||
|
*
|
||||||
|
* Note that this is only a type helper and is not the actual IDL. The original
|
||||||
|
* IDL can be found at `target/idl/bets.json`.
|
||||||
|
*/
|
||||||
|
export type Bets = {
|
||||||
|
"address": "Haj94DF925qNRgcoRwQfNsVLKgSmFhG4bjgtvusMkkpD",
|
||||||
|
"metadata": {
|
||||||
|
"name": "bets",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"spec": "0.1.0",
|
||||||
|
"description": "Created with Anchor"
|
||||||
|
},
|
||||||
|
"instructions": [
|
||||||
|
{
|
||||||
|
"name": "closeBet",
|
||||||
|
"discriminator": [
|
||||||
|
185,
|
||||||
|
206,
|
||||||
|
13,
|
||||||
|
184,
|
||||||
|
176,
|
||||||
|
108,
|
||||||
|
140,
|
||||||
|
107
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "betsList",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "betVault",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "feeWallet",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "winner",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ownerReferrer",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joinerReferrer",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "systemProgram",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "winner",
|
||||||
|
"type": "pubkey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userid",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "createBet",
|
||||||
|
"discriminator": [
|
||||||
|
197,
|
||||||
|
42,
|
||||||
|
153,
|
||||||
|
2,
|
||||||
|
59,
|
||||||
|
63,
|
||||||
|
143,
|
||||||
|
246
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "betsList",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "betVault",
|
||||||
|
"writable": true,
|
||||||
|
"pda": {
|
||||||
|
"seeds": [
|
||||||
|
{
|
||||||
|
"kind": "const",
|
||||||
|
"value": [
|
||||||
|
98,
|
||||||
|
101,
|
||||||
|
116,
|
||||||
|
95,
|
||||||
|
118,
|
||||||
|
97,
|
||||||
|
117,
|
||||||
|
108,
|
||||||
|
116
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "account",
|
||||||
|
"path": "payer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "arg",
|
||||||
|
"path": "gameId"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "arg",
|
||||||
|
"path": "nonce"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "systemProgram",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "wager",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gameId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nonce",
|
||||||
|
"type": "u64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "initialize",
|
||||||
|
"discriminator": [
|
||||||
|
175,
|
||||||
|
175,
|
||||||
|
109,
|
||||||
|
31,
|
||||||
|
13,
|
||||||
|
152,
|
||||||
|
155,
|
||||||
|
237
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "betsList",
|
||||||
|
"writable": true,
|
||||||
|
"pda": {
|
||||||
|
"seeds": [
|
||||||
|
{
|
||||||
|
"kind": "const",
|
||||||
|
"value": [
|
||||||
|
98,
|
||||||
|
101,
|
||||||
|
116,
|
||||||
|
115,
|
||||||
|
95,
|
||||||
|
108,
|
||||||
|
105,
|
||||||
|
115,
|
||||||
|
116
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "systemProgram",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joinBet",
|
||||||
|
"discriminator": [
|
||||||
|
69,
|
||||||
|
116,
|
||||||
|
82,
|
||||||
|
26,
|
||||||
|
144,
|
||||||
|
192,
|
||||||
|
58,
|
||||||
|
238
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "betVault",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "systemProgram",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "userId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "gameId",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "refundBet",
|
||||||
|
"discriminator": [
|
||||||
|
209,
|
||||||
|
182,
|
||||||
|
226,
|
||||||
|
96,
|
||||||
|
55,
|
||||||
|
121,
|
||||||
|
83,
|
||||||
|
183
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "betsList",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "betVault",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "payer",
|
||||||
|
"writable": true,
|
||||||
|
"signer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "systemProgram",
|
||||||
|
"address": "11111111111111111111111111111111"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "pubkey"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "betVault",
|
||||||
|
"discriminator": [
|
||||||
|
103,
|
||||||
|
78,
|
||||||
|
21,
|
||||||
|
234,
|
||||||
|
18,
|
||||||
|
250,
|
||||||
|
230,
|
||||||
|
209
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "betsList",
|
||||||
|
"discriminator": [
|
||||||
|
231,
|
||||||
|
234,
|
||||||
|
50,
|
||||||
|
58,
|
||||||
|
81,
|
||||||
|
179,
|
||||||
|
239,
|
||||||
|
117
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": 6000,
|
||||||
|
"name": "customError",
|
||||||
|
"msg": "Custom error message"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "betVault",
|
||||||
|
"type": {
|
||||||
|
"kind": "struct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "gameId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "pubkey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ownerId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joiner",
|
||||||
|
"type": "pubkey"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "joinerId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wager",
|
||||||
|
"type": "u64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "betsList",
|
||||||
|
"type": {
|
||||||
|
"kind": "struct",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "bets",
|
||||||
|
"type": {
|
||||||
|
"vec": "pubkey"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": [
|
||||||
|
{
|
||||||
|
"name": "feeCollector",
|
||||||
|
"type": "string",
|
||||||
|
"value": "\"9esrj2X33pr5og6fdkDMjaW6fdnnb9hT1cWshamxTdL4\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "seed",
|
||||||
|
"type": "string",
|
||||||
|
"value": "\"anchor\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
354
src/index.ts
Normal file
354
src/index.ts
Normal file
|
|
@ -0,0 +1,354 @@
|
||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
import cors from 'cors';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
import { close, connection, fetchBets, refundBet } from './solana';
|
||||||
|
import { log } from './logging_help';
|
||||||
|
import { duelfiApiUrl, GetReferreeWallet } from './shared';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = process.env.USE_DEVNET ? 3033 : 3032;
|
||||||
|
|
||||||
|
app.use(cors({ origin: '*', methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] }));
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// Add proxy endpoint for PHP API requests
|
||||||
|
// app.get('/api/*', async (req: Request, res: Response) => {
|
||||||
|
// const path = req.path.replace('/api/', '');
|
||||||
|
// const url = `${duelfiApiUrl}${path}${req.url.includes('?') ? '&' : '?'}${new URLSearchParams(req.query as any).toString()}`;
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// const response = await fetch(url);
|
||||||
|
// const data = await response.text();
|
||||||
|
// res.send(data);
|
||||||
|
// } catch (error) {
|
||||||
|
// log(`Error proxying request to ${url}: ${error}`, 'server');
|
||||||
|
// res.status(500).send('Error proxying request');
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
type PlayerSubmission = {
|
||||||
|
username: string;
|
||||||
|
game_id: string;
|
||||||
|
wager: number;
|
||||||
|
winner: string;
|
||||||
|
score: number;
|
||||||
|
leaderboard: any;
|
||||||
|
submittedAt: Date;
|
||||||
|
publicKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GameData = {
|
||||||
|
master?: PlayerSubmission;
|
||||||
|
client?: PlayerSubmission;
|
||||||
|
processing?: boolean;
|
||||||
|
finalized?: boolean;
|
||||||
|
winner?: 'master' | 'client';
|
||||||
|
};
|
||||||
|
|
||||||
|
const games: Map<string, GameData> = new Map();
|
||||||
|
const timeouts: Map<string, NodeJS.Timeout> = new Map();
|
||||||
|
const TIMEOUT_MS = 5_000;
|
||||||
|
|
||||||
|
app.get('/finalize', async (req: Request, res: Response) => {
|
||||||
|
const { gameAddress, winnerPublicKey, winnerUsername, wager, game_id, loserUsername, masterScore, clientScore, masterId, clientId } = req.query;
|
||||||
|
|
||||||
|
const winnerKey = masterId === winnerUsername ? 'master' : 'client';
|
||||||
|
|
||||||
|
if (!gameAddress || !winnerPublicKey || !winnerUsername || !wager || !game_id || !loserUsername || !masterScore || !clientScore || !masterId || !clientId) {
|
||||||
|
log(`Invalid payload: ${JSON.stringify(req.query)}`, 'server');
|
||||||
|
return res.status(400).json({ error: 'Invalid payload' });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
log(`Finalizing game ${gameAddress}. Winner: ${winnerKey}(${winnerUsername})`, `${gameAddress}`);
|
||||||
|
const tx = await close(gameAddress, winnerPublicKey as string, winnerUsername as string, loserUsername as string);
|
||||||
|
log(`Game closed: ${tx}, winner: ${winnerKey}, reward_tx: ${tx}`, `${gameAddress}`);
|
||||||
|
await submitGameHistory(gameAddress, winnerKey, tx, masterId, clientId, game_id as string, wager as number, masterScore as number, clientScore as number);
|
||||||
|
return res.json({ message: 'Game finalized and winner rewarded', tx: tx });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/submit', async (req: Request, res: Response) => {
|
||||||
|
const { gameAddress, playerType, username, winner, score, game_id, wager, leaderboard, publicKey } = req.body;
|
||||||
|
|
||||||
|
if (!gameAddress || !['master', 'client'].includes(playerType) || !publicKey) {
|
||||||
|
return res.status(400).json({ error: 'Invalid payload' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerKey = playerType as 'master' | 'client';
|
||||||
|
const otherKey = playerKey === 'master' ? 'client' : 'master';
|
||||||
|
|
||||||
|
const submission: PlayerSubmission = {
|
||||||
|
username,
|
||||||
|
game_id,
|
||||||
|
wager,
|
||||||
|
winner,
|
||||||
|
score,
|
||||||
|
leaderboard,
|
||||||
|
submittedAt: new Date(),
|
||||||
|
publicKey
|
||||||
|
};
|
||||||
|
|
||||||
|
const game = games.get(gameAddress) || {};
|
||||||
|
|
||||||
|
if (game.processing) {
|
||||||
|
log(`Game ${gameAddress} is already being processed.`, gameAddress);
|
||||||
|
return res.status(200).json({ message: 'Game is already being processed' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.finalized) {
|
||||||
|
log(`Game ${gameAddress} already finalized.`, gameAddress);
|
||||||
|
return res.status(200).json({ message: 'Game already finalized' });
|
||||||
|
}
|
||||||
|
|
||||||
|
game[playerKey] = submission;
|
||||||
|
games.set(gameAddress, game);
|
||||||
|
|
||||||
|
log(`${playerKey} submitted for ${gameAddress}. Winner: "${winner}"`, gameAddress);
|
||||||
|
|
||||||
|
const bothSubmitted = game.master && game.client;
|
||||||
|
const bothWinnersExist = bothSubmitted && game.master!.winner && game.client!.winner;
|
||||||
|
|
||||||
|
if (bothWinnersExist) {
|
||||||
|
if (game.master!.winner === game.client!.winner) {
|
||||||
|
return await finalizeGame(gameAddress, game.master!.winner);
|
||||||
|
} else {
|
||||||
|
return res.status(409).json({ error: 'Winner mismatch between players' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (game.master!.winner || game.client!.winner) {
|
||||||
|
log(`Starting timeout for ${gameAddress} (${playerKey} claims win, waiting for ${otherKey})`, gameAddress);
|
||||||
|
const timeout = setTimeout(async () => {
|
||||||
|
if (game.finalized) return;
|
||||||
|
|
||||||
|
log(`${otherKey} did not submit in time. ${playerKey} wins by timeout.`, gameAddress);
|
||||||
|
await finalizeGame(gameAddress, playerKey);
|
||||||
|
}, TIMEOUT_MS);
|
||||||
|
|
||||||
|
timeouts.set(gameAddress, timeout);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log(`Error starting timeout for ${gameAddress}: ${err}`, gameAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle player left (only one player submitted, winner is present)
|
||||||
|
const otherPlayerMissing = !game[otherKey];
|
||||||
|
const thisPlayerHasWinner = !!winner;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return res.json({ message: 'Submission accepted' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/requestRefund', async (req: Request, res: Response) => {
|
||||||
|
const { gameAddress } = req.query;
|
||||||
|
|
||||||
|
if (!gameAddress) {
|
||||||
|
log(`Refund request rejected: Invalid payload`, gameAddress);
|
||||||
|
return res.status(400).json({ error: 'Invalid payload' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const game = games.get(gameAddress);
|
||||||
|
if (!game) {
|
||||||
|
log(`Refund request rejected: Game ${gameAddress} not found`, gameAddress);
|
||||||
|
return res.status(400).json({ error: 'Game not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const twoMinutesAgo = now - (50 * 1000);
|
||||||
|
|
||||||
|
if (!game.master && !game.client) {
|
||||||
|
log(`Refund request rejected for ${gameAddress}: Game has no players`, gameAddress);
|
||||||
|
return res.status(400).json({ error: 'Game has no players' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const masterSubmittedRecently = game.master?.submittedAt && game.master.submittedAt.getTime() > twoMinutesAgo;
|
||||||
|
const clientSubmittedRecently = game.client?.submittedAt && game.client.submittedAt.getTime() > twoMinutesAgo;
|
||||||
|
|
||||||
|
if (masterSubmittedRecently && clientSubmittedRecently) {
|
||||||
|
log(`Refund request rejected for ${gameAddress}: Both players have submitted recently`, gameAddress);
|
||||||
|
return res.status(400).json({ error: 'Both players have submitted recently' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const winner: 'master' | 'client' = masterSubmittedRecently ? 'master' : 'client';
|
||||||
|
|
||||||
|
await finalizeGame(gameAddress, winner);
|
||||||
|
log(`Refund request accepted for ${gameAddress}. Winner: ${winner}`, gameAddress);
|
||||||
|
return res.json({ message: 'Refund requested' });
|
||||||
|
});
|
||||||
|
|
||||||
|
async function finalizeGame(gameAddress: string, winnerKey: string) {
|
||||||
|
const game = games.get(gameAddress);
|
||||||
|
if (!game || game.finalized) return;
|
||||||
|
|
||||||
|
let otherKey = winnerKey === 'master' ? 'client' : 'master';
|
||||||
|
let winner:PlayerSubmission = game[winnerKey]!;
|
||||||
|
let loser:PlayerSubmission = game[otherKey]!;
|
||||||
|
|
||||||
|
game.winner = winnerKey as 'master' | 'client';
|
||||||
|
games.set(gameAddress, game);
|
||||||
|
|
||||||
|
let success = false;
|
||||||
|
let attempts = 10;
|
||||||
|
|
||||||
|
while (!success && attempts > 0) {
|
||||||
|
try {
|
||||||
|
if(game.processing){
|
||||||
|
log(`Game ${gameAddress} is already being processed.`, gameAddress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _winner_key = winnerKey;
|
||||||
|
if(!winner){
|
||||||
|
log(`Winner (${winnerKey}) was undefined, Loser is (${!loser ? 'null' : loser.username ?? "no username"})`, gameAddress);
|
||||||
|
log(`Winner was undefined ${winnerKey}, Loser is ${loser.username}`, 'server');
|
||||||
|
|
||||||
|
winner = loser;
|
||||||
|
_winner_key = otherKey;
|
||||||
|
}
|
||||||
|
log(`Finalizing game ${gameAddress}. Winner: ${_winner_key} (${winner.username??'na'}) pubkey: ${winner.publicKey}`, gameAddress);
|
||||||
|
game.processing = true;
|
||||||
|
|
||||||
|
const tx = await close(gameAddress, winner.publicKey, winner.username, loser.username);
|
||||||
|
log(`Game closed: ${tx}, winner: ${_winner_key}, reward_tx: ${tx}`, 'server');
|
||||||
|
await submitGameHistory(gameAddress, _winner_key, tx, game.master?.username ?? 'na', game.client?.username ?? 'na', game.master?.game_id ?? 'na', game.master?.wager ?? 0, game.master?.score ?? 0, game.client?.score ?? 0);
|
||||||
|
game.processing = false;
|
||||||
|
game.finalized = true;
|
||||||
|
success = true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
log(`Error submitting game history: ${err}, retrying...`, gameAddress);
|
||||||
|
|
||||||
|
// Wait 1 second before retrying
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
attempts--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!success && attempts <= 0){
|
||||||
|
const tx = await refundBet(gameAddress);
|
||||||
|
log(`Refunded bet ${gameAddress}. Tx: ${tx}`, gameAddress);
|
||||||
|
try{
|
||||||
|
await submitGameHistory(gameAddress, winnerKey, tx, game.master?.username ?? 'na', game.client?.username ?? 'na', game.master?.game_id ?? 'na', game.master?.wager ?? 0, game.master?.score ?? 0, game.client?.score ?? 0);
|
||||||
|
}catch(err){
|
||||||
|
log(`Error submitting game history: ${err}, forcing refund...`, gameAddress);
|
||||||
|
await submitGameHistory(gameAddress, winnerKey, tx, 'na', 'na', 'na', 0, 0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeouts.has(gameAddress)) {
|
||||||
|
clearTimeout(timeouts.get(gameAddress)!);
|
||||||
|
timeouts.delete(gameAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { message: 'Game finalized and winner rewarded' };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitGameHistory(gameAddress: string, winnerKey: string, tx: string, master_id: string, client_id: string, game_id: string, wager: number, master_score: number, client_score: number) {
|
||||||
|
|
||||||
|
const winner_referree_wallet = await GetReferreeWallet(master_id);
|
||||||
|
const loser_referree_wallet = await GetReferreeWallet(client_id);
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
address: gameAddress,
|
||||||
|
game: game_id,
|
||||||
|
master_score: master_score.toString(),
|
||||||
|
client_score: client_score.toString(),
|
||||||
|
master_id: master_id,
|
||||||
|
client_id: client_id,
|
||||||
|
winner: winnerKey,
|
||||||
|
wager: wager.toString(),
|
||||||
|
reward_tx: tx,
|
||||||
|
winner_referree_wallet: winner_referree_wallet.toBase58(),
|
||||||
|
loser_referree_wallet: loser_referree_wallet.toBase58()
|
||||||
|
});
|
||||||
|
const url = `${duelfiApiUrl}add_game_history.php?${params.toString()}`;
|
||||||
|
|
||||||
|
const result = await fetch(url).then(res => res.text());
|
||||||
|
log(`Game history submitted: ${url}`, gameAddress);
|
||||||
|
log(`Game history response: ${result}`, gameAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bets = [];
|
||||||
|
const BETS_REFRESH_INTERVAL = 10000; // 10 seconds
|
||||||
|
let TIME_SINCE_LAST_FETCH = 0;
|
||||||
|
let been_asleep = false;
|
||||||
|
async function updateBets() {
|
||||||
|
if((Date.now() - TIME_SINCE_LAST_FETCH )> 10000){
|
||||||
|
if(!been_asleep){
|
||||||
|
log('Going to sleep', 'solana');
|
||||||
|
been_asleep = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bets = await fetchBets();
|
||||||
|
//log('Bets cache updated');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize bets cache and start periodic updates
|
||||||
|
updateBets();
|
||||||
|
setInterval(updateBets, BETS_REFRESH_INTERVAL);
|
||||||
|
|
||||||
|
app.get('/fetchBets', async (_req: Request, res: Response) => {
|
||||||
|
TIME_SINCE_LAST_FETCH = Date.now();
|
||||||
|
if(been_asleep){
|
||||||
|
log('Waking up from sleep', 'solana');
|
||||||
|
await updateBets();
|
||||||
|
been_asleep = false;
|
||||||
|
}
|
||||||
|
return res.json(bets);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/fetchBet', async (_req: Request, res: Response) => {
|
||||||
|
|
||||||
|
const {address} = _req.query;
|
||||||
|
|
||||||
|
TIME_SINCE_LAST_FETCH = Date.now();
|
||||||
|
if(been_asleep){
|
||||||
|
log('Waking up from sleep', 'solana');
|
||||||
|
await updateBets();
|
||||||
|
been_asleep = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bet = bets.find(bet => bet.address === address);
|
||||||
|
|
||||||
|
return res.json(bet);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/getGames', async (_req: Request, res: Response) => {
|
||||||
|
|
||||||
|
let games_list = [];
|
||||||
|
|
||||||
|
for (const [gameAddress, game] of games.entries()) {
|
||||||
|
games_list.push({
|
||||||
|
gameAddress,
|
||||||
|
game
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res.json({count: games_list.length, games: games_list});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
log(`Server running at http://localhost:${port}`, 'server');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to check games periodically
|
||||||
|
async function checkGames() {
|
||||||
|
const now = Date.now();
|
||||||
|
const twoMinutesAgo = now - (2 * 60 * 1000);
|
||||||
|
|
||||||
|
for (const [gameAddress, game] of games.entries()) {
|
||||||
|
if(game.finalized){
|
||||||
|
const hasGameHistory = await fetch(`${duelfiApiUrl}get_game_completed.php?address=${gameAddress}`);
|
||||||
|
const hasGameHistoryText = await hasGameHistory.text();
|
||||||
|
if(hasGameHistoryText == "0"){
|
||||||
|
log(`Game ${gameAddress} has no game history. Finalizing again.`, gameAddress);
|
||||||
|
await finalizeGame(gameAddress, game.winner!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start periodic game checking
|
||||||
|
setInterval(checkGames, 10000); // Check every 10 seconds
|
||||||
20
src/logging_help.ts
Normal file
20
src/logging_help.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export function log(message: string, filename: string) {
|
||||||
|
console.log(filename + " : " + message);
|
||||||
|
const workingPath = process.cwd();
|
||||||
|
const logDir = path.join(workingPath, 'duelfi_validator_logs');
|
||||||
|
const logFile = path.join(logDir, `${filename}.txt`);
|
||||||
|
|
||||||
|
// Create directory if it doesn't exist
|
||||||
|
if (!fs.existsSync(logDir)) {
|
||||||
|
fs.mkdirSync(logDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append message with timestamp to the log file
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const logMessage = `[${timestamp}] ${message}\n`;
|
||||||
|
|
||||||
|
fs.appendFileSync(logFile, logMessage);
|
||||||
|
}
|
||||||
37
src/shared.ts
Normal file
37
src/shared.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { clusterApiUrl, PublicKey } from "@solana/web3.js";
|
||||||
|
import { log } from "./logging_help";
|
||||||
|
// export const clusterUrl = "https://tiniest-cold-darkness.solana-mainnet.quiknode.pro/72332d636ff78d498b880bd8fdc3eb646c827da8/";
|
||||||
|
// export const clusterUrl = "https://go.getblock.io/908837801b534ae7a6f0869fc44cc567";
|
||||||
|
export const mainnetClusterUrl = "https://solana-mainnet.core.chainstack.com/c54e14eef17693283a0323efcc4ce731";
|
||||||
|
export const devnetClusterUrl = clusterApiUrl("devnet");
|
||||||
|
|
||||||
|
export const feeWallet = new PublicKey("9esrj2X33pr5og6fdkDMjaW6fdnnb9hT1cWshamxTdL4");
|
||||||
|
// Default to mainnet
|
||||||
|
export const clusterUrl = process.env.USE_DEVNET ? devnetClusterUrl : mainnetClusterUrl;
|
||||||
|
|
||||||
|
export const duelfiApiUrl = "https://api.duelfi.io/v1/";
|
||||||
|
|
||||||
|
export const testerSk = [0,86,239,216,67,18,45,223,17,96,119,58,187,90,175,61,72,117,44,13,224,255,64,74,222,14,50,134,240,250,14,212,13,59,115,13,19,107,33,227,1,184,184,96,20,214,181,23,53,244,82,197,36,189,83,82,134,211,83,200,67,14,143,90];
|
||||||
|
export const cocSk = [202,150,67,41,155,133,176,172,9,100,150,190,239,37,69,73,18,16,76,65,164,197,99,134,240,151,112,65,61,122,95,41,9,44,6,237,108,123,86,90,144,27,1,160,101,95,239,35,53,91,195,220,22,214,2,84,132,37,20,236,133,242,104,197];
|
||||||
|
|
||||||
|
export function getRandomInt(max) {
|
||||||
|
return Math.floor(Math.random() * max);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function GetReferreeWallet(id:string):Promise<PublicKey> {
|
||||||
|
const get_winner_referee_wallet = await fetch(`${duelfiApiUrl}get_referree_wallet.php?id=${id}`)
|
||||||
|
|
||||||
|
const winner_referee_wallet_text = (await get_winner_referee_wallet.text()).replace(' ','');
|
||||||
|
let winner_referree_wallet;
|
||||||
|
log(`winner_referee_wallet: ${winner_referee_wallet_text}`, "solana");
|
||||||
|
|
||||||
|
if(winner_referee_wallet_text.length < 10){
|
||||||
|
log(`No winner referee wallet found for ${id}`, "solana");
|
||||||
|
winner_referree_wallet = feeWallet;
|
||||||
|
}else{
|
||||||
|
winner_referree_wallet = new PublicKey(winner_referee_wallet_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
return winner_referree_wallet;
|
||||||
|
}
|
||||||
90
src/solana.ts
Normal file
90
src/solana.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { AnchorProvider, Wallet, Program } from "@coral-xyz/anchor";
|
||||||
|
import { Keypair, Connection, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
|
||||||
|
import { Bets } from "./bets";
|
||||||
|
import { testerSk, cocSk, clusterUrl, duelfiApiUrl, feeWallet, GetReferreeWallet } from "./shared";
|
||||||
|
import { log } from "./logging_help";
|
||||||
|
|
||||||
|
const IDL = require("./bets.json");
|
||||||
|
const keypair = Keypair.fromSecretKey(Uint8Array.from(cocSk));
|
||||||
|
|
||||||
|
export const connection = new Connection(clusterUrl);
|
||||||
|
const provider = new AnchorProvider(connection, new Wallet(keypair));
|
||||||
|
|
||||||
|
const program:Program<Bets> = new Program<Bets>(IDL,provider);
|
||||||
|
let bet_list_pda: PublicKey;
|
||||||
|
|
||||||
|
async function initialize() {
|
||||||
|
[bet_list_pda] = await PublicKey.findProgramAddress([Buffer.from("bets_list")], program.programId);
|
||||||
|
log(`init on ${clusterUrl}\nBets list PDA : ${bet_list_pda}`, "solana");
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
export async function fetchBets(){
|
||||||
|
let bets = [];
|
||||||
|
|
||||||
|
if(!bet_list_pda){
|
||||||
|
log("Bets list PDA not found, initializing", "solana");
|
||||||
|
await initialize();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const betsList = await program.account.betsList.fetch(bet_list_pda);
|
||||||
|
if (betsList && betsList.bets) {
|
||||||
|
for (const betPubkey of betsList.bets) {
|
||||||
|
try {
|
||||||
|
const bet = await program.account.betVault.fetch(betPubkey);
|
||||||
|
log(`Fetched bet: ${betPubkey}`, "solana");
|
||||||
|
bets.push({
|
||||||
|
address: betPubkey.toString(),
|
||||||
|
id: bet.gameId,
|
||||||
|
owner: bet.owner.toBase58(),
|
||||||
|
owner_id: bet.ownerId,
|
||||||
|
joiner: bet.joiner ? bet.joiner.toBase58() : "Open",
|
||||||
|
joiner_id: bet.joinerId,
|
||||||
|
wager: bet.wager.toNumber() / LAMPORTS_PER_SOL
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
log(`Error fetching bet ${betPubkey}: ${err}`, "solana");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log(`Error fetching bets list: ${err}`, "solana");
|
||||||
|
}
|
||||||
|
return bets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refundBet(bet:string):Promise<string>{
|
||||||
|
const betAcc = await program.account.betVault.fetch(bet);
|
||||||
|
const tx =await program.methods.closeBet(new PublicKey(betAcc.owner), betAcc.ownerId).accounts({
|
||||||
|
betVault: bet,
|
||||||
|
betsList: bet_list_pda,
|
||||||
|
winner: betAcc.owner,
|
||||||
|
feeWallet: feeWallet
|
||||||
|
}).rpc();
|
||||||
|
log(`refund tx: ${tx}`, "solana");
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function close(bet:string, winner:string, uid:string, loser:string):Promise<string>{
|
||||||
|
log(`Closing ${bet}`, "solana");
|
||||||
|
|
||||||
|
log(`getting referrees for ${bet}`, "solana");
|
||||||
|
|
||||||
|
const winner_referree_wallet = await GetReferreeWallet(uid);
|
||||||
|
const loser_referree_wallet = await GetReferreeWallet(loser);
|
||||||
|
|
||||||
|
const tx = await program.methods.closeBet(new PublicKey(winner), uid).accounts({
|
||||||
|
betVault: bet,
|
||||||
|
betsList: bet_list_pda,
|
||||||
|
winner: winner,
|
||||||
|
feeWallet: feeWallet,
|
||||||
|
ownerReferrer: feeWallet,
|
||||||
|
joinerReferrer: feeWallet
|
||||||
|
}).rpc();
|
||||||
|
await connection.confirmTransaction(tx, 'confirmed');
|
||||||
|
log(`close tx: ${tx}`, "solana");
|
||||||
|
return tx;
|
||||||
|
|
||||||
|
}
|
||||||
12
src/types.ts
Normal file
12
src/types.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export type PlayerSubmission = {
|
||||||
|
username: string;
|
||||||
|
winner: string;
|
||||||
|
leaderboard: any; // You can strongly type this if you want
|
||||||
|
submittedAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GameData = {
|
||||||
|
master?: PlayerSubmission;
|
||||||
|
client?: PlayerSubmission;
|
||||||
|
validated?: boolean;
|
||||||
|
};
|
||||||
11
start-server.sh
Executable file
11
start-server.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Load NVM
|
||||||
|
export NVM_DIR="/root/.nvm"
|
||||||
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||||
|
|
||||||
|
# Navigate to the project directory
|
||||||
|
cd /root/duelfi/duelfi_game_validator
|
||||||
|
|
||||||
|
# Run the server
|
||||||
|
npm run server
|
||||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"rootDir": "./src",
|
||||||
|
"target": "es6",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"lib": ["es2015"]
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user