@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,819 @@
|
|
|
1
|
+
# OP721 API Reference
|
|
2
|
+
|
|
3
|
+
The `OP721` class implements the non-fungible token (NFT) standard, equivalent to ERC721 on Ethereum.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { OP721, OP721InitParameters } from '@btc-vision/btc-runtime/runtime';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## OP721 Architecture
|
|
12
|
+
|
|
13
|
+
```mermaid
|
|
14
|
+
classDiagram
|
|
15
|
+
class OP721 {
|
|
16
|
+
<<abstract>>
|
|
17
|
+
+_name: StoredString
|
|
18
|
+
+_symbol: StoredString
|
|
19
|
+
+_baseURI: StoredString
|
|
20
|
+
+_totalSupply: StoredU256
|
|
21
|
+
+_maxSupply: StoredU256
|
|
22
|
+
+ownerOfMap: StoredMapU256
|
|
23
|
+
+balanceOfMap: AddressMemoryMap
|
|
24
|
+
+tokenApprovalMap: StoredMapU256
|
|
25
|
+
+operatorApprovalMap: MapOfMap~u256~
|
|
26
|
+
+instantiate(params) void
|
|
27
|
+
+ownerOf(tokenId) Address
|
|
28
|
+
+balanceOf(owner) u256
|
|
29
|
+
+approve(operator, tokenId) void
|
|
30
|
+
+safeTransfer(to, tokenId, data) void
|
|
31
|
+
+safeTransferFrom(from, to, tokenId, data) void
|
|
32
|
+
#_mint(to, tokenId) void
|
|
33
|
+
#_burn(tokenId) void
|
|
34
|
+
#_transfer(from, to, tokenId, data) void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class ReentrancyGuard {
|
|
38
|
+
<<abstract>>
|
|
39
|
+
#reentrancyLevel: ReentrancyLevel
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class OP_NET {
|
|
43
|
+
<<abstract>>
|
|
44
|
+
+address: Address
|
|
45
|
+
#emitEvent(event) void
|
|
46
|
+
Note: @method decorator handles routing
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class MyNFT {
|
|
50
|
+
+_nextTokenId: StoredU256
|
|
51
|
+
+_baseURI: StoredString
|
|
52
|
+
+constructor()
|
|
53
|
+
+onDeployment(calldata) void
|
|
54
|
+
+mint(calldata) BytesWriter
|
|
55
|
+
+tokenURI(tokenId) string
|
|
56
|
+
Note: @method decorator handles routing
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
OP_NET <|-- ReentrancyGuard
|
|
60
|
+
ReentrancyGuard <|-- OP721
|
|
61
|
+
OP721 <|-- MyNFT
|
|
62
|
+
|
|
63
|
+
note for OP721 "NFT standard with\nenumeration support\nand approval management"
|
|
64
|
+
note for MyNFT "Your NFT collection\nextends OP721"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Class Definition
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
@final
|
|
71
|
+
export class MyNFT extends OP721 {
|
|
72
|
+
public constructor() {
|
|
73
|
+
super();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public override onDeployment(calldata: Calldata): void {
|
|
77
|
+
const name = calldata.readString();
|
|
78
|
+
const symbol = calldata.readString();
|
|
79
|
+
const baseURI = calldata.readString();
|
|
80
|
+
const maxSupply = calldata.readU256();
|
|
81
|
+
|
|
82
|
+
this.instantiate(new OP721InitParameters(
|
|
83
|
+
name,
|
|
84
|
+
symbol,
|
|
85
|
+
baseURI,
|
|
86
|
+
maxSupply
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Initialization
|
|
93
|
+
|
|
94
|
+
### OP721InitParameters
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
class OP721InitParameters {
|
|
98
|
+
constructor(
|
|
99
|
+
name: string,
|
|
100
|
+
symbol: string,
|
|
101
|
+
baseURI: string,
|
|
102
|
+
maxSupply: u256,
|
|
103
|
+
collectionBanner: string = '',
|
|
104
|
+
collectionIcon: string = '',
|
|
105
|
+
collectionWebsite: string = '',
|
|
106
|
+
collectionDescription: string = ''
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| Parameter | Type | Required | Description |
|
|
112
|
+
|-----------|------|----------|-------------|
|
|
113
|
+
| `name` | `string` | Yes | Collection name |
|
|
114
|
+
| `symbol` | `string` | Yes | Collection symbol |
|
|
115
|
+
| `baseURI` | `string` | Yes | Base URI for token metadata |
|
|
116
|
+
| `maxSupply` | `u256` | Yes | Maximum number of tokens that can be minted |
|
|
117
|
+
| `collectionBanner` | `string` | No | Collection banner URL (default: '') |
|
|
118
|
+
| `collectionIcon` | `string` | No | Collection icon URL (default: '') |
|
|
119
|
+
| `collectionWebsite` | `string` | No | Collection website URL (default: '') |
|
|
120
|
+
| `collectionDescription` | `string` | No | Collection description (default: '') |
|
|
121
|
+
|
|
122
|
+
### instantiate
|
|
123
|
+
|
|
124
|
+
Initializes the OP721 NFT. Must be called in `onDeployment`.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
public instantiate(
|
|
128
|
+
params: OP721InitParameters,
|
|
129
|
+
skipDeployerVerification: boolean = false
|
|
130
|
+
): void
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
| Parameter | Type | Description |
|
|
134
|
+
|-----------|------|-------------|
|
|
135
|
+
| `params` | `OP721InitParameters` | Initialization parameters |
|
|
136
|
+
| `skipDeployerVerification` | `boolean` | Skip deployer check (default: false) |
|
|
137
|
+
|
|
138
|
+
**Solidity Comparison:**
|
|
139
|
+
| Solidity (ERC721) | OPNet (OP721) |
|
|
140
|
+
|-------------------|---------------|
|
|
141
|
+
| `constructor(string name, string symbol)` | `onDeployment(calldata)` + `instantiate()` |
|
|
142
|
+
|
|
143
|
+
## View Methods
|
|
144
|
+
|
|
145
|
+
### name
|
|
146
|
+
|
|
147
|
+
Returns the collection name.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
public name(): string
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### symbol
|
|
154
|
+
|
|
155
|
+
Returns the collection symbol.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
public symbol(): string
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### totalSupply
|
|
162
|
+
|
|
163
|
+
Returns total minted tokens.
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
public get totalSupply(): u256
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### maxSupply
|
|
170
|
+
|
|
171
|
+
Returns maximum supply limit.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
public get maxSupply(): u256
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### balanceOf
|
|
178
|
+
|
|
179
|
+
Returns number of tokens owned by address.
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
public balanceOf(owner: Address): u256
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### ownerOf
|
|
186
|
+
|
|
187
|
+
Returns owner of a token.
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
public ownerOf(tokenId: u256): Address
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### tokenURI
|
|
194
|
+
|
|
195
|
+
Returns metadata URI for a token.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
public tokenURI(tokenId: u256): string
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Override to customize metadata:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
public override tokenURI(tokenId: u256): string {
|
|
205
|
+
return this._baseURI.value + tokenId.toString() + '.json';
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### getApproved
|
|
210
|
+
|
|
211
|
+
Returns approved address for a token.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
public getApproved(tokenId: u256): Address
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### isApprovedForAll
|
|
218
|
+
|
|
219
|
+
Checks if operator is approved for all tokens.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
public isApprovedForAll(owner: Address, operator: Address): bool
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Solidity Comparison:**
|
|
226
|
+
| Solidity (ERC721) | OPNet (OP721) |
|
|
227
|
+
|-------------------|---------------|
|
|
228
|
+
| `function ownerOf(uint256) view returns (address)` | `ownerOf(u256): Address` |
|
|
229
|
+
| `function balanceOf(address) view returns (uint256)` | `balanceOf(Address): u256` |
|
|
230
|
+
| `function tokenURI(uint256) view returns (string)` | `tokenURI(u256): string` |
|
|
231
|
+
|
|
232
|
+
## Transfer Methods
|
|
233
|
+
|
|
234
|
+
### safeTransfer (Calldata)
|
|
235
|
+
|
|
236
|
+
Transfers an NFT from the sender to a recipient.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
public safeTransfer(calldata: Calldata): BytesWriter
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Calldata format:**
|
|
243
|
+
| Field | Type | Size |
|
|
244
|
+
|-------|------|------|
|
|
245
|
+
| to | Address | 32 bytes |
|
|
246
|
+
| tokenId | u256 | 32 bytes |
|
|
247
|
+
| data | bytes | variable (length-prefixed) |
|
|
248
|
+
|
|
249
|
+
The following diagram shows the complete NFT transfer validation and execution flow:
|
|
250
|
+
|
|
251
|
+
```mermaid
|
|
252
|
+
flowchart LR
|
|
253
|
+
subgraph "Validation"
|
|
254
|
+
Start([NFT Transfer]) --> CheckOwner{Verify owner}
|
|
255
|
+
CheckOwner -->|Invalid| Revert1[Revert: Wrong owner]
|
|
256
|
+
CheckOwner -->|Valid| CheckTo{to != zero?}
|
|
257
|
+
CheckTo -->|to == zero| Revert2[Revert: Invalid recipient]
|
|
258
|
+
CheckTo -->|Valid| CheckAuth{Authorization}
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
subgraph "Authorization"
|
|
262
|
+
CheckAuth -->|Not authorized| Revert3[Revert: Not authorized]
|
|
263
|
+
CheckAuth -->|Owner| DoTransfer[Execute transfer]
|
|
264
|
+
CheckAuth -->|Approved operator| DoTransfer
|
|
265
|
+
CheckAuth -->|Token approved| DoTransfer
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
subgraph "Transfer Execution"
|
|
269
|
+
DoTransfer --> ClearApproval[Clear approval]
|
|
270
|
+
ClearApproval --> UpdateEnum[Update enumerations]
|
|
271
|
+
UpdateEnum --> UpdateBal[Update balances]
|
|
272
|
+
UpdateBal --> UpdateOwner[Set new owner]
|
|
273
|
+
UpdateOwner --> EmitEvent[Emit TransferEvent]
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
subgraph "Callback Handling"
|
|
277
|
+
EmitEvent --> IsContract{Recipient<br/>is contract?}
|
|
278
|
+
IsContract -->|No| Success([Complete])
|
|
279
|
+
IsContract -->|Yes| Callback[Call onOP721Received]
|
|
280
|
+
Callback --> CheckResponse{Valid<br/>response?}
|
|
281
|
+
CheckResponse -->|Yes| Success
|
|
282
|
+
CheckResponse -->|No| Revert4[Revert: Rejected]
|
|
283
|
+
end
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### safeTransferFrom (Calldata)
|
|
287
|
+
|
|
288
|
+
Safe transfer with recipient callback.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
public safeTransferFrom(calldata: Calldata): BytesWriter
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Calldata format:**
|
|
295
|
+
| Field | Type | Size |
|
|
296
|
+
|-------|------|------|
|
|
297
|
+
| from | Address | 32 bytes |
|
|
298
|
+
| to | Address | 32 bytes |
|
|
299
|
+
| tokenId | u256 | 32 bytes |
|
|
300
|
+
| data | bytes | variable (length-prefixed) |
|
|
301
|
+
|
|
302
|
+
Calls `onOP721Received` on recipient if it's a contract.
|
|
303
|
+
|
|
304
|
+
### burn (Calldata)
|
|
305
|
+
|
|
306
|
+
Burns a token. Only owner or approved addresses can burn.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
public burn(calldata: Calldata): BytesWriter
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Calldata format:**
|
|
313
|
+
| Field | Type | Size |
|
|
314
|
+
|-------|------|------|
|
|
315
|
+
| tokenId | u256 | 32 bytes |
|
|
316
|
+
|
|
317
|
+
The following sequence diagram illustrates the complete mint and transfer flow:
|
|
318
|
+
|
|
319
|
+
```mermaid
|
|
320
|
+
sequenceDiagram
|
|
321
|
+
participant Owner
|
|
322
|
+
participant NFT as OP721 Contract
|
|
323
|
+
participant Maps as Storage Maps
|
|
324
|
+
participant Recipient
|
|
325
|
+
|
|
326
|
+
Note over Owner,Recipient: Mint Flow
|
|
327
|
+
|
|
328
|
+
Owner->>NFT: _mint(to, tokenId)
|
|
329
|
+
NFT->>NFT: Check tokenId doesn't exist
|
|
330
|
+
NFT->>NFT: Check max supply not reached
|
|
331
|
+
|
|
332
|
+
NFT->>Maps: ownerOfMap.set(tokenId, to)
|
|
333
|
+
NFT->>Maps: balanceOfMap[to] += 1
|
|
334
|
+
NFT->>Maps: Add to owner enumeration
|
|
335
|
+
NFT->>NFT: totalSupply += 1
|
|
336
|
+
|
|
337
|
+
NFT->>NFT: Emit TransferEvent(zero, to, tokenId)
|
|
338
|
+
|
|
339
|
+
Note over Owner,Recipient: Transfer Flow
|
|
340
|
+
|
|
341
|
+
Owner->>NFT: approve(operator, tokenId)
|
|
342
|
+
NFT->>NFT: Check caller is owner or approved for all
|
|
343
|
+
NFT->>Maps: tokenApprovalMap.set(tokenId, operator)
|
|
344
|
+
NFT->>NFT: Emit ApprovalEvent
|
|
345
|
+
|
|
346
|
+
Recipient->>NFT: safeTransferFrom(owner, recipient, tokenId, data)
|
|
347
|
+
NFT->>NFT: Check authorization
|
|
348
|
+
NFT->>NFT: _transfer(owner, recipient, tokenId, data)
|
|
349
|
+
|
|
350
|
+
NFT->>Maps: Clear token approval
|
|
351
|
+
NFT->>Maps: Remove from old owner enumeration
|
|
352
|
+
NFT->>Maps: Add to new owner enumeration
|
|
353
|
+
NFT->>Maps: balanceOfMap[owner] -= 1
|
|
354
|
+
NFT->>Maps: balanceOfMap[recipient] += 1
|
|
355
|
+
NFT->>Maps: ownerOfMap.set(tokenId, recipient)
|
|
356
|
+
|
|
357
|
+
NFT->>NFT: Emit TransferEvent
|
|
358
|
+
|
|
359
|
+
alt Recipient is Contract
|
|
360
|
+
NFT->>Recipient: call onOP721Received
|
|
361
|
+
Recipient->>NFT: Return selector
|
|
362
|
+
NFT->>NFT: Verify response
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
NFT->>Recipient: Success
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Solidity Comparison:**
|
|
369
|
+
| Solidity (ERC721) | OPNet (OP721) |
|
|
370
|
+
|-------------------|---------------|
|
|
371
|
+
| `function transferFrom(address, address, uint256)` | `safeTransferFrom(calldata): BytesWriter` |
|
|
372
|
+
| `function safeTransferFrom(address, address, uint256, bytes)` | `safeTransferFrom(calldata): BytesWriter` |
|
|
373
|
+
| N/A | `safeTransfer(calldata): BytesWriter` (from sender) |
|
|
374
|
+
|
|
375
|
+
## Approval Methods
|
|
376
|
+
|
|
377
|
+
### approve (Calldata)
|
|
378
|
+
|
|
379
|
+
Approves an address for a single token.
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
public approve(calldata: Calldata): BytesWriter
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**Calldata format:**
|
|
386
|
+
| Field | Type | Size |
|
|
387
|
+
|-------|------|------|
|
|
388
|
+
| operator | Address | 32 bytes |
|
|
389
|
+
| tokenId | u256 | 32 bytes |
|
|
390
|
+
|
|
391
|
+
### setApprovalForAll (Calldata)
|
|
392
|
+
|
|
393
|
+
Sets operator approval for all tokens.
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
public setApprovalForAll(calldata: Calldata): BytesWriter
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Calldata format:**
|
|
400
|
+
| Field | Type | Size |
|
|
401
|
+
|-------|------|------|
|
|
402
|
+
| operator | Address | 32 bytes |
|
|
403
|
+
| approved | bool | 1 byte |
|
|
404
|
+
|
|
405
|
+
**Solidity Comparison:**
|
|
406
|
+
| Solidity (ERC721) | OPNet (OP721) |
|
|
407
|
+
|-------------------|---------------|
|
|
408
|
+
| `function approve(address, uint256)` | `approve(calldata): BytesWriter` |
|
|
409
|
+
| `function setApprovalForAll(address, bool)` | `setApprovalForAll(calldata): BytesWriter` |
|
|
410
|
+
|
|
411
|
+
## Protected Methods
|
|
412
|
+
|
|
413
|
+
### _mint
|
|
414
|
+
|
|
415
|
+
Mints a new token.
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
protected _mint(to: Address, tokenId: u256): void
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
@method(
|
|
423
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
424
|
+
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
425
|
+
)
|
|
426
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
427
|
+
@emit('Transfer')
|
|
428
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
429
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
430
|
+
const to: Address = calldata.readAddress();
|
|
431
|
+
const tokenId: u256 = calldata.readU256();
|
|
432
|
+
this._mint(to, tokenId);
|
|
433
|
+
return new BytesWriter(0);
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### _burn
|
|
438
|
+
|
|
439
|
+
Burns a token.
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
protected _burn(tokenId: u256): void
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### _transfer
|
|
446
|
+
|
|
447
|
+
Internal transfer with data for safe transfer callbacks.
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
protected _transfer(from: Address, to: Address, tokenId: u256, data: Uint8Array): void
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### _approve
|
|
454
|
+
|
|
455
|
+
Internal approval.
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
protected _approve(operator: Address, tokenId: u256): void
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### _setApprovalForAll
|
|
462
|
+
|
|
463
|
+
Internal operator approval.
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
protected _setApprovalForAll(owner: Address, operator: Address, approved: bool): void
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### _setTokenURI
|
|
470
|
+
|
|
471
|
+
Sets a custom URI for a specific token.
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
protected _setTokenURI(tokenId: u256, uri: string): void
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### _setBaseURI
|
|
478
|
+
|
|
479
|
+
Sets the base URI for all tokens.
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
protected _setBaseURI(baseURI: string): void
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### _exists
|
|
486
|
+
|
|
487
|
+
Checks if a token exists.
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
protected _exists(tokenId: u256): bool
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### _ownerOf
|
|
494
|
+
|
|
495
|
+
Returns the owner of a token. Throws if token doesn't exist.
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
protected _ownerOf(tokenId: u256): Address
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### _balanceOf
|
|
502
|
+
|
|
503
|
+
Returns the balance of an address. Throws if zero address.
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
protected _balanceOf(owner: Address): u256
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### _isApprovedForAll
|
|
510
|
+
|
|
511
|
+
Checks if an operator is approved for all tokens.
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
protected _isApprovedForAll(owner: Address, operator: Address): boolean
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
**Solidity Comparison:**
|
|
518
|
+
| Solidity (ERC721) | OPNet (OP721) |
|
|
519
|
+
|-------------------|---------------|
|
|
520
|
+
| `function _mint(address, uint256) internal` | `_mint(Address, u256): void` |
|
|
521
|
+
| `function _burn(uint256) internal` | `_burn(u256): void` |
|
|
522
|
+
| `function _safeMint(address, uint256)` | `_mint()` (automatically checks recipient) |
|
|
523
|
+
|
|
524
|
+
## Enumeration System
|
|
525
|
+
|
|
526
|
+
### tokenOfOwnerByIndex
|
|
527
|
+
|
|
528
|
+
Returns token ID at index for owner.
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
public tokenOfOwnerByIndex(owner: Address, index: u256): u256
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
The enumeration system allows efficient iteration over tokens owned by an address:
|
|
535
|
+
|
|
536
|
+
```mermaid
|
|
537
|
+
graph LR
|
|
538
|
+
A[NFT Collection]
|
|
539
|
+
|
|
540
|
+
subgraph "Owner Enumeration"
|
|
541
|
+
B[ownerTokensMap<br/>Address -> StoredU256Array]
|
|
542
|
+
C[Owner's Token Array]
|
|
543
|
+
D[Token IDs owned<br/>by address]
|
|
544
|
+
B --> C
|
|
545
|
+
C --> D
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
subgraph "Index Tracking"
|
|
549
|
+
E[tokenIndexMap<br/>tokenId -> u256]
|
|
550
|
+
F[Index in owner's array]
|
|
551
|
+
E --> F
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
subgraph "Add Token Operations"
|
|
555
|
+
G[_addTokenToOwnerEnumeration<br/>On mint/transfer in]
|
|
556
|
+
H[Push tokenId to array]
|
|
557
|
+
I[Set tokenIndexMap]
|
|
558
|
+
G --> H --> I
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
subgraph "Remove Token Operations"
|
|
562
|
+
J[_removeTokenFromOwnerEnumeration<br/>On burn/transfer out]
|
|
563
|
+
K[Get last token in array]
|
|
564
|
+
L[Move last to removed position]
|
|
565
|
+
M[Delete last element]
|
|
566
|
+
N[Update tokenIndexMap]
|
|
567
|
+
J --> K --> L --> M --> N
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
A --> B
|
|
571
|
+
A --> E
|
|
572
|
+
G -.->|modifies| C
|
|
573
|
+
J -.->|modifies| C
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
The following sequence diagram shows how enumeration queries work:
|
|
577
|
+
|
|
578
|
+
```mermaid
|
|
579
|
+
sequenceDiagram
|
|
580
|
+
participant User as 👤 User
|
|
581
|
+
participant NFT as OP721
|
|
582
|
+
participant OwnerMap as ownerTokensMap
|
|
583
|
+
participant IndexMap as tokenIndexMap
|
|
584
|
+
|
|
585
|
+
Note over User,IndexMap: Query Owner's Tokens
|
|
586
|
+
|
|
587
|
+
User->>NFT: balanceOf(owner)
|
|
588
|
+
NFT->>NFT: Return balance (e.g., 5 tokens)
|
|
589
|
+
|
|
590
|
+
User->>NFT: tokenOfOwnerByIndex(owner, 0)
|
|
591
|
+
NFT->>OwnerMap: Get owner's token array
|
|
592
|
+
OwnerMap->>NFT: Return StoredU256Array
|
|
593
|
+
NFT->>NFT: array.get(0)
|
|
594
|
+
NFT->>User: Return tokenId (e.g., 42)
|
|
595
|
+
|
|
596
|
+
User->>NFT: tokenOfOwnerByIndex(owner, 1)
|
|
597
|
+
NFT->>NFT: array.get(1)
|
|
598
|
+
NFT->>User: Return tokenId (e.g., 137)
|
|
599
|
+
|
|
600
|
+
Note over User,IndexMap: Iterate All Tokens
|
|
601
|
+
|
|
602
|
+
loop For each index from 0 to balance-1
|
|
603
|
+
User->>NFT: tokenOfOwnerByIndex(owner, index)
|
|
604
|
+
NFT->>User: Return tokenId
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
Note over User,IndexMap: Internal: Add Token to Enumeration
|
|
608
|
+
|
|
609
|
+
NFT->>NFT: _addTokenToOwnerEnumeration(owner, tokenId)
|
|
610
|
+
NFT->>OwnerMap: Get owner's array
|
|
611
|
+
NFT->>NFT: newIndex = array.length
|
|
612
|
+
NFT->>OwnerMap: array.push(tokenId)
|
|
613
|
+
NFT->>IndexMap: tokenIndexMap.set(tokenId, newIndex)
|
|
614
|
+
|
|
615
|
+
Note over User,IndexMap: Internal: Remove Token from Enumeration
|
|
616
|
+
|
|
617
|
+
NFT->>NFT: _removeTokenFromOwnerEnumeration(owner, tokenId)
|
|
618
|
+
NFT->>IndexMap: Get tokenIndex for tokenId
|
|
619
|
+
NFT->>OwnerMap: Get last token in array
|
|
620
|
+
NFT->>OwnerMap: Move last token to removed position
|
|
621
|
+
NFT->>IndexMap: Update index for moved token
|
|
622
|
+
NFT->>OwnerMap: Delete last element
|
|
623
|
+
NFT->>IndexMap: Delete tokenId mapping
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
```typescript
|
|
627
|
+
// Get all tokens owned by address
|
|
628
|
+
const balance = this.balanceOf(owner);
|
|
629
|
+
for (let i = u256.Zero; i < balance; i = SafeMath.add(i, u256.One)) {
|
|
630
|
+
const tokenId = this.tokenOfOwnerByIndex(owner, i);
|
|
631
|
+
// Process tokenId
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Note:** Unlike ERC721Enumerable, OP721 does not include a global `tokenByIndex` method. It only provides `tokenOfOwnerByIndex` for per-owner enumeration.
|
|
636
|
+
|
|
637
|
+
## Events
|
|
638
|
+
|
|
639
|
+
### TransferredEvent
|
|
640
|
+
|
|
641
|
+
Emitted on transfers, mints, and burns.
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
class TransferredEvent extends NetEvent {
|
|
645
|
+
constructor(
|
|
646
|
+
operator: Address, // The address that initiated the transfer (Blockchain.tx.sender)
|
|
647
|
+
from: Address, // Previous owner (Address.zero() for mint)
|
|
648
|
+
to: Address, // New owner (Address.zero() for burn)
|
|
649
|
+
tokenId: u256 // The token being transferred
|
|
650
|
+
)
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### ApprovedEvent
|
|
655
|
+
|
|
656
|
+
Emitted on token approvals.
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
class ApprovedEvent extends NetEvent {
|
|
660
|
+
constructor(
|
|
661
|
+
owner: Address, // Token owner
|
|
662
|
+
spender: Address, // Approved address
|
|
663
|
+
tokenId: u256 // The token being approved
|
|
664
|
+
)
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### ApprovedForAllEvent
|
|
669
|
+
|
|
670
|
+
Emitted on operator approvals.
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
class ApprovedForAllEvent extends NetEvent {
|
|
674
|
+
constructor(
|
|
675
|
+
owner: Address, // Token owner
|
|
676
|
+
operator: Address, // Operator address
|
|
677
|
+
approved: bool // Approval status
|
|
678
|
+
)
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
### URIEvent
|
|
683
|
+
|
|
684
|
+
Emitted when a token URI is set or changed.
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
class URIEvent extends NetEvent {
|
|
688
|
+
constructor(
|
|
689
|
+
value: string, // The new URI
|
|
690
|
+
id: u256 // The token ID
|
|
691
|
+
)
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Solidity Comparison:**
|
|
696
|
+
| Solidity (ERC721) | OPNet (OP721) |
|
|
697
|
+
|-------------------|---------------|
|
|
698
|
+
| `event Transfer(address indexed, address indexed, uint256 indexed)` | `TransferredEvent(operator, from, to, tokenId)` |
|
|
699
|
+
| `event Approval(address indexed, address indexed, uint256 indexed)` | `ApprovedEvent(owner, spender, tokenId)` |
|
|
700
|
+
| `emit Transfer(from, to, tokenId)` | `emitEvent(new TransferredEvent(sender, from, to, tokenId))` |
|
|
701
|
+
|
|
702
|
+
## Storage Layout
|
|
703
|
+
|
|
704
|
+
OP721 uses multiple storage pointers:
|
|
705
|
+
|
|
706
|
+
| Purpose | Description |
|
|
707
|
+
|---------|-------------|
|
|
708
|
+
| Token owners | Maps tokenId -> owner |
|
|
709
|
+
| Balances | Maps owner -> count |
|
|
710
|
+
| Approvals | Maps tokenId -> approved |
|
|
711
|
+
| Operator approvals | Maps owner+operator -> bool |
|
|
712
|
+
| Owned tokens | Maps owner+index -> tokenId |
|
|
713
|
+
| Owned token index | Maps tokenId -> index |
|
|
714
|
+
| All tokens | Maps index -> tokenId |
|
|
715
|
+
| All tokens index | Maps tokenId -> index |
|
|
716
|
+
| Name | Collection name |
|
|
717
|
+
| Symbol | Collection symbol |
|
|
718
|
+
| Total supply | Current count |
|
|
719
|
+
|
|
720
|
+
## Method Selectors
|
|
721
|
+
|
|
722
|
+
| Selector | Method |
|
|
723
|
+
|----------|--------|
|
|
724
|
+
| `name` | Returns name |
|
|
725
|
+
| `symbol` | Returns symbol |
|
|
726
|
+
| `totalSupply` | Returns total supply |
|
|
727
|
+
| `maxSupply` | Returns maximum supply |
|
|
728
|
+
| `balanceOf` | Returns balance |
|
|
729
|
+
| `ownerOf` | Returns owner |
|
|
730
|
+
| `tokenURI` | Returns metadata URI |
|
|
731
|
+
| `approve` | Approve address |
|
|
732
|
+
| `getApproved` | Get approved address |
|
|
733
|
+
| `setApprovalForAll` | Set operator approval |
|
|
734
|
+
| `isApprovedForAll` | Check operator approval |
|
|
735
|
+
| `safeTransfer` | Transfer from sender |
|
|
736
|
+
| `safeTransferFrom` | Safe transfer with from address |
|
|
737
|
+
| `burn` | Burn token |
|
|
738
|
+
| `tokenOfOwnerByIndex` | Enumerable: owner token at index |
|
|
739
|
+
| `collectionInfo` | Returns collection metadata |
|
|
740
|
+
| `metadata` | Returns full collection metadata |
|
|
741
|
+
| `domainSeparator` | Returns EIP-712 domain separator |
|
|
742
|
+
| `getApproveNonce` | Returns signature nonce for address |
|
|
743
|
+
| `approveBySignature` | Approve via EIP-712 signature |
|
|
744
|
+
| `setApprovalForAllBySignature` | Set operator approval via signature |
|
|
745
|
+
| `setBaseURI` | Update base URI (deployer only) |
|
|
746
|
+
| `changeMetadata` | Update collection metadata (deployer only) |
|
|
747
|
+
|
|
748
|
+
## Complete Example
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
752
|
+
import {
|
|
753
|
+
OP721,
|
|
754
|
+
OP721InitParameters,
|
|
755
|
+
Blockchain,
|
|
756
|
+
Calldata,
|
|
757
|
+
BytesWriter,
|
|
758
|
+
SafeMath,
|
|
759
|
+
Address,
|
|
760
|
+
ABIDataTypes,
|
|
761
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
762
|
+
|
|
763
|
+
@final
|
|
764
|
+
export class MyNFT extends OP721 {
|
|
765
|
+
public constructor() {
|
|
766
|
+
super();
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
public override onDeployment(calldata: Calldata): void {
|
|
770
|
+
const name = calldata.readStringWithLength();
|
|
771
|
+
const symbol = calldata.readStringWithLength();
|
|
772
|
+
const baseURI = calldata.readStringWithLength();
|
|
773
|
+
const maxSupply = calldata.readU256();
|
|
774
|
+
|
|
775
|
+
this.instantiate(new OP721InitParameters(
|
|
776
|
+
name,
|
|
777
|
+
symbol,
|
|
778
|
+
baseURI,
|
|
779
|
+
maxSupply
|
|
780
|
+
));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
@method({ name: 'to', type: ABIDataTypes.ADDRESS })
|
|
784
|
+
@returns({ name: 'tokenId', type: ABIDataTypes.UINT256 })
|
|
785
|
+
@emit('Transferred')
|
|
786
|
+
public mint(calldata: Calldata): BytesWriter {
|
|
787
|
+
this.onlyDeployer(Blockchain.tx.sender);
|
|
788
|
+
|
|
789
|
+
const to: Address = calldata.readAddress();
|
|
790
|
+
|
|
791
|
+
// Use internal _nextTokenId from OP721 base class
|
|
792
|
+
const tokenId = this._nextTokenId.value;
|
|
793
|
+
this._mint(to, tokenId);
|
|
794
|
+
this._nextTokenId.value = SafeMath.add(tokenId, u256.One);
|
|
795
|
+
|
|
796
|
+
const writer = new BytesWriter(32);
|
|
797
|
+
writer.writeU256(tokenId);
|
|
798
|
+
return writer;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
## Solidity Comparison Summary
|
|
804
|
+
|
|
805
|
+
| Solidity (ERC721) | OPNet (OP721) |
|
|
806
|
+
|-------------------|---------------|
|
|
807
|
+
| `constructor(name, symbol)` | `instantiate(new OP721InitParameters(name, symbol, baseURI, maxSupply, ...))` |
|
|
808
|
+
| `function ownerOf(uint256)` | `ownerOf(u256): Address` |
|
|
809
|
+
| `_mint(address, uint256)` | `_mint(Address, u256)` |
|
|
810
|
+
| `_safeMint(address, uint256)` | `_mint()` (automatically emits TransferredEvent) |
|
|
811
|
+
| `emit Transfer(...)` | `emitEvent(new TransferredEvent(operator, from, to, tokenId))` |
|
|
812
|
+
| `transferFrom(from, to, tokenId)` | `safeTransferFrom(from, to, tokenId, data)` |
|
|
813
|
+
| N/A | `safeTransfer(to, tokenId, data)` (from sender) |
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
**Navigation:**
|
|
818
|
+
- Previous: [OP20 API](./op20.md)
|
|
819
|
+
- Next: [SafeMath API](./safe-math.md)
|