@btc-vision/btc-runtime 1.9.9 → 1.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btc-vision/btc-runtime",
3
- "version": "1.9.9",
3
+ "version": "1.9.11",
4
4
  "description": "Bitcoin Smart Contract Runtime",
5
5
  "main": "btc/index.ts",
6
6
  "scripts": {
@@ -203,8 +203,7 @@ export class BytesReader {
203
203
  }
204
204
 
205
205
  /**
206
- * [u32 length][raw bytes]. By default big-endian for the length,
207
- * to match AS BytesWriter's `writeBytesWithLength`.
206
+ * [u32 length][raw bytes]. By default big-endian for the length
208
207
  */
209
208
  public readBytesWithLength(be: boolean = true): Uint8Array {
210
209
  const length = this.readU32(be);
@@ -248,8 +247,17 @@ export class BytesReader {
248
247
 
249
248
  // ------------------- Arrays ------------------- //
250
249
 
250
+ public readArrayOfBuffer(be: boolean = true): Uint8Array[] {
251
+ const length = this.readU16(be);
252
+ const result: Uint8Array[] = new Array<Uint8Array>(length);
253
+ for (let i: u32 = 0; i < length; i++) {
254
+ result[i] = this.readBytesWithLength();
255
+ }
256
+ return result;
257
+ }
258
+
251
259
  public readU256Array(be: boolean = true): u256[] {
252
- const length = this.readU16();
260
+ const length = this.readU16(be);
253
261
  const result = new Array<u256>(length);
254
262
  for (let i: u32 = 0; i < length; i++) {
255
263
  result[i] = this.readU256(be);
@@ -19,6 +19,8 @@ import {
19
19
  } from '../utils';
20
20
  import { BytesReader } from './BytesReader';
21
21
 
22
+ const arrayTooLargeError: string = 'Array is too large';
23
+
22
24
  @final
23
25
  export class BytesWriter {
24
26
  private currentOffset: u32 = 0;
@@ -30,6 +32,17 @@ export class BytesWriter {
30
32
  this.buffer = new DataView(typedArray.buffer);
31
33
  }
32
34
 
35
+ public static estimateArrayOfBufferLength(values: Uint8Array[]): u32 {
36
+ if (values.length > 65535) throw new Revert(arrayTooLargeError);
37
+ let totalLength: u32 = U16_BYTE_LENGTH;
38
+
39
+ for (let i = 0; i < values.length; i++) {
40
+ totalLength += U32_BYTE_LENGTH + u32(values[i].length); // each entry has a u32 length prefix
41
+ }
42
+
43
+ return totalLength;
44
+ }
45
+
33
46
  public bufferLength(): u32 {
34
47
  return this.buffer.byteLength;
35
48
  }
@@ -197,8 +210,20 @@ export class BytesWriter {
197
210
 
198
211
  // ------------------ Array Writers ------------------ //
199
212
 
213
+ public writeArrayOfBuffer(values: Uint8Array[], be: boolean = true): void {
214
+ const totalLength = BytesWriter.estimateArrayOfBufferLength(values);
215
+
216
+ this.allocSafe(totalLength);
217
+ this.writeU16(u16(values.length), be);
218
+
219
+ for (let i = 0; i < values.length; i++) {
220
+ this.writeU32(u32(values[i].length), be);
221
+ this.writeBytes(values[i]);
222
+ }
223
+ }
224
+
200
225
  public writeU8Array(value: u8[], be: boolean = true): void {
201
- if (value.length > 65535) throw new Revert('Array size is too large');
226
+ if (value.length > 65535) throw new Revert(arrayTooLargeError);
202
227
  this.allocSafe(U16_BYTE_LENGTH + value.length);
203
228
  this.writeU16(u16(value.length), be);
204
229
 
@@ -208,7 +233,7 @@ export class BytesWriter {
208
233
  }
209
234
 
210
235
  public writeU16Array(value: u16[], be: boolean = true): void {
211
- if (value.length > 65535) throw new Revert('Array size is too large');
236
+ if (value.length > 65535) throw new Revert(arrayTooLargeError);
212
237
  this.allocSafe(U16_BYTE_LENGTH + value.length * U16_BYTE_LENGTH);
213
238
  this.writeU16(u16(value.length), be);
214
239
 
@@ -218,7 +243,7 @@ export class BytesWriter {
218
243
  }
219
244
 
220
245
  public writeU32Array(value: u32[], be: boolean = true): void {
221
- if (value.length > 65535) throw new Revert('Array size is too large');
246
+ if (value.length > 65535) throw new Revert(arrayTooLargeError);
222
247
  this.allocSafe(U16_BYTE_LENGTH + value.length * U32_BYTE_LENGTH);
223
248
  this.writeU16(u16(value.length), be);
224
249
 
@@ -228,7 +253,7 @@ export class BytesWriter {
228
253
  }
229
254
 
230
255
  public writeU64Array(value: u64[], be: boolean = true): void {
231
- if (value.length > 65535) throw new Revert('Array size is too large');
256
+ if (value.length > 65535) throw new Revert(arrayTooLargeError);
232
257
  this.allocSafe(U16_BYTE_LENGTH + value.length * U64_BYTE_LENGTH);
233
258
  this.writeU16(u16(value.length), be);
234
259
 
@@ -238,7 +263,7 @@ export class BytesWriter {
238
263
  }
239
264
 
240
265
  public writeU128Array(value: u128[], be: boolean = true): void {
241
- if (value.length > 65535) throw new Revert('Array size is too large');
266
+ if (value.length > 65535) throw new Revert(arrayTooLargeError);
242
267
  this.allocSafe(U16_BYTE_LENGTH + value.length * U128_BYTE_LENGTH);
243
268
  this.writeU16(u16(value.length), be);
244
269
 
@@ -248,7 +273,7 @@ export class BytesWriter {
248
273
  }
249
274
 
250
275
  public writeU256Array(value: u256[], be: boolean = true): void {
251
- if (value.length > 65535) throw new Revert('Array size is too large');
276
+ if (value.length > 65535) throw new Revert(arrayTooLargeError);
252
277
  this.allocSafe(U16_BYTE_LENGTH + value.length * U256_BYTE_LENGTH);
253
278
  this.writeU16(u16(value.length), be);
254
279
 
@@ -258,7 +283,7 @@ export class BytesWriter {
258
283
  }
259
284
 
260
285
  public writeAddressArray(value: Address[]): void {
261
- if (value.length > 65535) throw new Revert('Array size is too large');
286
+ if (value.length > 65535) throw new Revert(arrayTooLargeError);
262
287
  this.writeU16(u16(value.length));
263
288
 
264
289
  for (let i: i32 = 0; i < value.length; i++) {
@@ -14,7 +14,7 @@ export const ALLOWANCE_DECREASE_TYPE_HASH: u8[] = [
14
14
  ];
15
15
 
16
16
  // onOP721Received(address,address,uint256,bytes)
17
- export const ON_OP721_RECEIVED_SELECTOR: u32 = 0xd83e7dbc;
17
+ export const ON_OP721_RECEIVED_SELECTOR: u32 = 0x5349f6de;
18
18
 
19
19
  // onOP1155Received(address,address,uint256,uint256,bytes)
20
20
  export const ON_OP1155_RECEIVED_MAGIC: u32 = 0xcedc9fdf;
@@ -61,10 +61,10 @@ export const OP712_VERSION_HASH: u8[] = [
61
61
  0x47, 0xad, 0xa4, 0xea, 0xa2, 0x2f, 0x1d, 0x49, 0xc0, 0x1e, 0x52, 0xdd, 0xb7, 0x87, 0x5b, 0x4b,
62
62
  ];
63
63
 
64
- // sha256("OP721Approve(address owner,address spender,uint256 tokenId,uint256 nonce,uint64 deadline)")
65
- export const OP721_APPROVE_TYPE_HASH: u8[] = [
66
- 0x2e, 0x4b, 0x1b, 0xb9, 0x26, 0xd1, 0xfe, 0x9c, 0x56, 0x8a, 0xca, 0xc1, 0xb9, 0xfc, 0x8b, 0x98,
67
- 0xf5, 0xfe, 0x67, 0xcb, 0x7b, 0x6d, 0x12, 0x03, 0x69, 0xaf, 0x3d, 0xb8, 0x44, 0x67, 0xb4, 0xcb,
64
+ // sha256("OP721Approval(address owner,address spender,uint256 tokenId,uint256 nonce,uint64 deadline)")
65
+ export const OP721_APPROVAL_TYPE_HASH: u8[] = [
66
+ 0xb8, 0x6e, 0x99, 0xda, 0xc0, 0x47, 0x4b, 0x4a, 0x9f, 0xc3, 0x32, 0x3a, 0xd6, 0xed, 0x2f, 0x39,
67
+ 0x55, 0xe7, 0xb8, 0x6d, 0xc6, 0x8c, 0x62, 0x42, 0x82, 0x1c, 0xbc, 0xac, 0xa2, 0xd8, 0x79, 0xde
68
68
  ];
69
69
 
70
70
  // sha256("OP721Transfer(address from,address to,uint256 tokenId,uint256 nonce,uint64 deadline)")
@@ -214,6 +214,37 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
214
214
  return w;
215
215
  }
216
216
 
217
+ @method(
218
+ { name: 'to', type: ABIDataTypes.ADDRESS },
219
+ { name: 'amount', type: ABIDataTypes.UINT256 },
220
+ )
221
+ @emit('Transferred')
222
+ public transfer(calldata: Calldata): BytesWriter {
223
+ this._transfer(
224
+ Blockchain.tx.sender,
225
+ calldata.readAddress(),
226
+ calldata.readU256(),
227
+ );
228
+ return new BytesWriter(0);
229
+ }
230
+
231
+ @method(
232
+ { name: 'from', type: ABIDataTypes.ADDRESS },
233
+ { name: 'to', type: ABIDataTypes.ADDRESS },
234
+ { name: 'amount', type: ABIDataTypes.UINT256 },
235
+ )
236
+ @emit('Transferred')
237
+ public transferFrom(calldata: Calldata): BytesWriter {
238
+ const from = calldata.readAddress();
239
+ const to = calldata.readAddress();
240
+ const amount = calldata.readU256();
241
+
242
+ this._spendAllowance(from, Blockchain.tx.sender, amount);
243
+ this._transfer(from, to, amount);
244
+
245
+ return new BytesWriter(0);
246
+ }
247
+
217
248
  @method(
218
249
  { name: 'to', type: ABIDataTypes.ADDRESS },
219
250
  { name: 'amount', type: ABIDataTypes.UINT256 },
@@ -221,7 +252,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
221
252
  )
222
253
  @emit('Transferred')
223
254
  public safeTransfer(calldata: Calldata): BytesWriter {
224
- this._transfer(
255
+ this._safeTransfer(
225
256
  Blockchain.tx.sender,
226
257
  calldata.readAddress(),
227
258
  calldata.readU256(),
@@ -244,7 +275,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
244
275
  const data = calldata.readBytesWithLength();
245
276
 
246
277
  this._spendAllowance(from, Blockchain.tx.sender, amount);
247
- this._transfer(from, to, amount, data);
278
+ this._safeTransfer(from, to, amount, data);
248
279
 
249
280
  return new BytesWriter(0);
250
281
  }
@@ -326,10 +357,9 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
326
357
  @returns(
327
358
  { name: 'name', type: ABIDataTypes.STRING },
328
359
  { name: 'symbol', type: ABIDataTypes.STRING },
360
+ { name: 'icon', type: ABIDataTypes.STRING },
329
361
  { name: 'decimals', type: ABIDataTypes.UINT8 },
330
362
  { name: 'totalSupply', type: ABIDataTypes.UINT256 },
331
- { name: 'maximumSupply', type: ABIDataTypes.UINT256 },
332
- { name: 'icon', type: ABIDataTypes.STRING },
333
363
  { name: 'domainSeparator', type: ABIDataTypes.BYTES32 },
334
364
  )
335
365
  public metadata(_: Calldata): BytesWriter {
@@ -354,10 +384,9 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
354
384
  const w = new BytesWriter(totalSize);
355
385
  w.writeStringWithLength(name);
356
386
  w.writeStringWithLength(symbol);
387
+ w.writeStringWithLength(icon);
357
388
  w.writeU8(this.decimals);
358
389
  w.writeU256(this.totalSupply);
359
- w.writeU256(this.maxSupply);
360
- w.writeStringWithLength(icon);
361
390
  w.writeBytesWithLength(domainSeparator);
362
391
 
363
392
  return w;
@@ -373,7 +402,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
373
402
  return senderMap.get(spender);
374
403
  }
375
404
 
376
- protected _transfer(from: Address, to: Address, amount: u256, data: Uint8Array): void {
405
+ protected _transfer(from: Address, to: Address, amount: u256, emitEvent: boolean = true): void {
377
406
  if (from === Address.zero() || from === Address.dead()) {
378
407
  throw new Revert('Invalid sender');
379
408
  }
@@ -382,10 +411,6 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
382
411
  throw new Revert('Invalid receiver');
383
412
  }
384
413
 
385
- if (amount.isZero()) {
386
- throw new Revert('Amount must be greater than zero');
387
- }
388
-
389
414
  const balance: u256 = this.balanceOfMap.get(from);
390
415
  if (balance < amount) {
391
416
  throw new Revert('Insufficient balance');
@@ -396,13 +421,22 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
396
421
  const toBal: u256 = this.balanceOfMap.get(to);
397
422
  this.balanceOfMap.set(to, SafeMath.add(toBal, amount));
398
423
 
399
- this.createTransferredEvent(Blockchain.tx.sender, from, to, amount);
424
+ if (emitEvent) {
425
+ this.createTransferredEvent(Blockchain.tx.sender, from, to, amount);
426
+ }
427
+ }
428
+
429
+ protected _safeTransfer(from: Address, to: Address, amount: u256, data: Uint8Array): void {
430
+ this._transfer(from, to, amount, false);
400
431
 
401
432
  if (Blockchain.isContract(to)) {
402
433
  // In CALLBACK mode, the guard allows depth up to 1
403
434
  // In STANDARD mode, the guard blocks all reentrancy
404
435
  this._callOnOP20Received(from, to, amount, data);
405
436
  }
437
+
438
+ // Fire event at the end if everything succeeded indicating a successful transfer
439
+ this.createTransferredEvent(Blockchain.tx.sender, from, to, amount);
406
440
  }
407
441
 
408
442
  protected _spendAllowance(owner: Address, spender: Address, amount: u256): void {
@@ -24,19 +24,12 @@ import { IOP721 } from './interfaces/IOP721';
24
24
  import { OP721InitParameters } from './interfaces/OP721InitParameters';
25
25
  import { ReentrancyGuard } from './ReentrancyGuard';
26
26
  import { StoredMapU256 } from '../storage/maps/StoredMapU256';
27
- import {
28
- ApprovedEvent,
29
- ApprovedForAllEvent,
30
- MAX_URI_LENGTH,
31
- TransferredEvent,
32
- URIEvent,
33
- } from '../events/predefined';
27
+ import { ApprovedEvent, ApprovedForAllEvent, MAX_URI_LENGTH, TransferredEvent, URIEvent } from '../events/predefined';
34
28
  import {
35
29
  ON_OP721_RECEIVED_SELECTOR,
36
30
  OP712_DOMAIN_TYPE_HASH,
37
31
  OP712_VERSION_HASH,
38
- OP721_APPROVE_TYPE_HASH,
39
- OP721_TRANSFER_TYPE_HASH,
32
+ OP721_APPROVAL_TYPE_HASH,
40
33
  } from '../constants/Exports';
41
34
 
42
35
  const stringPointer: u16 = Blockchain.nextPointer;
@@ -52,7 +45,6 @@ const ownerTokensMapPointer: u16 = Blockchain.nextPointer;
52
45
  const tokenIndexMapPointer: u16 = Blockchain.nextPointer;
53
46
  const initializedPointer: u16 = Blockchain.nextPointer;
54
47
  const tokenURICounterPointer: u16 = Blockchain.nextPointer;
55
- const transferNonceMapPointer: u16 = Blockchain.nextPointer;
56
48
  const approveNonceMapPointer: u16 = Blockchain.nextPointer;
57
49
 
58
50
  export abstract class OP721 extends ReentrancyGuard implements IOP721 {
@@ -75,8 +67,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
75
67
  protected readonly balanceOfMap: AddressMemoryMap;
76
68
  protected readonly operatorApprovalMap: MapOfMap<u256>;
77
69
 
78
- // Separate nonces for different operations
79
- protected readonly _transferNonceMap: AddressMemoryMap;
80
70
  protected readonly _approveNonceMap: AddressMemoryMap;
81
71
 
82
72
  // Token URI storage - stores index to StoredString array
@@ -112,7 +102,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
112
102
  this.operatorApprovalMap = new MapOfMap<u256>(operatorApprovalMapPointer);
113
103
 
114
104
  // Initialize separate nonce maps
115
- this._transferNonceMap = new AddressMemoryMap(transferNonceMapPointer);
116
105
  this._approveNonceMap = new AddressMemoryMap(approveNonceMapPointer);
117
106
 
118
107
  this.tokenURIIndices = new StoredMapU256(tokenURIMapPointer);
@@ -308,25 +297,17 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
308
297
  }
309
298
 
310
299
  @method(
311
- { name: 'from', type: ABIDataTypes.ADDRESS },
312
300
  { name: 'to', type: ABIDataTypes.ADDRESS },
313
301
  { name: 'tokenId', type: ABIDataTypes.UINT256 },
314
302
  { name: 'data', type: ABIDataTypes.BYTES },
315
303
  )
316
304
  @emit('Transferred')
317
- public safeTransferFrom(calldata: Calldata): BytesWriter {
318
- const from = calldata.readAddress();
305
+ public safeTransfer(calldata: Calldata): BytesWriter {
319
306
  const to = calldata.readAddress();
320
307
  const tokenId = calldata.readU256();
321
308
  const data = calldata.readBytesWithLength();
322
309
 
323
- // All state changes happen before external call
324
- this._transfer(from, to, tokenId);
325
-
326
- // External call happens after all state changes
327
- if (Blockchain.isContract(to)) {
328
- this._checkOnOP721Received(from, to, tokenId, data);
329
- }
310
+ this._transfer(Blockchain.tx.sender, to, tokenId, data);
330
311
 
331
312
  return new BytesWriter(0);
332
313
  }
@@ -335,32 +316,34 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
335
316
  { name: 'from', type: ABIDataTypes.ADDRESS },
336
317
  { name: 'to', type: ABIDataTypes.ADDRESS },
337
318
  { name: 'tokenId', type: ABIDataTypes.UINT256 },
319
+ { name: 'data', type: ABIDataTypes.BYTES },
338
320
  )
339
321
  @emit('Transferred')
340
- public transferFrom(calldata: Calldata): BytesWriter {
322
+ public safeTransferFrom(calldata: Calldata): BytesWriter {
341
323
  const from = calldata.readAddress();
342
324
  const to = calldata.readAddress();
343
325
  const tokenId = calldata.readU256();
326
+ const data = calldata.readBytesWithLength();
344
327
 
345
- this._transfer(from, to, tokenId);
328
+ this._transfer(from, to, tokenId, data);
346
329
 
347
330
  return new BytesWriter(0);
348
331
  }
349
332
 
350
333
  @method(
351
- { name: 'to', type: ABIDataTypes.ADDRESS },
334
+ { name: 'operator', type: ABIDataTypes.ADDRESS },
352
335
  { name: 'tokenId', type: ABIDataTypes.UINT256 },
353
336
  )
354
337
  @emit('Approved')
355
338
  public approve(calldata: Calldata): BytesWriter {
356
- const to = calldata.readAddress();
339
+ const operator = calldata.readAddress();
357
340
  const tokenId = calldata.readU256();
358
341
 
359
342
  // Validate to address
360
- if (to === Address.zero()) throw new Revert('Cannot approve to zero address');
343
+ if (operator === Address.zero()) throw new Revert('Cannot approve zero address');
361
344
 
362
345
  const owner = this._ownerOf(tokenId);
363
- if (to === owner) throw new Revert('Approval to current owner');
346
+ if (operator === owner) throw new Revert('Approval to current owner');
364
347
 
365
348
  if (
366
349
  owner !== Blockchain.tx.sender &&
@@ -369,13 +352,12 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
369
352
  throw new Revert('Not authorized to approve');
370
353
  }
371
354
 
372
- this._approve(to, tokenId);
355
+ this._approve(operator, tokenId);
373
356
 
374
357
  return new BytesWriter(0);
375
358
  }
376
359
 
377
360
  @method({ name: 'tokenId', type: ABIDataTypes.UINT256 })
378
- @returns({ name: 'approved', type: ABIDataTypes.ADDRESS })
379
361
  public getApproved(calldata: Calldata): BytesWriter {
380
362
  const tokenId = calldata.readU256();
381
363
  if (!this._exists(tokenId)) throw new Revert('Token does not exist');
@@ -419,47 +401,50 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
419
401
 
420
402
  @method(
421
403
  { name: 'owner', type: ABIDataTypes.ADDRESS },
422
- { name: 'to', type: ABIDataTypes.ADDRESS },
404
+ { name: 'operator', type: ABIDataTypes.ADDRESS },
423
405
  { name: 'tokenId', type: ABIDataTypes.UINT256 },
424
406
  { name: 'deadline', type: ABIDataTypes.UINT64 },
425
407
  { name: 'signature', type: ABIDataTypes.BYTES },
426
408
  )
427
- @emit('Transferred')
428
- public transferBySignature(calldata: Calldata): BytesWriter {
409
+ @emit('Approved')
410
+ public approveBySignature(calldata: Calldata): BytesWriter {
429
411
  const owner = calldata.readAddress();
430
- const to = calldata.readAddress();
412
+ const operator = calldata.readAddress();
431
413
  const tokenId = calldata.readU256();
432
414
  const deadline = calldata.readU64();
433
415
  const signature = calldata.readBytesWithLength();
434
416
 
435
- this._verifyTransferSignature(owner, to, tokenId, deadline, signature);
436
- this._transfer(owner, to, tokenId);
417
+ // Verify ownership
418
+ const tokenOwner = this._ownerOf(tokenId);
419
+ if (tokenOwner !== owner) throw new Revert('Not token owner');
420
+
421
+ this._verifyApproveSignature(owner, operator, tokenId, deadline, signature);
422
+
423
+ this._approve(operator, tokenId);
437
424
 
438
425
  return new BytesWriter(0);
439
426
  }
440
427
 
441
428
  @method(
442
429
  { name: 'owner', type: ABIDataTypes.ADDRESS },
443
- { name: 'spender', type: ABIDataTypes.ADDRESS },
444
- { name: 'tokenId', type: ABIDataTypes.UINT256 },
430
+ { name: 'operator', type: ABIDataTypes.ADDRESS },
431
+ { name: 'approved', type: ABIDataTypes.BOOL },
445
432
  { name: 'deadline', type: ABIDataTypes.UINT64 },
446
433
  { name: 'signature', type: ABIDataTypes.BYTES },
447
434
  )
448
435
  @emit('Approved')
449
- public approveBySignature(calldata: Calldata): BytesWriter {
436
+ public setApprovalForAllBySignature(calldata: Calldata): BytesWriter {
450
437
  const owner = calldata.readAddress();
451
- const spender = calldata.readAddress();
452
- const tokenId = calldata.readU256();
438
+ const operator = calldata.readAddress();
439
+ const approved = calldata.readBoolean();
453
440
  const deadline = calldata.readU64();
454
441
  const signature = calldata.readBytesWithLength();
455
442
 
456
- // Verify ownership
457
- const tokenOwner = this._ownerOf(tokenId);
458
- if (tokenOwner !== owner) throw new Revert('Not token owner');
443
+ if (owner === operator) throw new Revert('Cannot approve self');
459
444
 
460
- this._verifyApproveSignature(owner, spender, tokenId, deadline, signature);
445
+ this._verifySetApprovalForAllSignature(owner, operator, approved, deadline, signature);
461
446
 
462
- this._approve(spender, tokenId);
447
+ this._setApprovalForAll(owner, operator, approved);
463
448
 
464
449
  return new BytesWriter(0);
465
450
  }
@@ -500,16 +485,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
500
485
  return w;
501
486
  }
502
487
 
503
- @method({ name: 'owner', type: ABIDataTypes.ADDRESS })
504
- @returns({ name: 'nonce', type: ABIDataTypes.UINT256 })
505
- public getTransferNonce(calldata: Calldata): BytesWriter {
506
- const owner = calldata.readAddress();
507
- const nonce = this._transferNonceMap.get(owner);
508
- const w = new BytesWriter(U256_BYTE_LENGTH);
509
- w.writeU256(nonce);
510
- return w;
511
- }
512
-
513
488
  @method({ name: 'owner', type: ABIDataTypes.ADDRESS })
514
489
  @returns({ name: 'nonce', type: ABIDataTypes.UINT256 })
515
490
  public getApproveNonce(calldata: Calldata): BytesWriter {
@@ -546,7 +521,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
546
521
  { name: 'description', type: ABIDataTypes.STRING },
547
522
  { name: 'website', type: ABIDataTypes.STRING },
548
523
  { name: 'totalSupply', type: ABIDataTypes.UINT256 },
549
- { name: 'maximumSupply', type: ABIDataTypes.UINT256 },
550
524
  { name: 'domainSeparator', type: ABIDataTypes.BYTES32 },
551
525
  )
552
526
  public metadata(_: Calldata): BytesWriter {
@@ -657,7 +631,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
657
631
  this.createTransferEvent(owner, Address.zero(), tokenId);
658
632
  }
659
633
 
660
- protected _transfer(from: Address, to: Address, tokenId: u256): void {
634
+ protected _transfer(from: Address, to: Address, tokenId: u256, data: Uint8Array): void {
661
635
  // Skip self-transfers
662
636
  if (from === to) return;
663
637
 
@@ -699,6 +673,11 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
699
673
  // Transfer ownership
700
674
  this.ownerOfMap.set(tokenId, this._u256FromAddress(to));
701
675
 
676
+ // External call happens after all state changes
677
+ if (Blockchain.isContract(to)) {
678
+ this._checkOnOP721Received(from, to, tokenId, data);
679
+ }
680
+
702
681
  this.createTransferEvent(from, to, tokenId);
703
682
  }
704
683
 
@@ -774,10 +753,10 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
774
753
  ): void {
775
754
  const calldata = new BytesWriter(
776
755
  SELECTOR_BYTE_LENGTH +
777
- ADDRESS_BYTE_LENGTH * 2 +
778
- U256_BYTE_LENGTH +
779
- U32_BYTE_LENGTH +
780
- data.length,
756
+ ADDRESS_BYTE_LENGTH * 2 +
757
+ U256_BYTE_LENGTH +
758
+ U32_BYTE_LENGTH +
759
+ data.length,
781
760
  );
782
761
  calldata.writeSelector(ON_OP721_RECEIVED_SELECTOR);
783
762
  calldata.writeAddress(Blockchain.tx.sender);
@@ -796,9 +775,9 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
796
775
  }
797
776
  }
798
777
 
799
- protected _verifyTransferSignature(
778
+ protected _verifyApproveSignature(
800
779
  owner: Address,
801
- to: Address,
780
+ spender: Address,
802
781
  tokenId: u256,
803
782
  deadline: u64,
804
783
  signature: Uint8Array,
@@ -810,38 +789,26 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
810
789
  throw new Revert('Signature expired');
811
790
  }
812
791
 
813
- const nonce = this._transferNonceMap.get(owner);
792
+ const nonce = this._approveNonceMap.get(owner);
814
793
 
815
794
  const structWriter = new BytesWriter(
816
795
  32 + ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH * 2 + U64_BYTE_LENGTH,
817
796
  );
818
- structWriter.writeBytesU8Array(OP721_TRANSFER_TYPE_HASH);
797
+ structWriter.writeBytesU8Array(OP721_APPROVAL_TYPE_HASH);
819
798
  structWriter.writeAddress(owner);
820
- structWriter.writeAddress(to);
799
+ structWriter.writeAddress(spender);
821
800
  structWriter.writeU256(tokenId);
822
801
  structWriter.writeU256(nonce);
823
802
  structWriter.writeU64(deadline);
824
803
 
825
804
  const structHash = sha256(structWriter.getBuffer());
826
-
827
- const messageWriter = new BytesWriter(2 + 32 + 32);
828
- messageWriter.writeU16(0x1901);
829
- messageWriter.writeBytes(this._buildDomainSeparator());
830
- messageWriter.writeBytes(structHash);
831
-
832
- const hash = sha256(messageWriter.getBuffer());
833
-
834
- if (!Blockchain.verifySchnorrSignature(owner, signature, hash)) {
835
- throw new Revert('Invalid signature');
836
- }
837
-
838
- this._transferNonceMap.set(owner, SafeMath.add(nonce, u256.One));
805
+ this._verifySignature(structHash, owner, signature, nonce);
839
806
  }
840
807
 
841
- protected _verifyApproveSignature(
808
+ protected _verifySetApprovalForAllSignature(
842
809
  owner: Address,
843
810
  spender: Address,
844
- tokenId: u256,
811
+ approved: boolean,
845
812
  deadline: u64,
846
813
  signature: Uint8Array,
847
814
  ): void {
@@ -857,15 +824,23 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
857
824
  const structWriter = new BytesWriter(
858
825
  32 + ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH * 2 + U64_BYTE_LENGTH,
859
826
  );
860
- structWriter.writeBytesU8Array(OP721_APPROVE_TYPE_HASH);
827
+ structWriter.writeBytesU8Array(OP721_APPROVAL_TYPE_HASH);
861
828
  structWriter.writeAddress(owner);
862
829
  structWriter.writeAddress(spender);
863
- structWriter.writeU256(tokenId);
830
+ structWriter.writeBoolean(approved);
864
831
  structWriter.writeU256(nonce);
865
832
  structWriter.writeU64(deadline);
866
833
 
867
834
  const structHash = sha256(structWriter.getBuffer());
835
+ this._verifySignature(structHash, owner, signature, nonce);
836
+ }
868
837
 
838
+ protected _verifySignature(
839
+ structHash: Uint8Array,
840
+ owner: Address,
841
+ signature: Uint8Array,
842
+ nonce: u256,
843
+ ): void {
869
844
  const messageWriter = new BytesWriter(2 + 32 + 32);
870
845
  messageWriter.writeU16(0x1901);
871
846
  messageWriter.writeBytes(this._buildDomainSeparator());
@@ -13,7 +13,7 @@ export interface IOP721 {
13
13
  ownerOf(calldata: Calldata): BytesWriter;
14
14
 
15
15
  // Transfer functions
16
- transferFrom(calldata: Calldata): BytesWriter;
16
+ safeTransfer(calldata: Calldata): BytesWriter;
17
17
  safeTransferFrom(calldata: Calldata): BytesWriter;
18
18
 
19
19
  // Approval functions
@@ -21,9 +21,10 @@ export interface IOP721 {
21
21
  getApproved(calldata: Calldata): BytesWriter;
22
22
  setApprovalForAll(calldata: Calldata): BytesWriter;
23
23
  isApprovedForAll(calldata: Calldata): BytesWriter;
24
+ approveBySignature(calldata: Calldata): BytesWriter;
25
+ setApprovalForAllBySignature(calldata: Calldata): BytesWriter;
24
26
 
25
27
  // Advanced functions
26
28
  burn(calldata: Calldata): BytesWriter;
27
- transferBySignature(calldata: Calldata): BytesWriter;
28
29
  domainSeparator(calldata: Calldata): BytesWriter;
29
30
  }
@@ -7,12 +7,17 @@ export class TransactionInput {
7
7
  public readonly txId: Uint8Array,
8
8
  public readonly outputIndex: u16,
9
9
  public readonly scriptSig: Uint8Array,
10
+ public readonly witnesses: Uint8Array[] | null,
10
11
  public readonly coinbase: Uint8Array | null,
11
12
  ) {}
12
13
 
13
14
  public get isCoinbase(): boolean {
14
15
  return (this.flags & TransactionInputFlags.hasCoinbase) !== 0;
15
16
  }
17
+
18
+ public get hasWitnesses(): boolean {
19
+ return (this.flags & TransactionInputFlags.hasWitnesses) !== 0;
20
+ }
16
21
  }
17
22
 
18
23
  @final
@@ -35,7 +35,14 @@ export class TransactionDecoder {
35
35
  ? buffer.readBytesWithLength()
36
36
  : null;
37
37
 
38
- return new TransactionInput(flags, txId, outputIndex, scriptSig, coinbase);
38
+ const witnesses: Uint8Array[] | null = this.hasFlag(
39
+ flags,
40
+ TransactionInputFlags.hasWitnesses,
41
+ )
42
+ ? buffer.readArrayOfBuffer()
43
+ : null;
44
+
45
+ return new TransactionInput(flags, txId, outputIndex, scriptSig, witnesses, coinbase);
39
46
  }
40
47
 
41
48
  private decodeOutput(buffer: BytesReader): TransactionOutput {
@@ -1,5 +1,6 @@
1
1
  export enum TransactionInputFlags {
2
2
  hasCoinbase = 0b00000001,
3
+ hasWitnesses = 0b00000010,
3
4
  }
4
5
 
5
6
  export enum TransactionOutputFlags {