added sepolia and scroll 0fce240d
Steve · 2023-10-14 13:41 8 file(s) · +248 −71
README.md +36 −9
1 -
# Sample Hardhat Project
1 +
# Cosmic Cowboys Contracts
2 +
3 +
This repo contains the Solidity smart contracts for Cosmic Cowboys, a dynamic on-chain game that gives life to NPC using ERC-6551 and AI. 
4 +
5 +
The project consists of several elements: 
6 +
- A primary key that can deploy / own the contracts 
7 +
- An altered ERC-721 where each NFT is an NPC. The additions to the contract include on-chain state like health and location.
8 +
- ERC-20 Contract for the in game currency
9 +
- Two ERC-1155 Contracts for in game supplies and items 
10 +
- A custom "Operator" contract that abstracts the other contracts and allows on chain dynamics.
11 +
- A custom implementation of ERC-6551 for deploying to chains that may not have an implementation address, like Scroll. 
12 +
13 +
With this repo we deploy all the previously mentioned contracts and mint 20 NPC characters, give each their own Tokenbound Account, and mint currency and items to those accounts. The end result will also give env variables that can be used for the front end portion of the app. 
14 +
15 +
# Deploying
16 +
17 +
Fist clone this repo and install dependences
18 +
```
19 +
git clone https://github.com/stevedylandev/cosmic-cowboys-contracts && cd cosmic-cowboys-contracts && npm install
20 +
```
2 21
3 -
This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract.
22 +
To deploy the contracts you can create a `.env` file with the following variables:
4 23
5 -
Try running some of the following tasks:
24 +
```
25 +
PRIVATE_KEY=
26 +
ALCHEMY_URL=
27 +
ALCHEMY_URL_BASE=
28 +
ALCHEMY_KEY=
29 +
ALCHEMY_KEY_BASE=
30 +
SEPOLIA_URL=
31 +
SEPOLIA_KEY=
32 +
ETHERSCAN_API_KEY=
33 +
```
34 +
Depending what networks you want to deploy to you can alter those variables. 
6 35
7 -
```shell
8 -
npx hardhat help
9 -
npx hardhat test
10 -
REPORT_GAS=true npx hardhat test
11 -
npx hardhat node
12 -
npx hardhat run scripts/deploy.js
36 +
Once ready you can deploy with your choice of network like goerli
37 +
```
38 +
npx hardhat run scripts/deployContracts.js --network goerli
13 39
```
40 +
Once you run this it will deploy all contracts as well as setup the NPCs for the game. 
contracts/Operator.sol +9 −4
58 58
        return (health, location);
59 59
    }
60 60
61 -
    function goToHome(uint256 tokenId) public {
62 -
        //require(cosmicCowboys.ownerOf(tokenId) == tba, "Not owner"); // Check OwnershipTransferred
63 -
        cosmicCowboys.goToHome(tokenId);
64 -
        uint8 newHealth = cosmicCowboys.getHealth(tokenId) + 2;
61 +
    function launchSupplyMission(uint256 tokenId) public {
62 +
        uint8 newHealth = cosmicCowboys.getHealth(tokenId) - 2;
65 63
        cosmicCowboys.setHealth(tokenId, newHealth);
66 64
    }
67 65
71 69
72 70
    function goToSupplyDepot(uint256 tokenId) public {
73 71
        cosmicCowboys.goToSupplyDepot(tokenId);
72 +
    }
73 +
74 +
    function goToHome(uint256 tokenId) public {
75 +
        //require(cosmicCowboys.ownerOf(tokenId) == tba, "Not owner"); // Check OwnershipTransferred
76 +
        cosmicCowboys.goToHome(tokenId);
77 +
        uint8 newHealth = cosmicCowboys.getHealth(tokenId) + 2;
78 +
        cosmicCowboys.setHealth(tokenId, newHealth);
74 79
    }
75 80
76 81
    function getOwner() external view returns (address) {
hardhat.config.js +2 −0
13 13
      url: "https://sepolia-rpc.scroll.io/" || "",
14 14
      accounts:
15 15
        process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
16 +
      gasPrice: 1500000000,
17 +
16 18
    },
17 19
    'base-goerli': {
18 20
      url: `${process.env.ALCHEMY_URL_BASE}`,
package-lock.json +3 −19
8 8
      "dependencies": {
9 9
        "@openzeppelin/contracts": "^5.0.0",
10 10
        "@tokenbound/sdk": "^0.3.12",
11 +
        "axios": "^1.5.1",
11 12
        "dotenv": "^16.3.1",
12 13
        "ethers": "^6.7.1"
13 14
      },
2314 2315
    "node_modules/asynckit": {
2315 2316
      "version": "0.4.0",
2316 2317
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
2317 -
      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
2318 -
      "dev": true,
2319 -
      "peer": true
2318 +
      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
2320 2319
    },
2321 2320
    "node_modules/at-least-node": {
2322 2321
      "version": "1.0.0",
2332 2331
      "version": "1.5.1",
2333 2332
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
2334 2333
      "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
2335 -
      "dev": true,
2336 -
      "peer": true,
2337 2334
      "dependencies": {
2338 2335
        "follow-redirects": "^1.15.0",
2339 2336
        "form-data": "^4.0.0",
2817 2814
      "version": "1.0.8",
2818 2815
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
2819 2816
      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
2820 -
      "dev": true,
2821 -
      "peer": true,
2822 2817
      "dependencies": {
2823 2818
        "delayed-stream": "~1.0.0"
2824 2819
      },
3087 3082
      "version": "1.0.0",
3088 3083
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
3089 3084
      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
3090 -
      "dev": true,
3091 -
      "peer": true,
3092 3085
      "engines": {
3093 3086
        "node": ">=0.4.0"
3094 3087
      }
3702 3695
      "version": "1.15.3",
3703 3696
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz",
3704 3697
      "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==",
3705 -
      "dev": true,
3706 3698
      "funding": [
3707 3699
        {
3708 3700
          "type": "individual",
3722 3714
      "version": "4.0.0",
3723 3715
      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
3724 3716
      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
3725 -
      "dev": true,
3726 -
      "peer": true,
3727 3717
      "dependencies": {
3728 3718
        "asynckit": "^0.4.0",
3729 3719
        "combined-stream": "^1.0.8",
4952 4942
      "version": "1.52.0",
4953 4943
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
4954 4944
      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
4955 -
      "dev": true,
4956 -
      "peer": true,
4957 4945
      "engines": {
4958 4946
        "node": ">= 0.6"
4959 4947
      }
4962 4950
      "version": "2.1.35",
4963 4951
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
4964 4952
      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
4965 -
      "dev": true,
4966 -
      "peer": true,
4967 4953
      "dependencies": {
4968 4954
        "mime-db": "1.52.0"
4969 4955
      },
5581 5567
    "node_modules/proxy-from-env": {
5582 5568
      "version": "1.1.0",
5583 5569
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
5584 -
      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
5585 -
      "dev": true,
5586 -
      "peer": true
5570 +
      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
5587 5571
    },
5588 5572
    "node_modules/punycode": {
5589 5573
      "version": "2.3.0",
package.json +1 −0
9 9
  "dependencies": {
10 10
    "@openzeppelin/contracts": "^5.0.0",
11 11
    "@tokenbound/sdk": "^0.3.12",
12 +
    "axios": "^1.5.1",
12 13
    "dotenv": "^16.3.1",
13 14
    "ethers": "^6.7.1"
14 15
  }
scripts/deployContractsScroll.js (added) +103 −0
1 +
// Import the ethers library
2 +
const { ethers } = require("hardhat");
3 +
const axios = require('axios')
4 +
//const provider = new ethers.AlchemyProvider("sepolia", process.env.SEPOLIA_KEY)
5 +
const provider = new ethers.getDefaultProvider("https://rpc.scroll.io")
6 +
const { TokenboundClient } = require("@tokenbound/sdk");
7 +
8 +
async function Main() {
9 +
  const feeData = await provider.getFeeData()
10 +
11 +
  // Get the signers from ethers
12 +
  const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider)
13 +
  console.log(`SERVER_WALLET_PRIVATE_KEY=${process.env.PRIVATE_KEY}`)
14 +
  console.log(`SERVER_WALLET_ADDRESS=${wallet.address}`)
15 +
16 +
  // Deploy NPC contract
17 +
  const NPCContract = await ethers.getContractFactory("CosmicCowboys");
18 +
  const npcContract = await NPCContract.deploy(wallet.address, { gasPrice: feeData.gasPrice })
19 +
  const npcContractAddress = await npcContract.getAddress()
20 +
  console.log(`NPC_CONTRACT_ADDRESS=${npcContractAddress}`);
21 +
22 +
  // Deploy ERC-20 Contract
23 +
  const CurrencyContract = await ethers.getContractFactory("GoldenCorn");
24 +
  const currencyContract = await CurrencyContract.deploy(wallet.address, { gasPrice: feeData.gasPrice })
25 +
  const currencyContractAddress = await currencyContract.getAddress()
26 +
  console.log(`CURRENCY_CONTRACT_ADDRESS=${currencyContractAddress}`)
27 +
28 +
  // Deploy 1155 Contracts
29 +
  const FoodContract = await ethers.getContractFactory("SpaceSlop");
30 +
  const foodContract = await FoodContract.deploy(wallet.address, { gasPrice: feeData.gasPrice })
31 +
  const foodContractAddress = await foodContract.getAddress()
32 +
  console.log(`FOOD_CONTRACT_ADDRESS=${foodContractAddress}`);
33 +
34 +
  const SupplyContract = await ethers.getContractFactory("JupiterJunk");
35 +
  const supplyContract = await SupplyContract.deploy(wallet.address, { gasPrice: feeData.gasPrice })
36 +
  const supplyContractAddress = await supplyContract.getAddress()
37 +
  console.log(`SUPPLY_CONTRACT_ADDRESS=${supplyContractAddress}`);
38 +
39 +
  // Deploy ERC6551
40 +
  const RegistryContract = await ethers.getContractFactory("ERC6551Registry");
41 +
  const registryContract = await RegistryContract.deploy({ gasPrice: feeData.gasPrice })
42 +
  const registryContractAddress = await registryContract.getAddress()
43 +
  console.log("Registry Contract deployed to address:", registryContractAddress);
44 +
45 +
  const AccountContract = await ethers.getContractFactory("ERC6551Account");
46 +
  const accountContract = await AccountContract.deploy({ gasPrice: feeData.gasPrice })
47 +
  const accountContractAddress = await accountContract.getAddress()
48 +
  console.log("Account Contract deployed to address:", accountContractAddress);
49 +
50 +
  /* const tokenboundClient = new TokenboundClient({
51 +
    walletClient: wallet,
52 +
    chainId: 534352,
53 +
    implementationAddress: accountContractAddress,
54 +
    registryAddress: registryContractAddress,
55 +
  })
56 +
  // Deploy Operator Contract
57 +
  const OperatorContract = await ethers.getContractFactory("Operator");
58 +
  const operatorContract = await OperatorContract.deploy(wallet.address, npcContractAddress, currencyContractAddress, foodContractAddress, supplyContractAddress)
59 +
  const operatorContractAddress = await operatorContract.getAddress()
60 +
  console.log(`OPERATOR_CONTRACT_ADDRESS=${operatorContractAddress}`)
61 +
62 +
  // Transfer NPC contract to Operator
63 +
  await npcContract.transferOwnership(operatorContractAddress);
64 +
  await currencyContract.transferOwnership(operatorContractAddress);
65 +
  await foodContract.transferOwnership(operatorContractAddress);
66 +
  await supplyContract.transferOwnership(operatorContractAddress);
67 +
68 +
  for (let i = 0; i < 19; i++) {
69 +
    // create NPC
70 +
    const npcTx = await operatorContract.createNPC(wallet.address, `ipfs://QmQbwCMwDETHHZ1g8YaSHqLBwCRgVHqFuRNRfiGyNqCcXj/${i}.json`)
71 +
    const npcTxReceipt = await npcTx.wait()
72 +
    console.log("NPC Created")
73 +
74 +
    // After the NPC is created
75 +
    const latestTokenId = await operatorContract.getLatestTokenId();
76 +
77 +
    // create TBA for NPC
78 +
    const tba = await tokenboundClient.createAccount({
79 +
      tokenContract: npcContractAddress,
80 +
      tokenId: latestTokenId,
81 +
    })
82 +
    console.log("TBA:", tba)
83 +
84 +
    // equip NPC via TBA
85 +
    //
86 +
    const fundNpcTx = await operatorContract.fundNPC(tba, 20)
87 +
    const fundNpxTxReceipt = await fundNpcTx.wait()
88 +
    console.log("NPC Funded")
89 +
90 +
    const feedNpcTx = await operatorContract.feedNPC(tba, 5)
91 +
    const feedNpcTxReceipt = await feedNpcTx.wait()
92 +
    console.log("NPC Fed")
93 +
94 +
    const supplyNpcTx = await operatorContract.supplyNPC(tba, 5)
95 +
    const supplyNpcTxReceipt = await supplyNpcTx.wait()
96 +
    console.log("NPC Supplied") 
97 +
98 +
    } */
99 +
100 +
101 +
}
102 +
103 +
Main()
scripts/deployContractsSepolia.js (added) +94 −0
1 +
// Import the ethers library
2 +
const { ethers } = require("hardhat");
3 +
//const provider = new ethers.AlchemyProvider("sepolia", process.env.SEPOLIA_KEY)
4 +
const provider = new ethers.getDefaultProvider("wss://ethereum-sepolia.publicnode.com")
5 +
const { TokenboundClient } = require("@tokenbound/sdk");
6 +
7 +
async function Main() {
8 +
9 +
  // Get the signers from ethers
10 +
  const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider)
11 +
  const tokenboundClient = new TokenboundClient({ signer: wallet, chainId: 11155111 })
12 +
  console.log(`SERVER_WALLET_PRIVATE_KEY=${process.env.PRIVATE_KEY}`)
13 +
  console.log(`SERVER_WALLET_ADDRESS=${wallet.address}`)
14 +
15 +
  // Deploy NPC contract
16 +
  const NPCContract = await ethers.getContractFactory("CosmicCowboys");
17 +
  const npcContract = await NPCContract.deploy(wallet.address);
18 +
  const npcContractAddress = await npcContract.getAddress()
19 +
  console.log(`NPC_CONTRACT_ADDRESS=${npcContractAddress}`);
20 +
21 +
  // Deploy ERC-20 Contract
22 +
  const CurrencyContract = await ethers.getContractFactory("GoldenCorn");
23 +
  const currencyContract = await CurrencyContract.deploy(wallet.address);
24 +
  const currencyContractAddress = await currencyContract.getAddress()
25 +
  console.log(`CURRENCY_CONTRACT_ADDRESS=${currencyContractAddress}`)
26 +
27 +
  // Deploy 1155 Contracts
28 +
  const FoodContract = await ethers.getContractFactory("SpaceSlop");
29 +
  const foodContract = await FoodContract.deploy(wallet.address);
30 +
  const foodContractAddress = await foodContract.getAddress()
31 +
  console.log(`FOOD_CONTRACT_ADDRESS=${foodContractAddress}`);
32 +
33 +
  const SupplyContract = await ethers.getContractFactory("JupiterJunk");
34 +
  const supplyContract = await SupplyContract.deploy(wallet.address);
35 +
  const supplyContractAddress = await supplyContract.getAddress()
36 +
  console.log(`SUPPLY_CONTRACT_ADDRESS=${supplyContractAddress}`);
37 +
38 +
  // Deploy ERC6551
39 +
  /* const RegistryContract = await ethers.getContractFactory("ERC6551Registry");
40 +
  const registryContract = await RegistryContract.deploy();
41 +
  const registryContractAddress = await registryContract.getAddress()
42 +
  console.log("Registry Contract deployed to address:", registryContractAddress);
43 +
44 +
  const AccountContract = await ethers.getContractFactory("ERC6551Account");
45 +
  const accountContract = await AccountContract.deploy();
46 +
  const accountContractAddress = await accountContract.getAddress()
47 +
  console.log("Account Contract deployed to address:", accountContractAddress); */
48 +
49 +
  // Deploy Operator Contract
50 +
  const OperatorContract = await ethers.getContractFactory("Operator");
51 +
  const operatorContract = await OperatorContract.deploy(wallet.address, npcContractAddress, currencyContractAddress, foodContractAddress, supplyContractAddress)
52 +
  const operatorContractAddress = await operatorContract.getAddress()
53 +
  console.log(`OPERATOR_CONTRACT_ADDRESS=${operatorContractAddress}`)
54 +
55 +
  // Transfer NPC contract to Operator
56 +
  await npcContract.transferOwnership(operatorContractAddress);
57 +
  await currencyContract.transferOwnership(operatorContractAddress);
58 +
  await foodContract.transferOwnership(operatorContractAddress);
59 +
  await supplyContract.transferOwnership(operatorContractAddress);
60 +
61 +
  for (let i = 0; i < 19; i++) {
62 +
    // create NPC
63 +
    const npcTx = await operatorContract.createNPC(wallet.address, `ipfs://QmQbwCMwDETHHZ1g8YaSHqLBwCRgVHqFuRNRfiGyNqCcXj/${i}.json`)
64 +
    const npcTxReceipt = await npcTx.wait()
65 +
    console.log("NPC Created")
66 +
67 +
    // After the NPC is created
68 +
    const latestTokenId = await operatorContract.getLatestTokenId();
69 +
70 +
    // create TBA for NPC
71 +
    const tba = await tokenboundClient.createAccount({
72 +
      tokenContract: npcContractAddress,
73 +
      tokenId: latestTokenId,
74 +
    })
75 +
    console.log("TBA:", tba)
76 +
77 +
    // equip NPC via TBA
78 +
    //
79 +
    const fundNpcTx = await operatorContract.fundNPC(tba, 20)
80 +
    const fundNpxTxReceipt = await fundNpcTx.wait()
81 +
    console.log("NPC Funded")
82 +
83 +
    const feedNpcTx = await operatorContract.feedNPC(tba, 5)
84 +
    const feedNpcTxReceipt = await feedNpcTx.wait()
85 +
    console.log("NPC Fed")
86 +
87 +
    const supplyNpcTx = await operatorContract.supplyNPC(tba, 5)
88 +
    const supplyNpcTxReceipt = await supplyNpcTx.wait()
89 +
    console.log("NPC Supplied")
90 +
91 +
  }
92 +
}
93 +
94 +
Main()
scripts/deployNPCs.js (deleted) +0 −39
1 -
//const { ethers } = require("hardhat");
2 -
const ethers = require("ethers")
3 -
const provider = new ethers.AlchemyProvider("goerli", process.env.ALCHEMY_KEY)
4 -
const operatorAbi = require("../artifacts/contracts/Operator.sol/Operator.json")
5 -
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider)
6 -
const { TokenboundClient } = require("@tokenbound/sdk");
7 -
8 -
9 -
const main = async () => {
10 -
11 -
  const operatorContract = new ethers.Contract(process.env.OPERATOR_CONTRACT_ADDRESS, operatorAbi.abi, wallet)
12 -
  const tokenboundClient = new TokenboundClient({ signer: wallet, chainId: 5 })
13 -
14 -
15 -
  for (let i = 0; i < 19; i++) {
16 -
    // create NPC
17 -
    const npcTx = await operatorContract.createNPC(wallet.address, `ipfs://QmQbwCMwDETHHZ1g8YaSHqLBwCRgVHqFuRNRfiGyNqCcXj/${i}.json`)
18 -
    const npcTxReceipt = await npcTx.wait()
19 -
    console.log("NPC Created")
20 -
21 -
    // After the NPC is created
22 -
    const latestTokenId = await operatorContract.getLatestTokenId();
23 -
24 -
    // create TBA for NPC
25 -
    const tba = await tokenboundClient.createAccount({
26 -
      tokenContract: process.env.NPC_CONTRACT_ADDRESS,
27 -
      tokenId: latestTokenId,
28 -
    })
29 -
    console.log("TBA:", tba)
30 -
31 -
    // equip NPC via TBA
32 -
33 -
    const equipTx = await operatorContract.equipNPC(tba, 20, 5, 5)
34 -
    const equipTxReceipt = await equipTx.wait()
35 -
    console.log("NPC Equipped")
36 -
  }
37 -
}
38 -
39 -
main()