-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathSSIDNS_v0.3.scilla
1262 lines (1147 loc) · 38.6 KB
/
SSIDNS_v0.3.scilla
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(* SSI DNS v0.3.1
SSI Domain Name System that integrates the Zilliqa-Reference-Contract #6 standard (which has an SPDX-License-Identifier: MIT)
Self-Sovereign Identity Protocol
Copyright Tyron Mapu Community Interest Company 2022. All rights reserved.
You acknowledge and agree that Tyron Mapu Community Interest Company (Tyron) own all legal right, title and interest in and to the work, software, application, source code, documentation and any other documents in this repository (collectively, the Program), including any intellectual property rights which subsist in the Program (whether those rights happen to be registered or not, and wherever in the world those rights may exist), whether in source code or any other form.
Subject to the limited license below, you may not (and you may not permit anyone else to) distribute, publish, copy, modify, merge, combine with another program, create derivative works of, reverse engineer, decompile or otherwise attempt to extract the source code of, the Program or any part thereof, except that you may contribute to this repository.
You are granted a non-exclusive, non-transferable, non-sublicensable license to distribute, publish, copy, modify, merge, combine with another program or create derivative works of the Program (such resulting program, collectively, the Resulting Program) solely for Non-Commercial Use as long as you:
1. give prominent notice (Notice) with each copy of the Resulting Program that the Program is used in the Resulting Program and that the Program is the copyright of Tyron; and
2. subject the Resulting Program and any distribution, publication, copy, modification, merger therewith, combination with another program or derivative works thereof to the same Notice requirement and Non-Commercial Use restriction set forth herein.
Non-Commercial Use means each use as described in clauses (1)-(3) below, as reasonably determined by Tyron in its sole discretion:
1. personal use for research, personal study, private entertainment, hobby projects or amateur pursuits, in each case without any anticipated commercial application;
2. use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization or government institution; or
3. the number of monthly active users of the Resulting Program across all versions thereof and platforms globally do not exceed 10,000 at any time.
You will not use any trade mark, service mark, trade name, logo of Tyron or any other company or organization in a way that is likely or intended to cause confusion about the owner or authorized user of such marks, names or logos.
If you have any questions, comments or interest in pursuing any other use cases, please reach out to us at [email protected].*)
scilla_version 0
(***************************************************)
(* Associated library *)
(***************************************************)
import BoolUtils ListUtils IntUtils PairUtils
library NonfungibleToken
type Operation =
| Add
| Sub
(* Global variables *)
let zero_address = 0x0000000000000000000000000000000000000000
let false = False
let true = True
let zero = Uint256 0
let zero_128 = Uint128 0
let one = Uint256 1
let empty_string = ""
let zilID = "zil"
let add_operation = Add
let sub_operation = Sub
let min_fee_bps = Uint128 1
let max_fee_bps = Uint128 10000
(* Library functions *)
let one_msg =
fun (msg: Message) =>
let nil_msg = Nil {Message} in
Cons {Message} msg nil_msg
let two_msgs =
fun (msg1: Message) =>
fun (msg2: Message) =>
let msgs_tmp = one_msg msg2 in
Cons {Message} msg1 msgs_tmp
let get_bal =
fun (maybe_bal: Option Uint256) =>
match maybe_bal with
| None => zero
| Some bal => bal
end
(* Error exception *)
type Error =
| NotPausedError
| PausedError
| SelfError
| NotContractOwnerError
| NotContractOwnershipRecipientError
| NotTokenOwnerError
(*| NotMinterError *)
| NotOwnerOrOperatorError
(*| MinterNotFoundError
| MinterFoundError *)
| SpenderFoundError
| OperatorNotFoundError
| OperatorFoundError
| NotAllowedToTransferError
| TokenNotFoundError
| InvalidFeeBPSError
| ZeroAddressDestinationError
| ThisAddressDestinationError
| DomainNotValidError
| DomainTakenError
| DomainNotFoundError
| DomainNotOwnerError
let make_error =
fun (result: Error) =>
let result_code =
match result with
| NotPausedError => Int32 -1
| PausedError => Int32 -2
| SelfError => Int32 -3
| NotContractOwnerError => Int32 -4
| NotTokenOwnerError => Int32 -5
(* | NotMinterError => Int32 -6 *)
| NotOwnerOrOperatorError => Int32 -7
(* | MinterNotFoundError => Int32 -8
| MinterFoundError => Int32 -9 *)
| SpenderFoundError => Int32 -10
| OperatorNotFoundError => Int32 -11
| OperatorFoundError => Int32 -12
| NotAllowedToTransferError => Int32 -13
| TokenNotFoundError => Int32 -14
| InvalidFeeBPSError => Int32 -15
| ZeroAddressDestinationError => Int32 -16
| ThisAddressDestinationError => Int32 -17
| NotContractOwnershipRecipientError => Int32 -18
| DomainNotValidError => Int32 -19
| DomainTakenError => Int32 -20
| DomainNotFoundError => Int32 -21
| DomainNotOwnerError => Int32 -22
end
in
{ _exception: "Error"; code: result_code }
let option_value = tfun 'A => fun( default: 'A ) => fun( input: Option 'A) =>
match input with
| Some v => v
| None => default end
let option_bystr20_value = let f = @option_value ByStr20 in f zero_address
(***************************************************)
(* The contract definition *)
(***************************************************)
contract NonfungibleToken
(
init: ByStr20 with contract field dApp: ByStr20 with contract
field implementation: ByStr20 with contract
field utility: Map String Map String Uint128 end,
field did_dns: Map String ByStr20 with contract
field services: Map String ByStr20 end end end,
initial_contract_owner: ByStr20,
(* Initial Base URI. e.g. 'https://creatures-api.zilliqa.com/api/creature/' *)
initial_base_uri: String,
name: String,
symbol: String,
init_dns: Map ByStr32 ByStr20
)
(* Contract constraints *)
with
(* 'initial_contract_owner' must not be the zero address *)
let is_contract_owner_invalid = builtin eq initial_contract_owner zero_address in
(* 'name' must not be an empty string *)
let is_name_invalid = builtin eq name empty_string in
(* 'symbol' must not be an empty string *)
let is_symbol_invalid = builtin eq symbol empty_string in
(* Check if any parameter is invalid *)
let is_name_or_symbol_invalid = orb is_name_invalid is_symbol_invalid in
let is_invalid = orb is_contract_owner_invalid is_name_or_symbol_invalid in
negb is_invalid
=>
(* Mutable fields *)
(* Emergency stop mechanism *)
(* Defaults to False *)
field is_paused: Bool = false
field is_paused_zil: Bool = false
(* Token Name *)
(* Defaults to 'name' *)
(* No need to mutate this field since this is for remote fetch to retrieve the immutable parameter. *)
field token_name: String = name
(* Token Symbol *)
(* Defaults to 'symbol' *)
(* No need to mutate this field since this is for remote fetch to retrieve the immutable parameter. *)
field token_symbol: String = symbol
(* Contract Owner *)
(* Defaults to 'initial_contract_owner' *)
field contract_owner: ByStr20 = initial_contract_owner
(* Contract ownership recipient *)
(* Defaults to 'zero_address' *)
field contract_ownership_recipient: ByStr20 = zero_address
(* Address to send royalties to *)
(* Defaults to 'initial_contract_owner' *)
field royalty_recipient: ByStr20 = initial_contract_owner
(* Royalty fee BPS *)
(* e.g. 1 = 0.01%, 10000 = 100% *)
(* Defaults to 1000 *)
field royalty_fee_bps: Uint128 = Uint128 1000
(* Base URI *)
(* Defaults to 'initial_base_uri' *)
field base_uri: String = initial_base_uri
(* Token URIs *)
field token_uris: Map Uint256 String = Emp Uint256 String
(* Mapping from token ID to its owner *)
field token_owners: Map Uint256 ByStr20 = Emp Uint256 ByStr20
field nft_domain_names: Map ByStr32 Uint256 = Emp ByStr32 Uint256
field nft_ssi_dns: Map ByStr32 ByStr20 = init_dns (* Emp ByStr32 ByStr20 *)
(* The total number of tokens minted *)
field token_id_count: Uint256 = Uint256 0
(* The total number of existing tokens *)
field total_supply: Uint256 = Uint256 0
(* Mapping from token owner to the number of existing tokens *)
field balances: Map ByStr20 Uint256 = Emp ByStr20 Uint256
(* Set for minters *)
(* 'initial_contract_owner' is a minter by default *)
(*
field minters: Map ByStr20 Bool =
let emp_map = Emp ByStr20 Bool in
builtin put emp_map initial_contract_owner true
*)
(* Mapping from token ID to a spender *)
field spenders: Map Uint256 ByStr20 = Emp Uint256 ByStr20
(* Mapping from token owner to operators authorized by the token owner *)
field operators: Map ByStr20 (Map ByStr20 Bool) = Emp ByStr20 (Map ByStr20 Bool)
(* Emit Errors *)
procedure Throw(error: Error)
e = make_error error;
throw e
end
procedure RequireNotPaused()
(* Reference: *)
(* https://consensys.github.io/smart-contract-best-practices/general_philosophy/#prepare-for-failure *)
paused <- is_paused;
match paused with
| False =>
| True =>
(* Contract is paused *)
error = PausedError;
Throw error
end
end
procedure RequireNotPausedZil()
paused <- is_paused_zil; match paused with
| False => | True => e = { _exception : "SSIDNS-ZilDomainIsPaused" }; throw e
end
end
procedure RequireNotTaken(domain_id: ByStr32)
is_empty =
let null = 0x0000000000000000000000000000000000000000000000000000000000000000 in
builtin eq domain_id null;
match is_empty with
| True =>
error = DomainNotValidError;
Throw error
| False => end;
is_taken <- exists nft_domain_names[domain_id];
match is_taken with
| True => (* The NFT Domain Name has an owner *)
error = DomainTakenError;
Throw error
| False =>
end
end
procedure RequireValidRoyaltyFee(fee_bps: Uint128)
is_gte_min = uint128_ge fee_bps min_fee_bps;
is_lte_max = uint128_le fee_bps max_fee_bps;
is_valid = andb is_gte_min is_lte_max;
match is_valid with
| True =>
| False =>
error = InvalidFeeBPSError;
Throw error
end
end
procedure RequireContractOwner()
cur_owner <- contract_owner;
is_contract_owner = builtin eq cur_owner _sender;
match is_contract_owner with
| True =>
| False =>
error = NotContractOwnerError;
Throw error
end
end
(* @xalkan assess
procedure VerifyController()
current_init <-& init.dApp; current_impl <-& current_init.implementation; current_controller <-& current_impl.controller;
verified = builtin eq _origin current_controller; match verified with
| True => | False => e = { _exception : "SSIDNS-WrongCaller" }; throw e end end
*)
procedure RequireNotSelf(address_a: ByStr20, address_b: ByStr20)
is_self = builtin eq address_a address_b;
match is_self with
| False =>
| True =>
error = SelfError;
Throw error
end
end
procedure RequireExistingToken(token_id: Uint256)
has_token <- exists token_owners[token_id];
match has_token with
| True =>
| False =>
error = TokenNotFoundError;
Throw error
end
end
procedure RequireValidDestination(to: ByStr20)
(* Reference: https://github.com/ConsenSys/smart-contract-best-practices/blob/master/docs/tokens.md *)
is_zero_address = builtin eq to zero_address;
match is_zero_address with
| False =>
| True =>
error = ZeroAddressDestinationError;
Throw error
end;
is_this_address = builtin eq to _this_address;
match is_this_address with
| False =>
| True =>
error = ThisAddressDestinationError;
Throw error
end
end
(*
procedure IsMinter(address: ByStr20)
has_minter <- exists minters[address];
match has_minter with
| True =>
| False =>
error = NotMinterError;
Throw error
end
end
*)
(*
procedure RequireTokenOwner(token_id: Uint256, address: ByStr20)
maybe_token_owner <- token_owners[token_id];
match maybe_token_owner with
| None =>
error = TokenNotFoundError;
Throw error
| Some addr =>
is_token_owner = builtin eq addr address;
match is_token_owner with
| True =>
| False =>
error = NotTokenOwnerError;
Throw error
end
end
end
*)
procedure RequireOwnerOrOperator(address: ByStr20)
is_owner = builtin eq _sender address;
has_operator <- exists operators[address][_sender];
is_allowed = orb is_owner has_operator;
match is_allowed with
| True =>
| False =>
error = NotOwnerOrOperatorError;
Throw error
end
end
procedure RequireAccessToTransfer(token_owner: ByStr20, token_id: Uint256)
(* check if _sender is token owner *)
is_token_owner = builtin eq token_owner _sender;
(* check if _sender is spender *)
maybe_spender <- spenders[token_id];
is_spender = match maybe_spender with
| None => False
| Some spender =>
builtin eq spender _sender
end;
(* check if _sender is operator *)
is_operator <- exists operators[token_owner][_sender];
is_spender_or_operator = orb is_spender is_operator;
is_allowed = orb is_spender_or_operator is_token_owner;
match is_allowed with
| True =>
| False =>
error = NotAllowedToTransferError;
Throw error
end
end
procedure UpdateBalance(operation: Operation, address: ByStr20)
match operation with
| Add =>
maybe_count <- balances[address];
new_count =
let cur_count = get_bal maybe_count in
(* if overflow occurs, it throws CALL_CONTRACT_FAILED *)
builtin add cur_count one;
balances[address] := new_count
| Sub =>
maybe_count <- balances[address];
new_count =
let cur_count = get_bal maybe_count in
(* if underflow occurs, it throws CALL_CONTRACT_FAILED *)
builtin sub cur_count one;
balances[address] := new_count
end
end
procedure HandlePayment(id: Pair String String)
payment_id = let fst_element = @fst String String in fst_element id;
txID = let snd_element = @snd String String in snd_element id;
current_init <-& init.dApp; current_impl <-& current_init.implementation;
get_fee <-& current_impl.utility[payment_id][txID]; match get_fee with
| None => e = { _exception : "SSIDNS-FeeIsNull" }; throw e
| Some fee =>
cur_owner <- contract_owner;
is_zil = builtin eq payment_id zilID; match is_zil with
| True =>
not_enough = builtin lt _amount fee; match not_enough with
| True => e = { _exception : "SSIDNS-InsufficientZIL" }; throw e
| False =>
accept; msg = let m = { _tag: "AddFunds"; _recipient: cur_owner; _amount: fee } in one_msg m; send msg;
refund = builtin sub _amount fee; is_zero = builtin eq refund zero_128; match is_zero with
| True => | False => rmsg = let m = { _tag: "AddFunds"; _recipient: _sender; _amount: refund } in one_msg m; send rmsg end end
| False =>
initId = "init"; get_impl_did <-& current_init.did_dns[initId]; match get_impl_did with
| None => e = { _exception: "SSIDNS-InitDidIsNull" }; throw e
| Some did_ =>
get_token_addr <-& did_.services[payment_id]; token_addr = option_bystr20_value get_token_addr;
msg = let m = { _tag: "TransferFrom"; _recipient: token_addr; _amount: zero_128;
from: _sender;
to: cur_owner;
amount: fee } in one_msg m; send msg end end end end
transition TransferFromSuccessCallBack(
initiator: ByStr20,
sender: ByStr20,
recipient: ByStr20,
amount: Uint128
)
RequireNotPaused;
is_valid = builtin eq initiator _this_address; match is_valid with
| True => | False => e = { _exception : "SSIDNS-WrongInitiator" }; throw e end end
procedure HandleBatchPayment(payment_id: String, txID: String, counter: Uint32 )
current_init <-& init.dApp; current_impl <-& current_init.implementation;
get_fee <-& current_impl.utility[payment_id][txID]; match get_fee with
| None => e = { _exception : "SSIDNS-FeeIsNull" }; throw e
| Some fee_ =>
get_counter = builtin to_uint128 counter; counter_ = match get_counter with
| Some c => c
| None => Uint128 0 (* should never happen *)
end;
fee = builtin mul fee_ counter_;
cur_owner <- contract_owner;
is_zil = builtin eq payment_id zilID; match is_zil with
| True =>
not_enough = builtin lt _amount fee; match not_enough with
| True => e = { _exception : "SSIDNS-InsufficientZIL" }; throw e
| False =>
accept; msg = let m = { _tag: "AddFunds"; _recipient: cur_owner; _amount: fee } in one_msg m; send msg;
refund = builtin sub _amount fee; is_zero = builtin eq refund zero_128; match is_zero with
| True => | False => rmsg = let m = { _tag: "AddFunds"; _recipient: _sender; _amount: refund } in one_msg m; send rmsg end end
| False =>
initId = "init"; get_impl_did <-& current_init.did_dns[initId]; match get_impl_did with
| None => e = { _exception: "SSIDNS-InitDidIsNull" }; throw e
| Some did_ =>
get_token_addr <-& did_.services[payment_id]; token_addr = option_bystr20_value get_token_addr;
msg = let m = { _tag: "TransferFrom"; _recipient: token_addr; _amount: zero_128;
from: _sender;
to: cur_owner;
amount: fee } in one_msg m; send msg end end end end
(* @Requirements: *)
(* - 'to' must not be the zero address. Otherwise, it must throw 'ZeroAddressDestinationError' *)
(* - 'to' must not be '_this_address'. Otherwise, it must throw 'ThisAddressDestinationError' *)
(* - '_sender' must be a minter. Otherwise, it must throw 'NotMinterError' *)
procedure MintToken(to: ByStr20)
RequireValidDestination to;
(*IsMinter _sender;*)
(* generate ID *)
current_token_id_count <- token_id_count;
new_token_id_count = builtin add current_token_id_count one;
token_id_count := new_token_id_count;
token_id = new_token_id_count;
(* mint a new token *)
token_owners[token_id] := to;
(* add one to the token owner balance *)
UpdateBalance add_operation to;
(* add one to the total supply *)
current_supply <- total_supply;
new_supply = builtin add current_supply one;
total_supply := new_supply
end
procedure SetTokenURI(token_id: Uint256, token_uri: String)
is_empty_string = builtin eq token_uri empty_string;
match is_empty_string with
| True =>
(* noop *)
| False =>
token_uris[token_id] := token_uri
end
end
procedure HandleMint(info: Pair ByStr20 String)
match info with
| Pair to token_uri =>
domain_id = builtin sha256hash token_uri;
RequireNotTaken domain_id;
MintToken to;
token_id <- token_id_count;
SetTokenURI token_id token_uri;
nft_domain_names[domain_id] := token_id;
nft_ssi_dns[domain_id] := to
end
end
(* @Requirements: *)
(* - 'token_id' must exist. Otherwise, it must throw 'TokenNotFoundError' *)
(* - '_sender' must be a token owner or an operator. Otherwise, it must throw 'NotOwnerOrOperatorError' *)
procedure BurnToken(token_id: Uint256)
(* Check if token exists *)
maybe_token_owner <- token_owners[token_id];
match maybe_token_owner with
| None =>
error = TokenNotFoundError;
Throw error
| Some token_owner =>
RequireOwnerOrOperator token_owner;
(* Destroy existing token *)
delete token_owners[token_id];
delete token_uris[token_id];
delete spenders[token_id];
(* subtract one from the balance *)
UpdateBalance sub_operation token_owner;
(* subtract one from the total supply *)
current_supply <- total_supply;
new_supply = builtin sub current_supply one;
total_supply := new_supply;
e = {
_eventname: "Burn";
token_owner: token_owner;
token_id: token_id
};
event e
end
end
(* @Requirements: *)
(* - 'to' must not be the zero address. Otherwise, it must throw 'ZeroAddressDestinationError' *)
(* - 'to' must not be '_this_address'. Otherwise, it must throw 'ThisAddressDestinationError' *)
(* - 'token_id' must exist. Otherwise, it must throw 'TokenNotFoundError' *)
(* - '_sender' must be a token owner, spender, or operator. Otherwise, it must throw 'NotAllowedToTransferError' *)
(* - '_sender' must not be 'to'. Otherwise, it must throw 'SelfError' *)
procedure TransferToken(to: ByStr20, token_id: Uint256)
RequireValidDestination to;
maybe_token_owner <- token_owners[token_id];
match maybe_token_owner with
| None =>
error = TokenNotFoundError;
Throw error
| Some token_owner =>
RequireAccessToTransfer token_owner token_id;
RequireNotSelf token_owner to;
(* change token_owner for that token_id *)
token_owners[token_id] := to;
delete spenders[token_id];
(* subtract one from previous token owner balance *)
UpdateBalance sub_operation token_owner;
(* add one to the new token owner balance *)
UpdateBalance add_operation to;
e = {
_eventname: "TransferFrom";
from: token_owner;
to: to;
token_id: token_id
};
event e
end
end
procedure HandleTransfer(info: Pair ByStr20 Uint256)
match info with
| Pair to token_id =>
TransferToken to token_id
end
end
(* Pauses the contract. Use this when things are going wrong ('circuit breaker'). *)
(* @Requirements: *)
(* - The contract must not be paused. Otherwise, it must throw 'PausedError' *)
(* - '_sender' must be the contract owner. Otherwise, it must throw 'NotContractOwnerError' *)
transition Pause()
RequireNotPaused;
RequireContractOwner;
is_paused := true;
e = {
_eventname: "Pause";
is_paused: true
};
event e;
msg_to_sender = {
_tag: "ZRC6_PauseCallback";
_recipient: _sender;
_amount: Uint128 0;
is_paused: true
};
msgs = one_msg msg_to_sender;
send msgs
end
transition PauseZil()
RequireNotPaused;
RequireContractOwner;
is_paused_zil := true;
e = {
_eventname: "PauseZil";
is_paused_zil: true
};
event e
end
(* Unpauses the contract. *)
(* @Requirements: *)
(* - The contract must be paused. Otherwise, it must throw 'NotPausedError' *)
(* - '_sender' must be the contract owner. Otherwise, it must throw 'NotContractOwnerError' *)
transition Unpause()
paused <- is_paused;
match paused with
| True =>
| False =>
error = NotPausedError;
Throw error
end;
RequireContractOwner;
is_paused := false;
e = {
_eventname: "Unpause";
is_paused: false
};
event e;
msg_to_sender = {
_tag: "ZRC6_UnpauseCallback";
_recipient: _sender;
_amount: Uint128 0;
is_paused: false
};
msgs = one_msg msg_to_sender;
send msgs
end
transition UnpauseZil()
paused <- is_paused_zil;
match paused with
| True =>
| False =>
error = NotPausedError;
Throw error
end;
RequireContractOwner;
is_paused_zil := false;
e = {
_eventname: "UnpauseZil";
is_paused_zil: false
};
event e
end
(* Sets 'to' as the royalty recipient. *)
(* @param: to - Royalty recipient address *)
(* @Requirements: *)
(* - '_sender' must be the contract owner. Otherwise, it must throw 'NotContractOwnerError' *)
(* - 'to' must not be the zero address. Otherwise, it must throw 'ZeroAddressDestinationError' *)
(* - 'to' must not be '_this_address'. Otherwise, it must throw 'ThisAddressDestinationError' *)
transition SetRoyaltyRecipient(to: ByStr20)
RequireContractOwner;
RequireValidDestination to;
royalty_recipient := to;
e = {
_eventname: "SetRoyaltyRecipient";
to: to
};
event e;
msg_to_sender = {
_tag: "ZRC6_SetRoyaltyRecipientCallback";
_recipient: _sender;
_amount: Uint128 0;
to: to
};
msgs = one_msg msg_to_sender;
send msgs
end
(* Sets 'fee_bps' as royalty fee bps. *)
(* @param: fee_bps - Royalty fee BPS *)
(* @Requirements: *)
(* - '_sender' must be the contract owner. Otherwise, it must throw 'NotContractOwnerError' *)
(* - 'fee_bps' must be in the range of 1 and 10000. Otherwise, it must throw 'InvalidFeeBPSError' *)
transition SetRoyaltyFeeBPS(fee_bps: Uint128)
RequireContractOwner;
RequireValidRoyaltyFee fee_bps;
royalty_fee_bps := fee_bps;
e = {
_eventname: "SetRoyaltyFeeBPS";
royalty_fee_bps: fee_bps
};
event e;
msg_to_sender = {
_tag: "ZRC6_SetRoyaltyFeeBPSCallback";
_recipient: _sender;
_amount: Uint128 0;
royalty_fee_bps: fee_bps
};
msgs = one_msg msg_to_sender;
send msgs
end
(* Sets 'uri' as the base URI. *)
(* @Requirements: *)
(* - '_sender' must be the contract owner. Otherwise, it must throw 'NotContractOwnerError' *)
transition SetBaseURI(uri: String)
RequireContractOwner;
base_uri := uri;
e = {
_eventname: "SetBaseURI";
base_uri: uri
};
event e;
msg_to_sender = {
_tag: "ZRC6_SetBaseURICallback";
_recipient: _sender;
_amount: Uint128 0;
base_uri: uri
};
msgs = one_msg msg_to_sender;
send msgs
end
(* Mints a token with a specific 'token_uri' and transfers it to 'to'. *)
(* Pass empty string to 'token_uri' to use the concatenated token URI. i.e. '<base_uri><token_id>'. *)
(* @param: to - Address of the token recipient *)
(* @param: token_uri - URI of a token *)
(* @Requirements: *)
(* - The contract must not be paused. Otherwise, it must throw 'PausedError' *)
transition Mint(to: ByStr20, token_uri: String, id: String)
RequireNotPaused;
domain_id = builtin sha256hash token_uri;
maybe_dns <- nft_ssi_dns[domain_id]; match maybe_dns with
| None =>
id_ = let txID = "BuyNftUsername" in Pair {String String} id txID;
HandlePayment id_
| Some address =>
RequireNotPausedZil;
is_owner = builtin eq _sender address; match is_owner with
| True => | False => e = { _exception : "SSIDNS-ZilDomainIsTaken" }; throw e end end;
MintToken to;
token_id <- token_id_count;
SetTokenURI token_id token_uri;
nft_domain_names[domain_id] := token_id;
nft_ssi_dns[domain_id] := to;
e = {
_eventname: "Mint";
to: to;
token_id: token_id;
token_uri: token_uri
};
event e;
msg_to_recipient = {
_tag: "ZRC6_RecipientAcceptMint";
_recipient: to;
_amount: Uint128 0
};
msg_to_sender = {
_tag: "ZRC6_MintCallback";
_recipient: _sender;
_amount: Uint128 0;
to: to;
token_id: token_id;
token_uri: token_uri
};
msgs = two_msgs msg_to_recipient msg_to_sender;
send msgs
end
(* Mints multiple tokens with 'token_uri's and transfers them to multiple 'to's. *)
(* Pass empty string to 'token_uri' to use the concatenated token URI. i.e. '<base_uri><token_id>'. *)
(* @param: to_token_uri_pair_list - List of Pair (to, token_uri). *)
(* @Requirements: *)
(* - The contract must not be paused. Otherwise, it must throw 'PausedError' *)
transition BatchMint(to_token_uri_pair_list: List (Pair ByStr20 String), id: String)
RequireNotPaused;
cur_id <- token_id_count;
start_id = builtin add cur_id one;
txID = "BuyNftUsername";
counter = let list_length = @list_length (Pair ByStr20 String) in list_length to_token_uri_pair_list;
HandleBatchPayment id txID counter;
forall to_token_uri_pair_list HandleMint;
end_id <- token_id_count;
e = {
_eventname: "BatchMint";
to_token_uri_pair_list: to_token_uri_pair_list;
start_id: start_id;
end_id: end_id
};
event e;
msg_to_sender = {
_tag: "ZRC6_BatchMintCallback";
_recipient: _sender;
_amount: Uint128 0
};
msgs = one_msg msg_to_sender;
send msgs
end
(* Destroys 'token_id'. *)
(* @param: token_id - Unique ID of the NFT to be destroyed *)
(* @Requirements: *)
(* - The contract must not be paused. Otherwise, it must throw 'PausedError' *)
transition Burn(token_id: Uint256)
RequireNotPaused;
(* Check if token exists *)
maybe_token_owner <- token_owners[token_id];
match maybe_token_owner with
| None =>
error = TokenNotFoundError;
Throw error
| Some token_owner =>
BurnToken token_id;
msg_to_sender = {
_tag: "ZRC6_BurnCallback";
_recipient: _sender;
_amount: Uint128 0;
token_owner: token_owner;
token_id: token_id
};
msgs = one_msg msg_to_sender;
send msgs
end
end
(* Destroys 'token_id_list'. *)
(* @param: token_id_list - List of unique IDs of the NFT to be destroyed *)
(* @Requirements: *)
(* - The contract must not be paused. Otherwise, it must throw 'PausedError' *)
transition BatchBurn(token_id_list: List Uint256)
RequireNotPaused;
forall token_id_list BurnToken;
msg_to_sender = {
_tag: "ZRC6_BatchBurnCallback";
_recipient: _sender;
_amount: Uint128 0
};
msgs = one_msg msg_to_sender;
send msgs
end
(* Adds 'minter'. *)
(* @Requirements: *)
(* - '_sender' must be the contract owner. Otherwise, it must throw 'NotContractOwnerError' *)
(* - 'minter' must not be already a minter. Otherwise, it must throw 'MinterFoundError' *)
(*
transition AddMinter(minter: ByStr20)
RequireContractOwner;
has_minter <- exists minters[minter];
match has_minter with
| True =>
error = MinterFoundError;
Throw error
| False =>
(* Add minter *)
minters[minter] := true
end;
e = {
_eventname: "AddMinter";
minter: minter
};
event e;
msg_to_sender = {
_tag: "ZRC6_AddMinterCallback";
_recipient: _sender;
_amount: Uint128 0;
minter: minter
};
msgs = one_msg msg_to_sender;
send msgs
end
*)
(* Removes 'minter'. *)
(* @Requirements: *)
(* - '_sender' must be the contract owner. Otherwise, it must throw 'NotContractOwnerError' *)
(* - 'minter' must be already a minter. Otherwise, it must throw 'MinterNotFoundError' *)
(*
transition RemoveMinter(minter: ByStr20)
RequireContractOwner;
has_minter <- exists minters[minter];
match has_minter with
| False =>
error = MinterNotFoundError;
Throw error
| True =>
delete minters[minter]
end;
e = {
_eventname: "RemoveMinter";
minter: minter
};
event e;
msg_to_sender = {
_tag: "ZRC6_RemoveMinterCallback";
_recipient: _sender;
_amount: Uint128 0;
minter: minter
};
msgs = one_msg msg_to_sender;
send msgs
end
*)
(* Sets 'spender' for 'token_id'. *)
(* To remove 'spender' for a token, use 'zero_address'. *)
(* i.e., '0x0000000000000000000000000000000000000000' *)
(* @Requirements: *)
(* - 'token_id' must exist. Otherwise, it must throw 'TokenNotFoundError' *)
(* - '_sender' must be a token owner or an operator. Otherwise, it must throw 'NotOwnerOrOperatorError' *)
(* - '_sender' must not be 'spender'. Otherwise, it must throw 'SelfError' *)
(* - 'spender' must not be already a spender. Otherwise, it must throw 'SpenderFoundError' *)
transition SetSpender(spender: ByStr20, token_id: Uint256)
RequireNotSelf spender _sender;
maybe_token_owner <- token_owners[token_id];
match maybe_token_owner with
| None =>
error = TokenNotFoundError;
Throw error
| Some token_owner =>
RequireOwnerOrOperator token_owner;
(* Check if the spender exists *)
maybe_spender <- spenders[token_id];
match maybe_spender with
| None =>
| Some cur_spender =>
has_spender = builtin eq cur_spender spender;
match has_spender with
| False =>
| True =>
error = SpenderFoundError;
Throw error
end
end;
spenders[token_id] := spender;
e = {
_eventname: "SetSpender";
token_owner: token_owner;
spender: spender;
token_id: token_id
};