movementkit-cli 1.0.1 → 1.0.3
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/index.js +11 -7
- package/kits/engineer/.claude/agents/devops.md +176 -0
- package/kits/engineer/.claude/agents/frontend.md +207 -0
- package/kits/engineer/.claude/agents/smart-contract.md +150 -0
- package/kits/engineer/.claude/agents/tester.md +174 -0
- package/kits/engineer/.claude/commands/cook/contracts.md +174 -0
- package/kits/engineer/.claude/commands/cook/frontend.md +325 -0
- package/kits/engineer/.claude/commands/cook.md +118 -0
- package/kits/engineer/.claude/commands/deploy-full.md +158 -0
- package/kits/engineer/.claude/commands/deploy-smart-contract.md +177 -0
- package/kits/engineer/.claude/commands/docs/generate.md +121 -0
- package/kits/engineer/.claude/commands/docs/init.md +132 -0
- package/kits/engineer/.claude/commands/plan.md +103 -0
- package/kits/engineer/.claude/commands/review.md +98 -0
- package/kits/engineer/.claude/commands/test.md +92 -0
- package/kits/engineer/.claude/commands/watzup.md +100 -0
- package/kits/engineer/.claude/workflows/development-rules.md +110 -0
- package/kits/engineer/.claude/workflows/primary-workflow.md +95 -0
- package/kits/engineer/CLAUDE.md +105 -0
- package/kits/engineer/contracts/Move.toml +13 -0
- package/kits/engineer/contracts/sources/counter.move +122 -0
- package/kits/engineer/contracts/tests/counter_tests.move +96 -0
- package/kits/engineer/docs/MOVE_LANGUAGE_REFERENCE.md +560 -0
- package/kits/engineer/frontend/.env.example +9 -0
- package/kits/engineer/frontend/index.html +14 -0
- package/kits/engineer/frontend/package.json +29 -0
- package/kits/engineer/frontend/src/App.tsx +41 -0
- package/kits/engineer/frontend/src/components/WalletConnect.tsx +54 -0
- package/kits/engineer/frontend/src/contexts/WalletContext.tsx +42 -0
- package/kits/engineer/frontend/src/hooks/useContract.ts +95 -0
- package/kits/engineer/frontend/src/index.css +76 -0
- package/kits/engineer/frontend/src/main.tsx +11 -0
- package/kits/engineer/frontend/tsconfig.json +22 -0
- package/kits/engineer/frontend/tsconfig.node.json +11 -0
- package/kits/engineer/frontend/vite.config.ts +17 -0
- package/package.json +3 -2
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# Move Language Reference for Movement Blockchain
|
|
2
|
+
|
|
3
|
+
This document provides a comprehensive reference for writing Move smart contracts on the Movement blockchain.
|
|
4
|
+
|
|
5
|
+
## Movement Network Configuration
|
|
6
|
+
|
|
7
|
+
### Network Endpoints
|
|
8
|
+
```
|
|
9
|
+
Mainnet: https://full.mainnet.movementinfra.xyz/v1 (Chain ID: 126)
|
|
10
|
+
Testnet: https://full.testnet.movementinfra.xyz/v1 (Chain ID: 250)
|
|
11
|
+
Faucet: https://faucet.movementnetwork.xyz/
|
|
12
|
+
Explorer: https://explorer.movementnetwork.xyz/
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### CLI Commands
|
|
16
|
+
```bash
|
|
17
|
+
# Initialize a new Move project
|
|
18
|
+
movement move init --name project_name
|
|
19
|
+
|
|
20
|
+
# Compile contracts
|
|
21
|
+
movement move compile
|
|
22
|
+
|
|
23
|
+
# Run tests
|
|
24
|
+
movement move test
|
|
25
|
+
|
|
26
|
+
# Publish to testnet
|
|
27
|
+
movement move publish --url https://full.testnet.movementinfra.xyz/v1 --named-addresses module_addr=default
|
|
28
|
+
|
|
29
|
+
# Publish to mainnet
|
|
30
|
+
movement move publish --url https://full.mainnet.movementinfra.xyz/v1 --named-addresses module_addr=default
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Move.toml Configuration
|
|
34
|
+
|
|
35
|
+
```toml
|
|
36
|
+
[package]
|
|
37
|
+
name = "project_name"
|
|
38
|
+
version = "1.0.0"
|
|
39
|
+
authors = []
|
|
40
|
+
|
|
41
|
+
[addresses]
|
|
42
|
+
module_addr = "_"
|
|
43
|
+
|
|
44
|
+
[dependencies]
|
|
45
|
+
AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-framework", rev = "mainnet" }
|
|
46
|
+
AptosStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/aptos-stdlib", rev = "mainnet" }
|
|
47
|
+
MoveStdlib = { git = "https://github.com/aptos-labs/aptos-core.git", subdir = "aptos-move/framework/move-stdlib", rev = "mainnet" }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Module Structure
|
|
51
|
+
|
|
52
|
+
```move
|
|
53
|
+
module module_addr::module_name {
|
|
54
|
+
// Imports
|
|
55
|
+
use std::signer;
|
|
56
|
+
use std::string::String;
|
|
57
|
+
use std::vector;
|
|
58
|
+
use std::option::{Self, Option};
|
|
59
|
+
use aptos_framework::event;
|
|
60
|
+
use aptos_framework::account;
|
|
61
|
+
use aptos_framework::timestamp;
|
|
62
|
+
use aptos_framework::coin;
|
|
63
|
+
use aptos_framework::aptos_coin::AptosCoin;
|
|
64
|
+
|
|
65
|
+
// Error codes (constants)
|
|
66
|
+
const E_NOT_AUTHORIZED: u64 = 1;
|
|
67
|
+
const E_ALREADY_EXISTS: u64 = 2;
|
|
68
|
+
const E_NOT_FOUND: u64 = 3;
|
|
69
|
+
const E_INSUFFICIENT_BALANCE: u64 = 4;
|
|
70
|
+
|
|
71
|
+
// Resources (structs with 'key' ability)
|
|
72
|
+
struct ResourceName has key, store {
|
|
73
|
+
field1: u64,
|
|
74
|
+
field2: String,
|
|
75
|
+
field3: vector<u8>,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Events
|
|
79
|
+
#[event]
|
|
80
|
+
struct EventName has drop, store {
|
|
81
|
+
actor: address,
|
|
82
|
+
value: u64,
|
|
83
|
+
timestamp: u64,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Entry functions (callable from transactions)
|
|
87
|
+
public entry fun function_name(
|
|
88
|
+
account: &signer,
|
|
89
|
+
param1: u64,
|
|
90
|
+
) acquires ResourceName {
|
|
91
|
+
// Implementation
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// View functions (read-only, callable without transaction)
|
|
95
|
+
#[view]
|
|
96
|
+
public fun get_value(addr: address): u64 acquires ResourceName {
|
|
97
|
+
borrow_global<ResourceName>(addr).field1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Internal functions
|
|
101
|
+
fun internal_helper(): u64 {
|
|
102
|
+
42
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Primitive Types
|
|
108
|
+
|
|
109
|
+
| Type | Description | Example |
|
|
110
|
+
|------|-------------|---------|
|
|
111
|
+
| `u8` | 8-bit unsigned integer | `let x: u8 = 255;` |
|
|
112
|
+
| `u16` | 16-bit unsigned integer | `let x: u16 = 65535;` |
|
|
113
|
+
| `u32` | 32-bit unsigned integer | `let x: u32 = 100;` |
|
|
114
|
+
| `u64` | 64-bit unsigned integer | `let x: u64 = 1000000;` |
|
|
115
|
+
| `u128` | 128-bit unsigned integer | `let x: u128 = 1000000;` |
|
|
116
|
+
| `u256` | 256-bit unsigned integer | `let x: u256 = 1000000;` |
|
|
117
|
+
| `bool` | Boolean | `let b: bool = true;` |
|
|
118
|
+
| `address` | 32-byte address | `let a: address = @0x1;` |
|
|
119
|
+
| `vector<T>` | Dynamic array | `let v: vector<u8> = vector[];` |
|
|
120
|
+
| `String` | UTF-8 string | `use std::string::String;` |
|
|
121
|
+
|
|
122
|
+
## Abilities
|
|
123
|
+
|
|
124
|
+
| Ability | Description |
|
|
125
|
+
|---------|-------------|
|
|
126
|
+
| `copy` | Value can be copied |
|
|
127
|
+
| `drop` | Value can be dropped (destroyed) |
|
|
128
|
+
| `store` | Value can be stored in global storage |
|
|
129
|
+
| `key` | Value can be used as a key in global storage |
|
|
130
|
+
|
|
131
|
+
```move
|
|
132
|
+
// Resource that can be stored globally
|
|
133
|
+
struct MyResource has key, store {
|
|
134
|
+
value: u64,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Struct that can be copied and dropped
|
|
138
|
+
struct MyData has copy, drop, store {
|
|
139
|
+
value: u64,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Event struct (must have drop and store)
|
|
143
|
+
#[event]
|
|
144
|
+
struct MyEvent has drop, store {
|
|
145
|
+
value: u64,
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Global Storage Operations
|
|
150
|
+
|
|
151
|
+
```move
|
|
152
|
+
// Store a resource at an address
|
|
153
|
+
move_to<ResourceName>(account, ResourceName { field1: 0, field2: string::utf8(b"hello") });
|
|
154
|
+
|
|
155
|
+
// Check if resource exists
|
|
156
|
+
exists<ResourceName>(addr)
|
|
157
|
+
|
|
158
|
+
// Borrow immutable reference
|
|
159
|
+
let resource_ref = borrow_global<ResourceName>(addr);
|
|
160
|
+
|
|
161
|
+
// Borrow mutable reference
|
|
162
|
+
let resource_mut = borrow_global_mut<ResourceName>(addr);
|
|
163
|
+
|
|
164
|
+
// Remove resource from storage
|
|
165
|
+
let resource = move_from<ResourceName>(addr);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Signer Operations
|
|
169
|
+
|
|
170
|
+
```move
|
|
171
|
+
use std::signer;
|
|
172
|
+
|
|
173
|
+
public entry fun my_function(account: &signer) {
|
|
174
|
+
// Get address from signer
|
|
175
|
+
let addr = signer::address_of(account);
|
|
176
|
+
|
|
177
|
+
// Validate caller
|
|
178
|
+
assert!(addr == @admin_address, E_NOT_AUTHORIZED);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Events
|
|
183
|
+
|
|
184
|
+
```move
|
|
185
|
+
use aptos_framework::event;
|
|
186
|
+
|
|
187
|
+
// Define event struct
|
|
188
|
+
#[event]
|
|
189
|
+
struct TransferEvent has drop, store {
|
|
190
|
+
from: address,
|
|
191
|
+
to: address,
|
|
192
|
+
amount: u64,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Emit event
|
|
196
|
+
event::emit(TransferEvent {
|
|
197
|
+
from: sender_addr,
|
|
198
|
+
to: recipient_addr,
|
|
199
|
+
amount: 100,
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Vectors
|
|
204
|
+
|
|
205
|
+
```move
|
|
206
|
+
use std::vector;
|
|
207
|
+
|
|
208
|
+
// Create empty vector
|
|
209
|
+
let v: vector<u64> = vector[];
|
|
210
|
+
|
|
211
|
+
// Create with initial values
|
|
212
|
+
let v = vector[1, 2, 3];
|
|
213
|
+
|
|
214
|
+
// Push element
|
|
215
|
+
vector::push_back(&mut v, 4);
|
|
216
|
+
|
|
217
|
+
// Pop element
|
|
218
|
+
let last = vector::pop_back(&mut v);
|
|
219
|
+
|
|
220
|
+
// Get length
|
|
221
|
+
let len = vector::length(&v);
|
|
222
|
+
|
|
223
|
+
// Check if empty
|
|
224
|
+
let is_empty = vector::is_empty(&v);
|
|
225
|
+
|
|
226
|
+
// Get element by index (immutable)
|
|
227
|
+
let elem = vector::borrow(&v, 0);
|
|
228
|
+
|
|
229
|
+
// Get element by index (mutable)
|
|
230
|
+
let elem_mut = vector::borrow_mut(&mut v, 0);
|
|
231
|
+
|
|
232
|
+
// Check if contains
|
|
233
|
+
let contains = vector::contains(&v, &42);
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Strings
|
|
237
|
+
|
|
238
|
+
```move
|
|
239
|
+
use std::string::{Self, String};
|
|
240
|
+
|
|
241
|
+
// Create from bytes
|
|
242
|
+
let s: String = string::utf8(b"Hello, World!");
|
|
243
|
+
|
|
244
|
+
// Get length
|
|
245
|
+
let len = string::length(&s);
|
|
246
|
+
|
|
247
|
+
// Check if empty
|
|
248
|
+
let is_empty = string::is_empty(&s);
|
|
249
|
+
|
|
250
|
+
// Append
|
|
251
|
+
string::append(&mut s, string::utf8(b" More text"));
|
|
252
|
+
|
|
253
|
+
// Convert to bytes
|
|
254
|
+
let bytes: vector<u8> = *string::bytes(&s);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Options
|
|
258
|
+
|
|
259
|
+
```move
|
|
260
|
+
use std::option::{Self, Option};
|
|
261
|
+
|
|
262
|
+
// Create Some
|
|
263
|
+
let opt: Option<u64> = option::some(42);
|
|
264
|
+
|
|
265
|
+
// Create None
|
|
266
|
+
let none: Option<u64> = option::none();
|
|
267
|
+
|
|
268
|
+
// Check if some
|
|
269
|
+
let is_some = option::is_some(&opt);
|
|
270
|
+
|
|
271
|
+
// Check if none
|
|
272
|
+
let is_none = option::is_none(&none);
|
|
273
|
+
|
|
274
|
+
// Extract value (aborts if none)
|
|
275
|
+
let value = option::extract(&mut opt);
|
|
276
|
+
|
|
277
|
+
// Get with default
|
|
278
|
+
let value = option::get_with_default(&opt, 0);
|
|
279
|
+
|
|
280
|
+
// Borrow value
|
|
281
|
+
let value_ref = option::borrow(&opt);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Coin Operations
|
|
285
|
+
|
|
286
|
+
```move
|
|
287
|
+
use aptos_framework::coin;
|
|
288
|
+
use aptos_framework::aptos_coin::AptosCoin;
|
|
289
|
+
|
|
290
|
+
// Register coin store for account
|
|
291
|
+
coin::register<AptosCoin>(account);
|
|
292
|
+
|
|
293
|
+
// Get balance
|
|
294
|
+
let balance = coin::balance<AptosCoin>(addr);
|
|
295
|
+
|
|
296
|
+
// Transfer coins
|
|
297
|
+
coin::transfer<AptosCoin>(from, to_addr, amount);
|
|
298
|
+
|
|
299
|
+
// Withdraw coins
|
|
300
|
+
let coins = coin::withdraw<AptosCoin>(account, amount);
|
|
301
|
+
|
|
302
|
+
// Deposit coins
|
|
303
|
+
coin::deposit(to_addr, coins);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Timestamp
|
|
307
|
+
|
|
308
|
+
```move
|
|
309
|
+
use aptos_framework::timestamp;
|
|
310
|
+
|
|
311
|
+
// Get current timestamp in seconds
|
|
312
|
+
let now_seconds = timestamp::now_seconds();
|
|
313
|
+
|
|
314
|
+
// Get current timestamp in microseconds
|
|
315
|
+
let now_microseconds = timestamp::now_microseconds();
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Testing
|
|
319
|
+
|
|
320
|
+
### Basic Test
|
|
321
|
+
```move
|
|
322
|
+
#[test_only]
|
|
323
|
+
module module_addr::module_name_tests {
|
|
324
|
+
use std::signer;
|
|
325
|
+
use module_addr::module_name;
|
|
326
|
+
|
|
327
|
+
#[test(account = @0x1)]
|
|
328
|
+
fun test_basic_function(account: &signer) {
|
|
329
|
+
// Setup
|
|
330
|
+
let addr = signer::address_of(account);
|
|
331
|
+
|
|
332
|
+
// Action
|
|
333
|
+
module_name::initialize(account);
|
|
334
|
+
|
|
335
|
+
// Assert
|
|
336
|
+
assert!(module_name::get_value(addr) == 0, 0);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Test with Expected Failure
|
|
342
|
+
```move
|
|
343
|
+
#[test]
|
|
344
|
+
#[expected_failure(abort_code = module_name::E_NOT_AUTHORIZED)]
|
|
345
|
+
fun test_unauthorized_access() {
|
|
346
|
+
// This should fail with E_NOT_AUTHORIZED
|
|
347
|
+
module_name::admin_only_function();
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Test with Multiple Signers
|
|
352
|
+
```move
|
|
353
|
+
#[test(admin = @0x1, user = @0x2)]
|
|
354
|
+
fun test_with_multiple_accounts(admin: &signer, user: &signer) {
|
|
355
|
+
// Setup admin
|
|
356
|
+
module_name::initialize(admin);
|
|
357
|
+
|
|
358
|
+
// User interacts
|
|
359
|
+
module_name::user_action(user);
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Test with Framework
|
|
364
|
+
```move
|
|
365
|
+
#[test(aptos_framework = @aptos_framework, account = @0x1)]
|
|
366
|
+
fun test_with_framework(aptos_framework: &signer, account: &signer) {
|
|
367
|
+
// Setup timestamp for testing
|
|
368
|
+
timestamp::set_time_has_started_for_testing(aptos_framework);
|
|
369
|
+
|
|
370
|
+
// Now timestamp::now_seconds() works in tests
|
|
371
|
+
module_name::time_dependent_function(account);
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Common Patterns
|
|
376
|
+
|
|
377
|
+
### Initialization Pattern
|
|
378
|
+
```move
|
|
379
|
+
struct Config has key {
|
|
380
|
+
admin: address,
|
|
381
|
+
is_initialized: bool,
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
public entry fun initialize(admin: &signer) {
|
|
385
|
+
let admin_addr = signer::address_of(admin);
|
|
386
|
+
assert!(!exists<Config>(admin_addr), E_ALREADY_EXISTS);
|
|
387
|
+
|
|
388
|
+
move_to(admin, Config {
|
|
389
|
+
admin: admin_addr,
|
|
390
|
+
is_initialized: true,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Admin-Only Pattern
|
|
396
|
+
```move
|
|
397
|
+
public entry fun admin_function(admin: &signer) acquires Config {
|
|
398
|
+
let admin_addr = signer::address_of(admin);
|
|
399
|
+
let config = borrow_global<Config>(@module_addr);
|
|
400
|
+
assert!(config.admin == admin_addr, E_NOT_AUTHORIZED);
|
|
401
|
+
|
|
402
|
+
// Admin-only logic
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Counter Pattern
|
|
407
|
+
```move
|
|
408
|
+
struct Counter has key {
|
|
409
|
+
value: u64,
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
public entry fun increment(account: &signer) acquires Counter {
|
|
413
|
+
let addr = signer::address_of(account);
|
|
414
|
+
|
|
415
|
+
if (!exists<Counter>(addr)) {
|
|
416
|
+
move_to(account, Counter { value: 0 });
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
let counter = borrow_global_mut<Counter>(addr);
|
|
420
|
+
counter.value = counter.value + 1;
|
|
421
|
+
|
|
422
|
+
event::emit(CounterIncremented {
|
|
423
|
+
account: addr,
|
|
424
|
+
new_value: counter.value
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
#[view]
|
|
429
|
+
public fun get_count(addr: address): u64 acquires Counter {
|
|
430
|
+
if (!exists<Counter>(addr)) {
|
|
431
|
+
return 0
|
|
432
|
+
};
|
|
433
|
+
borrow_global<Counter>(addr).value
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Token/NFT Collection Pattern
|
|
438
|
+
```move
|
|
439
|
+
struct Collection has key {
|
|
440
|
+
items: vector<Item>,
|
|
441
|
+
next_id: u64,
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
struct Item has store, drop {
|
|
445
|
+
id: u64,
|
|
446
|
+
name: String,
|
|
447
|
+
owner: address,
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
public entry fun mint(account: &signer, name: String) acquires Collection {
|
|
451
|
+
let addr = signer::address_of(account);
|
|
452
|
+
let collection = borrow_global_mut<Collection>(@module_addr);
|
|
453
|
+
|
|
454
|
+
let item = Item {
|
|
455
|
+
id: collection.next_id,
|
|
456
|
+
name,
|
|
457
|
+
owner: addr,
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
vector::push_back(&mut collection.items, item);
|
|
461
|
+
collection.next_id = collection.next_id + 1;
|
|
462
|
+
|
|
463
|
+
event::emit(ItemMinted { id: collection.next_id - 1, owner: addr });
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Security Best Practices
|
|
468
|
+
|
|
469
|
+
1. **Always validate signers** - Check that the caller has permission
|
|
470
|
+
2. **Use assert! with error codes** - Provide clear error messages
|
|
471
|
+
3. **Emit events for state changes** - Enable off-chain tracking
|
|
472
|
+
4. **Check for resource existence** - Use `exists<T>(addr)` before accessing
|
|
473
|
+
5. **Avoid unbounded loops** - Can cause out-of-gas errors
|
|
474
|
+
6. **Use acquires annotation** - Declare all resources accessed
|
|
475
|
+
7. **Initialize before use** - Ensure resources exist before borrowing
|
|
476
|
+
|
|
477
|
+
## Complete Example: Simple Token
|
|
478
|
+
|
|
479
|
+
```move
|
|
480
|
+
module module_addr::simple_token {
|
|
481
|
+
use std::signer;
|
|
482
|
+
use std::string::String;
|
|
483
|
+
use aptos_framework::event;
|
|
484
|
+
|
|
485
|
+
// Errors
|
|
486
|
+
const E_NOT_INITIALIZED: u64 = 1;
|
|
487
|
+
const E_INSUFFICIENT_BALANCE: u64 = 2;
|
|
488
|
+
|
|
489
|
+
// Resources
|
|
490
|
+
struct TokenInfo has key {
|
|
491
|
+
name: String,
|
|
492
|
+
symbol: String,
|
|
493
|
+
total_supply: u64,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
struct Balance has key {
|
|
497
|
+
value: u64,
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Events
|
|
501
|
+
#[event]
|
|
502
|
+
struct Transfer has drop, store {
|
|
503
|
+
from: address,
|
|
504
|
+
to: address,
|
|
505
|
+
amount: u64,
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Initialize token
|
|
509
|
+
public entry fun initialize(
|
|
510
|
+
admin: &signer,
|
|
511
|
+
name: String,
|
|
512
|
+
symbol: String,
|
|
513
|
+
initial_supply: u64,
|
|
514
|
+
) {
|
|
515
|
+
let admin_addr = signer::address_of(admin);
|
|
516
|
+
|
|
517
|
+
move_to(admin, TokenInfo {
|
|
518
|
+
name,
|
|
519
|
+
symbol,
|
|
520
|
+
total_supply: initial_supply,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
move_to(admin, Balance { value: initial_supply });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Transfer tokens
|
|
527
|
+
public entry fun transfer(
|
|
528
|
+
from: &signer,
|
|
529
|
+
to: address,
|
|
530
|
+
amount: u64,
|
|
531
|
+
) acquires Balance {
|
|
532
|
+
let from_addr = signer::address_of(from);
|
|
533
|
+
|
|
534
|
+
// Deduct from sender
|
|
535
|
+
let from_balance = borrow_global_mut<Balance>(from_addr);
|
|
536
|
+
assert!(from_balance.value >= amount, E_INSUFFICIENT_BALANCE);
|
|
537
|
+
from_balance.value = from_balance.value - amount;
|
|
538
|
+
|
|
539
|
+
// Add to recipient
|
|
540
|
+
if (!exists<Balance>(to)) {
|
|
541
|
+
// Create balance for new recipient (requires signer, simplified here)
|
|
542
|
+
// In practice, recipient would need to register first
|
|
543
|
+
};
|
|
544
|
+
let to_balance = borrow_global_mut<Balance>(to);
|
|
545
|
+
to_balance.value = to_balance.value + amount;
|
|
546
|
+
|
|
547
|
+
event::emit(Transfer { from: from_addr, to, amount });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// View balance
|
|
551
|
+
#[view]
|
|
552
|
+
public fun balance_of(addr: address): u64 acquires Balance {
|
|
553
|
+
if (!exists<Balance>(addr)) {
|
|
554
|
+
return 0
|
|
555
|
+
};
|
|
556
|
+
borrow_global<Balance>(addr).value
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Movement dApp</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
14
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "movement-dapp-frontend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Frontend for Movement blockchain dApp",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"test": "vitest",
|
|
11
|
+
"test:coverage": "vitest --coverage"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@aptos-labs/ts-sdk": "^1.33.1",
|
|
15
|
+
"@aptos-labs/wallet-adapter-react": "^3.0.0",
|
|
16
|
+
"@radix-ui/react-dialog": "^1.0.5",
|
|
17
|
+
"react": "^18.2.0",
|
|
18
|
+
"react-dom": "^18.2.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/react": "^18.2.0",
|
|
22
|
+
"@types/react-dom": "^18.2.0",
|
|
23
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
24
|
+
"typescript": "^5.3.0",
|
|
25
|
+
"vite": "^5.0.0",
|
|
26
|
+
"vitest": "^1.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { WalletProvider } from "./contexts/WalletContext";
|
|
2
|
+
import { WalletConnect } from "./components/WalletConnect";
|
|
3
|
+
|
|
4
|
+
function App() {
|
|
5
|
+
return (
|
|
6
|
+
<WalletProvider>
|
|
7
|
+
<div className="app">
|
|
8
|
+
<header>
|
|
9
|
+
<h1>🚀 Movement dApp</h1>
|
|
10
|
+
<p>Built with Movement Kit</p>
|
|
11
|
+
</header>
|
|
12
|
+
|
|
13
|
+
<main>
|
|
14
|
+
<div className="card">
|
|
15
|
+
<WalletConnect />
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div className="card">
|
|
19
|
+
<h2>Getting Started</h2>
|
|
20
|
+
<p>Connect your wallet to interact with the Movement blockchain.</p>
|
|
21
|
+
</div>
|
|
22
|
+
</main>
|
|
23
|
+
|
|
24
|
+
<footer>
|
|
25
|
+
<p>
|
|
26
|
+
<a href="https://explorer.movementnetwork.xyz/" target="_blank" rel="noopener noreferrer">
|
|
27
|
+
Movement Explorer
|
|
28
|
+
</a>
|
|
29
|
+
{" | "}
|
|
30
|
+
<a href="https://faucet.movementnetwork.xyz/" target="_blank" rel="noopener noreferrer">
|
|
31
|
+
Faucet
|
|
32
|
+
</a>
|
|
33
|
+
</p>
|
|
34
|
+
</footer>
|
|
35
|
+
</div>
|
|
36
|
+
</WalletProvider>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default App;
|
|
41
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useWallet } from "@aptos-labs/wallet-adapter-react";
|
|
2
|
+
|
|
3
|
+
export function WalletConnect() {
|
|
4
|
+
const {
|
|
5
|
+
connect,
|
|
6
|
+
disconnect,
|
|
7
|
+
account,
|
|
8
|
+
connected,
|
|
9
|
+
wallets,
|
|
10
|
+
isLoading,
|
|
11
|
+
} = useWallet();
|
|
12
|
+
|
|
13
|
+
// Format address for display
|
|
14
|
+
const formatAddress = (address: string) => {
|
|
15
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (isLoading) {
|
|
19
|
+
return <div>Loading wallet...</div>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (connected && account) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="wallet-connected">
|
|
25
|
+
<p>
|
|
26
|
+
<strong>Connected:</strong> {formatAddress(account.address.toString())}
|
|
27
|
+
</p>
|
|
28
|
+
<button onClick={disconnect}>
|
|
29
|
+
Disconnect Wallet
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="wallet-connect">
|
|
37
|
+
<h3>Connect Wallet</h3>
|
|
38
|
+
<div className="wallet-options">
|
|
39
|
+
{wallets?.filter(wallet => wallet.readyState === "Installed").map((wallet) => (
|
|
40
|
+
<button
|
|
41
|
+
key={wallet.name}
|
|
42
|
+
onClick={() => connect(wallet.name)}
|
|
43
|
+
>
|
|
44
|
+
Connect {wallet.name}
|
|
45
|
+
</button>
|
|
46
|
+
))}
|
|
47
|
+
{wallets?.filter(wallet => wallet.readyState === "Installed").length === 0 && (
|
|
48
|
+
<p>No wallets detected. Please install a wallet extension.</p>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|