create-near-app 8.5.0 → 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/package.json +2 -28
- package/templates/frontend/next-app/src/app/hello-near/page.tsx +2 -4
- package/templates/frontend/next-app/src/app/layout.tsx +3 -58
- package/templates/frontend/next-app/src/components/navigation.tsx +14 -14
- package/templates/frontend/next-page/package.json +3 -24
- package/templates/frontend/next-page/src/components/cards.tsx +0 -1
- package/templates/frontend/next-page/src/components/navigation.tsx +14 -14
- package/templates/frontend/next-page/src/pages/_app.tsx +3 -56
- package/templates/frontend/next-page/src/pages/hello-near/index.tsx +2 -2
- package/templates/frontend/vite-react/package.json +6 -28
- package/templates/frontend/vite-react/src/App.tsx +3 -62
- package/templates/frontend/vite-react/src/components/navigation.tsx +13 -19
- package/templates/frontend/vite-react/src/global.d.ts +13 -0
- package/templates/frontend/vite-react/src/pages/hello_near.tsx +3 -3
- 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/src/wallets/web3modal.ts +0 -27
- package/templates/frontend/next-page/src/wallets/web3modal.ts +0 -27
- package/templates/frontend/vite-react/src/wallets/web3modal.ts +0 -27
- /package/templates/contracts/{ts → auction-adv/ts}/tsconfig.json +0 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
use near_api::{AccountId, NearGas, NearToken};
|
|
2
|
+
use near_sdk::json_types::U128;
|
|
3
|
+
use near_sdk::serde_json;
|
|
4
|
+
|
|
5
|
+
const FT_WASM_FILEPATH: &str = "./tests/fungible_token.wasm";
|
|
6
|
+
const NFT_WASM_FILEPATH: &str = "./tests/non_fungible_token.wasm";
|
|
7
|
+
|
|
8
|
+
#[derive(near_sdk::serde::Deserialize)]
|
|
9
|
+
#[serde(crate = "near_sdk::serde")]
|
|
10
|
+
pub struct Bid {
|
|
11
|
+
pub bidder: AccountId,
|
|
12
|
+
pub bid: U128,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[tokio::test]
|
|
16
|
+
async fn test_contract_is_operational() -> testresult::TestResult<()> {
|
|
17
|
+
let contract_wasm_path = cargo_near_build::build_with_cli(Default::default())?;
|
|
18
|
+
let contract_wasm = std::fs::read(contract_wasm_path)?;
|
|
19
|
+
|
|
20
|
+
let sandbox = near_sandbox::Sandbox::start_sandbox().await?;
|
|
21
|
+
let sandbox_network =
|
|
22
|
+
near_api::NetworkConfig::from_rpc_url("sandbox", sandbox.rpc_addr.parse()?);
|
|
23
|
+
|
|
24
|
+
let signer = near_api::Signer::from_secret_key(
|
|
25
|
+
near_sandbox::config::DEFAULT_GENESIS_ACCOUNT_PRIVATE_KEY
|
|
26
|
+
.parse()
|
|
27
|
+
.unwrap(),
|
|
28
|
+
)?;
|
|
29
|
+
|
|
30
|
+
let root: AccountId = "sandbox".parse().unwrap();
|
|
31
|
+
|
|
32
|
+
// Create accounts
|
|
33
|
+
let alice = create_subaccount(&sandbox, "alice.sandbox").await?;
|
|
34
|
+
let bob = create_subaccount(&sandbox, "bob.sandbox").await?;
|
|
35
|
+
let auctioneer = create_subaccount(&sandbox, "auctioneer.sandbox").await?;
|
|
36
|
+
let contract_account = create_subaccount(&sandbox, "contract.sandbox").await?;
|
|
37
|
+
let contract = contract_account.as_contract();
|
|
38
|
+
|
|
39
|
+
// Deploy and initialize FT contract
|
|
40
|
+
let ft_contract_account = create_subaccount(&sandbox, "ft.sandbox").await?;
|
|
41
|
+
let ft_contract = ft_contract_account.as_contract();
|
|
42
|
+
let ft_wasm = std::fs::read(FT_WASM_FILEPATH)?;
|
|
43
|
+
|
|
44
|
+
near_api::Contract::deploy(ft_contract.account_id().clone())
|
|
45
|
+
.use_code(ft_wasm)
|
|
46
|
+
.with_init_call(
|
|
47
|
+
"new_default_meta",
|
|
48
|
+
serde_json::json!({"owner_id": root, "total_supply": U128(1_000_000)}),
|
|
49
|
+
)?
|
|
50
|
+
.with_signer(signer.clone())
|
|
51
|
+
.send_to(&sandbox_network)
|
|
52
|
+
.await?
|
|
53
|
+
.assert_success();
|
|
54
|
+
|
|
55
|
+
// Deploy and initialize NFT contract
|
|
56
|
+
let nft_contract_account = create_subaccount(&sandbox, "nft.sandbox").await?;
|
|
57
|
+
let nft_contract = nft_contract_account.as_contract();
|
|
58
|
+
let nft_wasm = std::fs::read(NFT_WASM_FILEPATH)?;
|
|
59
|
+
|
|
60
|
+
near_api::Contract::deploy(nft_contract.account_id().clone())
|
|
61
|
+
.use_code(nft_wasm)
|
|
62
|
+
.with_init_call("new_default_meta", serde_json::json!({"owner_id": root}))?
|
|
63
|
+
.with_signer(signer.clone())
|
|
64
|
+
.send_to(&sandbox_network)
|
|
65
|
+
.await?
|
|
66
|
+
.assert_success();
|
|
67
|
+
|
|
68
|
+
// Mint NFT
|
|
69
|
+
nft_contract
|
|
70
|
+
.call_function(
|
|
71
|
+
"nft_mint",
|
|
72
|
+
serde_json::json!({
|
|
73
|
+
"token_id": "1",
|
|
74
|
+
"receiver_id": contract.account_id(),
|
|
75
|
+
"token_metadata": {
|
|
76
|
+
"title": "LEEROYYYMMMJENKINSSS",
|
|
77
|
+
"description": "Alright time's up, let's do this.",
|
|
78
|
+
"media": "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse3.mm.bing.net%2Fth%3Fid%3DOIP.Fhp4lHufCdTzTeGCAblOdgHaF7%26pid%3DApi&f=1"
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
)
|
|
82
|
+
.transaction()
|
|
83
|
+
.deposit(NearToken::from_millinear(80))
|
|
84
|
+
.with_signer(root.clone(), signer.clone())
|
|
85
|
+
.send_to(&sandbox_network)
|
|
86
|
+
.await?
|
|
87
|
+
.assert_success();
|
|
88
|
+
|
|
89
|
+
// Register accounts for FT
|
|
90
|
+
for account_id in [
|
|
91
|
+
alice.account_id().clone(),
|
|
92
|
+
bob.account_id().clone(),
|
|
93
|
+
contract.account_id().clone(),
|
|
94
|
+
auctioneer.account_id().clone(),
|
|
95
|
+
]
|
|
96
|
+
.iter()
|
|
97
|
+
{
|
|
98
|
+
ft_contract
|
|
99
|
+
.call_function("storage_deposit", serde_json::json!({ "account_id": account_id }))
|
|
100
|
+
.transaction()
|
|
101
|
+
.deposit(NearToken::from_yoctonear(8000000000000000000000))
|
|
102
|
+
.with_signer(account_id.clone(), signer.clone())
|
|
103
|
+
.send_to(&sandbox_network)
|
|
104
|
+
.await?
|
|
105
|
+
.assert_success();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Transfer FTs to Alice and Bob
|
|
109
|
+
let transfer_amount = U128(150_000);
|
|
110
|
+
|
|
111
|
+
ft_transfer(
|
|
112
|
+
&ft_contract,
|
|
113
|
+
&root,
|
|
114
|
+
alice.account_id(),
|
|
115
|
+
transfer_amount,
|
|
116
|
+
&signer,
|
|
117
|
+
&sandbox_network,
|
|
118
|
+
)
|
|
119
|
+
.await?;
|
|
120
|
+
|
|
121
|
+
ft_transfer(
|
|
122
|
+
&ft_contract,
|
|
123
|
+
&root,
|
|
124
|
+
bob.account_id(),
|
|
125
|
+
transfer_amount,
|
|
126
|
+
&signer,
|
|
127
|
+
&sandbox_network,
|
|
128
|
+
)
|
|
129
|
+
.await?;
|
|
130
|
+
|
|
131
|
+
// Deploy and initialize auction contract
|
|
132
|
+
let now = std::time::SystemTime::now()
|
|
133
|
+
.duration_since(std::time::SystemTime::UNIX_EPOCH)?
|
|
134
|
+
.as_secs();
|
|
135
|
+
let a_minute_from_now = (now + 60) * 1000000000;
|
|
136
|
+
let starting_price = U128(10_000);
|
|
137
|
+
|
|
138
|
+
near_api::Contract::deploy(contract.account_id().clone())
|
|
139
|
+
.use_code(contract_wasm)
|
|
140
|
+
.with_init_call(
|
|
141
|
+
"init",
|
|
142
|
+
serde_json::json!({
|
|
143
|
+
"end_time": a_minute_from_now.to_string(),
|
|
144
|
+
"auctioneer": auctioneer.account_id(),
|
|
145
|
+
"ft_contract": ft_contract.account_id(),
|
|
146
|
+
"nft_contract": nft_contract.account_id(),
|
|
147
|
+
"token_id": "1",
|
|
148
|
+
"starting_price": starting_price
|
|
149
|
+
}),
|
|
150
|
+
)?
|
|
151
|
+
.with_signer(signer.clone())
|
|
152
|
+
.send_to(&sandbox_network)
|
|
153
|
+
.await?
|
|
154
|
+
.assert_success();
|
|
155
|
+
|
|
156
|
+
// Alice makes bid less than starting price
|
|
157
|
+
ft_transfer_call(
|
|
158
|
+
&ft_contract,
|
|
159
|
+
&alice,
|
|
160
|
+
contract.account_id(),
|
|
161
|
+
U128(5_000),
|
|
162
|
+
&signer,
|
|
163
|
+
&sandbox_network,
|
|
164
|
+
)
|
|
165
|
+
.await?;
|
|
166
|
+
|
|
167
|
+
let highest_bid: Bid = contract
|
|
168
|
+
.call_function("get_highest_bid", ())
|
|
169
|
+
.read_only()
|
|
170
|
+
.fetch_from(&sandbox_network)
|
|
171
|
+
.await?
|
|
172
|
+
.data;
|
|
173
|
+
assert_eq!(highest_bid.bid, U128(10_000));
|
|
174
|
+
assert_eq!(&highest_bid.bidder, contract.account_id());
|
|
175
|
+
|
|
176
|
+
let contract_balance: U128 =
|
|
177
|
+
ft_balance_of(&ft_contract, contract.account_id(), &sandbox_network).await?;
|
|
178
|
+
assert_eq!(contract_balance, U128(0));
|
|
179
|
+
|
|
180
|
+
let alice_balance: U128 =
|
|
181
|
+
ft_balance_of(&ft_contract, alice.account_id(), &sandbox_network).await?;
|
|
182
|
+
assert_eq!(alice_balance, U128(150_000));
|
|
183
|
+
|
|
184
|
+
// Alice makes valid bid
|
|
185
|
+
ft_transfer_call(
|
|
186
|
+
&ft_contract,
|
|
187
|
+
&alice,
|
|
188
|
+
contract.account_id(),
|
|
189
|
+
U128(50_000),
|
|
190
|
+
&signer,
|
|
191
|
+
&sandbox_network,
|
|
192
|
+
)
|
|
193
|
+
.await?;
|
|
194
|
+
|
|
195
|
+
let highest_bid: Bid = contract
|
|
196
|
+
.call_function("get_highest_bid", ())
|
|
197
|
+
.read_only()
|
|
198
|
+
.fetch_from(&sandbox_network)
|
|
199
|
+
.await?
|
|
200
|
+
.data;
|
|
201
|
+
assert_eq!(highest_bid.bid, U128(50_000));
|
|
202
|
+
assert_eq!(&highest_bid.bidder, alice.account_id());
|
|
203
|
+
|
|
204
|
+
let contract_balance: U128 =
|
|
205
|
+
ft_balance_of(&ft_contract, contract.account_id(), &sandbox_network).await?;
|
|
206
|
+
assert_eq!(contract_balance, U128(50_000));
|
|
207
|
+
|
|
208
|
+
let alice_balance: U128 =
|
|
209
|
+
ft_balance_of(&ft_contract, alice.account_id(), &sandbox_network).await?;
|
|
210
|
+
assert_eq!(alice_balance, U128(100_000));
|
|
211
|
+
|
|
212
|
+
// Bob makes a higher bid
|
|
213
|
+
ft_transfer_call(
|
|
214
|
+
&ft_contract,
|
|
215
|
+
&bob,
|
|
216
|
+
contract.account_id(),
|
|
217
|
+
U128(60_000),
|
|
218
|
+
&signer,
|
|
219
|
+
&sandbox_network,
|
|
220
|
+
)
|
|
221
|
+
.await?;
|
|
222
|
+
|
|
223
|
+
let highest_bid: Bid = contract
|
|
224
|
+
.call_function("get_highest_bid", ())
|
|
225
|
+
.read_only()
|
|
226
|
+
.fetch_from(&sandbox_network)
|
|
227
|
+
.await?
|
|
228
|
+
.data;
|
|
229
|
+
assert_eq!(highest_bid.bid, U128(60_000));
|
|
230
|
+
assert_eq!(&highest_bid.bidder, bob.account_id());
|
|
231
|
+
|
|
232
|
+
// Checks Alice was returned her bid
|
|
233
|
+
let alice_balance: U128 =
|
|
234
|
+
ft_balance_of(&ft_contract, alice.account_id(), &sandbox_network).await?;
|
|
235
|
+
assert_eq!(alice_balance, U128(150_000));
|
|
236
|
+
|
|
237
|
+
let bob_balance: U128 =
|
|
238
|
+
ft_balance_of(&ft_contract, bob.account_id(), &sandbox_network).await?;
|
|
239
|
+
assert_eq!(bob_balance, U128(90_000));
|
|
240
|
+
|
|
241
|
+
// Alice tries to make a bid with less FTs than the previous
|
|
242
|
+
ft_transfer_call(
|
|
243
|
+
&ft_contract,
|
|
244
|
+
&alice,
|
|
245
|
+
contract.account_id(),
|
|
246
|
+
U128(50_000),
|
|
247
|
+
&signer,
|
|
248
|
+
&sandbox_network,
|
|
249
|
+
)
|
|
250
|
+
.await?;
|
|
251
|
+
|
|
252
|
+
let highest_bid: Bid = contract
|
|
253
|
+
.call_function("get_highest_bid", ())
|
|
254
|
+
.read_only()
|
|
255
|
+
.fetch_from(&sandbox_network)
|
|
256
|
+
.await?
|
|
257
|
+
.data;
|
|
258
|
+
assert_eq!(highest_bid.bid, U128(60_000));
|
|
259
|
+
assert_eq!(&highest_bid.bidder, bob.account_id());
|
|
260
|
+
|
|
261
|
+
let contract_balance: U128 =
|
|
262
|
+
ft_balance_of(&ft_contract, contract.account_id(), &sandbox_network).await?;
|
|
263
|
+
assert_eq!(contract_balance, U128(60_000));
|
|
264
|
+
|
|
265
|
+
let alice_balance: U128 =
|
|
266
|
+
ft_balance_of(&ft_contract, alice.account_id(), &sandbox_network).await?;
|
|
267
|
+
assert_eq!(alice_balance, U128(150_000));
|
|
268
|
+
|
|
269
|
+
// Auctioneer claims auction but did not finish
|
|
270
|
+
contract
|
|
271
|
+
.call_function("claim", ())
|
|
272
|
+
.transaction()
|
|
273
|
+
.gas(NearGas::from_tgas(300))
|
|
274
|
+
.with_signer(auctioneer.account_id().clone(), signer.clone())
|
|
275
|
+
.send_to(&sandbox_network)
|
|
276
|
+
.await?
|
|
277
|
+
.assert_failure();
|
|
278
|
+
|
|
279
|
+
// Fast forward 200 blocks
|
|
280
|
+
let blocks_to_advance = 200;
|
|
281
|
+
sandbox.fast_forward(blocks_to_advance).await?;
|
|
282
|
+
|
|
283
|
+
// Auctioneer claims auction
|
|
284
|
+
contract
|
|
285
|
+
.call_function("claim", ())
|
|
286
|
+
.transaction()
|
|
287
|
+
.gas(NearGas::from_tgas(300))
|
|
288
|
+
.with_signer(auctioneer.account_id().clone(), signer.clone())
|
|
289
|
+
.send_to(&sandbox_network)
|
|
290
|
+
.await?
|
|
291
|
+
.assert_success();
|
|
292
|
+
|
|
293
|
+
let contract_balance: U128 =
|
|
294
|
+
ft_balance_of(&ft_contract, contract.account_id(), &sandbox_network).await?;
|
|
295
|
+
assert_eq!(contract_balance, U128(0));
|
|
296
|
+
|
|
297
|
+
let auctioneer_balance: U128 =
|
|
298
|
+
ft_balance_of(&ft_contract, auctioneer.account_id(), &sandbox_network).await?;
|
|
299
|
+
assert_eq!(auctioneer_balance, U128(60_000));
|
|
300
|
+
|
|
301
|
+
// Check highest bidder received the NFT
|
|
302
|
+
let token_info: serde_json::Value = nft_contract
|
|
303
|
+
.call_function("nft_token", serde_json::json!({"token_id": "1"}))
|
|
304
|
+
.read_only()
|
|
305
|
+
.fetch_from(&sandbox_network)
|
|
306
|
+
.await?
|
|
307
|
+
.data;
|
|
308
|
+
let owner_id: String = token_info["owner_id"].as_str().unwrap().to_string();
|
|
309
|
+
|
|
310
|
+
assert_eq!(
|
|
311
|
+
owner_id,
|
|
312
|
+
bob.account_id().to_string(),
|
|
313
|
+
"token owner is not the highest bidder"
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Auctioneer claims auction back but fails
|
|
317
|
+
contract
|
|
318
|
+
.call_function("claim", ())
|
|
319
|
+
.transaction()
|
|
320
|
+
.gas(NearGas::from_tgas(300))
|
|
321
|
+
.with_signer(auctioneer.account_id().clone(), signer.clone())
|
|
322
|
+
.send_to(&sandbox_network)
|
|
323
|
+
.await?
|
|
324
|
+
.assert_failure();
|
|
325
|
+
|
|
326
|
+
// Alice tries to make a bid when the auction is over
|
|
327
|
+
ft_transfer_call(
|
|
328
|
+
&ft_contract,
|
|
329
|
+
&alice,
|
|
330
|
+
contract.account_id(),
|
|
331
|
+
U128(70_000),
|
|
332
|
+
&signer,
|
|
333
|
+
&sandbox_network,
|
|
334
|
+
)
|
|
335
|
+
.await?;
|
|
336
|
+
|
|
337
|
+
let highest_bid: Bid = contract
|
|
338
|
+
.call_function("get_highest_bid", ())
|
|
339
|
+
.read_only()
|
|
340
|
+
.fetch_from(&sandbox_network)
|
|
341
|
+
.await?
|
|
342
|
+
.data;
|
|
343
|
+
assert_eq!(highest_bid.bid, U128(60_000));
|
|
344
|
+
assert_eq!(&highest_bid.bidder, bob.account_id());
|
|
345
|
+
|
|
346
|
+
let contract_balance: U128 =
|
|
347
|
+
ft_balance_of(&ft_contract, contract.account_id(), &sandbox_network).await?;
|
|
348
|
+
assert_eq!(contract_balance, U128(0));
|
|
349
|
+
|
|
350
|
+
let alice_balance: U128 =
|
|
351
|
+
ft_balance_of(&ft_contract, alice.account_id(), &sandbox_network).await?;
|
|
352
|
+
assert_eq!(alice_balance, U128(150_000));
|
|
353
|
+
|
|
354
|
+
let bob_balance: U128 =
|
|
355
|
+
ft_balance_of(&ft_contract, bob.account_id(), &sandbox_network).await?;
|
|
356
|
+
assert_eq!(bob_balance, U128(90_000));
|
|
357
|
+
|
|
358
|
+
Ok(())
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async fn create_subaccount(
|
|
362
|
+
sandbox: &near_sandbox::Sandbox,
|
|
363
|
+
name: &str,
|
|
364
|
+
) -> testresult::TestResult<near_api::Account> {
|
|
365
|
+
let account_id: AccountId = name.parse().unwrap();
|
|
366
|
+
sandbox
|
|
367
|
+
.create_account(account_id.clone())
|
|
368
|
+
.initial_balance(NearToken::from_near(10))
|
|
369
|
+
.send()
|
|
370
|
+
.await?;
|
|
371
|
+
Ok(near_api::Account(account_id))
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async fn ft_transfer(
|
|
375
|
+
ft_contract: &near_api::Contract,
|
|
376
|
+
from: &AccountId,
|
|
377
|
+
to: &AccountId,
|
|
378
|
+
amount: U128,
|
|
379
|
+
signer: &std::sync::Arc<near_api::Signer>,
|
|
380
|
+
network: &near_api::NetworkConfig,
|
|
381
|
+
) -> testresult::TestResult<()> {
|
|
382
|
+
ft_contract
|
|
383
|
+
.call_function(
|
|
384
|
+
"ft_transfer",
|
|
385
|
+
serde_json::json!({"receiver_id": to, "amount": amount}),
|
|
386
|
+
)
|
|
387
|
+
.transaction()
|
|
388
|
+
.deposit(NearToken::from_yoctonear(1))
|
|
389
|
+
.with_signer(from.clone(), signer.clone())
|
|
390
|
+
.send_to(network)
|
|
391
|
+
.await?
|
|
392
|
+
.assert_success();
|
|
393
|
+
Ok(())
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async fn ft_balance_of(
|
|
397
|
+
ft_contract: &near_api::Contract,
|
|
398
|
+
account_id: &AccountId,
|
|
399
|
+
network: &near_api::NetworkConfig,
|
|
400
|
+
) -> testresult::TestResult<U128> {
|
|
401
|
+
let result: U128 = ft_contract
|
|
402
|
+
.call_function("ft_balance_of", serde_json::json!({"account_id": account_id}))
|
|
403
|
+
.read_only()
|
|
404
|
+
.fetch_from(network)
|
|
405
|
+
.await?
|
|
406
|
+
.data;
|
|
407
|
+
Ok(result)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async fn ft_transfer_call(
|
|
411
|
+
ft_contract: &near_api::Contract,
|
|
412
|
+
account: &near_api::Account,
|
|
413
|
+
receiver_id: &AccountId,
|
|
414
|
+
amount: U128,
|
|
415
|
+
signer: &std::sync::Arc<near_api::Signer>,
|
|
416
|
+
network: &near_api::NetworkConfig,
|
|
417
|
+
) -> testresult::TestResult<()> {
|
|
418
|
+
let _ = ft_contract
|
|
419
|
+
.call_function(
|
|
420
|
+
"ft_transfer_call",
|
|
421
|
+
serde_json::json!({"receiver_id": receiver_id, "amount": amount, "msg": "0"}),
|
|
422
|
+
)
|
|
423
|
+
.transaction()
|
|
424
|
+
.deposit(NearToken::from_yoctonear(1))
|
|
425
|
+
.gas(NearGas::from_tgas(300))
|
|
426
|
+
.with_signer(account.account_id().clone(), signer.clone())
|
|
427
|
+
.send_to(network)
|
|
428
|
+
.await?;
|
|
429
|
+
Ok(())
|
|
430
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Auction Contract in FTs
|
|
2
|
+
|
|
3
|
+
This directory contains a JavaScript contract that is used as part of the [Bidding with FTs](https://docs.near.org/tutorials/auction/bidding-with-fts) section of the auction tutorial.
|
|
4
|
+
|
|
5
|
+
In this part the contract is adapted so users can bid in fungible tokens (FTs) instead of NEAR tokens. It is a great way to learn how to work with FTs in NEAR.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How to Build Locally?
|
|
10
|
+
|
|
11
|
+
Install the [NEAR CLI](https://docs.near.org/tools/near-cli#installation) and run:
|
|
12
|
+
|
|
13
|
+
Install the dependencies:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Build the contract:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run build
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## How to Test Locally?
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run test
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## How to Deploy?
|
|
32
|
+
|
|
33
|
+
Install the [NEAR CLI](https://docs.near.org/tools/near-cli#installation) and run:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Create a new account
|
|
37
|
+
near create <contractId> --useFaucet
|
|
38
|
+
|
|
39
|
+
# Deploy the contract
|
|
40
|
+
near deploy <contractId> ./build/auction-contract.wasm
|
|
41
|
+
|
|
42
|
+
# Initialize the contract
|
|
43
|
+
TWO_MINUTES_FROM_NOW=$(date -v+2M +%s000000000)
|
|
44
|
+
near call <contractId> init '{"end_time": "'$TWO_MINUTES_FROM_NOW'", "auctioneer": "<auctioneerAccountId>", "nft_contract": "<nftContractId>", "token_id": "<tokenId>"}' --accountId <contractId>
|
|
45
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auction-contract",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "(MIT AND Apache-2.0)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "near-sdk-js build src/contract.ts build/auction-contract.wasm",
|
|
8
|
+
"test": "$npm_execpath run build && ava -- ./build/auction-contract.wasm"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"near-sdk-js": "2.0.0"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"ava": "^6.1.3",
|
|
15
|
+
"near-workspaces": "^3.5.0",
|
|
16
|
+
"typescript": "^5.4.5"
|
|
17
|
+
},
|
|
18
|
+
"ava": {
|
|
19
|
+
"timeout": "50000",
|
|
20
|
+
"files": ["sandbox-test/*.ava.js"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import anyTest from 'ava';
|
|
4
|
+
import { NEAR, Worker } from 'near-workspaces';
|
|
5
|
+
import { setDefaultResultOrder } from 'dns'; setDefaultResultOrder('ipv4first'); // temp fix for node >v17
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {import('near-workspaces').NearAccount} NearAccount
|
|
10
|
+
* @type {import('ava').TestFn<{worker: Worker, accounts: Record<string, NearAccount>}>}
|
|
11
|
+
*/
|
|
12
|
+
const test = anyTest;
|
|
13
|
+
const FT_WASM_FILEPATH = "./sandbox-test/fungible_token.wasm";
|
|
14
|
+
const NFT_WASM_FILEPATH = "./sandbox-test/non_fungible_token.wasm";
|
|
15
|
+
test.beforeEach(async (t) => {
|
|
16
|
+
// Init the worker and start a Sandbox server
|
|
17
|
+
const worker = t.context.worker = await Worker.init();
|
|
18
|
+
|
|
19
|
+
// Create accounts
|
|
20
|
+
const root = worker.rootAccount;
|
|
21
|
+
|
|
22
|
+
const alice = await root.createSubAccount("alice",{initialBalance: NEAR.parse("10 N").toString()});
|
|
23
|
+
const bob = await root.createSubAccount("bob",{initialBalance: NEAR.parse("10 N").toString()});
|
|
24
|
+
const contract = await root.createSubAccount("contract",{initialBalance: NEAR.parse("10 N").toString()});
|
|
25
|
+
const auctioneer = await root.createSubAccount("auctioneer",{initialBalance: NEAR.parse("10 N").toString()});
|
|
26
|
+
|
|
27
|
+
// Deploy and initialize FT contract
|
|
28
|
+
const ft_contract = await root.devDeploy(FT_WASM_FILEPATH);
|
|
29
|
+
await ft_contract.call(ft_contract,"new_default_meta",{"owner_id":ft_contract.accountId,"total_supply":BigInt(1_000_000).toString()});
|
|
30
|
+
|
|
31
|
+
// Deploy and initialize NFT contract
|
|
32
|
+
const nft_contract = await root.devDeploy(NFT_WASM_FILEPATH);
|
|
33
|
+
await nft_contract.call(nft_contract, "new_default_meta", { "owner_id": nft_contract.accountId });
|
|
34
|
+
|
|
35
|
+
// Mint NFT
|
|
36
|
+
const token_id = "1";
|
|
37
|
+
let request_payload = {
|
|
38
|
+
"token_id": token_id,
|
|
39
|
+
"receiver_id": contract.accountId,
|
|
40
|
+
"metadata": {
|
|
41
|
+
"title": "LEEROYYYMMMJENKINSSS",
|
|
42
|
+
"description": "Alright time's up, let's do this.",
|
|
43
|
+
"media": "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse3.mm.bing.net%2Fth%3Fid%3DOIP.Fhp4lHufCdTzTeGCAblOdgHaF7%26pid%3DApi&f=1"
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await nft_contract.call(nft_contract,"nft_mint",request_payload,{ attachedDeposit: NEAR.from("8000000000000000000000").toString(),gas: "300000000000000" });
|
|
48
|
+
|
|
49
|
+
// Register accounts in FT contract
|
|
50
|
+
const contracts = [alice,bob,contract,auctioneer];
|
|
51
|
+
for (const contract_to_register of contracts) {
|
|
52
|
+
await contract_to_register.call(ft_contract, "storage_deposit",{ "account_id": contract_to_register.accountId },{ attachedDeposit: NEAR.from("8000000000000000000000").toString(),gas: "300000000000000" })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Give accounts FTs
|
|
56
|
+
await ft_contract.call(ft_contract,"ft_transfer",{"receiver_id":alice.accountId,"amount":BigInt(150_000).toString()},{ attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" });
|
|
57
|
+
await ft_contract.call(ft_contract,"ft_transfer",{"receiver_id":bob.accountId,"amount":BigInt(150_000).toString()},{ attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" });
|
|
58
|
+
|
|
59
|
+
// Deploy contract (input from package.json)
|
|
60
|
+
await contract.deploy(process.argv[2]);
|
|
61
|
+
|
|
62
|
+
// Initialize contract, finishes in 1 minute
|
|
63
|
+
await contract.call(contract, "init", {
|
|
64
|
+
end_time: String((Date.now() + 60000) * 10 ** 6),
|
|
65
|
+
auctioneer: auctioneer.accountId,
|
|
66
|
+
ft_contract: ft_contract.accountId,
|
|
67
|
+
nft_contract: nft_contract.accountId,
|
|
68
|
+
token_id: token_id,
|
|
69
|
+
starting_price: BigInt(10_000).toString()
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Save state for test runs, it is unique for each test
|
|
73
|
+
t.context.worker = worker;
|
|
74
|
+
t.context.accounts = { alice, bob, contract, auctioneer,ft_contract,nft_contract};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
test.afterEach.always(async (t) => {
|
|
79
|
+
// Stop Sandbox server
|
|
80
|
+
await t.context.worker.tearDown().catch((error) => {
|
|
81
|
+
console.log('Failed to stop the Sandbox:', error);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
test("Test full contract", async (t) => {
|
|
87
|
+
const { alice, bob, auctioneer, contract, nft_contract, ft_contract } = t.context.accounts;
|
|
88
|
+
|
|
89
|
+
// Alice makes bid less than starting price
|
|
90
|
+
await alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(5_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" });
|
|
91
|
+
let highest_bid = await contract.view("get_highest_bid", {});
|
|
92
|
+
t.is(highest_bid.bidder, contract.accountId);
|
|
93
|
+
t.is(highest_bid.bid, BigInt(10_000).toString());
|
|
94
|
+
|
|
95
|
+
let contract_balance = await ft_contract.view("ft_balance_of",{"account_id": contract.accountId})
|
|
96
|
+
t.is(contract_balance, BigInt(0).toString());
|
|
97
|
+
let alice_balance = await ft_contract.view("ft_balance_of",{"account_id": alice.accountId})
|
|
98
|
+
t.is(alice_balance, BigInt(150_000).toString());
|
|
99
|
+
|
|
100
|
+
// Alice makes valid bid
|
|
101
|
+
await alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(50_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" });
|
|
102
|
+
highest_bid = await contract.view("get_highest_bid", {});
|
|
103
|
+
t.is(highest_bid.bidder, alice.accountId);
|
|
104
|
+
t.is(highest_bid.bid, BigInt(50_000).toString());
|
|
105
|
+
|
|
106
|
+
contract_balance = await ft_contract.view("ft_balance_of",{"account_id": contract.accountId})
|
|
107
|
+
t.is(contract_balance, BigInt(50_000).toString());
|
|
108
|
+
alice_balance = await ft_contract.view("ft_balance_of",{"account_id": alice.accountId})
|
|
109
|
+
t.is(alice_balance, BigInt(100_000).toString());
|
|
110
|
+
|
|
111
|
+
// Bob makes a higher bid
|
|
112
|
+
await bob.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(60_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" });
|
|
113
|
+
highest_bid = await contract.view("get_highest_bid", {});
|
|
114
|
+
t.is(highest_bid.bidder, bob.accountId);
|
|
115
|
+
t.is(highest_bid.bid, BigInt(60_000).toString());
|
|
116
|
+
|
|
117
|
+
// Check Alice recieved her bid back
|
|
118
|
+
const aliceNewBalance = await ft_contract.view("ft_balance_of",{"account_id": alice.accountId});
|
|
119
|
+
t.is(BigInt(150_000).toString(),aliceNewBalance);
|
|
120
|
+
|
|
121
|
+
// Alice tries to make a bid with less FTs than the previous
|
|
122
|
+
await alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(50_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" });
|
|
123
|
+
highest_bid = await contract.view("get_highest_bid", {});
|
|
124
|
+
t.is(highest_bid.bidder, bob.accountId);
|
|
125
|
+
t.is(highest_bid.bid, BigInt(60_000).toString());
|
|
126
|
+
|
|
127
|
+
contract_balance = await ft_contract.view("ft_balance_of",{"account_id": contract.accountId})
|
|
128
|
+
t.is(contract_balance, BigInt(60_000).toString());
|
|
129
|
+
alice_balance = await ft_contract.view("ft_balance_of",{"account_id": alice.accountId})
|
|
130
|
+
t.is(alice_balance, BigInt(150_000).toString());
|
|
131
|
+
|
|
132
|
+
// Auctioneer claims auction but did not finish
|
|
133
|
+
await t.throwsAsync(auctioneer.call(contract, "claim",{},{ gas: "300000000000000" }))
|
|
134
|
+
|
|
135
|
+
// Fast forward 200 blocks
|
|
136
|
+
await t.context.worker.provider.fastForward(200)
|
|
137
|
+
|
|
138
|
+
// Auctioneer claims auction
|
|
139
|
+
await auctioneer.call(contract, "claim",{},{ gas: "300000000000000" });
|
|
140
|
+
|
|
141
|
+
contract_balance = await ft_contract.view("ft_balance_of",{"account_id": contract.accountId})
|
|
142
|
+
t.is(contract_balance, BigInt(0).toString());
|
|
143
|
+
const auctioneer_balance = await ft_contract.view("ft_balance_of",{"account_id": auctioneer.accountId});
|
|
144
|
+
t.is(auctioneer_balance, BigInt(60_000).toString());
|
|
145
|
+
|
|
146
|
+
// Check highest bidder received the NFT
|
|
147
|
+
const response = await nft_contract.call(nft_contract, "nft_token",{"token_id": "1"},{ gas: "300000000000000" });
|
|
148
|
+
t.is(response.owner_id,bob.accountId);
|
|
149
|
+
|
|
150
|
+
// Auctioneer claims auction back but fails
|
|
151
|
+
await t.throwsAsync(auctioneer.call(contract, "claim",{},{ gas: "300000000000000" }))
|
|
152
|
+
|
|
153
|
+
// Alice tries to make a bid when the auction is over
|
|
154
|
+
await alice.call(ft_contract, "ft_transfer_call", { "receiver_id": contract.accountId,"amount": BigInt(70_000).toString(),"msg":""}, { attachedDeposit: NEAR.from("1").toString(),gas: "300000000000000" });
|
|
155
|
+
highest_bid = await contract.view("get_highest_bid", {});
|
|
156
|
+
t.is(highest_bid.bidder, bob.accountId);
|
|
157
|
+
t.is(highest_bid.bid, BigInt(60_000).toString());
|
|
158
|
+
|
|
159
|
+
contract_balance = await ft_contract.view("ft_balance_of",{"account_id": contract.accountId})
|
|
160
|
+
t.is(contract_balance, BigInt(0).toString());
|
|
161
|
+
alice_balance = await ft_contract.view("ft_balance_of",{"account_id": alice.accountId})
|
|
162
|
+
t.is(alice_balance, BigInt(150_000).toString());
|
|
163
|
+
let bob_balance = await ft_contract.view("ft_balance_of",{"account_id": bob.accountId})
|
|
164
|
+
t.is(bob_balance, BigInt(90_000).toString());
|
|
165
|
+
});
|