├── packages ├── react-app │ ├── src │ │ ├── react-app-env.d.ts │ │ ├── pixels-metaverse.tsx │ │ ├── assets │ │ │ ├── font │ │ │ │ └── cn.ttf │ │ │ └── image │ │ │ │ └── bg.svg │ │ ├── i18n │ │ │ ├── en-us.json │ │ │ ├── zh-hk.json │ │ │ ├── config.ts │ │ │ └── zh-cn.json │ │ ├── setupTests.js │ │ ├── theme.less │ │ ├── reportWebVitals.js │ │ ├── pages │ │ │ ├── play │ │ │ │ ├── components │ │ │ │ │ ├── Avatar.tsx │ │ │ │ │ ├── Collections.tsx │ │ │ │ │ └── PersonCenter.tsx │ │ │ │ └── index.tsx │ │ │ ├── person-center │ │ │ │ ├── components │ │ │ │ │ ├── AssetsInfo.tsx │ │ │ │ │ └── BaseInfo.tsx │ │ │ │ └── index.tsx │ │ │ ├── produced │ │ │ │ ├── index.tsx │ │ │ │ └── components │ │ │ │ │ ├── Controller.tsx │ │ │ │ │ ├── UploadImg.tsx │ │ │ │ │ └── Submit.tsx │ │ │ ├── lockers │ │ │ │ ├── index.tsx │ │ │ │ └── components │ │ │ │ │ ├── MaterialCard.tsx │ │ │ │ │ ├── SearchQuery.tsx │ │ │ │ │ └── ComposeDetails.tsx │ │ │ └── home │ │ │ │ └── index.tsx │ │ ├── index.js │ │ ├── eos-api │ │ │ ├── config.ts │ │ │ └── fetch.ts │ │ ├── routes │ │ │ └── index.tsx │ │ ├── components │ │ │ ├── DataStateBox.tsx │ │ │ ├── Loading.tsx │ │ │ ├── AvatarCard.tsx │ │ │ └── Header.tsx │ │ ├── helpers │ │ │ ├── radix.ts │ │ │ ├── types.ts │ │ │ ├── bignumber.ts │ │ │ └── utilities.tsx │ │ ├── App.tsx │ │ ├── gql │ │ │ └── index.ts │ │ ├── index.css │ │ └── client │ │ │ └── PixelsMetaverse.tsx │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── introduction │ │ └── PixelsMetaverse.pdf │ ├── postcss.config.js │ ├── deploy.sh │ ├── electron │ │ ├── package.json │ │ └── main.js │ ├── tsconfig.json │ ├── craco.config.js │ ├── .eslintrc │ ├── scripts │ │ ├── s3.js │ │ └── ipfs.js │ └── package.json ├── subgraph │ ├── tsconfig.json │ ├── schema.graphql │ ├── package.json │ ├── subgraph.yaml │ ├── src │ │ └── mapping.ts │ └── abis │ │ └── localhost_PMT721.json ├── services │ ├── graph-node │ │ ├── bin │ │ │ ├── debug │ │ │ ├── create │ │ │ ├── remove │ │ │ ├── reassign │ │ │ └── deploy │ │ ├── hooks │ │ │ └── post_checkout │ │ ├── tag.sh │ │ ├── build.sh │ │ ├── docker-compose.yml │ │ ├── setup.sh │ │ ├── wait_for │ │ ├── cloudbuild.yaml │ │ ├── Dockerfile │ │ ├── README.md │ │ └── start │ └── package.json └── hardhat │ ├── contracts │ ├── IPixelsMetaverse.sol │ ├── Migrations.sol │ ├── IPMT721.sol │ ├── PMT721.sol │ └── PixelsMetaverse.sol │ ├── scripts │ ├── watch.js │ ├── publish.js │ └── deploy.js │ ├── .eslintrc.js │ ├── example.env │ ├── deploy │ └── 00_deploy.js │ ├── package.json │ └── test │ └── PixelsMetaverse.js ├── .editorconfig ├── .gitmodules ├── .gitpod.yml ├── LICENSE ├── .gitignore └── package.json /packages/react-app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/react-app/src/pixels-metaverse.tsx: -------------------------------------------------------------------------------- 1 | export * from "./react-pixels-metaverse/src" 2 | //export * from "react-pixels-metaverse" -------------------------------------------------------------------------------- /packages/react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMERC721/pixels-metaverse/HEAD/packages/react-app/public/favicon.ico -------------------------------------------------------------------------------- /packages/subgraph/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/react-app/src/assets/font/cn.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMERC721/pixels-metaverse/HEAD/packages/react-app/src/assets/font/cn.ttf -------------------------------------------------------------------------------- /packages/react-app/introduction/PixelsMetaverse.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PMERC721/pixels-metaverse/HEAD/packages/react-app/introduction/PixelsMetaverse.pdf -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [packages/**.js{,x}] 4 | indent_style = space 5 | indent_size = 2 6 | 7 | [*.{sol,yul}] 8 | indent_style = space 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /packages/react-app/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | module.exports = { 3 | plugins: [ 4 | tailwindcss('./tailwind.config.js'), 5 | require('autoprefixer'), 6 | ], 7 | } -------------------------------------------------------------------------------- /packages/services/graph-node/bin/debug: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ -f "$1" ] 4 | then 5 | exec rust-gdb -c "$1" /usr/local/cargo/bin/graph-node 6 | else 7 | echo "usage: debug " 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /packages/services/graph-node/hooks/post_checkout: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | echo "Setting SOURCE_BRANCH to ${SOURCE_BRANCH}" 7 | 8 | sed -i "s@^ENV SOURCE_BRANCH \"master\"@ENV SOURCE_BRANCH \"${SOURCE_BRANCH}\"@g" Dockerfile 9 | -------------------------------------------------------------------------------- /packages/react-app/src/i18n/en-us.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "首页meiguo", 4 | "content": "我是首页meiguo" 5 | }, 6 | "about": { 7 | "title": "关于我们meiguo", 8 | "content": "我是关于我们meiguo" 9 | } 10 | } -------------------------------------------------------------------------------- /packages/react-app/src/i18n/zh-hk.json: -------------------------------------------------------------------------------- 1 | { 2 | "home": { 3 | "title": "首页xianggang", 4 | "content": "我是首页xianggang" 5 | }, 6 | "about": { 7 | "title": "关于我们xianggang", 8 | "content": "我是关于我们xianggang" 9 | } 10 | } -------------------------------------------------------------------------------- /packages/hardhat/contracts/IPixelsMetaverse.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IPixelsMetavers { 5 | function handleTransfer( 6 | address from, 7 | address to, 8 | uint256 tokenId 9 | ) external; 10 | } 11 | -------------------------------------------------------------------------------- /packages/react-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /packages/react-app/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | npm run build 6 | 7 | cd build 8 | 9 | # echo 'www.example.com' > CNAME 10 | 11 | git init 12 | git add -A 13 | git commit -m 'deploy' 14 | 15 | git push -f git@github.com:pmerc721/pmerc721.github.io.git master:gh-pages 16 | 17 | cd - -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "packages/services/arbitrum"] 2 | path = packages/services/arbitrum 3 | url = https://github.com/OffchainLabs/arbitrum 4 | branch = master 5 | [submodule "packages/services/optimism"] 6 | path = packages/services/optimism 7 | url = https://github.com/ethereum-optimism/optimism 8 | branch = regenesis/0.4.0 9 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/create: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo "usage: create " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_create", "params": {"name":"%s"}, "id":"1"}' "$1") 11 | curl -s -H "content-type: application/json" --data "$data" "$api" 12 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/remove: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 1 ]; then 4 | echo "usage: create " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_remove", "params": {"name":"%s"}, "id":"1"}' "$1") 11 | curl -s -H "content-type: application/json" --data "$data" "$api" 12 | -------------------------------------------------------------------------------- /packages/react-app/src/theme.less: -------------------------------------------------------------------------------- 1 | @import "../../../node_modules/antd/dist/antd.less"; 2 | @primary-color: #EF4444; 3 | @checkbox-check-bg: 'none';/* 4 | @input-bg: rgba(255, 255, 255, 0.15); 5 | @input-color: rgba(225,225,225, .9); 6 | @input-border-color: transparent; 7 | @input-hover-border-color: transparent; */ 8 | @btn-disable-bg: rgba(225,225,225, .7);/* 9 | @modal-content-bg: #323945; 10 | @modal-close-color: rgba(225,225,225, .7); */ -------------------------------------------------------------------------------- /packages/react-app/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/reassign: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: reassign " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | echo Assigning to "$3" 11 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_reassign", "params": {"name":"%s", "ipfs_hash":"%s", "node_id":"%s"}, "id":"1"}' "$1" "$2" "$3") 12 | curl -s -H "content-type: application/json" --data "$data" "$api" 13 | -------------------------------------------------------------------------------- /packages/services/graph-node/bin/deploy: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ $# != 3 ]; then 4 | echo "usage: deploy " 5 | exit 1 6 | fi 7 | 8 | api="http://index-node.default/" 9 | 10 | echo "Deploying $1 (deployment $2)" 11 | data=$(printf '{"jsonrpc": "2.0", "method": "subgraph_deploy", "params": {"name":"%s", "ipfs_hash":"%s", "node_id":"%s"}, "id":"1"}' "$1" "$2" "$3") 12 | curl -s -H "content-type: application/json" --data "$data" "$api" 13 | -------------------------------------------------------------------------------- /packages/hardhat/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | if (msg.sender == owner) _; 10 | } 11 | 12 | constructor() { 13 | owner = msg.sender; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/subgraph/schema.graphql: -------------------------------------------------------------------------------- 1 | type Avater @entity { 2 | id: ID! 3 | avater: Material! 4 | } 5 | 6 | type Material @entity { 7 | id: ID! 8 | owner: Bytes! 9 | rawData: String 10 | remake: Boolean 11 | config: TConfig! 12 | composed: BigInt 13 | composes: [BigInt!]! 14 | createID: BigInt 15 | } 16 | 17 | type TConfig @entity { 18 | id: ID! 19 | name: String! 20 | time: String 21 | position: String 22 | zIndex: String 23 | decode: String 24 | sort: BigInt 25 | } -------------------------------------------------------------------------------- /packages/hardhat/contracts/IPMT721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 6 | 7 | /** 8 | * @dev Required interface of an ERC721 compliant contract. 9 | */ 10 | interface IPMT721 is IERC721 { 11 | function exits(uint256 tokenId) external view returns (bool); 12 | 13 | function mint(address to) external; 14 | function burn(uint256 tokenId) external; 15 | function currentID() external view returns (uint256); 16 | } 17 | -------------------------------------------------------------------------------- /packages/react-app/electron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pixels-metaverse", 3 | "version": "1.0.0", 4 | "main": "main.js", 5 | "author": { 6 | "name": "PixelsMetaverse" 7 | }, 8 | "homepage": ".", 9 | "scripts": { 10 | "pack": "electron-packager . PixelsMetaverse --win --out=release --arch=x64 --app-version=1.0.0 --electron-version=1.8.4 --overwrite --icon=./favicon.ico" 11 | }, 12 | "license": "MIT", 13 | "dependencies": { 14 | "electron": "^10.1.3", 15 | "electron-packager": "^15.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/hardhat/scripts/watch.js: -------------------------------------------------------------------------------- 1 | const watch = require("node-watch"); 2 | const { exec } = require("child_process"); 3 | 4 | const run = () => { 5 | console.log("🛠 Compiling & Deploying..."); 6 | exec("yarn deploy", function (error, stdout, stderr) { 7 | console.log(stdout); 8 | if (error) console.log(error); 9 | if (stderr) console.log(stderr); 10 | }); 11 | }; 12 | 13 | console.log("🔬 Watching Contracts..."); 14 | watch("./contracts", { recursive: true }, function (evt, name) { 15 | console.log("%s changed.", name); 16 | run(); 17 | }); 18 | run(); 19 | -------------------------------------------------------------------------------- /packages/hardhat/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true, 4 | }, 5 | extends: ["airbnb", "plugin:prettier/recommended"], 6 | plugins: ["babel"], 7 | rules: { 8 | "prettier/prettier": ["error"], 9 | "import/extensions": [ 10 | "error", 11 | "ignorePackages", 12 | { 13 | js: "never", 14 | ts: "never", 15 | }, 16 | ], 17 | "import/prefer-default-export": "off", 18 | "prefer-destructuring": "off", 19 | "prefer-template": "off", 20 | "no-console": "off", 21 | "func-names": "off", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/play/components/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import { PixelsMetaverseHandleImg, usePixelsMetaverseHandleImg } from "../../../pixels-metaverse"; 2 | 3 | export const Avatar = () => { 4 | const { config, canvas2Ref } = usePixelsMetaverseHandleImg() 5 | 6 | return ( 7 |
14 | 15 |
16 | ); 17 | }; -------------------------------------------------------------------------------- /packages/react-app/src/pages/person-center/components/AssetsInfo.tsx: -------------------------------------------------------------------------------- 1 | import { map } from "lodash"; 2 | import { AvatarCard } from "../../../components/AvatarCard"; 3 | import { DataStateBox } from "../../../components/DataStateBox"; 4 | import { useUserInfo } from "../../../components/UserProvider"; 5 | 6 | export const AssetsInfo = () => { 7 | const { materialList } = useUserInfo() 8 | 9 | return ( 10 | 11 |
12 | {map(materialList, item => )} 13 |
14 |
15 | ) 16 | } -------------------------------------------------------------------------------- /packages/react-app/src/index.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom'; 2 | import './index.css'; 3 | import './theme.less'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import { HashRouter } from "react-router-dom"; 7 | import "./i18n/config"; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - name: App 3 | init: > 4 | yarn && 5 | gp sync-done install 6 | command: REACT_APP_PROVIDER=$(gp url 8545) yarn start 7 | - name: Chain 8 | init: gp sync-await install 9 | command: yarn chain 10 | openMode: split-right 11 | - name: Deployment 12 | init: gp sync-await install 13 | command: yarn deploy 14 | openMode: split-right 15 | ports: 16 | - port: 3000 17 | onOpen: open-preview 18 | - port: 8545 19 | onOpen: ignore 20 | github: 21 | prebuilds: 22 | pullRequestsFromForks: true 23 | addComment: true 24 | vscode: 25 | extensions: 26 | - dbaeumer.vscode-eslint 27 | - esbenp.prettier-vscode 28 | - juanblanco.solidity -------------------------------------------------------------------------------- /packages/react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ], 26 | "exclude": [ 27 | "src/react-pixels-metaverse" 28 | ], 29 | "rules": { 30 | "no-unused-expressions": "", 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/services/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pmerc721/services", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "submodule-init": "git submodule init && git submodule update --remote", 7 | "arbitrum-init": "cd arbitrum && git submodule init && git submodule update && yarn install", 8 | "arbitrum-build-l1": "cd arbitrum && yarn docker:build:geth", 9 | "arbitrum-run-l1": "cd arbitrum && yarn docker:geth", 10 | "arbitrum-init-l2": "cd arbitrum && yarn demo:initialize", 11 | "arbitrum-run-l2": "cd arbitrum && yarn demo:deploy", 12 | "run-optimism": "cd optimism/ops && make up", 13 | "stop-optimism": "cd optimism/ops && make down", 14 | "run-graph-node": "cd graph-node && docker-compose up", 15 | "remove-graph-node": "cd graph-node && docker-compose down", 16 | "clean-graph-node": "rm -rf graph-node/data/" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/react-app/src/eos-api/config.ts: -------------------------------------------------------------------------------- 1 | import { Api, JsonRpc } from 'eosjs' 2 | const { JsSignatureProvider } = require('eosjs/dist/eosjs-jssig'); 3 | 4 | const contract = 'yijiaxunkeji'; 5 | 6 | const network = { 7 | blockchain: 'eos', 8 | protocol: 'https', 9 | host: 'api-kylin.eosasia.one', 10 | port: 443, 11 | chainId: '5fff1dae8dc8e2fc4d5b23b2c7665c97f9e9d8edf2b6485a86ba311c25639191', 12 | } 13 | 14 | const defaultPrivateKey = "5JSAusb6xRiunAo5x9qCwk7MMATxLEEKHWDa5FmLpXVLoXBbCsU"; // kylin yijiaxunkeji active 15 | const signatureProvider = new JsSignatureProvider([defaultPrivateKey]); 16 | const url = network.protocol + '://' + network.host + ':' + network.port; 17 | 18 | const rpc = new JsonRpc(url) 19 | const api = new Api({ 20 | rpc, 21 | signatureProvider, 22 | chainId: network.chainId 23 | }); 24 | 25 | export { api, JsSignatureProvider, contract, network, rpc } -------------------------------------------------------------------------------- /packages/services/graph-node/tag.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # This script is used by cloud build to push Docker images into Docker hub 4 | 5 | tag_and_push() { 6 | tag=$1 7 | docker tag gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA \ 8 | graphprotocol/graph-node:$tag 9 | docker push graphprotocol/graph-node:$tag 10 | 11 | docker tag gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA \ 12 | graphprotocol/graph-node-debug:$tag 13 | docker push graphprotocol/graph-node-debug:$tag 14 | } 15 | 16 | echo "Logging into Docker Hub" 17 | echo $PASSWORD | docker login --username="$DOCKER_HUB_USER" --password-stdin 18 | 19 | set -ex 20 | 21 | tag_and_push "$SHORT_SHA" 22 | 23 | # Builds on the master branch become the 'latest' 24 | [ "$BRANCH_NAME" = master ] && tag_and_push latest 25 | # Builds of tags set the tag in Docker Hub, too 26 | [ -n "$TAG_NAME" ] && tag_and_push "$TAG_NAME" 27 | 28 | exit 0 29 | -------------------------------------------------------------------------------- /packages/subgraph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pmerc721/subgraph", 3 | "license": "UNLICENSED", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "codegen": "graph codegen", 7 | "build": "graph build", 8 | "deploy": "graph deploy --node https://api.thegraph.com/deploy/ --ipfs https://api.thegraph.com/ipfs/ pmerc721/your-contract", 9 | "create-local": "graph create --node http://localhost:8020/ scaffold-eth/your-contract", 10 | "remove-local": "graph remove --node http://localhost:8020/ scaffold-eth/your-contract", 11 | "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 scaffold-eth/your-contract", 12 | "deploy1": "graph deploy --product hosted-service qianduanxinlv/Pixels" 13 | }, 14 | "dependencies": { 15 | "@graphprotocol/graph-cli": "^0.22.1", 16 | "@graphprotocol/graph-ts": "^0.22.1" 17 | }, 18 | "devDependencies": { 19 | "mustache": "^3.1.0" 20 | } 21 | } -------------------------------------------------------------------------------- /packages/services/graph-node/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # This file is only here to ease testing/development. Official images are 4 | # built using the 'cloudbuild.yaml' file 5 | 6 | type -p podman > /dev/null && docker=podman || docker=docker 7 | 8 | cd $(dirname $0)/.. 9 | 10 | if [ -d .git ] 11 | then 12 | COMMIT_SHA=$(git rev-parse HEAD) 13 | TAG_NAME=$(git tag --points-at HEAD) 14 | REPO_NAME="Checkout of $(git remote get-url origin) at $(git describe --dirty)" 15 | BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) 16 | fi 17 | for stage in graph-node-build graph-node graph-node-debug 18 | do 19 | $docker build --target $stage \ 20 | --build-arg "COMMIT_SHA=$COMMIT_SHA" \ 21 | --build-arg "REPO_NAME=$REPO_NAME" \ 22 | --build-arg "BRANCH_NAME=$BRANCH_NAME" \ 23 | --build-arg "TAG_NAME=$TAG_NAME" \ 24 | -t $stage \ 25 | -f docker/Dockerfile . 26 | done 27 | -------------------------------------------------------------------------------- /packages/react-app/craco.config.js: -------------------------------------------------------------------------------- 1 | // craco.config.js 2 | const CracoLessPlugin = require('craco-less') 3 | const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); 4 | const reactRefreshOverlayEntry = require.resolve( 5 | 'react-dev-utils/refreshOverlayInterop' 6 | ); 7 | 8 | module.exports = { 9 | style: { 10 | postcss: { 11 | plugins: [ 12 | require('tailwindcss'), 13 | require('autoprefixer'), 14 | ], 15 | }, 16 | }, 17 | webpack: { 18 | configure: (webpackConfig, { env, paths }) => { 19 | webpackConfig.output = { 20 | ...webpackConfig.output, 21 | } 22 | 23 | return webpackConfig; 24 | } 25 | }, 26 | plugins: [ 27 | { 28 | plugin: CracoLessPlugin, 29 | options: { 30 | lessLoaderOptions: { 31 | lessOptions: { 32 | javascriptEnabled: true, 33 | }, 34 | }, 35 | }, 36 | }, 37 | ] 38 | } -------------------------------------------------------------------------------- /packages/react-app/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { map } from "lodash" 2 | import { Route, Switch } from "react-router-dom" 3 | import { Website } from "../pages/home" 4 | import { Lockers } from "../pages/lockers" 5 | import { PersonCenter } from "../pages/person-center" 6 | import { PixelsMetaverse } from "../pages/play" 7 | import { Produced } from "../pages/produced" 8 | 9 | export const routes = [ 10 | { name: "控制台", path: "/action", component: PixelsMetaverse }, 11 | { name: "作坊", path: "/produced", component: Produced }, 12 | { name: "储物柜", path: "/lockers", component: Lockers }, 13 | // { name: "个人中心", path: "/person-center", component: PersonCenter }, 14 | { name: "首页", path: "/", component: Website }, 15 | ] 16 | 17 | export const Routes = () => { 18 | return ( 19 | 20 | {map(routes, item => )} 21 | 22 | 23 | ) 24 | } -------------------------------------------------------------------------------- /packages/hardhat/example.env: -------------------------------------------------------------------------------- 1 | # This is a template for the environment variables you'll need for 2 | # deployment on Rinkeby and mainnet 3 | 4 | # To use, copy this file, rename it .env, and fill in the values 5 | # Everything will be interpreted as a string by JavaScript even though 6 | # you should not wrap values in quotation marks 7 | 8 | # Infura endpoints should be passed in WITHOUT the rest of the url 9 | # Private keys should be pasted WITHOUT the 0x prefix 10 | 11 | # Example 12 | EXAMPLE_INFURA_KEY=582dabbadbeef8... 13 | EXAMPLE_DEPLOYER_PRIV_KEY=deadbeef01EEdf972aBB... 14 | 15 | # Rinkeby 16 | RINKEBY_INFURA_KEY= 17 | RINKEBY_DEPLOYER_PRIV_KEY= 18 | 19 | # Kovan 20 | KOVAN_INFURA_KEY= 21 | KOVAN_DEPLOYER_PRIV_KEY= 22 | 23 | # mainnet 24 | MAINNET_INFURA_KEY= 25 | MAINNET_DEPLOYER_PRIV_KEY= 26 | 27 | # Ropsten 28 | ROPSTEN_INFURA_KEY= 29 | ROPSTEN_DEPLOYER_PRIV_KEY= 30 | 31 | # Goerli 32 | GOERLI_INFURA_KEY= 33 | GOERLI_DEPLOYER_PRIV_KEY= 34 | 35 | # xDai 36 | XDAI_DEPLOYER_PRIV_KEY= -------------------------------------------------------------------------------- /packages/react-app/src/i18n/config.ts: -------------------------------------------------------------------------------- 1 | import i18n from "i18next"; 2 | import { initReactI18next } from "react-i18next"; 3 | //i18next-browser-languagedetector插件 4 | //这是一个 i18next 语言检测插件,用于检测浏览器中的用户语言, 5 | //详情请访问:https://github.com/i18next/i18next-browser-languageDetector 6 | import LanguageDetector from 'i18next-browser-languagedetector'; 7 | 8 | //import en from "./en-us.json"; 9 | //import hk from "./zh-hk.json"; 10 | import cn from "./zh-cn.json"; 11 | 12 | const resources = { 13 | cn: { 14 | translation: cn, 15 | }, 16 | /* en: { 17 | translation: en, 18 | }, 19 | hk: { 20 | translation: hk, 21 | }, */ 22 | }; 23 | 24 | i18n.use(LanguageDetector) //嗅探当前浏览器语言 zh-CN 25 | .use(initReactI18next).init({ 26 | resources, 27 | interpolation: { 28 | escapeValue: false, 29 | }, 30 | detection: { 31 | caches: ['localStorage', 'sessionStorage', 'cookie'], 32 | } 33 | }); 34 | 35 | export default i18n; -------------------------------------------------------------------------------- /packages/react-app/src/i18n/zh-cn.json: -------------------------------------------------------------------------------- 1 | { 2 | "common": { 3 | "home": "首页", 4 | "stars": "收藏夹" 5 | }, 6 | "header": { 7 | "title": "像素元宇宙", 8 | "play": "首页", 9 | "producted": "作坊", 10 | "lockers": "储物室", 11 | "person": "个人中心", 12 | "connect": "连接钱包", 13 | "disConnect": "断开连接", 14 | "lang": "切换语言", 15 | "search": "查询", 16 | "pleaseInputAddress": "请输入用户钱包地址/TOKEN ID" 17 | }, 18 | "footer": { 19 | "tab": "首页", 20 | "content": "我是关于我们" 21 | }, 22 | "home": { 23 | "title": "首页", 24 | "content": "我是首页", 25 | "webcome": "这里是像素元宇宙,一个已经创世但是还没有被绘制的世界!等待着我们去建造,你准备好了吗!我的少年!让我们一起在像素的世界里遨游吧!像素元宇宙,我们来啦!", 26 | "started": "开始" 27 | }, 28 | "play": { 29 | "tab": "首页", 30 | "content": "我是关于我们", 31 | "starMore": "去收藏更多" 32 | }, 33 | "lockers": { 34 | "tab": "首页", 35 | "content": "我是关于我们" 36 | }, 37 | "producted": { 38 | "tab": "首页", 39 | "content": "我是关于我们" 40 | }, 41 | "person": { 42 | "tab": "首页", 43 | "content": "我是关于我们" 44 | } 45 | } -------------------------------------------------------------------------------- /packages/hardhat/deploy/00_deploy.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | 3 | module.exports = async ({ getNamedAccounts, deployments }) => { 4 | const { deploy } = deployments; 5 | const { deployer } = await getNamedAccounts(); 6 | console.log(deployer, "deployer") 7 | 8 | const PMT721 = await deploy("PMT721", { 9 | from: deployer, 10 | log: true, 11 | }); 12 | console.log("PMT721", PMT721.address) 13 | 14 | // Getting a previously deployed contract 15 | const PMT721Contract = await ethers.getContract("PMT721", deployer); 16 | 17 | const PixelsMetaverse = await deploy("PixelsMetaverse", { 18 | from: deployer, 19 | args: [PMT721Contract.address], 20 | log: true, 21 | }); 22 | console.log("PixelsMetaverse", PixelsMetaverse.address) 23 | 24 | // Getting a previously deployed contract 25 | const PixelsMetaverseContract = await ethers.getContract("PixelsMetaverse", deployer); 26 | 27 | await PMT721Contract.setMinter(PixelsMetaverseContract.address); 28 | console.log("deploy success") 29 | }; 30 | module.exports.tags = ["PMT721"]; 31 | -------------------------------------------------------------------------------- /packages/react-app/src/eos-api/fetch.ts: -------------------------------------------------------------------------------- 1 | import { api, contract } from "./config" 2 | 3 | export interface IProps { 4 | actor?: string, 5 | permission?: "active" | "owner", 6 | action: string, 7 | data?: any, 8 | } 9 | 10 | export const pushAction = async (props: IProps) => { 11 | const { 12 | actor = contract, 13 | permission = "active", 14 | action, 15 | data, 16 | } = props 17 | try { 18 | const result = await api.transact( 19 | { 20 | actions: [ 21 | { 22 | account: actor, 23 | name: action, 24 | authorization: [ 25 | { 26 | actor, 27 | permission, 28 | }, 29 | ], 30 | data, 31 | }, 32 | ], 33 | }, 34 | { 35 | blocksBehind: 3, 36 | expireSeconds: 30, 37 | } 38 | ) 39 | return result 40 | } catch (error) { 41 | return {} 42 | } 43 | } 44 | 45 | export const add = (content: string) => pushAction({ action: "add", data: { content } }).then((res) => res) -------------------------------------------------------------------------------- /packages/react-app/src/components/DataStateBox.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, CSSProperties } from "react"; 2 | import { Empty } from "antd"; 3 | import { isEmpty } from "lodash"; 4 | import { useWeb3Info } from "abi-to-request"; 5 | 6 | export const DataStateBox = ({ 7 | data, 8 | children, 9 | styleCSS, 10 | classCSS, 11 | emptyDesc = "请链接钱包" 12 | }: { 13 | data: any; 14 | children: ReactNode; 15 | styleCSS?: CSSProperties; 16 | classCSS?: string; 17 | emptyDesc?: string; 18 | }) => { 19 | const { connected, networkId } = useWeb3Info(); 20 | 21 | return ( 22 |
26 | {isEmpty(data) 27 | ?
28 | 29 |
30 | : children} 31 |
32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /packages/react-app/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-useless-constructor": "off", 4 | "@typescript-eslint/no-unused-vars": "off", 5 | "@typescript-eslint/explicit-member-accessibility": "off", 6 | "@typescript-eslint/explicit-function-return-type": [ 7 | "off", 8 | { 9 | "allowExpressions": true, 10 | "allowTypedFunctionExpressions": true 11 | } 12 | ], 13 | "@typescript-eslint/interface-name-prefix": 0, 14 | "@typescript-eslint/no-explicit-any": "off", 15 | "@typescript-eslint/no-use-before-define": "off", 16 | "@typescript-eslint/no-parameter-properties": 0, 17 | "@typescript-eslint/camelcase": [ 18 | "off", 19 | { 20 | "properties": "always" 21 | } 22 | ], 23 | "no-console": "off", 24 | "eqeqeq": [ 25 | "warn", 26 | "always" 27 | ], 28 | "prettier/prettier": "off", 29 | "@typescript-eslint/indent": "off", 30 | "no-eval": "off", 31 | "react-hooks/exhaustive-deps": "off", 32 | "@typescript-eslint/no-array-constructor": "off" 33 | }, 34 | "extends": ["react-app"] 35 | } -------------------------------------------------------------------------------- /packages/services/graph-node/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | graph-node: 4 | image: graphprotocol/graph-node:latest 5 | ports: 6 | - "8000:8000" 7 | - "8001:8001" 8 | - "8020:8020" 9 | - "8030:8030" 10 | - "8040:8040" 11 | depends_on: 12 | - ipfs 13 | - postgres 14 | environment: 15 | postgres_host: postgres 16 | postgres_user: graph-node 17 | postgres_pass: let-me-in 18 | postgres_db: graph-node 19 | ipfs: "ipfs:5001" 20 | ethereum: "localhost:http://host.docker.internal:8545" 21 | GRAPH_LOG: info 22 | ipfs: 23 | image: ipfs/go-ipfs:latest 24 | ports: 25 | - "5001:5001" 26 | volumes: 27 | - ./data/ipfs:/data/ipfs 28 | postgres: 29 | image: postgres 30 | ports: 31 | - "5432:5432" 32 | command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"] 33 | environment: 34 | POSTGRES_USER: graph-node 35 | POSTGRES_PASSWORD: let-me-in 36 | POSTGRES_DB: graph-node 37 | volumes: 38 | - ./data/postgres:/var/lib/postgresql/data 39 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/play/components/Collections.tsx: -------------------------------------------------------------------------------- 1 | import { map } from "lodash"; 2 | import { useHistory } from "react-router-dom"; 3 | import { AvatarCard } from "../../../components/AvatarCard"; 4 | import { MaterialItem } from "../../../components/Card"; 5 | import { DataStateBox } from "../../../components/DataStateBox"; 6 | 7 | export const Collections = ({ noCollectionList }: { noCollectionList: MaterialItem[] }) => { 8 | const history = useHistory() 9 | 10 | return ( 11 |
12 |
13 |
未收藏
14 |
{ history.push("/lockers") }}>去收藏更多
15 |
16 | 17 |
18 | {map(noCollectionList, item => )} 19 |
20 |
21 |
22 | ); 23 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Austin Griffith 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/subgraph/subgraph.yaml: -------------------------------------------------------------------------------- 1 | specVersion: 0.0.4 2 | schema: 3 | file: ./schema.graphql 4 | dataSources: 5 | - kind: ethereum 6 | name: PixelsMetaverse 7 | network: kovan 8 | source: 9 | address: "0xe9b1Fb1B42C59aC6e660dAeCDe374505a1F0E75A" 10 | abi: PixelsMetaverse 11 | startBlock: 30684149 12 | mapping: 13 | kind: ethereum/events 14 | apiVersion: 0.0.6 15 | language: wasm/assemblyscript 16 | entities: 17 | - AvaterEvent 18 | - MaterialEvent 19 | - ComposeEvent 20 | - ConfigEvent 21 | abis: 22 | - name: PixelsMetaverse 23 | file: ./abis/localhost_PixelsMetaverse.json 24 | eventHandlers: 25 | - event: AvaterEvent(address,uint256) 26 | handler: handleAvaterEvent 27 | - event: MaterialEvent(address,uint256,uint256,uint256,string,bool) 28 | handler: handleMaterialEvent 29 | - event: ComposeEvent(uint256,uint256,uint256[],bool) 30 | handler: handleComposeEvent 31 | - event: ConfigEvent(uint256,string,string,string,string,string,uint256) 32 | handler: handleConfigEvent 33 | file: ./src/mapping.ts -------------------------------------------------------------------------------- /packages/services/graph-node/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if ! which docker 2>&1 > /dev/null; then 6 | echo "Please install 'docker' first" 7 | exit 1 8 | fi 9 | 10 | if ! which docker-compose 2>&1 > /dev/null; then 11 | echo "Please install 'docker-compose' first" 12 | exit 1 13 | fi 14 | 15 | if ! which jq 2>&1 > /dev/null; then 16 | echo "Please install 'jq' first" 17 | exit 1 18 | fi 19 | 20 | # Create the graph-node container 21 | docker-compose up --no-start graph-node 22 | 23 | # Start graph-node so we can inspect it 24 | docker-compose start graph-node 25 | 26 | # Identify the container ID 27 | CONTAINER_ID=$(docker container ls | grep graph-node | cut -d' ' -f1) 28 | 29 | # Inspect the container to identify the host IP address 30 | HOST_IP=$(docker inspect "$CONTAINER_ID" | jq -r .[0].NetworkSettings.Networks[].Gateway) 31 | 32 | echo "Host IP: $HOST_IP" 33 | 34 | # Inject the host IP into docker-compose.yml 35 | sed -i -e "s/host.docker.internal/$HOST_IP/g" docker-compose.yml 36 | 37 | function stop_graph_node { 38 | # Ensure graph-node is stopped 39 | docker-compose stop graph-node 40 | } 41 | 42 | trap stop_graph_node EXIT 43 | -------------------------------------------------------------------------------- /packages/react-app/scripts/s3.js: -------------------------------------------------------------------------------- 1 | const s3FolderUpload = require("s3-folder-upload"); 2 | const fs = require("fs"); 3 | 4 | const directoryName = "build"; 5 | 6 | const BUCKETNAME = ""; // <<---- SET YOUR BUCKET NAME AND CREATE aws.json ** see below vvvvvvvvvv 7 | 8 | if (!BUCKETNAME) { 9 | console.log("☢️ Enter a bucket name in packages/react-app/scripts/s3.js "); 10 | process.exit(1); 11 | } 12 | 13 | let credentials = {}; 14 | try { 15 | credentials = JSON.parse(fs.readFileSync("aws.json")); 16 | } catch (e) { 17 | console.log(e); 18 | console.log( 19 | '☢️ Create an aws.json credentials file in packages/react-app/ like { "accessKeyId": "xxx", "secretAccessKey": "xxx", "region": "xxx" } ', 20 | ); 21 | process.exit(1); 22 | } 23 | console.log("credentials", credentials); 24 | 25 | credentials.bucket = BUCKETNAME; 26 | 27 | // optional options to be passed as parameter to the method 28 | const options = { 29 | useFoldersForFileTypes: false, 30 | useIAMRoleCredentials: false, 31 | }; 32 | 33 | // optional cloudfront invalidation rule 34 | // const invalidation = { 35 | // awsDistributionId: "", 36 | // awsInvalidationPath: "/*" 37 | // } 38 | 39 | s3FolderUpload(directoryName, credentials, options /* , invalidation */); 40 | -------------------------------------------------------------------------------- /packages/react-app/electron/main.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | // 控制应用生命周期的模块 3 | const {app} = electron; 4 | // 创建本地浏览器窗口的模块 5 | const {BrowserWindow} = electron; 6 | 7 | // 指向窗口对象的一个全局引用,如果没有这个引用,那么当该javascript对象被垃圾回收的 8 | // 时候该窗口将会自动关闭 9 | let win; 10 | 11 | function createWindow() { 12 | // 创建一个新的浏览器窗口 13 | win = new BrowserWindow({width: 1920, height: 1080}); 14 | 15 | // 并且装载应用的index.html页面 16 | win.loadURL(`file://${__dirname}/index.html`); 17 | 18 | // 打开开发工具页面 19 | //win.webContents.openDevTools(); 20 | 21 | // 当窗口关闭时调用的方法 22 | win.on('closed', () => { 23 | // 解除窗口对象的引用,通常而言如果应用支持多个窗口的话,你会在一个数组里 24 | // 存放窗口对象,在窗口关闭的时候应当删除相应的元素。 25 | win = null; 26 | }); 27 | } 28 | 29 | // 当Electron完成初始化并且已经创建了浏览器窗口,则该方法将会被调用。 30 | // 有些API只能在该事件发生后才能被使用。 31 | app.on('ready', createWindow); 32 | /* var mainWindow = new BrowserWindow({ 33 | webPreferences: { 34 | nodeIntegration: false 35 | } 36 | }); */ 37 | // 当所有的窗口被关闭后退出应用 38 | app.on('window-all-closed', () => { 39 | // 对于OS X系统,应用和相应的菜单栏会一直激活直到用户通过Cmd + Q显式退出 40 | if (process.platform !== 'darwin') { 41 | app.quit(); 42 | } 43 | }); 44 | 45 | app.on('activate', () => { 46 | // 对于OS X系统,当dock图标被点击后会重新创建一个app窗口,并且不会有其他 47 | // 窗口打开 48 | if (win === null) { 49 | createWindow(); 50 | } 51 | }); 52 | 53 | // 在这个文件后面你可以直接包含你应用特定的由主进程运行的代码。 54 | // 也可以把这些代码放在另一个文件中然后在这里导入。 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #packages/subgraph/subgraph.yaml 2 | packages/subgraph/generated 3 | #packages/subgraph/abis/* 4 | packages/hardhat/*.txt 5 | **/aws.json 6 | 7 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 8 | **/node_modules 9 | 10 | packages/hardhat/artifacts* 11 | packages/hardhat/deployments 12 | #packages/react-app/src/contracts/* 13 | !packages/react-app/src/contracts/external_contracts.js 14 | packages/hardhat/cache* 15 | packages/**/data 16 | !packages/react-app/src/contracts/contracts.js 17 | 18 | packages/react-pixels-metaverse 19 | 20 | # ts 21 | packages/hardhat-ts/cache 22 | # secrets 23 | .secret 24 | 25 | packages/subgraph/config/config.json 26 | tenderly.yaml 27 | 28 | # dependencies 29 | /node_modules 30 | /.pnp 31 | .pnp.js 32 | 33 | # testing 34 | coverage 35 | 36 | # production 37 | build 38 | # yarn / eslint 39 | .yarn/cache 40 | .yarn/install-state.gz 41 | .yarn/build-state.yml 42 | .eslintcache 43 | # testing 44 | coverage 45 | 46 | # production 47 | build 48 | 49 | # Hardhat files 50 | cache 51 | artifacts 52 | 53 | # misc 54 | .DS_Store 55 | .env* 56 | 57 | # debug 58 | npm-debug.log* 59 | yarn-debug.log* 60 | yarn-error.log* 61 | 62 | .idea 63 | 64 | # Local Netlify folder 65 | .netlify 66 | *.tsbuildinfo 67 | *.stackdump 68 | 69 | # doc directory 70 | /doc 71 | 72 | **/out 73 | **/src/react-pixels-metaverse 74 | 75 | # production 76 | **/build 77 | **/play.md 78 | **/app.drawio 79 | 80 | # misc 81 | .DS_Store 82 | .env.local 83 | .env.development.local 84 | .env.test.local 85 | .env.production.local 86 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/produced/index.tsx: -------------------------------------------------------------------------------- 1 | import { Submit } from "./components/Submit"; 2 | import { PixelsMetaverseHandleImg, PixelsMetaverseHandleImgProvider, usePixelsMetaverseHandleImg, CanvasSlicImg } from "../../pixels-metaverse"; 3 | import { Controller } from "./components/Controller"; 4 | import { UploadImg } from "./components/UploadImg"; 5 | 6 | export const ProducedCore = () => { 7 | const { config, canvasRef, canvas2Ref } = usePixelsMetaverseHandleImg() 8 | 9 | return ( 10 |
11 |
12 | 13 | 14 |
15 | {config?.bgImg && <> 16 | 17 | 18 | } 19 | 20 |
21 |
22 | 23 |
24 | ) 25 | } 26 | 27 | export const Produced = () => { 28 | return ( 29 | 40 | 41 | 42 | ) 43 | } -------------------------------------------------------------------------------- /packages/hardhat/contracts/PMT721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 5 | import "./IPixelsMetaverse.sol"; 6 | 7 | contract PMT721 is ERC721 { 8 | address public minter; 9 | uint256 private _tokenId; 10 | address private _owner; 11 | 12 | modifier MustMinter(address from) { 13 | require(from == minter, "Only Minter Can Do It!"); 14 | _; 15 | } 16 | 17 | modifier MustOwner(address from) { 18 | require(from == _owner, "Only Owner Can Do It!"); 19 | _; 20 | } 21 | 22 | constructor() ERC721("PixelsMetavers", "PMT") { 23 | _owner = _msgSender(); 24 | } 25 | 26 | function mint(address to) public MustMinter(_msgSender()) { 27 | _mint(to, ++_tokenId); 28 | _approve(minter, _tokenId); 29 | } 30 | 31 | function burn(uint256 id) public { 32 | require(ownerOf(id) == _msgSender(), "Only Owner Can Do It!"); 33 | _burn(id); 34 | } 35 | 36 | function exits(uint256 id) public view returns (bool) { 37 | return _exists(id); 38 | } 39 | 40 | function setMinter(address _minter) public MustOwner(_msgSender()) { 41 | minter = _minter; 42 | } 43 | 44 | function setOwner(address owner) public MustOwner(_msgSender()) { 45 | _owner = owner; 46 | } 47 | 48 | function currentID() public view returns (uint256) { 49 | return _tokenId; 50 | } 51 | 52 | function _afterTokenTransfer( 53 | address from, 54 | address to, 55 | uint256 tokenId 56 | ) internal virtual override { 57 | IPixelsMetavers(minter).handleTransfer(from, to, tokenId); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 25 | Pixels Metaverse 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /packages/react-app/src/assets/image/bg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/services/graph-node/wait_for: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # POSIX compatible clone of wait-for-it.sh 4 | # This copy is from https://github.com/eficode/wait-for/commits/master 5 | # at commit 8d9b4446 6 | 7 | TIMEOUT=15 8 | QUIET=0 9 | 10 | echoerr() { 11 | if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi 12 | } 13 | 14 | usage() { 15 | exitcode="$1" 16 | cat << USAGE >&2 17 | Usage: 18 | $cmdname host:port [-t timeout] [-- command args] 19 | -q | --quiet Do not output any status messages 20 | -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout 21 | -- COMMAND ARGS Execute command with args after the test finishes 22 | USAGE 23 | exit "$exitcode" 24 | } 25 | 26 | wait_for() { 27 | for i in `seq $TIMEOUT` ; do 28 | nc -z "$HOST" "$PORT" > /dev/null 2>&1 29 | 30 | result=$? 31 | if [ $result -eq 0 ] ; then 32 | if [ $# -gt 0 ] ; then 33 | exec "$@" 34 | fi 35 | exit 0 36 | fi 37 | sleep 1 38 | done 39 | echo "Operation timed out" >&2 40 | exit 1 41 | } 42 | 43 | while [ $# -gt 0 ] 44 | do 45 | case "$1" in 46 | *:* ) 47 | HOST=$(printf "%s\n" "$1"| cut -d : -f 1) 48 | PORT=$(printf "%s\n" "$1"| cut -d : -f 2) 49 | shift 1 50 | ;; 51 | -q | --quiet) 52 | QUIET=1 53 | shift 1 54 | ;; 55 | -t) 56 | TIMEOUT="$2" 57 | if [ "$TIMEOUT" = "" ]; then break; fi 58 | shift 2 59 | ;; 60 | --timeout=*) 61 | TIMEOUT="${1#*=}" 62 | shift 1 63 | ;; 64 | --) 65 | shift 66 | break 67 | ;; 68 | --help) 69 | usage 0 70 | ;; 71 | *) 72 | echoerr "Unknown argument: $1" 73 | usage 1 74 | ;; 75 | esac 76 | done 77 | 78 | if [ "$HOST" = "" -o "$PORT" = "" ]; then 79 | echoerr "Error: you need to provide a host and port to test." 80 | usage 2 81 | fi 82 | 83 | wait_for "$@" 84 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/radix.ts: -------------------------------------------------------------------------------- 1 | 2 | export const char = `0123456789abcdef|ghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ/+)(*&^%$#@!{}> { 7 | let strRadix = "" 8 | try { 9 | if (beforeRadix === 10) { 10 | strRadix = string10ToOtherRadix(strNumber, afterRadix) 11 | return strRadix 12 | } 13 | if (afterRadix === 10) { 14 | strRadix = String(stringOtherRadixTo10(strNumber, beforeRadix)) 15 | return strRadix 16 | } 17 | const get10Radix = String(stringOtherRadixTo10(strNumber, beforeRadix)) 18 | strRadix = string10ToOtherRadix(get10Radix, afterRadix) 19 | } catch (error) { 20 | console.error(error) 21 | } 22 | return strRadix 23 | } 24 | 25 | //将其他进制数转换成10进制 26 | export const stringOtherRadixTo10 = (strNumber: string, beforeRadix: number) => { 27 | let res = BigInt(0), len = strNumber.length; 28 | const radixBig = BigInt(beforeRadix) 29 | const chars = char.slice(0, beforeRadix) 30 | 31 | for (let i = 0; i < len; i++) { 32 | const count = len - 1 - i 33 | let pow = eval("(function(a,b){return a**b})") 34 | Math.pow = pow; 35 | const miNum = radixBig ** BigInt(i) 36 | res += BigInt(chars.indexOf(strNumber[count])) * miNum 37 | } 38 | return res; 39 | } 40 | 41 | //将10进制数转换成其他进制 42 | export function string10ToOtherRadix(strNumber: string, afterRadix: number) { 43 | if (strNumber.length === 2) return "" 44 | let chars = char.slice(0, afterRadix).split(""), 45 | radix = BigInt(chars.length), 46 | qutient = BigInt(strNumber), 47 | arr = []; 48 | do { 49 | const mod = qutient % radix; 50 | qutient = (qutient - mod) / radix; 51 | arr.unshift(chars[Number(mod)]); 52 | } while (qutient); 53 | return arr.join(''); 54 | } -------------------------------------------------------------------------------- /packages/react-app/src/pages/play/components/PersonCenter.tsx: -------------------------------------------------------------------------------- 1 | import { isEmpty, map } from "lodash"; 2 | import { useHistory, useLocation } from "react-router-dom"; 3 | import { AvatarCard } from "../../../components/AvatarCard"; 4 | import { MaterialItem } from "../../../components/Card"; 5 | import { DataStateBox } from "../../../components/DataStateBox"; 6 | 7 | export const PersonCenter = ({ 8 | avater, 9 | colectionList, 10 | onwerList 11 | }: { 12 | avater?: MaterialItem; 13 | colectionList: MaterialItem[]; 14 | onwerList: MaterialItem[] 15 | }) => { 16 | const history = useHistory() 17 | const { search } = useLocation() 18 | const address = search ? search.split("=")[1] : "" 19 | 20 | return ( 21 |
22 |
23 |
个人中心
24 |
{ history.push(`/person-center${address ? "?address=" + address : ""}`) }} 27 | >查看更多
28 |
29 | 30 |
33 | {avater &&
34 |
Avater
35 | {map([avater], item => )} 36 |
} 37 | {!isEmpty(onwerList) &&
38 |
Your Material
39 | {map(onwerList, item => )} 40 |
} 41 | {!isEmpty(colectionList) &&
42 |
Your Star
43 | {map(colectionList, item => )} 44 |
} 45 |
46 |
47 |
48 | ); 49 | }; -------------------------------------------------------------------------------- /packages/hardhat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pmerc721/hardhat", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "eslint": "^7.5.0", 8 | "eslint-config-airbnb": "^18.2.0", 9 | "eslint-config-prettier": "^6.11.0", 10 | "eslint-plugin-babel": "^5.3.1", 11 | "eslint-plugin-prettier": "^3.4.0", 12 | "hardhat": "^2.8.4" 13 | }, 14 | "dependencies": { 15 | "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers", 16 | "@nomiclabs/hardhat-etherscan": "^2.1.7", 17 | "@nomiclabs/hardhat-waffle": "^2.0.0", 18 | "@openzeppelin/contracts": "^4.4.2", 19 | "@tenderly/hardhat-tenderly": "^1.0.10", 20 | "chai": "^4.2.0", 21 | "chalk": "^4.1.0", 22 | "dotenv": "^8.2.0", 23 | "ethereum-waffle": "^3.1.1", 24 | "ethers": "^5.4.4", 25 | "hardhat": "^2.8.4", 26 | "hardhat-deploy": "^0.10.5", 27 | "hardhat-gas-reporter": "^1.0.4", 28 | "node-watch": "^0.7.0", 29 | "qrcode-terminal": "^0.12.0", 30 | "ramda": "^0.27.1" 31 | }, 32 | "scripts": { 33 | "chain": "hardhat node --network hardhat --no-deploy", 34 | "fork": "hardhat node --no-deploy --network hardhat --fork https://mainnet.infura.io/v3/460f40a260564ac4a4f4b3fffb032dad", 35 | "test": "hardhat test --network hardhat", 36 | "compile": "hardhat compile", 37 | "deploy": "hardhat deploy --export-all ../react-app/src/contracts/hardhat_contracts.json && npm run send", 38 | "deploy1": "hardhat deploy --export-all ../react-app/src/contracts/hardhat_contracts.json", 39 | "postdeploy": "hardhat run scripts/publish.js", 40 | "watch": "node scripts/watch.js", 41 | "accounts": "hardhat accounts", 42 | "balance": "hardhat balance", 43 | "send": "hardhat send --from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --to 0xf0A3FdF9dC875041DFCF90ae81D7E01Ed9Bc2033 --amount 10", 44 | "generate": "hardhat generate", 45 | "account": "hardhat account", 46 | "verify": "hardhat etherscan-verify --api-key PSW8C433Q667DVEX5BCRMGNAH9FSGFZ7Q8", 47 | "ganache": "hardhat deploy --network ganache --export-all ../react-app/src/contracts/hardhat_contracts.json" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/person-center/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react" 2 | import { Dictionary, isEmpty, map } from "lodash"; 3 | import { useUserInfo } from "../../components/UserProvider"; 4 | import { PixelsMetaverseHandleImgProvider, useConvertedPostion } from "../../pixels-metaverse"; 5 | import { BaseInfo } from "./components/BaseInfo"; 6 | import { AssetsInfo } from "./components/AssetsInfo"; 7 | import { useGetPersonData } from "../play"; 8 | 9 | export const PersonCenter = () => { 10 | const { userInfo } = useUserInfo() 11 | const convertedPostion = useConvertedPostion() 12 | const { avater } = useGetPersonData() 13 | 14 | const avaterData = useMemo(() => { 15 | avater?.composeData?.push(avater) 16 | return avater 17 | }, [avater?.composeData]) 18 | 19 | const positions = React.useMemo(() => { 20 | if (isEmpty(avaterData?.composeData)) return "empty" 21 | let data: Dictionary = {} 22 | map(avaterData?.composeData, item => { 23 | const positionsData = convertedPostion({ 24 | positions: item?.baseInfo?.data 25 | }) 26 | data = { ...data, ...positionsData } 27 | }) 28 | 29 | const colors: Dictionary = {} 30 | for (let i in data) { 31 | colors[data[i]] ? colors[data[i]].push(Number(i)) : colors[data[i]] = [Number(i)] 32 | } 33 | 34 | let str = "" 35 | let min = 1 36 | for (let i in colors) { 37 | const position = map(colors[i], ite => (ite - min).toString(36)).join("|") 38 | str += `${parseInt(i.slice(1), 16).toString(36)}-${position}-` 39 | } 40 | return `${str}${min}` 41 | }, [avaterData?.composeData]) 42 | 43 | return ( 44 | <>{positions && 53 |
54 |
55 | 56 | 57 |
58 |
59 |
} 60 | 61 | ) 62 | } -------------------------------------------------------------------------------- /packages/services/graph-node/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | options: 2 | machineType: "N1_HIGHCPU_32" 3 | timeout: 1800s 4 | steps: 5 | - name: 'gcr.io/cloud-builders/docker' 6 | args: ['build', '--target', 'graph-node-build', 7 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 8 | '--build-arg', 'REPO_NAME=$REPO_NAME', 9 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 10 | '--build-arg', 'TAG_NAME=$TAG_NAME', 11 | '-t', 'gcr.io/$PROJECT_ID/graph-node-build:$SHORT_SHA', 12 | '-f', 'docker/Dockerfile', '.'] 13 | - name: 'gcr.io/cloud-builders/docker' 14 | args: ['build', '--target', 'graph-node', 15 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 16 | '--build-arg', 'REPO_NAME=$REPO_NAME', 17 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 18 | '--build-arg', 'TAG_NAME=$TAG_NAME', 19 | '-t', 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA', 20 | '-f', 'docker/Dockerfile', '.'] 21 | - name: 'gcr.io/cloud-builders/docker' 22 | args: ['build', '--target', 'graph-node-debug', 23 | '--build-arg', 'COMMIT_SHA=$COMMIT_SHA', 24 | '--build-arg', 'REPO_NAME=$REPO_NAME', 25 | '--build-arg', 'BRANCH_NAME=$BRANCH_NAME', 26 | '--build-arg', 'TAG_NAME=$TAG_NAME', 27 | '-t', 'gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA', 28 | '-f', 'docker/Dockerfile', '.'] 29 | - name: 'gcr.io/cloud-builders/docker' 30 | args: ['tag', 31 | 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA', 32 | 'lutter/graph-node:$SHORT_SHA'] 33 | - name: 'gcr.io/cloud-builders/docker' 34 | entrypoint: 'bash' 35 | args: ['docker/tag.sh'] 36 | secretEnv: ['PASSWORD'] 37 | env: 38 | - 'SHORT_SHA=$SHORT_SHA' 39 | - 'TAG_NAME=$TAG_NAME' 40 | - 'PROJECT_ID=$PROJECT_ID' 41 | - 'DOCKER_HUB_USER=$_DOCKER_HUB_USER' 42 | - 'BRANCH_NAME=$BRANCH_NAME' 43 | images: 44 | - 'gcr.io/$PROJECT_ID/graph-node-build:$SHORT_SHA' 45 | - 'gcr.io/$PROJECT_ID/graph-node:$SHORT_SHA' 46 | - 'gcr.io/$PROJECT_ID/graph-node-debug:$SHORT_SHA' 47 | substitutions: 48 | # The owner of the access token whose encrypted value is in PASSWORD 49 | _DOCKER_HUB_USER: "lutter" 50 | secrets: 51 | - kmsKeyName: projects/the-graph-staging/locations/global/keyRings/docker/cryptoKeys/docker-hub-push 52 | secretEnv: 53 | PASSWORD: 'CiQAdfFldbmUiHgGP1lPq6bAOfd+VQ/dFwyohB1IQwiwQg03ZE8STQDvWKpv6eJHVUN1YoFC5FcooJrH+Stvx9oMD7jBjgxEH5ngIiAysWP3E4Pgxt/73xnaanbM1EQ94eVFKCiY0GaEKFNu0BJx22vCYmU4' 54 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/produced/components/Controller.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { isEmpty } from "lodash"; 3 | import { Tooltip } from "antd"; 4 | import { AppstoreOutlined, ClearOutlined, DeleteOutlined, ExclamationCircleOutlined } from "@ant-design/icons"; 5 | import { usePixelsMetaverseHandleImg } from "../../../pixels-metaverse"; 6 | 7 | export const Controller = () => { 8 | const { 9 | setConfig, 10 | config, 11 | setPositionsArr, 12 | dealClick: { clear, value } 13 | } = usePixelsMetaverseHandleImg() 14 | 15 | return ( 16 |
20 | 21 | setConfig((pre) => ({ ...pre, withGrid: !config?.withGrid }))} /> 22 | 23 | 24 | { 25 | clear() 26 | setPositionsArr([]) 27 | }} /> 28 | 29 | 30 | setConfig((pre) => ({ ...pre, drawColor: e.target.value }))} /> 31 | 32 | 33 | setConfig((pre) => ({ ...pre, drawColor: "" }))} /> 34 | 35 | 36 | setConfig((pre) => ({ ...pre, bgColor: e.target.value }))} /> 37 | 38 | { 39 | return (
40 |
1.清除画布数据会导致之前所有绘制丢失
41 |
2.选择了绘制颜色后,将不会从当前选中地方取色,除非丢弃该颜色
42 |
3.丢弃当前选中色后,绘制的颜色将是点击处颜色
43 |
4.双击即可删除当前位置数据
44 |
) 45 | }} color="#29303d"> 46 | 47 |
48 |
49 | ) 50 | } -------------------------------------------------------------------------------- /packages/services/graph-node/Dockerfile: -------------------------------------------------------------------------------- 1 | # Full build with debuginfo for graph-node 2 | # 3 | # The expectation if that the docker build uses the parent directory as PWD 4 | # by running something like the following 5 | # docker build --target STAGE -f docker/Dockerfile . 6 | 7 | FROM rust:latest as graph-node-build 8 | 9 | ARG COMMIT_SHA=unknown 10 | ARG REPO_NAME=unknown 11 | ARG BRANCH_NAME=unknown 12 | ARG TAG_NAME=unknown 13 | 14 | ADD . /graph-node 15 | 16 | RUN cd /graph-node \ 17 | && RUSTFLAGS="-g" cargo install --locked --path node \ 18 | && cargo clean \ 19 | && objcopy --only-keep-debug /usr/local/cargo/bin/graph-node /usr/local/cargo/bin/graph-node.debug \ 20 | && strip -g /usr/local/cargo/bin/graph-node \ 21 | && cd /usr/local/cargo/bin \ 22 | && objcopy --add-gnu-debuglink=graph-node.debug graph-node \ 23 | && echo "REPO_NAME='$REPO_NAME'" > /etc/image-info \ 24 | && echo "TAG_NAME='$TAG_NAME'" >> /etc/image-info \ 25 | && echo "BRANCH_NAME='$BRANCH_NAME'" >> /etc/image-info \ 26 | && echo "COMMIT_SHA='$COMMIT_SHA'" >> /etc/image-info \ 27 | && echo "CARGO_VERSION='$(cargo --version)'" >> /etc/image-info \ 28 | && echo "RUST_VERSION='$(rustc --version)'" >> /etc/image-info 29 | 30 | # The graph-node runtime image with only the executable 31 | FROM debian:buster-slim as graph-node 32 | ENV RUST_LOG "" 33 | ENV GRAPH_LOG "" 34 | ENV EARLY_LOG_CHUNK_SIZE "" 35 | ENV ETHEREUM_RPC_PARALLEL_REQUESTS "" 36 | ENV ETHEREUM_BLOCK_CHUNK_SIZE "" 37 | 38 | ENV postgres_host "" 39 | ENV postgres_user "" 40 | ENV postgres_pass "" 41 | ENV postgres_db "" 42 | # The full URL to the IPFS node 43 | ENV ipfs "" 44 | # The etherum network(s) to connect to. Set this to a space-separated 45 | # list of the networks where each entry has the form NAME:URL 46 | ENV ethereum "" 47 | # The role the node should have, one of index-node, query-node, or 48 | # combined-node 49 | ENV node_role "combined-node" 50 | # The name of this node 51 | ENV node_id "default" 52 | 53 | # HTTP port 54 | EXPOSE 8000 55 | # WebSocket port 56 | EXPOSE 8001 57 | # JSON-RPC port 58 | EXPOSE 8020 59 | 60 | RUN apt-get update \ 61 | && apt-get install -y libpq-dev ca-certificates netcat 62 | 63 | ADD docker/wait_for docker/start /usr/local/bin/ 64 | COPY --from=graph-node-build /usr/local/cargo/bin/graph-node /usr/local/bin 65 | COPY --from=graph-node-build /etc/image-info /etc/image-info 66 | COPY docker/Dockerfile /Dockerfile 67 | CMD start 68 | 69 | # Debug image to access core dumps 70 | FROM graph-node-build as graph-node-debug 71 | RUN apt-get update \ 72 | && apt-get install -y curl gdb postgresql-client 73 | 74 | COPY docker/Dockerfile /Dockerfile 75 | COPY docker/bin/* /usr/local/bin/ 76 | -------------------------------------------------------------------------------- /packages/react-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from "react-router-dom"; 2 | import { Loading, LoadingProvider, useLoading } from './components/Loading'; 3 | import { UserInfoProvider } from './components/UserProvider'; 4 | import { PixelsMetaverseContextProvider } from './pixels-metaverse'; 5 | import { Header } from './components/Header'; 6 | import bgSvg from "./assets/image/bg.svg" 7 | import { Routes } from './routes'; 8 | import { ContractRequestContextProvider, useWeb3Info, Web3InfoProvider } from "abi-to-request"; 9 | import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client" 10 | import { abis } from "./client/abis"; 11 | 12 | const client = new ApolloClient({ 13 | uri: "https://api.thegraph.com/subgraphs/id/QmbuqjhEAzeU1hG9fn2tCEJwmj8FiQvax5rsb9CzZmj4mS", 14 | //uri: "https://api.thegraph.com/subgraphs/name/qianduanxinlv/pixels", 15 | cache: new InMemoryCache() 16 | }) 17 | 18 | declare global { 19 | // tslint:disable-next-line 20 | interface Window { 21 | web3: any; 22 | ethereum: any; 23 | Web3Modal: any; 24 | Box: any; 25 | box: any; 26 | space: any; 27 | [name: string]: any; 28 | } 29 | } 30 | 31 | const Main = () => { 32 | const { pathname } = useLocation() 33 | const { library } = useWeb3Info() 34 | const { openLoading, closeDelayLoading } = useLoading() 35 | 36 | return ( 37 | { 42 | openLoading() 43 | }, 44 | onSuccess: () => { 45 | closeDelayLoading() 46 | }, 47 | onFail: ()=>{ 48 | closeDelayLoading() 49 | } 50 | }}> 51 | 52 | 53 | {pathname !== "/" &&
} 54 | 55 | 56 | 57 | 58 | 59 | ) 60 | } 61 | 62 | const App = () => { 63 | return ( 64 |
65 |
67 | 68 | 69 | 70 |
71 | 72 | 73 | 74 |
75 |
76 | ) 77 | } 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /packages/services/graph-node/README.md: -------------------------------------------------------------------------------- 1 | # Graph Node Docker Image 2 | 3 | Preconfigured Docker image for running a Graph Node. 4 | 5 | ## Usage 6 | 7 | ```sh 8 | docker run -it \ 9 | -e postgres_host=[:] \ 10 | -e postgres_user= \ 11 | -e postgres_pass= \ 12 | -e postgres_db= \ 13 | -e ipfs=: \ 14 | -e ethereum=: \ 15 | graphprotocol/graph-node:latest 16 | ``` 17 | 18 | ### Example usage 19 | 20 | ```sh 21 | docker run -it \ 22 | -e postgres_host=host.docker.internal:5432 23 | -e postgres_user=graph-node \ 24 | -e postgres_pass=oh-hello \ 25 | -e postgres_db=graph-node \ 26 | -e ipfs=host.docker.internal:5001 \ 27 | -e ethereum=mainnet:http://localhost:8545/ \ 28 | graphprotocol/graph-node:latest 29 | ``` 30 | 31 | ## Docker Compose 32 | 33 | The Docker Compose setup requires an Ethereum network name and node 34 | to connect to. By default, it will use `mainnet:http://host.docker.internal:8545` 35 | in order to connect to an Ethereum node running on your host machine. 36 | You can replace this with anything else in `docker-compose.yaml`. 37 | 38 | > **Note for Linux users:** On Linux, `host.docker.internal` is not 39 | > currently supported. Instead, you will have to replace it with the 40 | > IP address of your Docker host (from the perspective of the Graph 41 | > Node container). 42 | > To do this, run: 43 | > 44 | > ``` 45 | > CONTAINER_ID=$(docker container ls | grep graph-node | cut -d' ' -f1) 46 | > docker exec $CONTAINER_ID /bin/bash -c 'ip route | awk \'/^default via /{print $3}\'' 47 | > ``` 48 | > 49 | > This will print the host's IP address. Then, put it into `docker-compose.yml`: 50 | > 51 | > ``` 52 | > sed -i -e 's/host.docker.internal//g' docker-compose.yml 53 | > ``` 54 | 55 | After you have set up an Ethereum node—e.g. Ganache or Parity—simply 56 | clone this repository and run 57 | 58 | ```sh 59 | docker-compose up 60 | ``` 61 | 62 | This will start IPFS, Postgres and Graph Node in Docker and create persistent 63 | data directories for IPFS and Postgres in `./data/ipfs` and `./data/postgres`. You 64 | can access these via: 65 | 66 | - Graph Node: 67 | - GraphiQL: `http://localhost:8000/` 68 | - HTTP: `http://localhost:8000/subgraphs/name/` 69 | - WebSockets: `ws://localhost:8001/subgraphs/name/` 70 | - Admin: `http://localhost:8020/` 71 | - IPFS: 72 | - `127.0.0.1:5001` or `/ip4/127.0.0.1/tcp/5001` 73 | - Postgres: 74 | - `postgresql://graph-node:let-me-in@localhost:5432/graph-node` 75 | 76 | Once this is up and running, you can use 77 | [`graph-cli`](https://github.com/graphprotocol/graph-cli) to create and 78 | deploy your subgraph to the running Graph Node. 79 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/produced/components/UploadImg.tsx: -------------------------------------------------------------------------------- 1 | import CloseCircleOutlined from "@ant-design/icons/lib/icons/CloseCircleOutlined" 2 | import { Button } from "antd"; 3 | import React, { useCallback, useEffect, useRef, useState } from "react" 4 | import { usePixelsMetaverseHandleImg } from "../../../pixels-metaverse"; 5 | 6 | export const UploadImg = () => { 7 | const { setConfig, config, dealClick: { setValue } } = usePixelsMetaverseHandleImg() 8 | const filedomRef = useRef(null) 9 | const [src, setSrc] = useState(localStorage.getItem("imgUrl") || "") 10 | const [url, setUrl] = useState(src) 11 | 12 | //上传图片 13 | const fileOnChange = useCallback(() => { 14 | const filedom = filedomRef.current 15 | if (filedom && filedom?.files) { 16 | const file = filedom?.files[0]; 17 | if (!file.type.match("image.*")) { 18 | return 19 | } 20 | let reader = new FileReader() 21 | reader.onload = function () { 22 | let bytes = this.result 23 | let img = new Image() 24 | img.src = "" + bytes 25 | img.onload = function () { 26 | setValue({}) 27 | setConfig((pre) => ({ ...pre, bgImg: img })) 28 | } 29 | } 30 | reader.readAsDataURL(file) 31 | } 32 | }, [filedomRef]) 33 | 34 | const onLoadImg = (src: string) => { 35 | let img = new Image() 36 | img.src = src 37 | img.crossOrigin = "" 38 | img.onload = function () { 39 | setValue({}) 40 | setConfig((pre) => ({ ...pre, bgImg: img })) 41 | } 42 | } 43 | 44 | useEffect(() => { 45 | localStorage.setItem("imgUrl", url) 46 | onLoadImg(src) 47 | }, [src]) 48 | 49 | return ( 50 |
51 |
52 | setUrl(e.target.value)} /> 53 | {url && setUrl("")} />} 54 | 60 |
61 | 65 |
66 | ) 67 | } -------------------------------------------------------------------------------- /packages/react-app/src/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import CloseCircleOutlined from "@ant-design/icons/lib/icons/CloseCircleOutlined"; 2 | import { debounce } from "lodash"; 3 | import * as React from "react"; 4 | 5 | export const LoadingContext = React.createContext( 6 | {} as { 7 | loading: any; 8 | closeLoading: () => void; 9 | openLoading: () => void; 10 | closeDelayLoading: () => void; 11 | }, 12 | ); 13 | 14 | export const useLoading = () => React.useContext(LoadingContext); 15 | 16 | export const LoadingProvider = ({ children }: { children: React.ReactNode }) => { 17 | const [loading, setLoading] = React.useState(false); 18 | 19 | const openLoading = () => { 20 | setLoading(true) 21 | } 22 | 23 | const closeDelayLoading = debounce(() => { 24 | setLoading(false) 25 | }, 1000) 26 | 27 | const closeLoading = () => { 28 | setLoading(false) 29 | } 30 | 31 | return ( 32 | 33 | {children} 34 | 35 | ) 36 | } 37 | 38 | export const Loading = () => { 39 | const { loading, closeLoading } = useLoading() 40 | const [show, setShow] = React.useState(false) 41 | React.useEffect(() => { 42 | let timer: any; 43 | if (loading) { 44 | setTimeout(() => { 45 | setShow(true) 46 | clearTimeout(timer) 47 | }, 2000) 48 | } 49 | 50 | return () => { 51 | setShow(false) 52 | clearTimeout(timer) 53 | } 54 | }, [loading]) 55 | 56 | return ( 57 | loading &&
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | {show &&
} 66 | {show &&
上链以及获取速度较慢,请耐心等待哦。
} 67 |
68 |
69 | ); 70 | }; 71 | 72 | export const useSmallLoading = () => { 73 | const [smallLoading, setSmallLoading] = React.useState(false); 74 | 75 | const SmallLoading = (props: { size: number, color?: string }) => { 76 | const { size, color = "#165DFF" } = props; 77 | return ( 78 | smallLoading &&
83 | ) 84 | } 85 | 86 | return { 87 | SmallLoading, 88 | setSmallLoading, 89 | smallLoading 90 | } 91 | } -------------------------------------------------------------------------------- /packages/react-app/src/components/AvatarCard.tsx: -------------------------------------------------------------------------------- 1 | import { find, isEmpty, map } from "lodash"; 2 | import { 3 | PixelsMetaverseImgByPositionData, 4 | usePixelsMetaverseHandleImg 5 | } from "../pixels-metaverse"; 6 | import { useLocation } from "react-router-dom"; 7 | import { useUserInfo } from "./UserProvider"; 8 | import { categoryData } from "../pages/produced/components/Submit"; 9 | import { useWeb3Info } from "abi-to-request"; 10 | import { useMemo } from "react"; 11 | import { Composes, MaterialItem, MaterialLabel } from "./Card"; 12 | 13 | export const AvatarCard = ({ item, star }: { 14 | item: MaterialItem, star?: boolean 15 | }) => { 16 | const { address: addresss } = useWeb3Info() 17 | const { setSelectList } = usePixelsMetaverseHandleImg() 18 | const { search } = useLocation() 19 | const address = search ? search.split("=")[1] : addresss 20 | const { materialListObj, userInfo } = useUserInfo() 21 | 22 | const data = useMemo(() => { 23 | if (isEmpty(item) || isEmpty(materialListObj)) return [] 24 | if (isEmpty(item?.composes)) return [({ ...item, data: item?.baseInfo?.data } as any)] 25 | return map(item?.composeData, it => ({ ...it, data: it?.baseInfo?.data } as any)) 26 | }, [item, materialListObj]) 27 | 28 | return ( 29 |
33 | { 38 | /* setSelectList((pre: any) => { 39 | const list = cloneDeep(pre) as string[] 40 | const index = list.indexOf(item?.baseInfo?.data) 41 | if (index >= 0) { 42 | list.splice(index, 1) 43 | } else { 44 | list.push(item?.baseInfo?.data) 45 | } 46 | return list 47 | }) */ 48 | }} 49 | /> 50 |
51 |
52 |
{item?.baseInfo?.name || "这什么鬼"}
53 | {!star && } 54 |
55 | {item?.material?.id} 56 | {(find(categoryData, ite => ite?.value === item?.baseInfo?.category) || {})?.label} 57 |
58 | {/* {star && } */} 59 |
60 |
61 |
62 | ) 63 | } -------------------------------------------------------------------------------- /packages/react-app/src/pages/lockers/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { SearchQuery } from "./components/SearchQuery"; 3 | import { MaterialCard } from "./components/MaterialCard"; 4 | import { DataStateBox } from "../../components/DataStateBox"; 5 | import { isEmpty, last, map } from "lodash"; 6 | import { useUserInfo } from "../../components/UserProvider"; 7 | import { useQueryString } from "../../helpers/utilities"; 8 | import RightOutlined from "@ant-design/icons/lib/icons/RightOutlined"; 9 | import { Input } from "antd"; 10 | 11 | export const Lockers = () => { 12 | const { materialList } = useUserInfo() 13 | const { searchString, setSearchString } = useQueryString(); 14 | const [createID, setCreateID] = useState("") 15 | useEffect(() => { setCreateID(searchString?.createID) }, [searchString?.createID]) 16 | 17 | return ( 18 |
19 |
26 |
27 |
储物室
28 | 29 |
30 | 31 |
32 |
33 | {map(materialList, (item, i) => { 34 | if (item?.baseInfo?.userId === "0" && item?.material?.id === "0") return null 35 | return 36 | })} 37 |
38 |
39 |
40 | {!searchString?.id && !isEmpty(materialList) &&
41 |
42 | { 49 | setSearchString({ ...searchString, createID }); 50 | }} 51 | onChange={(val) => { 52 | setCreateID(val?.target?.value) 53 | }} 54 | > 55 | {materialList?.length >= 10 && { 56 | const data = { 57 | ...searchString, 58 | createID: last(materialList)?.material?.id ? String(Number(last(materialList)?.material?.id) - 1) : "" 59 | } 60 | setSearchString(data); 61 | //getMaterials?.refetch(data as TQuery) 62 | }} />} 63 |
64 |
} 65 |
66 |
67 | ) 68 | } -------------------------------------------------------------------------------- /packages/hardhat/scripts/publish.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const chalk = require("chalk"); 3 | 4 | const graphDir = "../subgraph"; 5 | const deploymentsDir = "./deployments"; 6 | const publishDir = "../react-app/src/contracts"; 7 | 8 | function publishContract(contractName, networkName) { 9 | try { 10 | let contract = fs 11 | .readFileSync(`${deploymentsDir}/${networkName}/${contractName}.json`) 12 | .toString(); 13 | contract = JSON.parse(contract); 14 | const graphConfigPath = `${graphDir}/config/config.json`; 15 | let graphConfig; 16 | try { 17 | if (fs.existsSync(graphConfigPath)) { 18 | graphConfig = fs.readFileSync(graphConfigPath).toString(); 19 | } else { 20 | graphConfig = "{}"; 21 | } 22 | } catch (e) { 23 | console.log(e); 24 | } 25 | 26 | graphConfig = JSON.parse(graphConfig); 27 | graphConfig[`${networkName}_${contractName}Address`] = contract.address; 28 | 29 | const folderPath = graphConfigPath.replace("/config.json", ""); 30 | if (!fs.existsSync(folderPath)) { 31 | fs.mkdirSync(folderPath); 32 | } 33 | fs.writeFileSync(graphConfigPath, JSON.stringify(graphConfig, null, 2)); 34 | if (!fs.existsSync(`${graphDir}/abis`)) fs.mkdirSync(`${graphDir}/abis`); 35 | fs.writeFileSync( 36 | `${graphDir}/abis/${networkName}_${contractName}.json`, 37 | JSON.stringify(contract.abi, null, 2) 38 | ); 39 | 40 | //Hardhat Deploy writes a file with all ABIs in react-app/src/contracts/contracts.json 41 | //If you need the bytecodes and/or you want one file per ABIs, un-comment the following block. 42 | //Write the contracts ABI, address and bytecodes in case the front-end needs them 43 | // fs.writeFileSync( 44 | // `${publishDir}/${contractName}.address.js`, 45 | // `module.exports = "${contract.address}";` 46 | // ); 47 | // fs.writeFileSync( 48 | // `${publishDir}/${contractName}.abi.js`, 49 | // `module.exports = ${JSON.stringify(contract.abi, null, 2)};` 50 | // ); 51 | // fs.writeFileSync( 52 | // `${publishDir}/${contractName}.bytecode.js`, 53 | // `module.exports = "${contract.bytecode}";` 54 | // ); 55 | 56 | return true; 57 | } catch (e) { 58 | console.log( 59 | "Failed to publish " + chalk.red(contractName) + " to the subgraph." 60 | ); 61 | console.log(e); 62 | return false; 63 | } 64 | } 65 | 66 | async function main() { 67 | const directories = fs.readdirSync(deploymentsDir); 68 | directories.forEach(function (directory) { 69 | const files = fs.readdirSync(`${deploymentsDir}/${directory}`); 70 | files.forEach(function (file) { 71 | if (file.indexOf(".json") >= 0) { 72 | const contractName = file.replace(".json", ""); 73 | publishContract(contractName, directory); 74 | } 75 | }); 76 | }); 77 | console.log("✅ Published contracts to the subgraph package."); 78 | } 79 | main() 80 | .then(() => process.exit(0)) 81 | .catch((error) => { 82 | console.error(error); 83 | process.exit(1); 84 | }); 85 | -------------------------------------------------------------------------------- /packages/react-app/scripts/ipfs.js: -------------------------------------------------------------------------------- 1 | const ipfsAPI = require("ipfs-http-client"); 2 | const chalk = require("chalk"); 3 | const { clearLine } = require("readline"); 4 | 5 | const { globSource } = ipfsAPI; 6 | // https://ipfs.io/ipfs/QmTD4bMHBJ1pb8FHS3hmUY5yzVindYii8evqHTips5M6H7 7 | 8 | //const infura = { host: "ipfs.infura.io", port: "5001", protocol: "https" }; 9 | // run your own ipfs daemon: https://docs.ipfs.io/how-to/command-line-quick-start/#install-ipfs 10 | const infura = { host: "localhost", port: "5001", protocol: "http" }; 11 | 12 | const ipfs = ipfsAPI(infura); 13 | 14 | const ipfsGateway = "https://ipfs.io/ipfs/"; 15 | 16 | const addOptions = { 17 | pin: true, 18 | }; 19 | 20 | const pushDirectoryToIPFS = async path => { 21 | try { 22 | const response = await ipfs.add(globSource(path, { recursive: true }), addOptions); 23 | return response; 24 | } catch (e) { 25 | return {}; 26 | } 27 | }; 28 | 29 | const publishHashToIPNS = async ipfsHash => { 30 | try { 31 | const response = await ipfs.name.publish(`/ipfs/${ipfsHash}`); 32 | return response; 33 | } catch (e) { 34 | return {}; 35 | } 36 | }; 37 | 38 | const nodeMayAllowPublish = ipfsClient => { 39 | // You must have your own IPFS node in order to publish an IPNS name 40 | // This contains a blacklist of known nodes which do not allow users to publish IPNS names. 41 | const nonPublishingNodes = ["ipfs.infura.io"]; 42 | const { host } = ipfsClient.getEndpointConfig(); 43 | return !nonPublishingNodes.some(nodeUrl => host.includes(nodeUrl)); 44 | }; 45 | 46 | const deploy = async () => { 47 | console.log("🛰 Sending to IPFS..."); 48 | const { cid } = await pushDirectoryToIPFS("./build"); 49 | if (!cid) { 50 | console.log(`📡 App deployment failed`); 51 | return false; 52 | } 53 | console.log(`📡 App deployed to IPFS with hash: ${chalk.cyan(cid.toString())}`); 54 | 55 | console.log(); 56 | 57 | let ipnsName = ""; 58 | if (nodeMayAllowPublish(ipfs)) { 59 | console.log(`✍️ Publishing /ipfs/${cid.toString()} to IPNS...`); 60 | process.stdout.write(" Publishing to IPNS can take up to roughly two minutes.\r"); 61 | ipnsName = (await publishHashToIPNS(cid.toString())).name; 62 | clearLine(process.stdout, 0); 63 | if (!ipnsName) { 64 | console.log(" Publishing IPNS name on node failed."); 65 | } 66 | console.log(`🔖 App published to IPNS with name: ${chalk.cyan(ipnsName)}`); 67 | console.log(); 68 | } 69 | 70 | console.log("🚀 Deployment to IPFS complete!"); 71 | console.log(); 72 | 73 | console.log(`Use the link${ipnsName && "s"} below to access your app:`); 74 | console.log(` IPFS: ${chalk.cyan(`${ipfsGateway}${cid.toString()}`)}`); 75 | if (ipnsName) { 76 | console.log(` IPNS: ${chalk.cyan(`${ipfsGateway}${ipnsName}`)}`); 77 | console.log(); 78 | console.log( 79 | "Each new deployment will have a unique IPFS hash while the IPNS name will always point at the most recent deployment.", 80 | ); 81 | console.log( 82 | "It is recommended that you share the IPNS link so that people always see the newest version of your app.", 83 | ); 84 | } 85 | console.log(); 86 | return true; 87 | }; 88 | 89 | deploy(); 90 | -------------------------------------------------------------------------------- /packages/react-app/src/helpers/types.ts: -------------------------------------------------------------------------------- 1 | import { IAssetData } from "abi-to-request/lib/chain"; 2 | 3 | export interface ITxData { 4 | from: string; 5 | to: string; 6 | nonce: string; 7 | gasPrice: string; 8 | gasLimit: string; 9 | value: string; 10 | data: string; 11 | } 12 | 13 | export interface IBlockScoutTx { 14 | value: string; 15 | txreceipt_status: string; 16 | transactionIndex: string; 17 | to: string; 18 | timeStamp: string; 19 | nonce: string; 20 | isError: string; 21 | input: string; 22 | hash: string; 23 | gasUsed: string; 24 | gasPrice: string; 25 | gas: string; 26 | from: string; 27 | cumulativeGasUsed: string; 28 | contractAddress: string; 29 | confirmations: string; 30 | blockNumber: string; 31 | blockHash: string; 32 | } 33 | 34 | export interface IBlockScoutTokenTx { 35 | value: string; 36 | transactionIndex: string; 37 | tokenSymbol: string; 38 | tokenName: string; 39 | tokenDecimal: string; 40 | to: string; 41 | timeStamp: string; 42 | nonce: string; 43 | input: string; 44 | hash: string; 45 | gasUsed: string; 46 | gasPrice: string; 47 | gas: string; 48 | from: string; 49 | cumulativeGasUsed: string; 50 | contractAddress: string; 51 | confirmations: string; 52 | blockNumber: string; 53 | blockHash: string; 54 | } 55 | 56 | export interface IParsedTx { 57 | timestamp: string; 58 | hash: string; 59 | from: string; 60 | to: string; 61 | nonce: string; 62 | gasPrice: string; 63 | gasUsed: string; 64 | fee: string; 65 | value: string; 66 | input: string; 67 | error: boolean; 68 | asset: IAssetData; 69 | operations: ITxOperation[]; 70 | } 71 | 72 | export interface ITxOperation { 73 | asset: IAssetData; 74 | value: string; 75 | from: string; 76 | to: string; 77 | functionName: string; 78 | } 79 | 80 | export interface IGasPricesResponse { 81 | fastWait: number; 82 | avgWait: number; 83 | blockNum: number; 84 | fast: number; 85 | fastest: number; 86 | fastestWait: number; 87 | safeLow: number; 88 | safeLowWait: number; 89 | speed: number; 90 | block_time: number; 91 | average: number; 92 | } 93 | 94 | export interface IGasPrice { 95 | time: number; 96 | price: number; 97 | } 98 | 99 | export interface IGasPrices { 100 | timestamp: number; 101 | slow: IGasPrice; 102 | average: IGasPrice; 103 | fast: IGasPrice; 104 | } 105 | 106 | export interface IMethodArgument { 107 | type: string; 108 | } 109 | 110 | export interface IMethod { 111 | signature: string; 112 | name: string; 113 | args: IMethodArgument[]; 114 | } 115 | 116 | export interface IBoxImage { 117 | "@type": string; 118 | contentUrl: { 119 | [label: string]: string; 120 | }; 121 | } 122 | 123 | export interface IBoxProfile { 124 | memberSince: string; 125 | coverPhoto: IBoxImage[]; 126 | location: string; 127 | emoji: string; 128 | job: string; 129 | employer: string; 130 | website: string; 131 | description: string; 132 | ethereum_proof: { 133 | consent_msg: string; 134 | consent_signature: string; 135 | linked_did: string; 136 | }; 137 | proof_did: string; 138 | github: string; 139 | image: IBoxImage[]; 140 | name: string; 141 | } 142 | -------------------------------------------------------------------------------- /packages/services/graph-node/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | save_coredumps() { 4 | graph_dir=/var/lib/graph 5 | datestamp=$(date +"%Y-%m-%dT%H:%M:%S") 6 | ls /core.* >& /dev/null && have_cores=yes || have_cores=no 7 | if [ -d "$graph_dir" -a "$have_cores" = yes ] 8 | then 9 | core_dir=$graph_dir/cores 10 | mkdir -p $core_dir 11 | exec >> "$core_dir"/messages 2>&1 12 | echo "${HOSTNAME##*-} Saving core dump on ${HOSTNAME} at ${datestamp}" 13 | 14 | dst="$core_dir/$datestamp-${HOSTNAME}" 15 | mkdir "$dst" 16 | cp /usr/local/bin/graph-node "$dst" 17 | cp /proc/loadavg "$dst" 18 | [ -f /Dockerfile ] && cp /Dockerfile "$dst" 19 | tar czf "$dst/etc.tgz" /etc/ 20 | dmesg -e > "$dst/dmesg" 21 | # Capture environment variables, but filter out passwords 22 | env | sort | sed -r -e 's/^(postgres_pass|ELASTICSEARCH_PASSWORD)=.*$/\1=REDACTED/' > "$dst/env" 23 | 24 | for f in /core.* 25 | do 26 | echo "${HOSTNAME##*-} Found core dump $f" 27 | mv "$f" "$dst" 28 | done 29 | echo "${HOSTNAME##*-} Saving done" 30 | fi 31 | } 32 | 33 | wait_for_ipfs() { 34 | # Take the IPFS URL in $1 apart and extract host and port. If no explicit 35 | # host is given, use 443 for https, and 80 otherwise 36 | if [[ "$1" =~ ^((https?)://)?([^:/]+)(:([0-9]+))? ]] 37 | then 38 | proto=${BASH_REMATCH[2]:-http} 39 | host=${BASH_REMATCH[3]} 40 | port=${BASH_REMATCH[5]} 41 | if [ -z "$port" ] 42 | then 43 | [ "$proto" = "https" ] && port=443 || port=80 44 | fi 45 | wait_for "$host:$port" -t 120 46 | else 47 | echo "invalid IPFS URL: $1" 48 | exit 1 49 | fi 50 | } 51 | 52 | start_query_node() { 53 | export DISABLE_BLOCK_INGESTOR=true 54 | graph-node \ 55 | --postgres-url "$postgres_url" \ 56 | --ethereum-rpc $ethereum \ 57 | --ipfs "$ipfs" 58 | } 59 | 60 | start_index_node() { 61 | # Only the index node with the name set in BLOCK_INGESTOR should ingest 62 | # blocks 63 | if [[ ${node_id} != "${BLOCK_INGESTOR}" ]]; then 64 | export DISABLE_BLOCK_INGESTOR=true 65 | fi 66 | 67 | graph-node \ 68 | --node-id "${node_id//-/_}" \ 69 | --postgres-url "$postgres_url" \ 70 | --ethereum-rpc $ethereum \ 71 | --ipfs "$ipfs" 72 | } 73 | 74 | start_combined_node() { 75 | graph-node \ 76 | --postgres-url "$postgres_url" \ 77 | --ethereum-rpc $ethereum \ 78 | --ipfs "$ipfs" 79 | } 80 | 81 | postgres_url="postgresql://$postgres_user:$postgres_pass@$postgres_host/$postgres_db" 82 | 83 | wait_for_ipfs "$ipfs" 84 | wait_for "$postgres_host:5432" -t 120 85 | sleep 5 86 | 87 | trap save_coredumps EXIT 88 | 89 | export PGAPPNAME="${node_id-$HOSTNAME}" 90 | 91 | case "${node_role-combined-node}" in 92 | query-node) 93 | start_query_node 94 | ;; 95 | index-node) 96 | start_index_node 97 | ;; 98 | combined-node) 99 | start_combined_node 100 | ;; 101 | *) 102 | echo "Unknown mode for start-node: $1" 103 | echo "usage: start (combined-node|query-node|index-node)" 104 | exit 1 105 | esac 106 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/home/index.tsx: -------------------------------------------------------------------------------- 1 | import { useHistory } from "react-router-dom"; 2 | import { RefObject, useCallback, useEffect, useRef } from "react"; 3 | import { add } from "../../eos-api/fetch"; 4 | 5 | export const usePrint = () => { 6 | const print = useCallback( 7 | (ref: RefObject, text: string) => { 8 | if (!ref.current) return 9 | const dom = ref.current; 10 | let i = 0, timer: any = 0 11 | function typing() { 12 | if (i <= text.length) { 13 | dom.innerHTML = text.slice(0, i++) + '_' 14 | timer = setTimeout(typing, 200) 15 | } 16 | else { 17 | dom.innerHTML = text//结束打字,移除 _ 光标 18 | clearTimeout(timer) 19 | } 20 | } 21 | typing() 22 | }, []) 23 | 24 | return { print } 25 | } 26 | 27 | export const Website = () => { 28 | const history = useHistory() 29 | const ref = useRef(null) 30 | const { print } = usePrint() 31 | 32 | useEffect(() => { 33 | if (localStorage.getItem("transaction_id")) return 34 | add("项目被克隆和启动了").then((res) => { 35 | const transaction_id = (res as { transaction_id: string })?.transaction_id 36 | localStorage.setItem("transaction_id", transaction_id) 37 | }) 38 | }, []) 39 | 40 | useEffect(() => { 41 | print(ref, "这里是像素元宇宙,一个已经创世但是还没有被绘制的世界!等待着我们去建造,你准备好了吗!我的少年!让我们一起在像素的世界里遨游吧!像素元宇宙,我们来啦!") 42 | }, [ref]) 43 | 44 | return ( 45 | <> 46 |
47 |
48 |

49 |

欢迎来到,我的像素元宇宙!

50 |

Hello! Pixels Metaverse!

51 |

52 |

55 |

56 |
57 |
58 |
history.push("produced")}> 60 | Get started 61 |
62 |
63 | {/*
64 |
66 | 白皮书 67 |
68 |
*/} 69 |
70 |
71 |
72 | {/*
73 | 74 |
*/} 75 | 76 | ); 77 | }; -------------------------------------------------------------------------------- /packages/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pmerc721/react-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": ".", 6 | "dependencies": { 7 | "@ant-design/icons": "^4.6.4", 8 | "@craco/craco": "^6.2.0", 9 | "@openzeppelin/contracts": "^4.3.1", 10 | "@tailwindcss/postcss7-compat": "^2.2.8", 11 | "@testing-library/jest-dom": "^5.11.4", 12 | "@testing-library/react": "^11.1.0", 13 | "@testing-library/user-event": "^12.1.10", 14 | "@types/jest": "^27.0.1", 15 | "@types/lodash": "^4.14.172", 16 | "@types/node": "^16.7.2", 17 | "@types/react": "^17.0.19", 18 | "@types/react-dom": "^17.0.9", 19 | "@types/react-router-dom": "^5.1.8", 20 | "@walletconnect/web3-provider": "^1.6.5", 21 | "@apollo/client": "^3.5.10", 22 | "antd": "^4.16.13", 23 | "authereum": "^0.1.14", 24 | "bignumber.js": "^9.0.1", 25 | "bitski": "^0.11.0-beta.4", 26 | "blockies-ts": "^1.0.0", 27 | "ethereumjs-util": "^7.0.7", 28 | "i18next": "^21.3.3", 29 | "lodash": "^4.17.21", 30 | "react": "^17.0.2", 31 | "react-dom": "^17.0.2", 32 | "react-i18next": "^11.12.0", 33 | "react-pixels-metaverse": "^0.0.9", 34 | "react-router-dom": "^5.2.1", 35 | "react-scripts": "4.0.3", 36 | "tailwindcss": "npm:@tailwindcss/postcss7-compat", 37 | "typescript": "^4.3.5", 38 | "web-vitals": "^1.0.1", 39 | "web3": "^1.5.2", 40 | "web3-utils": "^1.7.0", 41 | "web3modal": "^1.9.4", 42 | "ethers": "^5.5.3", 43 | "web3-core": "^1.7.0" 44 | }, 45 | "scripts": { 46 | "start": "craco start", 47 | "build": "craco build && cd ./build && http-server", 48 | "test": "craco test", 49 | "deploy": "bash deploy.sh", 50 | "push:npm": "cd src/react-pixels-metaverse && git add . && git commit -m '人生何曾驻旧足,前端处处是新旅' && git push", 51 | "html": "npm run converted && cd out && http-server", 52 | "push:dev": "git push origin dev", 53 | "copy": "shx cp ./electron/main.js build && shx cp ./electron/package.json build", 54 | "desktop": "npm run build && npm run copy && cd build && npm run pack", 55 | "ipfs": "node ./scripts/ipfs.js", 56 | "surge": "surge ./build", 57 | "s3": "node ./scripts/s3.js", 58 | "ship": "npm run surge", 59 | "watch": "node ./scripts/watch.js", 60 | "abi:api": "converted -d --entry './src/contracts/' --chainId 42" 61 | }, 62 | "eslintConfig": { 63 | "extends": [ 64 | "react-app", 65 | "react-app/jest" 66 | ] 67 | }, 68 | "browserslist": { 69 | "production": [ 70 | ">0.2%", 71 | "not dead", 72 | "not op_mini all" 73 | ], 74 | "development": [ 75 | "last 1 chrome version", 76 | "last 1 firefox version", 77 | "last 1 safari version" 78 | ] 79 | }, 80 | "devDependencies": { 81 | "@types/ms.macro": "^2.0.0", 82 | "autoprefixer": "^9", 83 | "babel-plugin-import": "^1.13.3", 84 | "contract-json-converted-html": "^0.0.13", 85 | "craco-less": "^1.20.0", 86 | "craco-plugin-react-hot-reload": "^0.1.0", 87 | "eosjs": "^22.1.0", 88 | "hardhat": "^2.8.2", 89 | "i18next-browser-languagedetector": "^6.1.2", 90 | "ipfs-http-client": "^45.0.0", 91 | "postcss": "^7", 92 | "postcss-cli": "^8.3.1", 93 | "prettier": "^2.0.5", 94 | "react-hot-loader": "^4.13.0", 95 | "s3-folder-upload": "^2.3.1", 96 | "shx": "^0.3.4", 97 | "surge": "^0.21.5", 98 | "webpack-dev-server": "3.11.1", 99 | "react-error-overlay": "6.0.9" 100 | }, 101 | "resolutions": { 102 | "react-error-overlay": "6.0.9" 103 | } 104 | } -------------------------------------------------------------------------------- /packages/react-app/src/pages/play/index.tsx: -------------------------------------------------------------------------------- 1 | import { PersonCenter } from "./components/PersonCenter"; 2 | import { Avatar } from "./components/Avatar"; 3 | import { Collections } from "./components/Collections"; 4 | import { Dictionary, isEmpty, map } from "lodash"; 5 | import React, { useMemo } from "react"; 6 | import { useUserInfo } from "../../components/UserProvider"; 7 | import { useLocation, useParams } from "react-router-dom"; 8 | import { 9 | PixelsMetaverseHandleImgProvider, 10 | useConvertedPostion 11 | } from "../../pixels-metaverse"; 12 | import { useWeb3Info } from "abi-to-request"; 13 | import { MaterialItem } from "../../components/Card"; 14 | 15 | export const useGetPersonData = () => { 16 | const { address: addresss } = useWeb3Info() 17 | const { search } = useLocation() 18 | const address = search ? search.split("=")[1] : addresss 19 | const { materialList, userInfo } = useUserInfo(); 20 | return useMemo(() => { 21 | const noCollectionList: MaterialItem[] = [], colectionList: MaterialItem[] = [], onwerList: MaterialItem[] = []; 22 | let avater: MaterialItem | undefined; 23 | map(materialList, (item: MaterialItem) => { 24 | const isCurAddress = address === item?.material?.owner 25 | if (item?.material?.id === userInfo?.id) { 26 | avater = item; 27 | } else if (isCurAddress) { 28 | onwerList.push(item) 29 | } else if (item?.baseInfo?.userId !== "0" && item?.material?.id !== "0") { 30 | noCollectionList.push(item); 31 | } 32 | }) 33 | return { noCollectionList, avater, colectionList, onwerList } 34 | }, [materialList, userInfo]) 35 | } 36 | 37 | export const PixelsMetaverse = () => { 38 | const { address: addresss } = useWeb3Info(); 39 | const { search } = useLocation(); 40 | const address = search ? search.split("=")[1] : addresss; 41 | const { userInfo } = useUserInfo(); 42 | const convertedPostion = useConvertedPostion(); 43 | const a = useParams(); 44 | const { noCollectionList, avater, colectionList, onwerList } = useGetPersonData(); 45 | 46 | const avaterData = React.useMemo(() => { 47 | avater?.composeData?.push(avater) 48 | return avater 49 | }, [avater?.composeData]) 50 | 51 | const positions = useMemo(() => { 52 | if (isEmpty(avaterData?.composeData)) return "empty" 53 | let data: Dictionary = {} 54 | map(avaterData?.composeData, item => { 55 | if (item?.baseInfo?.data) { 56 | const positionsData = convertedPostion({ 57 | positions: item?.baseInfo?.data 58 | }) 59 | data = { ...data, ...positionsData } 60 | } 61 | }) 62 | 63 | const colors: Dictionary = {} 64 | for (let i in data) { 65 | colors[data[i]] ? colors[data[i]].push(Number(i)) : colors[data[i]] = [Number(i)] 66 | } 67 | 68 | let str = "" 69 | let min = 1 70 | for (let i in colors) { 71 | const position = map(colors[i], ite => (ite - min).toString(36)).join("|") 72 | str += `${parseInt(i.slice(1), 16).toString(36)}-${position}-` 73 | } 74 | return `${str}${min}` 75 | }, [avaterData?.composeData]) 76 | 77 | return ( 78 | 88 |
89 | 90 | 91 | 92 |
93 |
94 | ) 95 | } -------------------------------------------------------------------------------- /packages/subgraph/src/mapping.ts: -------------------------------------------------------------------------------- 1 | import { BigInt } from "@graphprotocol/graph-ts"; 2 | import { 3 | AvaterEvent, 4 | ComposeEvent, 5 | ConfigEvent, 6 | MaterialEvent 7 | } from "../generated/PixelsMetaverse/PixelsMetaverse" 8 | import { Avater, Material, TConfig } from "../generated/schema" 9 | 10 | export function handleAvaterEvent(event: AvaterEvent): void { 11 | let avater = Avater.load(event.params.owner.toHex()); 12 | if (avater == null) { 13 | avater = new Avater(event.params.owner.toHex()); 14 | } 15 | avater.avater = event.params.avater.toString(); 16 | 17 | avater.save(); 18 | } 19 | 20 | export function handleMaterialEvent(event: MaterialEvent): void { 21 | //let zeroAddr = Address.fromHexString("0x0000000000000000000000000000000000000000"); 22 | let material = Material.load(event.params.id.toString()); 23 | if (material == null) { 24 | material = new Material(event.params.id.toString()); 25 | material.owner = event.params.owner; 26 | material.rawData = event.params.rawData; 27 | material.remake = event.params.remake; 28 | material.config = event.params.configID.toString(); 29 | material.composed = new BigInt(0); 30 | material.createID = event.params.id; 31 | } else { 32 | material.owner = event.params.owner; 33 | //material.burned = event.params.owner == zeroAddr; 34 | 35 | // fuck: event.params.rawData !== "" is true, but graph query event.params.rawDat is "" ????? 36 | // fuck: === not supper....... 37 | if (event.params.rawData.length > 0) material.rawData = event.params.rawData; 38 | if (event.params.remake) material.remake = false 39 | if (!event.params.configID.isZero()) material.config = event.params.configID.toString(); 40 | } 41 | 42 | material.save(); 43 | } 44 | 45 | export function handleComposeEvent(event: ComposeEvent): void { 46 | let after = Material.load(event.params.toID.toString()); 47 | if (after == null) { 48 | after = new Material(event.params.toID.toString()); 49 | after.composes = event.params.id; 50 | } else { 51 | after.composes = after.composes.concat(event.params.id); 52 | } 53 | 54 | for (let i = 0; i < event.params.id.length; i++) { 55 | let id = event.params.id[i]; 56 | let compose = Material.load(id.toString()); 57 | if (compose == null) { 58 | compose = new Material(id.toString()); 59 | } 60 | compose.composed = event.params.toID; 61 | after.composes = after.composes.concat(compose.composes); 62 | compose.save() 63 | } 64 | 65 | let before = Material.load(event.params.fromID.toString()); 66 | if (before == null) { 67 | before = new Material(event.params.fromID.toString()); 68 | } else { 69 | let composes = before.composes; 70 | for (let i = 0; i < event.params.id.length; i++) { 71 | let id = event.params.id[i]; 72 | let index = composes.indexOf(id); 73 | composes.splice(index, 1); 74 | } 75 | before.composes = composes; 76 | } 77 | 78 | before.save() 79 | after.save(); 80 | } 81 | 82 | export function handleConfigEvent(event: ConfigEvent): void { 83 | let config = TConfig.load(event.params.id.toString()); 84 | if (config == null) { 85 | config = new TConfig(event.params.id.toString()); 86 | config.name = event.params.name; 87 | config.position = event.params.position; 88 | config.time = event.params.time; 89 | config.zIndex = event.params.zIndex; 90 | config.decode = event.params.decode; 91 | config.sort = event.params.sort; 92 | } else { 93 | if (event.params.name.length > 0) config.name = event.params.name; 94 | if (event.params.position.length > 0) config.position = event.params.position; 95 | if (event.params.time.length > 0) config.time = event.params.time; 96 | if (event.params.zIndex.length > 0) config.zIndex = event.params.zIndex; 97 | if (event.params.decode.length > 0) config.decode = event.params.decode; 98 | //if (!event.params.sort.isZero()) config.sort = event.params.sort; 99 | } 100 | 101 | config.save() 102 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pmerc721-nft", 3 | "version": "1.1.1", 4 | "keywords": [ 5 | "ethereum", 6 | "react", 7 | "workspaces", 8 | "yarn", 9 | "nft" 10 | ], 11 | "private": true, 12 | "scripts": { 13 | "start": "yarn workspace @pmerc721/react-app start", 14 | "build": "yarn workspace @pmerc721/react-app build", 15 | "deploy:html": "yarn workspace @pmerc721/react-app deploy", 16 | "html": "yarn workspace @pmerc721/react-app html", 17 | "desktop": "yarn workspace @pmerc721/react-app desktop", 18 | "prettier": "yarn workspace @pmerc721/react-app prettier", 19 | "ipfs": "yarn workspace @pmerc721/react-app ipfs", 20 | "surge": "yarn workspace @pmerc721/react-app surge", 21 | "s3": "yarn workspace @pmerc721/react-app s3", 22 | "ship": "yarn workspace @pmerc721/react-app ship", 23 | "theme": "yarn workspace @pmerc721/react-app theme", 24 | "watch-theme": "yarn workspace @pmerc721/react-app watch", 25 | "abi:api": "yarn workspace @pmerc721/react-app abi:api", 26 | "test": "yarn workspace @pmerc721/hardhat test", 27 | "chain": "yarn workspace @pmerc721/hardhat chain", 28 | "fork": "yarn workspace @pmerc721/hardhat fork", 29 | "node": "yarn workspace @pmerc721/hardhat chain", 30 | "compile": "yarn workspace @pmerc721/hardhat compile", 31 | "deploy": "yarn workspace @pmerc721/hardhat deploy", 32 | "ganache": "yarn workspace @pmerc721/hardhat ganache", 33 | "verify": "yarn workspace @pmerc721/hardhat verify", 34 | "watch": "yarn workspace @pmerc721/hardhat watch", 35 | "accounts": "yarn workspace @pmerc721/hardhat accounts", 36 | "balance": "yarn workspace @pmerc721/hardhat balance", 37 | "send": "yarn workspace @pmerc721/hardhat send", 38 | "generate": "yarn workspace @pmerc721/hardhat generate", 39 | "account": "yarn workspace @pmerc721/hardhat account", 40 | "mineContractAddress": "cd packages/hardhat && npx hardhat mineContractAddress", 41 | "wallet": "cd packages/hardhat && npx hardhat wallet", 42 | "fundedwallet": "cd packages/hardhat && npx hardhat fundedwallet", 43 | "flatten": "cd packages/hardhat && npx hardhat flatten", 44 | "clean": "cd packages/hardhat && npx hardhat clean", 45 | "run-graph-node": "yarn workspace @pmerc721/services run-graph-node", 46 | "remove-graph-node": "yarn workspace @pmerc721/services remove-graph-node", 47 | "clean-graph-node": "yarn workspace @pmerc721/services clean-graph-node", 48 | "graph-prepare": "mustache packages/subgraph/config/config.json packages/subgraph/src/subgraph.template.yaml > packages/subgraph/subgraph.yaml", 49 | "graph-codegen": "yarn workspace @pmerc721/subgraph graph codegen", 50 | "graph-build": "yarn workspace @pmerc721/subgraph graph build", 51 | "graph-create-local": "yarn workspace @pmerc721/subgraph graph create --node http://localhost:8020/ scaffold-eth/your-contract", 52 | "graph-remove-local": "yarn workspace @pmerc721/subgraph graph remove --node http://localhost:8020/ scaffold-eth/your-contract", 53 | "graph-deploy-local": "yarn workspace @pmerc721/subgraph graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 scaffold-eth/your-contract", 54 | "graph-ship-local": "yarn graph-prepare && yarn graph-codegen && yarn graph-deploy-local", 55 | "deploy-and-graph": "yarn deploy && yarn graph-ship-local", 56 | "postinstall": "husky install" 57 | }, 58 | "workspaces": { 59 | "packages": [ 60 | "packages/*" 61 | ], 62 | "nohoist": [ 63 | "**/@graphprotocol/graph-ts", 64 | "**/@graphprotocol/graph-ts/**", 65 | "**/hardhat", 66 | "**/hardhat/**", 67 | "**/hardhat-ts", 68 | "**/hardhat-ts/**" 69 | ] 70 | }, 71 | "dependencies": {}, 72 | "devDependencies": { 73 | "@babel/helper-environment-visitor": "^7.16.7", 74 | "autoprefixer": "^9", 75 | "babel-plugin-import": "^1.13.3", 76 | "contract-json-converted-html": "^0.0.13", 77 | "craco-less": "^1.20.0", 78 | "craco-plugin-react-hot-reload": "^0.1.0", 79 | "eosjs": "^22.1.0", 80 | "i18next-browser-languagedetector": "^6.1.2", 81 | "postcss": "^7", 82 | "postcss-cli": "^8.3.1", 83 | "react-hot-loader": "^4.13.0", 84 | "husky": "^7.0.2" 85 | } 86 | } -------------------------------------------------------------------------------- /packages/react-app/src/gql/index.ts: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client" 2 | 3 | export const AVATER_LIST = gql` 4 | query($address:Bytes){ 5 | avaters(where: {id: $address} ) { 6 | id 7 | avater{ 8 | id 9 | owner 10 | rawData 11 | remake 12 | composed 13 | composes 14 | config{ 15 | id 16 | name 17 | time 18 | position 19 | zIndex 20 | decode 21 | sort 22 | } 23 | } 24 | } 25 | } 26 | `; 27 | 28 | export const COMPOSE_LIST = gql` 29 | query($first: Int, $ids: [BigInt]) { 30 | materials( 31 | first: $first, 32 | where: { 33 | id_in: $ids 34 | }) { 35 | id 36 | owner 37 | rawData 38 | remake 39 | composed 40 | composes 41 | config{ 42 | id 43 | name 44 | time 45 | position 46 | zIndex 47 | decode 48 | sort 49 | } 50 | } 51 | }` 52 | 53 | export const MATERIAL_LEN_LIST = gql` 54 | query GetMaterialsLen { 55 | materials( 56 | first: 1, 57 | orderBy: "createID", 58 | orderDirection: "desc", 59 | where: { 60 | createID_not: "0" 61 | }) { 62 | id 63 | } 64 | }` 65 | 66 | export const MATERIAL_ALL_LIST = gql` 67 | query($first: Int, $orderBy: String, $orderDirection: String, $createID: String, $owner: Bytes) { 68 | materials( 69 | first: $first, 70 | orderBy: "createID", 71 | orderDirection: $orderDirection, 72 | where: { 73 | composed: 0, 74 | owner_not: "0x0000000000000000000000000000000000000000", 75 | createID_lt: $createID, 76 | }) { 77 | id 78 | owner 79 | rawData 80 | remake 81 | composed 82 | composes 83 | config{ 84 | id 85 | name 86 | time 87 | position 88 | zIndex 89 | decode 90 | sort 91 | } 92 | } 93 | }` 94 | 95 | export const MATERIAL_ID_LIST = gql` 96 | query($first: Int, $orderBy: String, $orderDirection: String, $createID: String, $id: [BigInt]) { 97 | materials( 98 | orderBy: "createID", 99 | orderDirection: $orderDirection, 100 | where: { 101 | createID_lt: $createID, 102 | id_in: $id 103 | }) { 104 | id 105 | owner 106 | rawData 107 | remake 108 | composed 109 | composes 110 | config{ 111 | id 112 | name 113 | time 114 | position 115 | zIndex 116 | decode 117 | sort 118 | } 119 | } 120 | }` 121 | 122 | export const MATERIAL_ADDRESS_LIST = gql` 123 | query($first: Int, $orderBy: String, $orderDirection: String, $createID: String, $owner: Bytes) { 124 | materials( 125 | first: $first, 126 | orderBy: "createID", 127 | orderDirection: $orderDirection, 128 | where: { 129 | createID_lt: $createID, 130 | owner: $owner 131 | }) { 132 | id 133 | owner 134 | rawData 135 | remake 136 | composed 137 | composes 138 | config{ 139 | id 140 | name 141 | time 142 | position 143 | zIndex 144 | decode 145 | sort 146 | } 147 | } 148 | }` 149 | 150 | export const MATERIAL_ADDRESS_ID_LIST = gql` 151 | query($first: Int, $orderBy: String, $orderDirection: String, $createID: String, $owner: [Bytes], $id: BigInt) { 152 | materials( 153 | first: $first, 154 | orderBy: "createID", 155 | orderDirection: $orderDirection, 156 | where: { 157 | createID_gt: $createID, 158 | id_in: $id, 159 | owner: $owner 160 | }) { 161 | id 162 | owner 163 | rawData 164 | remake 165 | composed 166 | composes 167 | config{ 168 | id 169 | name 170 | time 171 | position 172 | zIndex 173 | decode 174 | sort 175 | } 176 | } 177 | }` -------------------------------------------------------------------------------- /packages/react-app/src/pages/lockers/components/MaterialCard.tsx: -------------------------------------------------------------------------------- 1 | import { find, isEmpty, map } from "lodash"; 2 | import { useUserInfo } from "../../../components/UserProvider"; 3 | import { categoryData } from "../../produced/components/Submit"; 4 | import { useHistory } from "react-router"; 5 | import { PixelsMetaverseImgByPositionData } from "../../../pixels-metaverse"; 6 | import { useMemo, useState } from "react"; 7 | import { ellipseAddress } from "../../../helpers/utilities"; 8 | import { Composes, Details, MaterialItem, MaterialLabel } from "../../../components/Card"; 9 | import { message, Modal, Typography } from "antd"; 10 | import { useWeb3Info } from "abi-to-request"; 11 | const { Paragraph } = Typography; 12 | 13 | export const MaterialCard = ({ item }: { item: MaterialItem }) => { 14 | const { userInfo, materialListObj } = useUserInfo() 15 | const [isModalVisible, setIsModalVisible] = useState(false); 16 | const [isBigImg, setIsBigImg] = useState(false); 17 | const { address } = useWeb3Info() 18 | const data = useMemo(() => { 19 | if (isEmpty(item) || isEmpty(materialListObj)) return [] 20 | if (isEmpty(item?.composes)) return [({ ...item, data: item?.baseInfo?.data } as any)] 21 | return map(item?.composeData, it => ({ ...it, data: it?.baseInfo?.data } as any)) 22 | }, [item, materialListObj]) 23 | 24 | const copyToClipboard = (content: string) => { 25 | const el = document.createElement("textarea"); 26 | el.value = content; 27 | el.setAttribute("readonly", ""); 28 | el.style.position = "absolute"; 29 | el.style.left = "-9999px"; 30 | document.body.appendChild(el); 31 | el.select(); 32 | el.setSelectionRange(0, 99999); 33 | document.execCommand("copy"); 34 | document.body.removeChild(el); 35 | message.success("复制成功!"); 36 | }; 37 | 38 | return ( 39 |
47 | { setIsBigImg(true) }} 52 | /> 53 | {/* */} 54 |
55 |
{item?.baseInfo?.name || "这什么鬼"}
56 |
57 |
58 | {item?.material?.id} 59 | {/* {(find(categoryData, ite => ite?.value === item?.baseInfo?.category) || {})?.label} */} 60 |
61 | 62 |
63 |
64 | {item?.material?.owner &&
{ 68 | copyToClipboard(item?.material?.owner) 69 | }} 70 | >{ellipseAddress(item?.material?.owner, 15)}
} 71 |
72 |
73 | {isModalVisible && item?.material?.id && { setIsModalVisible(false) }} 79 | > 80 |
81 | } 82 | {isBigImg && item?.material?.id && { setIsBigImg(false) }} 90 | > 91 | 95 | } 96 |
97 | ) 98 | } -------------------------------------------------------------------------------- /packages/react-app/src/pages/lockers/components/SearchQuery.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { map } from "lodash"; 3 | import { Button, Input, Modal } from "antd"; 4 | import { useUserInfo } from "../../../components/UserProvider"; 5 | import { CloseSquareOutlined } from "@ant-design/icons"; 6 | import { ComposeDetails } from "./ComposeDetails"; 7 | import { useQueryString } from "../../../helpers/utilities"; 8 | 9 | export const ClearIcon = () =>
10 | 11 | export const SearchQuery = () => { 12 | const [isModalVisible, setIsModalVisible] = useState(false); 13 | const { composeList } = useUserInfo(); 14 | const { searchString, setSearchString } = useQueryString(); 15 | const [{ 16 | id, 17 | owner 18 | }, setFilter] = useState({ 19 | owner: searchString?.owner || "", 20 | id: searchString?.id || "", 21 | }) 22 | 23 | const query = ()=>{ 24 | const data = { 25 | ...searchString, 26 | id: id.trim() ? id?.split(",") : undefined, 27 | owner: owner.trim() || undefined, 28 | createID: "" 29 | } 30 | setSearchString(data) 31 | } 32 | 33 | return ( 34 |
35 | 40 | { setIsModalVisible(false); }} 48 | > 49 | 50 | 51 | { 58 | setFilter((pre) => ({ ...pre, owner: val?.target?.value?.trim() })) 59 | }} 60 | > 61 | 62 | { 69 | const value = val?.target?.value 70 | if (value === "") setFilter((pre) => ({ ...pre, id: "" })) 71 | const lastStr = value?.slice(-1), last2Str = value?.slice(-2); 72 | if (lastStr?.trim() === "" || last2Str === ",,") return 73 | if (isNaN(Number(lastStr)) && lastStr !== ",") return 74 | setFilter((pre) => ({ ...pre, id: value })) 75 | }} 76 | > 77 | 78 | 81 | {/* 98 | 110 | */}{/* */} 122 | {/* */} 134 |
135 | ) 136 | } -------------------------------------------------------------------------------- /packages/react-app/src/pages/person-center/components/BaseInfo.tsx: -------------------------------------------------------------------------------- 1 | import { Button, message } from "antd"; 2 | import { AppstoreOutlined } from "@ant-design/icons"; 3 | import { useLocation } from "react-router"; 4 | import { useUserInfo } from "../../../components/UserProvider"; 5 | import { PixelsMetaverseHandleImg, usePixelsMetaverseHandleImg } from "../../../pixels-metaverse"; 6 | import { ReactNode, useEffect, useMemo } from "react"; 7 | import { useRequest, useWeb3Info } from "abi-to-request"; 8 | import { isEmpty, split } from "lodash"; 9 | import { ellipseAddress } from "../../../helpers/utilities"; 10 | import { setConfig as setConfigApi } from "../../../client/PixelsMetaverse"; 11 | 12 | const InfoLabel = ({ children, label }: { children: ReactNode, label: string }) => { 13 | return ( 14 |
15 |
{label}
16 | {children} 17 |
18 | ) 19 | } 20 | 21 | export const useGetUserConfig = () => { 22 | const { userInfo } = useUserInfo() 23 | return useMemo(() => { 24 | let bgColor: string, gridColor: string, withGrid: boolean; 25 | if (!userInfo?.other) { 26 | bgColor = ""; 27 | gridColor = ""; 28 | withGrid = false; 29 | } 30 | try { 31 | let other = split(userInfo?.other, "|") 32 | bgColor = other[0] === "T" ? "transparent" : `#${other[0]}`; 33 | gridColor = `#${other[1]}`; 34 | withGrid = !!other[2]; 35 | } catch (error) { 36 | bgColor = ""; 37 | gridColor = ""; 38 | withGrid = false; 39 | } 40 | return { bgColor, gridColor, withGrid } 41 | }, [userInfo?.other]) 42 | } 43 | 44 | export const BaseInfo = () => { 45 | const { setConfig, config, canvas2Ref } = usePixelsMetaverseHandleImg() 46 | const { address: addresss } = useWeb3Info() 47 | const { search } = useLocation() 48 | const address = search ? search.split("=")[1] : addresss 49 | const { userInfo } = useUserInfo() 50 | 51 | const [goSetConfig] = useRequest(setConfigApi, { 52 | onTransactionSuccess: () => { 53 | message.success("更新信息成功!") 54 | //getUserInfo() 55 | } 56 | }, [config, address]) 57 | 58 | const { bgColor, gridColor, withGrid } = useGetUserConfig() 59 | useEffect(() => { 60 | if (!bgColor && !gridColor && !withGrid) return 61 | setConfig((pre) => ({ ...pre, withGrid, bgColor, gridColor })) 62 | }, [bgColor, gridColor, withGrid]) 63 | 64 | const isCurUser = useMemo(() => address ? address?.toUpperCase() === addresss?.toUpperCase() : false, [addresss, address]) 65 | 66 | return ( 67 |
68 | 77 |
78 | 79 |
80 | {ellipseAddress(address, 10) || "0x000000000000000000000000000000000000000000000000000"} 81 |
82 |
83 | {/* 84 |
85 | {userInfo?.id === "0" 86 | ? <>访客
激活
87 | : (isEmpty(userInfo) ? "请部署合约" : "宇宙居民")} 88 |
89 |
*/} 90 | 91 | setConfig((pre) => ({ ...pre, withGrid: !config?.withGrid }))} /> 93 | 94 | 95 | setConfig((pre) => ({ ...pre, gridColor: e.target.value }))} /> 97 | 98 | 99 | setConfig((pre) => ({ ...pre, bgColor: e.target.value }))} /> 101 | 102 | 115 |
116 |
117 | ) 118 | } -------------------------------------------------------------------------------- /packages/react-app/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useHistory } from "react-router"; 2 | import { useLocation } from "react-router-dom"; 3 | import { memo, useEffect, useState } from "react"; 4 | import { ellipseAddress } from "../helpers/utilities"; 5 | import { useTranslation } from "react-i18next" 6 | import { Button, Menu, message } from "antd"; 7 | import { isEmpty } from "lodash"; 8 | import i18n from "i18next"; 9 | import { useWeb3Info } from "abi-to-request" 10 | import { useUserInfo } from "./UserProvider"; 11 | 12 | const nav = [ 13 | // { label: "加工", path: "/action" }, 14 | { label: "生产", path: "/produced" }, 15 | { label: "仓库", path: "/lockers" }, 16 | // { label: "个人中心", path: "/person-center" }, 17 | ] 18 | 19 | const menu = () => { 20 | const changeLanguage = (val: string) => { 21 | i18n.changeLanguage(val); 22 | }; 23 | 24 | return ( 25 | 26 | changeLanguage("cn")}>简体 27 | changeLanguage("hk")}>繁体 28 | changeLanguage("en")}>英文 29 | 30 | ) 31 | } 32 | 33 | export const useQueryParams = () => { 34 | const { search } = useLocation(); 35 | const parmasString = search.substring(1); 36 | const params = new URLSearchParams(parmasString); 37 | return params 38 | } 39 | 40 | export const Header = memo(() => { 41 | const { connected, address, killSession, toConnect, chainData } = useWeb3Info(); 42 | const { t } = useTranslation() 43 | const history = useHistory() 44 | const { pathname } = useLocation() 45 | const params = useQueryParams() 46 | const [inputStr, setInputStr] = useState() 47 | const { SmallLoading } = useUserInfo() 48 | /* const [data] = useGetDataRequest(fetchGetMaterialLength, undefined) 49 | 50 | console.log(data?.toString()) */ 51 | 52 | useEffect(() => { 53 | if (params && params?.get("address")) { 54 | setInputStr(params?.get("address")) 55 | } 56 | }, [params?.get("address")]) 57 | 58 | return ( 59 |
60 |
history.push("/")}>像素元宇宙
61 |
62 |
63 | {nav.map(item => { 64 | return (
history.push(item?.path)} 69 | >{item?.label}
) 70 | })} 71 |
72 |
73 | {/*
74 | setInputStr(e.target.value)} 79 | /> 80 | 90 |
*/} 91 |
92 | 93 |
94 | 95 | 96 | {address && !isEmpty(chainData) && connected ? ( 97 |
98 |
{ellipseAddress(address, 12)}
99 |
100 |
101 | {chainData?.name} 102 |
103 | 断开连接 104 |
105 |
) 106 | :
{ 108 | localStorage.clear() 109 | toConnect && toConnect() 110 | }}>连接钱包
111 | } 112 | {/* 113 |
{t("home.content")}
114 |
*/} 115 |
116 |
117 |
118 | ); 119 | }); -------------------------------------------------------------------------------- /packages/react-app/src/helpers/bignumber.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | import { BigNumber as ethBigNumber, BigNumberish, ethers } from 'ethers' 3 | 4 | export function isNaN(value: string | number): boolean { 5 | return new BigNumber(`${value}`).isNaN() 6 | } 7 | 8 | export function isNumber(value: string | number): boolean { 9 | const isNaNResult = isNaN(value) 10 | return !isNaNResult 11 | } 12 | 13 | export function isInteger(value: string | number): boolean { 14 | return new BigNumber(`${value}`).isInteger() 15 | } 16 | 17 | export function isPositive(value: string | number): boolean { 18 | return new BigNumber(`${value}`).isPositive() 19 | } 20 | 21 | export function isNegative(value: string | number): boolean { 22 | return new BigNumber(`${value}`).isNegative() 23 | } 24 | 25 | export function isZero(value: string | number): boolean { 26 | return new BigNumber(`${value}`).isZero() 27 | } 28 | 29 | export function countDecimalPlaces(value: string | number): number { 30 | return new BigNumber(`${value}`).dp() 31 | } 32 | 33 | export function convertNumberToString(value: string | number): string { 34 | return new BigNumber(`${value}`).toString() 35 | } 36 | 37 | export function convertStringToNumber(value: string | number): number { 38 | return new BigNumber(`${value}`).toNumber() 39 | } 40 | 41 | export function convertHexToString(hex: string): string { 42 | return new BigNumber(`${hex}`).toString() 43 | } 44 | 45 | export function convertStringToHex(value: string | number): string { 46 | return new BigNumber(`${value}`).toString(16) 47 | } 48 | 49 | export function greaterThan( 50 | numberOne: number | string, 51 | numberTwo: number | string 52 | ): boolean { 53 | return ( 54 | new BigNumber(`${numberOne}`).comparedTo(new BigNumber(`${numberTwo}`)) === 55 | 1 56 | ) 57 | } 58 | 59 | export function greaterThanOrEqual( 60 | numberOne: number, 61 | numberTwo: number 62 | ): boolean { 63 | return ( 64 | new BigNumber(`${numberOne}`).comparedTo(new BigNumber(`${numberTwo}`)) >= 0 65 | ) 66 | } 67 | 68 | export function smallerThan( 69 | numberOne: number | string, 70 | numberTwo: number | string 71 | ): boolean { 72 | return ( 73 | new BigNumber(`${numberOne}`).comparedTo(new BigNumber(`${numberTwo}`)) === 74 | -1 75 | ) 76 | } 77 | 78 | export function smallerThanOrEqual( 79 | numberOne: number, 80 | numberTwo: number 81 | ): boolean { 82 | return ( 83 | new BigNumber(`${numberOne}`).comparedTo(new BigNumber(`${numberTwo}`)) <= 0 84 | ) 85 | } 86 | 87 | export function multiply( 88 | numberOne: number | string, 89 | numberTwo: number | string 90 | ): string { 91 | return new BigNumber(`${numberOne}`) 92 | .times(new BigNumber(`${numberTwo}`)) 93 | .toString() 94 | } 95 | 96 | export function divide( 97 | numberOne: number | string, 98 | numberTwo: number | string 99 | ): string { 100 | return new BigNumber(`${numberOne}`) 101 | .dividedBy(new BigNumber(`${numberTwo}`)) 102 | .toString() 103 | } 104 | 105 | export function floorDivide( 106 | numberOne: number | string, 107 | numberTwo: number | string 108 | ): string { 109 | return new BigNumber(`${numberOne}`) 110 | .dividedToIntegerBy(new BigNumber(`${numberTwo}`)) 111 | .toString() 112 | } 113 | 114 | export function mod( 115 | numberOne: number | string, 116 | numberTwo: number | string 117 | ): string { 118 | return new BigNumber(`${numberOne}`) 119 | .mod(new BigNumber(`${numberTwo}`)) 120 | .toString() 121 | } 122 | 123 | export function add( 124 | numberOne: number | string, 125 | numberTwo: number | string 126 | ): string { 127 | return new BigNumber(`${numberOne}`) 128 | .plus(new BigNumber(`${numberTwo}`)) 129 | .toString() 130 | } 131 | 132 | export function subtract( 133 | numberOne: number | string, 134 | numberTwo: number | string 135 | ): string { 136 | return new BigNumber(`${numberOne}`) 137 | .minus(new BigNumber(`${numberTwo}`)) 138 | .toString() 139 | } 140 | 141 | export function convertAmountToRawNumber( 142 | value: string | number, 143 | decimals: number = 18 144 | ): string { 145 | return new BigNumber(`${value}`) 146 | .times(new BigNumber('10').pow(decimals)) 147 | .toString() 148 | } 149 | 150 | export function convertAmountFromRawNumber( 151 | value: string | number, 152 | decimals: number = 18 153 | ): string { 154 | return new BigNumber(`${value}`) 155 | .dividedBy(new BigNumber('10').pow(decimals)) 156 | .toString() 157 | } 158 | 159 | export function handleSignificantDecimals( 160 | value: string, 161 | decimals: number, 162 | buffer?: number 163 | ): string | null { 164 | if ( 165 | !new BigNumber(`${decimals}`).isInteger() || 166 | (buffer && !new BigNumber(`${buffer}`).isInteger()) 167 | ) { 168 | return null 169 | } 170 | buffer = buffer ? convertStringToNumber(buffer) : 3 171 | decimals = convertStringToNumber(decimals) 172 | const absolute = new BigNumber(`${value}`).abs().toNumber() 173 | if (smallerThan(absolute, 1)) { 174 | decimals = value.slice(2).search(/[^0]/g) + buffer 175 | decimals = decimals < 8 ? decimals : 8 176 | } else { 177 | decimals = decimals < buffer ? decimals : buffer 178 | } 179 | let result = new BigNumber(`${value}`).toFixed(decimals) 180 | result = new BigNumber(`${result}`).toString() 181 | return new BigNumber(`${result}`).dp() <= 2 182 | ? new BigNumber(`${result}`).toFormat(2) 183 | : new BigNumber(`${result}`).toFormat() 184 | } 185 | 186 | export function formatFixedDecimals(value: string, decimals: number): string { 187 | const _value = convertNumberToString(value) 188 | const _decimals = convertStringToNumber(decimals) 189 | const result = new BigNumber( 190 | new BigNumber(_value).toFixed(_decimals) 191 | ).toString() 192 | return result 193 | } 194 | 195 | export function formatInputDecimals( 196 | inputOne: string, 197 | inputTwo: string 198 | ): string { 199 | const _nativeAmountDecimalPlaces = countDecimalPlaces(inputTwo) 200 | const decimals = 201 | _nativeAmountDecimalPlaces > 8 ? _nativeAmountDecimalPlaces : 8 202 | const result = new BigNumber(formatFixedDecimals(inputOne, decimals)) 203 | .toFormat() 204 | .replace(/,/g, '') 205 | return result 206 | } 207 | 208 | export const getNumber = (big: BigNumberish)=>{ 209 | if(big === "") return "" 210 | return big.toString() 211 | } 212 | -------------------------------------------------------------------------------- /packages/react-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | color: rgba(255, 255, 255, 0.7); 9 | } 10 | 11 | .ellipsis { 12 | white-space:nowrap; 13 | overflow:hidden; 14 | text-overflow:ellipsis; 15 | } 16 | 17 | *::-webkit-scrollbar { 18 | display: none; 19 | } 20 | 21 | code { 22 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 23 | monospace; 24 | } 25 | 26 | .cursorP{ 27 | cursor: pointer; 28 | } 29 | 30 | .mainColor{ 31 | color: #009eff 32 | } 33 | 34 | input{ 35 | outline: "none" !important 36 | } 37 | 38 | @font-face { 39 | /* font-properties */ 40 | font-family: PixelZH; 41 | src:url("./assets/font/cn.ttf"), 42 | url("./assets/font/cn.ttf"), 43 | url("./assets/font/cn.ttf"); /* IE9 */ 44 | } 45 | 46 | * { 47 | font-family: "PixelZH" 48 | } 49 | 50 | @tailwind base; 51 | @tailwind components; 52 | @tailwind utilities; 53 | 54 | .card{ 55 | height: calc(100vh - 130px); 56 | width: 320px; 57 | background-color: transparent; 58 | box-shadow: -5px 5px 10px rgba(225,225,225,0.3); 59 | border-radius: 10px; 60 | border-color: rgba(225,225,225,0.3); 61 | color: rgba(255, 255, 255, 0.7) 62 | } 63 | 64 | .main-box{ 65 | width: 480px; 66 | min-width: 480px; 67 | background-color: rgba(17, 24, 39, 1); 68 | box-shadow: -0px 0px 20px rgba(225,225,225,0.3); 69 | height: 480px; 70 | border-radius: 2px; 71 | border-color: rgba(225,225,225,0.3); 72 | color: rgba(255, 255, 255, 0.7) 73 | } 74 | 75 | .box { 76 | background-image: linear-gradient(-90deg, #353437, #33626C); 77 | } 78 | 79 | .inputPlaceholder::-webkit-input-placeholder { 80 | color: #bfbfbf; 81 | } 82 | 83 | .select input::-webkit-input-placeholder { 84 | color: red !important 85 | } 86 | 87 | .select{ 88 | color: rgba(255, 255, 255, 0.7) !important 89 | } 90 | 91 | .select .ant-select-arrow{ 92 | color: #bfbfbf !important 93 | } 94 | 95 | .ant-message-custom-content{ 96 | display: flex !important; 97 | align-items: center !important; 98 | } 99 | 100 | .ant-select-selector{ 101 | background-color: rgba(255, 255, 255, 0.15) !important; 102 | border: rgba(255, 255, 255, 0.15) !important; 103 | color: rgba(255, 255, 255, 0.7) !important 104 | } 105 | 106 | #create-material .ant-select-selector{ 107 | background-color: rgba(255, 255, 255, 0.15) !important; 108 | border: rgba(255, 255, 255, 0.15) !important; 109 | color: rgba(0, 0, 0, 0.7) !important 110 | } 111 | 112 | .ant-select-selector:active{ 113 | border: rgba(255, 255, 255, 0.15) !important; 114 | } 115 | 116 | .ant-select-arrow{ 117 | color: rgba(255, 255, 255, 0.7) !important 118 | } 119 | 120 | #loading{ 121 | background: rgba(0, 0, 0, 0.2); 122 | height: 100%; 123 | width: 100%; 124 | position: fixed; 125 | z-index: 1100; 126 | margin-top: 0px; 127 | top: 0px; 128 | } 129 | #loading-center{ 130 | width: 100%; 131 | height: 100%; 132 | position: relative; 133 | } 134 | #loading-center-absolute { 135 | position: absolute; 136 | left: 50%; 137 | top: 50%; 138 | height: 200px; 139 | width: 200px; 140 | margin-top: -100px; 141 | margin-left: -100px; 142 | -ms-transform: rotate(-135deg); 143 | -webkit-transform: rotate(-135deg); 144 | transform: rotate(-135deg); 145 | } 146 | .object{ 147 | -moz-border-radius: 50% 50% 50% 50%; 148 | -webkit-border-radius: 50% 50% 50% 50%; 149 | border-radius: 50% 50% 50% 50%; 150 | position: absolute; 151 | border-top: 5px solid rgba(255, 255, 255, 0.7); 152 | border-bottom: 5px solid transparent; 153 | border-left: 5px solid rgba(255, 255, 255, 0.7); 154 | border-right: 5px solid transparent; 155 | -webkit-animation: animate 2s infinite; 156 | animation: animate 2s infinite; 157 | 158 | } 159 | #object_one{ 160 | left: 75px; 161 | top: 75px; 162 | width: 50px; 163 | height: 50px; 164 | } 165 | 166 | #object_two{ 167 | left: 65px; 168 | top: 65px; 169 | width: 70px; 170 | height: 70px; 171 | -webkit-animation-delay: 0.2s; 172 | animation-delay: 0.2s; 173 | } 174 | 175 | #object_three{ 176 | left: 55px; 177 | top: 55px; 178 | width: 90px; 179 | height: 90px; 180 | -webkit-animation-delay: 0.4s; 181 | animation-delay: 0.4s; 182 | } 183 | #object_four{ 184 | left: 45px; 185 | top: 45px; 186 | width: 110px; 187 | height: 110px; 188 | -webkit-animation-delay: 0.6s; 189 | animation-delay: 0.6s; 190 | 191 | } 192 | 193 | @-webkit-keyframes animate { 194 | 195 | 196 | 50% { 197 | 198 | -ms-transform: rotate(360deg) scale(0.8); 199 | -webkit-transform: rotate(360deg) scale(0.8); 200 | transform: rotate(360deg) scale(0.8); 201 | } 202 | } 203 | 204 | @keyframes animate { 205 | 206 | 50% { 207 | 208 | -ms-transform: rotate(360deg) scale(0.8); 209 | -webkit-transform: rotate(360deg) scale(0.8); 210 | transform: rotate(360deg) scale(0.8); 211 | } 212 | } 213 | 214 | #close-loading{ 215 | position: absolute; 216 | left: 60%; 217 | top: 30%; 218 | color: rgba(225,225,225, 0.7); 219 | font-size: 30px; 220 | cursor: pointer; 221 | z-index: 10000; 222 | } 223 | 224 | #close-text{ 225 | position: absolute; 226 | left: 50%; 227 | margin-left: -150px; 228 | bottom: 50%; 229 | margin-bottom: -150px; 230 | color: rgba(225,225,225, 0.7); 231 | font-size: 20px; 232 | cursor: pointer; 233 | z-index: 10000; 234 | } 235 | 236 | .item-avatar:hover{ 237 | background: rgba(225,225,225, 0.1); 238 | border-radius: 5px; 239 | } 240 | 241 | .ant-checkbox-inner{ 242 | background-color: transparent !important; 243 | } 244 | 245 | .ant-checkbox-disabled { 246 | background-color: rgba(225,225,225, 0.15) !important; 247 | } 248 | 249 | .ant-input{ 250 | background-color: transparent !important; 251 | caret-color: rgba(225,225,225); 252 | } 253 | 254 | .ant-pagination input{ 255 | color: #EF4444 !important; 256 | } 257 | 258 | .ant-pagination,.anticon-right,.anticon-left{ 259 | color: rgba(225,225,225) !important; 260 | } 261 | 262 | .small-loading { 263 | border-radius: 50%; 264 | --mask: radial-gradient(closest-side, transparent 75%, black 76%); 265 | -webkit-mask-image: var(--mask); 266 | mask-image: var(--mask); 267 | animation: spin 1s linear infinite reverse; 268 | } 269 | @keyframes spin { 270 | from { transform: rotate(0deg); } 271 | to { transform: rotate(360deg); } 272 | } -------------------------------------------------------------------------------- /packages/react-app/src/helpers/utilities.tsx: -------------------------------------------------------------------------------- 1 | import * as ethUtil from "ethereumjs-util"; 2 | import { message } from "antd"; 3 | import { Dictionary, keys, throttle } from "lodash"; 4 | import { useHistory, useLocation } from "react-router-dom"; 5 | import { useCallback, useMemo } from "react"; 6 | 7 | export function capitalize(string: string): string { 8 | return string 9 | .split(" ") 10 | .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) 11 | .join(" "); 12 | } 13 | 14 | export function ellipseText( 15 | text: string = "", 16 | maxLength: number = 9999 17 | ): string { 18 | if (text.length <= maxLength) { 19 | return text; 20 | } 21 | const _maxLength = maxLength - 3; 22 | let ellipse = false; 23 | let currentLength = 0; 24 | const result = 25 | text 26 | .split(" ") 27 | .filter(word => { 28 | currentLength += word.length; 29 | if (ellipse || currentLength >= _maxLength) { 30 | ellipse = true; 31 | return false; 32 | } else { 33 | return true; 34 | } 35 | }) 36 | .join(" ") + "..."; 37 | return result; 38 | } 39 | 40 | export function ellipseAddress( 41 | address: string = "", 42 | width: number = 8 43 | ): string { 44 | return `${address.slice(0, width)}...${address.slice(-width)}`; 45 | } 46 | 47 | export function padLeft(n: string, width: number, z?: string): string { 48 | z = z || "0"; 49 | n = n + ""; 50 | return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; 51 | } 52 | 53 | export function sanitizeHex(hex: string): string { 54 | hex = hex.substring(0, 2) === "0x" ? hex.substring(2) : hex; 55 | if (hex === "") { 56 | return ""; 57 | } 58 | hex = hex.length % 2 !== 0 ? "0" + hex : hex; 59 | return "0x" + hex; 60 | } 61 | 62 | export function removeHexPrefix(hex: string): string { 63 | return hex.toLowerCase().replace("0x", ""); 64 | } 65 | 66 | export function getDataString(func: string, arrVals: any[]): string { 67 | let val = ""; 68 | for (let i = 0; i < arrVals.length; i++) { 69 | val += padLeft(arrVals[i], 64); 70 | } 71 | const data = func + val; 72 | return data; 73 | } 74 | 75 | export function isMobile(): boolean { 76 | let mobile: boolean = false; 77 | 78 | function hasTouchEvent(): boolean { 79 | try { 80 | document.createEvent("TouchEvent"); 81 | return true; 82 | } catch (e) { 83 | return false; 84 | } 85 | } 86 | 87 | function hasMobileUserAgent(): boolean { 88 | if ( 89 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test( 90 | navigator.userAgent 91 | ) || 92 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test( 93 | navigator.userAgent.substr(0, 4) 94 | ) 95 | ) { 96 | return true; 97 | } else if (hasTouchEvent()) { 98 | return true; 99 | } 100 | return false; 101 | } 102 | 103 | mobile = hasMobileUserAgent(); 104 | 105 | return mobile; 106 | } 107 | 108 | export const warning = throttle((chainId: number | string) => { 109 | message.warning(`很遗憾,暂时不支持 Chain ID 为 ${chainId} 的链!`) 110 | }, 5000) 111 | 112 | export function hashPersonalMessage(msg: string): string { 113 | const buffer = Buffer.from(msg); 114 | const result = ethUtil.hashPersonalMessage(buffer); 115 | const hash = ethUtil.bufferToHex(result); 116 | return hash; 117 | } 118 | 119 | export function recoverPublicKey(sig: string, hash: string): string { 120 | const sigParams = ethUtil.fromRpcSig(sig); 121 | const hashBuffer = Buffer.from(hash.replace("0x", ""), "hex"); 122 | const result = ethUtil.ecrecover( 123 | hashBuffer, 124 | sigParams.v, 125 | sigParams.r, 126 | sigParams.s 127 | ); 128 | const signer = ethUtil.bufferToHex(ethUtil.publicToAddress(result)); 129 | return signer; 130 | } 131 | 132 | export function recoverPersonalSignature(sig: string, msg: string): string { 133 | const hash = hashPersonalMessage(msg); 134 | const signer = recoverPublicKey(sig, hash); 135 | return signer; 136 | } 137 | 138 | export function isObject(obj: any): boolean { 139 | return typeof obj === "object" && !!Object.keys(obj).length; 140 | } 141 | 142 | // 提供36位的表达 0-9 a-z 143 | export function getNums(number: number) { 144 | var nums = []; 145 | for (var i = 0; i < number; i++) { 146 | if (i >= 0 && i <= 9) { 147 | nums.push(i) 148 | } else { 149 | nums.push(String.fromCharCode(i + 55)); 150 | } 151 | } 152 | return nums; 153 | } 154 | 155 | export const useQueryString = () => { 156 | const { search } = useLocation(); 157 | const history = useHistory() 158 | const setSearchString = useCallback((obj) => { 159 | let str = "/lockers?" 160 | for (let i in obj) { 161 | if (!obj[i]) continue 162 | str += `${i}=${obj[i]}&` 163 | } 164 | history.push(str?.slice(0, -1)) 165 | }, []); 166 | 167 | const getSearchObj = useCallback((str: string) => { 168 | let queryArray = str.split('&'); 169 | const obj: Dictionary = {}; 170 | queryArray.map((query) => { 171 | let temp = query.split('='); 172 | if (temp.length > 1) { 173 | obj[temp[0]] = temp[1]; 174 | } 175 | return query 176 | }) 177 | return obj 178 | }, []) 179 | 180 | const searchString = useMemo(() => { 181 | const str = search.slice(1); 182 | return getSearchObj(str) 183 | }, [search]); 184 | 185 | return { searchString, setSearchString, getSearchObj } 186 | } -------------------------------------------------------------------------------- /packages/hardhat/contracts/PixelsMetaverse.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IPMT721.sol"; 5 | 6 | contract PixelsMetaverse { 7 | IPMT721 private PMT721; 8 | mapping(address => uint256) public avater; 9 | event AvaterEvent(address indexed owner, uint256 indexed avater); 10 | 11 | mapping(bytes32 => address) public dataOwner; 12 | event DataOwnerEvent(address indexed owner, bytes32 dataBytes); 13 | 14 | struct Material { 15 | uint256 composed; //被合并到哪个id去了 16 | bytes32 dataBytes; //原属数据data转换成bytes32 17 | bool remake; //是否基于当前id再次制作了与该id同样的其他虚拟物品 18 | } 19 | mapping(uint256 => Material) public material; 20 | 21 | /** 22 | rawData 当前ID的原始数据 23 | dataID 当前ID的基本数据来自于哪个id 24 | */ 25 | event MaterialEvent( 26 | address indexed owner, 27 | uint256 indexed id, 28 | uint256 indexed dataID, 29 | uint256 configID, 30 | string rawData, 31 | bool remake 32 | ); 33 | 34 | /** 35 | sort 当前配置信息拼接的顺序 36 | */ 37 | event ConfigEvent( 38 | uint256 indexed id, 39 | string name, 40 | string time, 41 | string position, 42 | string zIndex, 43 | string decode, 44 | uint256 sort 45 | ); 46 | 47 | /** 48 | fromID 被合并或解除合并之前的上级id 49 | toID 被合并或解除合并之后的上级id 50 | */ 51 | event ComposeEvent(uint256 fromID, uint256 toID, uint256[] id, bool isAdd); 52 | 53 | modifier Owner(address sender, uint256 id) { 54 | require(IPMT721(PMT721).exits(id), "Items must exist"); 55 | require(sender == IPMT721(PMT721).ownerOf(id), "Only the owner"); 56 | _; 57 | } 58 | 59 | constructor(address pmt721) { 60 | PMT721 = IPMT721(pmt721); 61 | } 62 | 63 | function setAvater(uint256 id) public Owner(msg.sender, id) { 64 | avater[msg.sender] = id; 65 | emit AvaterEvent(msg.sender, id); 66 | } 67 | 68 | function setDataOwner(bytes32 dataBytes, address to) public { 69 | require(dataOwner[dataBytes] == msg.sender, "Items must exist"); 70 | dataOwner[dataBytes] = to; 71 | emit DataOwnerEvent(msg.sender, dataBytes); 72 | } 73 | 74 | function setConfig( 75 | uint256 id, 76 | string memory name, 77 | string memory time, 78 | string memory position, 79 | string memory zIndex, 80 | string memory decode, 81 | uint256 sort 82 | ) public Owner(msg.sender, id) { 83 | require( 84 | material[id].composed == 0, 85 | "The item must not have been synthesized" 86 | ); 87 | emit ConfigEvent(id, name, time, position, zIndex, decode, sort); 88 | emit MaterialEvent(msg.sender, id, 0, id, "", false); 89 | } 90 | 91 | function make( 92 | string memory name, 93 | string memory rawData, 94 | string memory time, 95 | string memory position, 96 | string memory zIndex, 97 | string memory decode, 98 | uint256 num 99 | ) public { 100 | require(num > 0, "The quantity must be greater than 0"); 101 | 102 | bytes32 d = keccak256(abi.encodePacked(rawData)); 103 | require(dataOwner[d] == address(0), "This data already has an owner"); 104 | 105 | uint256 ID = IPMT721(PMT721).currentID() + num; 106 | emit ConfigEvent(ID, name, time, position, zIndex, decode, 0); 107 | 108 | for (uint256 i; i < num; i++) { 109 | _make(msg.sender, rawData, d, 0, ID); 110 | } 111 | 112 | dataOwner[d] = msg.sender; 113 | } 114 | 115 | function reMake(uint256 id, uint256 num) public Owner(msg.sender, id) { 116 | Material storage m = material[id]; 117 | require(dataOwner[m.dataBytes] == msg.sender, "Only the owner"); 118 | 119 | emit MaterialEvent(msg.sender, id, 0, 0, "", true); 120 | for (uint256 i; i < num; i++) { 121 | _make(msg.sender, "", m.dataBytes, id, id); 122 | } 123 | material[id].remake = true; 124 | } 125 | 126 | function compose( 127 | uint256[] memory idList, 128 | string memory name, 129 | string memory time, 130 | string memory position, 131 | string memory zIndex, 132 | string memory decode 133 | ) public { 134 | uint256 len = idList.length; 135 | require(len > 1, "The quantity must be greater than 1"); 136 | 137 | uint256 nextID = IPMT721(PMT721).currentID() + 1; 138 | bytes32 dataBytes = keccak256(abi.encodePacked(msg.sender, nextID)); 139 | emit ConfigEvent(nextID, name, time, position, zIndex, decode, 0); 140 | _make(msg.sender, "", dataBytes, nextID, nextID); 141 | 142 | for (uint256 i; i < len; i++) { 143 | _compose(nextID, idList[i], msg.sender); 144 | } 145 | emit ComposeEvent(0, nextID, idList, true); 146 | dataOwner[dataBytes] = msg.sender; 147 | } 148 | 149 | function _make( 150 | address sender, 151 | string memory rawData, 152 | bytes32 dataBytes, 153 | uint256 dataID, 154 | uint256 configID 155 | ) private { 156 | IPMT721(PMT721).mint(sender); 157 | uint256 id = IPMT721(PMT721).currentID(); 158 | material[id] = Material(0, dataBytes, false); 159 | emit MaterialEvent(msg.sender, id, dataID, configID, rawData, false); 160 | } 161 | 162 | function addition(uint256 ids, uint256[] memory idList) 163 | public 164 | Owner(msg.sender, ids) 165 | { 166 | for (uint256 i; i < idList.length; i++) { 167 | _compose(ids, idList[i], msg.sender); 168 | } 169 | emit ComposeEvent(0, ids, idList, false); 170 | } 171 | 172 | function _compose( 173 | uint256 ids, 174 | uint256 id, 175 | address _sender 176 | ) private Owner(_sender, id) { 177 | require(material[id].composed == 0, "this Material composed"); 178 | material[id].composed = ids; 179 | } 180 | 181 | function subtract(uint256 ids, uint256[] memory idList) 182 | public 183 | Owner(msg.sender, ids) 184 | { 185 | Material memory m = material[ids]; 186 | require(m.composed == 0, "The item must not have been synthesized"); 187 | for (uint256 i; i < idList.length; i++) { 188 | uint256 id = idList[i]; 189 | require( 190 | material[id].composed == ids, 191 | "The item was not synthesized into the ids" 192 | ); 193 | material[id].composed = 0; 194 | } 195 | emit ComposeEvent(ids, 0, idList, false); 196 | } 197 | 198 | function handleTransfer( 199 | address from, 200 | address to, 201 | uint256 id 202 | ) public { 203 | Material memory m = material[id]; 204 | require(m.composed == 0, "The item must not have been synthesized"); 205 | require(msg.sender == address(PMT721), "Only the owner"); 206 | require(avater[from] != id, "This id been avater"); 207 | 208 | if (to == address(0)) { 209 | require(!m.remake, "This id been remake"); 210 | delete material[id]; 211 | } 212 | if (from != address(0)) { 213 | emit MaterialEvent(to, id, 0, 0, "", false); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /packages/hardhat/test/PixelsMetaverse.js: -------------------------------------------------------------------------------- 1 | const { ethers } = require("hardhat"); 2 | const { use, expect } = require("chai"); 3 | const { solidity } = require("ethereum-waffle"); 4 | 5 | use(solidity); 6 | 7 | describe("Test My Dapp", function () { 8 | let PMT721Contract; 9 | let PixelsMetaverseContract; 10 | let owner; 11 | let otherAccount = "0xf0A3FdF9dC875041DFCF90ae81D7E01Ed9Bc2033" 12 | 13 | it("Deploy Contract", async function () { 14 | const signers = await ethers.getSigners(); 15 | owner = signers[0]; 16 | const PMT721 = await ethers.getContractFactory("PMT721"); 17 | const PixelsMetaverse = await ethers.getContractFactory("PixelsMetaverse"); 18 | 19 | PMT721Contract = await PMT721.deploy(); 20 | await PMT721Contract.deployed(); 21 | PixelsMetaverseContract = await PixelsMetaverse.deploy(PMT721Contract.address); 22 | await PixelsMetaverseContract.deployed(); 23 | }); 24 | 25 | describe("调用PixelsMetaverse合约函数", async function () { 26 | it("设置PMT721的发行者", async function () { 27 | await PMT721Contract.setMinter(PixelsMetaverseContract.address); 28 | expect(await PMT721Contract.minter()).to.equal(PixelsMetaverseContract.address); 29 | }); 30 | 31 | it("制作2个虚拟物品1、2", async function () { 32 | await PixelsMetaverseContract.make("name", "rawData", "time", "position", "zIndex", "decode", 2); 33 | const currentID = await PMT721Contract.currentID() 34 | expect(currentID).to.equal(2); 35 | }); 36 | 37 | it("设置头像为1和2", async function () { 38 | expect(await PixelsMetaverseContract.avater(owner.address)).to.equal(0) 39 | expect(await PixelsMetaverseContract.setAvater(1)).to. 40 | emit(PixelsMetaverseContract, "AvaterEvent"). 41 | withArgs(owner.address, 1); 42 | expect(await PixelsMetaverseContract.avater(owner.address)).to.equal(1) 43 | expect(await PixelsMetaverseContract.setAvater(2)).to. 44 | emit(PixelsMetaverseContract, "AvaterEvent"). 45 | withArgs(owner.address, 2); 46 | expect(await PixelsMetaverseContract.avater(owner.address)).to.equal(2) 47 | }); 48 | 49 | it("设置1的配置信息", async function () { 50 | expect(await PixelsMetaverseContract.setConfig(1, "name1", "time1", "position1", "zIndex1", "decode1", 0)).to. 51 | emit(PixelsMetaverseContract, "ConfigEvent"). 52 | withArgs(1, "name1", "time1", "position1", "zIndex1", "decode1", 0); 53 | }); 54 | 55 | it("设置2的配置信息", async function () { 56 | expect(await PixelsMetaverseContract.setConfig(2, "name12222222", "time1222222", "position12222222", "zIndex1222222", "decode1222222", 0)).to. 57 | emit(PixelsMetaverseContract, "MaterialEvent"). 58 | withArgs(owner.address, 2, 0, 2, "", false); 59 | }); 60 | 61 | it("再次制作和2同样的2个虚拟物品3和4", async function () { 62 | const currentID1 = await PMT721Contract.currentID() 63 | expect(currentID1).to.equal(2); 64 | await PixelsMetaverseContract.reMake(2, 2); 65 | const currentID = await PMT721Contract.currentID() 66 | expect(currentID).to.equal(4); 67 | }); 68 | 69 | it("再制作1个虚拟物品5", async function () { 70 | expect(await PixelsMetaverseContract.make("name5", "x-43-fdfad4-54343dfdfd4-43543543dffds-443543d4-45354353d453567554653-dfsafads", "time5", "position5", "zIndex5", "decode5", 1)).to. 71 | emit(PixelsMetaverseContract, "MaterialEvent"). 72 | withArgs(owner.address, 5, 0, 5, "x-43-fdfad4-54343dfdfd4-43543543dffds-443543d4-45354353d453567554653-dfsafads", false); 73 | }); 74 | 75 | it("合成2和4为第6个物品", async function () { 76 | expect(await PixelsMetaverseContract.compose([2, 4], "name6", "time6", "position6", "zIndex6", "decode6")).to. 77 | emit(PixelsMetaverseContract, "ConfigEvent"). 78 | withArgs(6, "name6", "time6", "position6", "zIndex6", "decode6", 0); 79 | }); 80 | 81 | it("再合成1和3为第7个物品", async function () { 82 | await PixelsMetaverseContract.compose([1, 3], "name7", "time7", "position7", "zIndex7", "decode7"); 83 | }); 84 | 85 | it("再合成5和6为第8个物品", async function () { 86 | expect(await await PixelsMetaverseContract.compose([5, 6], "name8", "time8", "position8", "zIndex8", "decode8")).to. 87 | emit(PixelsMetaverseContract, "ComposeEvent"). 88 | withArgs(0, 8, [5,6], true); 89 | }); 90 | 91 | it("再次制作3个不同的虚拟物品9、10、11", async function () { 92 | await PixelsMetaverseContract.make("name9", "rawData9", "time9", "position9", "zIndex9", "decode9", 3); 93 | const m9 = await PixelsMetaverseContract.material(9) 94 | expect(m9.composed).to.equal(0); 95 | }); 96 | 97 | it("合并9到6里面去", async function () { 98 | const m6 = await PixelsMetaverseContract.material(6) 99 | expect(m6.composed).to.equal(8); 100 | expect(await PixelsMetaverseContract.addition(6, [9])).to. 101 | emit(PixelsMetaverseContract, "ComposeEvent"). 102 | withArgs(0, 6, [9], false); 103 | const currentID = await PMT721Contract.currentID() 104 | expect(currentID).to.equal(11); 105 | const m9 = await PixelsMetaverseContract.material(9) 106 | expect(m9.composed).to.equal(6); 107 | }); 108 | 109 | it("合并10和7到8里面去", async function () { 110 | expect(await PixelsMetaverseContract.addition(8, [10, 7])).to. 111 | emit(PixelsMetaverseContract, "ComposeEvent"). 112 | withArgs(0, 8, [10, 7], false); 113 | const currentID = await PMT721Contract.currentID() 114 | expect(currentID).to.equal(11); 115 | const m7 = await PixelsMetaverseContract.material(7) 116 | expect(m7.composed).to.equal(8); 117 | }); 118 | 119 | it("移除8里面的10", async function () { 120 | const m10 = await PixelsMetaverseContract.material(10) 121 | expect(m10.composed).to.equal(8); 122 | expect(await PixelsMetaverseContract.subtract(8, [10])).to. 123 | emit(PixelsMetaverseContract, "ComposeEvent"). 124 | withArgs(8, 0, [10], false); 125 | const currentID = await PMT721Contract.currentID() 126 | expect(currentID).to.equal(11); 127 | const m1010 = await PixelsMetaverseContract.material(10) 128 | expect(m1010.composed).to.equal(0); 129 | const m1 = await PixelsMetaverseContract.material(1) 130 | expect(m1.composed).to.equal(7); 131 | }); 132 | 133 | it("制作1个虚拟物品12", async function () { 134 | await PixelsMetaverseContract.make("name12", "rawData12", "time12", "position12", "zIndex12", "decode12", 1); 135 | }); 136 | 137 | it("再次制作和6一样的2个合成虚拟物品13、14", async function () { 138 | await PixelsMetaverseContract.reMake(6, 2); 139 | }); 140 | }); 141 | 142 | describe("调用PMT721合约函数", async function () { 143 | it("检查11是否存在", async function () { 144 | expect(await PMT721Contract.exits(11)).to.equal(true); 145 | }); 146 | it("检查11的所有者", async function () { 147 | expect(await PMT721Contract.ownerOf(11)).to.equal(owner.address); 148 | }); 149 | it("转账11给" + otherAccount, async function () { 150 | expect(await PMT721Contract.ownerOf(11)).to.equal(owner.address); 151 | expect(await PMT721Contract.transferFrom(owner.address, otherAccount, 11)).to. 152 | emit(PixelsMetaverseContract, "MaterialEvent"). 153 | withArgs(otherAccount, 11, 0, 0, "", false); 154 | expect(await PMT721Contract.ownerOf(11)).to.equal(otherAccount); 155 | const m1111 = await PixelsMetaverseContract.material(11) 156 | expect(m1111.composed).to.equal(0); 157 | }); 158 | it("销毁10", async function () { 159 | const m10 = await PixelsMetaverseContract.material(10) 160 | expect(m10.composed).to.equal(0); 161 | 162 | expect(await PMT721Contract.burn(10)).to. 163 | emit(PMT721Contract, "Transfer"). 164 | withArgs(owner.address, ethers.constants.AddressZero, 10); 165 | const m1010 = await PixelsMetaverseContract.material(10) 166 | expect(m1010.composed).to.equal(0); 167 | expect(await PMT721Contract.balanceOf(otherAccount)).to.equal(1); 168 | }); 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/lockers/components/ComposeDetails.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, InputHTMLAttributes, ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; 2 | import { Select, message, Tabs, Button, Radio } from 'antd'; 3 | import { isEmpty, map } from 'lodash'; 4 | import { useUserInfo } from '../../../components/UserProvider'; 5 | import { PixelsMetaverseImgByPositionData } from '../../../pixels-metaverse'; 6 | import { ClearIcon } from './SearchQuery'; 7 | import { MaterialItem } from '../../../components/Card'; 8 | import { categoryData, IMerchandise } from '../../produced/components/Submit'; 9 | import { useRequest } from 'abi-to-request'; 10 | import { addition, compose } from '../../../client/PixelsMetaverse'; 11 | const { Option } = Select; 12 | const { TabPane } = Tabs; 13 | 14 | interface ICompose { 15 | singles: string[], 16 | composes: string[], 17 | composesData: MaterialItem[], 18 | } 19 | 20 | const Label = ({ children, noNeed }: { children: ReactNode, noNeed?: boolean }) => { 21 | return
{children}{!noNeed && *}
22 | } 23 | 24 | export const ComposeDetails = ({ setIsModalVisible }: { setIsModalVisible: Dispatch> }) => { 25 | const [type, setType] = useState() 26 | const [tab, setTab] = useState("new") 27 | const [value, setValue] = React.useState("-1"); 28 | const { composeList, setComposeList, materialListObj, userInfo, setSmallLoading } = useUserInfo() 29 | const [{ name }, setMerchandies] = React.useState({ name: "", num: "" }) 30 | 31 | const [composeFun] = useRequest(compose, { 32 | isGlobalTransactionHookValid: true, 33 | onSuccess: () => { 34 | setIsModalVisible(false) 35 | setSmallLoading(true); 36 | }, 37 | onTransactionSuccess: () => { 38 | message.success("合成成功!正在获取链上新数据...") 39 | setComposeList && setComposeList([]) 40 | } 41 | }, []) 42 | 43 | const [join] = useRequest(addition, { 44 | isGlobalTransactionHookValid: true, 45 | onSuccess: () => { 46 | setIsModalVisible(false) 47 | setSmallLoading(true); 48 | }, 49 | onTransactionSuccess: () => { 50 | message.success(`合成至 ${value} 成功!正在获取链上新数据...`) 51 | setComposeList && setComposeList([]) 52 | setIsModalVisible(false) 53 | } 54 | }, [value, composeList]) 55 | 56 | useEffect(() => { 57 | if (isEmpty(composeList) || isEmpty(materialListObj)) return 58 | const type: ICompose = { 59 | singles: [], 60 | composes: [], 61 | composesData: [] 62 | } 63 | map(composeList, item => { 64 | if (isEmpty(materialListObj[item]?.composes)) { 65 | type?.singles.push(item) 66 | type?.composesData.push(materialListObj[item]); 67 | } else { 68 | type?.composes.push(item) 69 | type.composesData = [...type?.composesData, ...materialListObj[item]?.composeData]; 70 | } 71 | }) 72 | setType(type) 73 | }, [composeList, materialListObj]) 74 | 75 | const data = useMemo(() => { 76 | if (isEmpty(type?.composesData)) return [] 77 | return map(type?.composesData, it => ({ ...it, data: it?.baseInfo?.data } as any)) 78 | }, [type?.composesData]) 79 | 80 | const checkData = useCallback(() => { 81 | if (!name) { 82 | message.warn("请输入物品名称"); 83 | return; 84 | } 85 | return true; 86 | }, [name]); 87 | 88 | const isUser = useMemo(() => userInfo?.id !== "0", [userInfo]); 89 | 90 | return ( 91 |
92 | 96 |
97 | { 98 | isEmpty(type?.composes) 99 | ? 100 | : { 101 | setTab(key) 102 | }}> 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | } 111 | 134 |
135 |
136 | ); 137 | }; 138 | 139 | export const Input = (props: InputHTMLAttributes) => { 140 | return ( 141 | 146 | ) 147 | } 148 | 149 | export const CreateMaterial = ({ 150 | name, 151 | category, 152 | setMerchandies 153 | }: { 154 | name: string; 155 | category?: string, 156 | setMerchandies: Dispatch> 157 | }) => { 158 | return ( 159 |
160 | 161 | setMerchandies((pre) => ({ ...pre, name: e.target.value }))} /> 162 |
163 | {/* 164 | */} 179 |
180 | ) 181 | } 182 | 183 | export const MergeMaterial = ({ composes, value, setValue }: { composes?: string[], value?: string, setValue: Dispatch> }) => { 184 | const { materialListObj } = useUserInfo() 185 | 186 | return
187 | { 188 | setValue(e.target.value) 189 | }} value={value}> 190 | {map(composes, item => { 191 | return ( 192 | 193 |
194 |
ID:{item}
195 |
 {materialListObj[item]?.baseInfo?.name}
196 |
197 |
198 | ) 199 | })} 200 |
201 |
202 | } -------------------------------------------------------------------------------- /packages/hardhat/scripts/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint no-use-before-define: "warn" */ 2 | const fs = require("fs"); 3 | const chalk = require("chalk"); 4 | const { config, ethers, tenderly, run } = require("hardhat"); 5 | const { utils } = require("ethers"); 6 | const R = require("ramda"); 7 | 8 | /* 9 | 10 | _______ _________ _______ _______ 11 | ( ____ \\__ __/( ___ )( ____ ) 12 | | ( \/ ) ( | ( ) || ( )| 13 | | (_____ | | | | | || (____)| 14 | (_____ ) | | | | | || _____) 15 | ) | | | | | | || ( 16 | /\____) | | | | (___) || ) 17 | \_______) )_( (_______)|/ 18 | 19 | This deploy script is no longer in use, but is left for reference purposes! 20 | 21 | scaffold-eth now uses hardhat-deploy to manage deployments, see the /deploy folder 22 | And learn more here: https://www.npmjs.com/package/hardhat-deploy 23 | 24 | */ 25 | 26 | const main = async () => { 27 | console.log("\n\n 📡 Deploying...\n"); 28 | 29 | const yourContract = await deploy("YourContract"); // <-- add in constructor args like line 19 vvvv 30 | // use for local token bridging 31 | // const mockToken = await deploy("MockERC20") // <-- add in constructor args like line 19 vvvv 32 | 33 | //const yourContract = await ethers.getContractAt('YourContract', "0xaAC799eC2d00C013f1F11c37E654e59B0429DF6A") //<-- if you want to instantiate a version of a contract at a specific address! 34 | //const secondContract = await deploy("SecondContract") 35 | 36 | // const exampleToken = await deploy("ExampleToken") 37 | // const examplePriceOracle = await deploy("ExamplePriceOracle") 38 | // const smartContractWallet = await deploy("SmartContractWallet",[exampleToken.address,examplePriceOracle.address]) 39 | 40 | /* 41 | //If you want to send value to an address from the deployer 42 | const deployerWallet = ethers.provider.getSigner() 43 | await deployerWallet.sendTransaction({ 44 | to: "0x34aA3F359A9D614239015126635CE7732c18fDF3", 45 | value: ethers.utils.parseEther("0.001") 46 | }) 47 | */ 48 | 49 | /* 50 | //If you want to send some ETH to a contract on deploy (make your constructor payable!) 51 | const yourContract = await deploy("YourContract", [], { 52 | value: ethers.utils.parseEther("0.05") 53 | }); 54 | */ 55 | 56 | /* 57 | //If you want to link a library into your contract: 58 | // reference: https://github.com/austintgriffith/scaffold-eth/blob/using-libraries-example/packages/hardhat/scripts/deploy.js#L19 59 | const yourContract = await deploy("YourContract", [], {}, { 60 | LibraryName: **LibraryAddress** 61 | }); 62 | */ 63 | 64 | //If you want to verify your contract on tenderly.co (see setup details in the scaffold-eth README!) 65 | /* 66 | await tenderlyVerify( 67 | {contractName: "YourContract", 68 | contractAddress: yourContract.address 69 | }) 70 | */ 71 | 72 | console.log( 73 | " 💾 Artifacts (address, abi, and args) saved to: ", 74 | chalk.blue("packages/hardhat/artifacts/"), 75 | "\n\n" 76 | ); 77 | }; 78 | 79 | const deploy = async ( 80 | contractName, 81 | _args = [], 82 | overrides = {}, 83 | libraries = {} 84 | ) => { 85 | console.log(` 🛰 Deploying: ${contractName}`); 86 | 87 | const contractArgs = _args || []; 88 | const contractArtifacts = await ethers.getContractFactory(contractName, { 89 | libraries: libraries, 90 | }); 91 | const deployed = await contractArtifacts.deploy(...contractArgs, overrides); 92 | const encoded = abiEncodeArgs(deployed, contractArgs); 93 | fs.writeFileSync(`artifacts/${contractName}.address`, deployed.address); 94 | 95 | let extraGasInfo = ""; 96 | if (deployed && deployed.deployTransaction) { 97 | const gasUsed = deployed.deployTransaction.gasLimit.mul( 98 | deployed.deployTransaction.gasPrice 99 | ); 100 | extraGasInfo = `${utils.formatEther(gasUsed)} ETH, tx hash ${ 101 | deployed.deployTransaction.hash 102 | }`; 103 | } 104 | 105 | console.log( 106 | " 📄", 107 | chalk.cyan(contractName), 108 | "deployed to:", 109 | chalk.magenta(deployed.address) 110 | ); 111 | console.log(" ⛽", chalk.grey(extraGasInfo)); 112 | 113 | await tenderly.persistArtifacts({ 114 | name: contractName, 115 | address: deployed.address, 116 | }); 117 | 118 | if (!encoded || encoded.length <= 2) return deployed; 119 | fs.writeFileSync(`artifacts/${contractName}.args`, encoded.slice(2)); 120 | 121 | return deployed; 122 | }; 123 | 124 | // ------ utils ------- 125 | 126 | // abi encodes contract arguments 127 | // useful when you want to manually verify the contracts 128 | // for example, on Etherscan 129 | const abiEncodeArgs = (deployed, contractArgs) => { 130 | // not writing abi encoded args if this does not pass 131 | if ( 132 | !contractArgs || 133 | !deployed || 134 | !R.hasPath(["interface", "deploy"], deployed) 135 | ) { 136 | return ""; 137 | } 138 | const encoded = utils.defaultAbiCoder.encode( 139 | deployed.interface.deploy.inputs, 140 | contractArgs 141 | ); 142 | return encoded; 143 | }; 144 | 145 | // checks if it is a Solidity file 146 | const isSolidity = (fileName) => 147 | fileName.indexOf(".sol") >= 0 && 148 | fileName.indexOf(".swp") < 0 && 149 | fileName.indexOf(".swap") < 0; 150 | 151 | const readArgsFile = (contractName) => { 152 | let args = []; 153 | try { 154 | const argsFile = `./contracts/${contractName}.args`; 155 | if (!fs.existsSync(argsFile)) return args; 156 | args = JSON.parse(fs.readFileSync(argsFile)); 157 | } catch (e) { 158 | console.log(e); 159 | } 160 | return args; 161 | }; 162 | 163 | function sleep(ms) { 164 | return new Promise((resolve) => setTimeout(resolve, ms)); 165 | } 166 | 167 | // If you want to verify on https://tenderly.co/ 168 | const tenderlyVerify = async ({ contractName, contractAddress }) => { 169 | let tenderlyNetworks = [ 170 | "kovan", 171 | "goerli", 172 | "mainnet", 173 | "rinkeby", 174 | "ropsten", 175 | "matic", 176 | "mumbai", 177 | "xDai", 178 | "POA", 179 | ]; 180 | let targetNetwork = process.env.HARDHAT_NETWORK || config.defaultNetwork; 181 | 182 | if (tenderlyNetworks.includes(targetNetwork)) { 183 | console.log( 184 | chalk.blue( 185 | ` 📁 Attempting tenderly verification of ${contractName} on ${targetNetwork}` 186 | ) 187 | ); 188 | 189 | await tenderly.persistArtifacts({ 190 | name: contractName, 191 | address: contractAddress, 192 | }); 193 | 194 | let verification = await tenderly.verify({ 195 | name: contractName, 196 | address: contractAddress, 197 | network: targetNetwork, 198 | }); 199 | 200 | return verification; 201 | } else { 202 | console.log( 203 | chalk.grey(` 🧐 Contract verification not supported on ${targetNetwork}`) 204 | ); 205 | } 206 | 207 | 208 | 209 | /* await YourContract.setPurpose("Hello"); 210 | 211 | To take ownership of yourContract using the ownable library uncomment next line and add the 212 | address you want to be the owner. 213 | // await yourContract.transferOwnership(YOUR_ADDRESS_HERE); 214 | 215 | //const yourContract = await ethers.getContractAt('YourContract', "0xaAC799eC2d00C013f1F11c37E654e59B0429DF6A") //<-- if you want to instantiate a version of a contract at a specific address! 216 | */ 217 | 218 | /* 219 | //If you want to send value to an address from the deployer 220 | const deployerWallet = ethers.provider.getSigner() 221 | await deployerWallet.sendTransaction({ 222 | to: "0x34aA3F359A9D614239015126635CE7732c18fDF3", 223 | value: ethers.utils.parseEther("0.001") 224 | }) 225 | */ 226 | 227 | /* 228 | //If you want to send some ETH to a contract on deploy (make your constructor payable!) 229 | const yourContract = await deploy("YourContract", [], { 230 | value: ethers.utils.parseEther("0.05") 231 | }); 232 | */ 233 | 234 | /* 235 | //If you want to link a library into your contract: 236 | // reference: https://github.com/austintgriffith/scaffold-eth/blob/using-libraries-example/packages/hardhat/scripts/deploy.js#L19 237 | const yourContract = await deploy("YourContract", [], {}, { 238 | LibraryName: **LibraryAddress** 239 | }); 240 | */ 241 | 242 | // Verify from the command line by running `yarn verify` 243 | 244 | // You can also Verify your contracts with Etherscan here... 245 | // You don't want to verify on localhost 246 | // try { 247 | // if (chainId !== localChainId) { 248 | // await run("verify:verify", { 249 | // address: YourContract.address, 250 | // contract: "contracts/YourContract.sol:YourContract", 251 | // contractArguments: [], 252 | // }); 253 | // } 254 | // } catch (error) { 255 | // console.error(error); 256 | // } 257 | }; 258 | 259 | main() 260 | .then(() => process.exit(0)) 261 | .catch((error) => { 262 | console.error(error); 263 | process.exit(1); 264 | }); 265 | -------------------------------------------------------------------------------- /packages/react-app/src/client/PixelsMetaverse.tsx: -------------------------------------------------------------------------------- 1 | import { TContract, convertedBigNumber } from "abi-to-request"; 2 | import { Contract } from 'web3-eth-contract'; 3 | import { TransactionResponse } from "@ethersproject/abstract-provider"; 4 | import { TransactionReceipt } from 'web3-core'; 5 | import { ethers } from "ethers"; 6 | 7 | //nonpayable 8 | export const addition = { 9 | name: "addition", 10 | contract: "PixelsMetaverse", 11 | fun: async ( 12 | contract: TContract, 13 | arg?: { 14 | ids: string | number; //uint256 15 | idList: (string | number)[]; //uint256[] 16 | } 17 | ) => { 18 | if (!arg) return 19 | const { ids, idList } = arg; 20 | if ((contract as any)?.address && !(contract as any)?.methods) { 21 | let res = await (contract as ethers.Contract).addition(ids, idList) 22 | return res as TransactionResponse 23 | } else { 24 | let res = await (contract as Contract).methods.addition(ids, idList).send({ from: contract.sendAccount }) 25 | return res as TransactionReceipt 26 | } 27 | } 28 | } 29 | 30 | //view 31 | export const avater = { 32 | name: "avater", 33 | contract: "PixelsMetaverse", 34 | fun: async ( 35 | contract: TContract, 36 | arg?: { 37 | addressParams1: string; //address 38 | } 39 | ) => { 40 | if (!arg) return 41 | const { addressParams1 } = arg; 42 | if ((contract as any)?.address && !(contract as any)?.methods) { 43 | let res = await (contract as ethers.Contract).avater(addressParams1) 44 | return convertedBigNumber(res) as string; //uint256 45 | } else { 46 | let res = await (contract as Contract).methods.avater(addressParams1).call() 47 | return res as string; //uint256 48 | } 49 | } 50 | } 51 | 52 | //nonpayable 53 | export const compose = { 54 | name: "compose", 55 | contract: "PixelsMetaverse", 56 | fun: async ( 57 | contract: TContract, 58 | arg?: { 59 | idList: (string | number)[]; //uint256[] 60 | name: string; //string 61 | time: string; //string 62 | position: string; //string 63 | zIndex: string; //string 64 | decode: string; //string 65 | } 66 | ) => { 67 | if (!arg) return 68 | const { idList, name, time, position, zIndex, decode } = arg; 69 | if ((contract as any)?.address && !(contract as any)?.methods) { 70 | let res = await (contract as ethers.Contract).compose(idList, name, time, position, zIndex, decode) 71 | return res as TransactionResponse 72 | } else { 73 | let res = await (contract as Contract).methods.compose(idList, name, time, position, zIndex, decode).send({ from: contract.sendAccount }) 74 | return res as TransactionReceipt 75 | } 76 | } 77 | } 78 | 79 | //view 80 | export const dataOwner = { 81 | name: "dataOwner", 82 | contract: "PixelsMetaverse", 83 | fun: async ( 84 | contract: TContract, 85 | arg?: { 86 | bytes32Params1: string; //bytes32 87 | } 88 | ) => { 89 | if (!arg) return 90 | const { bytes32Params1 } = arg; 91 | if ((contract as any)?.address && !(contract as any)?.methods) { 92 | let res = await (contract as ethers.Contract).dataOwner(bytes32Params1) 93 | return convertedBigNumber(res) as string; //address 94 | } else { 95 | let res = await (contract as Contract).methods.dataOwner(bytes32Params1).call() 96 | return res as string; //address 97 | } 98 | } 99 | } 100 | 101 | //nonpayable 102 | export const handleTransfer = { 103 | name: "handleTransfer", 104 | contract: "PixelsMetaverse", 105 | fun: async ( 106 | contract: TContract, 107 | arg?: { 108 | from: string; //address 109 | to: string; //address 110 | id: string | number; //uint256 111 | } 112 | ) => { 113 | if (!arg) return 114 | const { from, to, id } = arg; 115 | if ((contract as any)?.address && !(contract as any)?.methods) { 116 | let res = await (contract as ethers.Contract).handleTransfer(from, to, id) 117 | return res as TransactionResponse 118 | } else { 119 | let res = await (contract as Contract).methods.handleTransfer(from, to, id).send({ from: contract.sendAccount }) 120 | return res as TransactionReceipt 121 | } 122 | } 123 | } 124 | 125 | //nonpayable 126 | export const make = { 127 | name: "make", 128 | contract: "PixelsMetaverse", 129 | fun: async ( 130 | contract: TContract, 131 | arg?: { 132 | name: string; //string 133 | rawData: string; //string 134 | time: string; //string 135 | position: string; //string 136 | zIndex: string; //string 137 | decode: string; //string 138 | num: string | number; //uint256 139 | } 140 | ) => { 141 | if (!arg) return 142 | const { name, rawData, time, position, zIndex, decode, num } = arg; 143 | if ((contract as any)?.address && !(contract as any)?.methods) { 144 | let res = await (contract as ethers.Contract).make(name, rawData, time, position, zIndex, decode, num) 145 | return res as TransactionResponse 146 | } else { 147 | let res = await (contract as Contract).methods.make(name, rawData, time, position, zIndex, decode, num).send({ from: contract.sendAccount }) 148 | return res as TransactionReceipt 149 | } 150 | } 151 | } 152 | 153 | //view 154 | export const material = { 155 | name: "material", 156 | contract: "PixelsMetaverse", 157 | fun: async ( 158 | contract: TContract, 159 | arg?: { 160 | uint256Params1: string | number; //uint256 161 | } 162 | ) => { 163 | if (!arg) return 164 | const { uint256Params1 } = arg; 165 | if ((contract as any)?.address && !(contract as any)?.methods) { 166 | let res = await (contract as ethers.Contract).material(uint256Params1) 167 | return convertedBigNumber(res) as { 168 | composed: string; //uint256 169 | dataBytes: string; //bytes32 170 | remake: string; //bool 171 | } 172 | } else { 173 | let res = await (contract as Contract).methods.material(uint256Params1).call() 174 | return res as { 175 | composed: string; //uint256 176 | dataBytes: string; //bytes32 177 | remake: string; //bool 178 | } 179 | } 180 | } 181 | } 182 | 183 | //nonpayable 184 | export const reMake = { 185 | name: "reMake", 186 | contract: "PixelsMetaverse", 187 | fun: async ( 188 | contract: TContract, 189 | arg?: { 190 | id: string | number; //uint256 191 | num: string | number; //uint256 192 | } 193 | ) => { 194 | if (!arg) return 195 | const { id, num } = arg; 196 | if ((contract as any)?.address && !(contract as any)?.methods) { 197 | let res = await (contract as ethers.Contract).reMake(id, num) 198 | return res as TransactionResponse 199 | } else { 200 | let res = await (contract as Contract).methods.reMake(id, num).send({ from: contract.sendAccount }) 201 | return res as TransactionReceipt 202 | } 203 | } 204 | } 205 | 206 | //nonpayable 207 | export const setAvater = { 208 | name: "setAvater", 209 | contract: "PixelsMetaverse", 210 | fun: async ( 211 | contract: TContract, 212 | arg?: { 213 | id: string | number; //uint256 214 | } 215 | ) => { 216 | if (!arg) return 217 | const { id } = arg; 218 | if ((contract as any)?.address && !(contract as any)?.methods) { 219 | let res = await (contract as ethers.Contract).setAvater(id) 220 | return res as TransactionResponse 221 | } else { 222 | let res = await (contract as Contract).methods.setAvater(id).send({ from: contract.sendAccount }) 223 | return res as TransactionReceipt 224 | } 225 | } 226 | } 227 | 228 | //nonpayable 229 | export const setConfig = { 230 | name: "setConfig", 231 | contract: "PixelsMetaverse", 232 | fun: async ( 233 | contract: TContract, 234 | arg?: { 235 | id: string | number; //uint256 236 | name: string; //string 237 | time: string; //string 238 | position: string; //string 239 | zIndex: string; //string 240 | decode: string; //string 241 | sort: string | number; //uint256 242 | } 243 | ) => { 244 | if (!arg) return 245 | const { id, name, time, position, zIndex, decode, sort } = arg; 246 | if ((contract as any)?.address && !(contract as any)?.methods) { 247 | let res = await (contract as ethers.Contract).setConfig(id, name, time, position, zIndex, decode, sort) 248 | return res as TransactionResponse 249 | } else { 250 | let res = await (contract as Contract).methods.setConfig(id, name, time, position, zIndex, decode, sort).send({ from: contract.sendAccount }) 251 | return res as TransactionReceipt 252 | } 253 | } 254 | } 255 | 256 | //nonpayable 257 | export const setDataOwner = { 258 | name: "setDataOwner", 259 | contract: "PixelsMetaverse", 260 | fun: async ( 261 | contract: TContract, 262 | arg?: { 263 | dataBytes: string; //bytes32 264 | to: string; //address 265 | } 266 | ) => { 267 | if (!arg) return 268 | const { dataBytes, to } = arg; 269 | if ((contract as any)?.address && !(contract as any)?.methods) { 270 | let res = await (contract as ethers.Contract).setDataOwner(dataBytes, to) 271 | return res as TransactionResponse 272 | } else { 273 | let res = await (contract as Contract).methods.setDataOwner(dataBytes, to).send({ from: contract.sendAccount }) 274 | return res as TransactionReceipt 275 | } 276 | } 277 | } 278 | 279 | //nonpayable 280 | export const subtract = { 281 | name: "subtract", 282 | contract: "PixelsMetaverse", 283 | fun: async ( 284 | contract: TContract, 285 | arg?: { 286 | ids: string | number; //uint256 287 | idList: (string | number)[]; //uint256[] 288 | } 289 | ) => { 290 | if (!arg) return 291 | const { ids, idList } = arg; 292 | if ((contract as any)?.address && !(contract as any)?.methods) { 293 | let res = await (contract as ethers.Contract).subtract(ids, idList) 294 | return res as TransactionResponse 295 | } else { 296 | let res = await (contract as Contract).methods.subtract(ids, idList).send({ from: contract.sendAccount }) 297 | return res as TransactionReceipt 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /packages/react-app/src/pages/produced/components/Submit.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, InputHTMLAttributes, ReactNode, useCallback, useMemo, useState } from 'react'; 2 | import { Select, message, Modal, Button } from 'antd'; 3 | import { Dictionary, keys, map } from 'lodash'; 4 | import { useUserInfo } from '../../../components/UserProvider'; 5 | import { usePixelsMetaverseHandleImg } from '../../../pixels-metaverse'; 6 | import React from 'react'; 7 | import { make } from '../../../client/PixelsMetaverse'; 8 | import { useWeb3Info, useRequest, useContractRequest } from 'abi-to-request'; 9 | import { useLoading } from '../../../components/Loading'; 10 | const { Option } = Select; 11 | 12 | export const Label = ({ children, noNeed }: { children: ReactNode, noNeed?: boolean }) => { 13 | return
{children}{!noNeed && *}
14 | } 15 | 16 | export const Input = (props: InputHTMLAttributes) => { 17 | return ( 18 | 23 | ) 24 | } 25 | 26 | export const mustNum = (e: ChangeEvent) => { 27 | const val = Number(e?.target?.value); 28 | if (Number(e.target.value) >= 0 && !isNaN(val)) { 29 | return e.target.value 30 | } 31 | if (isNaN(parseFloat(e.target.value))) { 32 | return "" 33 | } 34 | return `${parseFloat(e.target.value)}` 35 | } 36 | 37 | export const categoryData = [ 38 | { 39 | label: "脸", 40 | value: "face" 41 | }, { 42 | label: "头发", 43 | value: "hair" 44 | }, { 45 | label: "眼睛", 46 | value: "eye" 47 | }, { 48 | label: "鼻子", 49 | value: "nose" 50 | }, { 51 | label: "嘴巴", 52 | value: "mouth" 53 | }, { 54 | label: "耳朵", 55 | value: "ear" 56 | }, { 57 | label: "脖子", 58 | value: "neck" 59 | }, { 60 | label: "胡子", 61 | value: "beard" 62 | }, { 63 | label: "饰品", 64 | value: "accessories" 65 | }, { 66 | label: "其他", 67 | value: "other" 68 | } 69 | ] 70 | 71 | export interface IMerchandise { 72 | name: string; 73 | num: string; 74 | data?: string; 75 | decode?: string; 76 | time?: string; 77 | position?: string; 78 | zIndex?: string; 79 | } 80 | 81 | export const Submit = () => { 82 | const { positionsArr, setPositionsArr, config, dealClick: { value, clear } } = usePixelsMetaverseHandleImg() 83 | const [{ 84 | name, 85 | num, 86 | decode, 87 | time, 88 | position, 89 | zIndex 90 | }, setMerchandies] = React.useState({ 91 | name: "", 92 | num: "", 93 | decode: "", 94 | time: "", 95 | position: "", 96 | zIndex: "", 97 | }) 98 | const [positionData, setPostionData] = useState("") 99 | const [isModalVisible, setIsModalVisible] = useState(false); 100 | const { address: addresss } = useWeb3Info() 101 | const { setSmallLoading } = useUserInfo() 102 | const { openLoading, closeDelayLoading } = useLoading() 103 | const address = addresss 104 | const { contracts } = useContractRequest() 105 | 106 | const [makeFun] = useRequest(make, { 107 | onSuccessBefore: openLoading, 108 | onSuccess: ()=>{ 109 | setSmallLoading(true) 110 | closeDelayLoading(); 111 | }, 112 | onTransactionSuccess: () => { 113 | message.success("物品制造成功!正在获取链上新数据..."); 114 | setMerchandies({ name: "", num: "", }) 115 | clear() 116 | setPositionsArr([]) 117 | }, 118 | onFail: () => { 119 | setIsModalVisible(false) 120 | } 121 | }, [ 122 | address, 123 | name, 124 | num, 125 | decode, 126 | position, 127 | time, 128 | zIndex 129 | ]) 130 | 131 | const min = useMemo(() => Math.min(...positionsArr), [positionsArr]) 132 | 133 | const handleOk = useCallback(() => { 134 | const rawData = `${positionData}${min}`; 135 | makeFun({ name, num, rawData, decode: "", position: "", zIndex: "", time: "" }); 136 | setIsModalVisible(false); 137 | }, [positionData, min, name, num]); 138 | 139 | const handleCancel = () => { 140 | setIsModalVisible(false); 141 | }; 142 | 143 | const colorsObj = useMemo(() => { 144 | const colors: Dictionary = {} 145 | map(positionsArr, item => { 146 | colors[value[item]] ? colors[value[item]].push(item) : colors[value[item]] = [item] 147 | }) 148 | return colors 149 | }, [value, positionsArr]) 150 | 151 | const getPositionStr = useCallback(() => { 152 | let str = "" 153 | let min = Math.min(...positionsArr) 154 | const colorsArrBySort = keys(colorsObj).sort((a, b) => parseInt(a.slice(1), 16) - parseInt(b.slice(1), 16)) 155 | for (let i = 0; i < colorsArrBySort.length; i++) { 156 | //再对颜色排个序 小的放前面 157 | const position = map(colorsObj[colorsArrBySort[i]], ite => (ite - min).toString(36)).join("|") 158 | str += `${parseInt(colorsArrBySort[i].slice(1), 16).toString(36)}-${position}-` 159 | } 160 | return `${str}` 161 | }, [value, positionsArr, colorsObj, min]) 162 | 163 | const checkData = useCallback(() => { 164 | if (!name) { 165 | message.warn("请输入物品名称"); 166 | return; 167 | } 168 | if (!num) { 169 | message.warn("请输入物品数量"); 170 | return; 171 | } 172 | return true; 173 | }, [name, num, decode, position, time, zIndex]); 174 | 175 | return ( 176 |
177 |
制作虚拟物品  178 | {/* 179 | 180 | */} 181 |
182 |
183 | 184 | setMerchandies((pre) => ({ ...pre, name: e.target.value }))} /> 185 | {/* 186 | */} 201 | 202 | setMerchandies((pre) => ({ ...pre, num: mustNum(e) }))} /> 203 | {/* 204 | setMerchandies((pre) => ({ ...pre, num: mustNum(e) }))} /> 205 |
206 |
层级
207 | 208 | 209 | 210 |
211 | setMerchandies((pre) => ({ ...pre, num: mustNum(e) }))} /> 212 |
213 |
位置
214 | 215 | 216 | 217 |
218 | setMerchandies((pre) => ({ ...pre, weight: mustNum(e) }))} /> 219 |
220 |
解码方式
221 | 222 | 223 | 224 |
225 | setMerchandies((pre) => ({ ...pre, weight: mustNum(e) }))} /> 226 | */} 227 | {/* {
你还不是宇宙创始居民,请 228 | { 229 | setAvater({ 230 | id: 2 231 | }) 232 | }}>激活自己的元宇宙身份!
} */} 233 | 249 |
250 | 251 | = 64 ? "不用担心,硬核提交" : "确认"} 254 | cancelText="取消" 255 | visible={isModalVisible} 256 | onOk={handleOk} 257 | onCancel={handleCancel} 258 | > 259 |

是否确认发布该物品?

260 |

{positionData?.length >= 50 && "当前数据量较大,可能消耗的GAS较多,且有可能提交不成功,请问是否继续提交数据?"}

261 |
262 |
263 | ); 264 | }; -------------------------------------------------------------------------------- /packages/subgraph/abis/localhost_PMT721.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "anonymous": false, 9 | "inputs": [ 10 | { 11 | "indexed": true, 12 | "internalType": "address", 13 | "name": "owner", 14 | "type": "address" 15 | }, 16 | { 17 | "indexed": true, 18 | "internalType": "address", 19 | "name": "approved", 20 | "type": "address" 21 | }, 22 | { 23 | "indexed": true, 24 | "internalType": "uint256", 25 | "name": "tokenId", 26 | "type": "uint256" 27 | } 28 | ], 29 | "name": "Approval", 30 | "type": "event" 31 | }, 32 | { 33 | "anonymous": false, 34 | "inputs": [ 35 | { 36 | "indexed": true, 37 | "internalType": "address", 38 | "name": "owner", 39 | "type": "address" 40 | }, 41 | { 42 | "indexed": true, 43 | "internalType": "address", 44 | "name": "operator", 45 | "type": "address" 46 | }, 47 | { 48 | "indexed": false, 49 | "internalType": "bool", 50 | "name": "approved", 51 | "type": "bool" 52 | } 53 | ], 54 | "name": "ApprovalForAll", 55 | "type": "event" 56 | }, 57 | { 58 | "anonymous": false, 59 | "inputs": [ 60 | { 61 | "indexed": true, 62 | "internalType": "address", 63 | "name": "from", 64 | "type": "address" 65 | }, 66 | { 67 | "indexed": true, 68 | "internalType": "address", 69 | "name": "to", 70 | "type": "address" 71 | }, 72 | { 73 | "indexed": true, 74 | "internalType": "uint256", 75 | "name": "tokenId", 76 | "type": "uint256" 77 | } 78 | ], 79 | "name": "Transfer", 80 | "type": "event" 81 | }, 82 | { 83 | "inputs": [ 84 | { 85 | "internalType": "address", 86 | "name": "to", 87 | "type": "address" 88 | }, 89 | { 90 | "internalType": "uint256", 91 | "name": "tokenId", 92 | "type": "uint256" 93 | } 94 | ], 95 | "name": "approve", 96 | "outputs": [], 97 | "stateMutability": "nonpayable", 98 | "type": "function" 99 | }, 100 | { 101 | "inputs": [ 102 | { 103 | "internalType": "address", 104 | "name": "owner", 105 | "type": "address" 106 | } 107 | ], 108 | "name": "balanceOf", 109 | "outputs": [ 110 | { 111 | "internalType": "uint256", 112 | "name": "", 113 | "type": "uint256" 114 | } 115 | ], 116 | "stateMutability": "view", 117 | "type": "function" 118 | }, 119 | { 120 | "inputs": [ 121 | { 122 | "internalType": "uint256", 123 | "name": "id", 124 | "type": "uint256" 125 | } 126 | ], 127 | "name": "burn", 128 | "outputs": [], 129 | "stateMutability": "nonpayable", 130 | "type": "function" 131 | }, 132 | { 133 | "inputs": [], 134 | "name": "currentID", 135 | "outputs": [ 136 | { 137 | "internalType": "uint256", 138 | "name": "", 139 | "type": "uint256" 140 | } 141 | ], 142 | "stateMutability": "view", 143 | "type": "function" 144 | }, 145 | { 146 | "inputs": [ 147 | { 148 | "internalType": "uint256", 149 | "name": "id", 150 | "type": "uint256" 151 | } 152 | ], 153 | "name": "exits", 154 | "outputs": [ 155 | { 156 | "internalType": "bool", 157 | "name": "", 158 | "type": "bool" 159 | } 160 | ], 161 | "stateMutability": "view", 162 | "type": "function" 163 | }, 164 | { 165 | "inputs": [ 166 | { 167 | "internalType": "uint256", 168 | "name": "tokenId", 169 | "type": "uint256" 170 | } 171 | ], 172 | "name": "getApproved", 173 | "outputs": [ 174 | { 175 | "internalType": "address", 176 | "name": "", 177 | "type": "address" 178 | } 179 | ], 180 | "stateMutability": "view", 181 | "type": "function" 182 | }, 183 | { 184 | "inputs": [ 185 | { 186 | "internalType": "address", 187 | "name": "owner", 188 | "type": "address" 189 | }, 190 | { 191 | "internalType": "address", 192 | "name": "operator", 193 | "type": "address" 194 | } 195 | ], 196 | "name": "isApprovedForAll", 197 | "outputs": [ 198 | { 199 | "internalType": "bool", 200 | "name": "", 201 | "type": "bool" 202 | } 203 | ], 204 | "stateMutability": "view", 205 | "type": "function" 206 | }, 207 | { 208 | "inputs": [ 209 | { 210 | "internalType": "address", 211 | "name": "to", 212 | "type": "address" 213 | } 214 | ], 215 | "name": "mint", 216 | "outputs": [], 217 | "stateMutability": "nonpayable", 218 | "type": "function" 219 | }, 220 | { 221 | "inputs": [], 222 | "name": "minter", 223 | "outputs": [ 224 | { 225 | "internalType": "address", 226 | "name": "", 227 | "type": "address" 228 | } 229 | ], 230 | "stateMutability": "view", 231 | "type": "function" 232 | }, 233 | { 234 | "inputs": [], 235 | "name": "name", 236 | "outputs": [ 237 | { 238 | "internalType": "string", 239 | "name": "", 240 | "type": "string" 241 | } 242 | ], 243 | "stateMutability": "view", 244 | "type": "function" 245 | }, 246 | { 247 | "inputs": [ 248 | { 249 | "internalType": "uint256", 250 | "name": "tokenId", 251 | "type": "uint256" 252 | } 253 | ], 254 | "name": "ownerOf", 255 | "outputs": [ 256 | { 257 | "internalType": "address", 258 | "name": "", 259 | "type": "address" 260 | } 261 | ], 262 | "stateMutability": "view", 263 | "type": "function" 264 | }, 265 | { 266 | "inputs": [ 267 | { 268 | "internalType": "address", 269 | "name": "from", 270 | "type": "address" 271 | }, 272 | { 273 | "internalType": "address", 274 | "name": "to", 275 | "type": "address" 276 | }, 277 | { 278 | "internalType": "uint256", 279 | "name": "tokenId", 280 | "type": "uint256" 281 | } 282 | ], 283 | "name": "safeTransferFrom", 284 | "outputs": [], 285 | "stateMutability": "nonpayable", 286 | "type": "function" 287 | }, 288 | { 289 | "inputs": [ 290 | { 291 | "internalType": "address", 292 | "name": "from", 293 | "type": "address" 294 | }, 295 | { 296 | "internalType": "address", 297 | "name": "to", 298 | "type": "address" 299 | }, 300 | { 301 | "internalType": "uint256", 302 | "name": "tokenId", 303 | "type": "uint256" 304 | }, 305 | { 306 | "internalType": "bytes", 307 | "name": "_data", 308 | "type": "bytes" 309 | } 310 | ], 311 | "name": "safeTransferFrom", 312 | "outputs": [], 313 | "stateMutability": "nonpayable", 314 | "type": "function" 315 | }, 316 | { 317 | "inputs": [ 318 | { 319 | "internalType": "address", 320 | "name": "operator", 321 | "type": "address" 322 | }, 323 | { 324 | "internalType": "bool", 325 | "name": "approved", 326 | "type": "bool" 327 | } 328 | ], 329 | "name": "setApprovalForAll", 330 | "outputs": [], 331 | "stateMutability": "nonpayable", 332 | "type": "function" 333 | }, 334 | { 335 | "inputs": [ 336 | { 337 | "internalType": "address", 338 | "name": "_minter", 339 | "type": "address" 340 | } 341 | ], 342 | "name": "setMinter", 343 | "outputs": [], 344 | "stateMutability": "nonpayable", 345 | "type": "function" 346 | }, 347 | { 348 | "inputs": [ 349 | { 350 | "internalType": "address", 351 | "name": "owner", 352 | "type": "address" 353 | } 354 | ], 355 | "name": "setOwner", 356 | "outputs": [], 357 | "stateMutability": "nonpayable", 358 | "type": "function" 359 | }, 360 | { 361 | "inputs": [ 362 | { 363 | "internalType": "bytes4", 364 | "name": "interfaceId", 365 | "type": "bytes4" 366 | } 367 | ], 368 | "name": "supportsInterface", 369 | "outputs": [ 370 | { 371 | "internalType": "bool", 372 | "name": "", 373 | "type": "bool" 374 | } 375 | ], 376 | "stateMutability": "view", 377 | "type": "function" 378 | }, 379 | { 380 | "inputs": [], 381 | "name": "symbol", 382 | "outputs": [ 383 | { 384 | "internalType": "string", 385 | "name": "", 386 | "type": "string" 387 | } 388 | ], 389 | "stateMutability": "view", 390 | "type": "function" 391 | }, 392 | { 393 | "inputs": [ 394 | { 395 | "internalType": "uint256", 396 | "name": "tokenId", 397 | "type": "uint256" 398 | } 399 | ], 400 | "name": "tokenURI", 401 | "outputs": [ 402 | { 403 | "internalType": "string", 404 | "name": "", 405 | "type": "string" 406 | } 407 | ], 408 | "stateMutability": "view", 409 | "type": "function" 410 | }, 411 | { 412 | "inputs": [ 413 | { 414 | "internalType": "address", 415 | "name": "from", 416 | "type": "address" 417 | }, 418 | { 419 | "internalType": "address", 420 | "name": "to", 421 | "type": "address" 422 | }, 423 | { 424 | "internalType": "uint256", 425 | "name": "tokenId", 426 | "type": "uint256" 427 | } 428 | ], 429 | "name": "transferFrom", 430 | "outputs": [], 431 | "stateMutability": "nonpayable", 432 | "type": "function" 433 | } 434 | ] --------------------------------------------------------------------------------