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.
- package/dist/app.js +2 -1
- package/dist/make.js +7 -27
- package/dist/messages.js +1 -9
- package/dist/types.js +3 -2
- package/dist/user-input.js +32 -13
- package/dist/utils/index.js +1 -3
- package/package.json +2 -1
- package/templates/contracts/auction/rs/Cargo.toml +57 -0
- package/templates/contracts/auction/rs/README.md +39 -0
- package/templates/contracts/auction/rs/rust-toolchain.toml +4 -0
- package/templates/contracts/auction/rs/src/lib.rs +118 -0
- package/templates/contracts/auction/rs/tests/test_basics.rs +182 -0
- package/templates/contracts/auction/ts/README.md +47 -0
- package/templates/contracts/auction/ts/package.json +22 -0
- package/templates/contracts/auction/ts/sandbox-test/main.ava.js +88 -0
- package/templates/contracts/auction/ts/src/contract.ts +72 -0
- package/templates/contracts/auction/ts/tsconfig.json +14 -0
- package/templates/contracts/{rs → auction-adv/rs}/Cargo.toml +9 -7
- package/templates/contracts/auction-adv/rs/README.md +35 -0
- package/templates/contracts/{rs → auction-adv/rs}/rust-toolchain.toml +1 -1
- package/templates/contracts/auction-adv/rs/src/ext.rs +17 -0
- package/templates/contracts/auction-adv/rs/src/lib.rs +159 -0
- package/templates/contracts/auction-adv/rs/tests/fungible_token.wasm +0 -0
- package/templates/contracts/auction-adv/rs/tests/non_fungible_token.wasm +0 -0
- package/templates/contracts/auction-adv/rs/tests/test_basics.rs +430 -0
- package/templates/contracts/auction-adv/ts/README.md +45 -0
- package/templates/contracts/auction-adv/ts/package.json +22 -0
- package/templates/contracts/auction-adv/ts/sandbox-test/fungible_token.wasm +0 -0
- package/templates/contracts/auction-adv/ts/sandbox-test/main.ava.js +165 -0
- package/templates/contracts/auction-adv/ts/sandbox-test/non_fungible_token.wasm +0 -0
- package/templates/contracts/auction-adv/ts/src/contract.ts +87 -0
- package/templates/frontend/next-app/next-env.d.ts +6 -0
- package/templates/frontend/next-app/package.json +8 -22
- package/templates/frontend/{next-page/src/pages/hello-near/index.js → next-app/src/app/hello-near/page.tsx} +22 -21
- package/templates/frontend/next-app/src/app/layout.tsx +25 -0
- package/templates/frontend/next-app/src/components/{cards.js → cards.tsx} +4 -9
- package/templates/frontend/next-app/src/components/navigation.tsx +45 -0
- package/templates/frontend/next-app/tsconfig.json +42 -0
- package/templates/frontend/next-page/next-env.d.ts +6 -0
- package/templates/frontend/next-page/package.json +8 -22
- package/templates/frontend/next-page/src/components/{cards.js → cards.tsx} +0 -1
- package/templates/frontend/next-page/src/components/navigation.tsx +45 -0
- package/templates/frontend/next-page/src/pages/_app.tsx +15 -0
- package/templates/frontend/{vite-react/src/pages/hello_near.jsx → next-page/src/pages/hello-near/index.tsx} +18 -16
- package/templates/frontend/next-page/src/pages/{index.js → index.tsx} +8 -8
- package/templates/frontend/next-page/tsconfig.json +35 -0
- package/templates/frontend/vite-react/eslint.config.js +4 -1
- package/templates/frontend/vite-react/package.json +8 -25
- package/templates/frontend/vite-react/src/App.tsx +22 -0
- package/templates/frontend/vite-react/src/components/{cards.jsx → cards.tsx} +4 -4
- package/templates/frontend/vite-react/src/components/navigation.tsx +43 -0
- package/templates/frontend/vite-react/src/{config.js → config.ts} +1 -1
- package/templates/frontend/vite-react/src/global.d.ts +13 -0
- package/templates/frontend/vite-react/src/main.tsx +14 -0
- package/templates/frontend/vite-react/src/pages/hello_near.tsx +95 -0
- package/templates/frontend/vite-react/tsconfig.json +35 -0
- package/templates/contracts/py/.python-version +0 -1
- package/templates/contracts/py/README.md +0 -74
- package/templates/contracts/py/contract.py +0 -31
- package/templates/contracts/py/pyproject.toml +0 -10
- package/templates/contracts/py/tests/test_mod.py +0 -53
- package/templates/contracts/py/uv.lock +0 -878
- package/templates/contracts/rs/.github/workflows/deploy-production.yml +0 -25
- package/templates/contracts/rs/.github/workflows/deploy-staging.yml +0 -52
- package/templates/contracts/rs/.github/workflows/test.yml +0 -34
- package/templates/contracts/rs/.github/workflows/undeploy-staging.yml +0 -23
- package/templates/contracts/rs/README.md +0 -43
- package/templates/contracts/rs/src/lib.rs +0 -55
- package/templates/contracts/rs/tests/test_basics.rs +0 -30
- package/templates/contracts/ts/README.md +0 -83
- package/templates/contracts/ts/package.json +0 -23
- package/templates/contracts/ts/sandbox-test/main.ava.js +0 -45
- package/templates/contracts/ts/src/contract.ts +0 -23
- package/templates/contracts/ts/yarn.lock +0 -3290
- package/templates/frontend/next-app/jsconfig.json +0 -7
- package/templates/frontend/next-app/src/app/hello-near/page.js +0 -67
- package/templates/frontend/next-app/src/app/layout.js +0 -54
- package/templates/frontend/next-app/src/components/navigation.js +0 -35
- package/templates/frontend/next-app/src/wallets/web3modal.js +0 -27
- package/templates/frontend/next-page/jsconfig.json +0 -7
- package/templates/frontend/next-page/src/components/navigation.js +0 -35
- package/templates/frontend/next-page/src/pages/_app.js +0 -45
- package/templates/frontend/next-page/src/wallets/web3modal.js +0 -27
- package/templates/frontend/vite-react/src/App.jsx +0 -56
- package/templates/frontend/vite-react/src/components/navigation.jsx +0 -37
- package/templates/frontend/vite-react/src/main.jsx +0 -11
- package/templates/frontend/vite-react/src/wallets/web3modal.js +0 -27
- /package/templates/contracts/{ts → auction-adv/ts}/tsconfig.json +0 -0
- /package/templates/frontend/next-app/src/app/{page.js → page.tsx} +0 -0
- /package/templates/frontend/next-app/src/{config.js → config.ts} +0 -0
- /package/templates/frontend/next-page/src/{config.js → config.ts} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
|
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', '
|
|
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
|
package/dist/user-input.js
CHANGED
|
@@ -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: '
|
|
53
|
+
{ title: 'Web Application', description: 'A Web App that talks with Near contracts', value: 'gateway' },
|
|
53
54
|
{
|
|
54
|
-
title: '
|
|
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: '
|
|
59
|
-
{ title: '
|
|
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
|
|
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: '
|
|
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
|
|
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
|
package/dist/utils/index.js
CHANGED
|
@@ -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 =
|
|
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": "
|
|
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,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
|
+
}
|