@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,745 @@
|
|
|
1
|
+
# Basic Token Example
|
|
2
|
+
|
|
3
|
+
A complete, production-ready OP20 token implementation with minting, burning, and administrative controls.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This example demonstrates:
|
|
8
|
+
- OP20 token initialization
|
|
9
|
+
- Decorators (`@method`, `@returns`, `@emit`)
|
|
10
|
+
- Custom minting function
|
|
11
|
+
- Burn functionality
|
|
12
|
+
- Access control
|
|
13
|
+
- Event emission
|
|
14
|
+
|
|
15
|
+
## Token Lifecycle
|
|
16
|
+
|
|
17
|
+
The token follows a standard lifecycle from deployment through minting, transfers, and burning:
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
---
|
|
21
|
+
config:
|
|
22
|
+
theme: dark
|
|
23
|
+
---
|
|
24
|
+
graph LR
|
|
25
|
+
subgraph "👤 User Interactions"
|
|
26
|
+
A["Deploy Contract"]
|
|
27
|
+
B["Call mint"]
|
|
28
|
+
C["Call transfer"]
|
|
29
|
+
D["Call burn"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
subgraph "Bitcoin Layer"
|
|
33
|
+
E[Deploy TX]
|
|
34
|
+
F[Mint TX]
|
|
35
|
+
G[Transfer TX]
|
|
36
|
+
H[Burn TX]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
subgraph "Contract Execution"
|
|
40
|
+
I[Initialize Token]
|
|
41
|
+
J[Mint Tokens]
|
|
42
|
+
K[Transfer Tokens]
|
|
43
|
+
L[Burn Tokens]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
subgraph "Storage Operations"
|
|
47
|
+
M[Store Metadata]
|
|
48
|
+
N[Update Balances]
|
|
49
|
+
O[Update Total Supply]
|
|
50
|
+
P[Reduce Supply]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
A --> E
|
|
54
|
+
E --> I
|
|
55
|
+
I --> M
|
|
56
|
+
|
|
57
|
+
B --> F
|
|
58
|
+
F --> J
|
|
59
|
+
J --> N
|
|
60
|
+
J --> O
|
|
61
|
+
|
|
62
|
+
C --> G
|
|
63
|
+
G --> K
|
|
64
|
+
K --> N
|
|
65
|
+
|
|
66
|
+
D --> H
|
|
67
|
+
H --> L
|
|
68
|
+
L --> N
|
|
69
|
+
L --> P
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Complete Implementation
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
76
|
+
import {
|
|
77
|
+
OP20,
|
|
78
|
+
OP20InitParameters,
|
|
79
|
+
Blockchain,
|
|
80
|
+
Address,
|
|
81
|
+
Calldata,
|
|
82
|
+
BytesWriter,
|
|
83
|
+
SafeMath,
|
|
84
|
+
Revert,
|
|
85
|
+
MintEvent,
|
|
86
|
+
BurnEvent,
|
|
87
|
+
ABIDataTypes,
|
|
88
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
89
|
+
|
|
90
|
+
@final
|
|
91
|
+
export class BasicToken extends OP20 {
|
|
92
|
+
public constructor() {
|
|
93
|
+
super();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Initialize the token on deployment.
|
|
98
|
+
* This is equivalent to Solidity's constructor.
|
|
99
|
+
*/
|
|
100
|
+
public override onDeployment(calldata: Calldata): void {
|
|
101
|
+
// Read initialization parameters
|
|
102
|
+
const maxSupply = calldata.readU256();
|
|
103
|
+
const decimals = calldata.readU8();
|
|
104
|
+
const name = calldata.readString();
|
|
105
|
+
const symbol = calldata.readString();
|
|
106
|
+
const initialMintTo = calldata.readAddress();
|
|
107
|
+
const initialMintAmount = calldata.readU256();
|
|
108
|
+
|
|
109
|
+
// Validate decimals
|
|
110
|
+
if (decimals > 32) {
|
|
111
|
+
throw new Revert('Decimals cannot exceed 32');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Validate initial mint doesn't exceed max supply
|
|
115
|
+
if (initialMintAmount > maxSupply) {
|
|
116
|
+
throw new Revert('Initial mint exceeds max supply');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Initialize OP20
|
|
120
|
+
this.instantiate(new OP20InitParameters(maxSupply, decimals, name, symbol));
|
|
121
|
+
|
|
122
|
+
// Mint initial supply if specified
|
|
123
|
+
if (!initialMintAmount.isZero() && !initialMintTo.equals(Address.zero())) {
|
|
124
|
+
this._mint(initialMintTo, initialMintAmount);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Mint new tokens to an address.
|
|
130
|
+
* Only callable by the contract deployer.
|
|
131
|
+
*/
|
|
132
|
+
@method(
|
|
133
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
134
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
135
|
+
)
|
|
136
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
137
|
+
@emit('Minted')
|
|
138
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
139
|
+
// Access control
|
|
140
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
141
|
+
|
|
142
|
+
// Read parameters
|
|
143
|
+
const to = calldata.readAddress();
|
|
144
|
+
const amount = calldata.readU256();
|
|
145
|
+
|
|
146
|
+
// Validate
|
|
147
|
+
if (to.equals(Address.zero())) {
|
|
148
|
+
throw new Revert('Cannot mint to zero address');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (amount.isZero()) {
|
|
152
|
+
throw new Revert('Mint amount must be positive');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check max supply
|
|
156
|
+
const currentSupply = this.totalSupply();
|
|
157
|
+
const newSupply = SafeMath.add(currentSupply, amount);
|
|
158
|
+
if (newSupply > this.maxSupply()) {
|
|
159
|
+
throw new Revert('Mint would exceed max supply');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Mint tokens
|
|
163
|
+
this._mint(to, amount);
|
|
164
|
+
|
|
165
|
+
// Emit event
|
|
166
|
+
this.emitEvent(new MintEvent(to, amount));
|
|
167
|
+
|
|
168
|
+
return new BytesWriter(0);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Burn tokens from the caller's balance.
|
|
173
|
+
*/
|
|
174
|
+
@method({ name: 'amount', type: ABIDataTypes.UINT256 })
|
|
175
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
176
|
+
@emit('Burned')
|
|
177
|
+
public burn(calldata: Calldata): BytesWriter {
|
|
178
|
+
const amount = calldata.readU256();
|
|
179
|
+
|
|
180
|
+
// Validate
|
|
181
|
+
if (amount.isZero()) {
|
|
182
|
+
throw new Revert('Burn amount must be positive');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const sender = Blockchain.tx.sender;
|
|
186
|
+
const balance = this.balanceOf(sender);
|
|
187
|
+
|
|
188
|
+
if (balance < amount) {
|
|
189
|
+
throw new Revert('Burn amount exceeds balance');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Burn tokens
|
|
193
|
+
this._burn(sender, amount);
|
|
194
|
+
|
|
195
|
+
// Emit event
|
|
196
|
+
this.emitEvent(new BurnEvent(sender, amount));
|
|
197
|
+
|
|
198
|
+
return new BytesWriter(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Burn tokens from another account using allowance.
|
|
203
|
+
*/
|
|
204
|
+
@method(
|
|
205
|
+
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
206
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
207
|
+
)
|
|
208
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
209
|
+
@emit('Burned')
|
|
210
|
+
public burnFrom(calldata: Calldata): BytesWriter {
|
|
211
|
+
const from = calldata.readAddress();
|
|
212
|
+
const amount = calldata.readU256();
|
|
213
|
+
const sender = Blockchain.tx.sender;
|
|
214
|
+
|
|
215
|
+
// Check allowance
|
|
216
|
+
const currentAllowance = this.allowance(from, sender);
|
|
217
|
+
if (currentAllowance < amount) {
|
|
218
|
+
throw new Revert('Burn amount exceeds allowance');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Check balance
|
|
222
|
+
const balance = this.balanceOf(from);
|
|
223
|
+
if (balance < amount) {
|
|
224
|
+
throw new Revert('Burn amount exceeds balance');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Update allowance (unless unlimited)
|
|
228
|
+
if (!currentAllowance.equals(u256.Max)) {
|
|
229
|
+
this._approve(from, sender, SafeMath.sub(currentAllowance, amount));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Burn tokens
|
|
233
|
+
this._burn(from, amount);
|
|
234
|
+
|
|
235
|
+
// Emit event
|
|
236
|
+
this.emitEvent(new BurnEvent(from, amount));
|
|
237
|
+
|
|
238
|
+
return new BytesWriter(0);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get token metadata in a single call.
|
|
243
|
+
*/
|
|
244
|
+
@method()
|
|
245
|
+
@returns(
|
|
246
|
+
{ name: 'name', type: ABIDataTypes.STRING },
|
|
247
|
+
{ name: 'symbol', type: ABIDataTypes.STRING },
|
|
248
|
+
{ name: 'decimals', type: ABIDataTypes.UINT8 },
|
|
249
|
+
{ name: 'totalSupply', type: ABIDataTypes.UINT256 },
|
|
250
|
+
{ name: 'maxSupply', type: ABIDataTypes.UINT256 },
|
|
251
|
+
)
|
|
252
|
+
public tokenInfo(_: Calldata): BytesWriter {
|
|
253
|
+
const name = this._name.value;
|
|
254
|
+
const symbol = this._symbol.value;
|
|
255
|
+
|
|
256
|
+
const writer = new BytesWriter(256);
|
|
257
|
+
writer.writeString(name);
|
|
258
|
+
writer.writeString(symbol);
|
|
259
|
+
writer.writeU8(this._decimals.value);
|
|
260
|
+
writer.writeU256(this.totalSupply());
|
|
261
|
+
writer.writeU256(this.maxSupply());
|
|
262
|
+
return writer;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Token Supply States
|
|
268
|
+
|
|
269
|
+
The token supply transitions through different states based on mint and burn operations:
|
|
270
|
+
|
|
271
|
+
```mermaid
|
|
272
|
+
stateDiagram-v2
|
|
273
|
+
[*] --> Initialized: Deploy Contract
|
|
274
|
+
Initialized --> HasSupply: Initial Mint TX
|
|
275
|
+
HasSupply --> SupplyIncreased: mint() TX
|
|
276
|
+
SupplyIncreased --> HasSupply
|
|
277
|
+
HasSupply --> SupplyDecreased: burn() TX
|
|
278
|
+
SupplyDecreased --> HasSupply
|
|
279
|
+
|
|
280
|
+
note right of HasSupply
|
|
281
|
+
Storage: Current Supply <= Max Supply
|
|
282
|
+
Enforced on every mint operation
|
|
283
|
+
end note
|
|
284
|
+
|
|
285
|
+
note right of SupplyIncreased
|
|
286
|
+
Only deployer can mint
|
|
287
|
+
Max supply check performed
|
|
288
|
+
end note
|
|
289
|
+
|
|
290
|
+
note right of SupplyDecreased
|
|
291
|
+
Any holder can burn
|
|
292
|
+
Reduces total supply in storage
|
|
293
|
+
end note
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Mint Operation Flow
|
|
297
|
+
|
|
298
|
+
The mint operation includes multiple validation checks before updating storage:
|
|
299
|
+
|
|
300
|
+
```mermaid
|
|
301
|
+
sequenceDiagram
|
|
302
|
+
participant User as 👤 User/Deployer
|
|
303
|
+
participant BTC as Bitcoin Network
|
|
304
|
+
participant Contract as Contract Execution
|
|
305
|
+
participant Storage as Storage Layer
|
|
306
|
+
participant Events as Event System
|
|
307
|
+
|
|
308
|
+
User->>BTC: Submit mint TX
|
|
309
|
+
BTC->>Contract: Execute mint(to, amount)
|
|
310
|
+
Contract->>Contract: onlyDeployer check
|
|
311
|
+
alt Not deployer
|
|
312
|
+
Contract-->>BTC: Revert: Not deployer
|
|
313
|
+
BTC-->>User: TX Failed
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
Contract->>Contract: Validate to != zero address
|
|
317
|
+
alt Invalid address
|
|
318
|
+
Contract-->>BTC: Revert: Zero address
|
|
319
|
+
BTC-->>User: TX Failed
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
Contract->>Contract: Validate amount > 0
|
|
323
|
+
alt Invalid amount
|
|
324
|
+
Contract-->>BTC: Revert: Amount must be positive
|
|
325
|
+
BTC-->>User: TX Failed
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
Contract->>Storage: Read current supply
|
|
329
|
+
Storage-->>Contract: currentSupply
|
|
330
|
+
Contract->>Contract: newSupply = current + amount
|
|
331
|
+
Contract->>Contract: Check newSupply <= maxSupply
|
|
332
|
+
alt Exceeds max supply
|
|
333
|
+
Contract-->>BTC: Revert: Exceeds max supply
|
|
334
|
+
BTC-->>User: TX Failed
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
Contract->>Contract: _mint(to, amount)
|
|
338
|
+
Contract->>Storage: Write balance updates
|
|
339
|
+
Contract->>Storage: Write totalSupply update
|
|
340
|
+
Storage-->>Contract: Success
|
|
341
|
+
|
|
342
|
+
Contract->>Events: Emit MintEvent(to, amount)
|
|
343
|
+
Events-->>BTC: Event logged
|
|
344
|
+
Contract-->>BTC: Success (BytesWriter)
|
|
345
|
+
BTC-->>User: TX Confirmed
|
|
346
|
+
|
|
347
|
+
Note over Contract,Storage: SafeMath used for all arithmetic
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Decorator Breakdown
|
|
351
|
+
|
|
352
|
+
### Mint Method
|
|
353
|
+
|
|
354
|
+
The `@method` decorator defines the ABI parameters, `@returns` defines the return type, and `@emit` declares the event:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
@method(
|
|
358
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS }, // First parameter
|
|
359
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 }, // Second parameter
|
|
360
|
+
)
|
|
361
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL }) // Return type
|
|
362
|
+
@emit('Minted') // Emits Minted event
|
|
363
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
364
|
+
const to = calldata.readAddress(); // Read first param
|
|
365
|
+
const amount = calldata.readU256(); // Read second param
|
|
366
|
+
// ...
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Solidity Comparison:**
|
|
371
|
+
|
|
372
|
+
```solidity
|
|
373
|
+
// Solidity
|
|
374
|
+
function mint(address to, uint256 amount) external onlyOwner {
|
|
375
|
+
// ...
|
|
376
|
+
emit Minted(to, amount);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// OPNet
|
|
380
|
+
@method(
|
|
381
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
382
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
383
|
+
)
|
|
384
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
385
|
+
@emit('Minted')
|
|
386
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
387
|
+
// ...
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Burn Method
|
|
392
|
+
|
|
393
|
+
Single parameter methods use a simplified decorator syntax:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
@method({ name: 'amount', type: ABIDataTypes.UINT256 }) // Single parameter
|
|
397
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL }) // Return type
|
|
398
|
+
@emit('Burned')
|
|
399
|
+
public burn(calldata: Calldata): BytesWriter {
|
|
400
|
+
const amount = calldata.readU256();
|
|
401
|
+
// ...
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Getter with Multiple Returns
|
|
406
|
+
|
|
407
|
+
View functions use `@returns` to define output types:
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
@method() // No input parameters
|
|
411
|
+
@returns(
|
|
412
|
+
{ name: 'name', type: ABIDataTypes.STRING },
|
|
413
|
+
{ name: 'symbol', type: ABIDataTypes.STRING },
|
|
414
|
+
{ name: 'decimals', type: ABIDataTypes.UINT8 },
|
|
415
|
+
{ name: 'totalSupply', type: ABIDataTypes.UINT256 },
|
|
416
|
+
{ name: 'maxSupply', type: ABIDataTypes.UINT256 },
|
|
417
|
+
)
|
|
418
|
+
public tokenInfo(_: Calldata): BytesWriter {
|
|
419
|
+
// ...
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Usage
|
|
424
|
+
|
|
425
|
+
### Deployment
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
// Prepare deployment calldata
|
|
429
|
+
const writer = new BytesWriter(256);
|
|
430
|
+
|
|
431
|
+
// Parameters
|
|
432
|
+
writer.writeU256(u256.fromString('1000000000000000000000000')); // 1M max supply
|
|
433
|
+
writer.writeU8(18); // 18 decimals
|
|
434
|
+
writer.writeString('My Basic Token'); // name
|
|
435
|
+
writer.writeString('MBT'); // symbol
|
|
436
|
+
writer.writeAddress(deployerAddress); // initial mint to
|
|
437
|
+
writer.writeU256(u256.fromString('500000000000000000000000')); // 500k initial mint
|
|
438
|
+
|
|
439
|
+
const deployCalldata = writer.getBuffer();
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Minting
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { encodeSelector } from '@btc-vision/btc-runtime/runtime';
|
|
446
|
+
|
|
447
|
+
// Define selector (or use pre-computed u32)
|
|
448
|
+
const MINT_SELECTOR: u32 = 0x40c10f19; // mint(address,uint256)
|
|
449
|
+
|
|
450
|
+
// Prepare mint calldata
|
|
451
|
+
const writer = new BytesWriter(64);
|
|
452
|
+
writer.writeSelector(MINT_SELECTOR);
|
|
453
|
+
writer.writeAddress(recipientAddress);
|
|
454
|
+
writer.writeU256(u256.fromString('1000000000000000000000')); // 1000 tokens
|
|
455
|
+
|
|
456
|
+
const mintCalldata = writer.getBuffer();
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Burning
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
// Define selector (or use pre-computed u32)
|
|
463
|
+
const BURN_SELECTOR: u32 = 0x42966c68; // burn(uint256)
|
|
464
|
+
|
|
465
|
+
// Prepare burn calldata
|
|
466
|
+
const writer = new BytesWriter(36);
|
|
467
|
+
writer.writeSelector(BURN_SELECTOR);
|
|
468
|
+
writer.writeU256(u256.fromString('500000000000000000000')); // 500 tokens
|
|
469
|
+
|
|
470
|
+
const burnCalldata = writer.getBuffer();
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
## Key Concepts Demonstrated
|
|
474
|
+
|
|
475
|
+
### 1. Decorators
|
|
476
|
+
|
|
477
|
+
Always use decorators for public methods to define the ABI:
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
// With decorators - proper ABI generation
|
|
481
|
+
@method({ name: 'to', type: ABIDataTypes.ADDRESS })
|
|
482
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
483
|
+
@emit('Transfer')
|
|
484
|
+
public transfer(calldata: Calldata): BytesWriter
|
|
485
|
+
|
|
486
|
+
// Without decorators - no ABI, hard for callers to use
|
|
487
|
+
public transfer(calldata: Calldata): BytesWriter
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### 2. Access Control
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
494
|
+
// Only deployer can call
|
|
495
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
496
|
+
// ...
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Solidity Comparison:**
|
|
501
|
+
|
|
502
|
+
```solidity
|
|
503
|
+
// Solidity uses modifiers
|
|
504
|
+
modifier onlyOwner() {
|
|
505
|
+
require(msg.sender == owner, "Not owner");
|
|
506
|
+
_;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function mint(address to, uint256 amount) external onlyOwner { }
|
|
510
|
+
|
|
511
|
+
// OPNet uses inline checks
|
|
512
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
513
|
+
this.onlyDeployer(Blockchain.tx.sender); // Throws if not deployer
|
|
514
|
+
// ...
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 3. Input Validation
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
// Zero address check
|
|
522
|
+
if (to.equals(Address.zero())) {
|
|
523
|
+
throw new Revert('Cannot mint to zero address');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Zero amount check
|
|
527
|
+
if (amount.isZero()) {
|
|
528
|
+
throw new Revert('Mint amount must be positive');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Supply cap check
|
|
532
|
+
if (newSupply > this.maxSupply()) {
|
|
533
|
+
throw new Revert('Mint would exceed max supply');
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 4. Event Emission
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
// Mint event
|
|
541
|
+
this.emitEvent(new MintEvent(to, amount));
|
|
542
|
+
|
|
543
|
+
// Burn event
|
|
544
|
+
this.emitEvent(new BurnEvent(from, amount));
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### 5. Method Routing
|
|
548
|
+
|
|
549
|
+
Method routing is handled **AUTOMATICALLY** via `@method` decorators. You do NOT need to override the `execute` method:
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// CORRECT: Use @method decorator - routing is automatic
|
|
553
|
+
@method(
|
|
554
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
555
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
556
|
+
)
|
|
557
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
558
|
+
@emit('Minted')
|
|
559
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
560
|
+
// Implementation - runtime routes calls automatically
|
|
561
|
+
return new BytesWriter(0);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// DO NOT manually override execute() - decorators handle this
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Solidity Equivalent
|
|
568
|
+
|
|
569
|
+
For developers familiar with Solidity, here is the equivalent ERC20 implementation using OpenZeppelin:
|
|
570
|
+
|
|
571
|
+
```solidity
|
|
572
|
+
// SPDX-License-Identifier: MIT
|
|
573
|
+
pragma solidity ^0.8.20;
|
|
574
|
+
|
|
575
|
+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
576
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
577
|
+
|
|
578
|
+
contract BasicToken is ERC20, Ownable {
|
|
579
|
+
uint256 public maxSupply;
|
|
580
|
+
|
|
581
|
+
event Minted(address indexed to, uint256 amount);
|
|
582
|
+
event Burned(address indexed from, uint256 amount);
|
|
583
|
+
|
|
584
|
+
constructor(
|
|
585
|
+
string memory name,
|
|
586
|
+
string memory symbol,
|
|
587
|
+
uint256 _maxSupply,
|
|
588
|
+
address initialMintTo,
|
|
589
|
+
uint256 initialMintAmount
|
|
590
|
+
) ERC20(name, symbol) Ownable(msg.sender) {
|
|
591
|
+
maxSupply = _maxSupply;
|
|
592
|
+
if (initialMintAmount > 0 && initialMintTo != address(0)) {
|
|
593
|
+
require(initialMintAmount <= _maxSupply, "Initial mint exceeds max supply");
|
|
594
|
+
_mint(initialMintTo, initialMintAmount);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function mint(address to, uint256 amount) external onlyOwner {
|
|
599
|
+
require(to != address(0), "Cannot mint to zero address");
|
|
600
|
+
require(amount > 0, "Mint amount must be positive");
|
|
601
|
+
require(totalSupply() + amount <= maxSupply, "Mint would exceed max supply");
|
|
602
|
+
_mint(to, amount);
|
|
603
|
+
emit Minted(to, amount);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function burn(uint256 amount) external {
|
|
607
|
+
require(amount > 0, "Burn amount must be positive");
|
|
608
|
+
_burn(msg.sender, amount);
|
|
609
|
+
emit Burned(msg.sender, amount);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function burnFrom(address from, uint256 amount) external {
|
|
613
|
+
_spendAllowance(from, msg.sender, amount);
|
|
614
|
+
_burn(from, amount);
|
|
615
|
+
emit Burned(from, amount);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function tokenInfo() external view returns (
|
|
619
|
+
string memory name_,
|
|
620
|
+
string memory symbol_,
|
|
621
|
+
uint8 decimals_,
|
|
622
|
+
uint256 totalSupply_,
|
|
623
|
+
uint256 maxSupply_
|
|
624
|
+
) {
|
|
625
|
+
return (name(), symbol(), decimals(), totalSupply(), maxSupply);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
## Solidity vs OPNet Comparison
|
|
631
|
+
|
|
632
|
+
### Key Differences Table
|
|
633
|
+
|
|
634
|
+
| Aspect | Solidity (ERC20) | OPNet (OP20) |
|
|
635
|
+
|--------|------------------|--------------|
|
|
636
|
+
| **Inheritance** | `contract MyToken is ERC20, Ownable` | `class MyToken extends OP20` |
|
|
637
|
+
| **Constructor** | `constructor() ERC20("Name", "SYM")` | `onDeployment()` + `this.instantiate(new OP20InitParameters(...))` |
|
|
638
|
+
| **Mint** | `_mint(to, amount)` | `this._mint(to, amount)` |
|
|
639
|
+
| **Burn** | `_burn(from, amount)` | `this._burn(from, amount)` |
|
|
640
|
+
| **Access Control** | `modifier onlyOwner()` | `this.onlyDeployer(Blockchain.tx.sender)` |
|
|
641
|
+
| **Events** | `event Minted(...); emit Minted(...)` | `@emit('Minted')` + `this.emitEvent(new MintEvent(...))` |
|
|
642
|
+
| **Function Declaration** | `function mint(address to, uint256 amount) external` | `@method({ name: 'to', type: ABIDataTypes.ADDRESS }, ...)` |
|
|
643
|
+
| **Return Values** | `returns (uint256)` | `@returns({ name: 'value', type: ABIDataTypes.UINT256 })` |
|
|
644
|
+
| **Msg.sender** | `msg.sender` | `Blockchain.tx.sender` |
|
|
645
|
+
| **Revert** | `require(condition, "message")` or `revert("message")` | `throw new Revert('message')` |
|
|
646
|
+
| **Safe Math** | Built-in (Solidity 0.8+) | `SafeMath.add()`, `SafeMath.sub()`, etc. |
|
|
647
|
+
| **Method Routing** | Automatic via function selectors | Automatic via `@method` decorators |
|
|
648
|
+
|
|
649
|
+
### Structural Differences
|
|
650
|
+
|
|
651
|
+
**Solidity:**
|
|
652
|
+
```solidity
|
|
653
|
+
// Function with modifier
|
|
654
|
+
function mint(address to, uint256 amount) external onlyOwner {
|
|
655
|
+
require(to != address(0), "Cannot mint to zero address");
|
|
656
|
+
_mint(to, amount);
|
|
657
|
+
emit Minted(to, amount);
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**OPNet:**
|
|
662
|
+
```typescript
|
|
663
|
+
// Method with decorators
|
|
664
|
+
@method(
|
|
665
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
666
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
667
|
+
)
|
|
668
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
669
|
+
@emit('Minted')
|
|
670
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
671
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
672
|
+
const to = calldata.readAddress();
|
|
673
|
+
const amount = calldata.readU256();
|
|
674
|
+
|
|
675
|
+
if (to.equals(Address.zero())) {
|
|
676
|
+
throw new Revert('Cannot mint to zero address');
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
this._mint(to, amount);
|
|
680
|
+
this.emitEvent(new MintEvent(to, amount));
|
|
681
|
+
return new BytesWriter(0);
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Advantages of OPNet Approach
|
|
686
|
+
|
|
687
|
+
| Feature | Benefit |
|
|
688
|
+
|---------|---------|
|
|
689
|
+
| **TypeScript/AssemblyScript** | Familiar syntax for web developers, strong typing |
|
|
690
|
+
| **Explicit ABI via Decorators** | Self-documenting code, automatic ABI generation |
|
|
691
|
+
| **u256 Native Support** | First-class 256-bit integer support via `@btc-vision/as-bignum` |
|
|
692
|
+
| **Bitcoin Native** | Direct integration with Bitcoin's security model |
|
|
693
|
+
| **Unified Storage Pointers** | Consistent storage access pattern with `Blockchain.nextPointer` |
|
|
694
|
+
| **Predictable Execution** | Bitcoin transaction model provides predictable execution |
|
|
695
|
+
| **Automatic Method Routing** | `@method` decorators handle selector generation and routing |
|
|
696
|
+
|
|
697
|
+
### Initialization Pattern Comparison
|
|
698
|
+
|
|
699
|
+
**Solidity (constructor runs once at deployment):**
|
|
700
|
+
```solidity
|
|
701
|
+
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
|
|
702
|
+
// Initialize state
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
**OPNet (onDeployment called during contract deployment):**
|
|
707
|
+
```typescript
|
|
708
|
+
public override onDeployment(calldata: Calldata): void {
|
|
709
|
+
const name = calldata.readString();
|
|
710
|
+
const symbol = calldata.readString();
|
|
711
|
+
this.instantiate(new OP20InitParameters(maxSupply, decimals, name, symbol));
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
### Event Emission Comparison
|
|
716
|
+
|
|
717
|
+
**Solidity:**
|
|
718
|
+
```solidity
|
|
719
|
+
event Minted(address indexed to, uint256 amount);
|
|
720
|
+
// Later in code:
|
|
721
|
+
emit Minted(to, amount);
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
**OPNet:**
|
|
725
|
+
```typescript
|
|
726
|
+
// Event class definition
|
|
727
|
+
class MintEvent extends NetEvent {
|
|
728
|
+
constructor(public to: Address, public amount: u256) {
|
|
729
|
+
super('Minted');
|
|
730
|
+
}
|
|
731
|
+
protected override encodeData(writer: BytesWriter): void {
|
|
732
|
+
writer.writeAddress(this.to);
|
|
733
|
+
writer.writeU256(this.amount);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Later in code:
|
|
738
|
+
this.emitEvent(new MintEvent(to, amount));
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
---
|
|
742
|
+
|
|
743
|
+
**Navigation:**
|
|
744
|
+
- Previous: [Plugins](../advanced/plugins.md)
|
|
745
|
+
- Next: [NFT with Reservations](./nft-with-reservations.md)
|