@btc-vision/btc-runtime 1.10.8 → 1.10.11
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/LICENSE +190 -0
- package/README.md +258 -137
- package/SECURITY.md +226 -0
- package/docs/README.md +614 -0
- package/docs/advanced/bitcoin-scripts.md +939 -0
- package/docs/advanced/cross-contract-calls.md +579 -0
- package/docs/advanced/plugins.md +1006 -0
- package/docs/advanced/quantum-resistance.md +660 -0
- package/docs/advanced/signature-verification.md +715 -0
- package/docs/api-reference/blockchain.md +729 -0
- package/docs/api-reference/events.md +642 -0
- package/docs/api-reference/op20.md +902 -0
- package/docs/api-reference/op721.md +819 -0
- package/docs/api-reference/safe-math.md +510 -0
- package/docs/api-reference/storage.md +840 -0
- package/docs/contracts/op-net-base.md +786 -0
- package/docs/contracts/op20-token.md +687 -0
- package/docs/contracts/op20s-signatures.md +614 -0
- package/docs/contracts/op721-nft.md +785 -0
- package/docs/contracts/reentrancy-guard.md +787 -0
- package/docs/core-concepts/blockchain-environment.md +724 -0
- package/docs/core-concepts/decorators.md +466 -0
- package/docs/core-concepts/events.md +652 -0
- package/docs/core-concepts/pointers.md +391 -0
- package/docs/core-concepts/security.md +473 -0
- package/docs/core-concepts/storage-system.md +969 -0
- package/docs/examples/basic-token.md +745 -0
- package/docs/examples/nft-with-reservations.md +1440 -0
- package/docs/examples/oracle-integration.md +1212 -0
- package/docs/examples/stablecoin.md +1180 -0
- package/docs/getting-started/first-contract.md +575 -0
- package/docs/getting-started/installation.md +384 -0
- package/docs/getting-started/project-structure.md +630 -0
- package/docs/storage/memory-maps.md +764 -0
- package/docs/storage/stored-arrays.md +778 -0
- package/docs/storage/stored-maps.md +758 -0
- package/docs/storage/stored-primitives.md +655 -0
- package/docs/types/address.md +773 -0
- package/docs/types/bytes-writer-reader.md +938 -0
- package/docs/types/calldata.md +744 -0
- package/docs/types/safe-math.md +446 -0
- package/package.json +52 -27
- package/runtime/memory/MapOfMap.ts +1 -0
- package/LICENSE.md +0 -21
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
# Your First Contract
|
|
2
|
+
|
|
3
|
+
This tutorial guides you through creating a complete OP20 token contract from scratch. By the end, you'll understand the core concepts of OPNet smart contract development.
|
|
4
|
+
|
|
5
|
+
## What We're Building
|
|
6
|
+
|
|
7
|
+
A simple fungible token (like an ERC20 on Ethereum) with:
|
|
8
|
+
- Fixed maximum supply
|
|
9
|
+
- Minting capability (deployer only)
|
|
10
|
+
- Transfer functionality
|
|
11
|
+
- Balance queries
|
|
12
|
+
|
|
13
|
+
## Step 1: Create the Contract File
|
|
14
|
+
|
|
15
|
+
Create `src/token/MyToken.ts`:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
19
|
+
import {
|
|
20
|
+
Blockchain,
|
|
21
|
+
BytesWriter,
|
|
22
|
+
Calldata,
|
|
23
|
+
OP20,
|
|
24
|
+
OP20InitParameters,
|
|
25
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
26
|
+
|
|
27
|
+
@final
|
|
28
|
+
export class MyToken extends OP20 {
|
|
29
|
+
public constructor() {
|
|
30
|
+
super();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public override onDeployment(_calldata: Calldata): void {
|
|
34
|
+
const maxSupply: u256 = u256.fromString('1000000000000000000000000');
|
|
35
|
+
const decimals: u8 = 18;
|
|
36
|
+
const name: string = 'MyToken';
|
|
37
|
+
const symbol: string = 'MTK';
|
|
38
|
+
|
|
39
|
+
this.instantiate(new OP20InitParameters(maxSupply, decimals, name, symbol));
|
|
40
|
+
this._mint(Blockchain.tx.origin, maxSupply);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@method(
|
|
44
|
+
{ name: 'address', type: ABIDataTypes.ADDRESS },
|
|
45
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 }
|
|
46
|
+
)
|
|
47
|
+
@emit('Minted')
|
|
48
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
49
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
50
|
+
|
|
51
|
+
this._mint(calldata.readAddress(), calldata.readU256());
|
|
52
|
+
|
|
53
|
+
return new BytesWriter(0);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Let's break this down piece by piece.
|
|
59
|
+
|
|
60
|
+
## Contract Lifecycle Overview
|
|
61
|
+
|
|
62
|
+
This diagram illustrates the complete lifecycle of an OPNet smart contract from deployment to execution:
|
|
63
|
+
|
|
64
|
+
```mermaid
|
|
65
|
+
---
|
|
66
|
+
config:
|
|
67
|
+
theme: dark
|
|
68
|
+
---
|
|
69
|
+
flowchart LR
|
|
70
|
+
A["Deployment"] --> B["onDeployment()"]
|
|
71
|
+
B --> C["Initialize Parameters"]
|
|
72
|
+
C --> D["Contract Active"]
|
|
73
|
+
D --> E{"Transaction"}
|
|
74
|
+
E --> F["execute()"]
|
|
75
|
+
F -->|"transfer()"| G["Transfer Tokens"]
|
|
76
|
+
F -->|"mint()"| H["Mint Tokens"]
|
|
77
|
+
F -->|"balanceOf()"| I["Query Balance"]
|
|
78
|
+
F -->|"approve()"| J["Approve Spender"]
|
|
79
|
+
G --> K["Complete"]
|
|
80
|
+
H --> K
|
|
81
|
+
I --> K
|
|
82
|
+
J --> K
|
|
83
|
+
K --> E
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Step 2: Understanding the Code
|
|
87
|
+
|
|
88
|
+
### Token Contract Architecture
|
|
89
|
+
|
|
90
|
+
This diagram shows how your MyToken contract inherits functionality from the OP20 base class:
|
|
91
|
+
|
|
92
|
+
```mermaid
|
|
93
|
+
---
|
|
94
|
+
config:
|
|
95
|
+
theme: dark
|
|
96
|
+
---
|
|
97
|
+
classDiagram
|
|
98
|
+
class OP_NET {
|
|
99
|
+
+Address address
|
|
100
|
+
+Address contractDeployer
|
|
101
|
+
+onDeployment(calldata)
|
|
102
|
+
+execute(selector, calldata)
|
|
103
|
+
+onlyDeployer(caller)
|
|
104
|
+
+emitEvent(event)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
class ReentrancyGuard {
|
|
108
|
+
-u8 reentrancyStatus
|
|
109
|
+
+nonReentrant()
|
|
110
|
+
+protected()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class OP20 {
|
|
114
|
+
+StoredU256 _totalSupply
|
|
115
|
+
+StoredString _name
|
|
116
|
+
+StoredString _symbol
|
|
117
|
+
+u8 _decimals
|
|
118
|
+
+u256 _maxSupply
|
|
119
|
+
+transfer(to, amount)
|
|
120
|
+
+approve(spender, amount)
|
|
121
|
+
+balanceOf(address)
|
|
122
|
+
+_mint(to, amount)
|
|
123
|
+
+_burn(from, amount)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class MyToken {
|
|
127
|
+
+onDeployment(calldata)
|
|
128
|
+
+mint(calldata)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
OP_NET <|-- ReentrancyGuard
|
|
132
|
+
ReentrancyGuard <|-- OP20
|
|
133
|
+
OP20 <|-- MyToken
|
|
134
|
+
|
|
135
|
+
note for MyToken "Custom implementation:\n- Deployment logic\n- Additional mint function"
|
|
136
|
+
note for OP20 "Built-in methods:\n- transfer\n- approve\n- balanceOf\n- totalSupply"
|
|
137
|
+
note for OP_NET "Base contract:\n- Access control\n- Event system\n- Execution router"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### The Class Declaration
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
@final
|
|
144
|
+
export class MyToken extends OP20 {
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
| Component | Meaning |
|
|
148
|
+
|-----------|---------|
|
|
149
|
+
| `@final` | AssemblyScript decorator - prevents inheritance |
|
|
150
|
+
| `export` | Makes the class accessible outside the file |
|
|
151
|
+
| `extends OP20` | Inherits from the fungible token standard |
|
|
152
|
+
|
|
153
|
+
**Solidity equivalent:**
|
|
154
|
+
```solidity
|
|
155
|
+
contract MyToken is ERC20 {
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### The Constructor
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
public constructor() {
|
|
162
|
+
super();
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**IMPORTANT:** In OPNet, the constructor runs on **every** contract interaction, not just deployment. This is different from Solidity!
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// OPNet // Solidity
|
|
170
|
+
public constructor() { // constructor() {
|
|
171
|
+
super(); // // Runs ONCE at deployment
|
|
172
|
+
// Runs EVERY time! // }
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Never put initialization logic in the constructor. Use `onDeployment` instead.
|
|
177
|
+
|
|
178
|
+
### The Deployment Hook
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
public override onDeployment(_calldata: Calldata): void {
|
|
182
|
+
const maxSupply: u256 = u256.fromString('1000000000000000000000000');
|
|
183
|
+
const decimals: u8 = 18;
|
|
184
|
+
const name: string = 'MyToken';
|
|
185
|
+
const symbol: string = 'MTK';
|
|
186
|
+
|
|
187
|
+
this.instantiate(new OP20InitParameters(maxSupply, decimals, name, symbol));
|
|
188
|
+
this._mint(Blockchain.tx.origin, maxSupply);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
This method runs **once** when the contract is first deployed. It's the equivalent of Solidity's `constructor()`.
|
|
193
|
+
|
|
194
|
+
| Parameter | Value | Meaning |
|
|
195
|
+
|-----------|-------|---------|
|
|
196
|
+
| `maxSupply` | 1,000,000 (with 18 decimals) | Maximum tokens that can ever exist |
|
|
197
|
+
| `decimals` | 18 | Decimal places (like ETH/wei) |
|
|
198
|
+
| `name` | "MyToken" | Human-readable name |
|
|
199
|
+
| `symbol` | "MTK" | Ticker symbol |
|
|
200
|
+
|
|
201
|
+
**Solidity equivalent:**
|
|
202
|
+
```solidity
|
|
203
|
+
constructor() ERC20("MyToken", "MTK") {
|
|
204
|
+
_mint(msg.sender, 1000000 * 10**18);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### The Mint Function
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
@method(
|
|
212
|
+
{ name: 'address', type: ABIDataTypes.ADDRESS },
|
|
213
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 }
|
|
214
|
+
)
|
|
215
|
+
@emit('Minted')
|
|
216
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
217
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
218
|
+
|
|
219
|
+
this._mint(calldata.readAddress(), calldata.readU256());
|
|
220
|
+
|
|
221
|
+
return new BytesWriter(0);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Mint Operation Data Flow
|
|
226
|
+
|
|
227
|
+
This sequence diagram shows what happens when the mint function is called:
|
|
228
|
+
|
|
229
|
+
```mermaid
|
|
230
|
+
---
|
|
231
|
+
config:
|
|
232
|
+
theme: dark
|
|
233
|
+
---
|
|
234
|
+
sequenceDiagram
|
|
235
|
+
participant User as 👤 User
|
|
236
|
+
participant Blockchain as Blockchain
|
|
237
|
+
participant MyToken as MyToken
|
|
238
|
+
participant OP20 as OP20
|
|
239
|
+
participant Storage as Storage
|
|
240
|
+
|
|
241
|
+
User->>Blockchain: Call mint(to, amount)
|
|
242
|
+
Blockchain->>MyToken: constructor()
|
|
243
|
+
Note over MyToken: Runs on EVERY call
|
|
244
|
+
Blockchain->>MyToken: execute(selector, calldata)
|
|
245
|
+
|
|
246
|
+
MyToken->>MyToken: mint(calldata)
|
|
247
|
+
MyToken->>Blockchain: Check tx.sender
|
|
248
|
+
Blockchain-->>MyToken: sender address
|
|
249
|
+
|
|
250
|
+
MyToken->>MyToken: onlyDeployer(sender)
|
|
251
|
+
alt sender != deployer
|
|
252
|
+
MyToken-->>User: Revert("Only deployer")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
MyToken->>MyToken: readAddress() from calldata
|
|
256
|
+
MyToken->>MyToken: readU256() from calldata
|
|
257
|
+
|
|
258
|
+
MyToken->>OP20: _mint(to, amount)
|
|
259
|
+
|
|
260
|
+
OP20->>Storage: Read current balance[to]
|
|
261
|
+
Storage-->>OP20: currentBalance
|
|
262
|
+
|
|
263
|
+
OP20->>OP20: newBalance = currentBalance + amount
|
|
264
|
+
|
|
265
|
+
OP20->>Storage: Write balance[to] = newBalance
|
|
266
|
+
OP20->>Storage: Read totalSupply
|
|
267
|
+
Storage-->>OP20: currentSupply
|
|
268
|
+
|
|
269
|
+
OP20->>OP20: newSupply = currentSupply + amount
|
|
270
|
+
OP20->>Storage: Write totalSupply = newSupply
|
|
271
|
+
|
|
272
|
+
OP20->>Blockchain: emit(MintEvent)
|
|
273
|
+
|
|
274
|
+
OP20-->>MyToken: Success
|
|
275
|
+
MyToken->>MyToken: new BytesWriter(0)
|
|
276
|
+
MyToken-->>Blockchain: Empty response
|
|
277
|
+
Blockchain-->>User: Transaction success
|
|
278
|
+
|
|
279
|
+
Note over Storage: All changes persisted<br/>to blockchain state
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Breaking this down:
|
|
283
|
+
|
|
284
|
+
| Line | Purpose |
|
|
285
|
+
|------|---------|
|
|
286
|
+
| `@method(...)` | Declares method parameters for ABI generation |
|
|
287
|
+
| `@emit('Minted')` | Declares event emission for ABI documentation |
|
|
288
|
+
| `onlyDeployer(...)` | Access control - only the deployer can call |
|
|
289
|
+
| `calldata.readAddress()` | Parse the recipient address from input |
|
|
290
|
+
| `calldata.readU256()` | Parse the amount from input |
|
|
291
|
+
| `_mint(to, amount)` | Internal mint function from OP20 |
|
|
292
|
+
| `return new BytesWriter(0)` | Return empty response |
|
|
293
|
+
|
|
294
|
+
**Solidity equivalent:**
|
|
295
|
+
```solidity
|
|
296
|
+
function mint(address to, uint256 amount) external onlyOwner {
|
|
297
|
+
_mint(to, amount);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Step 3: Understanding Types
|
|
302
|
+
|
|
303
|
+
### u256 - Big Numbers
|
|
304
|
+
|
|
305
|
+
OPNet uses `u256` for large numbers (like balances):
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
309
|
+
|
|
310
|
+
// Creating u256 values
|
|
311
|
+
const a = u256.fromU64(100); // From small number
|
|
312
|
+
const b = u256.fromU64(1_000_000); // From u64
|
|
313
|
+
const c = u256.fromString('99999999999999'); // From string (large numbers)
|
|
314
|
+
|
|
315
|
+
// NEVER use floating point!
|
|
316
|
+
// const bad = u256.fromU64(1.5); // WRONG! - No floating point!
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Why not native numbers?**
|
|
320
|
+
|
|
321
|
+
| JavaScript/TypeScript | AssemblyScript/OPNet |
|
|
322
|
+
|----------------------|----------------------|
|
|
323
|
+
| `number` (64-bit float) | Non-deterministic! |
|
|
324
|
+
| `BigInt` | Not supported in WASM |
|
|
325
|
+
| N/A | `u256` (deterministic) |
|
|
326
|
+
|
|
327
|
+
### Address
|
|
328
|
+
|
|
329
|
+
Addresses are 32 bytes in OPNet:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { Address, Blockchain } from '@btc-vision/btc-runtime/runtime';
|
|
333
|
+
|
|
334
|
+
// Get the current sender
|
|
335
|
+
const sender: Address = Blockchain.tx.sender;
|
|
336
|
+
|
|
337
|
+
// Zero address (like address(0) in Solidity)
|
|
338
|
+
const zero = Address.zero();
|
|
339
|
+
|
|
340
|
+
// Compare addresses
|
|
341
|
+
if (sender.equals(zero)) {
|
|
342
|
+
throw new Revert('Cannot be zero address');
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Calldata
|
|
347
|
+
|
|
348
|
+
Input parsing uses `Calldata`:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
public myMethod(calldata: Calldata): BytesWriter {
|
|
352
|
+
// Read parameters in order
|
|
353
|
+
const address = calldata.readAddress(); // 32 bytes
|
|
354
|
+
const amount = calldata.readU256(); // 32 bytes
|
|
355
|
+
const flag = calldata.readBoolean(); // 1 byte
|
|
356
|
+
const data = calldata.readBytes(); // Variable length
|
|
357
|
+
|
|
358
|
+
// ...
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Step 4: Inherited OP20 Methods
|
|
363
|
+
|
|
364
|
+
By extending `OP20`, your token automatically gets these methods:
|
|
365
|
+
|
|
366
|
+
| Method | Description | Selector |
|
|
367
|
+
|--------|-------------|----------|
|
|
368
|
+
| `transfer(to, amount)` | Transfer tokens | Built-in |
|
|
369
|
+
| `transferFrom(from, to, amount)` | Transfer with approval | Built-in |
|
|
370
|
+
| `approve(spender, amount)` | Approve spender | Built-in |
|
|
371
|
+
| `balanceOf(address)` | Get balance | Built-in |
|
|
372
|
+
| `allowance(owner, spender)` | Get allowance | Built-in |
|
|
373
|
+
| `totalSupply()` | Total supply | Built-in |
|
|
374
|
+
| `name()` | Token name | Built-in |
|
|
375
|
+
| `symbol()` | Token symbol | Built-in |
|
|
376
|
+
| `decimals()` | Decimal places | Built-in |
|
|
377
|
+
|
|
378
|
+
## Step 5: Building the Contract
|
|
379
|
+
|
|
380
|
+
Add to your `package.json`:
|
|
381
|
+
|
|
382
|
+
```json
|
|
383
|
+
{
|
|
384
|
+
"scripts": {
|
|
385
|
+
"build:token": "asc src/token/index.ts --target token --measure --uncheckedBehavior never"
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Create `src/token/index.ts`:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { Blockchain } from '@btc-vision/btc-runtime/runtime';
|
|
394
|
+
import { revertOnError } from '@btc-vision/btc-runtime/runtime/abort/abort';
|
|
395
|
+
import { MyToken } from './MyToken';
|
|
396
|
+
|
|
397
|
+
// DO NOT TOUCH TO THIS.
|
|
398
|
+
Blockchain.contract = () => {
|
|
399
|
+
// ONLY CHANGE THE CONTRACT CLASS NAME.
|
|
400
|
+
// DO NOT ADD CUSTOM LOGIC HERE.
|
|
401
|
+
|
|
402
|
+
return new MyToken();
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// VERY IMPORTANT
|
|
406
|
+
export * from '@btc-vision/btc-runtime/runtime/exports';
|
|
407
|
+
|
|
408
|
+
// VERY IMPORTANT
|
|
409
|
+
export function abort(message: string, fileName: string, line: u32, column: u32): void {
|
|
410
|
+
revertOnError(message, fileName, line, column);
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Build:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
npm run build:token
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Solidity Comparison
|
|
421
|
+
|
|
422
|
+
Here's a side-by-side comparison of the complete contract:
|
|
423
|
+
|
|
424
|
+
<table>
|
|
425
|
+
<tr>
|
|
426
|
+
<th>OPNet (AssemblyScript)</th>
|
|
427
|
+
<th>Solidity</th>
|
|
428
|
+
</tr>
|
|
429
|
+
<tr>
|
|
430
|
+
<td>
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
434
|
+
import {
|
|
435
|
+
Blockchain,
|
|
436
|
+
BytesWriter,
|
|
437
|
+
Calldata,
|
|
438
|
+
OP20,
|
|
439
|
+
OP20InitParameters,
|
|
440
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
441
|
+
|
|
442
|
+
@final
|
|
443
|
+
export class MyToken extends OP20 {
|
|
444
|
+
public constructor() {
|
|
445
|
+
super();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
public override onDeployment(_: Calldata): void {
|
|
449
|
+
const maxSupply = u256.fromString('1000000000000000000000000');
|
|
450
|
+
this.instantiate(new OP20InitParameters(
|
|
451
|
+
maxSupply,
|
|
452
|
+
18,
|
|
453
|
+
'MyToken',
|
|
454
|
+
'MTK'
|
|
455
|
+
));
|
|
456
|
+
this._mint(Blockchain.tx.origin, maxSupply);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
@method(
|
|
460
|
+
{ name: 'address', type: ABIDataTypes.ADDRESS },
|
|
461
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 }
|
|
462
|
+
)
|
|
463
|
+
@emit('Minted')
|
|
464
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
465
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
466
|
+
this._mint(
|
|
467
|
+
calldata.readAddress(),
|
|
468
|
+
calldata.readU256()
|
|
469
|
+
);
|
|
470
|
+
return new BytesWriter(0);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
</td>
|
|
476
|
+
<td>
|
|
477
|
+
|
|
478
|
+
```solidity
|
|
479
|
+
// SPDX-License-Identifier: MIT
|
|
480
|
+
pragma solidity ^0.8.0;
|
|
481
|
+
|
|
482
|
+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
483
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
484
|
+
|
|
485
|
+
contract MyToken is ERC20, Ownable {
|
|
486
|
+
uint256 public constant MAX_SUPPLY =
|
|
487
|
+
1000000 * 10**18;
|
|
488
|
+
|
|
489
|
+
constructor()
|
|
490
|
+
ERC20("MyToken", "MTK")
|
|
491
|
+
Ownable(msg.sender)
|
|
492
|
+
{
|
|
493
|
+
_mint(msg.sender, MAX_SUPPLY);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function mint(
|
|
497
|
+
address to,
|
|
498
|
+
uint256 amount
|
|
499
|
+
) external onlyOwner {
|
|
500
|
+
_mint(to, amount);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
</td>
|
|
506
|
+
</tr>
|
|
507
|
+
</table>
|
|
508
|
+
|
|
509
|
+
## Common Patterns
|
|
510
|
+
|
|
511
|
+
### Access Control
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
// Only deployer can call
|
|
515
|
+
@method({ name: 'param', type: ABIDataTypes.UINT256 })
|
|
516
|
+
@returns({ name: 'result', type: ABIDataTypes.UINT256 })
|
|
517
|
+
public adminFunction(calldata: Calldata): BytesWriter {
|
|
518
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
519
|
+
const param = calldata.readU256();
|
|
520
|
+
// ... perform admin logic
|
|
521
|
+
const result = new BytesWriter(32);
|
|
522
|
+
result.writeU256(param);
|
|
523
|
+
return result;
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Error Handling
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
import { Revert, Address, BytesWriter, Calldata } from '@btc-vision/btc-runtime/runtime';
|
|
531
|
+
|
|
532
|
+
@method(
|
|
533
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
534
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 }
|
|
535
|
+
)
|
|
536
|
+
@emit('Transferred')
|
|
537
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
538
|
+
const to = calldata.readAddress();
|
|
539
|
+
const amount = calldata.readU256();
|
|
540
|
+
|
|
541
|
+
if (to.equals(Address.zero())) {
|
|
542
|
+
throw new Revert('Cannot transfer to zero address');
|
|
543
|
+
}
|
|
544
|
+
// ...
|
|
545
|
+
return new BytesWriter(0);
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Reading Storage
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
import { SafeMath } from '@btc-vision/btc-runtime/runtime';
|
|
553
|
+
|
|
554
|
+
// In OP20, balances are managed automatically
|
|
555
|
+
const balance: u256 = this.balanceOf(address);
|
|
556
|
+
|
|
557
|
+
// When performing u256 operations, always use SafeMath
|
|
558
|
+
const newBalance = SafeMath.add(balance, amount);
|
|
559
|
+
const result = SafeMath.sub(balance, amount);
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Next Steps
|
|
563
|
+
|
|
564
|
+
Now that you've created your first contract:
|
|
565
|
+
|
|
566
|
+
1. [Understand the project structure](./project-structure.md)
|
|
567
|
+
2. [Learn about the blockchain environment](../core-concepts/blockchain-environment.md)
|
|
568
|
+
3. [Explore storage in depth](../core-concepts/storage-system.md)
|
|
569
|
+
4. [See more examples](../examples/basic-token.md)
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
**Navigation:**
|
|
574
|
+
- Previous: [Installation](./installation.md)
|
|
575
|
+
- Next: [Project Structure](./project-structure.md)
|