rematch proto
This commit is contained in:
parent
c37f8f7457
commit
de435d89aa
115
package-lock.json
generated
115
package-lock.json
generated
|
|
@ -12,6 +12,7 @@
|
||||||
"@fontsource/manrope": "^5.2.5",
|
"@fontsource/manrope": "^5.2.5",
|
||||||
"@privy-io/react-auth": "^2.7.2",
|
"@privy-io/react-auth": "^2.7.2",
|
||||||
"@solana/web3.js": "^1.98.0",
|
"@solana/web3.js": "^1.98.0",
|
||||||
|
"axios": "^1.8.4",
|
||||||
"bs58": "^6.0.0",
|
"bs58": "^6.0.0",
|
||||||
"ethers": "^6.13.5",
|
"ethers": "^6.13.5",
|
||||||
"next": "15.2.4",
|
"next": "15.2.4",
|
||||||
|
|
@ -4177,6 +4178,12 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/atomic-sleep": {
|
"node_modules/atomic-sleep": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||||
|
|
@ -4212,6 +4219,17 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||||
|
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
|
|
@ -4438,7 +4456,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -4641,6 +4658,18 @@
|
||||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
|
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "2.20.3",
|
"version": "2.20.3",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
|
@ -4906,6 +4935,15 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/destr": {
|
"node_modules/destr": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz",
|
||||||
|
|
@ -4963,7 +5001,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
|
|
@ -5122,7 +5159,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -5132,7 +5168,6 @@
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -5170,7 +5205,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
|
|
@ -5183,7 +5217,6 @@
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -6045,6 +6078,26 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
|
|
@ -6061,11 +6114,25 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
|
@ -6115,7 +6182,6 @@
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.2",
|
"call-bind-apply-helpers": "^1.0.2",
|
||||||
|
|
@ -6140,7 +6206,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
|
|
@ -6228,7 +6293,6 @@
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -6324,7 +6388,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -6337,7 +6400,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
|
|
@ -6363,7 +6425,6 @@
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
|
|
@ -7569,7 +7630,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -7616,6 +7676,27 @@
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimalistic-assert": {
|
"node_modules/minimalistic-assert": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||||
|
|
@ -8430,6 +8511,12 @@
|
||||||
"integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==",
|
"integrity": "sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pump": {
|
"node_modules/pump": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"@fontsource/manrope": "^5.2.5",
|
"@fontsource/manrope": "^5.2.5",
|
||||||
"@privy-io/react-auth": "^2.7.2",
|
"@privy-io/react-auth": "^2.7.2",
|
||||||
"@solana/web3.js": "^1.98.0",
|
"@solana/web3.js": "^1.98.0",
|
||||||
|
"axios": "^1.8.4",
|
||||||
"bs58": "^6.0.0",
|
"bs58": "^6.0.0",
|
||||||
"ethers": "^6.13.5",
|
"ethers": "^6.13.5",
|
||||||
"next": "15.2.4",
|
"next": "15.2.4",
|
||||||
|
|
|
||||||
8
public/UnityBuild/bridge_template
Normal file
8
public/UnityBuild/bridge_template
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*UNITY BRIDGE*/
|
||||||
|
function sendMessageToReact(message) {
|
||||||
|
if (window.parent) {
|
||||||
|
window.parent.postMessage(message, "*"); // Replace "*" with your React app's origin for security
|
||||||
|
} else {
|
||||||
|
console.error("Parent window not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -1,122 +1,133 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-us">
|
<html lang="en-us">
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Unity WebGL Player | TetrisMP</title>
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
<link rel="shortcut icon" href="TemplateData/favicon.ico">
|
<title>Unity WebGL Player | TetrisMP</title>
|
||||||
<link rel="stylesheet" href="TemplateData/style.css">
|
<link rel="shortcut icon" href="TemplateData/favicon.ico">
|
||||||
</head>
|
<link rel="stylesheet" href="TemplateData/style.css">
|
||||||
<body>
|
</head>
|
||||||
<div id="unity-container" class="unity-desktop">
|
|
||||||
<canvas id="unity-canvas" width=1280 height=720 tabindex="-1"></canvas>
|
<body>
|
||||||
<div id="unity-loading-bar">
|
<div id="unity-container" class="unity-desktop">
|
||||||
<div id="unity-logo"></div>
|
<canvas id="unity-canvas" width=1280 height=720 tabindex="-1"></canvas>
|
||||||
<div id="unity-progress-bar-empty">
|
<div id="unity-loading-bar">
|
||||||
<div id="unity-progress-bar-full"></div>
|
<div id="unity-logo"></div>
|
||||||
</div>
|
<div id="unity-progress-bar-empty">
|
||||||
</div>
|
<div id="unity-progress-bar-full"></div>
|
||||||
<div id="unity-warning"> </div>
|
|
||||||
<div id="unity-footer">
|
|
||||||
<div id="unity-webgl-logo"></div>
|
|
||||||
<div id="unity-fullscreen-button"></div>
|
|
||||||
<div id="unity-build-title">TetrisMP</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<div id="unity-warning"> </div>
|
||||||
|
<div id="unity-footer">
|
||||||
|
<div id="unity-webgl-logo"></div>
|
||||||
|
<div id="unity-fullscreen-button"></div>
|
||||||
|
<div id="unity-build-title">TetrisMP</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
|
||||||
var container = document.querySelector("#unity-container");
|
var container = document.querySelector("#unity-container");
|
||||||
var canvas = document.querySelector("#unity-canvas");
|
var canvas = document.querySelector("#unity-canvas");
|
||||||
var loadingBar = document.querySelector("#unity-loading-bar");
|
var loadingBar = document.querySelector("#unity-loading-bar");
|
||||||
var progressBarFull = document.querySelector("#unity-progress-bar-full");
|
var progressBarFull = document.querySelector("#unity-progress-bar-full");
|
||||||
var fullscreenButton = document.querySelector("#unity-fullscreen-button");
|
var fullscreenButton = document.querySelector("#unity-fullscreen-button");
|
||||||
var warningBanner = document.querySelector("#unity-warning");
|
var warningBanner = document.querySelector("#unity-warning");
|
||||||
|
|
||||||
// Shows a temporary message banner/ribbon for a few seconds, or
|
// Shows a temporary message banner/ribbon for a few seconds, or
|
||||||
// a permanent error message on top of the canvas if type=='error'.
|
// a permanent error message on top of the canvas if type=='error'.
|
||||||
// If type=='warning', a yellow highlight color is used.
|
// If type=='warning', a yellow highlight color is used.
|
||||||
// Modify or remove this function to customize the visually presented
|
// Modify or remove this function to customize the visually presented
|
||||||
// way that non-critical warnings and error messages are presented to the
|
// way that non-critical warnings and error messages are presented to the
|
||||||
// user.
|
// user.
|
||||||
function unityShowBanner(msg, type) {
|
function unityShowBanner(msg, type) {
|
||||||
function updateBannerVisibility() {
|
function updateBannerVisibility() {
|
||||||
warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
|
warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
|
||||||
}
|
|
||||||
var div = document.createElement('div');
|
|
||||||
div.innerHTML = msg;
|
|
||||||
warningBanner.appendChild(div);
|
|
||||||
if (type == 'error') div.style = 'background: red; padding: 10px;';
|
|
||||||
else {
|
|
||||||
if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
|
|
||||||
setTimeout(function() {
|
|
||||||
warningBanner.removeChild(div);
|
|
||||||
updateBannerVisibility();
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
updateBannerVisibility();
|
|
||||||
}
|
}
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = msg;
|
||||||
|
warningBanner.appendChild(div);
|
||||||
|
if (type == 'error') div.style = 'background: red; padding: 10px;';
|
||||||
|
else {
|
||||||
|
if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
|
||||||
|
setTimeout(function () {
|
||||||
|
warningBanner.removeChild(div);
|
||||||
|
updateBannerVisibility();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
updateBannerVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
var buildUrl = "Build";
|
var buildUrl = "Build";
|
||||||
var loaderUrl = buildUrl + "/prod.loader.js";
|
var loaderUrl = buildUrl + "/prod.loader.js";
|
||||||
var config = {
|
var config = {
|
||||||
dataUrl: buildUrl + "/prod.data",
|
dataUrl: buildUrl + "/prod.data",
|
||||||
frameworkUrl: buildUrl + "/prod.framework.js",
|
frameworkUrl: buildUrl + "/prod.framework.js",
|
||||||
codeUrl: buildUrl + "/prod.wasm",
|
codeUrl: buildUrl + "/prod.wasm",
|
||||||
streamingAssetsUrl: "StreamingAssets",
|
streamingAssetsUrl: "StreamingAssets",
|
||||||
companyName: "DefaultCompany",
|
companyName: "DefaultCompany",
|
||||||
productName: "TetrisMP",
|
productName: "TetrisMP",
|
||||||
productVersion: "1.0",
|
productVersion: "1.0",
|
||||||
showBanner: unityShowBanner,
|
showBanner: unityShowBanner,
|
||||||
};
|
};
|
||||||
|
|
||||||
// By default, Unity keeps WebGL canvas render target size matched with
|
|
||||||
// the DOM size of the canvas element (scaled by window.devicePixelRatio)
|
|
||||||
// Set this to false if you want to decouple this synchronization from
|
|
||||||
// happening inside the engine, and you would instead like to size up
|
|
||||||
// the canvas DOM size and WebGL render target sizes yourself.
|
|
||||||
// config.matchWebGLToCanvasSize = false;
|
|
||||||
|
|
||||||
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
|
|
||||||
// Mobile device style: fill the whole browser client area with the game canvas:
|
|
||||||
|
|
||||||
var meta = document.createElement('meta');
|
|
||||||
meta.name = 'viewport';
|
|
||||||
meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(meta);
|
|
||||||
container.className = "unity-mobile";
|
|
||||||
canvas.className = "unity-mobile";
|
|
||||||
|
|
||||||
// To lower canvas resolution on mobile devices to gain some
|
|
||||||
// performance, uncomment the following line:
|
|
||||||
// config.devicePixelRatio = 1;
|
|
||||||
|
|
||||||
|
|
||||||
|
/*UNITY BRIDGE*/
|
||||||
|
function sendMessageToReact(message) {
|
||||||
|
if (window.parent) {
|
||||||
|
window.parent.postMessage(message, "*"); // Replace "*" with your React app's origin for security
|
||||||
} else {
|
} else {
|
||||||
// Desktop style: Render the game canvas in a window that can be maximized to fullscreen:
|
console.error("Parent window not found.");
|
||||||
|
|
||||||
canvas.style.width = "1280px";
|
|
||||||
canvas.style.height = "720px";
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// By default, Unity keeps WebGL canvas render target size matched with
|
||||||
|
// the DOM size of the canvas element (scaled by window.devicePixelRatio)
|
||||||
|
// Set this to false if you want to decouple this synchronization from
|
||||||
|
// happening inside the engine, and you would instead like to size up
|
||||||
|
// the canvas DOM size and WebGL render target sizes yourself.
|
||||||
|
// config.matchWebGLToCanvasSize = false;
|
||||||
|
|
||||||
loadingBar.style.display = "block";
|
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
|
||||||
|
// Mobile device style: fill the whole browser client area with the game canvas:
|
||||||
|
|
||||||
var script = document.createElement("script");
|
var meta = document.createElement('meta');
|
||||||
script.src = loaderUrl;
|
meta.name = 'viewport';
|
||||||
script.onload = () => {
|
meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
|
||||||
createUnityInstance(canvas, config, (progress) => {
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||||
progressBarFull.style.width = 100 * progress + "%";
|
container.className = "unity-mobile";
|
||||||
}).then((unityInstance) => {
|
canvas.className = "unity-mobile";
|
||||||
loadingBar.style.display = "none";
|
|
||||||
fullscreenButton.onclick = () => {
|
|
||||||
unityInstance.SetFullscreen(1);
|
|
||||||
};
|
|
||||||
}).catch((message) => {
|
|
||||||
alert(message);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
document.body.appendChild(script);
|
// To lower canvas resolution on mobile devices to gain some
|
||||||
|
// performance, uncomment the following line:
|
||||||
|
// config.devicePixelRatio = 1;
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
} else {
|
||||||
</html>
|
// Desktop style: Render the game canvas in a window that can be maximized to fullscreen:
|
||||||
|
|
||||||
|
canvas.style.width = "1280px";
|
||||||
|
canvas.style.height = "720px";
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingBar.style.display = "block";
|
||||||
|
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.src = loaderUrl;
|
||||||
|
script.onload = () => {
|
||||||
|
createUnityInstance(canvas, config, (progress) => {
|
||||||
|
progressBarFull.style.width = 100 * progress + "%";
|
||||||
|
}).then((unityInstance) => {
|
||||||
|
loadingBar.style.display = "none";
|
||||||
|
fullscreenButton.onclick = () => {
|
||||||
|
unityInstance.SetFullscreen(1);
|
||||||
|
};
|
||||||
|
}).catch((message) => {
|
||||||
|
alert(message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -93,4 +93,64 @@ body {
|
||||||
|
|
||||||
.modal-exit {
|
.modal-exit {
|
||||||
animation: slide-up 0.2s ease-in;
|
animation: slide-up 0.2s ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Hovering card */
|
||||||
|
/* Ensure the card container is positioned relatively for absolute positioning */
|
||||||
|
.card-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #2D2D2D;
|
||||||
|
border: 1px solid #4B4B4B;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This will ensure that the content (text, images) stays in place */
|
||||||
|
.card-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the profile image */
|
||||||
|
.card-image {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style for the thumbnail image */
|
||||||
|
.card-thumbnail {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Positioning and styling of the 'View TX' button */
|
||||||
|
.view-tx-btn {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #007BFF;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
border-radius: 8px 0 0 8px; /* Rounded left side */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* On hover, slide the card to the left and show the button */
|
||||||
|
.card-container:hover .view-tx-btn {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0); /* Slide the button to the left */
|
||||||
|
}
|
||||||
|
|
|
||||||
205
src/components/GameHistory.tsx
Normal file
205
src/components/GameHistory.tsx
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
import { MouseEventHandler, useEffect, useState } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import { Game } from "@/types/Game";
|
||||||
|
import { games } from "@/data/games";
|
||||||
|
import { WAGER_PRIZE_MULT } from "@/shared/constants";
|
||||||
|
import { EXPLORER_ADDRESS_TEMPLATE } from "@/data/shared";
|
||||||
|
|
||||||
|
interface GameHistory {
|
||||||
|
address: string;
|
||||||
|
master_score: string;
|
||||||
|
client_score: string;
|
||||||
|
winner: string;
|
||||||
|
wager: string;
|
||||||
|
master_id: string;
|
||||||
|
client_id: string;
|
||||||
|
game: string; // game ID
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Opponent {
|
||||||
|
username: string;
|
||||||
|
x_profile_url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GameHistoryModalProps {
|
||||||
|
userId: string | undefined;
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: MouseEventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GameHistoryModal({
|
||||||
|
userId,
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
}: GameHistoryModalProps) {
|
||||||
|
const [gamesHistory, setGamesHistory] = useState<GameHistory[]>([]);
|
||||||
|
const [opponentInfo, setOpponentInfo] = useState<{
|
||||||
|
[key: string]: Opponent;
|
||||||
|
}>({});
|
||||||
|
const [gameImages, setGameImages] = useState<{ [key: string]: string }>({});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen || !userId) return;
|
||||||
|
|
||||||
|
const fetchGames = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await axios.get(
|
||||||
|
`https://vps.playpoolstudios.com/duelfi/api/get_game_history.php?uid=${userId}`
|
||||||
|
);
|
||||||
|
const gameData = res.data || [];
|
||||||
|
setGamesHistory(gameData);
|
||||||
|
|
||||||
|
const opponentIds: string[] = gameData.map((game: GameHistory) =>
|
||||||
|
game.master_id === userId ? game.client_id : game.master_id
|
||||||
|
);
|
||||||
|
const uniqueOpponentIds: string[] = Array.from(new Set(opponentIds));
|
||||||
|
|
||||||
|
const fetchedOpponentInfo: { [key: string]: Opponent } = {};
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
uniqueOpponentIds.map(async (uid) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://vps.playpoolstudios.com/duelfi/api/get_user_by_id.php?id=${uid}`
|
||||||
|
);
|
||||||
|
const { username, x_profile_url } = response.data;
|
||||||
|
fetchedOpponentInfo[uid] = {
|
||||||
|
username,
|
||||||
|
x_profile_url,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch opponent info for", uid, err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setOpponentInfo(fetchedOpponentInfo);
|
||||||
|
|
||||||
|
const gameDataWithImages: { [key: string]: string } = {};
|
||||||
|
await Promise.all(
|
||||||
|
gameData.map(async (gameHistory: GameHistory) => {
|
||||||
|
try {
|
||||||
|
const gameImage = games.find((game: Game) => game.id == gameHistory.game);
|
||||||
|
gameDataWithImages[gameHistory.game] = gameImage?.thumbnail ?? "";
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch game image for", gameHistory.game, err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
setGameImages(gameDataWithImages);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching game history", err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchGames();
|
||||||
|
}, [isOpen, userId]);
|
||||||
|
|
||||||
|
const handleViewTxClick = (address: string) => {
|
||||||
|
// Open the transaction in a new tab (you can modify this URL to dynamically handle the TX)
|
||||||
|
window.open(`${EXPLORER_ADDRESS_TEMPLATE.replace("{address}",address)}`, "_blank");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-0 right-0 h-full w-[400px] bg-[rgb(10,10,10)] shadow-lg z-50 p-6 overflow-y-auto">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h2 className="text-xl font-bold text-white">Game History</h2>
|
||||||
|
<button onClick={onClose} className="text-red-500 font-bold text-lg">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<p className="text-gray-500">Loading...</p>
|
||||||
|
) : gamesHistory.length === 0 ? (
|
||||||
|
<p className="text-gray-500">No games played yet.</p>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{gamesHistory.map((game, idx) => {
|
||||||
|
const isUserMaster = game.master_id === userId;
|
||||||
|
const userScore = isUserMaster
|
||||||
|
? game.master_score
|
||||||
|
: game.client_score;
|
||||||
|
const opponentScore = isUserMaster
|
||||||
|
? game.client_score
|
||||||
|
: game.master_score;
|
||||||
|
const opponentId = isUserMaster
|
||||||
|
? game.client_id
|
||||||
|
: game.master_id;
|
||||||
|
const didUserWin =
|
||||||
|
(isUserMaster && game.winner === "master") ||
|
||||||
|
(!isUserMaster && game.winner === "client");
|
||||||
|
|
||||||
|
const opponent = opponentInfo[opponentId];
|
||||||
|
const profileUrl =
|
||||||
|
opponent?.x_profile_url ||
|
||||||
|
(opponent?.username
|
||||||
|
? `https://vps.playpoolstudios.com/duelfi/profile_picture/${opponent.username}.jpg`
|
||||||
|
: "/duelfiassets/default-avatar.png");
|
||||||
|
|
||||||
|
const gameImageUrl = gameImages[game.game] || "/duelfiassets/default-game-thumbnail.png";
|
||||||
|
|
||||||
|
const wagerAmount = parseFloat(game.wager);
|
||||||
|
const outcomeText = didUserWin
|
||||||
|
? `+${(wagerAmount / 1e8) * 2 * WAGER_PRIZE_MULT} SOL`
|
||||||
|
: `-${(wagerAmount / 1e8)} SOL`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="relative border border-[rgb(30,30,30)] rounded-xl p-3 flex gap-3 items-center group"
|
||||||
|
>
|
||||||
|
{/* Card content */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<p className="text-sm font-semibold text-white">
|
||||||
|
{opponent?.username || "Unknown Opponent"}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
Score: {userScore} - {opponentScore}
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className={`text-sm font-semibold ${
|
||||||
|
didUserWin ? "text-green-500" : "text-gray-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{didUserWin ? "You won" : "You lost"}
|
||||||
|
</p>
|
||||||
|
<p className={`text-xs ${didUserWin ? "text-green-500" : "text-red-500"}`}>
|
||||||
|
{outcomeText}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src={profileUrl}
|
||||||
|
alt="Profile"
|
||||||
|
className="w-10 h-10 rounded-full border border-gray-700 object-cover"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={gameImageUrl}
|
||||||
|
alt="Game Thumbnail"
|
||||||
|
className="w-16 h-16 rounded-md object-cover ml-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* View TX Action */}
|
||||||
|
<div className="absolute top-0 right-0 h-full w-28 bg-blue-500 text-white flex items-center justify-center opacity-0 group-hover:opacity-100 group-hover:translate-x-0 transition-all">
|
||||||
|
<button
|
||||||
|
onClick={() => handleViewTxClick(game.address)}
|
||||||
|
className="px-4 py-2 text-sm font-semibold"
|
||||||
|
>
|
||||||
|
View TX
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
try {
|
try {
|
||||||
const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame);
|
const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame.id, false);
|
||||||
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
||||||
if (tx.length > 5) {
|
if (tx.length > 5) {
|
||||||
toast.success(`Bet created successfully!`, {
|
toast.success(`Bet created successfully!`, {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import OpenGames from "./OpenGames";
|
import OpenGames from "./OpenGames";
|
||||||
import GameModal from "./GameModal";
|
import GameModal from "./GameModal";
|
||||||
import { HowItWorksModal } from "./HowItWorksModal";
|
import { HowItWorksModal } from "./HowItWorksModal";
|
||||||
import YourGames from "./YourGames";
|
import YourGames from "./YourGames";
|
||||||
import { Bet } from "@/types/Bet";
|
import { Bet } from "@/types/Bet";
|
||||||
import { fetchOpenBets } from "@/shared/solana_helpers";
|
import { fetchOpenBets, createBet, joinBet, getVaultByAddress } from "@/shared/solana_helpers";
|
||||||
import { ConnectedSolanaWallet, usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
import { ConnectedSolanaWallet, usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
||||||
|
import { RematchModal } from "./RematchModal";
|
||||||
|
|
||||||
export default function HeroSection() {
|
export default function HeroSection() {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
@ -16,60 +17,284 @@ export default function HeroSection() {
|
||||||
const [bets, setBets] = useState<Bet[]>([]);
|
const [bets, setBets] = useState<Bet[]>([]);
|
||||||
const [solWallet, setSolWallet] = useState<ConnectedSolanaWallet>();
|
const [solWallet, setSolWallet] = useState<ConnectedSolanaWallet>();
|
||||||
const [myActiveBet, setMyActiveBet] = useState<Bet>();
|
const [myActiveBet, setMyActiveBet] = useState<Bet>();
|
||||||
const { wallets, ready } = useSolanaWallets();
|
const [rematch, setRematch] = useState(false);
|
||||||
const {user} = usePrivy();
|
const [lastActiveBet, setLastActiveBet] = useState<Bet>();
|
||||||
|
const [rematchInProgress, setRematchInProgress] = useState(false);
|
||||||
|
const [rematchTxError, setRematchTxError] = useState(false);
|
||||||
|
const [rematchBetAddress, setRematchBetAddress] = useState<string | null>(null);
|
||||||
|
|
||||||
async function updateBets() {
|
const { wallets, ready } = useSolanaWallets();
|
||||||
if (!ready || wallets.length === 0) return;
|
const { user } = usePrivy();
|
||||||
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
if(rematch){
|
||||||
|
if(!lastActiveBet){
|
||||||
|
console.log(`last active bet was null, heres active bet ${myActiveBet}`);
|
||||||
|
}
|
||||||
|
const isOwner = lastActiveBet?.owner_id == user?.id;
|
||||||
|
console.log(`rematch function ${isOwner ? "owner" : "joiner"} owner_id:${lastActiveBet?.owner_id}`);
|
||||||
|
|
||||||
// Find the wallet to use (either Solana wallet or the first available wallet)
|
if(isOwner){
|
||||||
|
handleCreateRematch();
|
||||||
|
}else{
|
||||||
|
handleJoinRematch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},[rematch])
|
||||||
|
|
||||||
|
const game_close_signal = (status: number) => {
|
||||||
|
setRematch(status == 1);
|
||||||
|
setMyActiveBet(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const updateBets = async () => {
|
||||||
|
if (!ready || wallets.length === 0) return;
|
||||||
|
|
||||||
const wallet = wallets.find((_wallet) => _wallet.type === "solana") || wallets[0];
|
const wallet = wallets.find((_wallet) => _wallet.type === "solana") || wallets[0];
|
||||||
setSolWallet(wallet);
|
setSolWallet(wallet);
|
||||||
// Fetch the open bets
|
|
||||||
const fetchedBets = await fetchOpenBets(wallet);
|
|
||||||
|
|
||||||
|
|
||||||
|
const fetchedBets = await fetchOpenBets(wallet);
|
||||||
// Filter out the bets that are not filled (do not have both owner_id and joiner_id)
|
|
||||||
const filteredBets = fetchedBets.filter((bet) => !(bet.owner_id && bet.joiner_id));
|
const filteredBets = fetchedBets.filter((bet) => !(bet.owner_id && bet.joiner_id));
|
||||||
|
|
||||||
// Create a new list for filled bets (bets that have both owner_id and joiner_id set)
|
|
||||||
const filledBets = fetchedBets.filter((bet) => bet.owner_id && bet.joiner_id);
|
const filledBets = fetchedBets.filter((bet) => bet.owner_id && bet.joiner_id);
|
||||||
const activeBet = filledBets.find((bet)=> bet.owner_id == user?.id || bet.joiner_id==user?.id);
|
const activeBet = filledBets.find((bet) => bet.owner_id === user?.id || bet.joiner_id === user?.id);
|
||||||
console.log("My active bet:", activeBet);
|
|
||||||
console.log("Filtered bets:", filteredBets);
|
if(rematch){
|
||||||
console.log("Filled bets:", filledBets); // This is the new list for filled bets
|
setMyActiveBet(undefined);
|
||||||
|
return;
|
||||||
// Set the state for filtered bets and active bet
|
}
|
||||||
setBets(filteredBets);
|
setBets(filteredBets);
|
||||||
setMyActiveBet(activeBet);
|
if (!myActiveBet && lastActiveBet?.address !== activeBet?.address) {
|
||||||
|
setMyActiveBet(activeBet);
|
||||||
|
setLastActiveBet(activeBet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateRematch = async () => {
|
||||||
|
console.log('Creating rematch...');
|
||||||
|
if (!lastActiveBet || !solWallet) return;
|
||||||
|
|
||||||
// If needed, you can also use the filledBets list for something else
|
setRematchInProgress(true);
|
||||||
}
|
setRematchTxError(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Step 1: Create new bet
|
||||||
|
const tx = await createBet(
|
||||||
|
solWallet,
|
||||||
|
user?.id ?? "",
|
||||||
|
lastActiveBet.wager,
|
||||||
|
lastActiveBet.id,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
console.log("Rematch created. Transaction ID:", tx);
|
||||||
|
|
||||||
|
// Step 2: Inform backend of rematch link
|
||||||
|
const set_response = await fetch(
|
||||||
|
`https://vps.playpoolstudios.com/duelfi/api/set_rematch_address.php?address=${lastActiveBet.address}&rematch_address=${tx}`
|
||||||
|
);
|
||||||
|
console.log(await set_response.text());
|
||||||
|
|
||||||
|
// Step 3: Poll until vault account is found
|
||||||
|
const pollUntilFound = async (
|
||||||
|
maxRetries = 10,
|
||||||
|
delayMs = 3000
|
||||||
|
): Promise<any> => {
|
||||||
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
|
const vault = await getVaultByAddress(solWallet, tx);
|
||||||
|
if (vault) return vault;
|
||||||
|
console.log(`Waiting for vault account... (${i + 1}/${maxRetries})`);
|
||||||
|
await new Promise(res => setTimeout(res, delayMs));
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const newBetAcc = await pollUntilFound();
|
||||||
|
|
||||||
|
if (!newBetAcc) {
|
||||||
|
console.error("Failed to retrieve new bet vault after retries.");
|
||||||
|
setRematchTxError(true);
|
||||||
|
} else {
|
||||||
|
// Step 4: Set new active bet
|
||||||
|
setMyActiveBet(newBetAcc);
|
||||||
|
setLastActiveBet(newBetAcc);
|
||||||
|
setRematch(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Create rematch failed:", err);
|
||||||
|
setRematchTxError(true);
|
||||||
|
} finally {
|
||||||
|
setRematchInProgress(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleJoinRematch = async () => {
|
||||||
|
if (!lastActiveBet || !solWallet) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pollForRematchAddress = async (
|
||||||
|
maxRetries = 10,
|
||||||
|
delayMs = 3000
|
||||||
|
): Promise<string | null> => {
|
||||||
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
|
console.log(`Polling rematch address... (${i + 1}/${maxRetries})`);
|
||||||
|
const response = await fetch(
|
||||||
|
`https://vps.playpoolstudios.com/duelfi/api/get_rematch_address.php?address=${lastActiveBet.address}`
|
||||||
|
);
|
||||||
|
const rematchAddress = (await response.text()).trim();
|
||||||
|
|
||||||
|
if (rematchAddress.length > 5) {
|
||||||
|
console.log("Found rematch address:", rematchAddress);
|
||||||
|
return rematchAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(res => setTimeout(res, delayMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Rematch address not found after max retries.");
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rematchAddress = await pollForRematchAddress();
|
||||||
|
|
||||||
|
if (!rematchAddress) return;
|
||||||
|
|
||||||
|
const pollForVault = async (
|
||||||
|
address: string,
|
||||||
|
maxRetries = 10,
|
||||||
|
delayMs = 3000
|
||||||
|
): Promise<any> => {
|
||||||
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
|
console.log(`Polling vault for address ${address}... (${i + 1}/${maxRetries})`);
|
||||||
|
const vault = await getVaultByAddress(solWallet, address);
|
||||||
|
if (vault) return vault;
|
||||||
|
await new Promise(res => setTimeout(res, delayMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Vault not found after max retries.");
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rematchVault:Bet = await pollForVault(rematchAddress);
|
||||||
|
|
||||||
|
if (rematchVault) {
|
||||||
|
const tx = await joinBet(solWallet, user?.id!, rematchVault.id, rematchVault.address);
|
||||||
|
setMyActiveBet(rematchVault);
|
||||||
|
setLastActiveBet(rematchVault);
|
||||||
|
setRematch(false);
|
||||||
|
console.log("Rematch vault set as active.");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error during handleJoinRematch:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// const handleJoinRematch = async () => {
|
||||||
|
// console.log('rejoining rematch');
|
||||||
|
|
||||||
|
// if (!solWallet || !lastActiveBet?.owner_id || !lastActiveBet?.wager) return;
|
||||||
|
|
||||||
|
// setRematchInProgress(true);
|
||||||
|
// setRematchTxError(false);
|
||||||
|
|
||||||
|
// const maxAttempts = 50;
|
||||||
|
// let attempts = 0;
|
||||||
|
|
||||||
|
// const pollForMatchingBet = async (): Promise<Bet | null> => {
|
||||||
|
// try {
|
||||||
|
// const openBets = await fetchOpenBets(solWallet);
|
||||||
|
// const match = openBets.find(
|
||||||
|
// (bet) =>
|
||||||
|
// bet.owner_id === lastActiveBet.owner_id &&
|
||||||
|
// bet.wager === lastActiveBet.wager &&
|
||||||
|
// bet.address !== lastActiveBet.address // to ensure it's a new one
|
||||||
|
// );
|
||||||
|
// return match ?? null;
|
||||||
|
// } catch (err) {
|
||||||
|
// console.error("Error fetching bets:", err);
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const tryJoin = async () => {
|
||||||
|
// while (attempts < maxAttempts) {
|
||||||
|
// const matchingBet = await pollForMatchingBet();
|
||||||
|
// if (matchingBet) {
|
||||||
|
// try {
|
||||||
|
// setRematchBetAddress(matchingBet.address);
|
||||||
|
// const tx = await joinBet(
|
||||||
|
// solWallet,
|
||||||
|
// user?.id ?? "",
|
||||||
|
// lastActiveBet.id ?? "tetris",
|
||||||
|
// matchingBet.address
|
||||||
|
// );
|
||||||
|
// console.log("Joined rematch:", tx);
|
||||||
|
// setRematch(false);
|
||||||
|
// return;
|
||||||
|
// } catch (err) {
|
||||||
|
// console.error("Join rematch failed:", err);
|
||||||
|
// setRematchTxError(true);
|
||||||
|
// return;
|
||||||
|
// } finally {
|
||||||
|
// setRematchInProgress(false);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// attempts++;
|
||||||
|
// await new Promise((res) => setTimeout(res, 5000));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Max attempts reached
|
||||||
|
// setRematchTxError(true);
|
||||||
|
// setRematchInProgress(false);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// tryJoin();
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ready) return;
|
if (!ready) return;
|
||||||
|
|
||||||
updateBets();
|
updateBets();
|
||||||
const interval = setInterval(updateBets, 10000);
|
const interval = setInterval(updateBets, 3500);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [ready]);
|
}, [ready]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMessage = (event: MessageEvent) => {
|
||||||
|
if (event.origin === window.location.origin && typeof event.data === "string") {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
if (message?.type === "gameClose" && typeof message.status === "number") {
|
||||||
|
game_close_signal(message.status);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("JSON parse error from Unity message:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("message", handleMessage);
|
||||||
|
return () => window.removeEventListener("message", handleMessage);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{myActiveBet ? (
|
{myActiveBet && !rematch ? (
|
||||||
// Render Unity WebGL game when myActiveBet is found
|
|
||||||
<div className="w-full h-screen flex justify-center items-center bg-black">
|
<div className="w-full h-screen flex justify-center items-center bg-black">
|
||||||
<iframe
|
<iframe
|
||||||
src={`/UnityBuild/${myActiveBet.id}/index.html?betId=${myActiveBet.id}&owner=${myActiveBet.owner_id}&joiner=${myActiveBet.joiner_id}&address=${myActiveBet.address}&uid=${user?.id}&pubkey=${solWallet?.address}`} // Change this to the actual path of your Unity WebGL build
|
ref={iframeRef}
|
||||||
|
src={`/UnityBuild/${myActiveBet.id}/index.html?betId=${myActiveBet.id}&owner=${myActiveBet.owner_id}&joiner=${myActiveBet.joiner_id}&address=${myActiveBet.address}&uid=${user?.id}&pubkey=${solWallet?.address}&wager=${myActiveBet.wager * 1e8}`}
|
||||||
className="w-full h-full"
|
className="w-full h-full"
|
||||||
allowFullScreen
|
allowFullScreen
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// Render the original UI when no active bet
|
|
||||||
<section className="flex flex-col items-center text-center py-16">
|
<section className="flex flex-col items-center text-center py-16">
|
||||||
<Image
|
<Image
|
||||||
src="/duelfiassets/Playing on Arcade Machine no BG.png"
|
src="/duelfiassets/Playing on Arcade Machine no BG.png"
|
||||||
|
|
@ -116,6 +341,15 @@ export default function HeroSection() {
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{rematch && (
|
||||||
|
<RematchModal
|
||||||
|
isOwner= {lastActiveBet?.owner_id === user?.id}
|
||||||
|
inProgress={rematchInProgress}
|
||||||
|
hasError={rematchTxError}
|
||||||
|
wallets={solWallet!}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<GameModal isOpen={isGameModalOpen} onClose={() => setIsGameModalOpen(false)} />
|
<GameModal isOpen={isGameModalOpen} onClose={() => setIsGameModalOpen(false)} />
|
||||||
<HowItWorksModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
|
<HowItWorksModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { Bet } from "../types/Bet";
|
||||||
import { fetchUserById } from "@/shared/data_fetcher";
|
import { fetchUserById } from "@/shared/data_fetcher";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
import { EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
||||||
|
import { WAGER_PRIZE_MULT } from "@/shared/constants";
|
||||||
|
|
||||||
interface GameModalProps {
|
interface GameModalProps {
|
||||||
bets: Bet[];
|
bets: Bet[];
|
||||||
|
|
@ -120,7 +121,7 @@ export default function YourGames({ bets }: GameModalProps) {
|
||||||
|
|
||||||
<div className="flex justify-between text-xs font-mono py-1">
|
<div className="flex justify-between text-xs font-mono py-1">
|
||||||
<p className="text-white">{bet.wager} SOL</p>
|
<p className="text-white">{bet.wager} SOL</p>
|
||||||
<p className="text-white">{(bet.wager * 2 * 0.95).toFixed(2)} SOL</p>
|
<p className="text-white">{(bet.wager * 2 * WAGER_PRIZE_MULT).toFixed(2)} SOL</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{bet.ownerProfile && (
|
{bet.ownerProfile && (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
import Image from "next/image";
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
||||||
import { Connection, PublicKey } from "@solana/web3.js";
|
import { Connection, PublicKey } from "@solana/web3.js";
|
||||||
|
|
@ -7,12 +7,14 @@ import { toast } from "sonner";
|
||||||
import "react-toastify/dist/ReactToastify.css";
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
import { CLUSTER_URL } from "@/data/shared";
|
import { CLUSTER_URL } from "@/data/shared";
|
||||||
import { useFundWallet } from "@privy-io/react-auth/solana";
|
import { useFundWallet } from "@privy-io/react-auth/solana";
|
||||||
|
import GameHistoryModal from "./GameHistory";
|
||||||
|
|
||||||
export default function PrivyButton() {
|
export default function PrivyButton() {
|
||||||
const { login, logout, user, linkTwitter, unlinkTwitter } = usePrivy();
|
const { login, logout, user, linkTwitter, unlinkTwitter } = usePrivy();
|
||||||
const { fundWallet} = useFundWallet();
|
const { fundWallet } = useFundWallet();
|
||||||
const { wallets } = useSolanaWallets();
|
const { wallets, ready } = useSolanaWallets();
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [showHistory, setShowHistory] = useState(false);
|
||||||
const [solWallet, setSolWallet] = useState("");
|
const [solWallet, setSolWallet] = useState("");
|
||||||
const [solBalance, setSolBalance] = useState("--");
|
const [solBalance, setSolBalance] = useState("--");
|
||||||
|
|
||||||
|
|
@ -24,7 +26,7 @@ export default function PrivyButton() {
|
||||||
const [newUsername, setNewUsername] = useState("");
|
const [newUsername, setNewUsername] = useState("");
|
||||||
|
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
const updateSolWallet = ()=>{
|
const updateSolWallet = () => {
|
||||||
wallets.forEach((wallet) => {
|
wallets.forEach((wallet) => {
|
||||||
if (wallet.type === "solana") {
|
if (wallet.type === "solana") {
|
||||||
setSolWallet(wallet.address);
|
setSolWallet(wallet.address);
|
||||||
|
|
@ -47,18 +49,27 @@ export default function PrivyButton() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
fetchSolBalance();
|
||||||
|
}, 5000); // 5000 milliseconds = 5 seconds
|
||||||
|
|
||||||
|
// Cleanup function to clear the interval on unmount or when `ready` changes
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [ready]);
|
||||||
|
|
||||||
const saveProfileChanges = async () => {
|
const saveProfileChanges = async () => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
toast.error("User not found!");
|
toast.error("User not found!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateUrl = `https://vps.playpoolstudios.com/duelfi/api/update_profile.php?id=${user.id}&username=${username}&bio=${bio}`;
|
const updateUrl = `https://vps.playpoolstudios.com/duelfi/api/update_profile.php?id=${user.id}&username=${username}&bio=${bio}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(updateUrl);
|
const response = await fetch(updateUrl);
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
|
|
||||||
if (data == "0") {
|
if (data == "0") {
|
||||||
toast.success("Profile updated successfully!");
|
toast.success("Profile updated successfully!");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -139,36 +150,36 @@ export default function PrivyButton() {
|
||||||
if (file) {
|
if (file) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = async () => {
|
reader.onloadend = async () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ensure the user is authenticated and the privy_id is available
|
// Ensure the user is authenticated and the privy_id is available
|
||||||
if (!user?.id) {
|
if (!user?.id) {
|
||||||
toast.error('No Privy ID found!');
|
toast.error('No Privy ID found!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the form data
|
// Prepare the form data
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
formData.append('privy_id', user.id); // Append the privy_id
|
formData.append('privy_id', user.id); // Append the privy_id
|
||||||
|
|
||||||
// Upload the avatar image to your server
|
// Upload the avatar image to your server
|
||||||
const uploadResponse = await fetch('https://vps.playpoolstudios.com/duelfi/api/upload_profile_picture.php', {
|
const uploadResponse = await fetch('https://vps.playpoolstudios.com/duelfi/api/upload_profile_picture.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
});
|
});
|
||||||
|
|
||||||
const uploadData = await uploadResponse.json();
|
const uploadData = await uploadResponse.json();
|
||||||
console.log(uploadData);
|
console.log(uploadData);
|
||||||
if (uploadData.success) {
|
if (uploadData.success) {
|
||||||
// Get the image URL from the response (assuming it returns the image path)
|
// Get the image URL from the response (assuming it returns the image path)
|
||||||
const imageUrl = uploadData.imageUrl;
|
const imageUrl = uploadData.imageUrl;
|
||||||
|
|
||||||
// Update the avatar state and database
|
// Update the avatar state and database
|
||||||
setAvatar(imageUrl);
|
setAvatar(imageUrl);
|
||||||
const updatePicUrlApi = `https://vps.playpoolstudios.com/duelfi/api/update_x_pic_url.php?id=${user?.id}&url=${imageUrl}`;
|
const updatePicUrlApi = `https://vps.playpoolstudios.com/duelfi/api/update_x_pic_url.php?id=${user?.id}&url=${imageUrl}`;
|
||||||
await fetch(updatePicUrlApi);
|
await fetch(updatePicUrlApi);
|
||||||
|
|
||||||
toast.success('Profile picture uploaded successfully!');
|
toast.success('Profile picture uploaded successfully!');
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
|
@ -210,20 +221,66 @@ export default function PrivyButton() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<GameHistoryModal
|
||||||
|
userId={user?.id}
|
||||||
|
isOpen={showHistory}
|
||||||
|
onClose={() => setShowHistory(false)
|
||||||
|
}
|
||||||
|
/>
|
||||||
{user ? (
|
{user ? (
|
||||||
<button
|
<>
|
||||||
onClick={() => {
|
<div className="flex items-center justify-between gap-4">
|
||||||
setIsModalOpen(true);
|
<button
|
||||||
fetchSolBalance();
|
onClick={() => {
|
||||||
fetchUserData();
|
setIsModalOpen(true);
|
||||||
}}
|
fetchSolBalance();
|
||||||
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black px-6 py-3 rounded-md transition duration-300 hover:scale-105"
|
fetchUserData();
|
||||||
>
|
}}
|
||||||
<span className="font-bold">Connected</span>
|
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black px-6 py-3 rounded-md transition duration-300 hover:scale-105"
|
||||||
<p className="text-xs font-mono text-gray-700">
|
>
|
||||||
{solWallet?.slice(0, 6)}...{solWallet?.slice(-4)}
|
<span className="font-bold">Connected</span>
|
||||||
</p>
|
<p className="text-xs font-mono text-gray-700">
|
||||||
</button>
|
{solWallet?.slice(0, 6)}...{solWallet?.slice(-4)}
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p className="text-s font-mono text-white">
|
||||||
|
<Image
|
||||||
|
src="/duelfiassets/solana logo.png"
|
||||||
|
alt="SOL"
|
||||||
|
width={25}
|
||||||
|
height={25}
|
||||||
|
className="inline mx-2"
|
||||||
|
/>
|
||||||
|
{solBalance}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* History button */}
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowHistory(true)}
|
||||||
|
title="View Game History"
|
||||||
|
className="hover:text-orange-400 transition"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="w-6 h-6 text-white"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={customLogin}
|
onClick={customLogin}
|
||||||
|
|
@ -331,7 +388,7 @@ export default function PrivyButton() {
|
||||||
<p className="text-sm mb-3">{solBalance} SOL</p>
|
<p className="text-sm mb-3">{solBalance} SOL</p>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={()=>{fundWallet(solWallet, {})}}
|
onClick={() => { fundWallet(solWallet, {}) }}
|
||||||
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold py-2 rounded-xl transition"
|
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold py-2 rounded-xl transition"
|
||||||
>
|
>
|
||||||
Fund Wallet
|
Fund Wallet
|
||||||
|
|
@ -353,25 +410,25 @@ export default function PrivyButton() {
|
||||||
|
|
||||||
{/* Username Claim Modal */}
|
{/* Username Claim Modal */}
|
||||||
{isUsernameClaimModalOpen && (
|
{isUsernameClaimModalOpen && (
|
||||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-center z-50">
|
<div className="fixed inset-0 bg-black/70 flex justify-center items-center z-50">
|
||||||
<div className="bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg space-y-6">
|
<div className="bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg space-y-6">
|
||||||
<h2 className="text-2xl font-bold">Claim Your Username</h2>
|
<h2 className="text-2xl font-bold">Claim Your Username</h2>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={newUsername}
|
value={newUsername}
|
||||||
onChange={(e) => setNewUsername(e.target.value)}
|
onChange={(e) => setNewUsername(e.target.value)}
|
||||||
className="w-full bg-[rgb(10,10,10)] text-white p-2 rounded-md"
|
className="w-full bg-[rgb(10,10,10)] text-white p-2 rounded-md"
|
||||||
placeholder="Enter your new username"
|
placeholder="Enter your new username"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleUsernameClaim}
|
onClick={handleUsernameClaim}
|
||||||
className="w-full bg-[rgb(248,144,22)] hover:bg-orange-400 text-white px-4 py-2 rounded-md transition duration-500 hover:scale-105"
|
className="w-full bg-[rgb(248,144,22)] hover:bg-orange-400 text-white px-4 py-2 rounded-md transition duration-500 hover:scale-105"
|
||||||
>
|
>
|
||||||
Claim
|
Claim
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
60
src/components/RematchModal.tsx
Normal file
60
src/components/RematchModal.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
"use client";
|
||||||
|
import { Bet } from "@/types/Bet";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { fetchOpenBets } from "../shared/solana_helpers"; // adjust path as needed
|
||||||
|
import { ConnectedSolanaWallet } from "@privy-io/react-auth"; // import your wallet type
|
||||||
|
|
||||||
|
interface RematchModalProps {
|
||||||
|
isOwner: boolean;
|
||||||
|
inProgress: boolean;
|
||||||
|
hasError: boolean;
|
||||||
|
wallets: ConnectedSolanaWallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RematchModal: React.FC<RematchModalProps> = ({
|
||||||
|
isOwner,
|
||||||
|
inProgress,
|
||||||
|
hasError,
|
||||||
|
wallets
|
||||||
|
} : RematchModalProps) => {
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-10 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-[rgb(30,30,30)] rounded-xl shadow-lg p-6 max-w-md w-full text-center space-y-4">
|
||||||
|
<h2 className="text-2xl font-bold">Starting Rematch</h2>
|
||||||
|
{isOwner ? (
|
||||||
|
<>
|
||||||
|
<p>Waiting for transaction</p>
|
||||||
|
{/* <button
|
||||||
|
onClick={onCreate}
|
||||||
|
disabled={inProgress}
|
||||||
|
className="bg-orange-500 hover:bg-orange-600 text-white font-semibold py-2 px-6 rounded-xl disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{inProgress ? "Creating..." : "Create Rematch"}
|
||||||
|
</button> */}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>Your opponent is setting up a rematch. Ready to join?</p>
|
||||||
|
{/* <button
|
||||||
|
onClick={onJoin}
|
||||||
|
disabled={inProgress}
|
||||||
|
className="bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-6 rounded-xl disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{inProgress ? "Joining..." : "Join Rematch"}
|
||||||
|
</button> */}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{hasError && (
|
||||||
|
<button
|
||||||
|
// onClick={onRetry}
|
||||||
|
className="bg-red-500 text-white py-2 px-4 mt-4 rounded-md"
|
||||||
|
>
|
||||||
|
Try Again
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -82,7 +82,7 @@ export default function YourGames({bets}:GameModalProps) {
|
||||||
if (!game) return null;
|
if (!game) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={bet.id} className="relative group bg-[rgb(30,30,30)] rounded-2xl p-2 w-50 overflow-hidden cursor-pointer transition-all duration-300">
|
<div key={bet.address} className="relative group bg-[rgb(30,30,30)] rounded-2xl p-2 w-50 overflow-hidden cursor-pointer transition-all duration-300">
|
||||||
{/* Game Thumbnail */}
|
{/* Game Thumbnail */}
|
||||||
<div className="relative w-full h-40 overflow-hidden rounded-xl">
|
<div className="relative w-full h-40 overflow-hidden rounded-xl">
|
||||||
<Image
|
<Image
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Game } from "@/types/Game";
|
||||||
|
|
||||||
export const games = [
|
export const games = [
|
||||||
{
|
{
|
||||||
id: "tetris",
|
id: "tetris",
|
||||||
|
|
@ -19,3 +21,6 @@ export const games = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function GetGameByID(id:string):Game | undefined{
|
||||||
|
return games.find((game)=> game.id == id);
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { Commitment, PublicKey } from "@solana/web3.js";
|
||||||
|
|
||||||
export const FEE_COLLECTOR_PUBKEY = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc");
|
export const FEE_COLLECTOR_PUBKEY = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc");
|
||||||
|
export const WAGER_PRIZE_MULT = 0.95;
|
||||||
|
|
||||||
|
export const CONFIRMATION_THRESHOLD:Commitment = 'processed';
|
||||||
|
|
@ -7,146 +7,180 @@ import idl from "../idl/bets_idl.json";
|
||||||
import { Bet } from "@/types/Bet";
|
import { Bet } from "@/types/Bet";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Game } from "@/types/Game";
|
import { Game } from "@/types/Game";
|
||||||
import { FEE_COLLECTOR_PUBKEY } from "./constants";
|
import { CONFIRMATION_THRESHOLD, FEE_COLLECTOR_PUBKEY } from "./constants";
|
||||||
|
|
||||||
export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet[]> => {
|
export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet[]> => {
|
||||||
try {
|
try {
|
||||||
if (!wallets) return [];
|
if (!wallets) return [];
|
||||||
|
|
||||||
const connection = new Connection(CLUSTER_URL);
|
const connection = new Connection(CLUSTER_URL);
|
||||||
const wallet = {
|
const wallet = {
|
||||||
publicKey: new PublicKey(wallets.address),
|
|
||||||
signTransaction: wallets.signTransaction,
|
|
||||||
signAllTransactions: wallets.signAllTransactions,
|
|
||||||
};
|
|
||||||
const provider = new AnchorProvider(connection, wallet, {
|
|
||||||
preflightCommitment: "confirmed",
|
|
||||||
});
|
|
||||||
|
|
||||||
const program = new Program<Bets>(idl, provider);
|
|
||||||
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
|
||||||
[Buffer.from("bets_list")],
|
|
||||||
program.programId
|
|
||||||
);
|
|
||||||
// Fetch all open bet accounts
|
|
||||||
const bet_list = await program.account.betsList.fetch(bet_list_pda);
|
|
||||||
|
|
||||||
// Extract required bet data
|
|
||||||
const formattedBets = await Promise.all(
|
|
||||||
bet_list.bets.map(async (bet) => {
|
|
||||||
const betAcc = await program.account.betVault.fetch(bet);
|
|
||||||
console.log(betAcc.gameId);
|
|
||||||
return {
|
|
||||||
address: bet.toString(),
|
|
||||||
id: betAcc.gameId,
|
|
||||||
owner: betAcc.owner.toBase58(),
|
|
||||||
owner_id:betAcc.ownerId,
|
|
||||||
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
|
|
||||||
joiner_id:betAcc.joinerId,
|
|
||||||
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`Got ${formattedBets.length} bets`);
|
|
||||||
|
|
||||||
return formattedBets;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching open bets:", error);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): Promise<string> {
|
|
||||||
try {
|
|
||||||
const connection = new Connection(CLUSTER_URL);
|
|
||||||
const wallet = {
|
|
||||||
publicKey: new PublicKey(wallets.address),
|
|
||||||
signTransaction: wallets.signTransaction,
|
|
||||||
signAllTransactions: wallets.signAllTransactions,
|
|
||||||
};
|
|
||||||
const provider = new AnchorProvider(connection, wallet, {
|
|
||||||
preflightCommitment: "confirmed",
|
|
||||||
});
|
|
||||||
|
|
||||||
const program = new Program<Bets>(idl, provider);
|
|
||||||
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
|
||||||
[Buffer.from("bets_list")],
|
|
||||||
program.programId
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch the bet list
|
|
||||||
const betList = await program.account.betsList.fetch(bet_list_pda);
|
|
||||||
|
|
||||||
let chosenBet: PublicKey | null = null;
|
|
||||||
|
|
||||||
for (const bet of betList.bets) {
|
|
||||||
const betAcc = await program.account.betVault.fetch(bet);
|
|
||||||
if (betAcc.owner.toBase58() === wallets.address && betAcc.gameId.toString() === betId) {
|
|
||||||
chosenBet = bet;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chosenBet) {
|
|
||||||
console.error("Bet not found or not owned by the user");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const winner = new PublicKey(wallets.address);
|
|
||||||
|
|
||||||
// Execute the closeBet transaction
|
|
||||||
const tx = await program.methods
|
|
||||||
.closeBet(winner)
|
|
||||||
.accounts({
|
|
||||||
betVault: chosenBet,
|
|
||||||
betsList: bet_list_pda,
|
|
||||||
winner: winner,
|
|
||||||
feeWallet: FEE_COLLECTOR_PUBKEY
|
|
||||||
})
|
|
||||||
.transaction();
|
|
||||||
tx.feePayer = new PublicKey(wallets.address);
|
|
||||||
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
||||||
|
|
||||||
// Sign transaction with Privy
|
|
||||||
const signedTx = await wallet.signTransaction(tx);
|
|
||||||
|
|
||||||
// Send transaction// Replace with correct RPC endpoint
|
|
||||||
const txId = await connection.sendRawTransaction(signedTx.serialize());
|
|
||||||
console.log(`Transaction: ${tx}`);
|
|
||||||
return txId;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error closing bet:", error);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createBet(wallets:ConnectedSolanaWallet, uid:string,selectedPrice:number,selectedGame:Game):Promise<string>{
|
|
||||||
const connection = new Connection(CLUSTER_URL);
|
|
||||||
const wallet = {
|
|
||||||
publicKey: new PublicKey(wallets.address),
|
publicKey: new PublicKey(wallets.address),
|
||||||
signTransaction: wallets.signTransaction,
|
signTransaction: wallets.signTransaction,
|
||||||
signAllTransactions: wallets.signAllTransactions,
|
signAllTransactions: wallets.signAllTransactions,
|
||||||
|
};
|
||||||
|
const provider = new AnchorProvider(connection, wallet, {
|
||||||
|
preflightCommitment: CONFIRMATION_THRESHOLD,
|
||||||
|
});
|
||||||
|
|
||||||
|
const program = new Program<Bets>(idl, provider);
|
||||||
|
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
||||||
|
[Buffer.from("bets_list")],
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
// Fetch all open bet accounts
|
||||||
|
const bet_list = await program.account.betsList.fetch(bet_list_pda);
|
||||||
|
|
||||||
|
// Extract required bet data
|
||||||
|
const formattedBets = await Promise.all(
|
||||||
|
bet_list.bets.map(async (bet) => {
|
||||||
|
const betAcc = await program.account.betVault.fetch(bet);
|
||||||
|
// console.log(betAcc.gameId);
|
||||||
|
return {
|
||||||
|
address: bet.toString(),
|
||||||
|
id: betAcc.gameId,
|
||||||
|
owner: betAcc.owner.toBase58(),
|
||||||
|
owner_id: betAcc.ownerId,
|
||||||
|
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
|
||||||
|
joiner_id: betAcc.joinerId,
|
||||||
|
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// console.log(`Got ${formattedBets.length} bets`);
|
||||||
|
|
||||||
|
return formattedBets;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching open bets:", error);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getVaultByAddress(wallets: ConnectedSolanaWallet, address: string): Promise<Bet | undefined> {
|
||||||
|
try {
|
||||||
|
if (!wallets) return undefined;
|
||||||
|
|
||||||
|
const connection = new Connection(CLUSTER_URL);
|
||||||
|
const wallet = {
|
||||||
|
publicKey: new PublicKey(wallets.address),
|
||||||
|
signTransaction: wallets.signTransaction,
|
||||||
|
signAllTransactions: wallets.signAllTransactions,
|
||||||
|
};
|
||||||
|
const provider = new AnchorProvider(connection, wallet, {
|
||||||
|
preflightCommitment: CONFIRMATION_THRESHOLD,
|
||||||
|
});
|
||||||
|
|
||||||
|
const program = new Program<Bets>(idl, provider);
|
||||||
|
|
||||||
|
// Extract required bet data
|
||||||
|
const betAcc = await program.account.betVault.fetch(address);
|
||||||
|
// console.log(betAcc.gameId);
|
||||||
|
return {
|
||||||
|
address: address.toString(),
|
||||||
|
id: betAcc.gameId,
|
||||||
|
owner: betAcc.owner.toBase58(),
|
||||||
|
owner_id: betAcc.ownerId,
|
||||||
|
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
|
||||||
|
joiner_id: betAcc.joinerId,
|
||||||
|
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching open bets:", error);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
const connection = new Connection(CLUSTER_URL);
|
||||||
|
const wallet = {
|
||||||
|
publicKey: new PublicKey(wallets.address),
|
||||||
|
signTransaction: wallets.signTransaction,
|
||||||
|
signAllTransactions: wallets.signAllTransactions,
|
||||||
|
};
|
||||||
|
const provider = new AnchorProvider(connection, wallet, {
|
||||||
|
preflightCommitment: CONFIRMATION_THRESHOLD,
|
||||||
|
});
|
||||||
|
|
||||||
|
const program = new Program<Bets>(idl, provider);
|
||||||
|
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
||||||
|
[Buffer.from("bets_list")],
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch the bet list
|
||||||
|
const betList = await program.account.betsList.fetch(bet_list_pda);
|
||||||
|
|
||||||
|
let chosenBet: PublicKey | null = null;
|
||||||
|
|
||||||
|
for (const bet of betList.bets) {
|
||||||
|
const betAcc = await program.account.betVault.fetch(bet);
|
||||||
|
if (betAcc.owner.toBase58() === wallets.address && betAcc.gameId.toString() === betId) {
|
||||||
|
chosenBet = bet;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chosenBet) {
|
||||||
|
console.error("Bet not found or not owned by the user");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const winner = new PublicKey(wallets.address);
|
||||||
|
|
||||||
|
// Execute the closeBet transaction
|
||||||
|
const tx = await program.methods
|
||||||
|
.closeBet(winner)
|
||||||
|
.accounts({
|
||||||
|
betVault: chosenBet,
|
||||||
|
betsList: bet_list_pda,
|
||||||
|
winner: winner,
|
||||||
|
feeWallet: FEE_COLLECTOR_PUBKEY
|
||||||
|
})
|
||||||
|
.transaction();
|
||||||
|
tx.feePayer = new PublicKey(wallets.address);
|
||||||
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
||||||
|
|
||||||
|
// Sign transaction with Privy
|
||||||
|
const signedTx = await wallet.signTransaction(tx);
|
||||||
|
|
||||||
|
// Send transaction// Replace with correct RPC endpoint
|
||||||
|
const txId = await connection.sendRawTransaction(signedTx.serialize());
|
||||||
|
console.log(`Transaction: ${tx}`);
|
||||||
|
return txId;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error closing bet:", error);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createBet(wallets: ConnectedSolanaWallet, uid: string, selectedPrice: number, selectedGame: string, get_account_address: boolean): Promise<string> {
|
||||||
|
const connection = new Connection(CLUSTER_URL);
|
||||||
|
const wallet = {
|
||||||
|
publicKey: new PublicKey(wallets.address),
|
||||||
|
signTransaction: wallets.signTransaction,
|
||||||
|
signAllTransactions: wallets.signAllTransactions,
|
||||||
};
|
};
|
||||||
const provider = new AnchorProvider(connection, wallet, {
|
const provider = new AnchorProvider(connection, wallet, {
|
||||||
preflightCommitment: "confirmed",
|
preflightCommitment: CONFIRMATION_THRESHOLD,
|
||||||
});
|
});
|
||||||
|
|
||||||
const program = new Program<Bets>(idl, provider);
|
const program = new Program<Bets>(idl, provider);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
toast.loading("Creating bet...");
|
||||||
const nonce = getRandomInt(100000000);
|
const nonce = getRandomInt(100000000);
|
||||||
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
||||||
[Buffer.from("bets_list")],
|
[Buffer.from("bets_list")],
|
||||||
program.programId
|
program.programId
|
||||||
);
|
);
|
||||||
const connection = new Connection(CLUSTER_URL, "confirmed");
|
|
||||||
|
|
||||||
console.log(`bets list : ${bet_list_pda}`);
|
console.log(`bets list : ${bet_list_pda}`);
|
||||||
|
|
||||||
// Create transaction
|
// Create transaction
|
||||||
const tx = await program.methods
|
const tx = await program.methods
|
||||||
.createBet(new BN(selectedPrice * 1000000000),uid, selectedGame.id, new BN(nonce))
|
.createBet(new BN(selectedPrice * 1000000000), uid, selectedGame, new BN(nonce))
|
||||||
.accounts({
|
.accounts({
|
||||||
betsList: bet_list_pda,
|
betsList: bet_list_pda,
|
||||||
})
|
})
|
||||||
|
|
@ -158,14 +192,31 @@ export async function createBet(wallets:ConnectedSolanaWallet, uid:string,select
|
||||||
// Sign transaction with Privy
|
// Sign transaction with Privy
|
||||||
const signedTx = await wallet.signTransaction(tx);
|
const signedTx = await wallet.signTransaction(tx);
|
||||||
|
|
||||||
// Send transaction// Replace with correct RPC endpoint
|
// Send transaction
|
||||||
const txId = await connection.sendRawTransaction(signedTx.serialize());
|
const txId = await connection.sendRawTransaction(signedTx.serialize());
|
||||||
|
console.log(`Transaction sent: ${txId}`);
|
||||||
|
|
||||||
console.log(`Transaction ID: ${txId}`);
|
toast.success("Bet created successfully");
|
||||||
return txId;
|
console.log(`Transaction confirmed: ${txId}`);
|
||||||
} catch (error:unknown) {
|
if (get_account_address) {
|
||||||
|
const gameIdBytes = Buffer.from(selectedGame); // selectedGame is a string (game_id)
|
||||||
|
const nonceBytes = new BN(nonce).toArrayLike(Buffer, "le", 8); // _nonce.to_le_bytes()
|
||||||
|
const [betVaultPda] = await PublicKey.findProgramAddress(
|
||||||
|
[
|
||||||
|
Buffer.from("bet_vault"),
|
||||||
|
new PublicKey(wallets.address).toBuffer(), // payer
|
||||||
|
gameIdBytes,
|
||||||
|
nonceBytes,
|
||||||
|
],
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
|
||||||
|
return betVaultPda.toString();
|
||||||
|
} else {
|
||||||
|
return txId;
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
toast.dismiss();
|
||||||
const errorMessage = String(error); // Converts error to string safely
|
const errorMessage = String(error); // Converts error to string safely
|
||||||
|
|
||||||
if (errorMessage.includes("already in use")) {
|
if (errorMessage.includes("already in use")) {
|
||||||
|
|
@ -179,32 +230,32 @@ export async function createBet(wallets:ConnectedSolanaWallet, uid:string,select
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function joinBet(wallets:ConnectedSolanaWallet, uid:string, gameId:string, address:string):Promise<string>{
|
export async function joinBet(wallets: ConnectedSolanaWallet, uid: string, gameId: string, address: string): Promise<string> {
|
||||||
const connection = new Connection(CLUSTER_URL);
|
const connection = new Connection(CLUSTER_URL);
|
||||||
const wallet = {
|
const wallet = {
|
||||||
publicKey: new PublicKey(wallets.address),
|
publicKey: new PublicKey(wallets.address),
|
||||||
signTransaction: wallets.signTransaction,
|
signTransaction: wallets.signTransaction,
|
||||||
signAllTransactions: wallets.signAllTransactions,
|
signAllTransactions: wallets.signAllTransactions,
|
||||||
};
|
};
|
||||||
const provider = new AnchorProvider(connection, wallet, {
|
const provider = new AnchorProvider(connection, wallet, {
|
||||||
preflightCommitment: "confirmed",
|
preflightCommitment: CONFIRMATION_THRESHOLD,
|
||||||
});
|
});
|
||||||
|
|
||||||
const program = new Program<Bets>(idl, provider);
|
const program = new Program<Bets>(idl, provider);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
||||||
[Buffer.from("bets_list")],
|
[Buffer.from("bets_list")],
|
||||||
program.programId
|
program.programId
|
||||||
);
|
);
|
||||||
const connection = new Connection(CLUSTER_URL, "confirmed");
|
const connection = new Connection(CLUSTER_URL, CONFIRMATION_THRESHOLD);
|
||||||
const betVaultPubkey = new PublicKey(address);
|
const betVaultPubkey = new PublicKey(address);
|
||||||
|
|
||||||
console.log(`bets list : ${bet_list_pda}`);
|
console.log(`bets list : ${bet_list_pda}`);
|
||||||
|
|
||||||
// Create transaction
|
// Create transaction
|
||||||
const tx = await program.methods
|
const tx = await program.methods
|
||||||
.joinBet(uid,gameId)
|
.joinBet(uid, gameId)
|
||||||
.accounts({
|
.accounts({
|
||||||
betVault: betVaultPubkey,
|
betVault: betVaultPubkey,
|
||||||
})
|
})
|
||||||
|
|
@ -221,18 +272,18 @@ export async function joinBet(wallets:ConnectedSolanaWallet, uid:string, gameId:
|
||||||
|
|
||||||
console.log(`Transaction ID: ${txId}`);
|
console.log(`Transaction ID: ${txId}`);
|
||||||
return txId;
|
return txId;
|
||||||
} catch (error:unknown) {
|
} catch (error: unknown) {
|
||||||
|
|
||||||
|
|
||||||
// const errorMessage = String(error); // Converts error to string safely
|
// const errorMessage = String(error); // Converts error to string safely
|
||||||
toast.error("Failed to create bet.");
|
toast.error("Failed to create bet.");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRandomInt(max:number):number {
|
function getRandomInt(max: number): number {
|
||||||
return Math.floor(Math.random() * max);
|
return Math.floor(Math.random() * max);
|
||||||
}
|
}
|
||||||
10
src/types/GameHistory.ts
Normal file
10
src/types/GameHistory.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
interface GameHistory {
|
||||||
|
address: string;
|
||||||
|
master_score: string;
|
||||||
|
client_score: string;
|
||||||
|
winner: string;
|
||||||
|
wager: string;
|
||||||
|
master_id: string;
|
||||||
|
client_id: string;
|
||||||
|
game:string;
|
||||||
|
}
|
||||||
9
src/types/Submission.tsx
Normal file
9
src/types/Submission.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
export interface Submission{
|
||||||
|
username: string;
|
||||||
|
master_score: number;
|
||||||
|
client_score: number;
|
||||||
|
leaderboard: any;
|
||||||
|
master_id:string;
|
||||||
|
client_id:string;
|
||||||
|
winner:string;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user