create-near-app 8.4.1 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/app.js +2 -1
  2. package/dist/make.js +7 -27
  3. package/dist/messages.js +1 -9
  4. package/dist/types.js +3 -2
  5. package/dist/user-input.js +32 -13
  6. package/dist/utils/index.js +1 -3
  7. package/package.json +2 -1
  8. package/templates/contracts/auction/rs/Cargo.toml +57 -0
  9. package/templates/contracts/auction/rs/README.md +39 -0
  10. package/templates/contracts/auction/rs/rust-toolchain.toml +4 -0
  11. package/templates/contracts/auction/rs/src/lib.rs +118 -0
  12. package/templates/contracts/auction/rs/tests/test_basics.rs +182 -0
  13. package/templates/contracts/auction/ts/README.md +47 -0
  14. package/templates/contracts/auction/ts/package.json +22 -0
  15. package/templates/contracts/auction/ts/sandbox-test/main.ava.js +88 -0
  16. package/templates/contracts/auction/ts/src/contract.ts +72 -0
  17. package/templates/contracts/auction/ts/tsconfig.json +14 -0
  18. package/templates/contracts/{rs → auction-adv/rs}/Cargo.toml +9 -7
  19. package/templates/contracts/auction-adv/rs/README.md +35 -0
  20. package/templates/contracts/{rs → auction-adv/rs}/rust-toolchain.toml +1 -1
  21. package/templates/contracts/auction-adv/rs/src/ext.rs +17 -0
  22. package/templates/contracts/auction-adv/rs/src/lib.rs +159 -0
  23. package/templates/contracts/auction-adv/rs/tests/fungible_token.wasm +0 -0
  24. package/templates/contracts/auction-adv/rs/tests/non_fungible_token.wasm +0 -0
  25. package/templates/contracts/auction-adv/rs/tests/test_basics.rs +430 -0
  26. package/templates/contracts/auction-adv/ts/README.md +45 -0
  27. package/templates/contracts/auction-adv/ts/package.json +22 -0
  28. package/templates/contracts/auction-adv/ts/sandbox-test/fungible_token.wasm +0 -0
  29. package/templates/contracts/auction-adv/ts/sandbox-test/main.ava.js +165 -0
  30. package/templates/contracts/auction-adv/ts/sandbox-test/non_fungible_token.wasm +0 -0
  31. package/templates/contracts/auction-adv/ts/src/contract.ts +87 -0
  32. package/templates/frontend/next-app/next-env.d.ts +6 -0
  33. package/templates/frontend/next-app/package.json +8 -22
  34. package/templates/frontend/{next-page/src/pages/hello-near/index.js → next-app/src/app/hello-near/page.tsx} +22 -21
  35. package/templates/frontend/next-app/src/app/layout.tsx +25 -0
  36. package/templates/frontend/next-app/src/components/{cards.js → cards.tsx} +4 -9
  37. package/templates/frontend/next-app/src/components/navigation.tsx +45 -0
  38. package/templates/frontend/next-app/tsconfig.json +42 -0
  39. package/templates/frontend/next-page/next-env.d.ts +6 -0
  40. package/templates/frontend/next-page/package.json +8 -22
  41. package/templates/frontend/next-page/src/components/{cards.js → cards.tsx} +0 -1
  42. package/templates/frontend/next-page/src/components/navigation.tsx +45 -0
  43. package/templates/frontend/next-page/src/pages/_app.tsx +15 -0
  44. package/templates/frontend/{vite-react/src/pages/hello_near.jsx → next-page/src/pages/hello-near/index.tsx} +18 -16
  45. package/templates/frontend/next-page/src/pages/{index.js → index.tsx} +8 -8
  46. package/templates/frontend/next-page/tsconfig.json +35 -0
  47. package/templates/frontend/vite-react/eslint.config.js +4 -1
  48. package/templates/frontend/vite-react/package.json +8 -25
  49. package/templates/frontend/vite-react/src/App.tsx +22 -0
  50. package/templates/frontend/vite-react/src/components/{cards.jsx → cards.tsx} +4 -4
  51. package/templates/frontend/vite-react/src/components/navigation.tsx +43 -0
  52. package/templates/frontend/vite-react/src/{config.js → config.ts} +1 -1
  53. package/templates/frontend/vite-react/src/global.d.ts +13 -0
  54. package/templates/frontend/vite-react/src/main.tsx +14 -0
  55. package/templates/frontend/vite-react/src/pages/hello_near.tsx +95 -0
  56. package/templates/frontend/vite-react/tsconfig.json +35 -0
  57. package/templates/contracts/py/.python-version +0 -1
  58. package/templates/contracts/py/README.md +0 -74
  59. package/templates/contracts/py/contract.py +0 -31
  60. package/templates/contracts/py/pyproject.toml +0 -10
  61. package/templates/contracts/py/tests/test_mod.py +0 -53
  62. package/templates/contracts/py/uv.lock +0 -878
  63. package/templates/contracts/rs/.github/workflows/deploy-production.yml +0 -25
  64. package/templates/contracts/rs/.github/workflows/deploy-staging.yml +0 -52
  65. package/templates/contracts/rs/.github/workflows/test.yml +0 -34
  66. package/templates/contracts/rs/.github/workflows/undeploy-staging.yml +0 -23
  67. package/templates/contracts/rs/README.md +0 -43
  68. package/templates/contracts/rs/src/lib.rs +0 -55
  69. package/templates/contracts/rs/tests/test_basics.rs +0 -30
  70. package/templates/contracts/ts/README.md +0 -83
  71. package/templates/contracts/ts/package.json +0 -23
  72. package/templates/contracts/ts/sandbox-test/main.ava.js +0 -45
  73. package/templates/contracts/ts/src/contract.ts +0 -23
  74. package/templates/contracts/ts/yarn.lock +0 -3290
  75. package/templates/frontend/next-app/jsconfig.json +0 -7
  76. package/templates/frontend/next-app/src/app/hello-near/page.js +0 -67
  77. package/templates/frontend/next-app/src/app/layout.js +0 -54
  78. package/templates/frontend/next-app/src/components/navigation.js +0 -35
  79. package/templates/frontend/next-app/src/wallets/web3modal.js +0 -27
  80. package/templates/frontend/next-page/jsconfig.json +0 -7
  81. package/templates/frontend/next-page/src/components/navigation.js +0 -35
  82. package/templates/frontend/next-page/src/pages/_app.js +0 -45
  83. package/templates/frontend/next-page/src/wallets/web3modal.js +0 -27
  84. package/templates/frontend/vite-react/src/App.jsx +0 -56
  85. package/templates/frontend/vite-react/src/components/navigation.jsx +0 -37
  86. package/templates/frontend/vite-react/src/main.jsx +0 -11
  87. package/templates/frontend/vite-react/src/wallets/web3modal.js +0 -27
  88. /package/templates/contracts/{ts → auction-adv/ts}/tsconfig.json +0 -0
  89. /package/templates/frontend/next-app/src/app/{page.js → page.tsx} +0 -0
  90. /package/templates/frontend/next-app/src/{config.js → config.ts} +0 -0
  91. /package/templates/frontend/next-page/src/{config.js → config.ts} +0 -0
  92. /package/templates/frontend/vite-react/src/pages/{home.jsx → home.tsx} +0 -0
package/dist/app.js CHANGED
@@ -40,12 +40,13 @@ const utils_1 = require("./utils");
40
40
  const prompt = await (0, user_input_1.promptAndGetConfig)();
41
41
  if (prompt === undefined)
42
42
  return;
43
- const { config: { projectName, contract, frontend, install, }, projectPath, } = prompt;
43
+ const { config: { projectName, contract, template, frontend, install, }, projectPath, } = prompt;
44
44
  show.creatingApp();
45
45
  let createSuccess;
46
46
  try {
47
47
  createSuccess = await (0, make_1.createProject)({
48
48
  contract,
49
+ template: template || 'auction',
49
50
  frontend,
50
51
  templatesDir: path_1.default.resolve(__dirname, '../templates'),
51
52
  projectPath,
package/dist/make.js CHANGED
@@ -32,44 +32,24 @@ const fs_1 = __importDefault(require("fs"));
32
32
  const ncp_1 = require("ncp");
33
33
  const path_1 = __importDefault(require("path"));
34
34
  const show = __importStar(require("./messages"));
35
- const utils_1 = require("./utils");
36
- async function createProject({ contract, frontend, projectPath, templatesDir }) {
35
+ async function createProject({ contract, template, frontend, projectPath, templatesDir }) {
37
36
  if (contract !== 'none') {
38
- await createContract({ contract, projectPath, templatesDir });
37
+ await createContract({ contract, template, projectPath, templatesDir });
39
38
  }
40
- else {
39
+ else if (frontend !== 'none') {
41
40
  await createGateway({ frontend, projectPath, templatesDir });
42
41
  }
43
42
  return true;
44
43
  }
45
44
  exports.createProject = createProject;
46
- async function createContract({ contract, projectPath, templatesDir }) {
47
- await createContractFromTemplate({ contract, projectPath, templatesDir });
48
- if (contract === 'rs') {
49
- await updateTemplateFiles(projectPath);
50
- }
45
+ async function createContract({ contract, template, projectPath, templatesDir }) {
46
+ await createContractFromTemplate({ contract, template, projectPath, templatesDir });
51
47
  }
52
- async function createContractFromTemplate({ contract, projectPath, templatesDir }) {
53
- // contract folder
54
- const sourceContractDir = path_1.default.resolve(templatesDir, 'contracts', contract);
48
+ async function createContractFromTemplate({ contract, template, projectPath, templatesDir }) {
49
+ const sourceContractDir = path_1.default.resolve(templatesDir, 'contracts', template, contract);
55
50
  fs_1.default.mkdirSync(projectPath, { recursive: true });
56
51
  await copyDir(sourceContractDir, projectPath);
57
52
  }
58
- async function updateTemplateFiles(projectPath) {
59
- const targetDir = path_1.default.join(projectPath);
60
- const cargoTomlRemotePath = 'https://raw.githubusercontent.com/near/cargo-near/refs/heads/main/cargo-near/src/commands/new/new-project-template/Cargo.template.toml';
61
- const cargoTomlFilePath = path_1.default.join(targetDir, 'Cargo.toml');
62
- const rustToolchainRemotePath = 'https://raw.githubusercontent.com/near/cargo-near/refs/heads/main/cargo-near/src/commands/new/new-project-template/rust-toolchain.toml';
63
- const rustToolchainFilePath = path_1.default.join(targetDir, 'rust-toolchain.toml');
64
- show.updatingFiles();
65
- try {
66
- await (0, utils_1.downloadFile)(cargoTomlRemotePath, cargoTomlFilePath);
67
- await (0, utils_1.downloadFile)(rustToolchainRemotePath, rustToolchainFilePath);
68
- }
69
- catch (err) {
70
- show.updateFilesFailed();
71
- }
72
- }
73
53
  async function createGateway({ frontend, projectPath, templatesDir }) {
74
54
  const sourceFrontendDir = path_1.default.resolve(`${templatesDir}/frontend/${frontend}`);
75
55
  fs_1.default.mkdirSync(projectPath, { recursive: true });
package/dist/messages.js CHANGED
@@ -30,8 +30,6 @@ const mapContractLanguage = (contract) => {
30
30
  return 'Typescript';
31
31
  case 'rs':
32
32
  return 'Rust';
33
- case 'py':
34
- return 'Python';
35
33
  default:
36
34
  return '';
37
35
  }
@@ -76,9 +74,6 @@ const contractInstructions = (projectName, contract, install, needsToInstallCarg
76
74
  if (contract === 'ts') {
77
75
  message += (0, chalk_1.default) ` {blue npm {bold run build}}\n`;
78
76
  }
79
- else if (contract === 'py') {
80
- message += (0, chalk_1.default) ` {blue {bold uvx nearc contract.py --create-venv}}\n`;
81
- }
82
77
  else {
83
78
  message += (0, chalk_1.default) ` {blue {bold cargo near build}}\n`;
84
79
  }
@@ -86,11 +81,8 @@ const contractInstructions = (projectName, contract, install, needsToInstallCarg
86
81
  if (contract === 'ts') {
87
82
  message += (0, chalk_1.default) ` {blue npm {bold run test}}\n`;
88
83
  }
89
- else if (contract === 'py') {
90
- message += (0, chalk_1.default) ` {blue {bold uv run pytest}}\n`;
91
- }
92
84
  else {
93
- message += (0, chalk_1.default) ` {blue {bold cargo near test}}\n`;
85
+ message += (0, chalk_1.default) ` {blue {bold cargo test}}\n`;
94
86
  }
95
87
  message += (0, chalk_1.default) `\n🧠 Read {bold {greenBright README.md}} to explore further`;
96
88
  return message;
package/dist/types.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.APPS = exports.FRONTENDS = exports.CONTRACTS = void 0;
4
- exports.CONTRACTS = ['ts', 'rs', 'py', 'none'];
3
+ exports.APPS = exports.FRONTENDS = exports.TEMPLATES = exports.CONTRACTS = void 0;
4
+ exports.CONTRACTS = ['ts', 'rs', 'none'];
5
+ exports.TEMPLATES = ['auction', 'auction-adv'];
5
6
  exports.FRONTENDS = ['next-app', 'next-page', 'vite-react', 'none'];
6
7
  exports.APPS = ['contract', 'gateway'];
7
8
  //# sourceMappingURL=types.js.map
@@ -37,32 +37,36 @@ const fs_1 = __importDefault(require("fs"));
37
37
  async function getUserArgs() {
38
38
  commander_1.program
39
39
  .argument('[projectName]')
40
- .option('--frontend [next-page|next-app|none]')
40
+ .option('--frontend [next-page|next-app|vite-react|none]')
41
41
  .option('--contract [ts|rs|none]')
42
+ .option('--template [auction-adv|auction]')
42
43
  .option('--install')
43
44
  .addHelpText('after', 'You can create a frontend or a contract with tests');
44
45
  commander_1.program.parse();
45
46
  const options = commander_1.program.opts();
46
47
  const [projectName] = commander_1.program.args;
47
- const { contract, frontend, install } = options;
48
- return { contract, frontend, projectName, install, error: undefined };
48
+ const { contract, frontend, template, install } = options;
49
+ return { contract, frontend, template, projectName, install, error: undefined };
49
50
  }
50
51
  exports.getUserArgs = getUserArgs;
51
52
  const appChoices = [
52
- { title: 'A Web App', description: 'A Web App that talks with Near contracts', value: 'gateway' },
53
+ { title: 'Web Application', description: 'A Web App that talks with Near contracts', value: 'gateway' },
53
54
  {
54
- title: 'A Smart Contract', description: 'A smart contract to be deployed in the Near Blockchain', value: 'contract',
55
+ title: 'Smart Contract', description: 'A smart contract to be deployed in the Near Blockchain', value: 'contract',
55
56
  },
56
57
  ];
58
+ const templateChoices = [
59
+ { title: 'Auction', description: 'A simple auction smart contract', value: 'auction' },
60
+ { title: 'Auction (advance)', description: 'An auction contract were users can bid with FT and the winner gets a NFT', value: 'auction-adv' },
61
+ ];
57
62
  const contractChoices = [
58
- { title: 'JS/TS Contract', description: 'A Near contract written in javascript/typescript', value: 'ts' },
59
- { title: 'Rust Contract', description: 'A Near contract written in Rust', value: 'rs' },
60
- { title: 'Python Contract', description: 'A Near contract written in Python', value: 'py' },
63
+ { title: 'Rust (recommended)', description: 'A smart contract written in Rust', value: 'rs' },
64
+ { title: 'Typescript (experimental)', description: 'A smart contract written in typescript', value: 'ts' },
61
65
  ];
62
66
  const frontendChoices = [
67
+ { title: 'Vite (React)', description: 'A web-app built using Vite with React', value: 'vite-react' },
63
68
  { title: 'NextJs (Classic)', description: 'A web-app built using Next.js Page Router', value: 'next-page' },
64
69
  { title: 'NextJS (App Router)', description: 'A web-app built using Next.js new App Router', value: 'next-app' },
65
- { title: 'Vite (React)', description: 'A web-app built using Vite with React', value: 'vite-react' },
66
70
  ];
67
71
  const appPrompt = {
68
72
  type: 'select',
@@ -70,6 +74,12 @@ const appPrompt = {
70
74
  message: 'What do you want to build?',
71
75
  choices: appChoices,
72
76
  };
77
+ const templatePrompt = {
78
+ type: 'select',
79
+ name: 'template',
80
+ message: 'Select a contract template',
81
+ choices: templateChoices,
82
+ };
73
83
  const frontendPrompt = {
74
84
  type: 'select',
75
85
  name: 'frontend',
@@ -80,7 +90,7 @@ const contractPrompt = [
80
90
  {
81
91
  type: 'select',
82
92
  name: 'contract',
83
- message: 'Select a smart contract template for your project',
93
+ message: 'Select a language for your contract',
84
94
  choices: contractChoices,
85
95
  }
86
96
  ];
@@ -88,7 +98,7 @@ const namePrompts = {
88
98
  type: 'text',
89
99
  name: 'projectName',
90
100
  message: 'Name your project (we will create a directory with that name)',
91
- initial: 'hello-near',
101
+ initial: 'near-template',
92
102
  };
93
103
  const npmPrompt = {
94
104
  type: 'confirm',
@@ -113,11 +123,12 @@ async function getUserAnswers() {
113
123
  if (process.platform === 'win32') {
114
124
  return { frontend: 'none', contract: 'none', projectName: '', install: false, error: show.windowsWarning };
115
125
  }
116
- // If contract, ask for the language for the contract
126
+ // If contract, ask for the template and language
127
+ const { template } = await promptUser(templatePrompt);
117
128
  let { contract } = await promptUser(contractPrompt);
118
129
  const { projectName } = await promptUser(namePrompts);
119
130
  const install = contract === 'ts' ? (await promptUser(npmPrompt)).install : false;
120
- return { frontend: 'none', contract, projectName, install, error: undefined };
131
+ return { frontend: 'none', contract, template, projectName, install, error: undefined };
121
132
  }
122
133
  }
123
134
  exports.getUserAnswers = getUserAnswers;
@@ -159,6 +170,10 @@ const validateUserArgs = (args) => {
159
170
  show.argsError(`Invalid contract type: ${args.contract}`);
160
171
  return false;
161
172
  }
173
+ if (args.template && !types_1.TEMPLATES.includes(args.template)) {
174
+ show.argsError(`Invalid template type: ${args.template}`);
175
+ return false;
176
+ }
162
177
  if (!args.projectName) {
163
178
  show.argsError('Please provide a project name');
164
179
  return false;
@@ -167,6 +182,10 @@ const validateUserArgs = (args) => {
167
182
  show.argsError('Please create a contract OR a frontend');
168
183
  return false;
169
184
  }
185
+ if (args.contract !== 'none' && !args.template) {
186
+ show.argsError('Please specify a template for your contract');
187
+ return false;
188
+ }
170
189
  return true;
171
190
  };
172
191
  //# sourceMappingURL=user-input.js.map
@@ -3,9 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.isCargoNearInstalled = exports.downloadFile = void 0;
7
- const donwloadFile_1 = __importDefault(require("./donwloadFile"));
8
- exports.downloadFile = donwloadFile_1.default;
6
+ exports.isCargoNearInstalled = void 0;
9
7
  const checkCargoNear_1 = __importDefault(require("./checkCargoNear"));
10
8
  exports.isCargoNearInstalled = checkCargoNear_1.default;
11
9
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-near-app",
3
- "version": "8.4.1",
3
+ "version": "9.0.0",
4
4
  "description": "Quickly scaffold your dApp on NEAR Blockchain",
5
5
  "main": "index.js",
6
6
  "engines": {
@@ -44,6 +44,7 @@
44
44
  "@types/jest": "^29.5.2",
45
45
  "@types/ncp": "^2.0.5",
46
46
  "@types/node": "^20.3.2",
47
+ "@types/node-dir": "^0.0.37",
47
48
  "@types/prompts": "^2.4.4",
48
49
  "@types/semver": "^7.5.0",
49
50
  "eslint": "^8.43.0",
@@ -0,0 +1,57 @@
1
+ [package]
2
+ name = "cargo-near-new-project-name"
3
+ description = "cargo-near-new-project-description"
4
+ version = "0.1.0"
5
+ edition = "2021"
6
+ # TODO: Fill out the repository field to help NEAR ecosystem tools to discover your project.
7
+ # NEP-0330 is automatically implemented for all contracts built with https://github.com/near/cargo-near.
8
+ # Link to the repository will be available via `contract_source_metadata` view-function.
9
+ repository = "https://github.com/<xxx>/<xxx>"
10
+
11
+ [lib]
12
+ crate-type = ["cdylib", "rlib"]
13
+
14
+ # fields to configure build with WASM reproducibility, according to specs
15
+ # in https://github.com/near/NEPs/blob/master/neps/nep-0330.md
16
+ [package.metadata.near.reproducible_build]
17
+ # docker image, descriptor of build environment
18
+ image = "sourcescan/cargo-near:0.18.0-rust-1.86.0"
19
+ # tag after colon above serves only descriptive purpose; image is identified by digest
20
+ image_digest = "sha256:2d0d458d2357277df669eac6fa23a1ac922e5ed16646e1d3315336e4dff18043"
21
+ # list of environment variables names, whose values, if set, will be used as external build parameters
22
+ # in a reproducible manner
23
+ # supported by `sourcescan/cargo-near:0.10.1-rust-1.82.0` image or later images
24
+ passed_env = []
25
+ # build command inside of docker container
26
+ # if docker image from default gallery is used https://hub.docker.com/r/sourcescan/cargo-near/tags,
27
+ # the command may be any combination of flags of `cargo-near`,
28
+ # supported by respective version of binary inside the container besides `--no-locked` flag
29
+ container_build_command = [
30
+ "cargo",
31
+ "near",
32
+ "build",
33
+ "non-reproducible-wasm",
34
+ "--locked",
35
+ ]
36
+
37
+ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
38
+ [dependencies]
39
+ near-sdk = "5.23"
40
+
41
+ [dev-dependencies]
42
+ near-sdk = { version = "5.23", features = ["unit-testing"] }
43
+ near-sandbox = "0.3"
44
+ near-api = "0.8"
45
+ cargo-near-build = "0.9.0"
46
+ tokio = { version = "1.12.0", features = ["full"] }
47
+ testresult = "0.4.1"
48
+
49
+ [profile.release]
50
+ codegen-units = 1
51
+ # Tell `rustc` to optimize for small code size.
52
+ opt-level = "z"
53
+ lto = true
54
+ debug = false
55
+ panic = "abort"
56
+ # Opt into extra safety checks on arithmetic operations https://stackoverflow.com/a/64136471/249801
57
+ overflow-checks = true
@@ -0,0 +1,39 @@
1
+ # Basic Auction Contract
2
+
3
+ This directory contains a Rust contract that is used as part of the [Basic Auction Tutorial](https://docs.near.org/tutorials/auction/basic-auction).
4
+
5
+ The contract is a simple auction where you can place bids, view the highest bid, and claim the tokens at the end of the auction.
6
+
7
+ This repo showcases the basic anatomy of a contract including how to store data in a contract, how to update the state, and then how to view it. It also looks at how to use environment variables and macros. We have also written sandbox test the contract locally.
8
+
9
+ ---
10
+
11
+ ## How to Build Locally?
12
+
13
+ Install [`cargo-near`](https://github.com/near/cargo-near) and run:
14
+
15
+ ```bash
16
+ cargo near build
17
+ ```
18
+
19
+ ## How to Test Locally?
20
+
21
+ ```bash
22
+ cargo test
23
+ ```
24
+
25
+ ## How to Deploy?
26
+
27
+ To deploy manually, install [NEAR CLI](https://docs.near.org/tools/near-cli#installation) and run:
28
+
29
+ ```bash
30
+ # Create a new account
31
+ near create <contractId> --useFaucet
32
+
33
+ # Deploy the contract on it
34
+ near deploy <contractId> ./target/near/auction-contract.wasm
35
+
36
+ # Initialize the contract
37
+ TWO_MINUTES_FROM_NOW=$(date -v+2M +%s000000000)
38
+ near call <contractId> init '{"end_time": "'$TWO_MINUTES_FROM_NOW'", "auctioneer": "<auctioneerAccountId>"}' --accountId <contractId>
39
+ ```
@@ -0,0 +1,4 @@
1
+ [toolchain]
2
+ channel = "1.86.0"
3
+ components = ["rustfmt", "clippy", "rust-analyzer"]
4
+ targets = ["wasm32-unknown-unknown"]
@@ -0,0 +1,118 @@
1
+ // Find all our documentation at https://docs.near.org
2
+ use near_sdk::json_types::U64;
3
+ use near_sdk::{env, near, require, AccountId, NearToken, PanicOnDefault, Promise};
4
+
5
+ #[near(serializers = [json, borsh])]
6
+ #[derive(Clone)]
7
+ pub struct Bid {
8
+ pub bidder: AccountId,
9
+ pub bid: NearToken,
10
+ }
11
+
12
+ #[near(contract_state)]
13
+ #[derive(PanicOnDefault)]
14
+ pub struct Contract {
15
+ highest_bid: Bid,
16
+ auction_end_time: U64,
17
+ auctioneer: AccountId,
18
+ claimed: bool,
19
+ }
20
+
21
+ #[near]
22
+ impl Contract {
23
+ #[init]
24
+ #[private] // only callable by the contract's account
25
+ pub fn init(end_time: U64, auctioneer: AccountId) -> Self {
26
+ Self {
27
+ highest_bid: Bid {
28
+ bidder: env::current_account_id(),
29
+ bid: NearToken::from_yoctonear(1),
30
+ },
31
+ auction_end_time: end_time,
32
+ claimed: false,
33
+ auctioneer,
34
+ }
35
+ }
36
+
37
+ #[payable]
38
+ pub fn bid(&mut self) -> Promise {
39
+ // Assert the auction is still ongoing
40
+ require!(
41
+ env::block_timestamp() < self.auction_end_time.into(),
42
+ "Auction has ended"
43
+ );
44
+
45
+ // Current bid
46
+ let bid = env::attached_deposit();
47
+ let bidder = env::predecessor_account_id();
48
+
49
+ // Last bid
50
+ let Bid {
51
+ bidder: last_bidder,
52
+ bid: last_bid,
53
+ } = self.highest_bid.clone();
54
+
55
+ // Check if the deposit is higher than the current bid
56
+ require!(bid > last_bid, "You must place a higher bid");
57
+
58
+ // Update the highest bid
59
+ self.highest_bid = Bid { bidder, bid };
60
+
61
+ // Transfer tokens back to the last bidder
62
+ Promise::new(last_bidder).transfer(last_bid)
63
+ }
64
+
65
+ pub fn claim(&mut self) -> Promise {
66
+ require!(
67
+ env::block_timestamp() > self.auction_end_time.into(),
68
+ "Auction has not ended yet"
69
+ );
70
+
71
+ require!(!self.claimed, "Auction has already been claimed");
72
+ self.claimed = true;
73
+
74
+ // Transfer tokens to the auctioneer
75
+ Promise::new(self.auctioneer.clone()).transfer(self.highest_bid.bid)
76
+ }
77
+
78
+ pub fn get_highest_bid(&self) -> Bid {
79
+ self.highest_bid.clone()
80
+ }
81
+
82
+ pub fn get_auction_end_time(&self) -> U64 {
83
+ self.auction_end_time
84
+ }
85
+
86
+ pub fn get_auctioneer(&self) -> AccountId {
87
+ self.auctioneer.clone()
88
+ }
89
+
90
+ pub fn get_claimed(&self) -> bool {
91
+ self.claimed
92
+ }
93
+ }
94
+
95
+ #[cfg(test)]
96
+ mod tests {
97
+ use super::*;
98
+
99
+ #[test]
100
+ fn init_contract() {
101
+ let end_time: U64 = U64::from(1000);
102
+ let alice: AccountId = "alice.near".parse().unwrap();
103
+ let contract = Contract::init(end_time.clone(), alice.clone());
104
+
105
+ let default_bid = contract.get_highest_bid();
106
+ assert_eq!(default_bid.bidder, env::current_account_id());
107
+ assert_eq!(default_bid.bid, NearToken::from_yoctonear(1));
108
+
109
+ let auction_end_time = contract.get_auction_end_time();
110
+ assert_eq!(auction_end_time, end_time);
111
+
112
+ let auctioneer = contract.get_auctioneer();
113
+ assert_eq!(auctioneer, alice);
114
+
115
+ let claimed = contract.get_claimed();
116
+ assert_eq!(claimed, false);
117
+ }
118
+ }
@@ -0,0 +1,182 @@
1
+ use near_api::{AccountId, NearGas, NearToken};
2
+ use near_sdk::serde_json::json;
3
+
4
+ #[derive(near_sdk::serde::Deserialize)]
5
+ #[serde(crate = "near_sdk::serde")]
6
+ pub struct Bid {
7
+ pub bidder: AccountId,
8
+ pub bid: NearToken,
9
+ }
10
+
11
+ #[tokio::test]
12
+ async fn test_contract_is_operational() -> testresult::TestResult<()> {
13
+ let contract_wasm_path = cargo_near_build::build_with_cli(Default::default())?;
14
+ let contract_wasm = std::fs::read(contract_wasm_path)?;
15
+
16
+ let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
17
+ let sandbox_network =
18
+ near_api::NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
19
+
20
+ // Create accounts
21
+ let alice = create_subaccount(&sandbox, "alice.sandbox").await?;
22
+ let bob = create_subaccount(&sandbox, "bob.sandbox").await?;
23
+ let auctioneer = create_subaccount(&sandbox, "auctioneer.sandbox").await?;
24
+ let contract = create_subaccount(&sandbox, "contract.sandbox")
25
+ .await?
26
+ .as_contract();
27
+
28
+ // Deploy and initialize contract
29
+ let signer = near_api::Signer::from_secret_key(
30
+ near_sandbox::config::DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY
31
+ .parse()
32
+ .unwrap(),
33
+ )?;
34
+ let now = std::time::SystemTime::now()
35
+ .duration_since(std::time::SystemTime::UNIX_EPOCH)?
36
+ .as_secs();
37
+ let a_minute_from_now = (now + 60) * 1000000000;
38
+ near_api::Contract::deploy(contract.account_id().clone())
39
+ .use_code(contract_wasm)
40
+ .with_init_call(
41
+ "init",
42
+ json!({"end_time": a_minute_from_now.to_string(), "auctioneer": auctioneer.account_id()}),
43
+ )?
44
+ .with_signer(signer.clone())
45
+ .send_to(&sandbox_network)
46
+ .await?
47
+ .assert_success();
48
+
49
+ // Alice makes first bid
50
+ contract
51
+ .call_function("bid", ())
52
+ .transaction()
53
+ .deposit(NearToken::from_near(1))
54
+ .with_signer(alice.account_id().clone(), signer.clone())
55
+ .send_to(&sandbox_network)
56
+ .await?
57
+ .assert_success();
58
+
59
+ let highest_bid: Bid = contract
60
+ .call_function("get_highest_bid", ())
61
+ .read_only()
62
+ .fetch_from(&sandbox_network)
63
+ .await?
64
+ .data;
65
+ assert_eq!(highest_bid.bid, NearToken::from_near(1));
66
+ assert_eq!(&highest_bid.bidder, alice.account_id());
67
+
68
+ let alice_balance = alice
69
+ .tokens()
70
+ .near_balance()
71
+ .fetch_from(&sandbox_network)
72
+ .await?
73
+ .total;
74
+
75
+ // Bob makes a higher bid
76
+ contract
77
+ .call_function("bid", ())
78
+ .transaction()
79
+ .deposit(NearToken::from_near(2))
80
+ .with_signer(bob.account_id().clone(), signer.clone())
81
+ .send_to(&sandbox_network)
82
+ .await?
83
+ .assert_success();
84
+
85
+ let highest_bid: Bid = contract
86
+ .call_function("get_highest_bid", ())
87
+ .read_only()
88
+ .fetch_from(&sandbox_network)
89
+ .await?
90
+ .data;
91
+ assert_eq!(highest_bid.bid, NearToken::from_near(2));
92
+ assert_eq!(&highest_bid.bidder, bob.account_id());
93
+
94
+ // Check that Alice was refunded
95
+ let new_alice_balance = alice
96
+ .tokens()
97
+ .near_balance()
98
+ .fetch_from(&sandbox_network)
99
+ .await?
100
+ .total;
101
+
102
+ assert!(new_alice_balance == alice_balance.saturating_add(NearToken::from_near(1)));
103
+
104
+ // Alice tries to make a bid with less NEAR than the previous
105
+ contract
106
+ .call_function("bid", ())
107
+ .transaction()
108
+ .deposit(NearToken::from_near(1))
109
+ .with_signer(alice.account_id().clone(), signer.clone())
110
+ .send_to(&sandbox_network)
111
+ .await?
112
+ .assert_failure();
113
+
114
+ // Auctioneer claims auction but did not finish
115
+ contract
116
+ .call_function("claim", ())
117
+ .transaction()
118
+ .gas(NearGas::from_tgas(30))
119
+ .with_signer(auctioneer.account_id().clone(), signer.clone())
120
+ .send_to(&sandbox_network)
121
+ .await?
122
+ .assert_failure();
123
+
124
+ // Fast forward 200 blocks
125
+ let blocks_to_advance = 200;
126
+ sandbox.fast_forward(blocks_to_advance).await?;
127
+
128
+ // Auctioneer claims the auction
129
+ contract
130
+ .call_function("claim", ())
131
+ .transaction()
132
+ .gas(NearGas::from_tgas(30))
133
+ .with_signer(auctioneer.account_id().clone(), signer.clone())
134
+ .send_to(&sandbox_network)
135
+ .await?
136
+ .assert_success();
137
+
138
+ // Checks the auctioneer has the correct balance
139
+ let auctioneer_balance = auctioneer
140
+ .tokens()
141
+ .near_balance()
142
+ .fetch_from(&sandbox_network)
143
+ .await?
144
+ .total;
145
+ assert!(auctioneer_balance <= NearToken::from_near(12));
146
+ assert!(auctioneer_balance > NearToken::from_millinear(11990));
147
+
148
+ // Auctioneer tries to claim the auction again
149
+ contract
150
+ .call_function("claim", ())
151
+ .transaction()
152
+ .gas(NearGas::from_tgas(30))
153
+ .with_signer(auctioneer.account_id().clone(), signer.clone())
154
+ .send_to(&sandbox_network)
155
+ .await?
156
+ .assert_failure();
157
+
158
+ // Alice tries to make a bid when the auction is over
159
+ contract
160
+ .call_function("bid", ())
161
+ .transaction()
162
+ .deposit(NearToken::from_near(1))
163
+ .with_signer(alice.account_id().clone(), signer.clone())
164
+ .send_to(&sandbox_network)
165
+ .await?
166
+ .assert_failure();
167
+
168
+ Ok(())
169
+ }
170
+
171
+ async fn create_subaccount(
172
+ sandbox: &near_sandbox::Sandbox,
173
+ name: &str,
174
+ ) -> testresult::TestResult<near_api::Account> {
175
+ let account_id: AccountId = name.parse().unwrap();
176
+ sandbox
177
+ .create_account(account_id.clone())
178
+ .initial_balance(NearToken::from_near(10))
179
+ .send()
180
+ .await?;
181
+ Ok(near_api::Account(account_id))
182
+ }