From 56efc7e3e45275f45455599f14455f6a819930b3 Mon Sep 17 00:00:00 2001 From: gfanton Date: Tue, 24 Sep 2024 02:23:17 +0000 Subject: [PATCH] chore: update test4.gno.land backup --- test4.gno.land/README.md | 10 +- test4.gno.land/backup_1853076-1884748.jsonl | 38 ++ .../demo/v31/domain/domain_metadata.gno | 38 ++ .../demo/v31/domain/domain_registry.gno | 236 +++++++++ .../demo/v31/domain/domain_registry_test.gno | 378 +++++++++++++++ .../p/varmeta/demo/v31/domain/errors.gno | 12 + .../varmeta/demo/v31/domain/pkg_metadata.json | 1 + .../p/varmeta/demo/v31/domain/utils.gno | 13 + .../varmeta/demo/v31/grc/grc721/basic_nft.gno | 378 +++++++++++++++ .../demo/v31/grc/grc721/basic_nft_test.gno | 283 +++++++++++ .../p/varmeta/demo/v31/grc/grc721/errors.gno | 22 + .../demo/v31/grc/grc721/grc721_metadata.gno | 95 ++++ .../v31/grc/grc721/grc721_metadata_test.gno | 107 +++++ .../demo/v31/grc/grc721/grc721_royalty.gno | 78 +++ .../v31/grc/grc721/grc721_royalty_test.gno | 70 +++ .../p/varmeta/demo/v31/grc/grc721/igrc721.gno | 39 ++ .../demo/v31/grc/grc721/igrc721_metadata.gno | 38 ++ .../demo/v31/grc/grc721/igrc721_royalty.gno | 16 + .../demo/v31/grc/grc721/pkg_metadata.json | 1 + .../p/varmeta/demo/v31/grc/grc721/util.gno | 18 + .../demo/v32/domain/domain_metadata.gno | 38 ++ .../demo/v32/domain/domain_registry.gno | 236 +++++++++ .../demo/v32/domain/domain_registry_test.gno | 378 +++++++++++++++ .../p/varmeta/demo/v32/domain/errors.gno | 12 + .../varmeta/demo/v32/domain/pkg_metadata.json | 1 + .../p/varmeta/demo/v32/domain/utils.gno | 13 + .../varmeta/demo/v32/grc/grc721/basic_nft.gno | 378 +++++++++++++++ .../demo/v32/grc/grc721/basic_nft_test.gno | 283 +++++++++++ .../p/varmeta/demo/v32/grc/grc721/errors.gno | 22 + .../demo/v32/grc/grc721/grc721_metadata.gno | 95 ++++ .../v32/grc/grc721/grc721_metadata_test.gno | 107 +++++ .../demo/v32/grc/grc721/grc721_royalty.gno | 78 +++ .../v32/grc/grc721/grc721_royalty_test.gno | 70 +++ .../p/varmeta/demo/v32/grc/grc721/igrc721.gno | 39 ++ .../demo/v32/grc/grc721/igrc721_metadata.gno | 38 ++ .../demo/v32/grc/grc721/igrc721_royalty.gno | 16 + .../demo/v32/grc/grc721/pkg_metadata.json | 1 + .../p/varmeta/demo/v32/grc/grc721/util.gno | 18 + .../demo/v33/domain/domain_metadata.gno | 38 ++ .../demo/v33/domain/domain_registry.gno | 236 +++++++++ .../demo/v33/domain/domain_registry_test.gno | 378 +++++++++++++++ .../p/varmeta/demo/v33/domain/errors.gno | 12 + .../varmeta/demo/v33/domain/pkg_metadata.json | 1 + .../p/varmeta/demo/v33/domain/utils.gno | 13 + .../varmeta/demo/v33/grc/grc721/basic_nft.gno | 378 +++++++++++++++ .../demo/v33/grc/grc721/basic_nft_test.gno | 283 +++++++++++ .../p/varmeta/demo/v33/grc/grc721/errors.gno | 22 + .../demo/v33/grc/grc721/grc721_metadata.gno | 95 ++++ .../v33/grc/grc721/grc721_metadata_test.gno | 107 +++++ .../demo/v33/grc/grc721/grc721_royalty.gno | 78 +++ .../v33/grc/grc721/grc721_royalty_test.gno | 70 +++ .../p/varmeta/demo/v33/grc/grc721/igrc721.gno | 39 ++ .../demo/v33/grc/grc721/igrc721_metadata.gno | 38 ++ .../demo/v33/grc/grc721/igrc721_royalty.gno | 16 + .../demo/v33/grc/grc721/pkg_metadata.json | 1 + .../p/varmeta/demo/v33/grc/grc721/util.gno | 18 + .../demo/v34/domain/domain_metadata.gno | 38 ++ .../demo/v34/domain/domain_registry.gno | 236 +++++++++ .../demo/v34/domain/domain_registry_test.gno | 378 +++++++++++++++ .../p/varmeta/demo/v34/domain/errors.gno | 12 + .../varmeta/demo/v34/domain/pkg_metadata.json | 1 + .../p/varmeta/demo/v34/domain/utils.gno | 13 + .../varmeta/demo/v34/grc/grc721/basic_nft.gno | 378 +++++++++++++++ .../demo/v34/grc/grc721/basic_nft_test.gno | 283 +++++++++++ .../p/varmeta/demo/v34/grc/grc721/errors.gno | 22 + .../demo/v34/grc/grc721/grc721_metadata.gno | 95 ++++ .../v34/grc/grc721/grc721_metadata_test.gno | 107 +++++ .../demo/v34/grc/grc721/grc721_royalty.gno | 78 +++ .../v34/grc/grc721/grc721_royalty_test.gno | 70 +++ .../p/varmeta/demo/v34/grc/grc721/igrc721.gno | 39 ++ .../demo/v34/grc/grc721/igrc721_metadata.gno | 38 ++ .../demo/v34/grc/grc721/igrc721_royalty.gno | 16 + .../demo/v34/grc/grc721/pkg_metadata.json | 1 + .../p/varmeta/demo/v34/grc/grc721/util.gno | 18 + .../demo/v35/domain/domain_metadata.gno | 38 ++ .../demo/v35/domain/domain_registry.gno | 236 +++++++++ .../demo/v35/domain/domain_registry_test.gno | 378 +++++++++++++++ .../p/varmeta/demo/v35/domain/errors.gno | 12 + .../varmeta/demo/v35/domain/pkg_metadata.json | 1 + .../p/varmeta/demo/v35/domain/utils.gno | 13 + .../varmeta/demo/v35/grc/grc721/basic_nft.gno | 378 +++++++++++++++ .../demo/v35/grc/grc721/basic_nft_test.gno | 283 +++++++++++ .../p/varmeta/demo/v35/grc/grc721/errors.gno | 22 + .../demo/v35/grc/grc721/grc721_metadata.gno | 95 ++++ .../v35/grc/grc721/grc721_metadata_test.gno | 107 +++++ .../demo/v35/grc/grc721/grc721_royalty.gno | 78 +++ .../v35/grc/grc721/grc721_royalty_test.gno | 70 +++ .../p/varmeta/demo/v35/grc/grc721/igrc721.gno | 39 ++ .../demo/v35/grc/grc721/igrc721_metadata.gno | 38 ++ .../demo/v35/grc/grc721/igrc721_royalty.gno | 16 + .../demo/v35/grc/grc721/pkg_metadata.json | 1 + .../p/varmeta/demo/v35/grc/grc721/util.gno | 18 + .../demo/v36/domain/domain_metadata.gno | 38 ++ .../demo/v36/domain/domain_registry.gno | 236 +++++++++ .../demo/v36/domain/domain_registry_test.gno | 378 +++++++++++++++ .../p/varmeta/demo/v36/domain/errors.gno | 12 + .../varmeta/demo/v36/domain/pkg_metadata.json | 1 + .../p/varmeta/demo/v36/domain/utils.gno | 13 + .../varmeta/demo/v36/grc/grc721/basic_nft.gno | 378 +++++++++++++++ .../demo/v36/grc/grc721/basic_nft_test.gno | 283 +++++++++++ .../p/varmeta/demo/v36/grc/grc721/errors.gno | 22 + .../demo/v36/grc/grc721/grc721_metadata.gno | 95 ++++ .../v36/grc/grc721/grc721_metadata_test.gno | 107 +++++ .../demo/v36/grc/grc721/grc721_royalty.gno | 78 +++ .../v36/grc/grc721/grc721_royalty_test.gno | 70 +++ .../p/varmeta/demo/v36/grc/grc721/igrc721.gno | 39 ++ .../demo/v36/grc/grc721/igrc721_metadata.gno | 38 ++ .../demo/v36/grc/grc721/igrc721_royalty.gno | 16 + .../demo/v36/grc/grc721/pkg_metadata.json | 1 + .../p/varmeta/demo/v36/grc/grc721/util.gno | 18 + .../demo/v31/domain/registrar/bidding.gno | 435 +++++++++++++++++ .../v31/domain/registrar/bidding_model.gno | 33 ++ .../demo/v31/domain/registrar/errors.gno | 15 + .../varmeta/demo/v31/domain/registrar/fee.gno | 36 ++ .../demo/v31/domain/registrar/fee_checks.gno | 9 + .../demo/v31/domain/registrar/fee_native.gno | 52 ++ .../demo/v31/domain/registrar/fee_token.gno | 25 + .../demo/v31/domain/registrar/hashstring.gno | 13 + .../v31/domain/registrar/metadata_wrapper.gno | 40 ++ .../demo/v31/domain/registrar/models.gno | 22 + .../v31/domain/registrar/pkg_metadata.json | 1 + .../demo/v31/domain/registrar/prestep.gno | 41 ++ .../demo/v31/domain/registrar/registrar.gno | 154 ++++++ .../v31/domain/registrar/registrar_test.gno | 25 + .../demo/v31/domain/registrar/utils.gno | 31 ++ .../v31/domain/resolver/checks_resolver.gno | 27 ++ .../demo/v31/domain/resolver/errors.gno | 11 + .../v31/domain/resolver/pkg_metadata.json | 1 + .../demo/v31/domain/resolver/resolver.gno | 63 +++ .../v31/domain/resolver/resolver_metadata.gno | 15 + .../demo/v32/domain/registrar/bidding.gno | 435 +++++++++++++++++ .../v32/domain/registrar/bidding_model.gno | 33 ++ .../demo/v32/domain/registrar/errors.gno | 15 + .../varmeta/demo/v32/domain/registrar/fee.gno | 36 ++ .../demo/v32/domain/registrar/fee_checks.gno | 9 + .../demo/v32/domain/registrar/fee_native.gno | 52 ++ .../demo/v32/domain/registrar/fee_token.gno | 25 + .../demo/v32/domain/registrar/hashstring.gno | 13 + .../v32/domain/registrar/metadata_wrapper.gno | 40 ++ .../demo/v32/domain/registrar/models.gno | 22 + .../v32/domain/registrar/pkg_metadata.json | 1 + .../demo/v32/domain/registrar/prestep.gno | 41 ++ .../demo/v32/domain/registrar/registrar.gno | 154 ++++++ .../v32/domain/registrar/registrar_test.gno | 25 + .../demo/v32/domain/registrar/utils.gno | 31 ++ .../v32/domain/resolver/checks_resolver.gno | 27 ++ .../demo/v32/domain/resolver/errors.gno | 11 + .../v32/domain/resolver/pkg_metadata.json | 1 + .../demo/v32/domain/resolver/resolver.gno | 63 +++ .../v32/domain/resolver/resolver_metadata.gno | 15 + .../demo/v33/domain/registrar/bidding.gno | 435 +++++++++++++++++ .../v33/domain/registrar/bidding_model.gno | 33 ++ .../demo/v33/domain/registrar/errors.gno | 15 + .../varmeta/demo/v33/domain/registrar/fee.gno | 36 ++ .../demo/v33/domain/registrar/fee_checks.gno | 9 + .../demo/v33/domain/registrar/fee_native.gno | 52 ++ .../demo/v33/domain/registrar/fee_token.gno | 25 + .../demo/v33/domain/registrar/hashstring.gno | 13 + .../v33/domain/registrar/metadata_wrapper.gno | 40 ++ .../demo/v33/domain/registrar/models.gno | 22 + .../v33/domain/registrar/pkg_metadata.json | 1 + .../demo/v33/domain/registrar/prestep.gno | 41 ++ .../demo/v33/domain/registrar/registrar.gno | 152 ++++++ .../v33/domain/registrar/registrar_test.gno | 25 + .../demo/v33/domain/registrar/utils.gno | 39 ++ .../v33/domain/resolver/checks_resolver.gno | 27 ++ .../demo/v33/domain/resolver/errors.gno | 11 + .../v33/domain/resolver/pkg_metadata.json | 1 + .../demo/v33/domain/resolver/resolver.gno | 63 +++ .../v33/domain/resolver/resolver_metadata.gno | 15 + .../demo/v34/domain/registrar/bidding.gno | 454 ++++++++++++++++++ .../v34/domain/registrar/bidding_model.gno | 38 ++ .../demo/v34/domain/registrar/errors.gno | 15 + .../varmeta/demo/v34/domain/registrar/fee.gno | 36 ++ .../demo/v34/domain/registrar/fee_checks.gno | 9 + .../demo/v34/domain/registrar/fee_native.gno | 52 ++ .../demo/v34/domain/registrar/fee_token.gno | 25 + .../demo/v34/domain/registrar/hashstring.gno | 13 + .../v34/domain/registrar/metadata_wrapper.gno | 40 ++ .../demo/v34/domain/registrar/models.gno | 22 + .../v34/domain/registrar/pkg_metadata.json | 1 + .../demo/v34/domain/registrar/prestep.gno | 41 ++ .../demo/v34/domain/registrar/registrar.gno | 152 ++++++ .../v34/domain/registrar/registrar_test.gno | 25 + .../demo/v34/domain/registrar/utils.gno | 39 ++ .../v34/domain/resolver/checks_resolver.gno | 27 ++ .../demo/v34/domain/resolver/errors.gno | 11 + .../v34/domain/resolver/pkg_metadata.json | 1 + .../demo/v34/domain/resolver/resolver.gno | 63 +++ .../v34/domain/resolver/resolver_metadata.gno | 15 + .../demo/v35/domain/registrar/bidding.gno | 452 +++++++++++++++++ .../v35/domain/registrar/bidding_model.gno | 38 ++ .../demo/v35/domain/registrar/errors.gno | 15 + .../varmeta/demo/v35/domain/registrar/fee.gno | 36 ++ .../demo/v35/domain/registrar/fee_checks.gno | 9 + .../demo/v35/domain/registrar/fee_native.gno | 52 ++ .../demo/v35/domain/registrar/fee_token.gno | 25 + .../demo/v35/domain/registrar/hashstring.gno | 13 + .../v35/domain/registrar/metadata_wrapper.gno | 40 ++ .../demo/v35/domain/registrar/models.gno | 22 + .../v35/domain/registrar/pkg_metadata.json | 1 + .../demo/v35/domain/registrar/prestep.gno | 41 ++ .../demo/v35/domain/registrar/registrar.gno | 152 ++++++ .../v35/domain/registrar/registrar_test.gno | 25 + .../demo/v35/domain/registrar/utils.gno | 39 ++ .../v35/domain/resolver/checks_resolver.gno | 27 ++ .../demo/v35/domain/resolver/errors.gno | 11 + .../v35/domain/resolver/pkg_metadata.json | 1 + .../demo/v35/domain/resolver/resolver.gno | 63 +++ .../v35/domain/resolver/resolver_metadata.gno | 15 + .../demo/v36/domain/registrar/bidding.gno | 450 +++++++++++++++++ .../v36/domain/registrar/bidding_model.gno | 38 ++ .../demo/v36/domain/registrar/errors.gno | 15 + .../varmeta/demo/v36/domain/registrar/fee.gno | 36 ++ .../demo/v36/domain/registrar/fee_checks.gno | 9 + .../demo/v36/domain/registrar/fee_native.gno | 52 ++ .../demo/v36/domain/registrar/fee_token.gno | 25 + .../demo/v36/domain/registrar/hashstring.gno | 13 + .../v36/domain/registrar/metadata_wrapper.gno | 40 ++ .../demo/v36/domain/registrar/models.gno | 22 + .../v36/domain/registrar/pkg_metadata.json | 1 + .../demo/v36/domain/registrar/prestep.gno | 41 ++ .../demo/v36/domain/registrar/registrar.gno | 152 ++++++ .../v36/domain/registrar/registrar_test.gno | 25 + .../demo/v36/domain/registrar/utils.gno | 39 ++ .../v36/domain/resolver/checks_resolver.gno | 27 ++ .../demo/v36/domain/resolver/errors.gno | 11 + .../v36/domain/resolver/pkg_metadata.json | 1 + .../demo/v36/domain/resolver/resolver.gno | 63 +++ .../v36/domain/resolver/resolver_metadata.gno | 15 + test4.gno.land/metadata.json | 2 +- 231 files changed, 17367 insertions(+), 5 deletions(-) create mode 100755 test4.gno.land/backup_1853076-1884748.jsonl create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_registry.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_registry_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/domain/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/domain/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/domain/utils.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/basic_nft.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/basic_nft_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_metadata_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_royalty_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/util.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_registry.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_registry_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/domain/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/domain/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/domain/utils.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/basic_nft.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/basic_nft_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_metadata_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_royalty_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/util.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_registry.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_registry_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/domain/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/domain/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/domain/utils.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/basic_nft.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/basic_nft_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_metadata_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_royalty_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/util.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_registry.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_registry_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/domain/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/domain/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/domain/utils.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/basic_nft.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/basic_nft_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_metadata_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_royalty_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/util.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_registry.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_registry_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/domain/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/domain/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/domain/utils.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/basic_nft.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/basic_nft_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_metadata_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_royalty_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/util.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_registry.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_registry_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/domain/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/domain/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/domain/utils.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/basic_nft.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/basic_nft_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/errors.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_metadata_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_royalty_test.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721_metadata.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721_royalty.gno create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/pkg_metadata.json create mode 100644 test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/util.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/bidding.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/bidding_model.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_checks.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_native.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_token.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/hashstring.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/metadata_wrapper.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/models.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/prestep.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/registrar.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/registrar_test.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/utils.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/checks_resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/resolver_metadata.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/bidding.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/bidding_model.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_checks.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_native.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_token.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/hashstring.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/metadata_wrapper.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/models.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/prestep.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/registrar.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/registrar_test.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/utils.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/checks_resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/resolver_metadata.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/bidding.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/bidding_model.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_checks.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_native.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_token.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/hashstring.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/metadata_wrapper.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/models.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/prestep.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/registrar.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/registrar_test.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/utils.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/checks_resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/resolver_metadata.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/bidding.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/bidding_model.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_checks.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_native.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_token.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/hashstring.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/metadata_wrapper.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/models.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/prestep.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/registrar.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/registrar_test.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/utils.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/checks_resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/resolver_metadata.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/bidding.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/bidding_model.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_checks.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_native.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_token.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/hashstring.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/metadata_wrapper.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/models.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/prestep.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/registrar.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/registrar_test.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/utils.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/checks_resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/resolver_metadata.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/bidding.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/bidding_model.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_checks.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_native.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_token.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/hashstring.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/metadata_wrapper.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/models.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/prestep.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/registrar.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/registrar_test.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/utils.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/checks_resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/errors.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/pkg_metadata.json create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/resolver.gno create mode 100644 test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/resolver_metadata.gno diff --git a/test4.gno.land/README.md b/test4.gno.land/README.md index f1fc8e9e..b0bf8c25 100644 --- a/test4.gno.land/README.md +++ b/test4.gno.land/README.md @@ -2,7 +2,7 @@ ## TXs ``` -19534 +19572 ``` ## addpkgs @@ -11,12 +11,12 @@ ## top realm calls ``` - 4197 "gno.land/r/demo/wugnot" + 4201 "gno.land/r/demo/wugnot" 1142 "gno.land/r/gnoswap/gns" - 828 "gno.land/r/gnoswap/v2/gns" + 830 "gno.land/r/gnoswap/v2/gns" 764 "gno.land/r/gnoswap/router" 755 "gno.land/r/gnoswap/position" - 663 "gno.land/r/gnoswap/v2/router" + 665 "gno.land/r/gnoswap/v2/router" 654 "gno.land/r/gnoswap/gnft" 605 "gno.land/r/onbloc/usdc" 569 "gno.land/r/onbloc/foo" @@ -49,6 +49,7 @@ 10 "gno.land/r/demo/foo20" 10 "gno.land/r/g15mzjefvj9pt2ctv30l9ju03rewmfv9hken9wfm/v2/bridge" 10 "gno.land/r/g1w62226g8hykfmtuasvz80rdf0jl6phgxsphh5v/testing/disperse" + 10 "gno.land/r/varmeta/demo/v33/domain/registrar" 8 "gno.land/r/demo/boards" 8 "gno.land/r/g1e5hud66rs7ye4zgeqmqvwfhurs2mnf408hdqcl/feedback_v1" 8 "gno.land/r/molaryy/openocean" @@ -93,6 +94,7 @@ 1 "gno.land/r/gnoswap/consts" 1 "gno.land/r/gnoswap/v2/emission" 1 "gno.land/r/mikecito/grc20_launchpad" + 1 "gno.land/r/varmeta/demo/v32/domain/registrar" ``` ## top faucet requesters diff --git a/test4.gno.land/backup_1853076-1884748.jsonl b/test4.gno.land/backup_1853076-1884748.jsonl new file mode 100755 index 00000000..ca2febc3 --- /dev/null +++ b/test4.gno.land/backup_1853076-1884748.jsonl @@ -0,0 +1,38 @@ +{"tx":{"msg":[{"@type":"/bank.MsgSend","from_address":"g18l9us6trqaljw39j94wzf5ftxmd9qqkvrxghd2","to_address":"g14ptgg9gqydc5myx89f48edvl5y4md30p6u63yl","amount":"10000000ugnot"}],"fee":{"gas_wanted":"100000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AxjyQBsJI8CesbgwihvS/eQtYKoezyoA9P+FRlLAKRwg"},"signature":"l8vAOkkTvm4yOupAkZsB32cF57s+MpT4r7bCXeKWJMFiSddLQH86OboX5W59TZFGwWOt60+2GDEtiWTI9PaUkQ=="}],"memo":""},"blockNum":"1859439"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"grc721","path":"gno.land/p/varmeta/demo/v31/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, tid TokenID) error\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"VlqjgdXKaWC2VNuoVuzLhYVVQA1RIxwgWrP33Si13yBq9IGgrPxAdsTrtEXCPjhU6aqWJc2nZ5kV5qp06jIDug=="}],"memo":""},"blockNum":"1859546"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"domain","path":"gno.land/p/varmeta/demo/v31/domain","files":[{"name":"domain_metadata.gno","body":"package domain\n\nimport (\n\t\"time\"\n)\n\n// Trait represents a key-value pair with an optional display type for metadata attributes\ntype Trait struct {\n\tDisplayType string // Optional display type (e.g., \"date\", \"number\", etc.)\n\tTraitType string // Type of the trait (e.g., \"age\", \"height\", etc.)\n\tValue string // Value of the trait\n}\n\n// Metadata represents the metadata associated with a domain\ntype Metadata struct {\n\tAvatar string // URL or identifier for an avatar image\n\tRegistrationTime time.Time // The time when the domain was registered\n\tExpirationTime time.Time // The time when the domain will be expire\n\tAttributes []Trait // Additional attributes of the domain\n\tDescription string // A description of the domain\n\tContactInfo string // Contact information for the domain owner\n\tRenewalFee string // The fee required to renew the domain, represented as a string\n}\n\n// NewMetadata creates a new Metadata instance\nfunc NewMetadata(avatar, description, contactInfo, renewalFee string,\n\tregistrationTime, expirationTime time.Time, attributes []Trait,\n) Metadata {\n\treturn Metadata{\n\t\tAvatar: avatar,\n\t\tRegistrationTime: registrationTime,\n\t\tExpirationTime: expirationTime,\n\t\tRenewalFee: renewalFee,\n\t\tAttributes: attributes,\n\t\tDescription: description,\n\t\tContactInfo: contactInfo,\n\t}\n}\n"},{"name":"domain_registry.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v31/grc/grc721\"\n)\n\n// domainRegistry represents a registry for domain names with metadata\ntype domainRegistry struct {\n\tdomains grc721.IGRC721 // Interface for basic NFT functionality\n\tmetadata *avl.Tree // AVL tree for storing domain metadata\n\texpDate time.Time\n}\n\n// DomainRegistry defines the methods for managing domain names and metadata\ntype DomainRegistry interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(domainName string) (std.Address, error)\n\tSafeTransferFrom(from, to std.Address, domainName string) error\n\tTransferFrom(from, to std.Address, domainName string) error\n\tApprove(approved std.Address, domainName string) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(domainName string) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, domainName string) error\n\n\tRegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error\n\tSetDomainData(domainName string, metadata Metadata) error\n\tGetDomainData(domainName string, field MetadataField) (Metadata, error)\n\tGetDomainFields(domainName string, fields []MetadataField) (Metadata, error)\n\tRenewDomain(domainName string, additionalDuration time.Duration) error\n\tGetExpirationDate(domainName string) time.Time\n\tSetExpirationDate(domainName string, expDate time.Time) bool\n}\n\n// NewDomainRegistry creates a new domain registry with metadata extensions\nfunc NewDomainRegistry(name, symbol string) *domainRegistry {\n\tregistry := grc721.NewBasicNFT(name, symbol)\n\n\treturn \u0026domainRegistry{\n\t\tdomains: registry,\n\t\tmetadata: avl.NewTree(),\n\t}\n}\n\n// RegisterDomain registers a new domain with the given metadata\nfunc (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error {\n\terr := d.domains.Mint(owner, grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\td.expDate = time.Now().Add(dur)\n\td.metadata.Set(domainName, metadata)\n\n\treturn nil\n}\n\n// RenewDomain extends the expiration time of a domain name\nfunc (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn ErrInvalidDomainName\n\t}\n\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\t// set new expiration date\n\td.expDate = d.expDate.Add(additionalDuration)\n\treturn nil\n}\n\n// SetDomainData sets the metadata for a given domain name\nfunc (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error {\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\td.metadata.Set(domainName, metadata)\n\treturn nil\n}\n\n// GetDomainFields retrieves multiple fields of metadata for a given domain\nfunc (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tif len(fields) == 0 {\n\t\treturn metadata, nil\n\t}\n\n\tvar result Metadata\n\tfor _, field := range fields {\n\t\tswitch field {\n\t\tcase FieldAvatar:\n\t\t\tresult.Avatar = metadata.Avatar\n\t\tcase FieldRegistrationTime:\n\t\t\tresult.RegistrationTime = metadata.RegistrationTime\n\t\tcase FieldExpirationTime:\n\t\t\tresult.ExpirationTime = metadata.ExpirationTime\n\t\tcase FieldRenewalFee:\n\t\t\tresult.RenewalFee = metadata.RenewalFee\n\t\tcase FieldAttributes:\n\t\t\tresult.Attributes = metadata.Attributes\n\t\tcase FieldDescription:\n\t\t\tresult.Description = metadata.Description\n\t\tcase FieldContactInfo:\n\t\t\tresult.ContactInfo = metadata.ContactInfo\n\t\tdefault:\n\t\t\treturn Metadata{}, ErrInvalidMetadataField\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// GetDomainData retrieves metadata for a given domain\nfunc (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tswitch field {\n\tcase FieldAvatar:\n\t\treturn Metadata{\n\t\t\tAvatar: metadata.Avatar,\n\t\t}, nil\n\tcase FieldRegistrationTime:\n\t\treturn Metadata{\n\t\t\tRegistrationTime: metadata.RegistrationTime,\n\t\t}, nil\n\tcase FieldExpirationTime:\n\t\treturn Metadata{\n\t\t\tExpirationTime: metadata.ExpirationTime,\n\t\t}, nil\n\tcase FieldRenewalFee:\n\t\treturn Metadata{\n\t\t\tRenewalFee: metadata.RenewalFee,\n\t\t}, nil\n\tcase FieldAttributes:\n\t\treturn Metadata{\n\t\t\tAttributes: metadata.Attributes,\n\t\t}, nil\n\tcase FieldDescription:\n\t\treturn Metadata{\n\t\t\tDescription: metadata.Description,\n\t\t}, nil\n\tcase FieldContactInfo:\n\t\treturn Metadata{\n\t\t\tContactInfo: metadata.ContactInfo,\n\t\t}, nil\n\tdefault:\n\t\treturn Metadata{}, ErrInvalidMetadataField\n\t}\n}\n\n// BalanceOf returns the number of domains owned by a given address\nfunc (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) {\n\treturn d.domains.BalanceOf(owner)\n}\n\n// OwnerOf returns the owner of a given domain name\nfunc (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) {\n\treturn d.domains.OwnerOf(grc721.TokenID(domainName))\n}\n\n// SafeTransferFrom safely transfers a domain from one address to another\nfunc (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// TransferFrom transfers a domain from one address to another\nfunc (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.TransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// Approve grants approval to another address to manage a specific domain\nfunc (d *domainRegistry) Approve(approved std.Address, domainName string) error {\n\treturn d.domains.Approve(approved, grc721.TokenID(domainName))\n}\n\n// SetApprovalForAll sets approval for an operator to manage all domains of the owner\nfunc (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error {\n\treturn d.domains.SetApprovalForAll(operator, approved)\n}\n\n// GetApproved returns the approved address for a specific domain\nfunc (d *domainRegistry) GetApproved(domainName string) (std.Address, error) {\n\treturn d.domains.GetApproved(grc721.TokenID(domainName))\n}\n\n// IsApprovedForAll checks if an operator is approved to manage all domains of the owner\nfunc (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool {\n\treturn d.domains.IsApprovedForAll(owner, operator)\n}\n\n// Mint creates a new domain for a given address\nfunc (d *domainRegistry) Mint(to std.Address, domainName string) error {\n\treturn d.domains.Mint(to, grc721.TokenID(domainName))\n}\n\nfunc (d *domainRegistry) GetExpirationDate(domainName string) time.Time {\n\treturn d.expDate\n}\n\nfunc (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn false\n\t}\n\td.expDate = expDate\n\treturn true\n}\n"},{"name":"domain_registry_test.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v3/grc/grc721\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\taddr1 = testutils.TestAddress(\"bob\")\n\taddr2 = testutils.TestAddress(\"alice\")\n)\n\nfunc TestRegisterDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"contact@registered.com\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Duplicate Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\terr := registry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.owner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\t\t\terr := registry.SetDomainData(c.domainName, c.metadata)\n\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\tretrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRenewDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tadditionalTime time.Duration\n\t\texpectError bool\n\t\texpectedExpiry time.Time\n\t}{\n\t\t{\n\t\t\tname: \"Successful Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A renewable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.RenewDomain(c.domainName, c.additionalTime)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\trenewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\t// urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfield MetadataField\n\t\texpectError bool\n\t\texpectedVal string\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Avatar\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: false,\n\t\t\texpectedVal: \"avatar_url\",\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain Name\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t_, err := registry.GetDomainData(c.domainName, c.field)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainFields(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfields []MetadataField\n\t\texpectError bool\n\t\texpected Metadata\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Multiple Fields\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo},\n\t\t\texpectError: false,\n\t\t\texpected: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tretrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar)\n\t\t\t\turequire.Equal(t, c.expected.Description, retrievedMetadata.Description)\n\t\t\t\turequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTransferDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tnewOwner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A transferable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.TransferFrom(c.owner, c.newOwner, c.domainName)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.newOwner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"errors.gno","body":"package domain\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"caller is not domain owner\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name\")\n\tErrInvalidMetadataField = errors.New(\"invalid metadata field\")\n\tErrInsufficientFunds = errors.New(\"insufficient funds for renewal\")\n)\n"},{"name":"utils.gno","body":"package domain\n\ntype MetadataField int\n\nconst (\n\tFieldAvatar MetadataField = iota\n\tFieldRegistrationTime\n\tFieldRenewalFee\n\tFieldExpirationTime\n\tFieldAttributes\n\tFieldDescription\n\tFieldContactInfo\n)\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"dkfY1weqVgUJus11IGJZT31Tj457E4icZ+V/Zxrz92N89COIYEQB0vcJwrlXfpHKQz2LK9Lk3yUR/uPm7L8Stg=="}],"memo":""},"blockNum":"1859571"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"registrar","path":"gno.land/r/varmeta/demo/v31/domain/registrar","files":[{"name":"bidding.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\t// \"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/avl\"\n)\n\ntype bidRecord struct {\n\tDomainName string\n\tBidder std.Address\n\tHashString string\n\tPrice int\n\tStartTime time.Time\n\tEndCommitTime time.Time\n\tEndPriceTime time.Time\n\tCurrentPhase actionCode\n\tIsOpen bool\n}\n\nvar (\n\tBidRec *avl.Tree // bidRecord \u003c- []bidRec\n\twinnerRec *avl.Tree\n\tjoinedBid avl.Tree\n)\n\n// joinedBid: address \u003c- []domainName\nfunc recordJoinedBid(domainName string) {\n\tcaller := std.GetOrigCaller()\n\tdList := []string{}\n\tdata, existed := joinedBid.Get(domainName)\n\tif !existed {\n\t\tdList = []string{domainName}\n\t\tjoinedBid.Set(caller.String(), dList)\n\t\treturn\n\t}\n\tdList = data.([]string)\n\tdList = append(dList, domainName)\n\tjoinedBid.Set(caller.String(), dList)\n\treturn\n}\n\n// get the joined bids of an account\nfunc GetJoinedBid() []string {\n\tcaller := std.GetOrigCaller().String()\n\tdata, existed := joinedBid.Get(caller)\n\tif !existed {\n\t\treturn []string{}\n\t}\n\tlist := data.([]string)\n\treturn list\n}\n\n// get the next state of bidding session\nfunc GetCurrentStatus(domainName string) string {\n\t// if there is record in joinedBid -\u003e user joined\n\t// check for tine.Now() and startTime\n\tnow := time.Now()\n\tcaller := std.GetOrigCaller()\n\t// find the record\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tif _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister {\n\t\t\treturn \"hash\"\n\t\t}\n\t\tufmt.Println(\"run here\")\n\t\treturn \"dName is invalid\"\n\t}\n\trecList := data.([]bidRecord)\n\trec := recList[0]\n\n\tif now.Before(rec.EndCommitTime) {\n\t\treturn \"waiting price\"\n\t}\n\tif now.Before(rec.EndPriceTime) \u0026\u0026 now.After(rec.EndCommitTime) {\n\t\treturn \"price\"\n\t}\n\tif now.After(rec.EndPriceTime) {\n\t\treturn \"closed\"\n\t}\n\treturn \"undefined\"\n}\n\n// Render() renders welcome message :D\nfunc Render(path string) string {\n\treturn \"welcome to varmeta domain name service\"\n}\n\n/*\n\tlogic should be:\n\t1. User click register for a dns.\n\t-\u003e 1.1: dns does not exists -\u003e open bidding session\n\t-\u003e 1.2: dns existed -\u003e check if open bidding session is openning or not -\u003e if open -\u003e new bidRecord\n\t2. Bidding session:\n\t-\u003e 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -\u003e creat bidRecord to add to bidRec\n\t-\u003e 2.2: CommitPrice phase: commit price + secret key: -\u003e if matches -\u003e write price into bidRec ( send the amunt of coin corresponding)\n\t-\u003e 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button\n\t---\u003e admin end bidding auction\n\t-\u003e 2.3.1: cooldown phase -\u003e waiting for winner -\u003e 2nd winner\n\t-\u003e 2.4: re-transfer coin from admin to user who not win the bid\n\t\tthe flow is: user want to register new domain name -\u003e start new bidding session -\u003e got the bidding session id\n\t\tif other want to register to the same domain name -\u003e bidding into this session, if not return to new state\n\t\twhen the session ends, need to have 2nd phase called commit -\u003e commit their price and secret to compute the hash\n\t\t-\u003e compare the hash as in record -\u003e accept the hash -\u003e accept the price\n\t\t*** temp approach: everytime user commit, user open a bid session if not existed, if existed -\u003e check if this session is ended.\n*/\n// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate\n// the time whenever there is action to commit.\n\n// commit the domain name and computed hash string\nfunc CommitHash(domainName, hashString string) bidStatus {\n\tbStt := bidStatus{}\n\tcaller := std.GetOrigCaller()\n\tufmt.Println(\"caller: \", caller.String())\n\tnow := time.Now()\n\n\t// update the bid record\n\tdata, existed := BidRec.Get(domainName)\n\n\t// if not existed -\u003e create new record\n\tif !existed {\n\t\tvar bidRec bidRecord\n\t\tendCommitTime := now.Add(defaultCommitHashTime)\n\t\tendPriceTime := endCommitTime.Add(defaultCommitPriceTime)\n\t\tufmt.Println(\"endCommitTime: \", endCommitTime.Format(time.RFC3339))\n\t\tufmt.Println(\"endPriceTime: \", endPriceTime.Format(time.RFC3339))\n\t\tbidRec = bidRecord{\n\t\t\tDomainName: domainName,\n\t\t\tBidder: caller,\n\t\t\tHashString: hashString,\n\t\t\tStartTime: now,\n\t\t\tEndCommitTime: endCommitTime,\n\t\t\tEndPriceTime: endPriceTime,\n\t\t\tIsOpen: true,\n\t\t}\n\t\tbidRecList := []bidRecord{bidRec}\n\t\tBidRec.Set(domainName, bidRecList)\n\n\t\t// charge fee\n\t\tchargeFee(fee.BidJoinFee, caller)\n\t\tbStt.CurrentStatus = NewBiddingSession\n\t\tbStt.ActionCode = CommitPricePhase\n\t\treturn bStt\n\t}\n\t// if existed\n\t// TODO: Check if commit more than 1 time -\u003e done\n\tbidRecList := data.([]bidRecord)\n\tstartTime := bidRecList[0].StartTime\n\tif now.After(bidRecList[0].EndCommitTime) {\n\t\tpanic(\"can not commit hash anymore\")\n\t}\n\tfor _, bR := range bidRecList {\n\t\tif bR.Bidder == caller {\n\t\t\tpanic(\"you already commited hash\")\n\t\t}\n\t}\n\n\toldEndCommitTime := bidRecList[0].EndCommitTime\n\toldEndPriceTime := bidRecList[0].EndPriceTime\n\tbidRec := bidRecord{\n\t\tDomainName: domainName,\n\t\tHashString: hashString,\n\t\tBidder: caller,\n\t\tStartTime: startTime,\n\t\tEndCommitTime: oldEndCommitTime,\n\t\tEndPriceTime: oldEndPriceTime,\n\t\tIsOpen: true,\n\t}\n\tbidRecList = append(bidRecList, bidRec)\n\t// Save record\n\tBidRec.Set(domainName, bidRecList)\n\t// charge commit hash fee\n\tchargeFee(fee.BidJoinFee, caller)\n\tbStt.CurrentStatus = AppendBiddingSession\n\tbStt.ActionCode = CommitPricePhase\n\treturn bStt\n}\n\n// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me\n// commit price and secret to reveal auction session\nfunc CommitPrice(price int, secret string, domainName string) bidStatus {\n\tvar bStt bidStatus\n\t// compute the hash string, compare to saved hash string in record\n\tnow := time.Now()\n\tjoinedString := secret + strconv.Itoa(price)\n\tcomputedHashString := Get256String(joinedString)\n\tufmt.Println(\"computed hash: \", computedHashString)\n\tcaller := std.GetOrigCaller()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\" domain name is invalid\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerRec.Set(domainName, bidRecList[0])\n\n\tufmt.Println(\"time now: \", now.Format(time.RFC3339))\n\tufmt.Println(\"end commit phase time: \", bidRecList[0].EndCommitTime.Format(time.RFC3339))\n\t// case commit after end - consider panic or not\n\tif now.After(bidRecList[0].EndPriceTime) {\n\t\tufmt.Println(\"commit price phase is ended\")\n\t\tbStt.ActionCode = 0\n\t\tbStt.CurrentStatus = 0\n\t\treturn bStt\n\t}\n\t// case commit when price phase not started\n\tif time.Now().Before(bidRecList[0].EndCommitTime) {\n\t\tufmt.Println(\"commit price phase is not started yet\")\n\t\tbStt.ActionCode = 0\n\t\tbStt.CurrentStatus = 0\n\t\treturn bStt\n\t}\n\n\t// search for the corresponding hash\n\tfor _, bidRec := range bidRecList {\n\t\t// panic because wrong price or wrong secret string\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString != computedHashString {\n\t\t\tpanic(\"invalid hash string\")\n\t\t}\n\t\t// found it, update the winner price\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString == computedHashString {\n\t\t\tdata, _ := winnerRec.Get(domainName)\n\t\t\tcurrentWinnerRec := data.(bidRecord)\n\t\t\tif price \u003e currentWinnerRec.Price \u0026\u0026 now.Before(currentWinnerRec.EndPriceTime) {\n\t\t\t\tufmt.Println(\"found new winner, setting up\")\n\t\t\t\tcurrentWinnerRec.Price = price\n\t\t\t\tcurrentWinnerRec.Bidder = bidRec.Bidder\n\t\t\t\tcurrentWinnerRec.HashString = bidRec.HashString\n\t\t\t\twinnerRec.Set(domainName, currentWinnerRec)\n\t\t\t\tbStt.ActionCode = ClaimPhase\n\t\t\t\treturn bStt\n\t\t\t}\n\t\t}\n\t}\n\t// if not match above case, then panic\n\tpanic(\"commit failed\")\n}\n\nfunc GetCurrentWinner(domainName string) bidRecord {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"no winner yet\")\n\t}\n\treturn data.(bidRecord)\n}\n\n// find the highest bid in session - incase everyone commited price\nfunc findTheWinner(domainName string) bidRecord {\n\tvar winnerBid bidRecord\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerBid = bidRecList[0]\n\tfor _, bidRec := range bidRecList {\n\t\tif bidRec.Price \u003e winnerBid.Price {\n\t\t\twinnerBid = bidRec\n\t\t}\n\t}\n\treturn winnerBid\n}\n\n// register the domain for winner\nfunc registerForWinner(domainName string, winnerRec bidRecord) bool {\n\twinnerAddr := winnerRec.Bidder\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: winnerAddr,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tfeeProcess(requestInfo)\n\treturn false\n}\n\n// everyone can call EndBid()\n// this EndBid checks endTime -\u003e end the auction\nfunc EndBid(domainName string) error {\n\tnow := time.Now()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\treturn ufmt.Errorf(\"endbid: invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\tfirstBidRec := bidRecList[0]\n\tif now.Before(firstBidRec.EndPriceTime) {\n\t\treturn ufmt.Errorf(\"endbid: this session can not end before the end time\")\n\t}\n\t// change all state\n\tfor _, bidRec := range bidRecList {\n\t\tbidRec.IsOpen = false\n\t}\n\tok := BidRec.Set(domainName, bidRecList)\n\tif !ok {\n\t\treturn ufmt.Errorf(\"endbid: can not change bid record state\")\n\t}\n\t// need more conditions for findTheWinner()\n\tfindTheWinner(domainName)\n\treturn nil\n}\n\n// register new domain with bidding process inside - if message is commit hash -\u003e dapp need to call commit hash\nfunc RegisterDomain(domainName string) bidStatus {\n\treqInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: \"native\",\n\t}\n\n\t// bidStatus\n\tresult := checkRegisterState(reqInfo)\n\trecordJoinedBid(domainName)\n\t// if checkState get the message of which phase this domain name belongs to, then return the status\n\treturn result\n}\n\nfunc checkRegisterState(req RequestInfo) bidStatus {\n\tvar bStt bidStatus\n\t// check if domain name is regex valid\n\tif !isValidDomain(req.WantedDomain) {\n\t\tpanic(\"invalid domain name\")\n\t}\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\tpanic(\"domain name already registered\")\n\t}\n\t// changelogs v2: we are using sealed bidding now\n\t// check if a bidding session is openning -\u003e append new commit hash into record list\n\t// both existed or not we open new bidding session\n\t// for now we return a signal for dapps / service to know what to do next\n\tisExisted, isOpen := checkBiddingState(req.WantedDomain)\n\tif isExisted \u0026\u0026 isOpen {\n\t\t// return commit hash signal for dapps\n\t\tbStt.CurrentStatus = AppendBiddingSession\n\t\tbStt.ActionCode = CommitHashPhase\n\t\treturn bStt\n\t}\n\n\t// create new session\n\tif !isExisted {\n\t\tbStt.CurrentStatus = NewBiddingSession\n\t\tbStt.ActionCode = CommitHashPhase\n\t\treturn bStt\n\t}\n\n\t// not found in register repository but also not found in Bidding Record -\u003e panic(\"error somewhere :D\")\n\tpanic(ufmt.Errorf(\"should not happend\"))\n\treturn bStt\n}\n\n// // open a bidding session\n// func openBiddingSession(domainName string, dur time.Duration) string {\n// \tnow := time.Now()\n// \tbidRec := bidRecord{\n// \t\tDomainName: domainName,\n// \t\tStartTime: now,\n// \t}\n// \tbidRecList := []bidRecord{bidRec}\n// \tok := BidRec.Set(domainName, bidRecList)\n// \tif !ok {\n// \t\tpanic(\"can not open bidding session\")\n// \t}\n// \treturn \"created bidding session\"\n// }\n\nfunc checkBiddingState(dName string) (isExisted bool, isOpen bool) {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tisExisted = false\n\t\tisOpen = false\n\t\treturn\n\t}\n\tisExisted = true\n\trecList := data.([]bidRecord)\n\tif recList[0].IsOpen {\n\t\tisOpen = true\n\t} else {\n\t\tisOpen = false\n\t}\n\treturn isExisted, isOpen\n}\n\n// get all the price list that joined the bid for displaying in dapp\nfunc GetRecords(dName string) []bidRecord {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tpanic(\"should not\")\n\t}\n\treturn data.([]bidRecord)\n}\n\n// chargeFee will charge amount - send from this contract to admin\nfunc chargeFee(amount int64, from std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", amount)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcoins := checkCoin(from)\n\tufmt.Println(\"check balances: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// pay fee and claim the domain name if you are winner\nfunc Claim(domainName string) bool {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"claim: invalid domain name\")\n\t}\n\tcaller := std.GetOrigCaller()\n\trec := data.(bidRecord)\n\tif caller != rec.Bidder {\n\t\tpanic(\"only winner can claim\")\n\t}\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: rec.Bidder,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tchargeFee(100, caller)\n\tfeeProcess(requestInfo)\n\treturn true\n}\n"},{"name":"bidding_model.gno","body":"package registrar\n\n/*\n\t0: StatusFailed\n\tCurrentStatus:\t1: NewBiddingSession\n\t\t\t\t\t2: AppendBiddingSession\n\t\t\t\t\t3: BiddingSessionClosed\n\tActionCode:\t\t1: CommitHashPhase\n\t\t\t\t\t2: CommitPricePhase\n\t\t\t\t\t3: ClaimPhase...\n*/\n\ntype bidStatus struct {\n\tCurrentStatus currStatus\n\tActionCode actionCode\n}\n\ntype (\n\tcurrStatus int\n\tactionCode int\n)\n\nvar (\n\tStatusFailed currStatus = 0\n\tNewBiddingSession currStatus = 1\n\tAppendBiddingSession currStatus = 2\n\tBiddingSessionClosed currStatus = 3\n\n\tActionFailed actionCode = 0\n\tCommitHashPhase actionCode = 1\n\tCommitPricePhase actionCode = 2\n\tClaimPhase actionCode = 3\n)\n"},{"name":"errors.gno","body":"package registrar\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnknown = errors.New(\"unknow errors\")\n\tErrOK = errors.New(\"ok\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"ErrInvalidDomainName\")\n\tErrAlreadyRegistered = errors.New(\"this domain is registered\")\n\tErrCrossRealms = errors.New(\"cross realms function error\")\n\tErrNotFound = errors.New(\"domain not found\")\n)\n"},{"name":"fee.gno","body":"package registrar\n\nimport (\n\t\"time\"\n)\n\n// only admin can set Fee, other just can read only\ntype feeInfo struct {\n\tRegisterBaseFee int64\n\tRenewalFee int64\n\tRegisterAdditionFee int64\n\tBidJoinFee int64\n}\n\nfunc GetRegisterFee(dName string) int64 {\n\treturn fee.RegisterBaseFee\n}\n\nfunc GetRenewalFee(dName string, amount time.Duration) int64 {\n\treturn fee.RenewalFee\n}\n\n// Admin set register fee and renewal fee\nfunc AdminSetFee(regFee int64, renewFee int64) {\n\t// consider logic\n\tassertIsAdmin()\n\tfee.RegisterBaseFee = regFee\n\tfee.RenewalFee = renewFee\n}\n\n// simple err check\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"fee_checks.gno","body":"package registrar\n\n// import (\n// \t\"\"\n// \t// \"std\"\n// \t// \"time\"\n// )\n\n\n"},{"name":"fee_native.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// admin access only\nfunc AdminWithdraw(amount int64) {\n\tassertIsAdmin()\n\tthisContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tsuperBanker.SendCoins(thisContract, admin, coinsToTransfer)\n}\n\nfunc nativeProcess() {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcaller := std.GetOrigCaller()\n\tcoins := checkCoin(caller)\n\tufmt.Println(\"check: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// RevertTransfer will revert the transaction - send amount of coin to user\nfunc revertTransfer(userAddr std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToReturn := std.NewCoins(ugnotCoin)\n\tufmt.Println(\"return coins from contract \", bankerContract.String(), \" to \", userAddr.String())\n\tbankerUser.SendCoins(bankerContract, userAddr, coinsToReturn)\n}\n\n// simple check for admin call\nfunc assertIsAdmin() {\n\t// check if GetCallerAt 2 or 3 when deployed\n\tcaller := std.GetCallerAt(3)\n\terr := ufmt.Sprintf(\"unauthorize with caller: %s\\n\", caller)\n\tif caller != admin \u0026\u0026 caller != adminVar {\n\t\tpanic(err)\n\t}\n}\n\n// checking for availble coins\nfunc checkCoin(from std.Address) std.Coins {\n\t// caller := std.GetOrigCaller()\n\treturn bankerUser.GetCoins(from)\n}\n"},{"name":"fee_token.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/varmeta/demo1/domain/vmt\"\n)\n\n// expected approved already from client -\u003e transfer from caller to admin\nfunc tokenProcess(dName string, callerStd std.Address) {\n\tcaller := pusers.AddressOrName(callerStd.String())\n\n\tnow := std.CurrentRealm().Addr()\n\tnowAddr := pusers.AddressOrName(now.String())\n\tufmt.Println(\"current realm transfer: \", now.String())\n\tcallerAllowance := vmt.Allowance(caller, nowAddr)\n\tcallerAllowanceString := ufmt.Sprintf(\"%d\", callerAllowance)\n\tufmt.Println(\"caller allowance \", callerAllowanceString)\n\n\tadminAddr := pusers.AddressOrName(admin.String())\n\tufmt.Println(\"admin: \", admin.String())\n\tvmt.TransferFrom(caller, adminAddr, 1)\n}\n"},{"name":"hashstring.gno","body":"package registrar\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n)\n\nfunc Get256String(input string) string {\n\tdata := []byte(input)\n\thashed := sha256.Sum256(data)\n\thashedBytes := hashed[:]\n\treturn hex.EncodeToString(hashedBytes)\n}\n"},{"name":"metadata_wrapper.gno","body":"package registrar\n\nimport (\n\t\"bytes\"\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/p/varmeta/demo/v31/domain\"\n)\n\n// Metadata wrapper\n// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait)\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcreatedAt := time.Now()\n\texpTime := createdAt.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", createdAt, expTime, []domain.Trait{})\n}\n\ntype remapMetadata struct {\n\tAvatar string // avatar - URL or identifier for an avatar image\n\tRegistrationTime string // regtime - The time when the domain was registered\n\tExpirationTime string // exptime - The time when the domain will be expire\n\tAttributes []domain.Trait // atts - Additional attributes of the domain\n\tDescription string // des - A description of the domain\n\tContactInfo string // contacts - Contact information for the domain owner\n\tRenewalFee string // renewalfee - The fee required to renew the domain, represented as a string\n}\n\n// currently not support for arrays\nfunc (m remapMetadata) MarshalJSON() ([]byte, error) {\n\tjson := new(bytes.Buffer)\n\tif m.Attributes == nil {\n\t\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, \"empty\", m.Description, m.ContactInfo, m.RenewalFee))\n\t\treturn json.Bytes(), nil\n\t}\n\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee))\n\treturn json.Bytes(), nil\n}\n"},{"name":"models.gno","body":"package registrar\n\nimport (\n\t\"std\"\n)\n\ntype RequestInfo struct {\n\tMode string\n\tWantedDomain string\n\tCaller std.Address\n\tTransInfo TransferInfo\n\t// xxx extendTime, renew...\n}\ntype TransferInfo struct {\n\tFrom std.Address\n\tTo std.Address\n}\ntype ExecuteResult struct {\n\tSuccess bool\n\tResultDetails error\n\tMessage string\n}\n"},{"name":"prestep.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v31/domain\"\n)\n\nvar (\n\tdomainStorage *avl.Tree // domainName -\u003e std.Address\n\trootRegistry domain.DomainRegistry\n\n\t// fee\n\tsuperBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction\n\tbankerUser std.Banker // full access to coins sent with the transaction that called the banker\n\n\tadmin std.Address // admin\n\tadminVar std.Address // admin in server\n\tfee feeInfo\n)\n\nfunc init() {\n\tdomainStorage = avl.NewTree()\n\tBidRec = avl.NewTree()\n\twinnerRec = avl.NewTree()\n\trootRegistry = domain.NewDomainRegistry(\"Varmeta\", \"vmt\")\n\n\t// fee init\n\tadmin = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" //@thinhnx\n\tadminVar = \"g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx\" //@varmeta-sponsorkey\n\t// ugnot\n\tfee = feeInfo{\n\t\tRegisterBaseFee: 100,\n\t\tRenewalFee: 100,\n\t\tRegisterAdditionFee: 0,\n\t\tBidJoinFee: 100,\n\t}\n\tsuperBanker = std.GetBanker(std.BankerTypeRealmSend)\n\tbankerUser = std.GetBanker(std.BankerTypeOrigSend)\n}\n"},{"name":"registrar.gno","body":"/*\nThis package contains functions that will actually execute the request from user\nFeatures: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion...\n*/\n// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm.\n// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session\n\n// currently we dont using too much panic because we dont have defer functions to revert the state of storage\npackage registrar\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v31/domain\"\n)\n\n// XXX: consider using panic instead of return string or errors\nfunc Register(domainName string, mode string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: mode,\n\t}\n\n\tregResult := executeRegister(requestInfo)\n\n\t// calling panic to stop paying fee\n\tif !regResult.Success {\n\t\tpanic(regResult.ResultDetails.Error())\n\t}\n\t// pay fee with panic inside\n\tfeeProcess(requestInfo)\n\treturn \"Register Done\"\n}\n\nfunc executeRegister(req RequestInfo) ExecuteResult {\n\t// check if domain name is regex valid\n\tvar execRes ExecuteResult\n\tif !isValidDomain(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrInvalidDomainName\n\t\treturn execRes\n\t}\n\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrAlreadyRegistered\n\t\treturn execRes \n\t}\n\n\t// execute register domain - mint the nft\n\t// changelogs v2: we are using sealed bidding now\n\n\n\n\tcaller := req.Caller\n\tttl := defaultExpireTime\n\tmetadata := metadataWrapper(caller, req.WantedDomain, ttl)\n\t// create a new registry instance to save metadata and mint the NFT\n\terrRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl)\n\tif errRegister != nil {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrCrossRealms\n\t\treturn execRes\n\t}\n\t// now save caller to corressponding tree to manage\n\tdomainStorage.Set(req.WantedDomain, caller)\n\n\texecRes.Success = true\n\treturn execRes\n}\n\nfunc feeProcess(req RequestInfo) {\n\tif req.Mode == \"token\" {\n\t\ttokenProcess(req.WantedDomain, req.Caller)\n\t} else {\n\t\tnativeProcess()\n\t}\n}\n\nfunc AlreadyRegistered(domainName string) bool {\n\t// if can get owner -\u003e existed\n\taddr, err := rootRegistry.OwnerOf(domainName)\n\tif err == nil \u0026\u0026 addr != \"\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetOwner(domainName string) std.Address {\n\tvl, existed := domainStorage.Get(domainName)\n\tif !existed {\n\t\treturn \"\"\n\t}\n\treturn vl.(std.Address)\n}\n\nfunc Search(domainName string) (remapMetadata, string) {\n\tvalidMetadata := remapMetadata{}\n\tmd, err := getMetadata(domainName)\n\tif err != nil {\n\t\t// return validMetadata, err.Error()\n\t\tpanic(err)\n\t}\n\tvalidMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339)\n\tvalidMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339)\n\t// jsonData, _ := validMetadata.MarshalJSON()\n\treturn validMetadata, \"Search Success\"\n}\n\nfunc getMetadata(wantedDomain string) (domain.Metadata, error) {\n\t// confirm the method? -\u003e get all the fields if the fields slice is empty\n\tmetadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{})\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\treturn metadata, nil\n}\n\n// Transfer\nfunc TransferDomain(from, to, domainName string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t}\n\tif err := excuteTransfer(requestInfo); err != \"\" {\n\t\tpanic(err)\n\t}\n\treturn \"Transfer Done\"\n}\n\nfunc excuteTransfer(req RequestInfo) string {\n\tif !AlreadyRegistered(req.WantedDomain) {\n\t\treturn ErrAlreadyRegistered.Error()\n\t}\n\trootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain)\n\treturn \"\"\n}\n\nfunc GetDomainName(addr string) []string {\n\tdomainList := []string{}\n\t// search from local storage\n\tdomainStorage.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcaller := value.(std.Address)\n\t\t// not checking isExpired\n\t\tif caller.String() == addr {\n\t\t\tdomainList = append(domainList, key)\n\t\t}\n\t\treturn false\n\t})\n\treturn domainList\n}\n"},{"name":"registrar_test.gno","body":"package registrar\n\n// import (\n// \t\"fmt\"\n// \t\"std\"\n// \t\"testing\"\n// )\n\n// func TestRegisterDomain(t *testing.T) {\n// \ttcs := []struct {\n// \t\tinput string\n// \t\texpected string\n// \t}{\n// \t\t{\"thinhnx\", \"Register done\"},\n// \t}\n// \tfor tc := range tcs {\n// \t\tname := tc.input\n// \t\tt.Run(name, func(t *testing.T) {\n// \t\t\toutput := Register(tc.input)\n// \t\t\tif output != tc.expected {\n// \t\t\t\tt.Errorf(\"Expected '%q, but got %q\", tc.expected, output)\n// \t\t\t}\n// \t\t})\n// \t}\n// }\n"},{"name":"utils.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage registrar\n\nimport (\n\t\"regexp\"\n\t\"time\"\n)\n\nvar (\n\tdefaultCommitHashTime = time.Second * 60\n\tdefaultCommitPriceTime = time.Second * 60\n\tdefaultExpireTime = time.Hour // 30 days\n\treName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc GetExpirationDate(dName string) time.Time {\n\treturn rootRegistry.GetExpirationDate(dName)\n}\n\n// for now, this function only let admin set\nfunc SetExpirationDate(dName string, expDate time.Time) bool {\n\tassertIsAdmin()\n\treturn rootRegistry.SetExpirationDate(dName, expDate)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"XG2u7YTp8voetfiuQ+3LrOO1YmQzCYIbyMTx18lCdw5NnfmYJXbxUYu0PDTnut1dUbwr3N/id4cm75CBKA52FQ=="}],"memo":""},"blockNum":"1859608"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"resolver","path":"gno.land/r/varmeta/demo/v31/domain/resolver","files":[{"name":"checks_resolver.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage resolver\n\nimport (\n\t\"regexp\"\n\t\"time\"\n\n\t\"gno.land/r/varmeta/demo/v3/domain/registrar\"\n)\n\n// const (\n// \tadmin std.Address = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" // -\u003e @thinhnx\n// )\n\nvar reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc isExpired(dName string) bool {\n\texpDate := registrar.GetExpirationDate(dName)\n\treturn expDate.Before(time.Now())\n}\n"},{"name":"errors.gno","body":"package resolver\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name to register\")\n)\n"},{"name":"resolver.gno","body":"/*\nThe goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner.\nIt serves the purpose of \"resolving\" the ICNS Name\nto the correct address (e.g \"alice.gno\" -\u003e g1xxx).\n*/\n// changelogs: move Register feature into this resolver package\n// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result\n\npackage resolver\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/varmeta/demo/v3/domain/registrar\"\n)\n\ntype Record struct {\n\tOwner std.Address\n\tIsValid bool\n\tMemo string // no more need this\n\tPriority int\n}\n\n// retrieve the record list to get the onchain address\nfunc Resolve(domainName string) *Record {\n\tif !isValidDomain(domainName) {\n\t\tpanic(\"bad domain name\")\n\t}\n\trecord := \u0026Record{}\n\n\towner := getOwnerFromDomainStorage(domainName)\n\tif owner == \"\" {\n\t\trecord.Memo = \"not found\"\n\t\trecord.IsValid = false\n\t\treturn record\n\t}\n\n\tif !isExpired(domainName) {\n\t\trecord.IsValid = true\n\t\trecord.Owner = owner\n\t} else {\n\t\trecord.IsValid = false\n\t}\n\treturn record\n}\n\nfunc GetDomainName(addr string) []string {\n\treturn registrar.GetDomainName(addr)\n}\n\n/*\nIf query in local storage not found\nQuery to DomainStorage by domainName -\u003e get the registry -\u003e use that registry to get the Owner()\nand check the validation time?\n*/\n\nfunc existedInDomainStorage(domainName string) bool {\n\treturn registrar.AlreadyRegistered(domainName)\n}\n\nfunc getOwnerFromDomainStorage(domainName string) std.Address {\n\treturn registrar.GetOwner(domainName)\n}\n"},{"name":"resolver_metadata.gno","body":"package resolver\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v3/domain\"\n)\n\n// Metadata wrapper\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcrrTime := time.Now()\n\texpTime := crrTime.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", crrTime, expTime, []domain.Trait{})\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"2010QwtBs7RT8VqdYDRyo2o/jLmzPDrksKkvyraRgZkOXg0rMeaWjOOeRj7mPSLlzbLkSVD/1zHePgiHWDWSeA=="}],"memo":""},"blockNum":"1859615"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"grc721","path":"gno.land/p/varmeta/demo/v32/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, tid TokenID) error\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"FjQib+K6zbpCNBagQ33ifSWfbJZuc4vU3a599q7mauAxIrSzeMAXvcHbdog4Ga1hRRHFja6vC7OAVEUwYnFwCg=="}],"memo":""},"blockNum":"1859684"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"domain","path":"gno.land/p/varmeta/demo/v32/domain","files":[{"name":"domain_metadata.gno","body":"package domain\n\nimport (\n\t\"time\"\n)\n\n// Trait represents a key-value pair with an optional display type for metadata attributes\ntype Trait struct {\n\tDisplayType string // Optional display type (e.g., \"date\", \"number\", etc.)\n\tTraitType string // Type of the trait (e.g., \"age\", \"height\", etc.)\n\tValue string // Value of the trait\n}\n\n// Metadata represents the metadata associated with a domain\ntype Metadata struct {\n\tAvatar string // URL or identifier for an avatar image\n\tRegistrationTime time.Time // The time when the domain was registered\n\tExpirationTime time.Time // The time when the domain will be expire\n\tAttributes []Trait // Additional attributes of the domain\n\tDescription string // A description of the domain\n\tContactInfo string // Contact information for the domain owner\n\tRenewalFee string // The fee required to renew the domain, represented as a string\n}\n\n// NewMetadata creates a new Metadata instance\nfunc NewMetadata(avatar, description, contactInfo, renewalFee string,\n\tregistrationTime, expirationTime time.Time, attributes []Trait,\n) Metadata {\n\treturn Metadata{\n\t\tAvatar: avatar,\n\t\tRegistrationTime: registrationTime,\n\t\tExpirationTime: expirationTime,\n\t\tRenewalFee: renewalFee,\n\t\tAttributes: attributes,\n\t\tDescription: description,\n\t\tContactInfo: contactInfo,\n\t}\n}\n"},{"name":"domain_registry.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v32/grc/grc721\"\n)\n\n// domainRegistry represents a registry for domain names with metadata\ntype domainRegistry struct {\n\tdomains grc721.IGRC721 // Interface for basic NFT functionality\n\tmetadata *avl.Tree // AVL tree for storing domain metadata\n\texpDate time.Time\n}\n\n// DomainRegistry defines the methods for managing domain names and metadata\ntype DomainRegistry interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(domainName string) (std.Address, error)\n\tSafeTransferFrom(from, to std.Address, domainName string) error\n\tTransferFrom(from, to std.Address, domainName string) error\n\tApprove(approved std.Address, domainName string) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(domainName string) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, domainName string) error\n\n\tRegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error\n\tSetDomainData(domainName string, metadata Metadata) error\n\tGetDomainData(domainName string, field MetadataField) (Metadata, error)\n\tGetDomainFields(domainName string, fields []MetadataField) (Metadata, error)\n\tRenewDomain(domainName string, additionalDuration time.Duration) error\n\tGetExpirationDate(domainName string) time.Time\n\tSetExpirationDate(domainName string, expDate time.Time) bool\n}\n\n// NewDomainRegistry creates a new domain registry with metadata extensions\nfunc NewDomainRegistry(name, symbol string) *domainRegistry {\n\tregistry := grc721.NewBasicNFT(name, symbol)\n\n\treturn \u0026domainRegistry{\n\t\tdomains: registry,\n\t\tmetadata: avl.NewTree(),\n\t}\n}\n\n// RegisterDomain registers a new domain with the given metadata\nfunc (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error {\n\terr := d.domains.Mint(owner, grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\td.expDate = time.Now().Add(dur)\n\td.metadata.Set(domainName, metadata)\n\n\treturn nil\n}\n\n// RenewDomain extends the expiration time of a domain name\nfunc (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn ErrInvalidDomainName\n\t}\n\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\t// set new expiration date\n\td.expDate = d.expDate.Add(additionalDuration)\n\treturn nil\n}\n\n// SetDomainData sets the metadata for a given domain name\nfunc (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error {\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\td.metadata.Set(domainName, metadata)\n\treturn nil\n}\n\n// GetDomainFields retrieves multiple fields of metadata for a given domain\nfunc (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tif len(fields) == 0 {\n\t\treturn metadata, nil\n\t}\n\n\tvar result Metadata\n\tfor _, field := range fields {\n\t\tswitch field {\n\t\tcase FieldAvatar:\n\t\t\tresult.Avatar = metadata.Avatar\n\t\tcase FieldRegistrationTime:\n\t\t\tresult.RegistrationTime = metadata.RegistrationTime\n\t\tcase FieldExpirationTime:\n\t\t\tresult.ExpirationTime = metadata.ExpirationTime\n\t\tcase FieldRenewalFee:\n\t\t\tresult.RenewalFee = metadata.RenewalFee\n\t\tcase FieldAttributes:\n\t\t\tresult.Attributes = metadata.Attributes\n\t\tcase FieldDescription:\n\t\t\tresult.Description = metadata.Description\n\t\tcase FieldContactInfo:\n\t\t\tresult.ContactInfo = metadata.ContactInfo\n\t\tdefault:\n\t\t\treturn Metadata{}, ErrInvalidMetadataField\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// GetDomainData retrieves metadata for a given domain\nfunc (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tswitch field {\n\tcase FieldAvatar:\n\t\treturn Metadata{\n\t\t\tAvatar: metadata.Avatar,\n\t\t}, nil\n\tcase FieldRegistrationTime:\n\t\treturn Metadata{\n\t\t\tRegistrationTime: metadata.RegistrationTime,\n\t\t}, nil\n\tcase FieldExpirationTime:\n\t\treturn Metadata{\n\t\t\tExpirationTime: metadata.ExpirationTime,\n\t\t}, nil\n\tcase FieldRenewalFee:\n\t\treturn Metadata{\n\t\t\tRenewalFee: metadata.RenewalFee,\n\t\t}, nil\n\tcase FieldAttributes:\n\t\treturn Metadata{\n\t\t\tAttributes: metadata.Attributes,\n\t\t}, nil\n\tcase FieldDescription:\n\t\treturn Metadata{\n\t\t\tDescription: metadata.Description,\n\t\t}, nil\n\tcase FieldContactInfo:\n\t\treturn Metadata{\n\t\t\tContactInfo: metadata.ContactInfo,\n\t\t}, nil\n\tdefault:\n\t\treturn Metadata{}, ErrInvalidMetadataField\n\t}\n}\n\n// BalanceOf returns the number of domains owned by a given address\nfunc (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) {\n\treturn d.domains.BalanceOf(owner)\n}\n\n// OwnerOf returns the owner of a given domain name\nfunc (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) {\n\treturn d.domains.OwnerOf(grc721.TokenID(domainName))\n}\n\n// SafeTransferFrom safely transfers a domain from one address to another\nfunc (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// TransferFrom transfers a domain from one address to another\nfunc (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.TransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// Approve grants approval to another address to manage a specific domain\nfunc (d *domainRegistry) Approve(approved std.Address, domainName string) error {\n\treturn d.domains.Approve(approved, grc721.TokenID(domainName))\n}\n\n// SetApprovalForAll sets approval for an operator to manage all domains of the owner\nfunc (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error {\n\treturn d.domains.SetApprovalForAll(operator, approved)\n}\n\n// GetApproved returns the approved address for a specific domain\nfunc (d *domainRegistry) GetApproved(domainName string) (std.Address, error) {\n\treturn d.domains.GetApproved(grc721.TokenID(domainName))\n}\n\n// IsApprovedForAll checks if an operator is approved to manage all domains of the owner\nfunc (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool {\n\treturn d.domains.IsApprovedForAll(owner, operator)\n}\n\n// Mint creates a new domain for a given address\nfunc (d *domainRegistry) Mint(to std.Address, domainName string) error {\n\treturn d.domains.Mint(to, grc721.TokenID(domainName))\n}\n\nfunc (d *domainRegistry) GetExpirationDate(domainName string) time.Time {\n\treturn d.expDate\n}\n\nfunc (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn false\n\t}\n\td.expDate = expDate\n\treturn true\n}\n"},{"name":"domain_registry_test.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v32/grc/grc721\"\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n)\n\nvar (\n\taddr1 = testutils.TestAddress(\"bob\")\n\taddr2 = testutils.TestAddress(\"alice\")\n)\n\nfunc TestRegisterDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"contact@registered.com\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Duplicate Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\terr := registry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.owner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\t\t\terr := registry.SetDomainData(c.domainName, c.metadata)\n\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\tretrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRenewDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tadditionalTime time.Duration\n\t\texpectError bool\n\t\texpectedExpiry time.Time\n\t}{\n\t\t{\n\t\t\tname: \"Successful Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A renewable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.RenewDomain(c.domainName, c.additionalTime)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\trenewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\t// urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfield MetadataField\n\t\texpectError bool\n\t\texpectedVal string\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Avatar\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: false,\n\t\t\texpectedVal: \"avatar_url\",\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain Name\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t_, err := registry.GetDomainData(c.domainName, c.field)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainFields(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfields []MetadataField\n\t\texpectError bool\n\t\texpected Metadata\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Multiple Fields\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo},\n\t\t\texpectError: false,\n\t\t\texpected: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tretrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar)\n\t\t\t\turequire.Equal(t, c.expected.Description, retrievedMetadata.Description)\n\t\t\t\turequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTransferDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tnewOwner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A transferable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.TransferFrom(c.owner, c.newOwner, c.domainName)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.newOwner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"errors.gno","body":"package domain\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"caller is not domain owner\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name\")\n\tErrInvalidMetadataField = errors.New(\"invalid metadata field\")\n\tErrInsufficientFunds = errors.New(\"insufficient funds for renewal\")\n)\n"},{"name":"utils.gno","body":"package domain\n\ntype MetadataField int\n\nconst (\n\tFieldAvatar MetadataField = iota\n\tFieldRegistrationTime\n\tFieldRenewalFee\n\tFieldExpirationTime\n\tFieldAttributes\n\tFieldDescription\n\tFieldContactInfo\n)\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"TGCxBi43DXEf3VWUbV8w1Z/46NExbjElcmEs9uOf198tBCp5acwYNN5Rg1LFCqC6KlTT4w6+cHNXxlx1S9Vu/Q=="}],"memo":""},"blockNum":"1859690"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"registrar","path":"gno.land/r/varmeta/demo/v32/domain/registrar","files":[{"name":"bidding.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\t// \"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/avl\"\n)\n\ntype bidRecord struct {\n\tDomainName string\n\tBidder std.Address\n\tHashString string\n\tPrice int\n\tStartTime time.Time\n\tEndCommitTime time.Time\n\tEndPriceTime time.Time\n\tCurrentPhase actionCode\n\tIsOpen bool\n}\n\nvar (\n\tBidRec *avl.Tree // bidRecord \u003c- []bidRec\n\twinnerRec *avl.Tree\n\tjoinedBid avl.Tree\n)\n\n// joinedBid: address \u003c- []domainName\nfunc recordJoinedBid(domainName string) {\n\tcaller := std.GetOrigCaller()\n\tdList := []string{}\n\tdata, existed := joinedBid.Get(domainName)\n\tif !existed {\n\t\tdList = []string{domainName}\n\t\tjoinedBid.Set(caller.String(), dList)\n\t\treturn\n\t}\n\tdList = data.([]string)\n\tdList = append(dList, domainName)\n\tjoinedBid.Set(caller.String(), dList)\n\treturn\n}\n\n// get the joined bids of an account\nfunc GetJoinedBid() []string {\n\tcaller := std.GetOrigCaller().String()\n\tdata, existed := joinedBid.Get(caller)\n\tif !existed {\n\t\treturn []string{}\n\t}\n\tlist := data.([]string)\n\treturn list\n}\n\n// get the next state of bidding session\nfunc GetCurrentStatus(domainName string) string {\n\t// if there is record in joinedBid -\u003e user joined\n\t// check for tine.Now() and startTime\n\tnow := time.Now()\n\tcaller := std.GetOrigCaller()\n\t// find the record\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tif _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister {\n\t\t\treturn \"hash\"\n\t\t}\n\t\tufmt.Println(\"run here\")\n\t\treturn \"dName is invalid\"\n\t}\n\trecList := data.([]bidRecord)\n\trec := recList[0]\n\n\tif now.Before(rec.EndCommitTime) {\n\t\treturn \"waiting price\"\n\t}\n\tif now.Before(rec.EndPriceTime) \u0026\u0026 now.After(rec.EndCommitTime) {\n\t\treturn \"price\"\n\t}\n\tif now.After(rec.EndPriceTime) {\n\t\treturn \"closed\"\n\t}\n\treturn \"undefined\"\n}\n\n// Render() renders welcome message :D\nfunc Render(path string) string {\n\treturn \"welcome to varmeta domain name service\"\n}\n\n/*\n\tlogic should be:\n\t1. User click register for a dns.\n\t-\u003e 1.1: dns does not exists -\u003e open bidding session\n\t-\u003e 1.2: dns existed -\u003e check if open bidding session is openning or not -\u003e if open -\u003e new bidRecord\n\t2. Bidding session:\n\t-\u003e 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -\u003e creat bidRecord to add to bidRec\n\t-\u003e 2.2: CommitPrice phase: commit price + secret key: -\u003e if matches -\u003e write price into bidRec ( send the amunt of coin corresponding)\n\t-\u003e 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button\n\t---\u003e admin end bidding auction\n\t-\u003e 2.3.1: cooldown phase -\u003e waiting for winner -\u003e 2nd winner\n\t-\u003e 2.4: re-transfer coin from admin to user who not win the bid\n\t\tthe flow is: user want to register new domain name -\u003e start new bidding session -\u003e got the bidding session id\n\t\tif other want to register to the same domain name -\u003e bidding into this session, if not return to new state\n\t\twhen the session ends, need to have 2nd phase called commit -\u003e commit their price and secret to compute the hash\n\t\t-\u003e compare the hash as in record -\u003e accept the hash -\u003e accept the price\n\t\t*** temp approach: everytime user commit, user open a bid session if not existed, if existed -\u003e check if this session is ended.\n*/\n// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate\n// the time whenever there is action to commit.\n\n// commit the domain name and computed hash string\nfunc CommitHash(domainName, hashString string) bidStatus {\n\tbStt := bidStatus{}\n\tcaller := std.GetOrigCaller()\n\tufmt.Println(\"caller: \", caller.String())\n\tnow := time.Now()\n\n\t// update the bid record\n\tdata, existed := BidRec.Get(domainName)\n\n\t// if not existed -\u003e create new record\n\tif !existed {\n\t\tvar bidRec bidRecord\n\t\tendCommitTime := now.Add(defaultCommitHashTime)\n\t\tendPriceTime := endCommitTime.Add(defaultCommitPriceTime)\n\t\tufmt.Println(\"endCommitTime: \", endCommitTime.Format(time.RFC3339))\n\t\tufmt.Println(\"endPriceTime: \", endPriceTime.Format(time.RFC3339))\n\t\tbidRec = bidRecord{\n\t\t\tDomainName: domainName,\n\t\t\tBidder: caller,\n\t\t\tHashString: hashString,\n\t\t\tStartTime: now,\n\t\t\tEndCommitTime: endCommitTime,\n\t\t\tEndPriceTime: endPriceTime,\n\t\t\tIsOpen: true,\n\t\t}\n\t\tbidRecList := []bidRecord{bidRec}\n\t\tBidRec.Set(domainName, bidRecList)\n\n\t\t// charge fee\n\t\tchargeFee(fee.BidJoinFee, caller)\n\t\tbStt.CurrentStatus = NewBiddingSession\n\t\tbStt.ActionCode = CommitPricePhase\n\t\treturn bStt\n\t}\n\t// if existed\n\t// TODO: Check if commit more than 1 time -\u003e done\n\tbidRecList := data.([]bidRecord)\n\tstartTime := bidRecList[0].StartTime\n\tif now.After(bidRecList[0].EndCommitTime) {\n\t\tpanic(\"can not commit hash anymore\")\n\t}\n\tfor _, bR := range bidRecList {\n\t\tif bR.Bidder == caller {\n\t\t\tpanic(\"you already commited hash\")\n\t\t}\n\t}\n\n\toldEndCommitTime := bidRecList[0].EndCommitTime\n\toldEndPriceTime := bidRecList[0].EndPriceTime\n\tbidRec := bidRecord{\n\t\tDomainName: domainName,\n\t\tHashString: hashString,\n\t\tBidder: caller,\n\t\tStartTime: startTime,\n\t\tEndCommitTime: oldEndCommitTime,\n\t\tEndPriceTime: oldEndPriceTime,\n\t\tIsOpen: true,\n\t}\n\tbidRecList = append(bidRecList, bidRec)\n\t// Save record\n\tBidRec.Set(domainName, bidRecList)\n\t// charge commit hash fee\n\tchargeFee(fee.BidJoinFee, caller)\n\tbStt.CurrentStatus = AppendBiddingSession\n\tbStt.ActionCode = CommitPricePhase\n\treturn bStt\n}\n\n// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me\n// commit price and secret to reveal auction session\nfunc CommitPrice(price int, secret string, domainName string) bidStatus {\n\tvar bStt bidStatus\n\t// compute the hash string, compare to saved hash string in record\n\tnow := time.Now()\n\tjoinedString := secret + strconv.Itoa(price)\n\tcomputedHashString := Get256String(joinedString)\n\tufmt.Println(\"computed hash: \", computedHashString)\n\tcaller := std.GetOrigCaller()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\" domain name is invalid\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerRec.Set(domainName, bidRecList[0])\n\n\tufmt.Println(\"time now: \", now.Format(time.RFC3339))\n\tufmt.Println(\"end commit phase time: \", bidRecList[0].EndCommitTime.Format(time.RFC3339))\n\t// case commit after end - consider panic or not\n\tif now.After(bidRecList[0].EndPriceTime) {\n\t\tufmt.Println(\"commit price phase is ended\")\n\t\tbStt.ActionCode = 0\n\t\tbStt.CurrentStatus = 0\n\t\treturn bStt\n\t}\n\t// case commit when price phase not started\n\tif time.Now().Before(bidRecList[0].EndCommitTime) {\n\t\tufmt.Println(\"commit price phase is not started yet\")\n\t\tbStt.ActionCode = 0\n\t\tbStt.CurrentStatus = 0\n\t\treturn bStt\n\t}\n\n\t// search for the corresponding hash\n\tfor _, bidRec := range bidRecList {\n\t\t// panic because wrong price or wrong secret string\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString != computedHashString {\n\t\t\tpanic(\"invalid hash string\")\n\t\t}\n\t\t// found it, update the winner price\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString == computedHashString {\n\t\t\tdata, _ := winnerRec.Get(domainName)\n\t\t\tcurrentWinnerRec := data.(bidRecord)\n\t\t\tif price \u003e currentWinnerRec.Price \u0026\u0026 now.Before(currentWinnerRec.EndPriceTime) {\n\t\t\t\tufmt.Println(\"found new winner, setting up\")\n\t\t\t\tcurrentWinnerRec.Price = price\n\t\t\t\tcurrentWinnerRec.Bidder = bidRec.Bidder\n\t\t\t\tcurrentWinnerRec.HashString = bidRec.HashString\n\t\t\t\twinnerRec.Set(domainName, currentWinnerRec)\n\t\t\t\tbStt.ActionCode = ClaimPhase\n\t\t\t\treturn bStt\n\t\t\t}\n\t\t}\n\t}\n\t// if not match above case, then panic\n\tpanic(\"commit failed\")\n}\n\nfunc GetCurrentWinner(domainName string) bidRecord {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"no winner yet\")\n\t}\n\treturn data.(bidRecord)\n}\n\n// find the highest bid in session - incase everyone commited price\nfunc findTheWinner(domainName string) bidRecord {\n\tvar winnerBid bidRecord\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerBid = bidRecList[0]\n\tfor _, bidRec := range bidRecList {\n\t\tif bidRec.Price \u003e winnerBid.Price {\n\t\t\twinnerBid = bidRec\n\t\t}\n\t}\n\treturn winnerBid\n}\n\n// register the domain for winner\nfunc registerForWinner(domainName string, winnerRec bidRecord) bool {\n\twinnerAddr := winnerRec.Bidder\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: winnerAddr,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tfeeProcess(requestInfo)\n\treturn false\n}\n\n// everyone can call EndBid()\n// this EndBid checks endTime -\u003e end the auction\nfunc EndBid(domainName string) error {\n\tnow := time.Now()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\treturn ufmt.Errorf(\"endbid: invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\tfirstBidRec := bidRecList[0]\n\tif now.Before(firstBidRec.EndPriceTime) {\n\t\treturn ufmt.Errorf(\"endbid: this session can not end before the end time\")\n\t}\n\t// change all state\n\tfor _, bidRec := range bidRecList {\n\t\tbidRec.IsOpen = false\n\t}\n\tok := BidRec.Set(domainName, bidRecList)\n\tif !ok {\n\t\treturn ufmt.Errorf(\"endbid: can not change bid record state\")\n\t}\n\t// need more conditions for findTheWinner()\n\tfindTheWinner(domainName)\n\treturn nil\n}\n\n// register new domain with bidding process inside - if message is commit hash -\u003e dapp need to call commit hash\nfunc RegisterDomain(domainName string) bidStatus {\n\treqInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: \"native\",\n\t}\n\n\t// bidStatus\n\tresult := checkRegisterState(reqInfo)\n\trecordJoinedBid(domainName)\n\t// if checkState get the message of which phase this domain name belongs to, then return the status\n\treturn result\n}\n\nfunc checkRegisterState(req RequestInfo) bidStatus {\n\tvar bStt bidStatus\n\t// check if domain name is regex valid\n\tif !isValidDomain(req.WantedDomain) {\n\t\tpanic(\"invalid domain name\")\n\t}\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\tpanic(\"domain name already registered\")\n\t}\n\t// changelogs v2: we are using sealed bidding now\n\t// check if a bidding session is openning -\u003e append new commit hash into record list\n\t// both existed or not we open new bidding session\n\t// for now we return a signal for dapps / service to know what to do next\n\tisExisted, isOpen := checkBiddingState(req.WantedDomain)\n\tif isExisted \u0026\u0026 isOpen {\n\t\t// return commit hash signal for dapps\n\t\tbStt.CurrentStatus = AppendBiddingSession\n\t\tbStt.ActionCode = CommitHashPhase\n\t\treturn bStt\n\t}\n\n\t// create new session\n\tif !isExisted {\n\t\tbStt.CurrentStatus = NewBiddingSession\n\t\tbStt.ActionCode = CommitHashPhase\n\t\treturn bStt\n\t}\n\n\t// not found in register repository but also not found in Bidding Record -\u003e panic(\"error somewhere :D\")\n\tpanic(ufmt.Errorf(\"should not happend\"))\n\treturn bStt\n}\n\n// // open a bidding session\n// func openBiddingSession(domainName string, dur time.Duration) string {\n// \tnow := time.Now()\n// \tbidRec := bidRecord{\n// \t\tDomainName: domainName,\n// \t\tStartTime: now,\n// \t}\n// \tbidRecList := []bidRecord{bidRec}\n// \tok := BidRec.Set(domainName, bidRecList)\n// \tif !ok {\n// \t\tpanic(\"can not open bidding session\")\n// \t}\n// \treturn \"created bidding session\"\n// }\n\nfunc checkBiddingState(dName string) (isExisted bool, isOpen bool) {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tisExisted = false\n\t\tisOpen = false\n\t\treturn\n\t}\n\tisExisted = true\n\trecList := data.([]bidRecord)\n\tif recList[0].IsOpen {\n\t\tisOpen = true\n\t} else {\n\t\tisOpen = false\n\t}\n\treturn isExisted, isOpen\n}\n\n// get all the price list that joined the bid for displaying in dapp\nfunc GetRecords(dName string) []bidRecord {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tpanic(\"should not\")\n\t}\n\treturn data.([]bidRecord)\n}\n\n// chargeFee will charge amount - send from this contract to admin\nfunc chargeFee(amount int64, from std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", amount)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcoins := checkCoin(from)\n\tufmt.Println(\"check balances: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// pay fee and claim the domain name if you are winner\nfunc Claim(domainName string) bool {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"claim: invalid domain name\")\n\t}\n\tcaller := std.GetOrigCaller()\n\trec := data.(bidRecord)\n\tif caller != rec.Bidder {\n\t\tpanic(\"only winner can claim\")\n\t}\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: rec.Bidder,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tchargeFee(100, caller)\n\tfeeProcess(requestInfo)\n\treturn true\n}\n"},{"name":"bidding_model.gno","body":"package registrar\n\n/*\n\t0: StatusFailed\n\tCurrentStatus:\t1: NewBiddingSession\n\t\t\t\t\t2: AppendBiddingSession\n\t\t\t\t\t3: BiddingSessionClosed\n\tActionCode:\t\t1: CommitHashPhase\n\t\t\t\t\t2: CommitPricePhase\n\t\t\t\t\t3: ClaimPhase...\n*/\n\ntype bidStatus struct {\n\tCurrentStatus currStatus\n\tActionCode actionCode\n}\n\ntype (\n\tcurrStatus int\n\tactionCode int\n)\n\nvar (\n\tStatusFailed currStatus = 0\n\tNewBiddingSession currStatus = 1\n\tAppendBiddingSession currStatus = 2\n\tBiddingSessionClosed currStatus = 3\n\n\tActionFailed actionCode = 0\n\tCommitHashPhase actionCode = 1\n\tCommitPricePhase actionCode = 2\n\tClaimPhase actionCode = 3\n)\n"},{"name":"errors.gno","body":"package registrar\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnknown = errors.New(\"unknow errors\")\n\tErrOK = errors.New(\"ok\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"ErrInvalidDomainName\")\n\tErrAlreadyRegistered = errors.New(\"this domain is registered\")\n\tErrCrossRealms = errors.New(\"cross realms function error\")\n\tErrNotFound = errors.New(\"domain not found\")\n)\n"},{"name":"fee.gno","body":"package registrar\n\nimport (\n\t\"time\"\n)\n\n// only admin can set Fee, other just can read only\ntype feeInfo struct {\n\tRegisterBaseFee int64\n\tRenewalFee int64\n\tRegisterAdditionFee int64\n\tBidJoinFee int64\n}\n\nfunc GetRegisterFee(dName string) int64 {\n\treturn fee.RegisterBaseFee\n}\n\nfunc GetRenewalFee(dName string, amount time.Duration) int64 {\n\treturn fee.RenewalFee\n}\n\n// Admin set register fee and renewal fee\nfunc AdminSetFee(regFee int64, renewFee int64) {\n\t// consider logic\n\tassertIsAdmin()\n\tfee.RegisterBaseFee = regFee\n\tfee.RenewalFee = renewFee\n}\n\n// simple err check\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"fee_checks.gno","body":"package registrar\n\n// import (\n// \t\"\"\n// \t// \"std\"\n// \t// \"time\"\n// )\n\n\n"},{"name":"fee_native.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// admin access only\nfunc AdminWithdraw(amount int64) {\n\tassertIsAdmin()\n\tthisContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tsuperBanker.SendCoins(thisContract, admin, coinsToTransfer)\n}\n\nfunc nativeProcess() {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcaller := std.GetOrigCaller()\n\tcoins := checkCoin(caller)\n\tufmt.Println(\"check: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// RevertTransfer will revert the transaction - send amount of coin to user\nfunc revertTransfer(userAddr std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToReturn := std.NewCoins(ugnotCoin)\n\tufmt.Println(\"return coins from contract \", bankerContract.String(), \" to \", userAddr.String())\n\tbankerUser.SendCoins(bankerContract, userAddr, coinsToReturn)\n}\n\n// simple check for admin call\nfunc assertIsAdmin() {\n\t// check if GetCallerAt 2 or 3 when deployed\n\tcaller := std.GetCallerAt(3)\n\terr := ufmt.Sprintf(\"unauthorize with caller: %s\\n\", caller)\n\tif caller != admin \u0026\u0026 caller != adminVar {\n\t\tpanic(err)\n\t}\n}\n\n// checking for availble coins\nfunc checkCoin(from std.Address) std.Coins {\n\t// caller := std.GetOrigCaller()\n\treturn bankerUser.GetCoins(from)\n}\n"},{"name":"fee_token.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/varmeta/demo1/domain/vmt\"\n)\n\n// expected approved already from client -\u003e transfer from caller to admin\nfunc tokenProcess(dName string, callerStd std.Address) {\n\tcaller := pusers.AddressOrName(callerStd.String())\n\n\tnow := std.CurrentRealm().Addr()\n\tnowAddr := pusers.AddressOrName(now.String())\n\tufmt.Println(\"current realm transfer: \", now.String())\n\tcallerAllowance := vmt.Allowance(caller, nowAddr)\n\tcallerAllowanceString := ufmt.Sprintf(\"%d\", callerAllowance)\n\tufmt.Println(\"caller allowance \", callerAllowanceString)\n\n\tadminAddr := pusers.AddressOrName(admin.String())\n\tufmt.Println(\"admin: \", admin.String())\n\tvmt.TransferFrom(caller, adminAddr, 1)\n}\n"},{"name":"hashstring.gno","body":"package registrar\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n)\n\nfunc Get256String(input string) string {\n\tdata := []byte(input)\n\thashed := sha256.Sum256(data)\n\thashedBytes := hashed[:]\n\treturn hex.EncodeToString(hashedBytes)\n}\n"},{"name":"metadata_wrapper.gno","body":"package registrar\n\nimport (\n\t\"bytes\"\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/p/varmeta/demo/v32/domain\"\n)\n\n// Metadata wrapper\n// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait)\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcreatedAt := time.Now()\n\texpTime := createdAt.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", createdAt, expTime, []domain.Trait{})\n}\n\ntype remapMetadata struct {\n\tAvatar string // avatar - URL or identifier for an avatar image\n\tRegistrationTime string // regtime - The time when the domain was registered\n\tExpirationTime string // exptime - The time when the domain will be expire\n\tAttributes []domain.Trait // atts - Additional attributes of the domain\n\tDescription string // des - A description of the domain\n\tContactInfo string // contacts - Contact information for the domain owner\n\tRenewalFee string // renewalfee - The fee required to renew the domain, represented as a string\n}\n\n// currently not support for arrays\nfunc (m remapMetadata) MarshalJSON() ([]byte, error) {\n\tjson := new(bytes.Buffer)\n\tif m.Attributes == nil {\n\t\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, \"empty\", m.Description, m.ContactInfo, m.RenewalFee))\n\t\treturn json.Bytes(), nil\n\t}\n\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee))\n\treturn json.Bytes(), nil\n}\n"},{"name":"models.gno","body":"package registrar\n\nimport (\n\t\"std\"\n)\n\ntype RequestInfo struct {\n\tMode string\n\tWantedDomain string\n\tCaller std.Address\n\tTransInfo TransferInfo\n\t// xxx extendTime, renew...\n}\ntype TransferInfo struct {\n\tFrom std.Address\n\tTo std.Address\n}\ntype ExecuteResult struct {\n\tSuccess bool\n\tResultDetails error\n\tMessage string\n}\n"},{"name":"prestep.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v32/domain\"\n)\n\nvar (\n\tdomainStorage *avl.Tree // domainName -\u003e std.Address\n\trootRegistry domain.DomainRegistry\n\n\t// fee\n\tsuperBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction\n\tbankerUser std.Banker // full access to coins sent with the transaction that called the banker\n\n\tadmin std.Address // admin\n\tadminVar std.Address // admin in server\n\tfee feeInfo\n)\n\nfunc init() {\n\tdomainStorage = avl.NewTree()\n\tBidRec = avl.NewTree()\n\twinnerRec = avl.NewTree()\n\trootRegistry = domain.NewDomainRegistry(\"Varmeta\", \"vmt\")\n\n\t// fee init\n\tadmin = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" //@thinhnx\n\tadminVar = \"g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx\" //@varmeta-sponsorkey\n\t// ugnot\n\tfee = feeInfo{\n\t\tRegisterBaseFee: 100,\n\t\tRenewalFee: 100,\n\t\tRegisterAdditionFee: 0,\n\t\tBidJoinFee: 100,\n\t}\n\tsuperBanker = std.GetBanker(std.BankerTypeRealmSend)\n\tbankerUser = std.GetBanker(std.BankerTypeOrigSend)\n}\n"},{"name":"registrar.gno","body":"/*\nThis package contains functions that will actually execute the request from user\nFeatures: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion...\n*/\n// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm.\n// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session\n\n// currently we dont using too much panic because we dont have defer functions to revert the state of storage\npackage registrar\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v32/domain\"\n)\n\n// XXX: consider using panic instead of return string or errors\nfunc Register(domainName string, mode string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: mode,\n\t}\n\n\tregResult := executeRegister(requestInfo)\n\n\t// calling panic to stop paying fee\n\tif !regResult.Success {\n\t\tpanic(regResult.ResultDetails.Error())\n\t}\n\t// pay fee with panic inside\n\tfeeProcess(requestInfo)\n\treturn \"Register Done\"\n}\n\nfunc executeRegister(req RequestInfo) ExecuteResult {\n\t// check if domain name is regex valid\n\tvar execRes ExecuteResult\n\tif !isValidDomain(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrInvalidDomainName\n\t\treturn execRes\n\t}\n\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrAlreadyRegistered\n\t\treturn execRes \n\t}\n\n\t// execute register domain - mint the nft\n\t// changelogs v2: we are using sealed bidding now\n\n\n\n\tcaller := req.Caller\n\tttl := defaultExpireTime\n\tmetadata := metadataWrapper(caller, req.WantedDomain, ttl)\n\t// create a new registry instance to save metadata and mint the NFT\n\terrRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl)\n\tif errRegister != nil {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrCrossRealms\n\t\treturn execRes\n\t}\n\t// now save caller to corressponding tree to manage\n\tdomainStorage.Set(req.WantedDomain, caller)\n\n\texecRes.Success = true\n\treturn execRes\n}\n\nfunc feeProcess(req RequestInfo) {\n\tif req.Mode == \"token\" {\n\t\ttokenProcess(req.WantedDomain, req.Caller)\n\t} else {\n\t\tnativeProcess()\n\t}\n}\n\nfunc AlreadyRegistered(domainName string) bool {\n\t// if can get owner -\u003e existed\n\taddr, err := rootRegistry.OwnerOf(domainName)\n\tif err == nil \u0026\u0026 addr != \"\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetOwner(domainName string) std.Address {\n\tvl, existed := domainStorage.Get(domainName)\n\tif !existed {\n\t\treturn \"\"\n\t}\n\treturn vl.(std.Address)\n}\n\nfunc Search(domainName string) (remapMetadata, string) {\n\tvalidMetadata := remapMetadata{}\n\tmd, err := getMetadata(domainName)\n\tif err != nil {\n\t\t// return validMetadata, err.Error()\n\t\tpanic(err)\n\t}\n\tvalidMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339)\n\tvalidMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339)\n\t// jsonData, _ := validMetadata.MarshalJSON()\n\treturn validMetadata, \"Search Success\"\n}\n\nfunc getMetadata(wantedDomain string) (domain.Metadata, error) {\n\t// confirm the method? -\u003e get all the fields if the fields slice is empty\n\tmetadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{})\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\treturn metadata, nil\n}\n\n// Transfer\nfunc TransferDomain(from, to, domainName string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t}\n\tif err := excuteTransfer(requestInfo); err != \"\" {\n\t\tpanic(err)\n\t}\n\treturn \"Transfer Done\"\n}\n\nfunc excuteTransfer(req RequestInfo) string {\n\tif !AlreadyRegistered(req.WantedDomain) {\n\t\treturn ErrAlreadyRegistered.Error()\n\t}\n\trootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain)\n\treturn \"\"\n}\n\nfunc GetDomainName(addr string) []string {\n\tdomainList := []string{}\n\t// search from local storage\n\tdomainStorage.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcaller := value.(std.Address)\n\t\t// not checking isExpired\n\t\tif caller.String() == addr {\n\t\t\tdomainList = append(domainList, key)\n\t\t}\n\t\treturn false\n\t})\n\treturn domainList\n}\n"},{"name":"registrar_test.gno","body":"package registrar\n\n// import (\n// \t\"fmt\"\n// \t\"std\"\n// \t\"testing\"\n// )\n\n// func TestRegisterDomain(t *testing.T) {\n// \ttcs := []struct {\n// \t\tinput string\n// \t\texpected string\n// \t}{\n// \t\t{\"thinhnx\", \"Register done\"},\n// \t}\n// \tfor tc := range tcs {\n// \t\tname := tc.input\n// \t\tt.Run(name, func(t *testing.T) {\n// \t\t\toutput := Register(tc.input)\n// \t\t\tif output != tc.expected {\n// \t\t\t\tt.Errorf(\"Expected '%q, but got %q\", tc.expected, output)\n// \t\t\t}\n// \t\t})\n// \t}\n// }\n"},{"name":"utils.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage registrar\n\nimport (\n\t\"regexp\"\n\t\"time\"\n)\n\nvar (\n\tdefaultCommitHashTime = time.Second * 60\n\tdefaultCommitPriceTime = time.Second * 60\n\tdefaultExpireTime = time.Hour // 30 days\n\treName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc GetExpirationDate(dName string) time.Time {\n\treturn rootRegistry.GetExpirationDate(dName)\n}\n\n// for now, this function only let admin set\nfunc SetExpirationDate(dName string, expDate time.Time) bool {\n\tassertIsAdmin()\n\treturn rootRegistry.SetExpirationDate(dName, expDate)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"aYd1az8LC/GVlMzmvZgfOEwTB8tPDiPcTkZB6RB+nlEMCW4VHcATFngAG3JO4hXhQjhCNapGcsRvtlOverumQA=="}],"memo":""},"blockNum":"1859695"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"resolver","path":"gno.land/r/varmeta/demo/v32/domain/resolver","files":[{"name":"checks_resolver.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage resolver\n\nimport (\n\t\"regexp\"\n\t\"time\"\n\n\t\"gno.land/r/varmeta/demo/v32/domain/registrar\"\n)\n\n// const (\n// \tadmin std.Address = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" // -\u003e @thinhnx\n// )\n\nvar reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc isExpired(dName string) bool {\n\texpDate := registrar.GetExpirationDate(dName)\n\treturn expDate.Before(time.Now())\n}\n"},{"name":"errors.gno","body":"package resolver\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name to register\")\n)\n"},{"name":"resolver.gno","body":"/*\nThe goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner.\nIt serves the purpose of \"resolving\" the ICNS Name\nto the correct address (e.g \"alice.gno\" -\u003e g1xxx).\n*/\n// changelogs: move Register feature into this resolver package\n// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result\n\npackage resolver\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/varmeta/demo/v32/domain/registrar\"\n)\n\ntype Record struct {\n\tOwner std.Address\n\tIsValid bool\n\tMemo string // no more need this\n\tPriority int\n}\n\n// retrieve the record list to get the onchain address\nfunc Resolve(domainName string) *Record {\n\tif !isValidDomain(domainName) {\n\t\tpanic(\"bad domain name\")\n\t}\n\trecord := \u0026Record{}\n\n\towner := getOwnerFromDomainStorage(domainName)\n\tif owner == \"\" {\n\t\trecord.Memo = \"not found\"\n\t\trecord.IsValid = false\n\t\treturn record\n\t}\n\n\tif !isExpired(domainName) {\n\t\trecord.IsValid = true\n\t\trecord.Owner = owner\n\t} else {\n\t\trecord.IsValid = false\n\t}\n\treturn record\n}\n\nfunc GetDomainName(addr string) []string {\n\treturn registrar.GetDomainName(addr)\n}\n\n/*\nIf query in local storage not found\nQuery to DomainStorage by domainName -\u003e get the registry -\u003e use that registry to get the Owner()\nand check the validation time?\n*/\n\nfunc existedInDomainStorage(domainName string) bool {\n\treturn registrar.AlreadyRegistered(domainName)\n}\n\nfunc getOwnerFromDomainStorage(domainName string) std.Address {\n\treturn registrar.GetOwner(domainName)\n}\n"},{"name":"resolver_metadata.gno","body":"package resolver\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v32/domain\"\n)\n\n// Metadata wrapper\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcrrTime := time.Now()\n\texpTime := crrTime.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", crrTime, expTime, []domain.Trait{})\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"CKp13v9JCO55elkmZyRpdkDv5cpTtFZNfuiOnAK9Jt17ftj7cl/8oukBFWTKb3RMxa79m0nJGkbP5KP7UNiPfg=="}],"memo":""},"blockNum":"1859704"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"","pkg_path":"gno.land/r/varmeta/demo/v32/domain/registrar","func":"GetCurrentWinner","args":["thinhnxtest.gno"]}],"fee":{"gas_wanted":"10000000","gas_fee":"50000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"zb6X6kuOK7GjbHi+jHM8yoDpxWV3xfAYWMzFZYOjtQBoVVq5D4FbEcJvugc65w3Ja+KuIJOOBDnRONIoyZTpTw=="}],"memo":"Called through gno.studio"},"blockNum":"1859729"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"grc721","path":"gno.land/p/varmeta/demo/v33/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, tid TokenID) error\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"xxzFc/nCECTmqFy4VbNT5oQdNcPXSx9DY2PMjJx8QTIkbdOZ87HGLWr6jgOz4Hge1psqnt/c280KWTtPR+hDpw=="}],"memo":""},"blockNum":"1859855"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"domain","path":"gno.land/p/varmeta/demo/v33/domain","files":[{"name":"domain_metadata.gno","body":"package domain\n\nimport (\n\t\"time\"\n)\n\n// Trait represents a key-value pair with an optional display type for metadata attributes\ntype Trait struct {\n\tDisplayType string // Optional display type (e.g., \"date\", \"number\", etc.)\n\tTraitType string // Type of the trait (e.g., \"age\", \"height\", etc.)\n\tValue string // Value of the trait\n}\n\n// Metadata represents the metadata associated with a domain\ntype Metadata struct {\n\tAvatar string // URL or identifier for an avatar image\n\tRegistrationTime time.Time // The time when the domain was registered\n\tExpirationTime time.Time // The time when the domain will be expire\n\tAttributes []Trait // Additional attributes of the domain\n\tDescription string // A description of the domain\n\tContactInfo string // Contact information for the domain owner\n\tRenewalFee string // The fee required to renew the domain, represented as a string\n}\n\n// NewMetadata creates a new Metadata instance\nfunc NewMetadata(avatar, description, contactInfo, renewalFee string,\n\tregistrationTime, expirationTime time.Time, attributes []Trait,\n) Metadata {\n\treturn Metadata{\n\t\tAvatar: avatar,\n\t\tRegistrationTime: registrationTime,\n\t\tExpirationTime: expirationTime,\n\t\tRenewalFee: renewalFee,\n\t\tAttributes: attributes,\n\t\tDescription: description,\n\t\tContactInfo: contactInfo,\n\t}\n}\n"},{"name":"domain_registry.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v33/grc/grc721\"\n)\n\n// domainRegistry represents a registry for domain names with metadata\ntype domainRegistry struct {\n\tdomains grc721.IGRC721 // Interface for basic NFT functionality\n\tmetadata *avl.Tree // AVL tree for storing domain metadata\n\texpDate time.Time\n}\n\n// DomainRegistry defines the methods for managing domain names and metadata\ntype DomainRegistry interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(domainName string) (std.Address, error)\n\tSafeTransferFrom(from, to std.Address, domainName string) error\n\tTransferFrom(from, to std.Address, domainName string) error\n\tApprove(approved std.Address, domainName string) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(domainName string) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, domainName string) error\n\n\tRegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error\n\tSetDomainData(domainName string, metadata Metadata) error\n\tGetDomainData(domainName string, field MetadataField) (Metadata, error)\n\tGetDomainFields(domainName string, fields []MetadataField) (Metadata, error)\n\tRenewDomain(domainName string, additionalDuration time.Duration) error\n\tGetExpirationDate(domainName string) time.Time\n\tSetExpirationDate(domainName string, expDate time.Time) bool\n}\n\n// NewDomainRegistry creates a new domain registry with metadata extensions\nfunc NewDomainRegistry(name, symbol string) *domainRegistry {\n\tregistry := grc721.NewBasicNFT(name, symbol)\n\n\treturn \u0026domainRegistry{\n\t\tdomains: registry,\n\t\tmetadata: avl.NewTree(),\n\t}\n}\n\n// RegisterDomain registers a new domain with the given metadata\nfunc (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error {\n\terr := d.domains.Mint(owner, grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\td.expDate = time.Now().Add(dur)\n\td.metadata.Set(domainName, metadata)\n\n\treturn nil\n}\n\n// RenewDomain extends the expiration time of a domain name\nfunc (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn ErrInvalidDomainName\n\t}\n\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\t// set new expiration date\n\td.expDate = d.expDate.Add(additionalDuration)\n\treturn nil\n}\n\n// SetDomainData sets the metadata for a given domain name\nfunc (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error {\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\td.metadata.Set(domainName, metadata)\n\treturn nil\n}\n\n// GetDomainFields retrieves multiple fields of metadata for a given domain\nfunc (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tif len(fields) == 0 {\n\t\treturn metadata, nil\n\t}\n\n\tvar result Metadata\n\tfor _, field := range fields {\n\t\tswitch field {\n\t\tcase FieldAvatar:\n\t\t\tresult.Avatar = metadata.Avatar\n\t\tcase FieldRegistrationTime:\n\t\t\tresult.RegistrationTime = metadata.RegistrationTime\n\t\tcase FieldExpirationTime:\n\t\t\tresult.ExpirationTime = metadata.ExpirationTime\n\t\tcase FieldRenewalFee:\n\t\t\tresult.RenewalFee = metadata.RenewalFee\n\t\tcase FieldAttributes:\n\t\t\tresult.Attributes = metadata.Attributes\n\t\tcase FieldDescription:\n\t\t\tresult.Description = metadata.Description\n\t\tcase FieldContactInfo:\n\t\t\tresult.ContactInfo = metadata.ContactInfo\n\t\tdefault:\n\t\t\treturn Metadata{}, ErrInvalidMetadataField\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// GetDomainData retrieves metadata for a given domain\nfunc (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tswitch field {\n\tcase FieldAvatar:\n\t\treturn Metadata{\n\t\t\tAvatar: metadata.Avatar,\n\t\t}, nil\n\tcase FieldRegistrationTime:\n\t\treturn Metadata{\n\t\t\tRegistrationTime: metadata.RegistrationTime,\n\t\t}, nil\n\tcase FieldExpirationTime:\n\t\treturn Metadata{\n\t\t\tExpirationTime: metadata.ExpirationTime,\n\t\t}, nil\n\tcase FieldRenewalFee:\n\t\treturn Metadata{\n\t\t\tRenewalFee: metadata.RenewalFee,\n\t\t}, nil\n\tcase FieldAttributes:\n\t\treturn Metadata{\n\t\t\tAttributes: metadata.Attributes,\n\t\t}, nil\n\tcase FieldDescription:\n\t\treturn Metadata{\n\t\t\tDescription: metadata.Description,\n\t\t}, nil\n\tcase FieldContactInfo:\n\t\treturn Metadata{\n\t\t\tContactInfo: metadata.ContactInfo,\n\t\t}, nil\n\tdefault:\n\t\treturn Metadata{}, ErrInvalidMetadataField\n\t}\n}\n\n// BalanceOf returns the number of domains owned by a given address\nfunc (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) {\n\treturn d.domains.BalanceOf(owner)\n}\n\n// OwnerOf returns the owner of a given domain name\nfunc (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) {\n\treturn d.domains.OwnerOf(grc721.TokenID(domainName))\n}\n\n// SafeTransferFrom safely transfers a domain from one address to another\nfunc (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// TransferFrom transfers a domain from one address to another\nfunc (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.TransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// Approve grants approval to another address to manage a specific domain\nfunc (d *domainRegistry) Approve(approved std.Address, domainName string) error {\n\treturn d.domains.Approve(approved, grc721.TokenID(domainName))\n}\n\n// SetApprovalForAll sets approval for an operator to manage all domains of the owner\nfunc (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error {\n\treturn d.domains.SetApprovalForAll(operator, approved)\n}\n\n// GetApproved returns the approved address for a specific domain\nfunc (d *domainRegistry) GetApproved(domainName string) (std.Address, error) {\n\treturn d.domains.GetApproved(grc721.TokenID(domainName))\n}\n\n// IsApprovedForAll checks if an operator is approved to manage all domains of the owner\nfunc (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool {\n\treturn d.domains.IsApprovedForAll(owner, operator)\n}\n\n// Mint creates a new domain for a given address\nfunc (d *domainRegistry) Mint(to std.Address, domainName string) error {\n\treturn d.domains.Mint(to, grc721.TokenID(domainName))\n}\n\nfunc (d *domainRegistry) GetExpirationDate(domainName string) time.Time {\n\treturn d.expDate\n}\n\nfunc (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn false\n\t}\n\td.expDate = expDate\n\treturn true\n}\n"},{"name":"domain_registry_test.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n\t\"gno.land/p/varmeta/demo/v33/grc/grc721\"\n)\n\nvar (\n\taddr1 = testutils.TestAddress(\"bob\")\n\taddr2 = testutils.TestAddress(\"alice\")\n)\n\nfunc TestRegisterDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"contact@registered.com\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Duplicate Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\terr := registry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.owner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\t\t\terr := registry.SetDomainData(c.domainName, c.metadata)\n\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\tretrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRenewDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tadditionalTime time.Duration\n\t\texpectError bool\n\t\texpectedExpiry time.Time\n\t}{\n\t\t{\n\t\t\tname: \"Successful Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A renewable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.RenewDomain(c.domainName, c.additionalTime)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\trenewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\t// urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfield MetadataField\n\t\texpectError bool\n\t\texpectedVal string\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Avatar\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: false,\n\t\t\texpectedVal: \"avatar_url\",\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain Name\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t_, err := registry.GetDomainData(c.domainName, c.field)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainFields(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfields []MetadataField\n\t\texpectError bool\n\t\texpected Metadata\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Multiple Fields\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo},\n\t\t\texpectError: false,\n\t\t\texpected: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tretrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar)\n\t\t\t\turequire.Equal(t, c.expected.Description, retrievedMetadata.Description)\n\t\t\t\turequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTransferDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tnewOwner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A transferable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.TransferFrom(c.owner, c.newOwner, c.domainName)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.newOwner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"errors.gno","body":"package domain\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"caller is not domain owner\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name\")\n\tErrInvalidMetadataField = errors.New(\"invalid metadata field\")\n\tErrInsufficientFunds = errors.New(\"insufficient funds for renewal\")\n)\n"},{"name":"utils.gno","body":"package domain\n\ntype MetadataField int\n\nconst (\n\tFieldAvatar MetadataField = iota\n\tFieldRegistrationTime\n\tFieldRenewalFee\n\tFieldExpirationTime\n\tFieldAttributes\n\tFieldDescription\n\tFieldContactInfo\n)\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"eYJwWHQpoPuQHMOPa2+C0lriAkCF6H3k27vgql+7Sbwe1spOy/Q9fH53Zf46wYizAy3ehlbWD6j9T19v2dBcZg=="}],"memo":""},"blockNum":"1859860"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"registrar","path":"gno.land/r/varmeta/demo/v33/domain/registrar","files":[{"name":"bidding.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\t// \"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/avl\"\n)\n\ntype bidRecord struct {\n\tDomainName string\n\tBidder std.Address\n\tHashString string\n\tPrice int\n\tStartTime time.Time\n\tEndCommitTime time.Time\n\tEndPriceTime time.Time\n\tCurrentPhase actionCode\n\tIsOpen bool\n}\n\nvar (\n\tBidRec *avl.Tree // bidRecord \u003c- []bidRec\n\twinnerRec *avl.Tree\n\tjoinedBid avl.Tree\n)\n\n// joinedBid: address \u003c- []domainName\nfunc recordJoinedBid(domainName string) {\n\tcaller := std.GetOrigCaller()\n\tdList := []string{}\n\tdata, existed := joinedBid.Get(domainName)\n\tif !existed {\n\t\tdList = []string{domainName}\n\t\tjoinedBid.Set(caller.String(), dList)\n\t\treturn\n\t}\n\tdList = data.([]string)\n\tdList = append(dList, domainName)\n\tjoinedBid.Set(caller.String(), dList)\n\treturn\n}\n\n// get the joined bids of an account\nfunc GetJoinedBid() []string {\n\tcaller := std.GetOrigCaller().String()\n\tdata, existed := joinedBid.Get(caller)\n\tif !existed {\n\t\treturn []string{}\n\t}\n\tlist := data.([]string)\n\treturn list\n}\n\n// get the next state of bidding session\nfunc GetCurrentStatus(domainName string) string {\n\t// if there is record in joinedBid -\u003e user joined\n\t// check for tine.Now() and startTime\n\tnow := time.Now()\n\tcaller := std.GetOrigCaller()\n\t// find the record\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tif _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister {\n\t\t\treturn \"hash\"\n\t\t}\n\t\tufmt.Println(\"run here\")\n\t\treturn \"dName is invalid\"\n\t}\n\trecList := data.([]bidRecord)\n\trec := recList[0]\n\n\tif now.Before(rec.EndCommitTime) {\n\t\treturn \"waiting price\"\n\t}\n\tif now.Before(rec.EndPriceTime) \u0026\u0026 now.After(rec.EndCommitTime) {\n\t\treturn \"price\"\n\t}\n\tif now.After(rec.EndPriceTime) {\n\t\treturn \"closed\"\n\t}\n\treturn \"undefined\"\n}\n\n// Render() renders welcome message :D\nfunc Render(path string) string {\n\treturn \"welcome to varmeta domain name service\"\n}\n\n/*\n\tlogic should be:\n\t1. User click register for a dns.\n\t-\u003e 1.1: dns does not exists -\u003e open bidding session\n\t-\u003e 1.2: dns existed -\u003e check if open bidding session is openning or not -\u003e if open -\u003e new bidRecord\n\t2. Bidding session:\n\t-\u003e 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -\u003e creat bidRecord to add to bidRec\n\t-\u003e 2.2: CommitPrice phase: commit price + secret key: -\u003e if matches -\u003e write price into bidRec ( send the amunt of coin corresponding)\n\t-\u003e 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button\n\t---\u003e admin end bidding auction\n\t-\u003e 2.3.1: cooldown phase -\u003e waiting for winner -\u003e 2nd winner\n\t-\u003e 2.4: re-transfer coin from admin to user who not win the bid\n\t\tthe flow is: user want to register new domain name -\u003e start new bidding session -\u003e got the bidding session id\n\t\tif other want to register to the same domain name -\u003e bidding into this session, if not return to new state\n\t\twhen the session ends, need to have 2nd phase called commit -\u003e commit their price and secret to compute the hash\n\t\t-\u003e compare the hash as in record -\u003e accept the hash -\u003e accept the price\n\t\t*** temp approach: everytime user commit, user open a bid session if not existed, if existed -\u003e check if this session is ended.\n*/\n// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate\n// the time whenever there is action to commit.\n\n// commit the domain name and computed hash string\nfunc CommitHash(domainName, hashString string) bidStatus {\n\tbStt := bidStatus{}\n\tcaller := std.GetOrigCaller()\n\tufmt.Println(\"caller: \", caller.String())\n\tnow := time.Now()\n\n\t// update the bid record\n\tdata, existed := BidRec.Get(domainName)\n\n\t// if not existed -\u003e create new record\n\tif !existed {\n\t\tvar bidRec bidRecord\n\t\tendCommitTime := now.Add(defaultCommitHashTime)\n\t\tendPriceTime := endCommitTime.Add(defaultCommitPriceTime)\n\t\tufmt.Println(\"endCommitTime: \", endCommitTime.Format(time.RFC3339))\n\t\tufmt.Println(\"endPriceTime: \", endPriceTime.Format(time.RFC3339))\n\t\tbidRec = bidRecord{\n\t\t\tDomainName: domainName,\n\t\t\tBidder: caller,\n\t\t\tHashString: hashString,\n\t\t\tStartTime: now,\n\t\t\tEndCommitTime: endCommitTime,\n\t\t\tEndPriceTime: endPriceTime,\n\t\t\tIsOpen: true,\n\t\t}\n\t\tbidRecList := []bidRecord{bidRec}\n\t\tBidRec.Set(domainName, bidRecList)\n\n\t\t// charge fee\n\t\tchargeFee(fee.BidJoinFee, caller)\n\t\tbStt.CurrentStatus = NewBiddingSession\n\t\tbStt.ActionCode = CommitPricePhase\n\t\treturn bStt\n\t}\n\t// if existed\n\t// TODO: Check if commit more than 1 time -\u003e done\n\tbidRecList := data.([]bidRecord)\n\tstartTime := bidRecList[0].StartTime\n\tif now.After(bidRecList[0].EndCommitTime) {\n\t\tpanic(\"can not commit hash anymore\")\n\t}\n\tfor _, bR := range bidRecList {\n\t\tif bR.Bidder == caller {\n\t\t\tpanic(\"you already commited hash\")\n\t\t}\n\t}\n\n\toldEndCommitTime := bidRecList[0].EndCommitTime\n\toldEndPriceTime := bidRecList[0].EndPriceTime\n\tbidRec := bidRecord{\n\t\tDomainName: domainName,\n\t\tHashString: hashString,\n\t\tBidder: caller,\n\t\tStartTime: startTime,\n\t\tEndCommitTime: oldEndCommitTime,\n\t\tEndPriceTime: oldEndPriceTime,\n\t\tIsOpen: true,\n\t}\n\tbidRecList = append(bidRecList, bidRec)\n\t// Save record\n\tBidRec.Set(domainName, bidRecList)\n\t// charge commit hash fee\n\tchargeFee(fee.BidJoinFee, caller)\n\tbStt.CurrentStatus = AppendBiddingSession\n\tbStt.ActionCode = CommitPricePhase\n\treturn bStt\n}\n\n// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me\n// commit price and secret to reveal auction session\nfunc CommitPrice(price int, secret string, domainName string) bidStatus {\n\tvar bStt bidStatus\n\t// compute the hash string, compare to saved hash string in record\n\tnow := time.Now()\n\tjoinedString := secret + strconv.Itoa(price)\n\tcomputedHashString := Get256String(joinedString)\n\tufmt.Println(\"computed hash: \", computedHashString)\n\tcaller := std.GetOrigCaller()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\" domain name is invalid\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerRec.Set(domainName, bidRecList[0])\n\n\tufmt.Println(\"time now: \", now.Format(time.RFC3339))\n\tufmt.Println(\"end commit phase time: \", bidRecList[0].EndCommitTime.Format(time.RFC3339))\n\t// case commit after end - consider panic or not\n\tif now.After(bidRecList[0].EndPriceTime) {\n\t\tufmt.Println(\"commit price phase is ended\")\n\t\tbStt.ActionCode = 0\n\t\tbStt.CurrentStatus = 0\n\t\treturn bStt\n\t}\n\t// case commit when price phase not started\n\tif time.Now().Before(bidRecList[0].EndCommitTime) {\n\t\tufmt.Println(\"commit price phase is not started yet\")\n\t\tbStt.ActionCode = 0\n\t\tbStt.CurrentStatus = 0\n\t\treturn bStt\n\t}\n\n\t// search for the corresponding hash\n\tfor _, bidRec := range bidRecList {\n\t\t// panic because wrong price or wrong secret string\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString != computedHashString {\n\t\t\tpanic(\"invalid hash string\")\n\t\t}\n\t\t// found it, update the winner price\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString == computedHashString {\n\t\t\tdata, _ := winnerRec.Get(domainName)\n\t\t\tcurrentWinnerRec := data.(bidRecord)\n\t\t\tif price \u003e currentWinnerRec.Price \u0026\u0026 now.Before(currentWinnerRec.EndPriceTime) {\n\t\t\t\tufmt.Println(\"found new winner, setting up\")\n\t\t\t\tcurrentWinnerRec.Price = price\n\t\t\t\tcurrentWinnerRec.Bidder = bidRec.Bidder\n\t\t\t\tcurrentWinnerRec.HashString = bidRec.HashString\n\t\t\t\twinnerRec.Set(domainName, currentWinnerRec)\n\t\t\t\tbStt.ActionCode = ClaimPhase\n\t\t\t\treturn bStt\n\t\t\t}\n\t\t}\n\t}\n\t// if not match above case, then panic\n\tpanic(\"commit failed\")\n}\n\nfunc GetCurrentWinner(domainName string) bidRecord {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"no winner yet\")\n\t}\n\treturn data.(bidRecord)\n}\n\n// find the highest bid in session - incase everyone commited price\nfunc findTheWinner(domainName string) bidRecord {\n\tvar winnerBid bidRecord\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerBid = bidRecList[0]\n\tfor _, bidRec := range bidRecList {\n\t\tif bidRec.Price \u003e winnerBid.Price {\n\t\t\twinnerBid = bidRec\n\t\t}\n\t}\n\treturn winnerBid\n}\n\n// register the domain for winner\nfunc registerForWinner(domainName string, winnerRec bidRecord) bool {\n\twinnerAddr := winnerRec.Bidder\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: winnerAddr,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tfeeProcess(requestInfo)\n\treturn false\n}\n\n// everyone can call EndBid()\n// this EndBid checks endTime -\u003e end the auction\nfunc EndBid(domainName string) error {\n\tnow := time.Now()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\treturn ufmt.Errorf(\"endbid: invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\tfirstBidRec := bidRecList[0]\n\tif now.Before(firstBidRec.EndPriceTime) {\n\t\treturn ufmt.Errorf(\"endbid: this session can not end before the end time\")\n\t}\n\t// change all state\n\tfor _, bidRec := range bidRecList {\n\t\tbidRec.IsOpen = false\n\t}\n\tok := BidRec.Set(domainName, bidRecList)\n\tif !ok {\n\t\treturn ufmt.Errorf(\"endbid: can not change bid record state\")\n\t}\n\t// need more conditions for findTheWinner()\n\tfindTheWinner(domainName)\n\treturn nil\n}\n\n// register new domain with bidding process inside - if message is commit hash -\u003e dapp need to call commit hash\nfunc RegisterDomain(domainName string) bidStatus {\n\treqInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: \"native\",\n\t}\n\n\t// bidStatus\n\tresult := checkRegisterState(reqInfo)\n\trecordJoinedBid(domainName)\n\t// if checkState get the message of which phase this domain name belongs to, then return the status\n\treturn result\n}\n\nfunc checkRegisterState(req RequestInfo) bidStatus {\n\tvar bStt bidStatus\n\t// check if domain name is regex valid\n\tif !isValidDomain(req.WantedDomain) {\n\t\tpanic(\"invalid domain name\")\n\t}\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\tpanic(\"domain name already registered\")\n\t}\n\t// changelogs v2: we are using sealed bidding now\n\t// check if a bidding session is openning -\u003e append new commit hash into record list\n\t// both existed or not we open new bidding session\n\t// for now we return a signal for dapps / service to know what to do next\n\tisExisted, isOpen := checkBiddingState(req.WantedDomain)\n\tif isExisted \u0026\u0026 isOpen {\n\t\t// return commit hash signal for dapps\n\t\tbStt.CurrentStatus = AppendBiddingSession\n\t\tbStt.ActionCode = CommitHashPhase\n\t\treturn bStt\n\t}\n\n\t// create new session\n\tif !isExisted {\n\t\tbStt.CurrentStatus = NewBiddingSession\n\t\tbStt.ActionCode = CommitHashPhase\n\t\treturn bStt\n\t}\n\n\t// not found in register repository but also not found in Bidding Record -\u003e panic(\"error somewhere :D\")\n\tpanic(ufmt.Errorf(\"should not happend\"))\n\treturn bStt\n}\n\n// // open a bidding session\n// func openBiddingSession(domainName string, dur time.Duration) string {\n// \tnow := time.Now()\n// \tbidRec := bidRecord{\n// \t\tDomainName: domainName,\n// \t\tStartTime: now,\n// \t}\n// \tbidRecList := []bidRecord{bidRec}\n// \tok := BidRec.Set(domainName, bidRecList)\n// \tif !ok {\n// \t\tpanic(\"can not open bidding session\")\n// \t}\n// \treturn \"created bidding session\"\n// }\n\nfunc checkBiddingState(dName string) (isExisted bool, isOpen bool) {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tisExisted = false\n\t\tisOpen = false\n\t\treturn\n\t}\n\tisExisted = true\n\trecList := data.([]bidRecord)\n\tif recList[0].IsOpen {\n\t\tisOpen = true\n\t} else {\n\t\tisOpen = false\n\t}\n\treturn isExisted, isOpen\n}\n\n// get all the price list that joined the bid for displaying in dapp\nfunc GetRecords(dName string) []bidRecord {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tpanic(\"should not\")\n\t}\n\treturn data.([]bidRecord)\n}\n\n// chargeFee will charge amount - send from this contract to admin\nfunc chargeFee(amount int64, from std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", amount)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcoins := checkCoin(from)\n\tufmt.Println(\"check balances: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// pay fee and claim the domain name if you are winner\nfunc Claim(domainName string) bool {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"claim: invalid domain name\")\n\t}\n\tcaller := std.GetOrigCaller()\n\trec := data.(bidRecord)\n\tif caller != rec.Bidder {\n\t\tpanic(\"only winner can claim\")\n\t}\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: rec.Bidder,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tchargeFee(100, caller)\n\tfeeProcess(requestInfo)\n\treturn true\n}\n"},{"name":"bidding_model.gno","body":"package registrar\n\n/*\n\t0: StatusFailed\n\tCurrentStatus:\t1: NewBiddingSession\n\t\t\t\t\t2: AppendBiddingSession\n\t\t\t\t\t3: BiddingSessionClosed\n\tActionCode:\t\t1: CommitHashPhase\n\t\t\t\t\t2: CommitPricePhase\n\t\t\t\t\t3: ClaimPhase...\n*/\n\ntype bidStatus struct {\n\tCurrentStatus currStatus\n\tActionCode actionCode\n}\n\ntype (\n\tcurrStatus int\n\tactionCode int\n)\n\nvar (\n\tStatusFailed currStatus = 0\n\tNewBiddingSession currStatus = 1\n\tAppendBiddingSession currStatus = 2\n\tBiddingSessionClosed currStatus = 3\n\n\tActionFailed actionCode = 0\n\tCommitHashPhase actionCode = 1\n\tCommitPricePhase actionCode = 2\n\tClaimPhase actionCode = 3\n)\n"},{"name":"errors.gno","body":"package registrar\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnknown = errors.New(\"unknow errors\")\n\tErrOK = errors.New(\"ok\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"ErrInvalidDomainName\")\n\tErrAlreadyRegistered = errors.New(\"this domain is registered\")\n\tErrCrossRealms = errors.New(\"cross realms function error\")\n\tErrNotFound = errors.New(\"domain not found\")\n)\n"},{"name":"fee.gno","body":"package registrar\n\nimport (\n\t\"time\"\n)\n\n// only admin can set Fee, other just can read only\ntype feeInfo struct {\n\tRegisterBaseFee int64\n\tRenewalFee int64\n\tRegisterAdditionFee int64\n\tBidJoinFee int64\n}\n\nfunc GetRegisterFee(dName string) int64 {\n\treturn fee.RegisterBaseFee\n}\n\nfunc GetRenewalFee(dName string, amount time.Duration) int64 {\n\treturn fee.RenewalFee\n}\n\n// Admin set register fee and renewal fee\nfunc AdminSetFee(regFee int64, renewFee int64) {\n\t// consider logic\n\tassertIsAdmin()\n\tfee.RegisterBaseFee = regFee\n\tfee.RenewalFee = renewFee\n}\n\n// simple err check\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"fee_checks.gno","body":"package registrar\n\n// import (\n// \t\"\"\n// \t// \"std\"\n// \t// \"time\"\n// )\n\n\n"},{"name":"fee_native.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// admin access only\nfunc AdminWithdraw(amount int64) {\n\tassertIsAdmin()\n\tthisContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tsuperBanker.SendCoins(thisContract, admin, coinsToTransfer)\n}\n\nfunc nativeProcess() {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcaller := std.GetOrigCaller()\n\tcoins := checkCoin(caller)\n\tufmt.Println(\"check: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// RevertTransfer will revert the transaction - send amount of coin to user\nfunc revertTransfer(userAddr std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToReturn := std.NewCoins(ugnotCoin)\n\tufmt.Println(\"return coins from contract \", bankerContract.String(), \" to \", userAddr.String())\n\tbankerUser.SendCoins(bankerContract, userAddr, coinsToReturn)\n}\n\n// simple check for admin call\nfunc assertIsAdmin() {\n\t// check if GetCallerAt 2 or 3 when deployed\n\tcaller := std.GetCallerAt(3)\n\terr := ufmt.Sprintf(\"unauthorize with caller: %s\\n\", caller)\n\tif caller != admin \u0026\u0026 caller != adminVar {\n\t\tpanic(err)\n\t}\n}\n\n// checking for availble coins\nfunc checkCoin(from std.Address) std.Coins {\n\t// caller := std.GetOrigCaller()\n\treturn bankerUser.GetCoins(from)\n}\n"},{"name":"fee_token.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/varmeta/demo1/domain/vmt\"\n)\n\n// expected approved already from client -\u003e transfer from caller to admin\nfunc tokenProcess(dName string, callerStd std.Address) {\n\tcaller := pusers.AddressOrName(callerStd.String())\n\n\tnow := std.CurrentRealm().Addr()\n\tnowAddr := pusers.AddressOrName(now.String())\n\tufmt.Println(\"current realm transfer: \", now.String())\n\tcallerAllowance := vmt.Allowance(caller, nowAddr)\n\tcallerAllowanceString := ufmt.Sprintf(\"%d\", callerAllowance)\n\tufmt.Println(\"caller allowance \", callerAllowanceString)\n\n\tadminAddr := pusers.AddressOrName(admin.String())\n\tufmt.Println(\"admin: \", admin.String())\n\tvmt.TransferFrom(caller, adminAddr, 1)\n}\n"},{"name":"hashstring.gno","body":"package registrar\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n)\n\nfunc Get256String(input string) string {\n\tdata := []byte(input)\n\thashed := sha256.Sum256(data)\n\thashedBytes := hashed[:]\n\treturn hex.EncodeToString(hashedBytes)\n}\n"},{"name":"metadata_wrapper.gno","body":"package registrar\n\nimport (\n\t\"bytes\"\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/p/varmeta/demo/v33/domain\"\n)\n\n// Metadata wrapper\n// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait)\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcreatedAt := time.Now()\n\texpTime := createdAt.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", createdAt, expTime, []domain.Trait{})\n}\n\ntype remapMetadata struct {\n\tAvatar string // avatar - URL or identifier for an avatar image\n\tRegistrationTime string // regtime - The time when the domain was registered\n\tExpirationTime string // exptime - The time when the domain will be expire\n\tAttributes []domain.Trait // atts - Additional attributes of the domain\n\tDescription string // des - A description of the domain\n\tContactInfo string // contacts - Contact information for the domain owner\n\tRenewalFee string // renewalfee - The fee required to renew the domain, represented as a string\n}\n\n// currently not support for arrays\nfunc (m remapMetadata) MarshalJSON() ([]byte, error) {\n\tjson := new(bytes.Buffer)\n\tif m.Attributes == nil {\n\t\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, \"empty\", m.Description, m.ContactInfo, m.RenewalFee))\n\t\treturn json.Bytes(), nil\n\t}\n\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee))\n\treturn json.Bytes(), nil\n}\n"},{"name":"models.gno","body":"package registrar\n\nimport (\n\t\"std\"\n)\n\ntype RequestInfo struct {\n\tMode string\n\tWantedDomain string\n\tCaller std.Address\n\tTransInfo TransferInfo\n\t// xxx extendTime, renew...\n}\ntype TransferInfo struct {\n\tFrom std.Address\n\tTo std.Address\n}\ntype ExecuteResult struct {\n\tSuccess bool\n\tResultDetails error\n\tMessage string\n}\n"},{"name":"prestep.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v33/domain\"\n)\n\nvar (\n\tdomainStorage *avl.Tree // domainName -\u003e std.Address\n\trootRegistry domain.DomainRegistry\n\n\t// fee\n\tsuperBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction\n\tbankerUser std.Banker // full access to coins sent with the transaction that called the banker\n\n\tadmin std.Address // admin\n\tadminVar std.Address // admin in server\n\tfee feeInfo\n)\n\nfunc init() {\n\tdomainStorage = avl.NewTree()\n\tBidRec = avl.NewTree()\n\twinnerRec = avl.NewTree()\n\trootRegistry = domain.NewDomainRegistry(\"Varmeta\", \"vmt\")\n\n\t// fee init\n\tadmin = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" //@thinhnx\n\tadminVar = \"g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx\" //@varmeta-sponsorkey\n\t// ugnot\n\tfee = feeInfo{\n\t\tRegisterBaseFee: 100,\n\t\tRenewalFee: 100,\n\t\tRegisterAdditionFee: 0,\n\t\tBidJoinFee: 100,\n\t}\n\tsuperBanker = std.GetBanker(std.BankerTypeRealmSend)\n\tbankerUser = std.GetBanker(std.BankerTypeOrigSend)\n}\n"},{"name":"registrar.gno","body":"/*\nThis package contains functions that will actually execute the request from user\nFeatures: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion...\n*/\n// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm.\n// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session\n\n// currently we dont using too much panic because we dont have defer functions to revert the state of storage\npackage registrar\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v33/domain\"\n)\n\n// XXX: consider using panic instead of return string or errors\nfunc Register(domainName string, mode string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: mode,\n\t}\n\n\tregResult := executeRegister(requestInfo)\n\n\t// calling panic to stop paying fee\n\tif !regResult.Success {\n\t\tpanic(regResult.ResultDetails.Error())\n\t}\n\t// pay fee with panic inside\n\tfeeProcess(requestInfo)\n\treturn \"Register Done\"\n}\n\nfunc executeRegister(req RequestInfo) ExecuteResult {\n\t// check if domain name is regex valid\n\tvar execRes ExecuteResult\n\tif !isValidDomain(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrInvalidDomainName\n\t\treturn execRes\n\t}\n\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrAlreadyRegistered\n\t\treturn execRes\n\t}\n\n\t// execute register domain - mint the nft\n\t// changelogs v2: we are using sealed bidding now\n\n\tcaller := req.Caller\n\tttl := defaultExpireTime\n\tmetadata := metadataWrapper(caller, req.WantedDomain, ttl)\n\t// create a new registry instance to save metadata and mint the NFT\n\terrRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl)\n\tif errRegister != nil {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrCrossRealms\n\t\treturn execRes\n\t}\n\t// now save caller to corressponding tree to manage\n\tdomainStorage.Set(req.WantedDomain, caller)\n\n\texecRes.Success = true\n\treturn execRes\n}\n\nfunc feeProcess(req RequestInfo) {\n\tif req.Mode == \"token\" {\n\t\ttokenProcess(req.WantedDomain, req.Caller)\n\t} else {\n\t\tnativeProcess()\n\t}\n}\n\nfunc AlreadyRegistered(domainName string) bool {\n\t// if can get owner -\u003e existed\n\taddr, err := rootRegistry.OwnerOf(domainName)\n\tif err == nil \u0026\u0026 addr != \"\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetOwner(domainName string) std.Address {\n\tvl, existed := domainStorage.Get(domainName)\n\tif !existed {\n\t\treturn \"\"\n\t}\n\treturn vl.(std.Address)\n}\n\nfunc Search(domainName string) (remapMetadata, string) {\n\tvalidMetadata := remapMetadata{}\n\tmd, err := getMetadata(domainName)\n\tif err != nil {\n\t\t// return validMetadata, err.Error()\n\t\tpanic(err)\n\t}\n\tvalidMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339)\n\tvalidMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339)\n\t// jsonData, _ := validMetadata.MarshalJSON()\n\treturn validMetadata, \"Search Success\"\n}\n\nfunc getMetadata(wantedDomain string) (domain.Metadata, error) {\n\t// confirm the method? -\u003e get all the fields if the fields slice is empty\n\tmetadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{})\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\treturn metadata, nil\n}\n\n// Transfer\nfunc TransferDomain(from, to, domainName string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t}\n\tif err := excuteTransfer(requestInfo); err != \"\" {\n\t\tpanic(err)\n\t}\n\treturn \"Transfer Done\"\n}\n\nfunc excuteTransfer(req RequestInfo) string {\n\tif !AlreadyRegistered(req.WantedDomain) {\n\t\treturn ErrAlreadyRegistered.Error()\n\t}\n\trootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain)\n\treturn \"\"\n}\n\nfunc GetDomainName(addr string) []string {\n\tdomainList := []string{}\n\t// search from local storage\n\tdomainStorage.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcaller := value.(std.Address)\n\t\t// not checking isExpired\n\t\tif caller.String() == addr {\n\t\t\tdomainList = append(domainList, key)\n\t\t}\n\t\treturn false\n\t})\n\treturn domainList\n}\n"},{"name":"registrar_test.gno","body":"package registrar\n\n// import (\n// \t\"fmt\"\n// \t\"std\"\n// \t\"testing\"\n// )\n\n// func TestRegisterDomain(t *testing.T) {\n// \ttcs := []struct {\n// \t\tinput string\n// \t\texpected string\n// \t}{\n// \t\t{\"thinhnx\", \"Register done\"},\n// \t}\n// \tfor tc := range tcs {\n// \t\tname := tc.input\n// \t\tt.Run(name, func(t *testing.T) {\n// \t\t\toutput := Register(tc.input)\n// \t\t\tif output != tc.expected {\n// \t\t\t\tt.Errorf(\"Expected '%q, but got %q\", tc.expected, output)\n// \t\t\t}\n// \t\t})\n// \t}\n// }\n"},{"name":"utils.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage registrar\n\nimport (\n\t\"regexp\"\n\t\"time\"\n)\n\nvar (\n\tdefaultCommitHashTime = time.Second * 60\n\tdefaultCommitPriceTime = time.Second * 60\n\tdefaultExpireTime = time.Hour // 30 days\n\treName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc GetExpirationDate(dName string) time.Time {\n\treturn rootRegistry.GetExpirationDate(dName)\n}\n\n// for now, this function only let admin set\nfunc SetExpirationDate(dName string, expDate time.Time) bool {\n\tassertIsAdmin()\n\treturn rootRegistry.SetExpirationDate(dName, expDate)\n}\n\nfunc SetCommitPhaseTime(duration int) {\n\tdefaultCommitHashTime = time.Duration(duration) * time.Second\n}\n\nfunc SetCommitPriceTime(duration int) {\n\tdefaultCommitPriceTime = time.Duration(duration) * time.Second\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"Zp+hAVTJ80IfPsqBxvUKVoGeh9enhpIbTRH8JoxhWOxigEp2eikR7a06QKxFOd/gLZEgjNHK85z9uL3FIzfxng=="}],"memo":""},"blockNum":"1859931"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"resolver","path":"gno.land/r/varmeta/demo/v33/domain/resolver","files":[{"name":"checks_resolver.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage resolver\n\nimport (\n\t\"regexp\"\n\t\"time\"\n\n\t\"gno.land/r/varmeta/demo/v33/domain/registrar\"\n)\n\n// const (\n// \tadmin std.Address = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" // -\u003e @thinhnx\n// )\n\nvar reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc isExpired(dName string) bool {\n\texpDate := registrar.GetExpirationDate(dName)\n\treturn expDate.Before(time.Now())\n}\n"},{"name":"errors.gno","body":"package resolver\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name to register\")\n)\n"},{"name":"resolver.gno","body":"/*\nThe goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner.\nIt serves the purpose of \"resolving\" the ICNS Name\nto the correct address (e.g \"alice.gno\" -\u003e g1xxx).\n*/\n// changelogs: move Register feature into this resolver package\n// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result\n\npackage resolver\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/varmeta/demo/v33/domain/registrar\"\n)\n\ntype Record struct {\n\tOwner std.Address\n\tIsValid bool\n\tMemo string // no more need this\n\tPriority int\n}\n\n// retrieve the record list to get the onchain address\nfunc Resolve(domainName string) *Record {\n\tif !isValidDomain(domainName) {\n\t\tpanic(\"bad domain name\")\n\t}\n\trecord := \u0026Record{}\n\n\towner := getOwnerFromDomainStorage(domainName)\n\tif owner == \"\" {\n\t\trecord.Memo = \"not found\"\n\t\trecord.IsValid = false\n\t\treturn record\n\t}\n\n\tif !isExpired(domainName) {\n\t\trecord.IsValid = true\n\t\trecord.Owner = owner\n\t} else {\n\t\trecord.IsValid = false\n\t}\n\treturn record\n}\n\nfunc GetDomainName(addr string) []string {\n\treturn registrar.GetDomainName(addr)\n}\n\n/*\nIf query in local storage not found\nQuery to DomainStorage by domainName -\u003e get the registry -\u003e use that registry to get the Owner()\nand check the validation time?\n*/\n\nfunc existedInDomainStorage(domainName string) bool {\n\treturn registrar.AlreadyRegistered(domainName)\n}\n\nfunc getOwnerFromDomainStorage(domainName string) std.Address {\n\treturn registrar.GetOwner(domainName)\n}\n"},{"name":"resolver_metadata.gno","body":"package resolver\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v33/domain\"\n)\n\n// Metadata wrapper\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcrrTime := time.Now()\n\texpTime := crrTime.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", crrTime, expTime, []domain.Trait{})\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"vK2JtuAyOwcRVevMsd1wUa3N3NCYdKESiEK+Itd4uGlTfGWsgNgYJK+O+Ggsk22aE+kvO4gZdJcLg67d/HrLPw=="}],"memo":""},"blockNum":"1859936"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"100ugnot","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"RegisterDomain","args":["thinhnxtest.gno"]}],"fee":{"gas_wanted":"10000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"O3jVLdthp9D8HNGW3foe07Uj/zImhhquv1Va/PFVNQUtvxUJryFxHtqJgYNPOiYk37p+Fnhb1YCwjL+9QiTVRA=="}],"memo":"Called through gno.studio"},"blockNum":"1859965"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"CommitHash","args":["thinhnxtest.gno","6c091667070fa0d70b8bab92755dec7af8d9adce498d08e18c6446e0d71d6cd9"]}],"fee":{"gas_wanted":"10000000","gas_fee":"50000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"LMSbu/Fh4Lu/TxhJISK/Fr2vT/R2sgSrHfwghdnwh8YH5QgXcHYmUM/HLamAI/+ebw8uAJ85uManB6RiR2xNNg=="}],"memo":"Called through gno.studio"},"blockNum":"1859976"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"100ugnot","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"CommitHash","args":["thinhnxtest.gno","6c091667070fa0d70b8bab92755dec7af8d9adce498d08e18c6446e0d71d6cd9"]}],"fee":{"gas_wanted":"10000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"YfonKa2vOlulKfZvg6Xao3/kJtWXFb09LsLAmfFL11YmqOROo1JCcTGtXrySiAd3ox5krMgHjMuCBxcpy8IS+Q=="}],"memo":"Called through gno.studio"},"blockNum":"1859988"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"CommitPrice","args":["100","secret","thinhnxtest"]}],"fee":{"gas_wanted":"10000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"cIMGn22HCjLQn5lFjwggFIoalNCZpBU79wWSTtnARNlMcdxBlltbI9fpUglImICgFPA4dLmPouiR1IbJtCV8PA=="}],"memo":"Called through gno.studio"},"blockNum":"1860012"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"CommitPrice","args":["100","secret","thinhnxtest.gno"]}],"fee":{"gas_wanted":"10000000","gas_fee":"50000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"k9PVviKqN1nAICaaaJ9J+kyrmHn1ePx92GcdeX0Jhp1hSv2j/v724fU3Zyn2io7jA+wssrn51mMdPr+5Tt/YTA=="}],"memo":"Called through gno.studio"},"blockNum":"1860019"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"CommitPrice","args":["100","secret1","thinhnxtest.gno"]}],"fee":{"gas_wanted":"10000000","gas_fee":"50000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"D2LXJTLp3kiFmc/lTtW6IQxHuHj5S2R3Mu26CdZRbuYf0zhCbVBFkkLjdgvQ1clBzN/DM/lRc/IVf/oiDQ7DNA=="}],"memo":"Called through gno.studio"},"blockNum":"1860027"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"100ugnot","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"Claim","args":["thinhnxtest"]}],"fee":{"gas_wanted":"10000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"2cdIdo6epRpR05yuXXfRCWIEqT/B/cpNdmwFW6pKM3cbjHhR9sIETcQgdm11TrPcLke9/HSs4YiRrXC2Fdysqw=="}],"memo":"Called through gno.studio"},"blockNum":"1860042"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"Claim","args":["thinhnxtest.gno"]}],"fee":{"gas_wanted":"10000000","gas_fee":"50000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"H9kToPfwJvAGppKrJhNVhSvqVoaI2hEKZGH3yUrmtEwEgR4RZgOTGlCOiLRaZ2s70N+rwWmxKPhmHkSm4D50bA=="}],"memo":"Called through gno.studio"},"blockNum":"1860049"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"100ugnot","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"Claim","args":["thinhnxtest.gno"]}],"fee":{"gas_wanted":"10000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"icQMZiQ6RzyDTD/bvWGeomrBfCB3BydKmD/uZOyeG2dpGKsvh1PExivHf78K89Tt198KszKNyEJzMhIBGGyAzA=="}],"memo":"Called through gno.studio"},"blockNum":"1860056"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","send":"1000ugnot","pkg_path":"gno.land/r/varmeta/demo/v33/domain/registrar","func":"Claim","args":["thinhnxtest.gno"]}],"fee":{"gas_wanted":"10000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"DfSyguwhCvVbwVindcgGGzG7NDXHOMRXWemT1gnPKhEHltM6rSAsIjPgDrx1gnRf3yPVky8wEgoBtVJZEt68Ag=="}],"memo":"Called through gno.studio"},"blockNum":"1860065"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"grc721","path":"gno.land/p/varmeta/demo/v34/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, tid TokenID) error\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"J2wqTxH57AxmyGMQkuShUtYg5XGmaV8ACrObriXMuYkJ7X7o6ja6Jq0k7y30W36eRQimn711TXwltIIe9933NA=="}],"memo":""},"blockNum":"1861997"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"domain","path":"gno.land/p/varmeta/demo/v34/domain","files":[{"name":"domain_metadata.gno","body":"package domain\n\nimport (\n\t\"time\"\n)\n\n// Trait represents a key-value pair with an optional display type for metadata attributes\ntype Trait struct {\n\tDisplayType string // Optional display type (e.g., \"date\", \"number\", etc.)\n\tTraitType string // Type of the trait (e.g., \"age\", \"height\", etc.)\n\tValue string // Value of the trait\n}\n\n// Metadata represents the metadata associated with a domain\ntype Metadata struct {\n\tAvatar string // URL or identifier for an avatar image\n\tRegistrationTime time.Time // The time when the domain was registered\n\tExpirationTime time.Time // The time when the domain will be expire\n\tAttributes []Trait // Additional attributes of the domain\n\tDescription string // A description of the domain\n\tContactInfo string // Contact information for the domain owner\n\tRenewalFee string // The fee required to renew the domain, represented as a string\n}\n\n// NewMetadata creates a new Metadata instance\nfunc NewMetadata(avatar, description, contactInfo, renewalFee string,\n\tregistrationTime, expirationTime time.Time, attributes []Trait,\n) Metadata {\n\treturn Metadata{\n\t\tAvatar: avatar,\n\t\tRegistrationTime: registrationTime,\n\t\tExpirationTime: expirationTime,\n\t\tRenewalFee: renewalFee,\n\t\tAttributes: attributes,\n\t\tDescription: description,\n\t\tContactInfo: contactInfo,\n\t}\n}\n"},{"name":"domain_registry.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v34/grc/grc721\"\n)\n\n// domainRegistry represents a registry for domain names with metadata\ntype domainRegistry struct {\n\tdomains grc721.IGRC721 // Interface for basic NFT functionality\n\tmetadata *avl.Tree // AVL tree for storing domain metadata\n\texpDate time.Time\n}\n\n// DomainRegistry defines the methods for managing domain names and metadata\ntype DomainRegistry interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(domainName string) (std.Address, error)\n\tSafeTransferFrom(from, to std.Address, domainName string) error\n\tTransferFrom(from, to std.Address, domainName string) error\n\tApprove(approved std.Address, domainName string) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(domainName string) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, domainName string) error\n\n\tRegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error\n\tSetDomainData(domainName string, metadata Metadata) error\n\tGetDomainData(domainName string, field MetadataField) (Metadata, error)\n\tGetDomainFields(domainName string, fields []MetadataField) (Metadata, error)\n\tRenewDomain(domainName string, additionalDuration time.Duration) error\n\tGetExpirationDate(domainName string) time.Time\n\tSetExpirationDate(domainName string, expDate time.Time) bool\n}\n\n// NewDomainRegistry creates a new domain registry with metadata extensions\nfunc NewDomainRegistry(name, symbol string) *domainRegistry {\n\tregistry := grc721.NewBasicNFT(name, symbol)\n\n\treturn \u0026domainRegistry{\n\t\tdomains: registry,\n\t\tmetadata: avl.NewTree(),\n\t}\n}\n\n// RegisterDomain registers a new domain with the given metadata\nfunc (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error {\n\terr := d.domains.Mint(owner, grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\td.expDate = time.Now().Add(dur)\n\td.metadata.Set(domainName, metadata)\n\n\treturn nil\n}\n\n// RenewDomain extends the expiration time of a domain name\nfunc (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn ErrInvalidDomainName\n\t}\n\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\t// set new expiration date\n\td.expDate = d.expDate.Add(additionalDuration)\n\treturn nil\n}\n\n// SetDomainData sets the metadata for a given domain name\nfunc (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error {\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\td.metadata.Set(domainName, metadata)\n\treturn nil\n}\n\n// GetDomainFields retrieves multiple fields of metadata for a given domain\nfunc (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tif len(fields) == 0 {\n\t\treturn metadata, nil\n\t}\n\n\tvar result Metadata\n\tfor _, field := range fields {\n\t\tswitch field {\n\t\tcase FieldAvatar:\n\t\t\tresult.Avatar = metadata.Avatar\n\t\tcase FieldRegistrationTime:\n\t\t\tresult.RegistrationTime = metadata.RegistrationTime\n\t\tcase FieldExpirationTime:\n\t\t\tresult.ExpirationTime = metadata.ExpirationTime\n\t\tcase FieldRenewalFee:\n\t\t\tresult.RenewalFee = metadata.RenewalFee\n\t\tcase FieldAttributes:\n\t\t\tresult.Attributes = metadata.Attributes\n\t\tcase FieldDescription:\n\t\t\tresult.Description = metadata.Description\n\t\tcase FieldContactInfo:\n\t\t\tresult.ContactInfo = metadata.ContactInfo\n\t\tdefault:\n\t\t\treturn Metadata{}, ErrInvalidMetadataField\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// GetDomainData retrieves metadata for a given domain\nfunc (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tswitch field {\n\tcase FieldAvatar:\n\t\treturn Metadata{\n\t\t\tAvatar: metadata.Avatar,\n\t\t}, nil\n\tcase FieldRegistrationTime:\n\t\treturn Metadata{\n\t\t\tRegistrationTime: metadata.RegistrationTime,\n\t\t}, nil\n\tcase FieldExpirationTime:\n\t\treturn Metadata{\n\t\t\tExpirationTime: metadata.ExpirationTime,\n\t\t}, nil\n\tcase FieldRenewalFee:\n\t\treturn Metadata{\n\t\t\tRenewalFee: metadata.RenewalFee,\n\t\t}, nil\n\tcase FieldAttributes:\n\t\treturn Metadata{\n\t\t\tAttributes: metadata.Attributes,\n\t\t}, nil\n\tcase FieldDescription:\n\t\treturn Metadata{\n\t\t\tDescription: metadata.Description,\n\t\t}, nil\n\tcase FieldContactInfo:\n\t\treturn Metadata{\n\t\t\tContactInfo: metadata.ContactInfo,\n\t\t}, nil\n\tdefault:\n\t\treturn Metadata{}, ErrInvalidMetadataField\n\t}\n}\n\n// BalanceOf returns the number of domains owned by a given address\nfunc (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) {\n\treturn d.domains.BalanceOf(owner)\n}\n\n// OwnerOf returns the owner of a given domain name\nfunc (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) {\n\treturn d.domains.OwnerOf(grc721.TokenID(domainName))\n}\n\n// SafeTransferFrom safely transfers a domain from one address to another\nfunc (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// TransferFrom transfers a domain from one address to another\nfunc (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.TransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// Approve grants approval to another address to manage a specific domain\nfunc (d *domainRegistry) Approve(approved std.Address, domainName string) error {\n\treturn d.domains.Approve(approved, grc721.TokenID(domainName))\n}\n\n// SetApprovalForAll sets approval for an operator to manage all domains of the owner\nfunc (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error {\n\treturn d.domains.SetApprovalForAll(operator, approved)\n}\n\n// GetApproved returns the approved address for a specific domain\nfunc (d *domainRegistry) GetApproved(domainName string) (std.Address, error) {\n\treturn d.domains.GetApproved(grc721.TokenID(domainName))\n}\n\n// IsApprovedForAll checks if an operator is approved to manage all domains of the owner\nfunc (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool {\n\treturn d.domains.IsApprovedForAll(owner, operator)\n}\n\n// Mint creates a new domain for a given address\nfunc (d *domainRegistry) Mint(to std.Address, domainName string) error {\n\treturn d.domains.Mint(to, grc721.TokenID(domainName))\n}\n\nfunc (d *domainRegistry) GetExpirationDate(domainName string) time.Time {\n\treturn d.expDate\n}\n\nfunc (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn false\n\t}\n\td.expDate = expDate\n\treturn true\n}\n"},{"name":"domain_registry_test.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n\t\"gno.land/p/varmeta/demo/v34/grc/grc721\"\n)\n\nvar (\n\taddr1 = testutils.TestAddress(\"bob\")\n\taddr2 = testutils.TestAddress(\"alice\")\n)\n\nfunc TestRegisterDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"contact@registered.com\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Duplicate Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\terr := registry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.owner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\t\t\terr := registry.SetDomainData(c.domainName, c.metadata)\n\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\tretrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRenewDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tadditionalTime time.Duration\n\t\texpectError bool\n\t\texpectedExpiry time.Time\n\t}{\n\t\t{\n\t\t\tname: \"Successful Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A renewable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.RenewDomain(c.domainName, c.additionalTime)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\trenewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\t// urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfield MetadataField\n\t\texpectError bool\n\t\texpectedVal string\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Avatar\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: false,\n\t\t\texpectedVal: \"avatar_url\",\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain Name\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t_, err := registry.GetDomainData(c.domainName, c.field)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainFields(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfields []MetadataField\n\t\texpectError bool\n\t\texpected Metadata\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Multiple Fields\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo},\n\t\t\texpectError: false,\n\t\t\texpected: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tretrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar)\n\t\t\t\turequire.Equal(t, c.expected.Description, retrievedMetadata.Description)\n\t\t\t\turequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTransferDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tnewOwner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A transferable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.TransferFrom(c.owner, c.newOwner, c.domainName)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.newOwner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"errors.gno","body":"package domain\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"caller is not domain owner\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name\")\n\tErrInvalidMetadataField = errors.New(\"invalid metadata field\")\n\tErrInsufficientFunds = errors.New(\"insufficient funds for renewal\")\n)\n"},{"name":"utils.gno","body":"package domain\n\ntype MetadataField int\n\nconst (\n\tFieldAvatar MetadataField = iota\n\tFieldRegistrationTime\n\tFieldRenewalFee\n\tFieldExpirationTime\n\tFieldAttributes\n\tFieldDescription\n\tFieldContactInfo\n)\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"ZMUr71bEjjEudc7fIhhCAr7dskd+pBJsaTPrZr42vw52+MRwug3+78J5rUreKJc1+rF+IFy9CYwe4eGl432FCA=="}],"memo":""},"blockNum":"1862004"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"registrar","path":"gno.land/r/varmeta/demo/v34/domain/registrar","files":[{"name":"bidding.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\t// \"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/avl\"\n)\n\ntype bidRecord struct {\n\tDomainName string\n\tBidder std.Address\n\tHashString string\n\tPrice int\n\tStartTime time.Time\n\tEndCommitTime time.Time\n\tEndPriceTime time.Time\n\tCurrentPhase actionCode\n\tIsOpen bool\n}\n\nvar (\n\tBidRec *avl.Tree // bidRecord \u003c- []bidRec\n\twinnerRec *avl.Tree\n\tjoinedBid avl.Tree\n)\n\n// joinedBid: address \u003c- []domainName\nfunc recordJoinedBid(domainName string) {\n\tcaller := std.GetOrigCaller()\n\tdList := []string{}\n\tdata, existed := joinedBid.Get(domainName)\n\tif !existed {\n\t\tdList = []string{domainName}\n\t\tjoinedBid.Set(caller.String(), dList)\n\t\treturn\n\t}\n\tdList = data.([]string)\n\tdList = append(dList, domainName)\n\tjoinedBid.Set(caller.String(), dList)\n\treturn\n}\n\n// get the joined bids of an account\n// want to return both name and status\nfunc GetJoinedBid() []singleStatus {\n\tcaller := std.GetOrigCaller().String()\n\tdata, existed := joinedBid.Get(caller)\n\tif !existed {\n\t\treturn []singleStatus{}\n\t}\n\tlist := data.([]string)\n\tlistStatus := []singleStatus{}\n\tfor _, dName := range list {\n\t\tstt := GetCurrentStatus(dName)\n\t\tsingleStt := singleStatus{\n\t\t\tDomainName: dName,\n\t\t\tStatus: stt,\n\t\t}\n\t\tlistStatus = append(listStatus, singleStt)\n\t}\n\treturn listStatus\n}\n\n// get the next state of bidding session\n// XXX note commit price status + if time is expired and not commited yet\n// status clear\nfunc GetCurrentStatus(domainName string) string {\n\t// if there is record in joinedBid -\u003e user joined\n\t// check for tine.Now() and startTime\n\tnow := time.Now()\n\tcaller := std.GetOrigCaller()\n\t// find the record\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\t// no record in bidRec yet -\u003e not commited -\u003e check if user started auction or not - if yes: new auction\n\t\tif _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister {\n\t\t\treturn \"new auction\"\n\t\t}\n\t\treturn \"invalid\"\n\t}\n\n\t// commited yet\n\trecList := data.([]bidRecord)\n\trec := recList[0]\n\n\tif now.Before(rec.EndCommitTime) {\n\t\tif rec.HashString != \"\" {\n\t\t\treturn \"commited hash\"\n\t\t} else {\n\t\t\treturn \"hash\"\n\t\t}\n\t}\n\n\tif now.Before(rec.EndPriceTime) \u0026\u0026 now.After(rec.EndCommitTime) {\n\t\treturn \"price\"\n\t}\n\tif now.After(rec.EndPriceTime) {\n\t\treturn \"closed\"\n\t}\n\treturn \"undefined\"\n}\n\n// Render() renders welcome message :D\nfunc Render(path string) string {\n\treturn \"welcome to varmeta domain name service\"\n}\n\n/*\n\tlogic should be:\n\t1. User click register for a dns.\n\t-\u003e 1.1: dns does not exists -\u003e open bidding session\n\t-\u003e 1.2: dns existed -\u003e check if open bidding session is openning or not -\u003e if open -\u003e new bidRecord\n\t2. Bidding session:\n\t-\u003e 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -\u003e creat bidRecord to add to bidRec\n\t-\u003e 2.2: CommitPrice phase: commit price + secret key: -\u003e if matches -\u003e write price into bidRec ( send the amunt of coin corresponding)\n\t-\u003e 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button\n\t---\u003e admin end bidding auction\n\t-\u003e 2.3.1: cooldown phase -\u003e waiting for winner -\u003e 2nd winner\n\t-\u003e 2.4: re-transfer coin from admin to user who not win the bid\n\t\tthe flow is: user want to register new domain name -\u003e start new bidding session -\u003e got the bidding session id\n\t\tif other want to register to the same domain name -\u003e bidding into this session, if not return to new state\n\t\twhen the session ends, need to have 2nd phase called commit -\u003e commit their price and secret to compute the hash\n\t\t-\u003e compare the hash as in record -\u003e accept the hash -\u003e accept the price\n\t\t*** temp approach: everytime user commit, user open a bid session if not existed, if existed -\u003e check if this session is ended.\n*/\n// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate\n// the time whenever there is action to commit.\n\n// commit the domain name and computed hash string\nfunc CommitHash(domainName, hashString string) bidStatus {\n\tbStt := bidStatus{}\n\tcaller := std.GetOrigCaller()\n\tufmt.Println(\"caller: \", caller.String())\n\tnow := time.Now()\n\n\t// update the bid record\n\tdata, existed := BidRec.Get(domainName)\n\n\t// if not existed -\u003e create new record\n\tif !existed {\n\t\tvar bidRec bidRecord\n\t\tendCommitTime := now.Add(defaultCommitHashTime)\n\t\tendPriceTime := endCommitTime.Add(defaultCommitPriceTime)\n\t\tufmt.Println(\"endCommitTime: \", endCommitTime.Format(time.RFC3339))\n\t\tufmt.Println(\"endPriceTime: \", endPriceTime.Format(time.RFC3339))\n\t\tbidRec = bidRecord{\n\t\t\tDomainName: domainName,\n\t\t\tBidder: caller,\n\t\t\tHashString: hashString,\n\t\t\tStartTime: now,\n\t\t\tEndCommitTime: endCommitTime,\n\t\t\tEndPriceTime: endPriceTime,\n\t\t\tIsOpen: true,\n\t\t}\n\t\tbidRecList := []bidRecord{bidRec}\n\t\tBidRec.Set(domainName, bidRecList)\n\n\t\t// charge fee\n\t\tchargeFee(fee.BidJoinFee, caller)\n\t\tbStt.CurrentStatus = NewBiddingSession\n\t\tbStt.ActionCode = CommitPricePhase\n\t\treturn bStt\n\t}\n\t// if existed\n\t// TODO: Check if commit more than 1 time -\u003e done\n\tbidRecList := data.([]bidRecord)\n\tstartTime := bidRecList[0].StartTime\n\tif now.After(bidRecList[0].EndCommitTime) {\n\t\tpanic(\"can not commit hash anymore\")\n\t}\n\tfor _, bR := range bidRecList {\n\t\tif bR.Bidder == caller {\n\t\t\tpanic(\"you already commited hash\")\n\t\t}\n\t}\n\n\toldEndCommitTime := bidRecList[0].EndCommitTime\n\toldEndPriceTime := bidRecList[0].EndPriceTime\n\tbidRec := bidRecord{\n\t\tDomainName: domainName,\n\t\tHashString: hashString,\n\t\tBidder: caller,\n\t\tStartTime: startTime,\n\t\tEndCommitTime: oldEndCommitTime,\n\t\tEndPriceTime: oldEndPriceTime,\n\t\tIsOpen: true,\n\t}\n\tbidRecList = append(bidRecList, bidRec)\n\t// Save record\n\tBidRec.Set(domainName, bidRecList)\n\t// charge commit hash fee\n\tchargeFee(fee.BidJoinFee, caller)\n\tbStt.CurrentStatus = AppendBiddingSession\n\tbStt.ActionCode = CommitPricePhase\n\treturn bStt\n}\n\n// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me\n// commit price and secret to reveal auction session\nfunc CommitPrice(price int, secret string, domainName string) bidStatus {\n\tvar bStt bidStatus\n\t// compute the hash string, compare to saved hash string in record\n\tnow := time.Now()\n\tjoinedString := secret + strconv.Itoa(price)\n\tcomputedHashString := Get256String(joinedString)\n\tufmt.Println(\"computed hash: \", computedHashString)\n\tcaller := std.GetOrigCaller()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\" domain name is invalid\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerRec.Set(domainName, bidRecList[0])\n\n\tufmt.Println(\"time now: \", now.Format(time.RFC3339))\n\tufmt.Println(\"end commit phase time: \", bidRecList[0].EndCommitTime.Format(time.RFC3339))\n\t// case commit after end - consider panic or not\n\tif now.After(bidRecList[0].EndPriceTime) {\n\t\tufmt.Println(\"commit price phase is ended\")\n\t\tbStt.ActionCode = 0\n\t\tbStt.CurrentStatus = 0\n\t\treturn bStt\n\t}\n\t// case commit when price phase not started\n\tif time.Now().Before(bidRecList[0].EndCommitTime) {\n\t\tufmt.Println(\"commit price phase is not started yet\")\n\t\tbStt.ActionCode = 0\n\t\tbStt.CurrentStatus = 0\n\t\treturn bStt\n\t}\n\n\t// search for the corresponding hash\n\tfor _, bidRec := range bidRecList {\n\t\t// panic because wrong price or wrong secret string\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString != computedHashString {\n\t\t\tpanic(\"invalid hash string\")\n\t\t}\n\t\t// found it, update the winner price\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString == computedHashString {\n\t\t\tdata, _ := winnerRec.Get(domainName)\n\t\t\tcurrentWinnerRec := data.(bidRecord)\n\t\t\tif price \u003e currentWinnerRec.Price \u0026\u0026 now.Before(currentWinnerRec.EndPriceTime) {\n\t\t\t\tufmt.Println(\"found new winner, setting up\")\n\t\t\t\tcurrentWinnerRec.Price = price\n\t\t\t\tcurrentWinnerRec.Bidder = bidRec.Bidder\n\t\t\t\tcurrentWinnerRec.HashString = bidRec.HashString\n\t\t\t\twinnerRec.Set(domainName, currentWinnerRec)\n\t\t\t\tbStt.ActionCode = ClaimPhase\n\t\t\t\treturn bStt\n\t\t\t}\n\t\t}\n\t}\n\t// if not match above case, then panic\n\tpanic(\"commit failed\")\n}\n\nfunc GetCurrentWinner(domainName string) bidRecord {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"no winner yet\")\n\t}\n\treturn data.(bidRecord)\n}\n\n// find the highest bid in session - incase everyone commited price\nfunc findTheWinner(domainName string) bidRecord {\n\tvar winnerBid bidRecord\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerBid = bidRecList[0]\n\tfor _, bidRec := range bidRecList {\n\t\tif bidRec.Price \u003e winnerBid.Price {\n\t\t\twinnerBid = bidRec\n\t\t}\n\t}\n\treturn winnerBid\n}\n\n// register the domain for winner\nfunc registerForWinner(domainName string, winnerRec bidRecord) bool {\n\twinnerAddr := winnerRec.Bidder\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: winnerAddr,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tfeeProcess(requestInfo)\n\treturn false\n}\n\n// everyone can call EndBid()\n// this EndBid checks endTime -\u003e end the auction\nfunc EndBid(domainName string) error {\n\tnow := time.Now()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\treturn ufmt.Errorf(\"endbid: invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\tfirstBidRec := bidRecList[0]\n\tif now.Before(firstBidRec.EndPriceTime) {\n\t\treturn ufmt.Errorf(\"endbid: this session can not end before the end time\")\n\t}\n\t// change all state\n\tfor _, bidRec := range bidRecList {\n\t\tbidRec.IsOpen = false\n\t}\n\tok := BidRec.Set(domainName, bidRecList)\n\tif !ok {\n\t\treturn ufmt.Errorf(\"endbid: can not change bid record state\")\n\t}\n\t// need more conditions for findTheWinner()\n\tfindTheWinner(domainName)\n\treturn nil\n}\n\n// register new domain with bidding process inside - if message is commit hash -\u003e dapp need to call commit hash\nfunc RegisterDomain(domainName string) bidStatus {\n\treqInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: \"native\",\n\t}\n\n\t// bidStatus\n\tresult := checkRegisterState(reqInfo)\n\trecordJoinedBid(domainName)\n\t// if checkState get the message of which phase this domain name belongs to, then return the status\n\treturn result\n}\n\nfunc checkRegisterState(req RequestInfo) bidStatus {\n\tvar bStt bidStatus\n\t// check if domain name is regex valid\n\tif !isValidDomain(req.WantedDomain) {\n\t\tpanic(\"invalid domain name\")\n\t}\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\tpanic(\"domain name already registered\")\n\t}\n\t// changelogs v2: we are using sealed bidding now\n\t// check if a bidding session is openning -\u003e append new commit hash into record list\n\t// both existed or not we open new bidding session\n\t// for now we return a signal for dapps / service to know what to do next\n\tisExisted, isOpen := checkBiddingState(req.WantedDomain)\n\tif isExisted \u0026\u0026 isOpen {\n\t\t// return commit hash signal for dapps\n\t\tbStt.CurrentStatus = AppendBiddingSession\n\t\tbStt.ActionCode = CommitHashPhase\n\t\treturn bStt\n\t}\n\n\t// create new session\n\tif !isExisted {\n\t\tbStt.CurrentStatus = NewBiddingSession\n\t\tbStt.ActionCode = CommitHashPhase\n\t\treturn bStt\n\t}\n\n\t// not found in register repository but also not found in Bidding Record -\u003e panic(\"error somewhere :D\")\n\tpanic(ufmt.Errorf(\"should not happend\"))\n\treturn bStt\n}\n\n// // open a bidding session\n// func openBiddingSession(domainName string, dur time.Duration) string {\n// \tnow := time.Now()\n// \tbidRec := bidRecord{\n// \t\tDomainName: domainName,\n// \t\tStartTime: now,\n// \t}\n// \tbidRecList := []bidRecord{bidRec}\n// \tok := BidRec.Set(domainName, bidRecList)\n// \tif !ok {\n// \t\tpanic(\"can not open bidding session\")\n// \t}\n// \treturn \"created bidding session\"\n// }\n\nfunc checkBiddingState(dName string) (isExisted bool, isOpen bool) {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tisExisted = false\n\t\tisOpen = false\n\t\treturn\n\t}\n\tisExisted = true\n\trecList := data.([]bidRecord)\n\tif recList[0].IsOpen {\n\t\tisOpen = true\n\t} else {\n\t\tisOpen = false\n\t}\n\treturn isExisted, isOpen\n}\n\n// get all the price list that joined the bid for displaying in dapp\nfunc GetRecords(dName string) []bidRecord {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tpanic(\"should not\")\n\t}\n\treturn data.([]bidRecord)\n}\n\n// chargeFee will charge amount - send from this contract to admin\nfunc chargeFee(amount int64, from std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", amount)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcoins := checkCoin(from)\n\tufmt.Println(\"check balances: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// pay fee and claim the domain name if you are winner\nfunc Claim(domainName string) bool {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"claim: invalid domain name\")\n\t}\n\tcaller := std.GetOrigCaller()\n\trec := data.(bidRecord)\n\tif caller != rec.Bidder {\n\t\tpanic(\"only winner can claim\")\n\t}\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: rec.Bidder,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tchargeFee(100, caller)\n\tfeeProcess(requestInfo)\n\treturn true\n}\n"},{"name":"bidding_model.gno","body":"package registrar\n\n/*\n\t0: StatusFailed\n\tCurrentStatus:\t1: NewBiddingSession\n\t\t\t\t\t2: AppendBiddingSession\n\t\t\t\t\t3: BiddingSessionClosed\n\tActionCode:\t\t1: CommitHashPhase\n\t\t\t\t\t2: CommitPricePhase\n\t\t\t\t\t3: ClaimPhase...\n*/\n\ntype bidStatus struct {\n\tCurrentStatus currStatus\n\tActionCode actionCode\n}\n\ntype (\n\tcurrStatus int\n\tactionCode int\n)\n\nvar (\n\tStatusFailed currStatus = 0\n\tNewBiddingSession currStatus = 1\n\tAppendBiddingSession currStatus = 2\n\tBiddingSessionClosed currStatus = 3\n\n\tActionFailed actionCode = 0\n\tCommitHashPhase actionCode = 1\n\tCommitPricePhase actionCode = 2\n\tClaimPhase actionCode = 3\n)\n\ntype singleStatus struct {\n\tDomainName string\n\tStatus string\n}\n"},{"name":"errors.gno","body":"package registrar\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnknown = errors.New(\"unknow errors\")\n\tErrOK = errors.New(\"ok\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"ErrInvalidDomainName\")\n\tErrAlreadyRegistered = errors.New(\"this domain is registered\")\n\tErrCrossRealms = errors.New(\"cross realms function error\")\n\tErrNotFound = errors.New(\"domain not found\")\n)\n"},{"name":"fee.gno","body":"package registrar\n\nimport (\n\t\"time\"\n)\n\n// only admin can set Fee, other just can read only\ntype feeInfo struct {\n\tRegisterBaseFee int64\n\tRenewalFee int64\n\tRegisterAdditionFee int64\n\tBidJoinFee int64\n}\n\nfunc GetRegisterFee(dName string) int64 {\n\treturn fee.RegisterBaseFee\n}\n\nfunc GetRenewalFee(dName string, amount time.Duration) int64 {\n\treturn fee.RenewalFee\n}\n\n// Admin set register fee and renewal fee\nfunc AdminSetFee(regFee int64, renewFee int64) {\n\t// consider logic\n\tassertIsAdmin()\n\tfee.RegisterBaseFee = regFee\n\tfee.RenewalFee = renewFee\n}\n\n// simple err check\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"fee_checks.gno","body":"package registrar\n\n// import (\n// \t\"\"\n// \t// \"std\"\n// \t// \"time\"\n// )\n\n\n"},{"name":"fee_native.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// admin access only\nfunc AdminWithdraw(amount int64) {\n\tassertIsAdmin()\n\tthisContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tsuperBanker.SendCoins(thisContract, admin, coinsToTransfer)\n}\n\nfunc nativeProcess() {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcaller := std.GetOrigCaller()\n\tcoins := checkCoin(caller)\n\tufmt.Println(\"check: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// RevertTransfer will revert the transaction - send amount of coin to user\nfunc revertTransfer(userAddr std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToReturn := std.NewCoins(ugnotCoin)\n\tufmt.Println(\"return coins from contract \", bankerContract.String(), \" to \", userAddr.String())\n\tbankerUser.SendCoins(bankerContract, userAddr, coinsToReturn)\n}\n\n// simple check for admin call\nfunc assertIsAdmin() {\n\t// check if GetCallerAt 2 or 3 when deployed\n\tcaller := std.GetCallerAt(3)\n\terr := ufmt.Sprintf(\"unauthorize with caller: %s\\n\", caller)\n\tif caller != admin \u0026\u0026 caller != adminVar {\n\t\tpanic(err)\n\t}\n}\n\n// checking for availble coins\nfunc checkCoin(from std.Address) std.Coins {\n\t// caller := std.GetOrigCaller()\n\treturn bankerUser.GetCoins(from)\n}\n"},{"name":"fee_token.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/varmeta/demo1/domain/vmt\"\n)\n\n// expected approved already from client -\u003e transfer from caller to admin\nfunc tokenProcess(dName string, callerStd std.Address) {\n\tcaller := pusers.AddressOrName(callerStd.String())\n\n\tnow := std.CurrentRealm().Addr()\n\tnowAddr := pusers.AddressOrName(now.String())\n\tufmt.Println(\"current realm transfer: \", now.String())\n\tcallerAllowance := vmt.Allowance(caller, nowAddr)\n\tcallerAllowanceString := ufmt.Sprintf(\"%d\", callerAllowance)\n\tufmt.Println(\"caller allowance \", callerAllowanceString)\n\n\tadminAddr := pusers.AddressOrName(admin.String())\n\tufmt.Println(\"admin: \", admin.String())\n\tvmt.TransferFrom(caller, adminAddr, 1)\n}\n"},{"name":"hashstring.gno","body":"package registrar\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n)\n\nfunc Get256String(input string) string {\n\tdata := []byte(input)\n\thashed := sha256.Sum256(data)\n\thashedBytes := hashed[:]\n\treturn hex.EncodeToString(hashedBytes)\n}\n"},{"name":"metadata_wrapper.gno","body":"package registrar\n\nimport (\n\t\"bytes\"\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/p/varmeta/demo/v34/domain\"\n)\n\n// Metadata wrapper\n// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait)\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcreatedAt := time.Now()\n\texpTime := createdAt.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", createdAt, expTime, []domain.Trait{})\n}\n\ntype remapMetadata struct {\n\tAvatar string // avatar - URL or identifier for an avatar image\n\tRegistrationTime string // regtime - The time when the domain was registered\n\tExpirationTime string // exptime - The time when the domain will be expire\n\tAttributes []domain.Trait // atts - Additional attributes of the domain\n\tDescription string // des - A description of the domain\n\tContactInfo string // contacts - Contact information for the domain owner\n\tRenewalFee string // renewalfee - The fee required to renew the domain, represented as a string\n}\n\n// currently not support for arrays\nfunc (m remapMetadata) MarshalJSON() ([]byte, error) {\n\tjson := new(bytes.Buffer)\n\tif m.Attributes == nil {\n\t\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, \"empty\", m.Description, m.ContactInfo, m.RenewalFee))\n\t\treturn json.Bytes(), nil\n\t}\n\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee))\n\treturn json.Bytes(), nil\n}\n"},{"name":"models.gno","body":"package registrar\n\nimport (\n\t\"std\"\n)\n\ntype RequestInfo struct {\n\tMode string\n\tWantedDomain string\n\tCaller std.Address\n\tTransInfo TransferInfo\n\t// xxx extendTime, renew...\n}\ntype TransferInfo struct {\n\tFrom std.Address\n\tTo std.Address\n}\ntype ExecuteResult struct {\n\tSuccess bool\n\tResultDetails error\n\tMessage string\n}\n"},{"name":"prestep.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v34/domain\"\n)\n\nvar (\n\tdomainStorage *avl.Tree // domainName -\u003e std.Address\n\trootRegistry domain.DomainRegistry\n\n\t// fee\n\tsuperBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction\n\tbankerUser std.Banker // full access to coins sent with the transaction that called the banker\n\n\tadmin std.Address // admin\n\tadminVar std.Address // admin in server\n\tfee feeInfo\n)\n\nfunc init() {\n\tdomainStorage = avl.NewTree()\n\tBidRec = avl.NewTree()\n\twinnerRec = avl.NewTree()\n\trootRegistry = domain.NewDomainRegistry(\"Varmeta\", \"vmt\")\n\n\t// fee init\n\tadmin = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" //@thinhnx\n\tadminVar = \"g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx\" //@varmeta-sponsorkey\n\t// ugnot\n\tfee = feeInfo{\n\t\tRegisterBaseFee: 100,\n\t\tRenewalFee: 100,\n\t\tRegisterAdditionFee: 0,\n\t\tBidJoinFee: 100,\n\t}\n\tsuperBanker = std.GetBanker(std.BankerTypeRealmSend)\n\tbankerUser = std.GetBanker(std.BankerTypeOrigSend)\n}\n"},{"name":"registrar.gno","body":"/*\nThis package contains functions that will actually execute the request from user\nFeatures: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion...\n*/\n// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm.\n// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session\n\n// currently we dont using too much panic because we dont have defer functions to revert the state of storage\npackage registrar\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v34/domain\"\n)\n\n// XXX: consider using panic instead of return string or errors\nfunc Register(domainName string, mode string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: mode,\n\t}\n\n\tregResult := executeRegister(requestInfo)\n\n\t// calling panic to stop paying fee\n\tif !regResult.Success {\n\t\tpanic(regResult.ResultDetails.Error())\n\t}\n\t// pay fee with panic inside\n\tfeeProcess(requestInfo)\n\treturn \"Register Done\"\n}\n\nfunc executeRegister(req RequestInfo) ExecuteResult {\n\t// check if domain name is regex valid\n\tvar execRes ExecuteResult\n\tif !isValidDomain(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrInvalidDomainName\n\t\treturn execRes\n\t}\n\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrAlreadyRegistered\n\t\treturn execRes\n\t}\n\n\t// execute register domain - mint the nft\n\t// changelogs v2: we are using sealed bidding now\n\n\tcaller := req.Caller\n\tttl := defaultExpireTime\n\tmetadata := metadataWrapper(caller, req.WantedDomain, ttl)\n\t// create a new registry instance to save metadata and mint the NFT\n\terrRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl)\n\tif errRegister != nil {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrCrossRealms\n\t\treturn execRes\n\t}\n\t// now save caller to corressponding tree to manage\n\tdomainStorage.Set(req.WantedDomain, caller)\n\n\texecRes.Success = true\n\treturn execRes\n}\n\nfunc feeProcess(req RequestInfo) {\n\tif req.Mode == \"token\" {\n\t\ttokenProcess(req.WantedDomain, req.Caller)\n\t} else {\n\t\tnativeProcess()\n\t}\n}\n\nfunc AlreadyRegistered(domainName string) bool {\n\t// if can get owner -\u003e existed\n\taddr, err := rootRegistry.OwnerOf(domainName)\n\tif err == nil \u0026\u0026 addr != \"\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetOwner(domainName string) std.Address {\n\tvl, existed := domainStorage.Get(domainName)\n\tif !existed {\n\t\treturn \"\"\n\t}\n\treturn vl.(std.Address)\n}\n\nfunc Search(domainName string) (remapMetadata, string) {\n\tvalidMetadata := remapMetadata{}\n\tmd, err := getMetadata(domainName)\n\tif err != nil {\n\t\t// return validMetadata, err.Error()\n\t\tpanic(err)\n\t}\n\tvalidMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339)\n\tvalidMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339)\n\t// jsonData, _ := validMetadata.MarshalJSON()\n\treturn validMetadata, \"Search Success\"\n}\n\nfunc getMetadata(wantedDomain string) (domain.Metadata, error) {\n\t// confirm the method? -\u003e get all the fields if the fields slice is empty\n\tmetadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{})\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\treturn metadata, nil\n}\n\n// Transfer\nfunc TransferDomain(from, to, domainName string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t}\n\tif err := excuteTransfer(requestInfo); err != \"\" {\n\t\tpanic(err)\n\t}\n\treturn \"Transfer Done\"\n}\n\nfunc excuteTransfer(req RequestInfo) string {\n\tif !AlreadyRegistered(req.WantedDomain) {\n\t\treturn ErrAlreadyRegistered.Error()\n\t}\n\trootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain)\n\treturn \"\"\n}\n\nfunc GetDomainName(addr string) []string {\n\tdomainList := []string{}\n\t// search from local storage\n\tdomainStorage.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcaller := value.(std.Address)\n\t\t// not checking isExpired\n\t\tif caller.String() == addr {\n\t\t\tdomainList = append(domainList, key)\n\t\t}\n\t\treturn false\n\t})\n\treturn domainList\n}\n"},{"name":"registrar_test.gno","body":"package registrar\n\n// import (\n// \t\"fmt\"\n// \t\"std\"\n// \t\"testing\"\n// )\n\n// func TestRegisterDomain(t *testing.T) {\n// \ttcs := []struct {\n// \t\tinput string\n// \t\texpected string\n// \t}{\n// \t\t{\"thinhnx\", \"Register done\"},\n// \t}\n// \tfor tc := range tcs {\n// \t\tname := tc.input\n// \t\tt.Run(name, func(t *testing.T) {\n// \t\t\toutput := Register(tc.input)\n// \t\t\tif output != tc.expected {\n// \t\t\t\tt.Errorf(\"Expected '%q, but got %q\", tc.expected, output)\n// \t\t\t}\n// \t\t})\n// \t}\n// }\n"},{"name":"utils.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage registrar\n\nimport (\n\t\"regexp\"\n\t\"time\"\n)\n\nvar (\n\tdefaultCommitHashTime = time.Second * 60\n\tdefaultCommitPriceTime = time.Second * 60\n\tdefaultExpireTime = time.Hour // 30 days\n\treName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc GetExpirationDate(dName string) time.Time {\n\treturn rootRegistry.GetExpirationDate(dName)\n}\n\n// for now, this function only let admin set\nfunc SetExpirationDate(dName string, expDate time.Time) bool {\n\tassertIsAdmin()\n\treturn rootRegistry.SetExpirationDate(dName, expDate)\n}\n\nfunc SetCommitPhaseTime(duration int) {\n\tdefaultCommitHashTime = time.Duration(duration) * time.Second\n}\n\nfunc SetCommitPriceTime(duration int) {\n\tdefaultCommitPriceTime = time.Duration(duration) * time.Second\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"KS+zDKloTYK87Timw+gbgZAta9578N09uIw6YWJVRbo5LmMzpMCBzvXHTGpulRKSY4BCJqekdhnKw6k7GTSaWQ=="}],"memo":""},"blockNum":"1862012"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"resolver","path":"gno.land/r/varmeta/demo/v34/domain/resolver","files":[{"name":"checks_resolver.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage resolver\n\nimport (\n\t\"regexp\"\n\t\"time\"\n\n\t\"gno.land/r/varmeta/demo/v34/domain/registrar\"\n)\n\n// const (\n// \tadmin std.Address = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" // -\u003e @thinhnx\n// )\n\nvar reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc isExpired(dName string) bool {\n\texpDate := registrar.GetExpirationDate(dName)\n\treturn expDate.Before(time.Now())\n}\n"},{"name":"errors.gno","body":"package resolver\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name to register\")\n)\n"},{"name":"resolver.gno","body":"/*\nThe goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner.\nIt serves the purpose of \"resolving\" the ICNS Name\nto the correct address (e.g \"alice.gno\" -\u003e g1xxx).\n*/\n// changelogs: move Register feature into this resolver package\n// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result\n\npackage resolver\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/varmeta/demo/v34/domain/registrar\"\n)\n\ntype Record struct {\n\tOwner std.Address\n\tIsValid bool\n\tMemo string // no more need this\n\tPriority int\n}\n\n// retrieve the record list to get the onchain address\nfunc Resolve(domainName string) *Record {\n\tif !isValidDomain(domainName) {\n\t\tpanic(\"bad domain name\")\n\t}\n\trecord := \u0026Record{}\n\n\towner := getOwnerFromDomainStorage(domainName)\n\tif owner == \"\" {\n\t\trecord.Memo = \"not found\"\n\t\trecord.IsValid = false\n\t\treturn record\n\t}\n\n\tif !isExpired(domainName) {\n\t\trecord.IsValid = true\n\t\trecord.Owner = owner\n\t} else {\n\t\trecord.IsValid = false\n\t}\n\treturn record\n}\n\nfunc GetDomainName(addr string) []string {\n\treturn registrar.GetDomainName(addr)\n}\n\n/*\nIf query in local storage not found\nQuery to DomainStorage by domainName -\u003e get the registry -\u003e use that registry to get the Owner()\nand check the validation time?\n*/\n\nfunc existedInDomainStorage(domainName string) bool {\n\treturn registrar.AlreadyRegistered(domainName)\n}\n\nfunc getOwnerFromDomainStorage(domainName string) std.Address {\n\treturn registrar.GetOwner(domainName)\n}\n"},{"name":"resolver_metadata.gno","body":"package resolver\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v34/domain\"\n)\n\n// Metadata wrapper\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcrrTime := time.Now()\n\texpTime := crrTime.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", crrTime, expTime, []domain.Trait{})\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"Y/3pirlDBIi+e9BGiaU/o9j4Cvkzbo5OVEz0j+3QGdQAYX2lFUlacdjSkmw7posmgVrJCnXHrpfJozEeDtXsow=="}],"memo":""},"blockNum":"1862018"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"grc721","path":"gno.land/p/varmeta/demo/v35/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, tid TokenID) error\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"xl3+n9P5aIIUVFXTtw8cI/3BKbL2YYd7YveAUH2a2TpIG/vNEzoIq1z5C6fiXezwxyetdqyMg9PdDoRXbfrbqw=="}],"memo":""},"blockNum":"1864604"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"domain","path":"gno.land/p/varmeta/demo/v35/domain","files":[{"name":"domain_metadata.gno","body":"package domain\n\nimport (\n\t\"time\"\n)\n\n// Trait represents a key-value pair with an optional display type for metadata attributes\ntype Trait struct {\n\tDisplayType string // Optional display type (e.g., \"date\", \"number\", etc.)\n\tTraitType string // Type of the trait (e.g., \"age\", \"height\", etc.)\n\tValue string // Value of the trait\n}\n\n// Metadata represents the metadata associated with a domain\ntype Metadata struct {\n\tAvatar string // URL or identifier for an avatar image\n\tRegistrationTime time.Time // The time when the domain was registered\n\tExpirationTime time.Time // The time when the domain will be expire\n\tAttributes []Trait // Additional attributes of the domain\n\tDescription string // A description of the domain\n\tContactInfo string // Contact information for the domain owner\n\tRenewalFee string // The fee required to renew the domain, represented as a string\n}\n\n// NewMetadata creates a new Metadata instance\nfunc NewMetadata(avatar, description, contactInfo, renewalFee string,\n\tregistrationTime, expirationTime time.Time, attributes []Trait,\n) Metadata {\n\treturn Metadata{\n\t\tAvatar: avatar,\n\t\tRegistrationTime: registrationTime,\n\t\tExpirationTime: expirationTime,\n\t\tRenewalFee: renewalFee,\n\t\tAttributes: attributes,\n\t\tDescription: description,\n\t\tContactInfo: contactInfo,\n\t}\n}\n"},{"name":"domain_registry.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v35/grc/grc721\"\n)\n\n// domainRegistry represents a registry for domain names with metadata\ntype domainRegistry struct {\n\tdomains grc721.IGRC721 // Interface for basic NFT functionality\n\tmetadata *avl.Tree // AVL tree for storing domain metadata\n\texpDate time.Time\n}\n\n// DomainRegistry defines the methods for managing domain names and metadata\ntype DomainRegistry interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(domainName string) (std.Address, error)\n\tSafeTransferFrom(from, to std.Address, domainName string) error\n\tTransferFrom(from, to std.Address, domainName string) error\n\tApprove(approved std.Address, domainName string) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(domainName string) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, domainName string) error\n\n\tRegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error\n\tSetDomainData(domainName string, metadata Metadata) error\n\tGetDomainData(domainName string, field MetadataField) (Metadata, error)\n\tGetDomainFields(domainName string, fields []MetadataField) (Metadata, error)\n\tRenewDomain(domainName string, additionalDuration time.Duration) error\n\tGetExpirationDate(domainName string) time.Time\n\tSetExpirationDate(domainName string, expDate time.Time) bool\n}\n\n// NewDomainRegistry creates a new domain registry with metadata extensions\nfunc NewDomainRegistry(name, symbol string) *domainRegistry {\n\tregistry := grc721.NewBasicNFT(name, symbol)\n\n\treturn \u0026domainRegistry{\n\t\tdomains: registry,\n\t\tmetadata: avl.NewTree(),\n\t}\n}\n\n// RegisterDomain registers a new domain with the given metadata\nfunc (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error {\n\terr := d.domains.Mint(owner, grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\td.expDate = time.Now().Add(dur)\n\td.metadata.Set(domainName, metadata)\n\n\treturn nil\n}\n\n// RenewDomain extends the expiration time of a domain name\nfunc (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn ErrInvalidDomainName\n\t}\n\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\t// set new expiration date\n\td.expDate = d.expDate.Add(additionalDuration)\n\treturn nil\n}\n\n// SetDomainData sets the metadata for a given domain name\nfunc (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error {\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\td.metadata.Set(domainName, metadata)\n\treturn nil\n}\n\n// GetDomainFields retrieves multiple fields of metadata for a given domain\nfunc (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tif len(fields) == 0 {\n\t\treturn metadata, nil\n\t}\n\n\tvar result Metadata\n\tfor _, field := range fields {\n\t\tswitch field {\n\t\tcase FieldAvatar:\n\t\t\tresult.Avatar = metadata.Avatar\n\t\tcase FieldRegistrationTime:\n\t\t\tresult.RegistrationTime = metadata.RegistrationTime\n\t\tcase FieldExpirationTime:\n\t\t\tresult.ExpirationTime = metadata.ExpirationTime\n\t\tcase FieldRenewalFee:\n\t\t\tresult.RenewalFee = metadata.RenewalFee\n\t\tcase FieldAttributes:\n\t\t\tresult.Attributes = metadata.Attributes\n\t\tcase FieldDescription:\n\t\t\tresult.Description = metadata.Description\n\t\tcase FieldContactInfo:\n\t\t\tresult.ContactInfo = metadata.ContactInfo\n\t\tdefault:\n\t\t\treturn Metadata{}, ErrInvalidMetadataField\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// GetDomainData retrieves metadata for a given domain\nfunc (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tswitch field {\n\tcase FieldAvatar:\n\t\treturn Metadata{\n\t\t\tAvatar: metadata.Avatar,\n\t\t}, nil\n\tcase FieldRegistrationTime:\n\t\treturn Metadata{\n\t\t\tRegistrationTime: metadata.RegistrationTime,\n\t\t}, nil\n\tcase FieldExpirationTime:\n\t\treturn Metadata{\n\t\t\tExpirationTime: metadata.ExpirationTime,\n\t\t}, nil\n\tcase FieldRenewalFee:\n\t\treturn Metadata{\n\t\t\tRenewalFee: metadata.RenewalFee,\n\t\t}, nil\n\tcase FieldAttributes:\n\t\treturn Metadata{\n\t\t\tAttributes: metadata.Attributes,\n\t\t}, nil\n\tcase FieldDescription:\n\t\treturn Metadata{\n\t\t\tDescription: metadata.Description,\n\t\t}, nil\n\tcase FieldContactInfo:\n\t\treturn Metadata{\n\t\t\tContactInfo: metadata.ContactInfo,\n\t\t}, nil\n\tdefault:\n\t\treturn Metadata{}, ErrInvalidMetadataField\n\t}\n}\n\n// BalanceOf returns the number of domains owned by a given address\nfunc (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) {\n\treturn d.domains.BalanceOf(owner)\n}\n\n// OwnerOf returns the owner of a given domain name\nfunc (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) {\n\treturn d.domains.OwnerOf(grc721.TokenID(domainName))\n}\n\n// SafeTransferFrom safely transfers a domain from one address to another\nfunc (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// TransferFrom transfers a domain from one address to another\nfunc (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.TransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// Approve grants approval to another address to manage a specific domain\nfunc (d *domainRegistry) Approve(approved std.Address, domainName string) error {\n\treturn d.domains.Approve(approved, grc721.TokenID(domainName))\n}\n\n// SetApprovalForAll sets approval for an operator to manage all domains of the owner\nfunc (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error {\n\treturn d.domains.SetApprovalForAll(operator, approved)\n}\n\n// GetApproved returns the approved address for a specific domain\nfunc (d *domainRegistry) GetApproved(domainName string) (std.Address, error) {\n\treturn d.domains.GetApproved(grc721.TokenID(domainName))\n}\n\n// IsApprovedForAll checks if an operator is approved to manage all domains of the owner\nfunc (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool {\n\treturn d.domains.IsApprovedForAll(owner, operator)\n}\n\n// Mint creates a new domain for a given address\nfunc (d *domainRegistry) Mint(to std.Address, domainName string) error {\n\treturn d.domains.Mint(to, grc721.TokenID(domainName))\n}\n\nfunc (d *domainRegistry) GetExpirationDate(domainName string) time.Time {\n\treturn d.expDate\n}\n\nfunc (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn false\n\t}\n\td.expDate = expDate\n\treturn true\n}\n"},{"name":"domain_registry_test.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n\t\"gno.land/p/varmeta/demo/v35/grc/grc721\"\n)\n\nvar (\n\taddr1 = testutils.TestAddress(\"bob\")\n\taddr2 = testutils.TestAddress(\"alice\")\n)\n\nfunc TestRegisterDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"contact@registered.com\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Duplicate Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\terr := registry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.owner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\t\t\terr := registry.SetDomainData(c.domainName, c.metadata)\n\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\tretrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRenewDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tadditionalTime time.Duration\n\t\texpectError bool\n\t\texpectedExpiry time.Time\n\t}{\n\t\t{\n\t\t\tname: \"Successful Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A renewable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.RenewDomain(c.domainName, c.additionalTime)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\trenewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\t// urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfield MetadataField\n\t\texpectError bool\n\t\texpectedVal string\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Avatar\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: false,\n\t\t\texpectedVal: \"avatar_url\",\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain Name\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t_, err := registry.GetDomainData(c.domainName, c.field)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainFields(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfields []MetadataField\n\t\texpectError bool\n\t\texpected Metadata\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Multiple Fields\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo},\n\t\t\texpectError: false,\n\t\t\texpected: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tretrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar)\n\t\t\t\turequire.Equal(t, c.expected.Description, retrievedMetadata.Description)\n\t\t\t\turequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTransferDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tnewOwner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A transferable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.TransferFrom(c.owner, c.newOwner, c.domainName)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.newOwner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"errors.gno","body":"package domain\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"caller is not domain owner\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name\")\n\tErrInvalidMetadataField = errors.New(\"invalid metadata field\")\n\tErrInsufficientFunds = errors.New(\"insufficient funds for renewal\")\n)\n"},{"name":"utils.gno","body":"package domain\n\ntype MetadataField int\n\nconst (\n\tFieldAvatar MetadataField = iota\n\tFieldRegistrationTime\n\tFieldRenewalFee\n\tFieldExpirationTime\n\tFieldAttributes\n\tFieldDescription\n\tFieldContactInfo\n)\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"PGBlX+IJHoOOaeY5DWRg+Od4Cq1P6xCbFhCDbfm6KodNSaZlQ+G9oZCUeUZGsCRzD1ZP0PpvxA6Mq9O+UuXGQQ=="}],"memo":""},"blockNum":"1864610"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"registrar","path":"gno.land/r/varmeta/demo/v35/domain/registrar","files":[{"name":"bidding.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\t// \"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/avl\"\n)\n\ntype bidRecord struct {\n\tDomainName string\n\tBidder std.Address\n\tHashString string\n\tPrice int\n\tStartTime time.Time\n\tEndCommitTime time.Time\n\tEndPriceTime time.Time\n\tCurrentPhase actionCode\n\tIsOpen bool\n}\n\nvar (\n\tBidRec *avl.Tree // bidRecord \u003c- []bidRec\n\twinnerRec *avl.Tree\n\tjoinedBid avl.Tree\n)\n\n// joinedBid: address \u003c- []domainName\nfunc recordJoinedBid(domainName string) {\n\tcaller := std.GetOrigCaller()\n\tdList := []string{}\n\tdata, existed := joinedBid.Get(caller.String())\n\tif !existed {\n\t\tdList = []string{domainName}\n\t\tjoinedBid.Set(caller.String(), dList)\n\t\treturn\n\t}\n\tdList = data.([]string)\n\tdList = append(dList, domainName)\n\tjoinedBid.Set(caller.String(), dList)\n\treturn\n}\n\n// get the joined bids of an account\n// want to return both name and status\nfunc GetJoinedBid() []singleStatus {\n\tcaller := std.GetOrigCaller().String()\n\tdata, existed := joinedBid.Get(caller)\n\tif !existed {\n\t\treturn []singleStatus{}\n\t}\n\tlist := data.([]string)\n\tlistStatus := []singleStatus{}\n\tfor _, dName := range list {\n\t\tstt := \"\"\n\t\towner := GetOwner(dName)\n\t\tif owner != \"\" {\n\t\t\tstt = \"owned by \" + owner.String()\n\t\t} else {\n\t\t\tstt = GetCurrentStatus(dName)\n\t\t}\n\t\tsingleStt := singleStatus{\n\t\t\tDomainName: dName,\n\t\t\tStatus: stt,\n\t\t}\n\t\tlistStatus = append(listStatus, singleStt)\n\t}\n\treturn listStatus\n}\n\n// get the next state of bidding session\n// XXX note commit price status + if time is expired and not commited yet\n// status clear\nfunc GetCurrentStatus(domainName string) string {\n\t// if there is record in joinedBid -\u003e user joined\n\t// check for tine.Now() and startTime\n\tnow := time.Now()\n\tcaller := std.GetOrigCaller()\n\t// find the record\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\t// no record in bidRec yet -\u003e not commited -\u003e check if user started auction or not - if yes: new auction\n\t\tif _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister {\n\t\t\treturn \"new auction\"\n\t\t}\n\t\treturn \"invalid\"\n\t}\n\n\t// commited yet\n\trecList := data.([]bidRecord)\n\trec := recList[0]\n\n\tif now.Before(rec.EndCommitTime) {\n\t\tif rec.HashString != \"\" {\n\t\t\treturn \"commited hash\"\n\t\t} else {\n\t\t\treturn \"hash\"\n\t\t}\n\t}\n\n\tif now.Before(rec.EndPriceTime) \u0026\u0026 now.After(rec.EndCommitTime) {\n\t\treturn \"price\"\n\t}\n\tif now.After(rec.EndPriceTime) {\n\t\treturn \"closed\"\n\t}\n\treturn \"undefined\"\n}\n\n// Render() renders welcome message :D\nfunc Render(path string) string {\n\treturn \"welcome to varmeta domain name service\"\n}\n\n/*\n\tlogic should be:\n\t1. User click register for a dns.\n\t-\u003e 1.1: dns does not exists -\u003e open bidding session\n\t-\u003e 1.2: dns existed -\u003e check if open bidding session is openning or not -\u003e if open -\u003e new bidRecord\n\t2. Bidding session:\n\t-\u003e 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -\u003e creat bidRecord to add to bidRec\n\t-\u003e 2.2: CommitPrice phase: commit price + secret key: -\u003e if matches -\u003e write price into bidRec ( send the amunt of coin corresponding)\n\t-\u003e 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button\n\t---\u003e admin end bidding auction\n\t-\u003e 2.3.1: cooldown phase -\u003e waiting for winner -\u003e 2nd winner\n\t-\u003e 2.4: re-transfer coin from admin to user who not win the bid\n\t\tthe flow is: user want to register new domain name -\u003e start new bidding session -\u003e got the bidding session id\n\t\tif other want to register to the same domain name -\u003e bidding into this session, if not return to new state\n\t\twhen the session ends, need to have 2nd phase called commit -\u003e commit their price and secret to compute the hash\n\t\t-\u003e compare the hash as in record -\u003e accept the hash -\u003e accept the price\n\t\t*** temp approach: everytime user commit, user open a bid session if not existed, if existed -\u003e check if this session is ended.\n*/\n// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate\n// the time whenever there is action to commit.\n\n// commit the domain name and computed hash string\nfunc CommitHash(domainName, hashString string) string {\n\tcaller := std.GetOrigCaller()\n\tufmt.Println(\"caller: \", caller.String())\n\tnow := time.Now()\n\n\t// update the bid record\n\tdata, existed := BidRec.Get(domainName)\n\n\t// if not existed -\u003e create new record\n\tif !existed {\n\t\t// for further getStatus\n\t\trecordJoinedBid(domainName)\n\n\t\tvar bidRec bidRecord\n\t\tendCommitTime := now.Add(defaultCommitHashTime)\n\t\tendPriceTime := endCommitTime.Add(defaultCommitPriceTime)\n\t\tufmt.Println(\"endCommitTime: \", endCommitTime.Format(time.RFC3339))\n\t\tufmt.Println(\"endPriceTime: \", endPriceTime.Format(time.RFC3339))\n\t\tbidRec = bidRecord{\n\t\t\tDomainName: domainName,\n\t\t\tBidder: caller,\n\t\t\tHashString: hashString,\n\t\t\tStartTime: now,\n\t\t\tEndCommitTime: endCommitTime,\n\t\t\tEndPriceTime: endPriceTime,\n\t\t\tIsOpen: true,\n\t\t}\n\t\tbidRecList := []bidRecord{bidRec}\n\t\tBidRec.Set(domainName, bidRecList)\n\n\t\t// charge fee\n\t\tchargeFee(fee.BidJoinFee, caller)\n\t\treturn \"new session\"\n\t}\n\t// if existed\n\t// TODO: Check if commit more than 1 time -\u003e done\n\tbidRecList := data.([]bidRecord)\n\tstartTime := bidRecList[0].StartTime\n\tif now.After(bidRecList[0].EndCommitTime) {\n\t\tpanic(\"can not commit hash anymore\")\n\t}\n\tfor _, bR := range bidRecList {\n\t\tif bR.Bidder == caller {\n\t\t\tpanic(\"you already commited hash\")\n\t\t}\n\t}\n\n\toldEndCommitTime := bidRecList[0].EndCommitTime\n\toldEndPriceTime := bidRecList[0].EndPriceTime\n\tbidRec := bidRecord{\n\t\tDomainName: domainName,\n\t\tHashString: hashString,\n\t\tBidder: caller,\n\t\tStartTime: startTime,\n\t\tEndCommitTime: oldEndCommitTime,\n\t\tEndPriceTime: oldEndPriceTime,\n\t\tIsOpen: true,\n\t}\n\tbidRecList = append(bidRecList, bidRec)\n\t// Save record\n\tBidRec.Set(domainName, bidRecList)\n\t// charge commit hash fee\n\tchargeFee(fee.BidJoinFee, caller)\n\treturn \"existed\"\n}\n\n// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me\n// commit price and secret to reveal auction session\nfunc CommitPrice(price int, secret string, domainName string) string {\n\t// compute the hash string, compare to saved hash string in record\n\tnow := time.Now()\n\tjoinedString := secret + strconv.Itoa(price)\n\tcomputedHashString := Get256String(joinedString)\n\tufmt.Println(\"computed hash: \", computedHashString)\n\tcaller := std.GetOrigCaller()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\" domain name is invalid\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerRec.Set(domainName, bidRecList[0])\n\n\tufmt.Println(\"time now: \", now.Format(time.RFC3339))\n\tufmt.Println(\"end commit phase time: \", bidRecList[0].EndCommitTime.Format(time.RFC3339))\n\t// case commit after end - consider panic or not\n\tif now.After(bidRecList[0].EndPriceTime) {\n\t\tufmt.Println(\"commit price phase is ended\")\n\t\treturn \"ended\"\n\t}\n\t// case commit when price phase not started\n\tif time.Now().Before(bidRecList[0].EndCommitTime) {\n\t\tufmt.Println(\"commit price phase is not started yet\")\n\t\treturn \"not started yet\"\n\t}\n\n\t// search for the corresponding hash\n\tfor _, bidRec := range bidRecList {\n\t\t// panic because wrong price or wrong secret string\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString != computedHashString {\n\t\t\tpanic(\"invalid hash string\")\n\t\t}\n\t\t// found it, update the winner price\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString == computedHashString {\n\t\t\tdata, _ := winnerRec.Get(domainName)\n\t\t\tcurrentWinnerRec := data.(bidRecord)\n\t\t\tif price \u003e currentWinnerRec.Price \u0026\u0026 now.Before(currentWinnerRec.EndPriceTime) {\n\t\t\t\tufmt.Println(\"found new winner, setting up\")\n\t\t\t\tcurrentWinnerRec.Price = price\n\t\t\t\tcurrentWinnerRec.Bidder = bidRec.Bidder\n\t\t\t\tcurrentWinnerRec.HashString = bidRec.HashString\n\t\t\t\twinnerRec.Set(domainName, currentWinnerRec)\n\t\t\t\treturn \"claim\"\n\t\t\t}\n\t\t}\n\t}\n\t// if not match above case, then panic\n\tpanic(\"commit failed\")\n}\n\nfunc GetCurrentWinner(domainName string) bidRecord {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"no winner yet\")\n\t}\n\treturn data.(bidRecord)\n}\n\n// find the highest bid in session - incase everyone commited price\nfunc findTheWinner(domainName string) bidRecord {\n\tvar winnerBid bidRecord\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerBid = bidRecList[0]\n\tfor _, bidRec := range bidRecList {\n\t\tif bidRec.Price \u003e winnerBid.Price {\n\t\t\twinnerBid = bidRec\n\t\t}\n\t}\n\treturn winnerBid\n}\n\n// register the domain for winner\nfunc registerForWinner(domainName string, winnerRec bidRecord) bool {\n\twinnerAddr := winnerRec.Bidder\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: winnerAddr,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tfeeProcess(requestInfo)\n\treturn false\n}\n\n// everyone can call EndBid()\n// this EndBid checks endTime -\u003e end the auction\nfunc EndBid(domainName string) error {\n\tnow := time.Now()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\treturn ufmt.Errorf(\"endbid: invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\tfirstBidRec := bidRecList[0]\n\tif now.Before(firstBidRec.EndPriceTime) {\n\t\treturn ufmt.Errorf(\"endbid: this session can not end before the end time\")\n\t}\n\t// change all state\n\tfor _, bidRec := range bidRecList {\n\t\tbidRec.IsOpen = false\n\t}\n\tok := BidRec.Set(domainName, bidRecList)\n\tif !ok {\n\t\treturn ufmt.Errorf(\"endbid: can not change bid record state\")\n\t}\n\t// need more conditions for findTheWinner()\n\tfindTheWinner(domainName)\n\treturn nil\n}\n\n// register new domain with bidding process inside - if message is commit hash -\u003e dapp need to call commit hash\n// func RegisterDomain(domainName string) bidStatus {\n// \treqInfo := RequestInfo{\n// \t\tWantedDomain: domainName,\n// \t\tCaller: std.PrevRealm().Addr(),\n// \t\tMode: \"native\",\n// \t}\n\n// \t// bidStatus\n// \tresult := checkRegisterState(reqInfo)\n// \trecordJoinedBid(domainName)\n// \t// if checkState get the message of which phase this domain name belongs to, then return the status\n// \treturn result\n// }\n\n// func checkRegisterState(req RequestInfo) bidStatus {\n// \tvar bStt bidStatus\n// \t// check if domain name is regex valid\n// \tif !isValidDomain(req.WantedDomain) {\n// \t\tpanic(\"invalid domain name\")\n// \t}\n// \t// check if dName is registered\n// \tif AlreadyRegistered(req.WantedDomain) {\n// \t\tpanic(\"domain name already registered\")\n// \t}\n// \t// changelogs v2: we are using sealed bidding now\n// \t// check if a bidding session is openning -\u003e append new commit hash into record list\n// \t// both existed or not we open new bidding session\n// \t// for now we return a signal for dapps / service to know what to do next\n// \tisExisted, isOpen := checkBiddingState(req.WantedDomain)\n// \tif isExisted \u0026\u0026 isOpen {\n// \t\t// return commit hash signal for dapps\n// \t\tbStt.CurrentStatus = AppendBiddingSession\n// \t\tbStt.ActionCode = CommitHashPhase\n// \t\treturn bStt\n// \t}\n\n// \t// create new session\n// \tif !isExisted {\n// \t\tbStt.CurrentStatus = NewBiddingSession\n// \t\tbStt.ActionCode = CommitHashPhase\n// \t\treturn bStt\n// \t}\n\n// \t// not found in register repository but also not found in Bidding Record -\u003e panic(\"error somewhere :D\")\n// \tpanic(ufmt.Errorf(\"should not happend\"))\n// \treturn bStt\n// }\n\n// // open a bidding session\n// func openBiddingSession(domainName string, dur time.Duration) string {\n// \tnow := time.Now()\n// \tbidRec := bidRecord{\n// \t\tDomainName: domainName,\n// \t\tStartTime: now,\n// \t}\n// \tbidRecList := []bidRecord{bidRec}\n// \tok := BidRec.Set(domainName, bidRecList)\n// \tif !ok {\n// \t\tpanic(\"can not open bidding session\")\n// \t}\n// \treturn \"created bidding session\"\n// }\n\nfunc checkBiddingState(dName string) (isExisted bool, isOpen bool) {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tisExisted = false\n\t\tisOpen = false\n\t\treturn\n\t}\n\tisExisted = true\n\trecList := data.([]bidRecord)\n\tif recList[0].IsOpen {\n\t\tisOpen = true\n\t} else {\n\t\tisOpen = false\n\t}\n\treturn isExisted, isOpen\n}\n\n// get all the price list that joined the bid for displaying in dapp\nfunc GetRecords(dName string) []bidRecord {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tpanic(\"should not\")\n\t}\n\treturn data.([]bidRecord)\n}\n\n// chargeFee will charge amount - send from this contract to admin\nfunc chargeFee(amount int64, from std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", amount)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcoins := checkCoin(from)\n\tufmt.Println(\"check balances: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// pay fee and claim the domain name if you are winner\nfunc Claim(domainName string) bool {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"claim: invalid domain name\")\n\t}\n\tcaller := std.GetOrigCaller()\n\trec := data.(bidRecord)\n\tif caller != rec.Bidder {\n\t\tpanic(\"only winner can claim\")\n\t}\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: rec.Bidder,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tchargeFee(100, caller)\n\tfeeProcess(requestInfo)\n\treturn true\n}\n"},{"name":"bidding_model.gno","body":"package registrar\n\n/*\n\t0: StatusFailed\n\tCurrentStatus:\t1: NewBiddingSession\n\t\t\t\t\t2: AppendBiddingSession\n\t\t\t\t\t3: BiddingSessionClosed\n\tActionCode:\t\t1: CommitHashPhase\n\t\t\t\t\t2: CommitPricePhase\n\t\t\t\t\t3: ClaimPhase...\n*/\n\ntype bidStatus struct {\n\tCurrentStatus currStatus\n\tActionCode actionCode\n}\n\ntype (\n\tcurrStatus int\n\tactionCode int\n)\n\nvar (\n\tStatusFailed currStatus = 0\n\tNewBiddingSession currStatus = 1\n\tAppendBiddingSession currStatus = 2\n\tBiddingSessionClosed currStatus = 3\n\n\tActionFailed actionCode = 0\n\tCommitHashPhase actionCode = 1\n\tCommitPricePhase actionCode = 2\n\tClaimPhase actionCode = 3\n)\n\ntype singleStatus struct {\n\tDomainName string\n\tStatus string\n}\n"},{"name":"errors.gno","body":"package registrar\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnknown = errors.New(\"unknow errors\")\n\tErrOK = errors.New(\"ok\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"ErrInvalidDomainName\")\n\tErrAlreadyRegistered = errors.New(\"this domain is registered\")\n\tErrCrossRealms = errors.New(\"cross realms function error\")\n\tErrNotFound = errors.New(\"domain not found\")\n)\n"},{"name":"fee.gno","body":"package registrar\n\nimport (\n\t\"time\"\n)\n\n// only admin can set Fee, other just can read only\ntype feeInfo struct {\n\tRegisterBaseFee int64\n\tRenewalFee int64\n\tRegisterAdditionFee int64\n\tBidJoinFee int64\n}\n\nfunc GetRegisterFee(dName string) int64 {\n\treturn fee.RegisterBaseFee\n}\n\nfunc GetRenewalFee(dName string, amount time.Duration) int64 {\n\treturn fee.RenewalFee\n}\n\n// Admin set register fee and renewal fee\nfunc AdminSetFee(regFee int64, renewFee int64) {\n\t// consider logic\n\tassertIsAdmin()\n\tfee.RegisterBaseFee = regFee\n\tfee.RenewalFee = renewFee\n}\n\n// simple err check\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"fee_checks.gno","body":"package registrar\n\n// import (\n// \t\"\"\n// \t// \"std\"\n// \t// \"time\"\n// )\n\n\n"},{"name":"fee_native.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// admin access only\nfunc AdminWithdraw(amount int64) {\n\tassertIsAdmin()\n\tthisContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tsuperBanker.SendCoins(thisContract, admin, coinsToTransfer)\n}\n\nfunc nativeProcess() {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcaller := std.GetOrigCaller()\n\tcoins := checkCoin(caller)\n\tufmt.Println(\"check: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// RevertTransfer will revert the transaction - send amount of coin to user\nfunc revertTransfer(userAddr std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToReturn := std.NewCoins(ugnotCoin)\n\tufmt.Println(\"return coins from contract \", bankerContract.String(), \" to \", userAddr.String())\n\tbankerUser.SendCoins(bankerContract, userAddr, coinsToReturn)\n}\n\n// simple check for admin call\nfunc assertIsAdmin() {\n\t// check if GetCallerAt 2 or 3 when deployed\n\tcaller := std.GetCallerAt(3)\n\terr := ufmt.Sprintf(\"unauthorize with caller: %s\\n\", caller)\n\tif caller != admin \u0026\u0026 caller != adminVar {\n\t\tpanic(err)\n\t}\n}\n\n// checking for availble coins\nfunc checkCoin(from std.Address) std.Coins {\n\t// caller := std.GetOrigCaller()\n\treturn bankerUser.GetCoins(from)\n}\n"},{"name":"fee_token.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/varmeta/demo1/domain/vmt\"\n)\n\n// expected approved already from client -\u003e transfer from caller to admin\nfunc tokenProcess(dName string, callerStd std.Address) {\n\tcaller := pusers.AddressOrName(callerStd.String())\n\n\tnow := std.CurrentRealm().Addr()\n\tnowAddr := pusers.AddressOrName(now.String())\n\tufmt.Println(\"current realm transfer: \", now.String())\n\tcallerAllowance := vmt.Allowance(caller, nowAddr)\n\tcallerAllowanceString := ufmt.Sprintf(\"%d\", callerAllowance)\n\tufmt.Println(\"caller allowance \", callerAllowanceString)\n\n\tadminAddr := pusers.AddressOrName(admin.String())\n\tufmt.Println(\"admin: \", admin.String())\n\tvmt.TransferFrom(caller, adminAddr, 1)\n}\n"},{"name":"hashstring.gno","body":"package registrar\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n)\n\nfunc Get256String(input string) string {\n\tdata := []byte(input)\n\thashed := sha256.Sum256(data)\n\thashedBytes := hashed[:]\n\treturn hex.EncodeToString(hashedBytes)\n}\n"},{"name":"metadata_wrapper.gno","body":"package registrar\n\nimport (\n\t\"bytes\"\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/p/varmeta/demo/v35/domain\"\n)\n\n// Metadata wrapper\n// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait)\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcreatedAt := time.Now()\n\texpTime := createdAt.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", createdAt, expTime, []domain.Trait{})\n}\n\ntype remapMetadata struct {\n\tAvatar string // avatar - URL or identifier for an avatar image\n\tRegistrationTime string // regtime - The time when the domain was registered\n\tExpirationTime string // exptime - The time when the domain will be expire\n\tAttributes []domain.Trait // atts - Additional attributes of the domain\n\tDescription string // des - A description of the domain\n\tContactInfo string // contacts - Contact information for the domain owner\n\tRenewalFee string // renewalfee - The fee required to renew the domain, represented as a string\n}\n\n// currently not support for arrays\nfunc (m remapMetadata) MarshalJSON() ([]byte, error) {\n\tjson := new(bytes.Buffer)\n\tif m.Attributes == nil {\n\t\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, \"empty\", m.Description, m.ContactInfo, m.RenewalFee))\n\t\treturn json.Bytes(), nil\n\t}\n\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee))\n\treturn json.Bytes(), nil\n}\n"},{"name":"models.gno","body":"package registrar\n\nimport (\n\t\"std\"\n)\n\ntype RequestInfo struct {\n\tMode string\n\tWantedDomain string\n\tCaller std.Address\n\tTransInfo TransferInfo\n\t// xxx extendTime, renew...\n}\ntype TransferInfo struct {\n\tFrom std.Address\n\tTo std.Address\n}\ntype ExecuteResult struct {\n\tSuccess bool\n\tResultDetails error\n\tMessage string\n}\n"},{"name":"prestep.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v35/domain\"\n)\n\nvar (\n\tdomainStorage *avl.Tree // domainName -\u003e std.Address\n\trootRegistry domain.DomainRegistry\n\n\t// fee\n\tsuperBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction\n\tbankerUser std.Banker // full access to coins sent with the transaction that called the banker\n\n\tadmin std.Address // admin\n\tadminVar std.Address // admin in server\n\tfee feeInfo\n)\n\nfunc init() {\n\tdomainStorage = avl.NewTree()\n\tBidRec = avl.NewTree()\n\twinnerRec = avl.NewTree()\n\trootRegistry = domain.NewDomainRegistry(\"Varmeta\", \"vmt\")\n\n\t// fee init\n\tadmin = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" //@thinhnx\n\tadminVar = \"g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx\" //@varmeta-sponsorkey\n\t// ugnot\n\tfee = feeInfo{\n\t\tRegisterBaseFee: 100,\n\t\tRenewalFee: 100,\n\t\tRegisterAdditionFee: 0,\n\t\tBidJoinFee: 100,\n\t}\n\tsuperBanker = std.GetBanker(std.BankerTypeRealmSend)\n\tbankerUser = std.GetBanker(std.BankerTypeOrigSend)\n}\n"},{"name":"registrar.gno","body":"/*\nThis package contains functions that will actually execute the request from user\nFeatures: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion...\n*/\n// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm.\n// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session\n\n// currently we dont using too much panic because we dont have defer functions to revert the state of storage\npackage registrar\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v35/domain\"\n)\n\n// XXX: consider using panic instead of return string or errors\nfunc Register(domainName string, mode string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: mode,\n\t}\n\n\tregResult := executeRegister(requestInfo)\n\n\t// calling panic to stop paying fee\n\tif !regResult.Success {\n\t\tpanic(regResult.ResultDetails.Error())\n\t}\n\t// pay fee with panic inside\n\tfeeProcess(requestInfo)\n\treturn \"Register Done\"\n}\n\nfunc executeRegister(req RequestInfo) ExecuteResult {\n\t// check if domain name is regex valid\n\tvar execRes ExecuteResult\n\tif !isValidDomain(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrInvalidDomainName\n\t\treturn execRes\n\t}\n\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrAlreadyRegistered\n\t\treturn execRes\n\t}\n\n\t// execute register domain - mint the nft\n\t// changelogs v2: we are using sealed bidding now\n\n\tcaller := req.Caller\n\tttl := defaultExpireTime\n\tmetadata := metadataWrapper(caller, req.WantedDomain, ttl)\n\t// create a new registry instance to save metadata and mint the NFT\n\terrRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl)\n\tif errRegister != nil {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrCrossRealms\n\t\treturn execRes\n\t}\n\t// now save caller to corressponding tree to manage\n\tdomainStorage.Set(req.WantedDomain, caller)\n\n\texecRes.Success = true\n\treturn execRes\n}\n\nfunc feeProcess(req RequestInfo) {\n\tif req.Mode == \"token\" {\n\t\ttokenProcess(req.WantedDomain, req.Caller)\n\t} else {\n\t\tnativeProcess()\n\t}\n}\n\nfunc AlreadyRegistered(domainName string) bool {\n\t// if can get owner -\u003e existed\n\taddr, err := rootRegistry.OwnerOf(domainName)\n\tif err == nil \u0026\u0026 addr != \"\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetOwner(domainName string) std.Address {\n\tvl, existed := domainStorage.Get(domainName)\n\tif !existed {\n\t\treturn \"\"\n\t}\n\treturn vl.(std.Address)\n}\n\nfunc Search(domainName string) (remapMetadata, string) {\n\tvalidMetadata := remapMetadata{}\n\tmd, err := getMetadata(domainName)\n\tif err != nil {\n\t\t// return validMetadata, err.Error()\n\t\tpanic(err)\n\t}\n\tvalidMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339)\n\tvalidMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339)\n\t// jsonData, _ := validMetadata.MarshalJSON()\n\treturn validMetadata, \"Search Success\"\n}\n\nfunc getMetadata(wantedDomain string) (domain.Metadata, error) {\n\t// confirm the method? -\u003e get all the fields if the fields slice is empty\n\tmetadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{})\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\treturn metadata, nil\n}\n\n// Transfer\nfunc TransferDomain(from, to, domainName string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t}\n\tif err := excuteTransfer(requestInfo); err != \"\" {\n\t\tpanic(err)\n\t}\n\treturn \"Transfer Done\"\n}\n\nfunc excuteTransfer(req RequestInfo) string {\n\tif !AlreadyRegistered(req.WantedDomain) {\n\t\treturn ErrAlreadyRegistered.Error()\n\t}\n\trootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain)\n\treturn \"\"\n}\n\nfunc GetDomainName(addr string) []string {\n\tdomainList := []string{}\n\t// search from local storage\n\tdomainStorage.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcaller := value.(std.Address)\n\t\t// not checking isExpired\n\t\tif caller.String() == addr {\n\t\t\tdomainList = append(domainList, key)\n\t\t}\n\t\treturn false\n\t})\n\treturn domainList\n}\n"},{"name":"registrar_test.gno","body":"package registrar\n\n// import (\n// \t\"fmt\"\n// \t\"std\"\n// \t\"testing\"\n// )\n\n// func TestRegisterDomain(t *testing.T) {\n// \ttcs := []struct {\n// \t\tinput string\n// \t\texpected string\n// \t}{\n// \t\t{\"thinhnx\", \"Register done\"},\n// \t}\n// \tfor tc := range tcs {\n// \t\tname := tc.input\n// \t\tt.Run(name, func(t *testing.T) {\n// \t\t\toutput := Register(tc.input)\n// \t\t\tif output != tc.expected {\n// \t\t\t\tt.Errorf(\"Expected '%q, but got %q\", tc.expected, output)\n// \t\t\t}\n// \t\t})\n// \t}\n// }\n"},{"name":"utils.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage registrar\n\nimport (\n\t\"regexp\"\n\t\"time\"\n)\n\nvar (\n\tdefaultCommitHashTime = time.Second * 60\n\tdefaultCommitPriceTime = time.Second * 60\n\tdefaultExpireTime = time.Hour // 30 days\n\treName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc GetExpirationDate(dName string) time.Time {\n\treturn rootRegistry.GetExpirationDate(dName)\n}\n\n// for now, this function only let admin set\nfunc SetExpirationDate(dName string, expDate time.Time) bool {\n\tassertIsAdmin()\n\treturn rootRegistry.SetExpirationDate(dName, expDate)\n}\n\nfunc SetCommitPhaseTime(duration int) {\n\tdefaultCommitHashTime = time.Duration(duration) * time.Second\n}\n\nfunc SetCommitPriceTime(duration int) {\n\tdefaultCommitPriceTime = time.Duration(duration) * time.Second\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"8aXN3zHMAFCwfqmQOlebNxt+e5Mss2aW0W7OZy7kbfI2OVOU8TjPLHXTM/t9J0HgcmpwF5ThMpu78LF0T7zhrg=="}],"memo":""},"blockNum":"1864627"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"resolver","path":"gno.land/r/varmeta/demo/v35/domain/resolver","files":[{"name":"checks_resolver.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage resolver\n\nimport (\n\t\"regexp\"\n\t\"time\"\n\n\t\"gno.land/r/varmeta/demo/v35/domain/registrar\"\n)\n\n// const (\n// \tadmin std.Address = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" // -\u003e @thinhnx\n// )\n\nvar reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc isExpired(dName string) bool {\n\texpDate := registrar.GetExpirationDate(dName)\n\treturn expDate.Before(time.Now())\n}\n"},{"name":"errors.gno","body":"package resolver\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name to register\")\n)\n"},{"name":"resolver.gno","body":"/*\nThe goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner.\nIt serves the purpose of \"resolving\" the ICNS Name\nto the correct address (e.g \"alice.gno\" -\u003e g1xxx).\n*/\n// changelogs: move Register feature into this resolver package\n// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result\n\npackage resolver\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/varmeta/demo/v35/domain/registrar\"\n)\n\ntype Record struct {\n\tOwner std.Address\n\tIsValid bool\n\tMemo string // no more need this\n\tPriority int\n}\n\n// retrieve the record list to get the onchain address\nfunc Resolve(domainName string) *Record {\n\tif !isValidDomain(domainName) {\n\t\tpanic(\"bad domain name\")\n\t}\n\trecord := \u0026Record{}\n\n\towner := getOwnerFromDomainStorage(domainName)\n\tif owner == \"\" {\n\t\trecord.Memo = \"not found\"\n\t\trecord.IsValid = false\n\t\treturn record\n\t}\n\n\tif !isExpired(domainName) {\n\t\trecord.IsValid = true\n\t\trecord.Owner = owner\n\t} else {\n\t\trecord.IsValid = false\n\t}\n\treturn record\n}\n\nfunc GetDomainName(addr string) []string {\n\treturn registrar.GetDomainName(addr)\n}\n\n/*\nIf query in local storage not found\nQuery to DomainStorage by domainName -\u003e get the registry -\u003e use that registry to get the Owner()\nand check the validation time?\n*/\n\nfunc existedInDomainStorage(domainName string) bool {\n\treturn registrar.AlreadyRegistered(domainName)\n}\n\nfunc getOwnerFromDomainStorage(domainName string) std.Address {\n\treturn registrar.GetOwner(domainName)\n}\n"},{"name":"resolver_metadata.gno","body":"package resolver\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v35/domain\"\n)\n\n// Metadata wrapper\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcrrTime := time.Now()\n\texpTime := crrTime.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", crrTime, expTime, []domain.Trait{})\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"HAFVyJ6oAhBRpW5rykSTzuW2uySxBQj+7PLQyLm47Od9hx4s2w245kiO07A4bvlkh6s8pA/GkDr9+AGTeYL3Mg=="}],"memo":""},"blockNum":"1864634"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"grc721","path":"gno.land/p/varmeta/demo/v36/grc/grc721","files":[{"name":"basic_nft.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ufmt\"\n)\n\ntype basicNFT struct {\n\tname string\n\tsymbol string\n\towners avl.Tree // tokenId -\u003e OwnerAddress\n\tbalances avl.Tree // OwnerAddress -\u003e TokenCount\n\ttokenApprovals avl.Tree // TokenId -\u003e ApprovedAddress\n\ttokenURIs avl.Tree // TokenId -\u003e URIs\n\toperatorApprovals avl.Tree // \"OwnerAddress:OperatorAddress\" -\u003e bool\n}\n\n// Returns new basic NFT\nfunc NewBasicNFT(name string, symbol string) *basicNFT {\n\treturn \u0026basicNFT{\n\t\tname: name,\n\t\tsymbol: symbol,\n\n\t\towners: avl.Tree{},\n\t\tbalances: avl.Tree{},\n\t\ttokenApprovals: avl.Tree{},\n\t\ttokenURIs: avl.Tree{},\n\t\toperatorApprovals: avl.Tree{},\n\t}\n}\n\nfunc (s *basicNFT) Name() string { return s.name }\nfunc (s *basicNFT) Symbol() string { return s.symbol }\nfunc (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }\n\n// BalanceOf returns balance of input address\nfunc (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {\n\tif err := isValidAddress(addr); err != nil {\n\t\treturn 0, err\n\t}\n\n\tbalance, found := s.balances.Get(addr.String())\n\tif !found {\n\t\treturn 0, nil\n\t}\n\n\treturn balance.(uint64), nil\n}\n\n// OwnerOf returns owner of input token id\nfunc (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn owner.(std.Address), nil\n}\n\n// TokenURI returns the URI of input token id\nfunc (s *basicNFT) TokenURI(tid TokenID) (string, error) {\n\turi, found := s.tokenURIs.Get(string(tid))\n\tif !found {\n\t\treturn \"\", ErrInvalidTokenId\n\t}\n\n\treturn uri.(string), nil\n}\n\nfunc (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {\n\t// check for invalid TokenID\n\tif !s.exists(tid) {\n\t\treturn false, ErrInvalidTokenId\n\t}\n\n\t// check for the right owner\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn false, ErrCallerIsNotOwner\n\t}\n\ts.tokenURIs.Set(string(tid), string(tURI))\n\treturn true, nil\n}\n\n// IsApprovedForAll returns true if operator is approved for all by the owner.\n// Otherwise, returns false\nfunc (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {\n\tkey := owner.String() + \":\" + operator.String()\n\t_, found := s.operatorApprovals.Get(key)\n\tif !found {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Approve approves the input address for particular token\nfunc (s *basicNFT) Approve(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner == to {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner \u0026\u0026 !s.IsApprovedForAll(owner, caller) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\ts.tokenApprovals.Set(string(tid), to.String())\n\tevent := ApprovalEvent{owner, to, tid}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// GetApproved return the approved address for token\nfunc (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {\n\taddr, found := s.tokenApprovals.Get(string(tid))\n\tif !found {\n\t\treturn zeroAddress, ErrTokenIdNotHasApproved\n\t}\n\n\treturn std.Address(addr.(string)), nil\n}\n\n// SetApprovalForAll can approve the operator to operate on all tokens\nfunc (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {\n\tif err := isValidAddress(operator); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\treturn s.setApprovalForAll(caller, operator, approved)\n}\n\n// Safely transfers `tokenId` token from `from` to `to`, checking that\n// contract recipients are aware of the GRC721 protocol to prevent\n// tokens from being forever locked.\nfunc (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(from, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\n// Transfers `tokenId` token from `from` to `to`.\nfunc (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {\n\tcaller := std.PrevRealm().Addr()\n\tif !s.isApprovedOrOwner(caller, tid) {\n\t\treturn ErrCallerIsNotOwnerOrApproved\n\t}\n\n\terr := s.transfer(from, to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Mints `tokenId` and transfers it to `to`.\nfunc (s *basicNFT) Mint(to std.Address, tid TokenID) error {\n\treturn s.mint(to, tid)\n}\n\n// Mints `tokenId` and transfers it to `to`. Also checks that\n// contract recipients are using GRC721 protocol\nfunc (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {\n\terr := s.mint(to, tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !s.checkOnGRC721Received(zeroAddress, to, tid) {\n\t\treturn ErrTransferToNonGRC721Receiver\n\t}\n\n\treturn nil\n}\n\nfunc (s *basicNFT) Burn(tid TokenID) error {\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.beforeTokenTransfer(owner, zeroAddress, tid, 1)\n\n\ts.tokenApprovals.Remove(string(tid))\n\tbalance, err := s.BalanceOf(owner)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbalance -= 1\n\ts.balances.Set(owner.String(), balance)\n\ts.owners.Remove(string(tid))\n\n\tevent := TransferEvent{owner, zeroAddress, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(owner, zeroAddress, tid, 1)\n\n\treturn nil\n}\n\n/* Helper methods */\n\n// Helper for SetApprovalForAll()\nfunc (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {\n\tif owner == operator {\n\t\treturn ErrApprovalToCurrentOwner\n\t}\n\n\tkey := owner.String() + \":\" + operator.String()\n\ts.operatorApprovals.Set(key, approved)\n\n\tevent := ApprovalForAllEvent{owner, operator, approved}\n\temit(\u0026event)\n\n\treturn nil\n}\n\n// Helper for TransferFrom() and SafeTransferFrom()\nfunc (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {\n\tif err := isValidAddress(from); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\tif err := isValidAddress(to); err != nil {\n\t\treturn ErrInvalidAddress\n\t}\n\n\tif from == to {\n\t\treturn ErrCannotTransferToSelf\n\t}\n\n\towner, err := s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.beforeTokenTransfer(from, to, tid, 1)\n\n\t// Check that tokenId was not transferred by `beforeTokenTransfer`\n\towner, err = s.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif owner != from {\n\t\treturn ErrTransferFromIncorrectOwner\n\t}\n\n\ts.tokenApprovals.Remove(string(tid))\n\tfromBalance, err := s.BalanceOf(from)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfromBalance -= 1\n\ttoBalance += 1\n\ts.balances.Set(from.String(), fromBalance)\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{from, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(from, to, tid, 1)\n\n\treturn nil\n}\n\n// Helper for Mint() and SafeMint()\nfunc (s *basicNFT) mint(to std.Address, tid TokenID) error {\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check that tokenId was not minted by `beforeTokenTransfer`\n\tif s.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ttoBalance, err := s.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.balances.Set(to.String(), toBalance)\n\ts.owners.Set(string(tid), to)\n\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n\nfunc (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {\n\towner, found := s.owners.Get(string(tid))\n\tif !found {\n\t\treturn false\n\t}\n\n\tif addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {\n\t\treturn true\n\t}\n\n\t_, err := s.GetApproved(tid)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// Checks if token id already exists\nfunc (s *basicNFT) exists(tid TokenID) bool {\n\t_, found := s.owners.Get(string(tid))\n\treturn found\n}\n\nfunc (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {\n\t// TODO: Implementation\n}\n\nfunc (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {\n\t// TODO: Implementation\n\treturn true\n}\n\nfunc (s *basicNFT) RenderHome() (str string) {\n\tstr += ufmt.Sprintf(\"# %s ($%s)\\n\\n\", s.name, s.symbol)\n\tstr += ufmt.Sprintf(\"* **Total supply**: %d\\n\", s.TokenCount())\n\tstr += ufmt.Sprintf(\"* **Known accounts**: %d\\n\", s.balances.Size())\n\n\treturn\n}\n"},{"name":"basic_nft_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/uassert\"\n)\n\nvar (\n\tdummyNFTName = \"DummyNFT\"\n\tdummyNFTSymbol = \"DNFT\"\n)\n\nfunc TestNewBasicNFT(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n}\n\nfunc TestName(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tname := dummy.Name()\n\tuassert.Equal(t, dummyNFTName, name)\n}\n\nfunc TestSymbol(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tsymbol := dummy.Symbol()\n\tuassert.Equal(t, dummyNFTSymbol, symbol)\n}\n\nfunc TestTokenCount(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcount := dummy.TokenCount()\n\tuassert.Equal(t, uint64(0), count)\n\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"1\"))\n\tdummy.mint(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\", TokenID(\"2\"))\n\n\tcount = dummy.TokenCount()\n\tuassert.Equal(t, uint64(2), count)\n}\n\nfunc TestBalanceOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tbalanceAddr1, err := dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(0), balanceAddr1)\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr1, TokenID(\"2\"))\n\tdummy.mint(addr2, TokenID(\"3\"))\n\n\tbalanceAddr1, err = dummy.BalanceOf(addr1)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tbalanceAddr2, err := dummy.BalanceOf(addr2)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tuassert.Equal(t, uint64(2), balanceAddr1)\n\tuassert.Equal(t, uint64(1), balanceAddr2)\n}\n\nfunc TestOwnerOf(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\towner, err := dummy.OwnerOf(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\tdummy.mint(addr2, TokenID(\"2\"))\n\n\t// Checking for token id \"1\"\n\towner, err = dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n\n\t// Checking for token id \"2\"\n\towner, err = dummy.OwnerOf(TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr2.String(), owner.String())\n}\n\nfunc TestIsApprovedForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(addr1, addr2)\n\tuassert.False(t, isApprovedForAll)\n}\n\nfunc TestSetApprovalForAll(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tisApprovedForAll := dummy.IsApprovedForAll(caller, addr)\n\tuassert.False(t, isApprovedForAll)\n\n\terr := dummy.SetApprovalForAll(addr, true)\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tisApprovedForAll = dummy.IsApprovedForAll(caller, addr)\n\tuassert.True(t, isApprovedForAll)\n}\n\nfunc TestGetApproved(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"invalid\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestApprove(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\n\t_, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n\n\terr = dummy.Approve(addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\tapprovedAddr, err := dummy.GetApproved(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), approvedAddr.String())\n}\n\nfunc TestTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.TransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestSafeTransferFrom(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\tcaller := std.PrevRealm().Addr()\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(caller, TokenID(\"1\"))\n\tdummy.mint(caller, TokenID(\"2\"))\n\n\terr := dummy.SafeTransferFrom(caller, addr, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check balance of caller after transfer\n\tbalanceOfCaller, err := dummy.BalanceOf(caller)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfCaller)\n\n\t// Check balance of addr after transfer\n\tbalanceOfAddr, err := dummy.BalanceOf(addr)\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, uint64(1), balanceOfAddr)\n\n\t// Check Owner of transferred Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr.String(), owner.String())\n}\n\nfunc TestMint(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\n\terr := dummy.Mint(addr1, TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr1, TokenID(\"2\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\terr = dummy.Mint(addr2, TokenID(\"3\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Try minting duplicate token id\n\terr = dummy.Mint(addr2, TokenID(\"1\"))\n\tuassert.Error(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\tuassert.Equal(t, addr1.String(), owner.String())\n}\n\nfunc TestBurn(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\n\tdummy.mint(addr, TokenID(\"1\"))\n\tdummy.mint(addr, TokenID(\"2\"))\n\n\terr := dummy.Burn(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"should not result in error\")\n\n\t// Check Owner of Token id\n\towner, err := dummy.OwnerOf(TokenID(\"1\"))\n\tuassert.Error(t, err, \"should result in error\")\n}\n\nfunc TestSetTokenURI(t *testing.T) {\n\tdummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := std.Address(\"g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm\")\n\taddr2 := std.Address(\"g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj\")\n\ttokenURI := \"http://example.com/token\"\n\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\t_, derr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI))\n\tuassert.NoError(t, derr, \"should not result in error\")\n\n\t// Test case: Invalid token ID\n\t_, err := dummy.SetTokenURI(TokenID(\"3\"), TokenURI(tokenURI))\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(std.Address(addr2)) // addr2\n\n\t_, cerr := dummy.SetTokenURI(TokenID(\"1\"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Retrieving TokenURI\n\tstd.TestSetOrigCaller(std.Address(addr1)) // addr1\n\n\tdummyTokenURI, err := dummy.TokenURI(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"TokenURI error\")\n\tuassert.Equal(t, string(tokenURI), string(dummyTokenURI))\n}\n"},{"name":"errors.gno","body":"package grc721\n\nimport \"errors\"\n\nvar (\n\tErrInvalidTokenId = errors.New(\"invalid token id\")\n\tErrInvalidAddress = errors.New(\"invalid address\")\n\tErrTokenIdNotHasApproved = errors.New(\"token id not approved for anyone\")\n\tErrApprovalToCurrentOwner = errors.New(\"approval to current owner\")\n\tErrCallerIsNotOwner = errors.New(\"caller is not token owner\")\n\tErrCallerNotApprovedForAll = errors.New(\"caller is not approved for all\")\n\tErrCannotTransferToSelf = errors.New(\"cannot send transfer to self\")\n\tErrTransferFromIncorrectOwner = errors.New(\"transfer from incorrect owner\")\n\tErrTransferToNonGRC721Receiver = errors.New(\"transfer to non GRC721Receiver implementer\")\n\tErrCallerIsNotOwnerOrApproved = errors.New(\"caller is not token owner or approved\")\n\tErrTokenIdAlreadyExists = errors.New(\"token id already exists\")\n\n\t// ERC721Royalty\n\tErrInvalidRoyaltyPercentage = errors.New(\"invalid royalty percentage\")\n\tErrInvalidRoyaltyPaymentAddress = errors.New(\"invalid royalty paymentAddress\")\n\tErrCannotCalculateRoyaltyAmount = errors.New(\"cannot calculate royalty amount\")\n)\n"},{"name":"grc721_metadata.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// metadataNFT represents an NFT with metadata extensions.\ntype metadataNFT struct {\n\t*basicNFT // Embedded basicNFT struct for basic NFT functionality\n\textensions *avl.Tree // AVL tree for storing metadata extensions\n}\n\n// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface.\nvar _ IGRC721MetadataOnchain = (*metadataNFT)(nil)\n\n// NewNFTWithMetadata creates a new basic NFT with metadata extensions.\nfunc NewNFTWithMetadata(name string, symbol string) *metadataNFT {\n\t// Create a new basic NFT\n\tnft := NewBasicNFT(name, symbol)\n\n\t// Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions\n\treturn \u0026metadataNFT{\n\t\tbasicNFT: nft,\n\t\textensions: avl.NewTree(),\n\t}\n}\n\n// SetTokenMetadata sets metadata for a given token ID.\nfunc (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error {\n\t// Check if the caller is the owner of the token\n\towner, err := s.basicNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set the metadata for the token ID in the extensions AVL tree\n\ts.extensions.Set(string(tid), metadata)\n\treturn nil\n}\n\n// TokenMetadata retrieves metadata for a given token ID.\nfunc (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {\n\t// Retrieve metadata from the extensions AVL tree\n\tmetadata, found := s.extensions.Get(string(tid))\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidTokenId\n\t}\n\n\treturn metadata.(Metadata), nil\n}\n\n// mint mints a new token and assigns it to the specified address.\nfunc (s *metadataNFT) mint(to std.Address, tid TokenID) error {\n\t// Check if the address is valid\n\tif err := isValidAddress(to); err != nil {\n\t\treturn err\n\t}\n\n\t// Check if the token ID already exists\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\ts.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1)\n\n\t// Check if the token ID was minted by beforeTokenTransfer\n\tif s.basicNFT.exists(tid) {\n\t\treturn ErrTokenIdAlreadyExists\n\t}\n\n\t// Increment balance of the recipient address\n\ttoBalance, err := s.basicNFT.BalanceOf(to)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttoBalance += 1\n\ts.basicNFT.balances.Set(to.String(), toBalance)\n\n\t// Set owner of the token ID to the recipient address\n\ts.basicNFT.owners.Set(string(tid), to)\n\n\t// Emit transfer event\n\tevent := TransferEvent{zeroAddress, to, tid}\n\temit(\u0026event)\n\n\ts.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1)\n\n\treturn nil\n}\n"},{"name":"grc721_metadata_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetMetadata(t *testing.T) {\n\t// Create a new dummy NFT with metadata\n\tdummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol)\n\tif dummy == nil {\n\t\tt.Errorf(\"should not be nil\")\n\t}\n\n\t// Define addresses for testing purposes\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\t// Define metadata attributes\n\tname := \"test\"\n\tdescription := \"test\"\n\timage := \"test\"\n\timageData := \"test\"\n\texternalURL := \"test\"\n\tattributes := []Trait{}\n\tbackgroundColor := \"test\"\n\tanimationURL := \"test\"\n\tyoutubeURL := \"test\"\n\n\t// Set the original caller to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Mint a new token for addr1\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\t// Set metadata for token 1\n\tderr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if there was an error setting metadata\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenMetadata(TokenID(\"3\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, err, ErrInvalidTokenId)\n\n\t// Set the original caller to addr2\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\t// Try to set metadata for token 1 from addr2 (should fail)\n\tcerr := dummy.SetTokenMetadata(TokenID(\"1\"), Metadata{\n\t\tName: name,\n\t\tDescription: description,\n\t\tImage: image,\n\t\tImageData: imageData,\n\t\tExternalURL: externalURL,\n\t\tAttributes: attributes,\n\t\tBackgroundColor: backgroundColor,\n\t\tAnimationURL: animationURL,\n\t\tYoutubeURL: youtubeURL,\n\t})\n\n\t// Check if the error returned matches the expected error\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Set the original caller back to addr1\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\t// Retrieve metadata for token 1\n\tdummyMetadata, err := dummy.TokenMetadata(TokenID(\"1\"))\n\tuassert.NoError(t, err, \"Metadata error\")\n\n\t// Check if metadata attributes match expected values\n\tuassert.Equal(t, image, dummyMetadata.Image)\n\tuassert.Equal(t, imageData, dummyMetadata.ImageData)\n\tuassert.Equal(t, externalURL, dummyMetadata.ExternalURL)\n\tuassert.Equal(t, description, dummyMetadata.Description)\n\tuassert.Equal(t, name, dummyMetadata.Name)\n\tuassert.Equal(t, len(attributes), len(dummyMetadata.Attributes))\n\tuassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor)\n\tuassert.Equal(t, animationURL, dummyMetadata.AnimationURL)\n\tuassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL)\n}\n"},{"name":"grc721_royalty.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\n// royaltyNFT represents a non-fungible token (NFT) with royalty functionality.\ntype royaltyNFT struct {\n\t*metadataNFT // Embedding metadataNFT for NFT functionality\n\ttokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token\n\tmaxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale\n}\n\n// Ensure that royaltyNFT implements the IGRC2981 interface.\nvar _ IGRC2981 = (*royaltyNFT)(nil)\n\n// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator.\nfunc NewNFTWithRoyalty(name string, symbol string) *royaltyNFT {\n\t// Create a new NFT with metadata\n\tnft := NewNFTWithMetadata(name, symbol)\n\n\treturn \u0026royaltyNFT{\n\t\tmetadataNFT: nft,\n\t\ttokenRoyaltyInfo: avl.NewTree(),\n\t\tmaxRoyaltyPercentage: 100,\n\t}\n}\n\n// SetTokenRoyalty sets the royalty information for a specific token ID.\nfunc (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error {\n\t// Validate the payment address\n\tif err := isValidAddress(royaltyInfo.PaymentAddress); err != nil {\n\t\treturn ErrInvalidRoyaltyPaymentAddress\n\t}\n\n\t// Check if royalty percentage exceeds maxRoyaltyPercentage\n\tif royaltyInfo.Percentage \u003e r.maxRoyaltyPercentage {\n\t\treturn ErrInvalidRoyaltyPercentage\n\t}\n\n\t// Check if the caller is the owner of the token\n\towner, err := r.metadataNFT.OwnerOf(tid)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrCallerIsNotOwner\n\t}\n\n\t// Set royalty information for the token\n\tr.tokenRoyaltyInfo.Set(string(tid), royaltyInfo)\n\n\treturn nil\n}\n\n// RoyaltyInfo returns the royalty information for the given token ID and sale price.\nfunc (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) {\n\t// Retrieve royalty information for the token\n\tval, found := r.tokenRoyaltyInfo.Get(string(tid))\n\tif !found {\n\t\treturn \"\", 0, ErrInvalidTokenId\n\t}\n\n\troyaltyInfo := val.(RoyaltyInfo)\n\n\t// Calculate royalty amount\n\troyaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage)\n\n\treturn royaltyInfo.PaymentAddress, royaltyAmount, nil\n}\n\nfunc (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) {\n\troyaltyAmount := (salePrice * percentage) / 100\n\treturn royaltyAmount, nil\n}\n"},{"name":"grc721_royalty_test.gno","body":"package grc721\n\nimport (\n\t\"std\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/uassert\"\n)\n\nfunc TestSetTokenRoyalty(t *testing.T) {\n\tdummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol)\n\tuassert.True(t, dummy != nil, \"should not be nil\")\n\n\taddr1 := testutils.TestAddress(\"alice\")\n\taddr2 := testutils.TestAddress(\"bob\")\n\n\tpaymentAddress := testutils.TestAddress(\"john\")\n\tpercentage := uint64(10) // 10%\n\n\tsalePrice := uint64(1000)\n\texpectRoyaltyAmount := uint64(100)\n\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummy.mint(addr1, TokenID(\"1\"))\n\n\tderr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.NoError(t, derr, \"Should not result in error\")\n\n\t// Test case: Invalid token ID\n\terr := dummy.SetTokenRoyalty(TokenID(\"3\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, derr, ErrInvalidTokenId)\n\n\tstd.TestSetOrigCaller(addr2) // addr2\n\n\tcerr := dummy.SetTokenRoyalty(TokenID(\"1\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, cerr, ErrCallerIsNotOwner)\n\n\t// Test case: Invalid payment address\n\taerr := dummy.SetTokenRoyalty(TokenID(\"4\"), RoyaltyInfo{\n\t\tPaymentAddress: std.Address(\"###\"), // invalid address\n\t\tPercentage: percentage,\n\t})\n\tuassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress)\n\n\t// Test case: Invalid percentage\n\tperr := dummy.SetTokenRoyalty(TokenID(\"5\"), RoyaltyInfo{\n\t\tPaymentAddress: paymentAddress,\n\t\tPercentage: uint64(200), // over maxRoyaltyPercentage\n\t})\n\tuassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage)\n\n\t// Test case: Retrieving Royalty Info\n\tstd.TestSetOrigCaller(addr1) // addr1\n\n\tdummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID(\"1\"), salePrice)\n\tuassert.NoError(t, rerr, \"RoyaltyInfo error\")\n\tuassert.Equal(t, paymentAddress, dummyPaymentAddress)\n\tuassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount)\n}\n"},{"name":"igrc721.gno","body":"package grc721\n\nimport \"std\"\n\ntype IGRC721 interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(tid TokenID) (std.Address, error)\n\tSetTokenURI(tid TokenID, tURI TokenURI) (bool, error)\n\tSafeTransferFrom(from, to std.Address, tid TokenID) error\n\tTransferFrom(from, to std.Address, tid TokenID) error\n\tApprove(approved std.Address, tid TokenID) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(tid TokenID) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, tid TokenID) error\n}\n\ntype (\n\tTokenID string\n\tTokenURI string\n)\n\ntype TransferEvent struct {\n\tFrom std.Address\n\tTo std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalEvent struct {\n\tOwner std.Address\n\tApproved std.Address\n\tTokenID TokenID\n}\n\ntype ApprovalForAllEvent struct {\n\tOwner std.Address\n\tOperator std.Address\n\tApproved bool\n}\n"},{"name":"igrc721_metadata.gno","body":"package grc721\n\n// IGRC721CollectionMetadata describes basic information about an NFT collection.\ntype IGRC721CollectionMetadata interface {\n\tName() string // Name returns the name of the collection.\n\tSymbol() string // Symbol returns the symbol of the collection.\n}\n\n// IGRC721Metadata follows the Ethereum standard\ntype IGRC721Metadata interface {\n\tIGRC721CollectionMetadata\n\tTokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token.\n}\n\n// IGRC721Metadata follows the OpenSea metadata standard\ntype IGRC721MetadataOnchain interface {\n\tIGRC721CollectionMetadata\n\tTokenMetadata(tid TokenID) (Metadata, error)\n}\n\ntype Trait struct {\n\tDisplayType string\n\tTraitType string\n\tValue string\n}\n\n// see: https://docs.opensea.io/docs/metadata-standards\ntype Metadata struct {\n\tImage string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image.\n\tImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter.\n\tExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site.\n\tDescription string // Human-readable description of the item. Markdown is supported.\n\tName string // Name of the item.\n\tAttributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item.\n\tBackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended #\n\tAnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported.\n\tYoutubeURL string // URL to a YouTube video (only used if animation_url is not provided).\n}\n"},{"name":"igrc721_royalty.gno","body":"package grc721\n\nimport \"std\"\n\n// IGRC2981 follows the Ethereum standard\ntype IGRC2981 interface {\n\t// RoyaltyInfo retrieves royalty information for a tokenID and salePrice.\n\t// It returns the payment address, royalty amount, and an error if any.\n\tRoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error)\n}\n\n// RoyaltyInfo represents royalty information for a token.\ntype RoyaltyInfo struct {\n\tPaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent.\n\tPercentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 =\u003e 10%\n}\n"},{"name":"util.gno","body":"package grc721\n\nimport (\n\t\"std\"\n)\n\nvar zeroAddress = std.Address(\"\")\n\nfunc isValidAddress(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn ErrInvalidAddress\n\t}\n\treturn nil\n}\n\nfunc emit(event interface{}) {\n\t// TODO: setup a pubsub system here?\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"Q5WajEmGeCOLoD2rmUDbo0GEh2htFWt4A+5rLVzJT1YNPBXob84LBUIodo4tAv8RTWiR0GWZFbyDf6VKbmb0JA=="}],"memo":""},"blockNum":"1865278"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"domain","path":"gno.land/p/varmeta/demo/v36/domain","files":[{"name":"domain_metadata.gno","body":"package domain\n\nimport (\n\t\"time\"\n)\n\n// Trait represents a key-value pair with an optional display type for metadata attributes\ntype Trait struct {\n\tDisplayType string // Optional display type (e.g., \"date\", \"number\", etc.)\n\tTraitType string // Type of the trait (e.g., \"age\", \"height\", etc.)\n\tValue string // Value of the trait\n}\n\n// Metadata represents the metadata associated with a domain\ntype Metadata struct {\n\tAvatar string // URL or identifier for an avatar image\n\tRegistrationTime time.Time // The time when the domain was registered\n\tExpirationTime time.Time // The time when the domain will be expire\n\tAttributes []Trait // Additional attributes of the domain\n\tDescription string // A description of the domain\n\tContactInfo string // Contact information for the domain owner\n\tRenewalFee string // The fee required to renew the domain, represented as a string\n}\n\n// NewMetadata creates a new Metadata instance\nfunc NewMetadata(avatar, description, contactInfo, renewalFee string,\n\tregistrationTime, expirationTime time.Time, attributes []Trait,\n) Metadata {\n\treturn Metadata{\n\t\tAvatar: avatar,\n\t\tRegistrationTime: registrationTime,\n\t\tExpirationTime: expirationTime,\n\t\tRenewalFee: renewalFee,\n\t\tAttributes: attributes,\n\t\tDescription: description,\n\t\tContactInfo: contactInfo,\n\t}\n}\n"},{"name":"domain_registry.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v36/grc/grc721\"\n)\n\n// domainRegistry represents a registry for domain names with metadata\ntype domainRegistry struct {\n\tdomains grc721.IGRC721 // Interface for basic NFT functionality\n\tmetadata *avl.Tree // AVL tree for storing domain metadata\n\texpDate time.Time\n}\n\n// DomainRegistry defines the methods for managing domain names and metadata\ntype DomainRegistry interface {\n\tBalanceOf(owner std.Address) (uint64, error)\n\tOwnerOf(domainName string) (std.Address, error)\n\tSafeTransferFrom(from, to std.Address, domainName string) error\n\tTransferFrom(from, to std.Address, domainName string) error\n\tApprove(approved std.Address, domainName string) error\n\tSetApprovalForAll(operator std.Address, approved bool) error\n\tGetApproved(domainName string) (std.Address, error)\n\tIsApprovedForAll(owner, operator std.Address) bool\n\tMint(to std.Address, domainName string) error\n\n\tRegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error\n\tSetDomainData(domainName string, metadata Metadata) error\n\tGetDomainData(domainName string, field MetadataField) (Metadata, error)\n\tGetDomainFields(domainName string, fields []MetadataField) (Metadata, error)\n\tRenewDomain(domainName string, additionalDuration time.Duration) error\n\tGetExpirationDate(domainName string) time.Time\n\tSetExpirationDate(domainName string, expDate time.Time) bool\n}\n\n// NewDomainRegistry creates a new domain registry with metadata extensions\nfunc NewDomainRegistry(name, symbol string) *domainRegistry {\n\tregistry := grc721.NewBasicNFT(name, symbol)\n\n\treturn \u0026domainRegistry{\n\t\tdomains: registry,\n\t\tmetadata: avl.NewTree(),\n\t}\n}\n\n// RegisterDomain registers a new domain with the given metadata\nfunc (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error {\n\terr := d.domains.Mint(owner, grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\td.expDate = time.Now().Add(dur)\n\td.metadata.Set(domainName, metadata)\n\n\treturn nil\n}\n\n// RenewDomain extends the expiration time of a domain name\nfunc (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn ErrInvalidDomainName\n\t}\n\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\t// set new expiration date\n\td.expDate = d.expDate.Add(additionalDuration)\n\treturn nil\n}\n\n// SetDomainData sets the metadata for a given domain name\nfunc (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error {\n\towner, err := d.domains.OwnerOf(grc721.TokenID(domainName))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcaller := std.PrevRealm().Addr()\n\tif caller != owner {\n\t\treturn ErrUnauthorized\n\t}\n\n\td.metadata.Set(domainName, metadata)\n\treturn nil\n}\n\n// GetDomainFields retrieves multiple fields of metadata for a given domain\nfunc (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tif len(fields) == 0 {\n\t\treturn metadata, nil\n\t}\n\n\tvar result Metadata\n\tfor _, field := range fields {\n\t\tswitch field {\n\t\tcase FieldAvatar:\n\t\t\tresult.Avatar = metadata.Avatar\n\t\tcase FieldRegistrationTime:\n\t\t\tresult.RegistrationTime = metadata.RegistrationTime\n\t\tcase FieldExpirationTime:\n\t\t\tresult.ExpirationTime = metadata.ExpirationTime\n\t\tcase FieldRenewalFee:\n\t\t\tresult.RenewalFee = metadata.RenewalFee\n\t\tcase FieldAttributes:\n\t\t\tresult.Attributes = metadata.Attributes\n\t\tcase FieldDescription:\n\t\t\tresult.Description = metadata.Description\n\t\tcase FieldContactInfo:\n\t\t\tresult.ContactInfo = metadata.ContactInfo\n\t\tdefault:\n\t\t\treturn Metadata{}, ErrInvalidMetadataField\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// GetDomainData retrieves metadata for a given domain\nfunc (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) {\n\tdata, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn Metadata{}, ErrInvalidDomainName\n\t}\n\n\tmetadata := data.(Metadata)\n\n\tswitch field {\n\tcase FieldAvatar:\n\t\treturn Metadata{\n\t\t\tAvatar: metadata.Avatar,\n\t\t}, nil\n\tcase FieldRegistrationTime:\n\t\treturn Metadata{\n\t\t\tRegistrationTime: metadata.RegistrationTime,\n\t\t}, nil\n\tcase FieldExpirationTime:\n\t\treturn Metadata{\n\t\t\tExpirationTime: metadata.ExpirationTime,\n\t\t}, nil\n\tcase FieldRenewalFee:\n\t\treturn Metadata{\n\t\t\tRenewalFee: metadata.RenewalFee,\n\t\t}, nil\n\tcase FieldAttributes:\n\t\treturn Metadata{\n\t\t\tAttributes: metadata.Attributes,\n\t\t}, nil\n\tcase FieldDescription:\n\t\treturn Metadata{\n\t\t\tDescription: metadata.Description,\n\t\t}, nil\n\tcase FieldContactInfo:\n\t\treturn Metadata{\n\t\t\tContactInfo: metadata.ContactInfo,\n\t\t}, nil\n\tdefault:\n\t\treturn Metadata{}, ErrInvalidMetadataField\n\t}\n}\n\n// BalanceOf returns the number of domains owned by a given address\nfunc (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) {\n\treturn d.domains.BalanceOf(owner)\n}\n\n// OwnerOf returns the owner of a given domain name\nfunc (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) {\n\treturn d.domains.OwnerOf(grc721.TokenID(domainName))\n}\n\n// SafeTransferFrom safely transfers a domain from one address to another\nfunc (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// TransferFrom transfers a domain from one address to another\nfunc (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error {\n\treturn d.domains.TransferFrom(from, to, grc721.TokenID(domainName))\n}\n\n// Approve grants approval to another address to manage a specific domain\nfunc (d *domainRegistry) Approve(approved std.Address, domainName string) error {\n\treturn d.domains.Approve(approved, grc721.TokenID(domainName))\n}\n\n// SetApprovalForAll sets approval for an operator to manage all domains of the owner\nfunc (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error {\n\treturn d.domains.SetApprovalForAll(operator, approved)\n}\n\n// GetApproved returns the approved address for a specific domain\nfunc (d *domainRegistry) GetApproved(domainName string) (std.Address, error) {\n\treturn d.domains.GetApproved(grc721.TokenID(domainName))\n}\n\n// IsApprovedForAll checks if an operator is approved to manage all domains of the owner\nfunc (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool {\n\treturn d.domains.IsApprovedForAll(owner, operator)\n}\n\n// Mint creates a new domain for a given address\nfunc (d *domainRegistry) Mint(to std.Address, domainName string) error {\n\treturn d.domains.Mint(to, grc721.TokenID(domainName))\n}\n\nfunc (d *domainRegistry) GetExpirationDate(domainName string) time.Time {\n\treturn d.expDate\n}\n\nfunc (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool {\n\t_, found := d.metadata.Get(domainName)\n\tif !found {\n\t\treturn false\n\t}\n\td.expDate = expDate\n\treturn true\n}\n"},{"name":"domain_registry_test.gno","body":"package domain\n\nimport (\n\t\"std\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gno.land/p/demo/testutils\"\n\t\"gno.land/p/demo/urequire\"\n\t\"gno.land/p/varmeta/demo/v36/grc/grc721\"\n)\n\nvar (\n\taddr1 = testutils.TestAddress(\"bob\")\n\taddr2 = testutils.TestAddress(\"alice\")\n)\n\nfunc TestRegisterDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"contact@registered.com\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Duplicate Registration\",\n\t\t\towner: addr1,\n\t\t\tdomainName: \"registered.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A registered domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\terr := registry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.owner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tmetadata Metadata\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Sets Metadata\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"test.gno\",\n\t\t\tmetadata: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, c.metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\t\t\terr := registry.SetDomainData(c.domainName, c.metadata)\n\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\tretrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRenewDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\tadditionalTime time.Duration\n\t\texpectError bool\n\t\texpectedExpiry time.Time\n\t}{\n\t\t{\n\t\t\tname: \"Successful Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Renewal\",\n\t\t\towner: addr1,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"renewable.gno\",\n\t\t\tadditionalTime: 30 * 24 * time.Hour,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A renewable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.RenewDomain(c.domainName, c.additionalTime)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrUnauthorized.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\trenewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\t// urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainData(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfield MetadataField\n\t\texpectError bool\n\t\texpectedVal string\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Avatar\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: false,\n\t\t\texpectedVal: \"avatar_url\",\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain Name\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfield: FieldAvatar,\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t_, err := registry.GetDomainData(c.domainName, c.field)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetDomainFields(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\tdomainName string\n\t\tfields []MetadataField\n\t\texpectError bool\n\t\texpected Metadata\n\t}{\n\t\t{\n\t\t\tname: \"Retrieve Multiple Fields\",\n\t\t\tdomainName: \"test.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo},\n\t\t\texpectError: false,\n\t\t\texpected: Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tDescription: \"A test domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid Domain\",\n\t\t\tdomainName: \"invalid.gno\",\n\t\t\tfields: []MetadataField{FieldAvatar},\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tif !c.expectError {\n\t\t\t\towner := addr1\n\t\t\t\tstd.TestSetRealm(std.NewUserRealm(owner))\n\t\t\t\tstd.TestSetOrigCaller(owner)\n\n\t\t\t\tmetadata := Metadata{\n\t\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\t\tDescription: \"A test domain\",\n\t\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t\t}\n\n\t\t\t\terr := registry.RegisterDomain(owner, c.domainName, metadata)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tretrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), ErrInvalidDomainName.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar)\n\t\t\t\turequire.Equal(t, c.expected.Description, retrievedMetadata.Description)\n\t\t\t\turequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTransferDomain(t *testing.T) {\n\tregistry := NewDomainRegistry(\"GNO Name Service\", \"GNS\")\n\n\tcases := []struct {\n\t\tname string\n\t\towner std.Address\n\t\tnewOwner std.Address\n\t\tcaller std.Address\n\t\tdomainName string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"Successful Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr1,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Non-Owner Attempts Transfer\",\n\t\t\towner: addr1,\n\t\t\tnewOwner: addr2,\n\t\t\tcaller: addr2,\n\t\t\tdomainName: \"transfer.gno\",\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.owner))\n\t\t\tstd.TestSetOrigCaller(c.owner)\n\n\t\t\tmetadata := Metadata{\n\t\t\t\tAvatar: \"avatar_url\",\n\t\t\t\tRegistrationTime: time.Now(),\n\t\t\t\tExpirationTime: time.Now().Add(365 * 24 * time.Hour),\n\t\t\t\tDescription: \"A transferable domain\",\n\t\t\t\tContactInfo: \"gno_name_service@gno.land\",\n\t\t\t}\n\n\t\t\tregistry.RegisterDomain(c.owner, c.domainName, metadata)\n\n\t\t\tstd.TestSetRealm(std.NewUserRealm(c.caller))\n\t\t\tstd.TestSetOrigCaller(c.caller)\n\n\t\t\terr := registry.TransferFrom(c.owner, c.newOwner, c.domainName)\n\t\t\tif c.expectError {\n\t\t\t\turequire.Error(t, err)\n\t\t\t\turequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error())\n\t\t\t} else {\n\t\t\t\turequire.NoError(t, err)\n\n\t\t\t\tretrievedOwner, err := registry.OwnerOf(c.domainName)\n\t\t\t\turequire.NoError(t, err)\n\t\t\t\turequire.Equal(t, c.newOwner, retrievedOwner)\n\t\t\t}\n\t\t})\n\t}\n}\n"},{"name":"errors.gno","body":"package domain\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnauthorized = errors.New(\"caller is not domain owner\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name\")\n\tErrInvalidMetadataField = errors.New(\"invalid metadata field\")\n\tErrInsufficientFunds = errors.New(\"insufficient funds for renewal\")\n)\n"},{"name":"utils.gno","body":"package domain\n\ntype MetadataField int\n\nconst (\n\tFieldAvatar MetadataField = iota\n\tFieldRegistrationTime\n\tFieldRenewalFee\n\tFieldExpirationTime\n\tFieldAttributes\n\tFieldDescription\n\tFieldContactInfo\n)\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"jeHrYEAQVg9ikVDY02V+xbTvpHzaD9H8BDWNZ5yGBI0Gc0rAvOuapBD3iJWkjK0oWq6M983TyMiE1o1QmS07JA=="}],"memo":""},"blockNum":"1865283"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"registrar","path":"gno.land/r/varmeta/demo/v36/domain/registrar","files":[{"name":"bidding.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\t// \"gno.land/p/demo/mux\"\n\t\"gno.land/p/demo/avl\"\n)\n\ntype bidRecord struct {\n\tDomainName string\n\tBidder std.Address\n\tHashString string\n\tPrice int\n\tStartTime time.Time\n\tEndCommitTime time.Time\n\tEndPriceTime time.Time\n\tCurrentPhase actionCode\n\tIsOpen bool\n}\n\nvar (\n\tBidRec *avl.Tree // bidRecord \u003c- []bidRec\n\twinnerRec *avl.Tree\n\tjoinedBid avl.Tree\n)\n\n// joinedBid: address \u003c- []domainName\nfunc recordJoinedBid(domainName string) {\n\tcaller := std.GetOrigCaller()\n\tdList := []string{}\n\tdata, existed := joinedBid.Get(caller.String())\n\tif !existed {\n\t\tdList = []string{domainName}\n\t\tjoinedBid.Set(caller.String(), dList)\n\t\treturn\n\t}\n\tdList = data.([]string)\n\tdList = append(dList, domainName)\n\tjoinedBid.Set(caller.String(), dList)\n\treturn\n}\n\n// get the joined bids of an account\n// want to return both name and status\nfunc GetJoinedBid() []singleStatus {\n\tcaller := std.GetOrigCaller().String()\n\tdata, existed := joinedBid.Get(caller)\n\tif !existed {\n\t\treturn []singleStatus{}\n\t}\n\tlist := data.([]string)\n\tlistStatus := []singleStatus{}\n\tfor _, dName := range list {\n\t\tstt := GetCurrentStatus(dName)\n\t\tsingleStt := singleStatus{\n\t\t\tDomainName: dName,\n\t\t\tStatus: stt,\n\t\t}\n\t\tlistStatus = append(listStatus, singleStt)\n\t}\n\treturn listStatus\n}\n\n// get the next state of bidding session\n// XXX note commit price status + if time is expired and not commited yet\n// status clear\nfunc GetCurrentStatus(domainName string) string {\n\t// if there is record in joinedBid -\u003e user joined\n\t// check for tine.Now() and startTime\n\tnow := time.Now()\n\towner := GetOwner(domainName)\n\tif owner != \"\" {\n\t\treturn \"owned by \" + owner.String()\n\t}\n\tcaller := std.GetOrigCaller()\n\t// find the record\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\t// no record in bidRec yet -\u003e not commited -\u003e check if user started auction or not - if yes: new auction\n\t\tif _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister {\n\t\t\treturn \"new auction\"\n\t\t}\n\t\treturn \"domain name is free\"\n\t}\n\n\t// commited yet\n\trecList := data.([]bidRecord)\n\trec := recList[0]\n\n\tif now.Before(rec.EndCommitTime) {\n\t\tif rec.HashString != \"\" {\n\t\t\treturn \"commited hash\"\n\t\t} else {\n\t\t\treturn \"hash\"\n\t\t}\n\t}\n\n\tif now.Before(rec.EndPriceTime) \u0026\u0026 now.After(rec.EndCommitTime) {\n\t\treturn \"price\"\n\t}\n\tif now.After(rec.EndPriceTime) {\n\t\treturn \"closed\"\n\t}\n\treturn \"undefined\"\n}\n\n// Render() renders welcome message :D\nfunc Render(path string) string {\n\treturn \"welcome to varmeta domain name service\"\n}\n\n/*\n\tlogic should be:\n\t1. User click register for a dns.\n\t-\u003e 1.1: dns does not exists -\u003e open bidding session\n\t-\u003e 1.2: dns existed -\u003e check if open bidding session is openning or not -\u003e if open -\u003e new bidRecord\n\t2. Bidding session:\n\t-\u003e 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -\u003e creat bidRecord to add to bidRec\n\t-\u003e 2.2: CommitPrice phase: commit price + secret key: -\u003e if matches -\u003e write price into bidRec ( send the amunt of coin corresponding)\n\t-\u003e 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button\n\t---\u003e admin end bidding auction\n\t-\u003e 2.3.1: cooldown phase -\u003e waiting for winner -\u003e 2nd winner\n\t-\u003e 2.4: re-transfer coin from admin to user who not win the bid\n\t\tthe flow is: user want to register new domain name -\u003e start new bidding session -\u003e got the bidding session id\n\t\tif other want to register to the same domain name -\u003e bidding into this session, if not return to new state\n\t\twhen the session ends, need to have 2nd phase called commit -\u003e commit their price and secret to compute the hash\n\t\t-\u003e compare the hash as in record -\u003e accept the hash -\u003e accept the price\n\t\t*** temp approach: everytime user commit, user open a bid session if not existed, if existed -\u003e check if this session is ended.\n*/\n// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate\n// the time whenever there is action to commit.\n\n// commit the domain name and computed hash string\nfunc CommitHash(domainName, hashString string) string {\n\tcaller := std.GetOrigCaller()\n\tufmt.Println(\"caller: \", caller.String())\n\tnow := time.Now()\n\n\t// update the bid record\n\tdata, existed := BidRec.Get(domainName)\n\n\t// if not existed -\u003e create new record\n\tif !existed {\n\t\t// for further getStatus\n\t\trecordJoinedBid(domainName)\n\n\t\tvar bidRec bidRecord\n\t\tendCommitTime := now.Add(defaultCommitHashTime)\n\t\tendPriceTime := endCommitTime.Add(defaultCommitPriceTime)\n\t\tufmt.Println(\"endCommitTime: \", endCommitTime.Format(time.RFC3339))\n\t\tufmt.Println(\"endPriceTime: \", endPriceTime.Format(time.RFC3339))\n\t\tbidRec = bidRecord{\n\t\t\tDomainName: domainName,\n\t\t\tBidder: caller,\n\t\t\tHashString: hashString,\n\t\t\tStartTime: now,\n\t\t\tEndCommitTime: endCommitTime,\n\t\t\tEndPriceTime: endPriceTime,\n\t\t\tIsOpen: true,\n\t\t}\n\t\tbidRecList := []bidRecord{bidRec}\n\t\tBidRec.Set(domainName, bidRecList)\n\n\t\t// charge fee\n\t\tchargeFee(fee.BidJoinFee, caller)\n\t\treturn \"new session\"\n\t}\n\t// if existed\n\t// TODO: Check if commit more than 1 time -\u003e done\n\tbidRecList := data.([]bidRecord)\n\tstartTime := bidRecList[0].StartTime\n\tif now.After(bidRecList[0].EndCommitTime) {\n\t\tpanic(\"can not commit hash anymore\")\n\t}\n\tfor _, bR := range bidRecList {\n\t\tif bR.Bidder == caller {\n\t\t\tpanic(\"you already commited hash\")\n\t\t}\n\t}\n\n\toldEndCommitTime := bidRecList[0].EndCommitTime\n\toldEndPriceTime := bidRecList[0].EndPriceTime\n\tbidRec := bidRecord{\n\t\tDomainName: domainName,\n\t\tHashString: hashString,\n\t\tBidder: caller,\n\t\tStartTime: startTime,\n\t\tEndCommitTime: oldEndCommitTime,\n\t\tEndPriceTime: oldEndPriceTime,\n\t\tIsOpen: true,\n\t}\n\tbidRecList = append(bidRecList, bidRec)\n\t// Save record\n\tBidRec.Set(domainName, bidRecList)\n\t// charge commit hash fee\n\tchargeFee(fee.BidJoinFee, caller)\n\treturn \"existed\"\n}\n\n// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me\n// commit price and secret to reveal auction session\nfunc CommitPrice(price int, secret string, domainName string) string {\n\t// compute the hash string, compare to saved hash string in record\n\tnow := time.Now()\n\tjoinedString := secret + strconv.Itoa(price)\n\tcomputedHashString := Get256String(joinedString)\n\tufmt.Println(\"computed hash: \", computedHashString)\n\tcaller := std.GetOrigCaller()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\" domain name is invalid\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerRec.Set(domainName, bidRecList[0])\n\n\tufmt.Println(\"time now: \", now.Format(time.RFC3339))\n\tufmt.Println(\"end commit phase time: \", bidRecList[0].EndCommitTime.Format(time.RFC3339))\n\t// case commit after end - consider panic or not\n\tif now.After(bidRecList[0].EndPriceTime) {\n\t\tufmt.Println(\"commit price phase is ended\")\n\t\treturn \"ended\"\n\t}\n\t// case commit when price phase not started\n\tif time.Now().Before(bidRecList[0].EndCommitTime) {\n\t\tufmt.Println(\"commit price phase is not started yet\")\n\t\treturn \"not started yet\"\n\t}\n\n\t// search for the corresponding hash\n\tfor _, bidRec := range bidRecList {\n\t\t// panic because wrong price or wrong secret string\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString != computedHashString {\n\t\t\tpanic(\"invalid hash string\")\n\t\t}\n\t\t// found it, update the winner price\n\t\tif bidRec.Bidder == caller \u0026\u0026 bidRec.HashString == computedHashString {\n\t\t\tdata, _ := winnerRec.Get(domainName)\n\t\t\tcurrentWinnerRec := data.(bidRecord)\n\t\t\tif price \u003e currentWinnerRec.Price \u0026\u0026 now.Before(currentWinnerRec.EndPriceTime) {\n\t\t\t\tufmt.Println(\"found new winner, setting up\")\n\t\t\t\tcurrentWinnerRec.Price = price\n\t\t\t\tcurrentWinnerRec.Bidder = bidRec.Bidder\n\t\t\t\tcurrentWinnerRec.HashString = bidRec.HashString\n\t\t\t\twinnerRec.Set(domainName, currentWinnerRec)\n\t\t\t\treturn \"claim\"\n\t\t\t}\n\t\t}\n\t}\n\t// if not match above case, then panic\n\tpanic(\"commit failed\")\n}\n\nfunc GetCurrentWinner(domainName string) bidRecord {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"no winner yet\")\n\t}\n\treturn data.(bidRecord)\n}\n\n// find the highest bid in session - incase everyone commited price\nfunc findTheWinner(domainName string) bidRecord {\n\tvar winnerBid bidRecord\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\twinnerBid = bidRecList[0]\n\tfor _, bidRec := range bidRecList {\n\t\tif bidRec.Price \u003e winnerBid.Price {\n\t\t\twinnerBid = bidRec\n\t\t}\n\t}\n\treturn winnerBid\n}\n\n// register the domain for winner\nfunc registerForWinner(domainName string, winnerRec bidRecord) bool {\n\twinnerAddr := winnerRec.Bidder\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: winnerAddr,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tfeeProcess(requestInfo)\n\treturn false\n}\n\n// everyone can call EndBid()\n// this EndBid checks endTime -\u003e end the auction\nfunc EndBid(domainName string) error {\n\tnow := time.Now()\n\tdata, existed := BidRec.Get(domainName)\n\tif !existed {\n\t\treturn ufmt.Errorf(\"endbid: invalid domain name\")\n\t}\n\tbidRecList := data.([]bidRecord)\n\tfirstBidRec := bidRecList[0]\n\tif now.Before(firstBidRec.EndPriceTime) {\n\t\treturn ufmt.Errorf(\"endbid: this session can not end before the end time\")\n\t}\n\t// change all state\n\tfor _, bidRec := range bidRecList {\n\t\tbidRec.IsOpen = false\n\t}\n\tok := BidRec.Set(domainName, bidRecList)\n\tif !ok {\n\t\treturn ufmt.Errorf(\"endbid: can not change bid record state\")\n\t}\n\t// need more conditions for findTheWinner()\n\tfindTheWinner(domainName)\n\treturn nil\n}\n\n// register new domain with bidding process inside - if message is commit hash -\u003e dapp need to call commit hash\n// func RegisterDomain(domainName string) bidStatus {\n// \treqInfo := RequestInfo{\n// \t\tWantedDomain: domainName,\n// \t\tCaller: std.PrevRealm().Addr(),\n// \t\tMode: \"native\",\n// \t}\n\n// \t// bidStatus\n// \tresult := checkRegisterState(reqInfo)\n// \trecordJoinedBid(domainName)\n// \t// if checkState get the message of which phase this domain name belongs to, then return the status\n// \treturn result\n// }\n\n// func checkRegisterState(req RequestInfo) bidStatus {\n// \tvar bStt bidStatus\n// \t// check if domain name is regex valid\n// \tif !isValidDomain(req.WantedDomain) {\n// \t\tpanic(\"invalid domain name\")\n// \t}\n// \t// check if dName is registered\n// \tif AlreadyRegistered(req.WantedDomain) {\n// \t\tpanic(\"domain name already registered\")\n// \t}\n// \t// changelogs v2: we are using sealed bidding now\n// \t// check if a bidding session is openning -\u003e append new commit hash into record list\n// \t// both existed or not we open new bidding session\n// \t// for now we return a signal for dapps / service to know what to do next\n// \tisExisted, isOpen := checkBiddingState(req.WantedDomain)\n// \tif isExisted \u0026\u0026 isOpen {\n// \t\t// return commit hash signal for dapps\n// \t\tbStt.CurrentStatus = AppendBiddingSession\n// \t\tbStt.ActionCode = CommitHashPhase\n// \t\treturn bStt\n// \t}\n\n// \t// create new session\n// \tif !isExisted {\n// \t\tbStt.CurrentStatus = NewBiddingSession\n// \t\tbStt.ActionCode = CommitHashPhase\n// \t\treturn bStt\n// \t}\n\n// \t// not found in register repository but also not found in Bidding Record -\u003e panic(\"error somewhere :D\")\n// \tpanic(ufmt.Errorf(\"should not happend\"))\n// \treturn bStt\n// }\n\n// // open a bidding session\n// func openBiddingSession(domainName string, dur time.Duration) string {\n// \tnow := time.Now()\n// \tbidRec := bidRecord{\n// \t\tDomainName: domainName,\n// \t\tStartTime: now,\n// \t}\n// \tbidRecList := []bidRecord{bidRec}\n// \tok := BidRec.Set(domainName, bidRecList)\n// \tif !ok {\n// \t\tpanic(\"can not open bidding session\")\n// \t}\n// \treturn \"created bidding session\"\n// }\n\nfunc checkBiddingState(dName string) (isExisted bool, isOpen bool) {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tisExisted = false\n\t\tisOpen = false\n\t\treturn\n\t}\n\tisExisted = true\n\trecList := data.([]bidRecord)\n\tif recList[0].IsOpen {\n\t\tisOpen = true\n\t} else {\n\t\tisOpen = false\n\t}\n\treturn isExisted, isOpen\n}\n\n// get all the price list that joined the bid for displaying in dapp\nfunc GetRecords(dName string) []bidRecord {\n\tdata, existed := BidRec.Get(dName)\n\tif !existed {\n\t\tpanic(\"should not\")\n\t}\n\treturn data.([]bidRecord)\n}\n\n// chargeFee will charge amount - send from this contract to admin\nfunc chargeFee(amount int64, from std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", amount)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcoins := checkCoin(from)\n\tufmt.Println(\"check balances: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// pay fee and claim the domain name if you are winner\nfunc Claim(domainName string) bool {\n\tdata, existed := winnerRec.Get(domainName)\n\tif !existed {\n\t\tpanic(\"claim: invalid domain name\")\n\t}\n\tcaller := std.GetOrigCaller()\n\trec := data.(bidRecord)\n\tif caller != rec.Bidder {\n\t\tpanic(\"only winner can claim\")\n\t}\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: rec.Bidder,\n\t\tMode: \"native\",\n\t}\n\tresult := executeRegister(requestInfo)\n\tif !result.Success {\n\t\tpanic(result.ResultDetails.Error())\n\t}\n\t// register done. Now charge the fee\n\tchargeFee(100, caller)\n\tfeeProcess(requestInfo)\n\treturn true\n}\n"},{"name":"bidding_model.gno","body":"package registrar\n\n/*\n\t0: StatusFailed\n\tCurrentStatus:\t1: NewBiddingSession\n\t\t\t\t\t2: AppendBiddingSession\n\t\t\t\t\t3: BiddingSessionClosed\n\tActionCode:\t\t1: CommitHashPhase\n\t\t\t\t\t2: CommitPricePhase\n\t\t\t\t\t3: ClaimPhase...\n*/\n\ntype bidStatus struct {\n\tCurrentStatus currStatus\n\tActionCode actionCode\n}\n\ntype (\n\tcurrStatus int\n\tactionCode int\n)\n\nvar (\n\tStatusFailed currStatus = 0\n\tNewBiddingSession currStatus = 1\n\tAppendBiddingSession currStatus = 2\n\tBiddingSessionClosed currStatus = 3\n\n\tActionFailed actionCode = 0\n\tCommitHashPhase actionCode = 1\n\tCommitPricePhase actionCode = 2\n\tClaimPhase actionCode = 3\n)\n\ntype singleStatus struct {\n\tDomainName string\n\tStatus string\n}\n"},{"name":"errors.gno","body":"package registrar\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrUnknown = errors.New(\"unknow errors\")\n\tErrOK = errors.New(\"ok\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"ErrInvalidDomainName\")\n\tErrAlreadyRegistered = errors.New(\"this domain is registered\")\n\tErrCrossRealms = errors.New(\"cross realms function error\")\n\tErrNotFound = errors.New(\"domain not found\")\n)\n"},{"name":"fee.gno","body":"package registrar\n\nimport (\n\t\"time\"\n)\n\n// only admin can set Fee, other just can read only\ntype feeInfo struct {\n\tRegisterBaseFee int64\n\tRenewalFee int64\n\tRegisterAdditionFee int64\n\tBidJoinFee int64\n}\n\nfunc GetRegisterFee(dName string) int64 {\n\treturn fee.RegisterBaseFee\n}\n\nfunc GetRenewalFee(dName string, amount time.Duration) int64 {\n\treturn fee.RenewalFee\n}\n\n// Admin set register fee and renewal fee\nfunc AdminSetFee(regFee int64, renewFee int64) {\n\t// consider logic\n\tassertIsAdmin()\n\tfee.RegisterBaseFee = regFee\n\tfee.RenewalFee = renewFee\n}\n\n// simple err check\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"fee_checks.gno","body":"package registrar\n\n// import (\n// \t\"\"\n// \t// \"std\"\n// \t// \"time\"\n// )\n\n\n"},{"name":"fee_native.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n)\n\n// admin access only\nfunc AdminWithdraw(amount int64) {\n\tassertIsAdmin()\n\tthisContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tsuperBanker.SendCoins(thisContract, admin, coinsToTransfer)\n}\n\nfunc nativeProcess() {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToTransfer := std.NewCoins(ugnotCoin)\n\tcaller := std.GetOrigCaller()\n\tcoins := checkCoin(caller)\n\tufmt.Println(\"check: \", coins)\n\tufmt.Println(\"send from contract \", bankerContract.String(), \" to admin \", admin.String(), \" amount: \", ugnotCoin)\n\tbankerUser.SendCoins(bankerContract, admin, coinsToTransfer)\n}\n\n// RevertTransfer will revert the transaction - send amount of coin to user\nfunc revertTransfer(userAddr std.Address) {\n\tbankerContract := std.CurrentRealm().Addr()\n\tugnotCoin := std.NewCoin(\"ugnot\", fee.RegisterBaseFee)\n\tcoinsToReturn := std.NewCoins(ugnotCoin)\n\tufmt.Println(\"return coins from contract \", bankerContract.String(), \" to \", userAddr.String())\n\tbankerUser.SendCoins(bankerContract, userAddr, coinsToReturn)\n}\n\n// simple check for admin call\nfunc assertIsAdmin() {\n\t// check if GetCallerAt 2 or 3 when deployed\n\tcaller := std.GetCallerAt(3)\n\terr := ufmt.Sprintf(\"unauthorize with caller: %s\\n\", caller)\n\tif caller != admin \u0026\u0026 caller != adminVar {\n\t\tpanic(err)\n\t}\n}\n\n// checking for availble coins\nfunc checkCoin(from std.Address) std.Coins {\n\t// caller := std.GetOrigCaller()\n\treturn bankerUser.GetCoins(from)\n}\n"},{"name":"fee_token.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n\tpusers \"gno.land/p/demo/users\"\n\t\"gno.land/r/varmeta/demo1/domain/vmt\"\n)\n\n// expected approved already from client -\u003e transfer from caller to admin\nfunc tokenProcess(dName string, callerStd std.Address) {\n\tcaller := pusers.AddressOrName(callerStd.String())\n\n\tnow := std.CurrentRealm().Addr()\n\tnowAddr := pusers.AddressOrName(now.String())\n\tufmt.Println(\"current realm transfer: \", now.String())\n\tcallerAllowance := vmt.Allowance(caller, nowAddr)\n\tcallerAllowanceString := ufmt.Sprintf(\"%d\", callerAllowance)\n\tufmt.Println(\"caller allowance \", callerAllowanceString)\n\n\tadminAddr := pusers.AddressOrName(admin.String())\n\tufmt.Println(\"admin: \", admin.String())\n\tvmt.TransferFrom(caller, adminAddr, 1)\n}\n"},{"name":"hashstring.gno","body":"package registrar\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n)\n\nfunc Get256String(input string) string {\n\tdata := []byte(input)\n\thashed := sha256.Sum256(data)\n\thashedBytes := hashed[:]\n\treturn hex.EncodeToString(hashedBytes)\n}\n"},{"name":"metadata_wrapper.gno","body":"package registrar\n\nimport (\n\t\"bytes\"\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/p/varmeta/demo/v36/domain\"\n)\n\n// Metadata wrapper\n// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait)\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcreatedAt := time.Now()\n\texpTime := createdAt.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", createdAt, expTime, []domain.Trait{})\n}\n\ntype remapMetadata struct {\n\tAvatar string // avatar - URL or identifier for an avatar image\n\tRegistrationTime string // regtime - The time when the domain was registered\n\tExpirationTime string // exptime - The time when the domain will be expire\n\tAttributes []domain.Trait // atts - Additional attributes of the domain\n\tDescription string // des - A description of the domain\n\tContactInfo string // contacts - Contact information for the domain owner\n\tRenewalFee string // renewalfee - The fee required to renew the domain, represented as a string\n}\n\n// currently not support for arrays\nfunc (m remapMetadata) MarshalJSON() ([]byte, error) {\n\tjson := new(bytes.Buffer)\n\tif m.Attributes == nil {\n\t\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, \"empty\", m.Description, m.ContactInfo, m.RenewalFee))\n\t\treturn json.Bytes(), nil\n\t}\n\tjson.WriteString(ufmt.Sprintf(`{\"avatar\": %s, \"regtime\": %s, \"exptime\": %s, \"atts\": %s, \"des\": %s, \"contacts\": %s, \"renewalfee\": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee))\n\treturn json.Bytes(), nil\n}\n"},{"name":"models.gno","body":"package registrar\n\nimport (\n\t\"std\"\n)\n\ntype RequestInfo struct {\n\tMode string\n\tWantedDomain string\n\tCaller std.Address\n\tTransInfo TransferInfo\n\t// xxx extendTime, renew...\n}\ntype TransferInfo struct {\n\tFrom std.Address\n\tTo std.Address\n}\ntype ExecuteResult struct {\n\tSuccess bool\n\tResultDetails error\n\tMessage string\n}\n"},{"name":"prestep.gno","body":"package registrar\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/varmeta/demo/v36/domain\"\n)\n\nvar (\n\tdomainStorage *avl.Tree // domainName -\u003e std.Address\n\trootRegistry domain.DomainRegistry\n\n\t// fee\n\tsuperBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction\n\tbankerUser std.Banker // full access to coins sent with the transaction that called the banker\n\n\tadmin std.Address // admin\n\tadminVar std.Address // admin in server\n\tfee feeInfo\n)\n\nfunc init() {\n\tdomainStorage = avl.NewTree()\n\tBidRec = avl.NewTree()\n\twinnerRec = avl.NewTree()\n\trootRegistry = domain.NewDomainRegistry(\"Varmeta\", \"vmt\")\n\n\t// fee init\n\tadmin = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" //@thinhnx\n\tadminVar = \"g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx\" //@varmeta-sponsorkey\n\t// ugnot\n\tfee = feeInfo{\n\t\tRegisterBaseFee: 100,\n\t\tRenewalFee: 100,\n\t\tRegisterAdditionFee: 0,\n\t\tBidJoinFee: 100,\n\t}\n\tsuperBanker = std.GetBanker(std.BankerTypeRealmSend)\n\tbankerUser = std.GetBanker(std.BankerTypeOrigSend)\n}\n"},{"name":"registrar.gno","body":"/*\nThis package contains functions that will actually execute the request from user\nFeatures: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion...\n*/\n// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm.\n// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session\n\n// currently we dont using too much panic because we dont have defer functions to revert the state of storage\npackage registrar\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v36/domain\"\n)\n\n// XXX: consider using panic instead of return string or errors\nfunc Register(domainName string, mode string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t\tMode: mode,\n\t}\n\n\tregResult := executeRegister(requestInfo)\n\n\t// calling panic to stop paying fee\n\tif !regResult.Success {\n\t\tpanic(regResult.ResultDetails.Error())\n\t}\n\t// pay fee with panic inside\n\tfeeProcess(requestInfo)\n\treturn \"Register Done\"\n}\n\nfunc executeRegister(req RequestInfo) ExecuteResult {\n\t// check if domain name is regex valid\n\tvar execRes ExecuteResult\n\tif !isValidDomain(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrInvalidDomainName\n\t\treturn execRes\n\t}\n\n\t// check if dName is registered\n\tif AlreadyRegistered(req.WantedDomain) {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrAlreadyRegistered\n\t\treturn execRes\n\t}\n\n\t// execute register domain - mint the nft\n\t// changelogs v2: we are using sealed bidding now\n\n\tcaller := req.Caller\n\tttl := defaultExpireTime\n\tmetadata := metadataWrapper(caller, req.WantedDomain, ttl)\n\t// create a new registry instance to save metadata and mint the NFT\n\terrRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl)\n\tif errRegister != nil {\n\t\texecRes.Success = false\n\t\texecRes.ResultDetails = ErrCrossRealms\n\t\treturn execRes\n\t}\n\t// now save caller to corressponding tree to manage\n\tdomainStorage.Set(req.WantedDomain, caller)\n\n\texecRes.Success = true\n\treturn execRes\n}\n\nfunc feeProcess(req RequestInfo) {\n\tif req.Mode == \"token\" {\n\t\ttokenProcess(req.WantedDomain, req.Caller)\n\t} else {\n\t\tnativeProcess()\n\t}\n}\n\nfunc AlreadyRegistered(domainName string) bool {\n\t// if can get owner -\u003e existed\n\taddr, err := rootRegistry.OwnerOf(domainName)\n\tif err == nil \u0026\u0026 addr != \"\" {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc GetOwner(domainName string) std.Address {\n\tvl, existed := domainStorage.Get(domainName)\n\tif !existed {\n\t\treturn \"\"\n\t}\n\treturn vl.(std.Address)\n}\n\nfunc Search(domainName string) (remapMetadata, string) {\n\tvalidMetadata := remapMetadata{}\n\tmd, err := getMetadata(domainName)\n\tif err != nil {\n\t\t// return validMetadata, err.Error()\n\t\tpanic(err)\n\t}\n\tvalidMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339)\n\tvalidMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339)\n\t// jsonData, _ := validMetadata.MarshalJSON()\n\treturn validMetadata, \"Search Success\"\n}\n\nfunc getMetadata(wantedDomain string) (domain.Metadata, error) {\n\t// confirm the method? -\u003e get all the fields if the fields slice is empty\n\tmetadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{})\n\tif err != nil {\n\t\treturn metadata, err\n\t}\n\treturn metadata, nil\n}\n\n// Transfer\nfunc TransferDomain(from, to, domainName string) string {\n\trequestInfo := RequestInfo{\n\t\tWantedDomain: domainName,\n\t\tCaller: std.PrevRealm().Addr(),\n\t}\n\tif err := excuteTransfer(requestInfo); err != \"\" {\n\t\tpanic(err)\n\t}\n\treturn \"Transfer Done\"\n}\n\nfunc excuteTransfer(req RequestInfo) string {\n\tif !AlreadyRegistered(req.WantedDomain) {\n\t\treturn ErrAlreadyRegistered.Error()\n\t}\n\trootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain)\n\treturn \"\"\n}\n\nfunc GetDomainName(addr string) []string {\n\tdomainList := []string{}\n\t// search from local storage\n\tdomainStorage.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\tcaller := value.(std.Address)\n\t\t// not checking isExpired\n\t\tif caller.String() == addr {\n\t\t\tdomainList = append(domainList, key)\n\t\t}\n\t\treturn false\n\t})\n\treturn domainList\n}\n"},{"name":"registrar_test.gno","body":"package registrar\n\n// import (\n// \t\"fmt\"\n// \t\"std\"\n// \t\"testing\"\n// )\n\n// func TestRegisterDomain(t *testing.T) {\n// \ttcs := []struct {\n// \t\tinput string\n// \t\texpected string\n// \t}{\n// \t\t{\"thinhnx\", \"Register done\"},\n// \t}\n// \tfor tc := range tcs {\n// \t\tname := tc.input\n// \t\tt.Run(name, func(t *testing.T) {\n// \t\t\toutput := Register(tc.input)\n// \t\t\tif output != tc.expected {\n// \t\t\t\tt.Errorf(\"Expected '%q, but got %q\", tc.expected, output)\n// \t\t\t}\n// \t\t})\n// \t}\n// }\n"},{"name":"utils.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage registrar\n\nimport (\n\t\"regexp\"\n\t\"time\"\n)\n\nvar (\n\tdefaultCommitHashTime = time.Second * 60\n\tdefaultCommitPriceTime = time.Second * 60\n\tdefaultExpireTime = time.Hour // 30 days\n\treName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc GetExpirationDate(dName string) time.Time {\n\treturn rootRegistry.GetExpirationDate(dName)\n}\n\n// for now, this function only let admin set\nfunc SetExpirationDate(dName string, expDate time.Time) bool {\n\tassertIsAdmin()\n\treturn rootRegistry.SetExpirationDate(dName, expDate)\n}\n\nfunc SetCommitPhaseTime(duration int) {\n\tdefaultCommitHashTime = time.Duration(duration) * time.Second\n}\n\nfunc SetCommitPriceTime(duration int) {\n\tdefaultCommitPriceTime = time.Duration(duration) * time.Second\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"KfPWGcuLykrKsJTLl4PZBUONOi9+jQKRZ2+/zE+4zoFtKYMuicWoO4yVuxGyFL43CgApiWr9cAGlL7R0A890Iw=="}],"memo":""},"blockNum":"1865288"} +{"tx":{"msg":[{"@type":"/vm.m_addpkg","creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","package":{"name":"resolver","path":"gno.land/r/varmeta/demo/v36/domain/resolver","files":[{"name":"checks_resolver.gno","body":"/*\nThis check module contains function to do the checking stuffs\n*/\npackage resolver\n\nimport (\n\t\"regexp\"\n\t\"time\"\n\n\t\"gno.land/r/varmeta/demo/v36/domain/registrar\"\n)\n\n// const (\n// \tadmin std.Address = \"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9\" // -\u003e @thinhnx\n// )\n\nvar reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\\.gno$`)\n\n// check for registering process\nfunc isValidDomain(d string) bool {\n\treturn reName.MatchString(d)\n}\n\nfunc isExpired(dName string) bool {\n\texpDate := registrar.GetExpirationDate(dName)\n\treturn expDate.Before(time.Now())\n}\n"},{"name":"errors.gno","body":"package resolver\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\tErrNotFound = errors.New(\"not found\")\n\tErrBadCall = errors.New(\"bad call\")\n\tErrInvalidDomainName = errors.New(\"invalid domain name to register\")\n)\n"},{"name":"resolver.gno","body":"/*\nThe goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner.\nIt serves the purpose of \"resolving\" the ICNS Name\nto the correct address (e.g \"alice.gno\" -\u003e g1xxx).\n*/\n// changelogs: move Register feature into this resolver package\n// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result\n\npackage resolver\n\nimport (\n\t\"std\"\n\n\t\"gno.land/r/varmeta/demo/v36/domain/registrar\"\n)\n\ntype Record struct {\n\tOwner std.Address\n\tIsValid bool\n\tMemo string // no more need this\n\tPriority int\n}\n\n// retrieve the record list to get the onchain address\nfunc Resolve(domainName string) *Record {\n\tif !isValidDomain(domainName) {\n\t\tpanic(\"bad domain name\")\n\t}\n\trecord := \u0026Record{}\n\n\towner := getOwnerFromDomainStorage(domainName)\n\tif owner == \"\" {\n\t\trecord.Memo = \"not found\"\n\t\trecord.IsValid = false\n\t\treturn record\n\t}\n\n\tif !isExpired(domainName) {\n\t\trecord.IsValid = true\n\t\trecord.Owner = owner\n\t} else {\n\t\trecord.IsValid = false\n\t}\n\treturn record\n}\n\nfunc GetDomainName(addr string) []string {\n\treturn registrar.GetDomainName(addr)\n}\n\n/*\nIf query in local storage not found\nQuery to DomainStorage by domainName -\u003e get the registry -\u003e use that registry to get the Owner()\nand check the validation time?\n*/\n\nfunc existedInDomainStorage(domainName string) bool {\n\treturn registrar.AlreadyRegistered(domainName)\n}\n\nfunc getOwnerFromDomainStorage(domainName string) std.Address {\n\treturn registrar.GetOwner(domainName)\n}\n"},{"name":"resolver_metadata.gno","body":"package resolver\n\nimport (\n\t\"std\"\n\t\"time\"\n\n\t\"gno.land/p/varmeta/demo/v36/domain\"\n)\n\n// Metadata wrapper\nfunc metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata {\n\tcrrTime := time.Now()\n\texpTime := crrTime.Add(ttl)\n\treturn domain.NewMetadata(\"\", name, \"\", \"\", crrTime, expTime, []domain.Trait{})\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"60000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AqD4AqUY/VAPgrwGhq7e9Px4Pj81GRDZYwRvm+A69599"},"signature":"QLjzdBig14kS/S6l6EMRBF7S70yPWYsC6rSNRjuEHFBYQRf1+gZhGCc+MUUCyeCW8U0I81jPipHNagB55BpycQ=="}],"memo":""},"blockNum":"1865292"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g14ks20xj4csuyn0jucwdt96rgr6cenht30avkyr","send":"","pkg_path":"gno.land/r/demo/wugnot","func":"Approve","args":["g126swhfaq2vyvvjywevhgw7lv9hg8qan93dasu8","18446744073709551615"]},{"@type":"/vm.m_call","caller":"g14ks20xj4csuyn0jucwdt96rgr6cenht30avkyr","send":"","pkg_path":"gno.land/r/demo/wugnot","func":"Approve","args":["g1cnz5gm2l09pm2k6rknjjar9a2w53fdhk4yjzy5","18446744073709551615"]},{"@type":"/vm.m_call","caller":"g14ks20xj4csuyn0jucwdt96rgr6cenht30avkyr","send":"","pkg_path":"gno.land/r/gnoswap/v2/gns","func":"Approve","args":["g1cnz5gm2l09pm2k6rknjjar9a2w53fdhk4yjzy5","18446744073709551615"]},{"@type":"/vm.m_call","caller":"g14ks20xj4csuyn0jucwdt96rgr6cenht30avkyr","send":"25000000ugnot","pkg_path":"gno.land/r/gnoswap/v2/router","func":"SwapRoute","args":["gnot","gno.land/r/gnoswap/v2/gns","25000000","EXACT_IN","gno.land/r/demo/wugnot:gno.land/r/gnoswap/v2/gns:3000","100","25016081"]}],"fee":{"gas_wanted":"100000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A5AUjFIdvgvvYPCszkBSj4LyyQVI/uczvuxzbg5IRDB3"},"signature":"xk6G6i7ZMjAf5QEM12l/noLq54NkbMt7JTnKJN11DIcfgEABmqiyN0XXHnHMSMh5TrTpgLSguCaybgMZ4yP5Yw=="}],"memo":""},"blockNum":"1870757"} +{"tx":{"msg":[{"@type":"/vm.m_call","caller":"g14ks20xj4csuyn0jucwdt96rgr6cenht30avkyr","send":"","pkg_path":"gno.land/r/demo/wugnot","func":"Approve","args":["g126swhfaq2vyvvjywevhgw7lv9hg8qan93dasu8","18446744073709551615"]},{"@type":"/vm.m_call","caller":"g14ks20xj4csuyn0jucwdt96rgr6cenht30avkyr","send":"","pkg_path":"gno.land/r/demo/wugnot","func":"Approve","args":["g1cnz5gm2l09pm2k6rknjjar9a2w53fdhk4yjzy5","18446744073709551615"]},{"@type":"/vm.m_call","caller":"g14ks20xj4csuyn0jucwdt96rgr6cenht30avkyr","send":"","pkg_path":"gno.land/r/gnoswap/v2/gns","func":"Approve","args":["g1cnz5gm2l09pm2k6rknjjar9a2w53fdhk4yjzy5","18446744073709551615"]},{"@type":"/vm.m_call","caller":"g14ks20xj4csuyn0jucwdt96rgr6cenht30avkyr","send":"2000000ugnot","pkg_path":"gno.land/r/gnoswap/v2/router","func":"SwapRoute","args":["gnot","gno.land/r/gnoswap/v2/gns","2000000","EXACT_IN","gno.land/r/demo/wugnot:gno.land/r/gnoswap/v2/gns:3000","100","2001284"]}],"fee":{"gas_wanted":"100000000","gas_fee":"1ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"A5AUjFIdvgvvYPCszkBSj4LyyQVI/uczvuxzbg5IRDB3"},"signature":"+jUoNDPhDoi3HpoyIvV8H08U+xZ/69oESUJY9aDRL0h7PNZUxaSeWUxVUQ03Jvx5CgjWtnKM837coTaG6hHMCw=="}],"memo":""},"blockNum":"1870766"} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_metadata.gno new file mode 100644 index 00000000..388902c4 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_metadata.gno @@ -0,0 +1,38 @@ +package domain + +import ( + "time" +) + +// Trait represents a key-value pair with an optional display type for metadata attributes +type Trait struct { + DisplayType string // Optional display type (e.g., "date", "number", etc.) + TraitType string // Type of the trait (e.g., "age", "height", etc.) + Value string // Value of the trait +} + +// Metadata represents the metadata associated with a domain +type Metadata struct { + Avatar string // URL or identifier for an avatar image + RegistrationTime time.Time // The time when the domain was registered + ExpirationTime time.Time // The time when the domain will be expire + Attributes []Trait // Additional attributes of the domain + Description string // A description of the domain + ContactInfo string // Contact information for the domain owner + RenewalFee string // The fee required to renew the domain, represented as a string +} + +// NewMetadata creates a new Metadata instance +func NewMetadata(avatar, description, contactInfo, renewalFee string, + registrationTime, expirationTime time.Time, attributes []Trait, +) Metadata { + return Metadata{ + Avatar: avatar, + RegistrationTime: registrationTime, + ExpirationTime: expirationTime, + RenewalFee: renewalFee, + Attributes: attributes, + Description: description, + ContactInfo: contactInfo, + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_registry.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_registry.gno new file mode 100644 index 00000000..b0e3338e --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_registry.gno @@ -0,0 +1,236 @@ +package domain + +import ( + "std" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v31/grc/grc721" +) + +// domainRegistry represents a registry for domain names with metadata +type domainRegistry struct { + domains grc721.IGRC721 // Interface for basic NFT functionality + metadata *avl.Tree // AVL tree for storing domain metadata + expDate time.Time +} + +// DomainRegistry defines the methods for managing domain names and metadata +type DomainRegistry interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(domainName string) (std.Address, error) + SafeTransferFrom(from, to std.Address, domainName string) error + TransferFrom(from, to std.Address, domainName string) error + Approve(approved std.Address, domainName string) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(domainName string) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, domainName string) error + + RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error + SetDomainData(domainName string, metadata Metadata) error + GetDomainData(domainName string, field MetadataField) (Metadata, error) + GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) + RenewDomain(domainName string, additionalDuration time.Duration) error + GetExpirationDate(domainName string) time.Time + SetExpirationDate(domainName string, expDate time.Time) bool +} + +// NewDomainRegistry creates a new domain registry with metadata extensions +func NewDomainRegistry(name, symbol string) *domainRegistry { + registry := grc721.NewBasicNFT(name, symbol) + + return &domainRegistry{ + domains: registry, + metadata: avl.NewTree(), + } +} + +// RegisterDomain registers a new domain with the given metadata +func (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error { + err := d.domains.Mint(owner, grc721.TokenID(domainName)) + if err != nil { + return err + } + d.expDate = time.Now().Add(dur) + d.metadata.Set(domainName, metadata) + + return nil +} + +// RenewDomain extends the expiration time of a domain name +func (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error { + _, found := d.metadata.Get(domainName) + if !found { + return ErrInvalidDomainName + } + + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + // set new expiration date + d.expDate = d.expDate.Add(additionalDuration) + return nil +} + +// SetDomainData sets the metadata for a given domain name +func (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error { + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + d.metadata.Set(domainName, metadata) + return nil +} + +// GetDomainFields retrieves multiple fields of metadata for a given domain +func (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + if len(fields) == 0 { + return metadata, nil + } + + var result Metadata + for _, field := range fields { + switch field { + case FieldAvatar: + result.Avatar = metadata.Avatar + case FieldRegistrationTime: + result.RegistrationTime = metadata.RegistrationTime + case FieldExpirationTime: + result.ExpirationTime = metadata.ExpirationTime + case FieldRenewalFee: + result.RenewalFee = metadata.RenewalFee + case FieldAttributes: + result.Attributes = metadata.Attributes + case FieldDescription: + result.Description = metadata.Description + case FieldContactInfo: + result.ContactInfo = metadata.ContactInfo + default: + return Metadata{}, ErrInvalidMetadataField + } + } + + return result, nil +} + +// GetDomainData retrieves metadata for a given domain +func (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + switch field { + case FieldAvatar: + return Metadata{ + Avatar: metadata.Avatar, + }, nil + case FieldRegistrationTime: + return Metadata{ + RegistrationTime: metadata.RegistrationTime, + }, nil + case FieldExpirationTime: + return Metadata{ + ExpirationTime: metadata.ExpirationTime, + }, nil + case FieldRenewalFee: + return Metadata{ + RenewalFee: metadata.RenewalFee, + }, nil + case FieldAttributes: + return Metadata{ + Attributes: metadata.Attributes, + }, nil + case FieldDescription: + return Metadata{ + Description: metadata.Description, + }, nil + case FieldContactInfo: + return Metadata{ + ContactInfo: metadata.ContactInfo, + }, nil + default: + return Metadata{}, ErrInvalidMetadataField + } +} + +// BalanceOf returns the number of domains owned by a given address +func (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) { + return d.domains.BalanceOf(owner) +} + +// OwnerOf returns the owner of a given domain name +func (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) { + return d.domains.OwnerOf(grc721.TokenID(domainName)) +} + +// SafeTransferFrom safely transfers a domain from one address to another +func (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error { + return d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName)) +} + +// TransferFrom transfers a domain from one address to another +func (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error { + return d.domains.TransferFrom(from, to, grc721.TokenID(domainName)) +} + +// Approve grants approval to another address to manage a specific domain +func (d *domainRegistry) Approve(approved std.Address, domainName string) error { + return d.domains.Approve(approved, grc721.TokenID(domainName)) +} + +// SetApprovalForAll sets approval for an operator to manage all domains of the owner +func (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error { + return d.domains.SetApprovalForAll(operator, approved) +} + +// GetApproved returns the approved address for a specific domain +func (d *domainRegistry) GetApproved(domainName string) (std.Address, error) { + return d.domains.GetApproved(grc721.TokenID(domainName)) +} + +// IsApprovedForAll checks if an operator is approved to manage all domains of the owner +func (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool { + return d.domains.IsApprovedForAll(owner, operator) +} + +// Mint creates a new domain for a given address +func (d *domainRegistry) Mint(to std.Address, domainName string) error { + return d.domains.Mint(to, grc721.TokenID(domainName)) +} + +func (d *domainRegistry) GetExpirationDate(domainName string) time.Time { + return d.expDate +} + +func (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool { + _, found := d.metadata.Get(domainName) + if !found { + return false + } + d.expDate = expDate + return true +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_registry_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_registry_test.gno new file mode 100644 index 00000000..bd52cb2f --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/domain_registry_test.gno @@ -0,0 +1,378 @@ +package domain + +import ( + "std" + "testing" + "time" + + "gno.land/p/varmeta/demo/v3/grc/grc721" + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" +) + +var ( + addr1 = testutils.TestAddress("bob") + addr2 = testutils.TestAddress("alice") +) + +func TestRegisterDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Successful Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "contact@registered.com", + }, + expectError: false, + }, + { + name: "Duplicate Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + err := registry.RegisterDomain(c.owner, c.domainName, c.metadata) + if c.expectError { + urequire.Error(t, err) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.owner, retrievedOwner) + } + }) + } +} + +func TestSetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Owner Sets Metadata", + owner: addr1, + caller: addr1, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: false, + }, + { + name: "Non-Owner Sets Metadata", + owner: addr1, + caller: addr2, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + registry.RegisterDomain(c.owner, c.domainName, c.metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + err := registry.SetDomainData(c.domainName, c.metadata) + + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + retrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar) + urequire.NoError(t, err) + urequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar) + } + }) + } +} + +func TestRenewDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + additionalTime time.Duration + expectError bool + expectedExpiry time.Time + }{ + { + name: "Successful Renewal", + owner: addr1, + caller: addr1, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: false, + }, + { + name: "Non-Owner Attempts Renewal", + owner: addr1, + caller: addr2, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A renewable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.RenewDomain(c.domainName, c.additionalTime) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + renewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime) + urequire.NoError(t, err) + // urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime)) + } + }) + } +} + +func TestGetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + field MetadataField + expectError bool + expectedVal string + }{ + { + name: "Retrieve Avatar", + domainName: "test.gno", + field: FieldAvatar, + expectError: false, + expectedVal: "avatar_url", + }, + { + name: "Invalid Domain Name", + domainName: "invalid.gno", + field: FieldAvatar, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + _, err := registry.GetDomainData(c.domainName, c.field) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + } + }) + } +} + +func TestGetDomainFields(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + fields []MetadataField + expectError bool + expected Metadata + }{ + { + name: "Retrieve Multiple Fields", + domainName: "test.gno", + fields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo}, + expectError: false, + expected: Metadata{ + Avatar: "avatar_url", + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + }, + { + name: "Invalid Domain", + domainName: "invalid.gno", + fields: []MetadataField{FieldAvatar}, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + retrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + urequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar) + urequire.Equal(t, c.expected.Description, retrievedMetadata.Description) + urequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo) + } + }) + } +} + +func TestTransferDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + newOwner std.Address + caller std.Address + domainName string + expectError bool + }{ + { + name: "Successful Transfer", + owner: addr1, + newOwner: addr2, + caller: addr1, + domainName: "transfer.gno", + expectError: false, + }, + { + name: "Non-Owner Attempts Transfer", + owner: addr1, + newOwner: addr2, + caller: addr2, + domainName: "transfer.gno", + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A transferable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.TransferFrom(c.owner, c.newOwner, c.domainName) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error()) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.newOwner, retrievedOwner) + } + }) + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/domain/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/errors.gno new file mode 100644 index 00000000..3de5d750 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/errors.gno @@ -0,0 +1,12 @@ +package domain + +import ( + "errors" +) + +var ( + ErrUnauthorized = errors.New("caller is not domain owner") + ErrInvalidDomainName = errors.New("invalid domain name") + ErrInvalidMetadataField = errors.New("invalid metadata field") + ErrInsufficientFunds = errors.New("insufficient funds for renewal") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/domain/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/domain/utils.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/utils.gno new file mode 100644 index 00000000..13e40d99 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/domain/utils.gno @@ -0,0 +1,13 @@ +package domain + +type MetadataField int + +const ( + FieldAvatar MetadataField = iota + FieldRegistrationTime + FieldRenewalFee + FieldExpirationTime + FieldAttributes + FieldDescription + FieldContactInfo +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/basic_nft.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/basic_nft.gno new file mode 100644 index 00000000..bec7338d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/basic_nft.gno @@ -0,0 +1,378 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +type basicNFT struct { + name string + symbol string + owners avl.Tree // tokenId -> OwnerAddress + balances avl.Tree // OwnerAddress -> TokenCount + tokenApprovals avl.Tree // TokenId -> ApprovedAddress + tokenURIs avl.Tree // TokenId -> URIs + operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool +} + +// Returns new basic NFT +func NewBasicNFT(name string, symbol string) *basicNFT { + return &basicNFT{ + name: name, + symbol: symbol, + + owners: avl.Tree{}, + balances: avl.Tree{}, + tokenApprovals: avl.Tree{}, + tokenURIs: avl.Tree{}, + operatorApprovals: avl.Tree{}, + } +} + +func (s *basicNFT) Name() string { return s.name } +func (s *basicNFT) Symbol() string { return s.symbol } +func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) } + +// BalanceOf returns balance of input address +func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) { + if err := isValidAddress(addr); err != nil { + return 0, err + } + + balance, found := s.balances.Get(addr.String()) + if !found { + return 0, nil + } + + return balance.(uint64), nil +} + +// OwnerOf returns owner of input token id +func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) { + owner, found := s.owners.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return owner.(std.Address), nil +} + +// TokenURI returns the URI of input token id +func (s *basicNFT) TokenURI(tid TokenID) (string, error) { + uri, found := s.tokenURIs.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return uri.(string), nil +} + +func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + // check for invalid TokenID + if !s.exists(tid) { + return false, ErrInvalidTokenId + } + + // check for the right owner + owner, err := s.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + s.tokenURIs.Set(string(tid), string(tURI)) + return true, nil +} + +// IsApprovedForAll returns true if operator is approved for all by the owner. +// Otherwise, returns false +func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool { + key := owner.String() + ":" + operator.String() + _, found := s.operatorApprovals.Get(key) + if !found { + return false + } + + return true +} + +// Approve approves the input address for particular token +func (s *basicNFT) Approve(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner == to { + return ErrApprovalToCurrentOwner + } + + caller := std.PrevRealm().Addr() + if caller != owner && !s.IsApprovedForAll(owner, caller) { + return ErrCallerIsNotOwnerOrApproved + } + + s.tokenApprovals.Set(string(tid), to.String()) + event := ApprovalEvent{owner, to, tid} + emit(&event) + + return nil +} + +// GetApproved return the approved address for token +func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) { + addr, found := s.tokenApprovals.Get(string(tid)) + if !found { + return zeroAddress, ErrTokenIdNotHasApproved + } + + return std.Address(addr.(string)), nil +} + +// SetApprovalForAll can approve the operator to operate on all tokens +func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error { + if err := isValidAddress(operator); err != nil { + return ErrInvalidAddress + } + + caller := std.PrevRealm().Addr() + return s.setApprovalForAll(caller, operator, approved) +} + +// Safely transfers `tokenId` token from `from` to `to`, checking that +// contract recipients are aware of the GRC721 protocol to prevent +// tokens from being forever locked. +func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(from, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +// Transfers `tokenId` token from `from` to `to`. +func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + return nil +} + +// Mints `tokenId` and transfers it to `to`. +func (s *basicNFT) Mint(to std.Address, tid TokenID) error { + return s.mint(to, tid) +} + +// Mints `tokenId` and transfers it to `to`. Also checks that +// contract recipients are using GRC721 protocol +func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error { + err := s.mint(to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(zeroAddress, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +func (s *basicNFT) Burn(tid TokenID) error { + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + + s.beforeTokenTransfer(owner, zeroAddress, tid, 1) + + s.tokenApprovals.Remove(string(tid)) + balance, err := s.BalanceOf(owner) + if err != nil { + return err + } + balance -= 1 + s.balances.Set(owner.String(), balance) + s.owners.Remove(string(tid)) + + event := TransferEvent{owner, zeroAddress, tid} + emit(&event) + + s.afterTokenTransfer(owner, zeroAddress, tid, 1) + + return nil +} + +/* Helper methods */ + +// Helper for SetApprovalForAll() +func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error { + if owner == operator { + return ErrApprovalToCurrentOwner + } + + key := owner.String() + ":" + operator.String() + s.operatorApprovals.Set(key, approved) + + event := ApprovalForAllEvent{owner, operator, approved} + emit(&event) + + return nil +} + +// Helper for TransferFrom() and SafeTransferFrom() +func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error { + if err := isValidAddress(from); err != nil { + return ErrInvalidAddress + } + if err := isValidAddress(to); err != nil { + return ErrInvalidAddress + } + + if from == to { + return ErrCannotTransferToSelf + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.beforeTokenTransfer(from, to, tid, 1) + + // Check that tokenId was not transferred by `beforeTokenTransfer` + owner, err = s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.tokenApprovals.Remove(string(tid)) + fromBalance, err := s.BalanceOf(from) + if err != nil { + return err + } + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + fromBalance -= 1 + toBalance += 1 + s.balances.Set(from.String(), fromBalance) + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{from, to, tid} + emit(&event) + + s.afterTokenTransfer(from, to, tid, 1) + + return nil +} + +// Helper for Mint() and SafeMint() +func (s *basicNFT) mint(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check that tokenId was not minted by `beforeTokenTransfer` + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} + +func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool { + owner, found := s.owners.Get(string(tid)) + if !found { + return false + } + + if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) { + return true + } + + _, err := s.GetApproved(tid) + if err != nil { + return false + } + + return true +} + +// Checks if token id already exists +func (s *basicNFT) exists(tid TokenID) bool { + _, found := s.owners.Get(string(tid)) + return found +} + +func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool { + // TODO: Implementation + return true +} + +func (s *basicNFT) RenderHome() (str string) { + str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol) + str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount()) + str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size()) + + return +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/basic_nft_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/basic_nft_test.gno new file mode 100644 index 00000000..6375b030 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/basic_nft_test.gno @@ -0,0 +1,283 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +var ( + dummyNFTName = "DummyNFT" + dummyNFTSymbol = "DNFT" +) + +func TestNewBasicNFT(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") +} + +func TestName(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + name := dummy.Name() + uassert.Equal(t, dummyNFTName, name) +} + +func TestSymbol(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + symbol := dummy.Symbol() + uassert.Equal(t, dummyNFTSymbol, symbol) +} + +func TestTokenCount(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + count := dummy.TokenCount() + uassert.Equal(t, uint64(0), count) + + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("1")) + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("2")) + + count = dummy.TokenCount() + uassert.Equal(t, uint64(2), count) +} + +func TestBalanceOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + balanceAddr1, err := dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceAddr1) + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr1, TokenID("2")) + dummy.mint(addr2, TokenID("3")) + + balanceAddr1, err = dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + + balanceAddr2, err := dummy.BalanceOf(addr2) + uassert.NoError(t, err, "should not result in error") + + uassert.Equal(t, uint64(2), balanceAddr1) + uassert.Equal(t, uint64(1), balanceAddr2) +} + +func TestOwnerOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + owner, err := dummy.OwnerOf(TokenID("invalid")) + uassert.Error(t, err, "should not result in error") + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr2, TokenID("2")) + + // Checking for token id "1" + owner, err = dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) + + // Checking for token id "2" + owner, err = dummy.OwnerOf(TokenID("2")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr2.String(), owner.String()) +} + +func TestIsApprovedForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) + uassert.False(t, isApprovedForAll) +} + +func TestSetApprovalForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + isApprovedForAll := dummy.IsApprovedForAll(caller, addr) + uassert.False(t, isApprovedForAll) + + err := dummy.SetApprovalForAll(addr, true) + uassert.NoError(t, err, "should not result in error") + + isApprovedForAll = dummy.IsApprovedForAll(caller, addr) + uassert.True(t, isApprovedForAll) +} + +func TestGetApproved(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + approvedAddr, err := dummy.GetApproved(TokenID("invalid")) + uassert.Error(t, err, "should result in error") +} + +func TestApprove(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + + _, err := dummy.GetApproved(TokenID("1")) + uassert.Error(t, err, "should result in error") + + err = dummy.Approve(addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + approvedAddr, err := dummy.GetApproved(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), approvedAddr.String()) +} + +func TestTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.TransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestSafeTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.SafeTransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestMint(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + err := dummy.Mint(addr1, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr1, TokenID("2")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr2, TokenID("3")) + uassert.NoError(t, err, "should not result in error") + + // Try minting duplicate token id + err = dummy.Mint(addr2, TokenID("1")) + uassert.Error(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) +} + +func TestBurn(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(addr, TokenID("1")) + dummy.mint(addr, TokenID("2")) + + err := dummy.Burn(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.Error(t, err, "should result in error") +} + +func TestSetTokenURI(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + tokenURI := "http://example.com/token" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1, TokenID("1")) + _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) + uassert.NoError(t, derr, "should not result in error") + + // Test case: Invalid token ID + _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Retrieving TokenURI + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyTokenURI, err := dummy.TokenURI(TokenID("1")) + uassert.NoError(t, err, "TokenURI error") + uassert.Equal(t, string(tokenURI), string(dummyTokenURI)) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/errors.gno new file mode 100644 index 00000000..2d512db3 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/errors.gno @@ -0,0 +1,22 @@ +package grc721 + +import "errors" + +var ( + ErrInvalidTokenId = errors.New("invalid token id") + ErrInvalidAddress = errors.New("invalid address") + ErrTokenIdNotHasApproved = errors.New("token id not approved for anyone") + ErrApprovalToCurrentOwner = errors.New("approval to current owner") + ErrCallerIsNotOwner = errors.New("caller is not token owner") + ErrCallerNotApprovedForAll = errors.New("caller is not approved for all") + ErrCannotTransferToSelf = errors.New("cannot send transfer to self") + ErrTransferFromIncorrectOwner = errors.New("transfer from incorrect owner") + ErrTransferToNonGRC721Receiver = errors.New("transfer to non GRC721Receiver implementer") + ErrCallerIsNotOwnerOrApproved = errors.New("caller is not token owner or approved") + ErrTokenIdAlreadyExists = errors.New("token id already exists") + + // ERC721Royalty + ErrInvalidRoyaltyPercentage = errors.New("invalid royalty percentage") + ErrInvalidRoyaltyPaymentAddress = errors.New("invalid royalty paymentAddress") + ErrCannotCalculateRoyaltyAmount = errors.New("cannot calculate royalty amount") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_metadata.gno new file mode 100644 index 00000000..360f73ed --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_metadata.gno @@ -0,0 +1,95 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// metadataNFT represents an NFT with metadata extensions. +type metadataNFT struct { + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions *avl.Tree // AVL tree for storing metadata extensions +} + +// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. +var _ IGRC721MetadataOnchain = (*metadataNFT)(nil) + +// NewNFTWithMetadata creates a new basic NFT with metadata extensions. +func NewNFTWithMetadata(name string, symbol string) *metadataNFT { + // Create a new basic NFT + nft := NewBasicNFT(name, symbol) + + // Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions + return &metadataNFT{ + basicNFT: nft, + extensions: avl.NewTree(), + } +} + +// SetTokenMetadata sets metadata for a given token ID. +func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { + // Check if the caller is the owner of the token + owner, err := s.basicNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set the metadata for the token ID in the extensions AVL tree + s.extensions.Set(string(tid), metadata) + return nil +} + +// TokenMetadata retrieves metadata for a given token ID. +func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { + // Retrieve metadata from the extensions AVL tree + metadata, found := s.extensions.Get(string(tid)) + if !found { + return Metadata{}, ErrInvalidTokenId + } + + return metadata.(Metadata), nil +} + +// mint mints a new token and assigns it to the specified address. +func (s *metadataNFT) mint(to std.Address, tid TokenID) error { + // Check if the address is valid + if err := isValidAddress(to); err != nil { + return err + } + + // Check if the token ID already exists + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check if the token ID was minted by beforeTokenTransfer + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + // Increment balance of the recipient address + toBalance, err := s.basicNFT.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.basicNFT.balances.Set(to.String(), toBalance) + + // Set owner of the token ID to the recipient address + s.basicNFT.owners.Set(string(tid), to) + + // Emit transfer event + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_metadata_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_metadata_test.gno new file mode 100644 index 00000000..ad002a7c --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_metadata_test.gno @@ -0,0 +1,107 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetMetadata(t *testing.T) { + // Create a new dummy NFT with metadata + dummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + // Define addresses for testing purposes + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + // Define metadata attributes + name := "test" + description := "test" + image := "test" + imageData := "test" + externalURL := "test" + attributes := []Trait{} + backgroundColor := "test" + animationURL := "test" + youtubeURL := "test" + + // Set the original caller to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Mint a new token for addr1 + dummy.mint(addr1, TokenID("1")) + + // Set metadata for token 1 + derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if there was an error setting metadata + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + // Set the original caller to addr2 + std.TestSetOrigCaller(addr2) // addr2 + + // Try to set metadata for token 1 from addr2 (should fail) + cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Set the original caller back to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Retrieve metadata for token 1 + dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) + uassert.NoError(t, err, "Metadata error") + + // Check if metadata attributes match expected values + uassert.Equal(t, image, dummyMetadata.Image) + uassert.Equal(t, imageData, dummyMetadata.ImageData) + uassert.Equal(t, externalURL, dummyMetadata.ExternalURL) + uassert.Equal(t, description, dummyMetadata.Description) + uassert.Equal(t, name, dummyMetadata.Name) + uassert.Equal(t, len(attributes), len(dummyMetadata.Attributes)) + uassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor) + uassert.Equal(t, animationURL, dummyMetadata.AnimationURL) + uassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_royalty.gno new file mode 100644 index 00000000..9831c709 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_royalty.gno @@ -0,0 +1,78 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// royaltyNFT represents a non-fungible token (NFT) with royalty functionality. +type royaltyNFT struct { + *metadataNFT // Embedding metadataNFT for NFT functionality + tokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token + maxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale +} + +// Ensure that royaltyNFT implements the IGRC2981 interface. +var _ IGRC2981 = (*royaltyNFT)(nil) + +// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. +func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { + // Create a new NFT with metadata + nft := NewNFTWithMetadata(name, symbol) + + return &royaltyNFT{ + metadataNFT: nft, + tokenRoyaltyInfo: avl.NewTree(), + maxRoyaltyPercentage: 100, + } +} + +// SetTokenRoyalty sets the royalty information for a specific token ID. +func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error { + // Validate the payment address + if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { + return ErrInvalidRoyaltyPaymentAddress + } + + // Check if royalty percentage exceeds maxRoyaltyPercentage + if royaltyInfo.Percentage > r.maxRoyaltyPercentage { + return ErrInvalidRoyaltyPercentage + } + + // Check if the caller is the owner of the token + owner, err := r.metadataNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set royalty information for the token + r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) + + return nil +} + +// RoyaltyInfo returns the royalty information for the given token ID and sale price. +func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) { + // Retrieve royalty information for the token + val, found := r.tokenRoyaltyInfo.Get(string(tid)) + if !found { + return "", 0, ErrInvalidTokenId + } + + royaltyInfo := val.(RoyaltyInfo) + + // Calculate royalty amount + royaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) + + return royaltyInfo.PaymentAddress, royaltyAmount, nil +} + +func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + return royaltyAmount, nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_royalty_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_royalty_test.gno new file mode 100644 index 00000000..7893453a --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/grc721_royalty_test.gno @@ -0,0 +1,70 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetTokenRoyalty(t *testing.T) { + dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + paymentAddress := testutils.TestAddress("john") + percentage := uint64(10) // 10% + + salePrice := uint64(1000) + expectRoyaltyAmount := uint64(100) + + std.TestSetOrigCaller(addr1) // addr1 + + dummy.mint(addr1, TokenID("1")) + + derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, derr, ErrInvalidTokenId) + + std.TestSetOrigCaller(addr2) // addr2 + + cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Invalid payment address + aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + PaymentAddress: std.Address("###"), // invalid address + Percentage: percentage, + }) + uassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress) + + // Test case: Invalid percentage + perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: uint64(200), // over maxRoyaltyPercentage + }) + uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) + + // Test case: Retrieving Royalty Info + std.TestSetOrigCaller(addr1) // addr1 + + dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) + uassert.NoError(t, rerr, "RoyaltyInfo error") + uassert.Equal(t, paymentAddress, dummyPaymentAddress) + uassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721.gno new file mode 100644 index 00000000..ece48f8e --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721.gno @@ -0,0 +1,39 @@ +package grc721 + +import "std" + +type IGRC721 interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(tid TokenID) (std.Address, error) + SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) + SafeTransferFrom(from, to std.Address, tid TokenID) error + TransferFrom(from, to std.Address, tid TokenID) error + Approve(approved std.Address, tid TokenID) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(tid TokenID) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, tid TokenID) error +} + +type ( + TokenID string + TokenURI string +) + +type TransferEvent struct { + From std.Address + To std.Address + TokenID TokenID +} + +type ApprovalEvent struct { + Owner std.Address + Approved std.Address + TokenID TokenID +} + +type ApprovalForAllEvent struct { + Owner std.Address + Operator std.Address + Approved bool +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721_metadata.gno new file mode 100644 index 00000000..8a2be1ff --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721_metadata.gno @@ -0,0 +1,38 @@ +package grc721 + +// IGRC721CollectionMetadata describes basic information about an NFT collection. +type IGRC721CollectionMetadata interface { + Name() string // Name returns the name of the collection. + Symbol() string // Symbol returns the symbol of the collection. +} + +// IGRC721Metadata follows the Ethereum standard +type IGRC721Metadata interface { + IGRC721CollectionMetadata + TokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token. +} + +// IGRC721Metadata follows the OpenSea metadata standard +type IGRC721MetadataOnchain interface { + IGRC721CollectionMetadata + TokenMetadata(tid TokenID) (Metadata, error) +} + +type Trait struct { + DisplayType string + TraitType string + Value string +} + +// see: https://docs.opensea.io/docs/metadata-standards +type Metadata struct { + Image string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image. + ImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter. + ExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site. + Description string // Human-readable description of the item. Markdown is supported. + Name string // Name of the item. + Attributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item. + BackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended # + AnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported. + YoutubeURL string // URL to a YouTube video (only used if animation_url is not provided). +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721_royalty.gno new file mode 100644 index 00000000..c4603d63 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/igrc721_royalty.gno @@ -0,0 +1,16 @@ +package grc721 + +import "std" + +// IGRC2981 follows the Ethereum standard +type IGRC2981 interface { + // RoyaltyInfo retrieves royalty information for a tokenID and salePrice. + // It returns the payment address, royalty amount, and an error if any. + RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) +} + +// RoyaltyInfo represents royalty information for a token. +type RoyaltyInfo struct { + PaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent. + Percentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 => 10% +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/util.gno b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/util.gno new file mode 100644 index 00000000..bb6bf24d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v31/grc/grc721/util.gno @@ -0,0 +1,18 @@ +package grc721 + +import ( + "std" +) + +var zeroAddress = std.Address("") + +func isValidAddress(addr std.Address) error { + if !addr.IsValid() { + return ErrInvalidAddress + } + return nil +} + +func emit(event interface{}) { + // TODO: setup a pubsub system here? +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_metadata.gno new file mode 100644 index 00000000..388902c4 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_metadata.gno @@ -0,0 +1,38 @@ +package domain + +import ( + "time" +) + +// Trait represents a key-value pair with an optional display type for metadata attributes +type Trait struct { + DisplayType string // Optional display type (e.g., "date", "number", etc.) + TraitType string // Type of the trait (e.g., "age", "height", etc.) + Value string // Value of the trait +} + +// Metadata represents the metadata associated with a domain +type Metadata struct { + Avatar string // URL or identifier for an avatar image + RegistrationTime time.Time // The time when the domain was registered + ExpirationTime time.Time // The time when the domain will be expire + Attributes []Trait // Additional attributes of the domain + Description string // A description of the domain + ContactInfo string // Contact information for the domain owner + RenewalFee string // The fee required to renew the domain, represented as a string +} + +// NewMetadata creates a new Metadata instance +func NewMetadata(avatar, description, contactInfo, renewalFee string, + registrationTime, expirationTime time.Time, attributes []Trait, +) Metadata { + return Metadata{ + Avatar: avatar, + RegistrationTime: registrationTime, + ExpirationTime: expirationTime, + RenewalFee: renewalFee, + Attributes: attributes, + Description: description, + ContactInfo: contactInfo, + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_registry.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_registry.gno new file mode 100644 index 00000000..8e07db52 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_registry.gno @@ -0,0 +1,236 @@ +package domain + +import ( + "std" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v32/grc/grc721" +) + +// domainRegistry represents a registry for domain names with metadata +type domainRegistry struct { + domains grc721.IGRC721 // Interface for basic NFT functionality + metadata *avl.Tree // AVL tree for storing domain metadata + expDate time.Time +} + +// DomainRegistry defines the methods for managing domain names and metadata +type DomainRegistry interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(domainName string) (std.Address, error) + SafeTransferFrom(from, to std.Address, domainName string) error + TransferFrom(from, to std.Address, domainName string) error + Approve(approved std.Address, domainName string) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(domainName string) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, domainName string) error + + RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error + SetDomainData(domainName string, metadata Metadata) error + GetDomainData(domainName string, field MetadataField) (Metadata, error) + GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) + RenewDomain(domainName string, additionalDuration time.Duration) error + GetExpirationDate(domainName string) time.Time + SetExpirationDate(domainName string, expDate time.Time) bool +} + +// NewDomainRegistry creates a new domain registry with metadata extensions +func NewDomainRegistry(name, symbol string) *domainRegistry { + registry := grc721.NewBasicNFT(name, symbol) + + return &domainRegistry{ + domains: registry, + metadata: avl.NewTree(), + } +} + +// RegisterDomain registers a new domain with the given metadata +func (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error { + err := d.domains.Mint(owner, grc721.TokenID(domainName)) + if err != nil { + return err + } + d.expDate = time.Now().Add(dur) + d.metadata.Set(domainName, metadata) + + return nil +} + +// RenewDomain extends the expiration time of a domain name +func (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error { + _, found := d.metadata.Get(domainName) + if !found { + return ErrInvalidDomainName + } + + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + // set new expiration date + d.expDate = d.expDate.Add(additionalDuration) + return nil +} + +// SetDomainData sets the metadata for a given domain name +func (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error { + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + d.metadata.Set(domainName, metadata) + return nil +} + +// GetDomainFields retrieves multiple fields of metadata for a given domain +func (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + if len(fields) == 0 { + return metadata, nil + } + + var result Metadata + for _, field := range fields { + switch field { + case FieldAvatar: + result.Avatar = metadata.Avatar + case FieldRegistrationTime: + result.RegistrationTime = metadata.RegistrationTime + case FieldExpirationTime: + result.ExpirationTime = metadata.ExpirationTime + case FieldRenewalFee: + result.RenewalFee = metadata.RenewalFee + case FieldAttributes: + result.Attributes = metadata.Attributes + case FieldDescription: + result.Description = metadata.Description + case FieldContactInfo: + result.ContactInfo = metadata.ContactInfo + default: + return Metadata{}, ErrInvalidMetadataField + } + } + + return result, nil +} + +// GetDomainData retrieves metadata for a given domain +func (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + switch field { + case FieldAvatar: + return Metadata{ + Avatar: metadata.Avatar, + }, nil + case FieldRegistrationTime: + return Metadata{ + RegistrationTime: metadata.RegistrationTime, + }, nil + case FieldExpirationTime: + return Metadata{ + ExpirationTime: metadata.ExpirationTime, + }, nil + case FieldRenewalFee: + return Metadata{ + RenewalFee: metadata.RenewalFee, + }, nil + case FieldAttributes: + return Metadata{ + Attributes: metadata.Attributes, + }, nil + case FieldDescription: + return Metadata{ + Description: metadata.Description, + }, nil + case FieldContactInfo: + return Metadata{ + ContactInfo: metadata.ContactInfo, + }, nil + default: + return Metadata{}, ErrInvalidMetadataField + } +} + +// BalanceOf returns the number of domains owned by a given address +func (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) { + return d.domains.BalanceOf(owner) +} + +// OwnerOf returns the owner of a given domain name +func (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) { + return d.domains.OwnerOf(grc721.TokenID(domainName)) +} + +// SafeTransferFrom safely transfers a domain from one address to another +func (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error { + return d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName)) +} + +// TransferFrom transfers a domain from one address to another +func (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error { + return d.domains.TransferFrom(from, to, grc721.TokenID(domainName)) +} + +// Approve grants approval to another address to manage a specific domain +func (d *domainRegistry) Approve(approved std.Address, domainName string) error { + return d.domains.Approve(approved, grc721.TokenID(domainName)) +} + +// SetApprovalForAll sets approval for an operator to manage all domains of the owner +func (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error { + return d.domains.SetApprovalForAll(operator, approved) +} + +// GetApproved returns the approved address for a specific domain +func (d *domainRegistry) GetApproved(domainName string) (std.Address, error) { + return d.domains.GetApproved(grc721.TokenID(domainName)) +} + +// IsApprovedForAll checks if an operator is approved to manage all domains of the owner +func (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool { + return d.domains.IsApprovedForAll(owner, operator) +} + +// Mint creates a new domain for a given address +func (d *domainRegistry) Mint(to std.Address, domainName string) error { + return d.domains.Mint(to, grc721.TokenID(domainName)) +} + +func (d *domainRegistry) GetExpirationDate(domainName string) time.Time { + return d.expDate +} + +func (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool { + _, found := d.metadata.Get(domainName) + if !found { + return false + } + d.expDate = expDate + return true +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_registry_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_registry_test.gno new file mode 100644 index 00000000..168a97c7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/domain_registry_test.gno @@ -0,0 +1,378 @@ +package domain + +import ( + "std" + "testing" + "time" + + "gno.land/p/varmeta/demo/v32/grc/grc721" + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" +) + +var ( + addr1 = testutils.TestAddress("bob") + addr2 = testutils.TestAddress("alice") +) + +func TestRegisterDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Successful Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "contact@registered.com", + }, + expectError: false, + }, + { + name: "Duplicate Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + err := registry.RegisterDomain(c.owner, c.domainName, c.metadata) + if c.expectError { + urequire.Error(t, err) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.owner, retrievedOwner) + } + }) + } +} + +func TestSetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Owner Sets Metadata", + owner: addr1, + caller: addr1, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: false, + }, + { + name: "Non-Owner Sets Metadata", + owner: addr1, + caller: addr2, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + registry.RegisterDomain(c.owner, c.domainName, c.metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + err := registry.SetDomainData(c.domainName, c.metadata) + + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + retrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar) + urequire.NoError(t, err) + urequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar) + } + }) + } +} + +func TestRenewDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + additionalTime time.Duration + expectError bool + expectedExpiry time.Time + }{ + { + name: "Successful Renewal", + owner: addr1, + caller: addr1, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: false, + }, + { + name: "Non-Owner Attempts Renewal", + owner: addr1, + caller: addr2, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A renewable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.RenewDomain(c.domainName, c.additionalTime) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + renewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime) + urequire.NoError(t, err) + // urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime)) + } + }) + } +} + +func TestGetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + field MetadataField + expectError bool + expectedVal string + }{ + { + name: "Retrieve Avatar", + domainName: "test.gno", + field: FieldAvatar, + expectError: false, + expectedVal: "avatar_url", + }, + { + name: "Invalid Domain Name", + domainName: "invalid.gno", + field: FieldAvatar, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + _, err := registry.GetDomainData(c.domainName, c.field) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + } + }) + } +} + +func TestGetDomainFields(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + fields []MetadataField + expectError bool + expected Metadata + }{ + { + name: "Retrieve Multiple Fields", + domainName: "test.gno", + fields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo}, + expectError: false, + expected: Metadata{ + Avatar: "avatar_url", + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + }, + { + name: "Invalid Domain", + domainName: "invalid.gno", + fields: []MetadataField{FieldAvatar}, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + retrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + urequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar) + urequire.Equal(t, c.expected.Description, retrievedMetadata.Description) + urequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo) + } + }) + } +} + +func TestTransferDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + newOwner std.Address + caller std.Address + domainName string + expectError bool + }{ + { + name: "Successful Transfer", + owner: addr1, + newOwner: addr2, + caller: addr1, + domainName: "transfer.gno", + expectError: false, + }, + { + name: "Non-Owner Attempts Transfer", + owner: addr1, + newOwner: addr2, + caller: addr2, + domainName: "transfer.gno", + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A transferable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.TransferFrom(c.owner, c.newOwner, c.domainName) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error()) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.newOwner, retrievedOwner) + } + }) + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/domain/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/errors.gno new file mode 100644 index 00000000..3de5d750 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/errors.gno @@ -0,0 +1,12 @@ +package domain + +import ( + "errors" +) + +var ( + ErrUnauthorized = errors.New("caller is not domain owner") + ErrInvalidDomainName = errors.New("invalid domain name") + ErrInvalidMetadataField = errors.New("invalid metadata field") + ErrInsufficientFunds = errors.New("insufficient funds for renewal") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/domain/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/domain/utils.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/utils.gno new file mode 100644 index 00000000..13e40d99 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/domain/utils.gno @@ -0,0 +1,13 @@ +package domain + +type MetadataField int + +const ( + FieldAvatar MetadataField = iota + FieldRegistrationTime + FieldRenewalFee + FieldExpirationTime + FieldAttributes + FieldDescription + FieldContactInfo +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/basic_nft.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/basic_nft.gno new file mode 100644 index 00000000..bec7338d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/basic_nft.gno @@ -0,0 +1,378 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +type basicNFT struct { + name string + symbol string + owners avl.Tree // tokenId -> OwnerAddress + balances avl.Tree // OwnerAddress -> TokenCount + tokenApprovals avl.Tree // TokenId -> ApprovedAddress + tokenURIs avl.Tree // TokenId -> URIs + operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool +} + +// Returns new basic NFT +func NewBasicNFT(name string, symbol string) *basicNFT { + return &basicNFT{ + name: name, + symbol: symbol, + + owners: avl.Tree{}, + balances: avl.Tree{}, + tokenApprovals: avl.Tree{}, + tokenURIs: avl.Tree{}, + operatorApprovals: avl.Tree{}, + } +} + +func (s *basicNFT) Name() string { return s.name } +func (s *basicNFT) Symbol() string { return s.symbol } +func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) } + +// BalanceOf returns balance of input address +func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) { + if err := isValidAddress(addr); err != nil { + return 0, err + } + + balance, found := s.balances.Get(addr.String()) + if !found { + return 0, nil + } + + return balance.(uint64), nil +} + +// OwnerOf returns owner of input token id +func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) { + owner, found := s.owners.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return owner.(std.Address), nil +} + +// TokenURI returns the URI of input token id +func (s *basicNFT) TokenURI(tid TokenID) (string, error) { + uri, found := s.tokenURIs.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return uri.(string), nil +} + +func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + // check for invalid TokenID + if !s.exists(tid) { + return false, ErrInvalidTokenId + } + + // check for the right owner + owner, err := s.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + s.tokenURIs.Set(string(tid), string(tURI)) + return true, nil +} + +// IsApprovedForAll returns true if operator is approved for all by the owner. +// Otherwise, returns false +func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool { + key := owner.String() + ":" + operator.String() + _, found := s.operatorApprovals.Get(key) + if !found { + return false + } + + return true +} + +// Approve approves the input address for particular token +func (s *basicNFT) Approve(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner == to { + return ErrApprovalToCurrentOwner + } + + caller := std.PrevRealm().Addr() + if caller != owner && !s.IsApprovedForAll(owner, caller) { + return ErrCallerIsNotOwnerOrApproved + } + + s.tokenApprovals.Set(string(tid), to.String()) + event := ApprovalEvent{owner, to, tid} + emit(&event) + + return nil +} + +// GetApproved return the approved address for token +func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) { + addr, found := s.tokenApprovals.Get(string(tid)) + if !found { + return zeroAddress, ErrTokenIdNotHasApproved + } + + return std.Address(addr.(string)), nil +} + +// SetApprovalForAll can approve the operator to operate on all tokens +func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error { + if err := isValidAddress(operator); err != nil { + return ErrInvalidAddress + } + + caller := std.PrevRealm().Addr() + return s.setApprovalForAll(caller, operator, approved) +} + +// Safely transfers `tokenId` token from `from` to `to`, checking that +// contract recipients are aware of the GRC721 protocol to prevent +// tokens from being forever locked. +func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(from, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +// Transfers `tokenId` token from `from` to `to`. +func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + return nil +} + +// Mints `tokenId` and transfers it to `to`. +func (s *basicNFT) Mint(to std.Address, tid TokenID) error { + return s.mint(to, tid) +} + +// Mints `tokenId` and transfers it to `to`. Also checks that +// contract recipients are using GRC721 protocol +func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error { + err := s.mint(to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(zeroAddress, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +func (s *basicNFT) Burn(tid TokenID) error { + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + + s.beforeTokenTransfer(owner, zeroAddress, tid, 1) + + s.tokenApprovals.Remove(string(tid)) + balance, err := s.BalanceOf(owner) + if err != nil { + return err + } + balance -= 1 + s.balances.Set(owner.String(), balance) + s.owners.Remove(string(tid)) + + event := TransferEvent{owner, zeroAddress, tid} + emit(&event) + + s.afterTokenTransfer(owner, zeroAddress, tid, 1) + + return nil +} + +/* Helper methods */ + +// Helper for SetApprovalForAll() +func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error { + if owner == operator { + return ErrApprovalToCurrentOwner + } + + key := owner.String() + ":" + operator.String() + s.operatorApprovals.Set(key, approved) + + event := ApprovalForAllEvent{owner, operator, approved} + emit(&event) + + return nil +} + +// Helper for TransferFrom() and SafeTransferFrom() +func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error { + if err := isValidAddress(from); err != nil { + return ErrInvalidAddress + } + if err := isValidAddress(to); err != nil { + return ErrInvalidAddress + } + + if from == to { + return ErrCannotTransferToSelf + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.beforeTokenTransfer(from, to, tid, 1) + + // Check that tokenId was not transferred by `beforeTokenTransfer` + owner, err = s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.tokenApprovals.Remove(string(tid)) + fromBalance, err := s.BalanceOf(from) + if err != nil { + return err + } + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + fromBalance -= 1 + toBalance += 1 + s.balances.Set(from.String(), fromBalance) + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{from, to, tid} + emit(&event) + + s.afterTokenTransfer(from, to, tid, 1) + + return nil +} + +// Helper for Mint() and SafeMint() +func (s *basicNFT) mint(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check that tokenId was not minted by `beforeTokenTransfer` + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} + +func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool { + owner, found := s.owners.Get(string(tid)) + if !found { + return false + } + + if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) { + return true + } + + _, err := s.GetApproved(tid) + if err != nil { + return false + } + + return true +} + +// Checks if token id already exists +func (s *basicNFT) exists(tid TokenID) bool { + _, found := s.owners.Get(string(tid)) + return found +} + +func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool { + // TODO: Implementation + return true +} + +func (s *basicNFT) RenderHome() (str string) { + str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol) + str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount()) + str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size()) + + return +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/basic_nft_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/basic_nft_test.gno new file mode 100644 index 00000000..6375b030 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/basic_nft_test.gno @@ -0,0 +1,283 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +var ( + dummyNFTName = "DummyNFT" + dummyNFTSymbol = "DNFT" +) + +func TestNewBasicNFT(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") +} + +func TestName(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + name := dummy.Name() + uassert.Equal(t, dummyNFTName, name) +} + +func TestSymbol(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + symbol := dummy.Symbol() + uassert.Equal(t, dummyNFTSymbol, symbol) +} + +func TestTokenCount(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + count := dummy.TokenCount() + uassert.Equal(t, uint64(0), count) + + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("1")) + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("2")) + + count = dummy.TokenCount() + uassert.Equal(t, uint64(2), count) +} + +func TestBalanceOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + balanceAddr1, err := dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceAddr1) + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr1, TokenID("2")) + dummy.mint(addr2, TokenID("3")) + + balanceAddr1, err = dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + + balanceAddr2, err := dummy.BalanceOf(addr2) + uassert.NoError(t, err, "should not result in error") + + uassert.Equal(t, uint64(2), balanceAddr1) + uassert.Equal(t, uint64(1), balanceAddr2) +} + +func TestOwnerOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + owner, err := dummy.OwnerOf(TokenID("invalid")) + uassert.Error(t, err, "should not result in error") + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr2, TokenID("2")) + + // Checking for token id "1" + owner, err = dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) + + // Checking for token id "2" + owner, err = dummy.OwnerOf(TokenID("2")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr2.String(), owner.String()) +} + +func TestIsApprovedForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) + uassert.False(t, isApprovedForAll) +} + +func TestSetApprovalForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + isApprovedForAll := dummy.IsApprovedForAll(caller, addr) + uassert.False(t, isApprovedForAll) + + err := dummy.SetApprovalForAll(addr, true) + uassert.NoError(t, err, "should not result in error") + + isApprovedForAll = dummy.IsApprovedForAll(caller, addr) + uassert.True(t, isApprovedForAll) +} + +func TestGetApproved(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + approvedAddr, err := dummy.GetApproved(TokenID("invalid")) + uassert.Error(t, err, "should result in error") +} + +func TestApprove(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + + _, err := dummy.GetApproved(TokenID("1")) + uassert.Error(t, err, "should result in error") + + err = dummy.Approve(addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + approvedAddr, err := dummy.GetApproved(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), approvedAddr.String()) +} + +func TestTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.TransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestSafeTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.SafeTransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestMint(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + err := dummy.Mint(addr1, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr1, TokenID("2")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr2, TokenID("3")) + uassert.NoError(t, err, "should not result in error") + + // Try minting duplicate token id + err = dummy.Mint(addr2, TokenID("1")) + uassert.Error(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) +} + +func TestBurn(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(addr, TokenID("1")) + dummy.mint(addr, TokenID("2")) + + err := dummy.Burn(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.Error(t, err, "should result in error") +} + +func TestSetTokenURI(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + tokenURI := "http://example.com/token" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1, TokenID("1")) + _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) + uassert.NoError(t, derr, "should not result in error") + + // Test case: Invalid token ID + _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Retrieving TokenURI + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyTokenURI, err := dummy.TokenURI(TokenID("1")) + uassert.NoError(t, err, "TokenURI error") + uassert.Equal(t, string(tokenURI), string(dummyTokenURI)) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/errors.gno new file mode 100644 index 00000000..2d512db3 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/errors.gno @@ -0,0 +1,22 @@ +package grc721 + +import "errors" + +var ( + ErrInvalidTokenId = errors.New("invalid token id") + ErrInvalidAddress = errors.New("invalid address") + ErrTokenIdNotHasApproved = errors.New("token id not approved for anyone") + ErrApprovalToCurrentOwner = errors.New("approval to current owner") + ErrCallerIsNotOwner = errors.New("caller is not token owner") + ErrCallerNotApprovedForAll = errors.New("caller is not approved for all") + ErrCannotTransferToSelf = errors.New("cannot send transfer to self") + ErrTransferFromIncorrectOwner = errors.New("transfer from incorrect owner") + ErrTransferToNonGRC721Receiver = errors.New("transfer to non GRC721Receiver implementer") + ErrCallerIsNotOwnerOrApproved = errors.New("caller is not token owner or approved") + ErrTokenIdAlreadyExists = errors.New("token id already exists") + + // ERC721Royalty + ErrInvalidRoyaltyPercentage = errors.New("invalid royalty percentage") + ErrInvalidRoyaltyPaymentAddress = errors.New("invalid royalty paymentAddress") + ErrCannotCalculateRoyaltyAmount = errors.New("cannot calculate royalty amount") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_metadata.gno new file mode 100644 index 00000000..360f73ed --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_metadata.gno @@ -0,0 +1,95 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// metadataNFT represents an NFT with metadata extensions. +type metadataNFT struct { + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions *avl.Tree // AVL tree for storing metadata extensions +} + +// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. +var _ IGRC721MetadataOnchain = (*metadataNFT)(nil) + +// NewNFTWithMetadata creates a new basic NFT with metadata extensions. +func NewNFTWithMetadata(name string, symbol string) *metadataNFT { + // Create a new basic NFT + nft := NewBasicNFT(name, symbol) + + // Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions + return &metadataNFT{ + basicNFT: nft, + extensions: avl.NewTree(), + } +} + +// SetTokenMetadata sets metadata for a given token ID. +func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { + // Check if the caller is the owner of the token + owner, err := s.basicNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set the metadata for the token ID in the extensions AVL tree + s.extensions.Set(string(tid), metadata) + return nil +} + +// TokenMetadata retrieves metadata for a given token ID. +func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { + // Retrieve metadata from the extensions AVL tree + metadata, found := s.extensions.Get(string(tid)) + if !found { + return Metadata{}, ErrInvalidTokenId + } + + return metadata.(Metadata), nil +} + +// mint mints a new token and assigns it to the specified address. +func (s *metadataNFT) mint(to std.Address, tid TokenID) error { + // Check if the address is valid + if err := isValidAddress(to); err != nil { + return err + } + + // Check if the token ID already exists + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check if the token ID was minted by beforeTokenTransfer + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + // Increment balance of the recipient address + toBalance, err := s.basicNFT.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.basicNFT.balances.Set(to.String(), toBalance) + + // Set owner of the token ID to the recipient address + s.basicNFT.owners.Set(string(tid), to) + + // Emit transfer event + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_metadata_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_metadata_test.gno new file mode 100644 index 00000000..ad002a7c --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_metadata_test.gno @@ -0,0 +1,107 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetMetadata(t *testing.T) { + // Create a new dummy NFT with metadata + dummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + // Define addresses for testing purposes + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + // Define metadata attributes + name := "test" + description := "test" + image := "test" + imageData := "test" + externalURL := "test" + attributes := []Trait{} + backgroundColor := "test" + animationURL := "test" + youtubeURL := "test" + + // Set the original caller to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Mint a new token for addr1 + dummy.mint(addr1, TokenID("1")) + + // Set metadata for token 1 + derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if there was an error setting metadata + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + // Set the original caller to addr2 + std.TestSetOrigCaller(addr2) // addr2 + + // Try to set metadata for token 1 from addr2 (should fail) + cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Set the original caller back to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Retrieve metadata for token 1 + dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) + uassert.NoError(t, err, "Metadata error") + + // Check if metadata attributes match expected values + uassert.Equal(t, image, dummyMetadata.Image) + uassert.Equal(t, imageData, dummyMetadata.ImageData) + uassert.Equal(t, externalURL, dummyMetadata.ExternalURL) + uassert.Equal(t, description, dummyMetadata.Description) + uassert.Equal(t, name, dummyMetadata.Name) + uassert.Equal(t, len(attributes), len(dummyMetadata.Attributes)) + uassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor) + uassert.Equal(t, animationURL, dummyMetadata.AnimationURL) + uassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_royalty.gno new file mode 100644 index 00000000..9831c709 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_royalty.gno @@ -0,0 +1,78 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// royaltyNFT represents a non-fungible token (NFT) with royalty functionality. +type royaltyNFT struct { + *metadataNFT // Embedding metadataNFT for NFT functionality + tokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token + maxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale +} + +// Ensure that royaltyNFT implements the IGRC2981 interface. +var _ IGRC2981 = (*royaltyNFT)(nil) + +// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. +func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { + // Create a new NFT with metadata + nft := NewNFTWithMetadata(name, symbol) + + return &royaltyNFT{ + metadataNFT: nft, + tokenRoyaltyInfo: avl.NewTree(), + maxRoyaltyPercentage: 100, + } +} + +// SetTokenRoyalty sets the royalty information for a specific token ID. +func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error { + // Validate the payment address + if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { + return ErrInvalidRoyaltyPaymentAddress + } + + // Check if royalty percentage exceeds maxRoyaltyPercentage + if royaltyInfo.Percentage > r.maxRoyaltyPercentage { + return ErrInvalidRoyaltyPercentage + } + + // Check if the caller is the owner of the token + owner, err := r.metadataNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set royalty information for the token + r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) + + return nil +} + +// RoyaltyInfo returns the royalty information for the given token ID and sale price. +func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) { + // Retrieve royalty information for the token + val, found := r.tokenRoyaltyInfo.Get(string(tid)) + if !found { + return "", 0, ErrInvalidTokenId + } + + royaltyInfo := val.(RoyaltyInfo) + + // Calculate royalty amount + royaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) + + return royaltyInfo.PaymentAddress, royaltyAmount, nil +} + +func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + return royaltyAmount, nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_royalty_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_royalty_test.gno new file mode 100644 index 00000000..7893453a --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/grc721_royalty_test.gno @@ -0,0 +1,70 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetTokenRoyalty(t *testing.T) { + dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + paymentAddress := testutils.TestAddress("john") + percentage := uint64(10) // 10% + + salePrice := uint64(1000) + expectRoyaltyAmount := uint64(100) + + std.TestSetOrigCaller(addr1) // addr1 + + dummy.mint(addr1, TokenID("1")) + + derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, derr, ErrInvalidTokenId) + + std.TestSetOrigCaller(addr2) // addr2 + + cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Invalid payment address + aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + PaymentAddress: std.Address("###"), // invalid address + Percentage: percentage, + }) + uassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress) + + // Test case: Invalid percentage + perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: uint64(200), // over maxRoyaltyPercentage + }) + uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) + + // Test case: Retrieving Royalty Info + std.TestSetOrigCaller(addr1) // addr1 + + dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) + uassert.NoError(t, rerr, "RoyaltyInfo error") + uassert.Equal(t, paymentAddress, dummyPaymentAddress) + uassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721.gno new file mode 100644 index 00000000..ece48f8e --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721.gno @@ -0,0 +1,39 @@ +package grc721 + +import "std" + +type IGRC721 interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(tid TokenID) (std.Address, error) + SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) + SafeTransferFrom(from, to std.Address, tid TokenID) error + TransferFrom(from, to std.Address, tid TokenID) error + Approve(approved std.Address, tid TokenID) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(tid TokenID) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, tid TokenID) error +} + +type ( + TokenID string + TokenURI string +) + +type TransferEvent struct { + From std.Address + To std.Address + TokenID TokenID +} + +type ApprovalEvent struct { + Owner std.Address + Approved std.Address + TokenID TokenID +} + +type ApprovalForAllEvent struct { + Owner std.Address + Operator std.Address + Approved bool +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721_metadata.gno new file mode 100644 index 00000000..8a2be1ff --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721_metadata.gno @@ -0,0 +1,38 @@ +package grc721 + +// IGRC721CollectionMetadata describes basic information about an NFT collection. +type IGRC721CollectionMetadata interface { + Name() string // Name returns the name of the collection. + Symbol() string // Symbol returns the symbol of the collection. +} + +// IGRC721Metadata follows the Ethereum standard +type IGRC721Metadata interface { + IGRC721CollectionMetadata + TokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token. +} + +// IGRC721Metadata follows the OpenSea metadata standard +type IGRC721MetadataOnchain interface { + IGRC721CollectionMetadata + TokenMetadata(tid TokenID) (Metadata, error) +} + +type Trait struct { + DisplayType string + TraitType string + Value string +} + +// see: https://docs.opensea.io/docs/metadata-standards +type Metadata struct { + Image string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image. + ImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter. + ExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site. + Description string // Human-readable description of the item. Markdown is supported. + Name string // Name of the item. + Attributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item. + BackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended # + AnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported. + YoutubeURL string // URL to a YouTube video (only used if animation_url is not provided). +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721_royalty.gno new file mode 100644 index 00000000..c4603d63 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/igrc721_royalty.gno @@ -0,0 +1,16 @@ +package grc721 + +import "std" + +// IGRC2981 follows the Ethereum standard +type IGRC2981 interface { + // RoyaltyInfo retrieves royalty information for a tokenID and salePrice. + // It returns the payment address, royalty amount, and an error if any. + RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) +} + +// RoyaltyInfo represents royalty information for a token. +type RoyaltyInfo struct { + PaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent. + Percentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 => 10% +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/util.gno b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/util.gno new file mode 100644 index 00000000..bb6bf24d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v32/grc/grc721/util.gno @@ -0,0 +1,18 @@ +package grc721 + +import ( + "std" +) + +var zeroAddress = std.Address("") + +func isValidAddress(addr std.Address) error { + if !addr.IsValid() { + return ErrInvalidAddress + } + return nil +} + +func emit(event interface{}) { + // TODO: setup a pubsub system here? +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_metadata.gno new file mode 100644 index 00000000..388902c4 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_metadata.gno @@ -0,0 +1,38 @@ +package domain + +import ( + "time" +) + +// Trait represents a key-value pair with an optional display type for metadata attributes +type Trait struct { + DisplayType string // Optional display type (e.g., "date", "number", etc.) + TraitType string // Type of the trait (e.g., "age", "height", etc.) + Value string // Value of the trait +} + +// Metadata represents the metadata associated with a domain +type Metadata struct { + Avatar string // URL or identifier for an avatar image + RegistrationTime time.Time // The time when the domain was registered + ExpirationTime time.Time // The time when the domain will be expire + Attributes []Trait // Additional attributes of the domain + Description string // A description of the domain + ContactInfo string // Contact information for the domain owner + RenewalFee string // The fee required to renew the domain, represented as a string +} + +// NewMetadata creates a new Metadata instance +func NewMetadata(avatar, description, contactInfo, renewalFee string, + registrationTime, expirationTime time.Time, attributes []Trait, +) Metadata { + return Metadata{ + Avatar: avatar, + RegistrationTime: registrationTime, + ExpirationTime: expirationTime, + RenewalFee: renewalFee, + Attributes: attributes, + Description: description, + ContactInfo: contactInfo, + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_registry.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_registry.gno new file mode 100644 index 00000000..9b87882f --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_registry.gno @@ -0,0 +1,236 @@ +package domain + +import ( + "std" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v33/grc/grc721" +) + +// domainRegistry represents a registry for domain names with metadata +type domainRegistry struct { + domains grc721.IGRC721 // Interface for basic NFT functionality + metadata *avl.Tree // AVL tree for storing domain metadata + expDate time.Time +} + +// DomainRegistry defines the methods for managing domain names and metadata +type DomainRegistry interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(domainName string) (std.Address, error) + SafeTransferFrom(from, to std.Address, domainName string) error + TransferFrom(from, to std.Address, domainName string) error + Approve(approved std.Address, domainName string) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(domainName string) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, domainName string) error + + RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error + SetDomainData(domainName string, metadata Metadata) error + GetDomainData(domainName string, field MetadataField) (Metadata, error) + GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) + RenewDomain(domainName string, additionalDuration time.Duration) error + GetExpirationDate(domainName string) time.Time + SetExpirationDate(domainName string, expDate time.Time) bool +} + +// NewDomainRegistry creates a new domain registry with metadata extensions +func NewDomainRegistry(name, symbol string) *domainRegistry { + registry := grc721.NewBasicNFT(name, symbol) + + return &domainRegistry{ + domains: registry, + metadata: avl.NewTree(), + } +} + +// RegisterDomain registers a new domain with the given metadata +func (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error { + err := d.domains.Mint(owner, grc721.TokenID(domainName)) + if err != nil { + return err + } + d.expDate = time.Now().Add(dur) + d.metadata.Set(domainName, metadata) + + return nil +} + +// RenewDomain extends the expiration time of a domain name +func (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error { + _, found := d.metadata.Get(domainName) + if !found { + return ErrInvalidDomainName + } + + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + // set new expiration date + d.expDate = d.expDate.Add(additionalDuration) + return nil +} + +// SetDomainData sets the metadata for a given domain name +func (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error { + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + d.metadata.Set(domainName, metadata) + return nil +} + +// GetDomainFields retrieves multiple fields of metadata for a given domain +func (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + if len(fields) == 0 { + return metadata, nil + } + + var result Metadata + for _, field := range fields { + switch field { + case FieldAvatar: + result.Avatar = metadata.Avatar + case FieldRegistrationTime: + result.RegistrationTime = metadata.RegistrationTime + case FieldExpirationTime: + result.ExpirationTime = metadata.ExpirationTime + case FieldRenewalFee: + result.RenewalFee = metadata.RenewalFee + case FieldAttributes: + result.Attributes = metadata.Attributes + case FieldDescription: + result.Description = metadata.Description + case FieldContactInfo: + result.ContactInfo = metadata.ContactInfo + default: + return Metadata{}, ErrInvalidMetadataField + } + } + + return result, nil +} + +// GetDomainData retrieves metadata for a given domain +func (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + switch field { + case FieldAvatar: + return Metadata{ + Avatar: metadata.Avatar, + }, nil + case FieldRegistrationTime: + return Metadata{ + RegistrationTime: metadata.RegistrationTime, + }, nil + case FieldExpirationTime: + return Metadata{ + ExpirationTime: metadata.ExpirationTime, + }, nil + case FieldRenewalFee: + return Metadata{ + RenewalFee: metadata.RenewalFee, + }, nil + case FieldAttributes: + return Metadata{ + Attributes: metadata.Attributes, + }, nil + case FieldDescription: + return Metadata{ + Description: metadata.Description, + }, nil + case FieldContactInfo: + return Metadata{ + ContactInfo: metadata.ContactInfo, + }, nil + default: + return Metadata{}, ErrInvalidMetadataField + } +} + +// BalanceOf returns the number of domains owned by a given address +func (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) { + return d.domains.BalanceOf(owner) +} + +// OwnerOf returns the owner of a given domain name +func (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) { + return d.domains.OwnerOf(grc721.TokenID(domainName)) +} + +// SafeTransferFrom safely transfers a domain from one address to another +func (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error { + return d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName)) +} + +// TransferFrom transfers a domain from one address to another +func (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error { + return d.domains.TransferFrom(from, to, grc721.TokenID(domainName)) +} + +// Approve grants approval to another address to manage a specific domain +func (d *domainRegistry) Approve(approved std.Address, domainName string) error { + return d.domains.Approve(approved, grc721.TokenID(domainName)) +} + +// SetApprovalForAll sets approval for an operator to manage all domains of the owner +func (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error { + return d.domains.SetApprovalForAll(operator, approved) +} + +// GetApproved returns the approved address for a specific domain +func (d *domainRegistry) GetApproved(domainName string) (std.Address, error) { + return d.domains.GetApproved(grc721.TokenID(domainName)) +} + +// IsApprovedForAll checks if an operator is approved to manage all domains of the owner +func (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool { + return d.domains.IsApprovedForAll(owner, operator) +} + +// Mint creates a new domain for a given address +func (d *domainRegistry) Mint(to std.Address, domainName string) error { + return d.domains.Mint(to, grc721.TokenID(domainName)) +} + +func (d *domainRegistry) GetExpirationDate(domainName string) time.Time { + return d.expDate +} + +func (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool { + _, found := d.metadata.Get(domainName) + if !found { + return false + } + d.expDate = expDate + return true +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_registry_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_registry_test.gno new file mode 100644 index 00000000..f6ef1892 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/domain_registry_test.gno @@ -0,0 +1,378 @@ +package domain + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" + "gno.land/p/varmeta/demo/v33/grc/grc721" +) + +var ( + addr1 = testutils.TestAddress("bob") + addr2 = testutils.TestAddress("alice") +) + +func TestRegisterDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Successful Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "contact@registered.com", + }, + expectError: false, + }, + { + name: "Duplicate Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + err := registry.RegisterDomain(c.owner, c.domainName, c.metadata) + if c.expectError { + urequire.Error(t, err) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.owner, retrievedOwner) + } + }) + } +} + +func TestSetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Owner Sets Metadata", + owner: addr1, + caller: addr1, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: false, + }, + { + name: "Non-Owner Sets Metadata", + owner: addr1, + caller: addr2, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + registry.RegisterDomain(c.owner, c.domainName, c.metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + err := registry.SetDomainData(c.domainName, c.metadata) + + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + retrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar) + urequire.NoError(t, err) + urequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar) + } + }) + } +} + +func TestRenewDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + additionalTime time.Duration + expectError bool + expectedExpiry time.Time + }{ + { + name: "Successful Renewal", + owner: addr1, + caller: addr1, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: false, + }, + { + name: "Non-Owner Attempts Renewal", + owner: addr1, + caller: addr2, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A renewable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.RenewDomain(c.domainName, c.additionalTime) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + renewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime) + urequire.NoError(t, err) + // urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime)) + } + }) + } +} + +func TestGetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + field MetadataField + expectError bool + expectedVal string + }{ + { + name: "Retrieve Avatar", + domainName: "test.gno", + field: FieldAvatar, + expectError: false, + expectedVal: "avatar_url", + }, + { + name: "Invalid Domain Name", + domainName: "invalid.gno", + field: FieldAvatar, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + _, err := registry.GetDomainData(c.domainName, c.field) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + } + }) + } +} + +func TestGetDomainFields(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + fields []MetadataField + expectError bool + expected Metadata + }{ + { + name: "Retrieve Multiple Fields", + domainName: "test.gno", + fields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo}, + expectError: false, + expected: Metadata{ + Avatar: "avatar_url", + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + }, + { + name: "Invalid Domain", + domainName: "invalid.gno", + fields: []MetadataField{FieldAvatar}, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + retrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + urequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar) + urequire.Equal(t, c.expected.Description, retrievedMetadata.Description) + urequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo) + } + }) + } +} + +func TestTransferDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + newOwner std.Address + caller std.Address + domainName string + expectError bool + }{ + { + name: "Successful Transfer", + owner: addr1, + newOwner: addr2, + caller: addr1, + domainName: "transfer.gno", + expectError: false, + }, + { + name: "Non-Owner Attempts Transfer", + owner: addr1, + newOwner: addr2, + caller: addr2, + domainName: "transfer.gno", + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A transferable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.TransferFrom(c.owner, c.newOwner, c.domainName) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error()) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.newOwner, retrievedOwner) + } + }) + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/domain/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/errors.gno new file mode 100644 index 00000000..3de5d750 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/errors.gno @@ -0,0 +1,12 @@ +package domain + +import ( + "errors" +) + +var ( + ErrUnauthorized = errors.New("caller is not domain owner") + ErrInvalidDomainName = errors.New("invalid domain name") + ErrInvalidMetadataField = errors.New("invalid metadata field") + ErrInsufficientFunds = errors.New("insufficient funds for renewal") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/domain/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/domain/utils.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/utils.gno new file mode 100644 index 00000000..13e40d99 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/domain/utils.gno @@ -0,0 +1,13 @@ +package domain + +type MetadataField int + +const ( + FieldAvatar MetadataField = iota + FieldRegistrationTime + FieldRenewalFee + FieldExpirationTime + FieldAttributes + FieldDescription + FieldContactInfo +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/basic_nft.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/basic_nft.gno new file mode 100644 index 00000000..bec7338d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/basic_nft.gno @@ -0,0 +1,378 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +type basicNFT struct { + name string + symbol string + owners avl.Tree // tokenId -> OwnerAddress + balances avl.Tree // OwnerAddress -> TokenCount + tokenApprovals avl.Tree // TokenId -> ApprovedAddress + tokenURIs avl.Tree // TokenId -> URIs + operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool +} + +// Returns new basic NFT +func NewBasicNFT(name string, symbol string) *basicNFT { + return &basicNFT{ + name: name, + symbol: symbol, + + owners: avl.Tree{}, + balances: avl.Tree{}, + tokenApprovals: avl.Tree{}, + tokenURIs: avl.Tree{}, + operatorApprovals: avl.Tree{}, + } +} + +func (s *basicNFT) Name() string { return s.name } +func (s *basicNFT) Symbol() string { return s.symbol } +func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) } + +// BalanceOf returns balance of input address +func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) { + if err := isValidAddress(addr); err != nil { + return 0, err + } + + balance, found := s.balances.Get(addr.String()) + if !found { + return 0, nil + } + + return balance.(uint64), nil +} + +// OwnerOf returns owner of input token id +func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) { + owner, found := s.owners.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return owner.(std.Address), nil +} + +// TokenURI returns the URI of input token id +func (s *basicNFT) TokenURI(tid TokenID) (string, error) { + uri, found := s.tokenURIs.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return uri.(string), nil +} + +func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + // check for invalid TokenID + if !s.exists(tid) { + return false, ErrInvalidTokenId + } + + // check for the right owner + owner, err := s.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + s.tokenURIs.Set(string(tid), string(tURI)) + return true, nil +} + +// IsApprovedForAll returns true if operator is approved for all by the owner. +// Otherwise, returns false +func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool { + key := owner.String() + ":" + operator.String() + _, found := s.operatorApprovals.Get(key) + if !found { + return false + } + + return true +} + +// Approve approves the input address for particular token +func (s *basicNFT) Approve(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner == to { + return ErrApprovalToCurrentOwner + } + + caller := std.PrevRealm().Addr() + if caller != owner && !s.IsApprovedForAll(owner, caller) { + return ErrCallerIsNotOwnerOrApproved + } + + s.tokenApprovals.Set(string(tid), to.String()) + event := ApprovalEvent{owner, to, tid} + emit(&event) + + return nil +} + +// GetApproved return the approved address for token +func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) { + addr, found := s.tokenApprovals.Get(string(tid)) + if !found { + return zeroAddress, ErrTokenIdNotHasApproved + } + + return std.Address(addr.(string)), nil +} + +// SetApprovalForAll can approve the operator to operate on all tokens +func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error { + if err := isValidAddress(operator); err != nil { + return ErrInvalidAddress + } + + caller := std.PrevRealm().Addr() + return s.setApprovalForAll(caller, operator, approved) +} + +// Safely transfers `tokenId` token from `from` to `to`, checking that +// contract recipients are aware of the GRC721 protocol to prevent +// tokens from being forever locked. +func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(from, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +// Transfers `tokenId` token from `from` to `to`. +func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + return nil +} + +// Mints `tokenId` and transfers it to `to`. +func (s *basicNFT) Mint(to std.Address, tid TokenID) error { + return s.mint(to, tid) +} + +// Mints `tokenId` and transfers it to `to`. Also checks that +// contract recipients are using GRC721 protocol +func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error { + err := s.mint(to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(zeroAddress, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +func (s *basicNFT) Burn(tid TokenID) error { + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + + s.beforeTokenTransfer(owner, zeroAddress, tid, 1) + + s.tokenApprovals.Remove(string(tid)) + balance, err := s.BalanceOf(owner) + if err != nil { + return err + } + balance -= 1 + s.balances.Set(owner.String(), balance) + s.owners.Remove(string(tid)) + + event := TransferEvent{owner, zeroAddress, tid} + emit(&event) + + s.afterTokenTransfer(owner, zeroAddress, tid, 1) + + return nil +} + +/* Helper methods */ + +// Helper for SetApprovalForAll() +func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error { + if owner == operator { + return ErrApprovalToCurrentOwner + } + + key := owner.String() + ":" + operator.String() + s.operatorApprovals.Set(key, approved) + + event := ApprovalForAllEvent{owner, operator, approved} + emit(&event) + + return nil +} + +// Helper for TransferFrom() and SafeTransferFrom() +func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error { + if err := isValidAddress(from); err != nil { + return ErrInvalidAddress + } + if err := isValidAddress(to); err != nil { + return ErrInvalidAddress + } + + if from == to { + return ErrCannotTransferToSelf + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.beforeTokenTransfer(from, to, tid, 1) + + // Check that tokenId was not transferred by `beforeTokenTransfer` + owner, err = s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.tokenApprovals.Remove(string(tid)) + fromBalance, err := s.BalanceOf(from) + if err != nil { + return err + } + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + fromBalance -= 1 + toBalance += 1 + s.balances.Set(from.String(), fromBalance) + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{from, to, tid} + emit(&event) + + s.afterTokenTransfer(from, to, tid, 1) + + return nil +} + +// Helper for Mint() and SafeMint() +func (s *basicNFT) mint(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check that tokenId was not minted by `beforeTokenTransfer` + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} + +func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool { + owner, found := s.owners.Get(string(tid)) + if !found { + return false + } + + if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) { + return true + } + + _, err := s.GetApproved(tid) + if err != nil { + return false + } + + return true +} + +// Checks if token id already exists +func (s *basicNFT) exists(tid TokenID) bool { + _, found := s.owners.Get(string(tid)) + return found +} + +func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool { + // TODO: Implementation + return true +} + +func (s *basicNFT) RenderHome() (str string) { + str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol) + str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount()) + str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size()) + + return +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/basic_nft_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/basic_nft_test.gno new file mode 100644 index 00000000..6375b030 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/basic_nft_test.gno @@ -0,0 +1,283 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +var ( + dummyNFTName = "DummyNFT" + dummyNFTSymbol = "DNFT" +) + +func TestNewBasicNFT(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") +} + +func TestName(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + name := dummy.Name() + uassert.Equal(t, dummyNFTName, name) +} + +func TestSymbol(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + symbol := dummy.Symbol() + uassert.Equal(t, dummyNFTSymbol, symbol) +} + +func TestTokenCount(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + count := dummy.TokenCount() + uassert.Equal(t, uint64(0), count) + + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("1")) + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("2")) + + count = dummy.TokenCount() + uassert.Equal(t, uint64(2), count) +} + +func TestBalanceOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + balanceAddr1, err := dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceAddr1) + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr1, TokenID("2")) + dummy.mint(addr2, TokenID("3")) + + balanceAddr1, err = dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + + balanceAddr2, err := dummy.BalanceOf(addr2) + uassert.NoError(t, err, "should not result in error") + + uassert.Equal(t, uint64(2), balanceAddr1) + uassert.Equal(t, uint64(1), balanceAddr2) +} + +func TestOwnerOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + owner, err := dummy.OwnerOf(TokenID("invalid")) + uassert.Error(t, err, "should not result in error") + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr2, TokenID("2")) + + // Checking for token id "1" + owner, err = dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) + + // Checking for token id "2" + owner, err = dummy.OwnerOf(TokenID("2")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr2.String(), owner.String()) +} + +func TestIsApprovedForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) + uassert.False(t, isApprovedForAll) +} + +func TestSetApprovalForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + isApprovedForAll := dummy.IsApprovedForAll(caller, addr) + uassert.False(t, isApprovedForAll) + + err := dummy.SetApprovalForAll(addr, true) + uassert.NoError(t, err, "should not result in error") + + isApprovedForAll = dummy.IsApprovedForAll(caller, addr) + uassert.True(t, isApprovedForAll) +} + +func TestGetApproved(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + approvedAddr, err := dummy.GetApproved(TokenID("invalid")) + uassert.Error(t, err, "should result in error") +} + +func TestApprove(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + + _, err := dummy.GetApproved(TokenID("1")) + uassert.Error(t, err, "should result in error") + + err = dummy.Approve(addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + approvedAddr, err := dummy.GetApproved(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), approvedAddr.String()) +} + +func TestTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.TransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestSafeTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.SafeTransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestMint(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + err := dummy.Mint(addr1, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr1, TokenID("2")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr2, TokenID("3")) + uassert.NoError(t, err, "should not result in error") + + // Try minting duplicate token id + err = dummy.Mint(addr2, TokenID("1")) + uassert.Error(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) +} + +func TestBurn(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(addr, TokenID("1")) + dummy.mint(addr, TokenID("2")) + + err := dummy.Burn(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.Error(t, err, "should result in error") +} + +func TestSetTokenURI(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + tokenURI := "http://example.com/token" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1, TokenID("1")) + _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) + uassert.NoError(t, derr, "should not result in error") + + // Test case: Invalid token ID + _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Retrieving TokenURI + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyTokenURI, err := dummy.TokenURI(TokenID("1")) + uassert.NoError(t, err, "TokenURI error") + uassert.Equal(t, string(tokenURI), string(dummyTokenURI)) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/errors.gno new file mode 100644 index 00000000..2d512db3 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/errors.gno @@ -0,0 +1,22 @@ +package grc721 + +import "errors" + +var ( + ErrInvalidTokenId = errors.New("invalid token id") + ErrInvalidAddress = errors.New("invalid address") + ErrTokenIdNotHasApproved = errors.New("token id not approved for anyone") + ErrApprovalToCurrentOwner = errors.New("approval to current owner") + ErrCallerIsNotOwner = errors.New("caller is not token owner") + ErrCallerNotApprovedForAll = errors.New("caller is not approved for all") + ErrCannotTransferToSelf = errors.New("cannot send transfer to self") + ErrTransferFromIncorrectOwner = errors.New("transfer from incorrect owner") + ErrTransferToNonGRC721Receiver = errors.New("transfer to non GRC721Receiver implementer") + ErrCallerIsNotOwnerOrApproved = errors.New("caller is not token owner or approved") + ErrTokenIdAlreadyExists = errors.New("token id already exists") + + // ERC721Royalty + ErrInvalidRoyaltyPercentage = errors.New("invalid royalty percentage") + ErrInvalidRoyaltyPaymentAddress = errors.New("invalid royalty paymentAddress") + ErrCannotCalculateRoyaltyAmount = errors.New("cannot calculate royalty amount") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_metadata.gno new file mode 100644 index 00000000..360f73ed --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_metadata.gno @@ -0,0 +1,95 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// metadataNFT represents an NFT with metadata extensions. +type metadataNFT struct { + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions *avl.Tree // AVL tree for storing metadata extensions +} + +// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. +var _ IGRC721MetadataOnchain = (*metadataNFT)(nil) + +// NewNFTWithMetadata creates a new basic NFT with metadata extensions. +func NewNFTWithMetadata(name string, symbol string) *metadataNFT { + // Create a new basic NFT + nft := NewBasicNFT(name, symbol) + + // Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions + return &metadataNFT{ + basicNFT: nft, + extensions: avl.NewTree(), + } +} + +// SetTokenMetadata sets metadata for a given token ID. +func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { + // Check if the caller is the owner of the token + owner, err := s.basicNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set the metadata for the token ID in the extensions AVL tree + s.extensions.Set(string(tid), metadata) + return nil +} + +// TokenMetadata retrieves metadata for a given token ID. +func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { + // Retrieve metadata from the extensions AVL tree + metadata, found := s.extensions.Get(string(tid)) + if !found { + return Metadata{}, ErrInvalidTokenId + } + + return metadata.(Metadata), nil +} + +// mint mints a new token and assigns it to the specified address. +func (s *metadataNFT) mint(to std.Address, tid TokenID) error { + // Check if the address is valid + if err := isValidAddress(to); err != nil { + return err + } + + // Check if the token ID already exists + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check if the token ID was minted by beforeTokenTransfer + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + // Increment balance of the recipient address + toBalance, err := s.basicNFT.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.basicNFT.balances.Set(to.String(), toBalance) + + // Set owner of the token ID to the recipient address + s.basicNFT.owners.Set(string(tid), to) + + // Emit transfer event + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_metadata_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_metadata_test.gno new file mode 100644 index 00000000..ad002a7c --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_metadata_test.gno @@ -0,0 +1,107 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetMetadata(t *testing.T) { + // Create a new dummy NFT with metadata + dummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + // Define addresses for testing purposes + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + // Define metadata attributes + name := "test" + description := "test" + image := "test" + imageData := "test" + externalURL := "test" + attributes := []Trait{} + backgroundColor := "test" + animationURL := "test" + youtubeURL := "test" + + // Set the original caller to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Mint a new token for addr1 + dummy.mint(addr1, TokenID("1")) + + // Set metadata for token 1 + derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if there was an error setting metadata + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + // Set the original caller to addr2 + std.TestSetOrigCaller(addr2) // addr2 + + // Try to set metadata for token 1 from addr2 (should fail) + cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Set the original caller back to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Retrieve metadata for token 1 + dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) + uassert.NoError(t, err, "Metadata error") + + // Check if metadata attributes match expected values + uassert.Equal(t, image, dummyMetadata.Image) + uassert.Equal(t, imageData, dummyMetadata.ImageData) + uassert.Equal(t, externalURL, dummyMetadata.ExternalURL) + uassert.Equal(t, description, dummyMetadata.Description) + uassert.Equal(t, name, dummyMetadata.Name) + uassert.Equal(t, len(attributes), len(dummyMetadata.Attributes)) + uassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor) + uassert.Equal(t, animationURL, dummyMetadata.AnimationURL) + uassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_royalty.gno new file mode 100644 index 00000000..9831c709 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_royalty.gno @@ -0,0 +1,78 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// royaltyNFT represents a non-fungible token (NFT) with royalty functionality. +type royaltyNFT struct { + *metadataNFT // Embedding metadataNFT for NFT functionality + tokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token + maxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale +} + +// Ensure that royaltyNFT implements the IGRC2981 interface. +var _ IGRC2981 = (*royaltyNFT)(nil) + +// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. +func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { + // Create a new NFT with metadata + nft := NewNFTWithMetadata(name, symbol) + + return &royaltyNFT{ + metadataNFT: nft, + tokenRoyaltyInfo: avl.NewTree(), + maxRoyaltyPercentage: 100, + } +} + +// SetTokenRoyalty sets the royalty information for a specific token ID. +func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error { + // Validate the payment address + if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { + return ErrInvalidRoyaltyPaymentAddress + } + + // Check if royalty percentage exceeds maxRoyaltyPercentage + if royaltyInfo.Percentage > r.maxRoyaltyPercentage { + return ErrInvalidRoyaltyPercentage + } + + // Check if the caller is the owner of the token + owner, err := r.metadataNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set royalty information for the token + r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) + + return nil +} + +// RoyaltyInfo returns the royalty information for the given token ID and sale price. +func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) { + // Retrieve royalty information for the token + val, found := r.tokenRoyaltyInfo.Get(string(tid)) + if !found { + return "", 0, ErrInvalidTokenId + } + + royaltyInfo := val.(RoyaltyInfo) + + // Calculate royalty amount + royaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) + + return royaltyInfo.PaymentAddress, royaltyAmount, nil +} + +func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + return royaltyAmount, nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_royalty_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_royalty_test.gno new file mode 100644 index 00000000..7893453a --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/grc721_royalty_test.gno @@ -0,0 +1,70 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetTokenRoyalty(t *testing.T) { + dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + paymentAddress := testutils.TestAddress("john") + percentage := uint64(10) // 10% + + salePrice := uint64(1000) + expectRoyaltyAmount := uint64(100) + + std.TestSetOrigCaller(addr1) // addr1 + + dummy.mint(addr1, TokenID("1")) + + derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, derr, ErrInvalidTokenId) + + std.TestSetOrigCaller(addr2) // addr2 + + cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Invalid payment address + aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + PaymentAddress: std.Address("###"), // invalid address + Percentage: percentage, + }) + uassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress) + + // Test case: Invalid percentage + perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: uint64(200), // over maxRoyaltyPercentage + }) + uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) + + // Test case: Retrieving Royalty Info + std.TestSetOrigCaller(addr1) // addr1 + + dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) + uassert.NoError(t, rerr, "RoyaltyInfo error") + uassert.Equal(t, paymentAddress, dummyPaymentAddress) + uassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721.gno new file mode 100644 index 00000000..ece48f8e --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721.gno @@ -0,0 +1,39 @@ +package grc721 + +import "std" + +type IGRC721 interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(tid TokenID) (std.Address, error) + SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) + SafeTransferFrom(from, to std.Address, tid TokenID) error + TransferFrom(from, to std.Address, tid TokenID) error + Approve(approved std.Address, tid TokenID) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(tid TokenID) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, tid TokenID) error +} + +type ( + TokenID string + TokenURI string +) + +type TransferEvent struct { + From std.Address + To std.Address + TokenID TokenID +} + +type ApprovalEvent struct { + Owner std.Address + Approved std.Address + TokenID TokenID +} + +type ApprovalForAllEvent struct { + Owner std.Address + Operator std.Address + Approved bool +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721_metadata.gno new file mode 100644 index 00000000..8a2be1ff --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721_metadata.gno @@ -0,0 +1,38 @@ +package grc721 + +// IGRC721CollectionMetadata describes basic information about an NFT collection. +type IGRC721CollectionMetadata interface { + Name() string // Name returns the name of the collection. + Symbol() string // Symbol returns the symbol of the collection. +} + +// IGRC721Metadata follows the Ethereum standard +type IGRC721Metadata interface { + IGRC721CollectionMetadata + TokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token. +} + +// IGRC721Metadata follows the OpenSea metadata standard +type IGRC721MetadataOnchain interface { + IGRC721CollectionMetadata + TokenMetadata(tid TokenID) (Metadata, error) +} + +type Trait struct { + DisplayType string + TraitType string + Value string +} + +// see: https://docs.opensea.io/docs/metadata-standards +type Metadata struct { + Image string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image. + ImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter. + ExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site. + Description string // Human-readable description of the item. Markdown is supported. + Name string // Name of the item. + Attributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item. + BackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended # + AnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported. + YoutubeURL string // URL to a YouTube video (only used if animation_url is not provided). +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721_royalty.gno new file mode 100644 index 00000000..c4603d63 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/igrc721_royalty.gno @@ -0,0 +1,16 @@ +package grc721 + +import "std" + +// IGRC2981 follows the Ethereum standard +type IGRC2981 interface { + // RoyaltyInfo retrieves royalty information for a tokenID and salePrice. + // It returns the payment address, royalty amount, and an error if any. + RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) +} + +// RoyaltyInfo represents royalty information for a token. +type RoyaltyInfo struct { + PaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent. + Percentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 => 10% +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/util.gno b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/util.gno new file mode 100644 index 00000000..bb6bf24d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v33/grc/grc721/util.gno @@ -0,0 +1,18 @@ +package grc721 + +import ( + "std" +) + +var zeroAddress = std.Address("") + +func isValidAddress(addr std.Address) error { + if !addr.IsValid() { + return ErrInvalidAddress + } + return nil +} + +func emit(event interface{}) { + // TODO: setup a pubsub system here? +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_metadata.gno new file mode 100644 index 00000000..388902c4 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_metadata.gno @@ -0,0 +1,38 @@ +package domain + +import ( + "time" +) + +// Trait represents a key-value pair with an optional display type for metadata attributes +type Trait struct { + DisplayType string // Optional display type (e.g., "date", "number", etc.) + TraitType string // Type of the trait (e.g., "age", "height", etc.) + Value string // Value of the trait +} + +// Metadata represents the metadata associated with a domain +type Metadata struct { + Avatar string // URL or identifier for an avatar image + RegistrationTime time.Time // The time when the domain was registered + ExpirationTime time.Time // The time when the domain will be expire + Attributes []Trait // Additional attributes of the domain + Description string // A description of the domain + ContactInfo string // Contact information for the domain owner + RenewalFee string // The fee required to renew the domain, represented as a string +} + +// NewMetadata creates a new Metadata instance +func NewMetadata(avatar, description, contactInfo, renewalFee string, + registrationTime, expirationTime time.Time, attributes []Trait, +) Metadata { + return Metadata{ + Avatar: avatar, + RegistrationTime: registrationTime, + ExpirationTime: expirationTime, + RenewalFee: renewalFee, + Attributes: attributes, + Description: description, + ContactInfo: contactInfo, + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_registry.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_registry.gno new file mode 100644 index 00000000..bf0e6e99 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_registry.gno @@ -0,0 +1,236 @@ +package domain + +import ( + "std" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v34/grc/grc721" +) + +// domainRegistry represents a registry for domain names with metadata +type domainRegistry struct { + domains grc721.IGRC721 // Interface for basic NFT functionality + metadata *avl.Tree // AVL tree for storing domain metadata + expDate time.Time +} + +// DomainRegistry defines the methods for managing domain names and metadata +type DomainRegistry interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(domainName string) (std.Address, error) + SafeTransferFrom(from, to std.Address, domainName string) error + TransferFrom(from, to std.Address, domainName string) error + Approve(approved std.Address, domainName string) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(domainName string) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, domainName string) error + + RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error + SetDomainData(domainName string, metadata Metadata) error + GetDomainData(domainName string, field MetadataField) (Metadata, error) + GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) + RenewDomain(domainName string, additionalDuration time.Duration) error + GetExpirationDate(domainName string) time.Time + SetExpirationDate(domainName string, expDate time.Time) bool +} + +// NewDomainRegistry creates a new domain registry with metadata extensions +func NewDomainRegistry(name, symbol string) *domainRegistry { + registry := grc721.NewBasicNFT(name, symbol) + + return &domainRegistry{ + domains: registry, + metadata: avl.NewTree(), + } +} + +// RegisterDomain registers a new domain with the given metadata +func (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error { + err := d.domains.Mint(owner, grc721.TokenID(domainName)) + if err != nil { + return err + } + d.expDate = time.Now().Add(dur) + d.metadata.Set(domainName, metadata) + + return nil +} + +// RenewDomain extends the expiration time of a domain name +func (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error { + _, found := d.metadata.Get(domainName) + if !found { + return ErrInvalidDomainName + } + + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + // set new expiration date + d.expDate = d.expDate.Add(additionalDuration) + return nil +} + +// SetDomainData sets the metadata for a given domain name +func (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error { + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + d.metadata.Set(domainName, metadata) + return nil +} + +// GetDomainFields retrieves multiple fields of metadata for a given domain +func (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + if len(fields) == 0 { + return metadata, nil + } + + var result Metadata + for _, field := range fields { + switch field { + case FieldAvatar: + result.Avatar = metadata.Avatar + case FieldRegistrationTime: + result.RegistrationTime = metadata.RegistrationTime + case FieldExpirationTime: + result.ExpirationTime = metadata.ExpirationTime + case FieldRenewalFee: + result.RenewalFee = metadata.RenewalFee + case FieldAttributes: + result.Attributes = metadata.Attributes + case FieldDescription: + result.Description = metadata.Description + case FieldContactInfo: + result.ContactInfo = metadata.ContactInfo + default: + return Metadata{}, ErrInvalidMetadataField + } + } + + return result, nil +} + +// GetDomainData retrieves metadata for a given domain +func (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + switch field { + case FieldAvatar: + return Metadata{ + Avatar: metadata.Avatar, + }, nil + case FieldRegistrationTime: + return Metadata{ + RegistrationTime: metadata.RegistrationTime, + }, nil + case FieldExpirationTime: + return Metadata{ + ExpirationTime: metadata.ExpirationTime, + }, nil + case FieldRenewalFee: + return Metadata{ + RenewalFee: metadata.RenewalFee, + }, nil + case FieldAttributes: + return Metadata{ + Attributes: metadata.Attributes, + }, nil + case FieldDescription: + return Metadata{ + Description: metadata.Description, + }, nil + case FieldContactInfo: + return Metadata{ + ContactInfo: metadata.ContactInfo, + }, nil + default: + return Metadata{}, ErrInvalidMetadataField + } +} + +// BalanceOf returns the number of domains owned by a given address +func (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) { + return d.domains.BalanceOf(owner) +} + +// OwnerOf returns the owner of a given domain name +func (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) { + return d.domains.OwnerOf(grc721.TokenID(domainName)) +} + +// SafeTransferFrom safely transfers a domain from one address to another +func (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error { + return d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName)) +} + +// TransferFrom transfers a domain from one address to another +func (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error { + return d.domains.TransferFrom(from, to, grc721.TokenID(domainName)) +} + +// Approve grants approval to another address to manage a specific domain +func (d *domainRegistry) Approve(approved std.Address, domainName string) error { + return d.domains.Approve(approved, grc721.TokenID(domainName)) +} + +// SetApprovalForAll sets approval for an operator to manage all domains of the owner +func (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error { + return d.domains.SetApprovalForAll(operator, approved) +} + +// GetApproved returns the approved address for a specific domain +func (d *domainRegistry) GetApproved(domainName string) (std.Address, error) { + return d.domains.GetApproved(grc721.TokenID(domainName)) +} + +// IsApprovedForAll checks if an operator is approved to manage all domains of the owner +func (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool { + return d.domains.IsApprovedForAll(owner, operator) +} + +// Mint creates a new domain for a given address +func (d *domainRegistry) Mint(to std.Address, domainName string) error { + return d.domains.Mint(to, grc721.TokenID(domainName)) +} + +func (d *domainRegistry) GetExpirationDate(domainName string) time.Time { + return d.expDate +} + +func (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool { + _, found := d.metadata.Get(domainName) + if !found { + return false + } + d.expDate = expDate + return true +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_registry_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_registry_test.gno new file mode 100644 index 00000000..57171eb3 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/domain_registry_test.gno @@ -0,0 +1,378 @@ +package domain + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" + "gno.land/p/varmeta/demo/v34/grc/grc721" +) + +var ( + addr1 = testutils.TestAddress("bob") + addr2 = testutils.TestAddress("alice") +) + +func TestRegisterDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Successful Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "contact@registered.com", + }, + expectError: false, + }, + { + name: "Duplicate Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + err := registry.RegisterDomain(c.owner, c.domainName, c.metadata) + if c.expectError { + urequire.Error(t, err) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.owner, retrievedOwner) + } + }) + } +} + +func TestSetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Owner Sets Metadata", + owner: addr1, + caller: addr1, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: false, + }, + { + name: "Non-Owner Sets Metadata", + owner: addr1, + caller: addr2, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + registry.RegisterDomain(c.owner, c.domainName, c.metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + err := registry.SetDomainData(c.domainName, c.metadata) + + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + retrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar) + urequire.NoError(t, err) + urequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar) + } + }) + } +} + +func TestRenewDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + additionalTime time.Duration + expectError bool + expectedExpiry time.Time + }{ + { + name: "Successful Renewal", + owner: addr1, + caller: addr1, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: false, + }, + { + name: "Non-Owner Attempts Renewal", + owner: addr1, + caller: addr2, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A renewable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.RenewDomain(c.domainName, c.additionalTime) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + renewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime) + urequire.NoError(t, err) + // urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime)) + } + }) + } +} + +func TestGetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + field MetadataField + expectError bool + expectedVal string + }{ + { + name: "Retrieve Avatar", + domainName: "test.gno", + field: FieldAvatar, + expectError: false, + expectedVal: "avatar_url", + }, + { + name: "Invalid Domain Name", + domainName: "invalid.gno", + field: FieldAvatar, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + _, err := registry.GetDomainData(c.domainName, c.field) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + } + }) + } +} + +func TestGetDomainFields(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + fields []MetadataField + expectError bool + expected Metadata + }{ + { + name: "Retrieve Multiple Fields", + domainName: "test.gno", + fields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo}, + expectError: false, + expected: Metadata{ + Avatar: "avatar_url", + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + }, + { + name: "Invalid Domain", + domainName: "invalid.gno", + fields: []MetadataField{FieldAvatar}, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + retrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + urequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar) + urequire.Equal(t, c.expected.Description, retrievedMetadata.Description) + urequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo) + } + }) + } +} + +func TestTransferDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + newOwner std.Address + caller std.Address + domainName string + expectError bool + }{ + { + name: "Successful Transfer", + owner: addr1, + newOwner: addr2, + caller: addr1, + domainName: "transfer.gno", + expectError: false, + }, + { + name: "Non-Owner Attempts Transfer", + owner: addr1, + newOwner: addr2, + caller: addr2, + domainName: "transfer.gno", + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A transferable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.TransferFrom(c.owner, c.newOwner, c.domainName) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error()) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.newOwner, retrievedOwner) + } + }) + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/domain/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/errors.gno new file mode 100644 index 00000000..3de5d750 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/errors.gno @@ -0,0 +1,12 @@ +package domain + +import ( + "errors" +) + +var ( + ErrUnauthorized = errors.New("caller is not domain owner") + ErrInvalidDomainName = errors.New("invalid domain name") + ErrInvalidMetadataField = errors.New("invalid metadata field") + ErrInsufficientFunds = errors.New("insufficient funds for renewal") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/domain/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/domain/utils.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/utils.gno new file mode 100644 index 00000000..13e40d99 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/domain/utils.gno @@ -0,0 +1,13 @@ +package domain + +type MetadataField int + +const ( + FieldAvatar MetadataField = iota + FieldRegistrationTime + FieldRenewalFee + FieldExpirationTime + FieldAttributes + FieldDescription + FieldContactInfo +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/basic_nft.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/basic_nft.gno new file mode 100644 index 00000000..bec7338d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/basic_nft.gno @@ -0,0 +1,378 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +type basicNFT struct { + name string + symbol string + owners avl.Tree // tokenId -> OwnerAddress + balances avl.Tree // OwnerAddress -> TokenCount + tokenApprovals avl.Tree // TokenId -> ApprovedAddress + tokenURIs avl.Tree // TokenId -> URIs + operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool +} + +// Returns new basic NFT +func NewBasicNFT(name string, symbol string) *basicNFT { + return &basicNFT{ + name: name, + symbol: symbol, + + owners: avl.Tree{}, + balances: avl.Tree{}, + tokenApprovals: avl.Tree{}, + tokenURIs: avl.Tree{}, + operatorApprovals: avl.Tree{}, + } +} + +func (s *basicNFT) Name() string { return s.name } +func (s *basicNFT) Symbol() string { return s.symbol } +func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) } + +// BalanceOf returns balance of input address +func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) { + if err := isValidAddress(addr); err != nil { + return 0, err + } + + balance, found := s.balances.Get(addr.String()) + if !found { + return 0, nil + } + + return balance.(uint64), nil +} + +// OwnerOf returns owner of input token id +func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) { + owner, found := s.owners.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return owner.(std.Address), nil +} + +// TokenURI returns the URI of input token id +func (s *basicNFT) TokenURI(tid TokenID) (string, error) { + uri, found := s.tokenURIs.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return uri.(string), nil +} + +func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + // check for invalid TokenID + if !s.exists(tid) { + return false, ErrInvalidTokenId + } + + // check for the right owner + owner, err := s.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + s.tokenURIs.Set(string(tid), string(tURI)) + return true, nil +} + +// IsApprovedForAll returns true if operator is approved for all by the owner. +// Otherwise, returns false +func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool { + key := owner.String() + ":" + operator.String() + _, found := s.operatorApprovals.Get(key) + if !found { + return false + } + + return true +} + +// Approve approves the input address for particular token +func (s *basicNFT) Approve(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner == to { + return ErrApprovalToCurrentOwner + } + + caller := std.PrevRealm().Addr() + if caller != owner && !s.IsApprovedForAll(owner, caller) { + return ErrCallerIsNotOwnerOrApproved + } + + s.tokenApprovals.Set(string(tid), to.String()) + event := ApprovalEvent{owner, to, tid} + emit(&event) + + return nil +} + +// GetApproved return the approved address for token +func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) { + addr, found := s.tokenApprovals.Get(string(tid)) + if !found { + return zeroAddress, ErrTokenIdNotHasApproved + } + + return std.Address(addr.(string)), nil +} + +// SetApprovalForAll can approve the operator to operate on all tokens +func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error { + if err := isValidAddress(operator); err != nil { + return ErrInvalidAddress + } + + caller := std.PrevRealm().Addr() + return s.setApprovalForAll(caller, operator, approved) +} + +// Safely transfers `tokenId` token from `from` to `to`, checking that +// contract recipients are aware of the GRC721 protocol to prevent +// tokens from being forever locked. +func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(from, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +// Transfers `tokenId` token from `from` to `to`. +func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + return nil +} + +// Mints `tokenId` and transfers it to `to`. +func (s *basicNFT) Mint(to std.Address, tid TokenID) error { + return s.mint(to, tid) +} + +// Mints `tokenId` and transfers it to `to`. Also checks that +// contract recipients are using GRC721 protocol +func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error { + err := s.mint(to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(zeroAddress, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +func (s *basicNFT) Burn(tid TokenID) error { + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + + s.beforeTokenTransfer(owner, zeroAddress, tid, 1) + + s.tokenApprovals.Remove(string(tid)) + balance, err := s.BalanceOf(owner) + if err != nil { + return err + } + balance -= 1 + s.balances.Set(owner.String(), balance) + s.owners.Remove(string(tid)) + + event := TransferEvent{owner, zeroAddress, tid} + emit(&event) + + s.afterTokenTransfer(owner, zeroAddress, tid, 1) + + return nil +} + +/* Helper methods */ + +// Helper for SetApprovalForAll() +func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error { + if owner == operator { + return ErrApprovalToCurrentOwner + } + + key := owner.String() + ":" + operator.String() + s.operatorApprovals.Set(key, approved) + + event := ApprovalForAllEvent{owner, operator, approved} + emit(&event) + + return nil +} + +// Helper for TransferFrom() and SafeTransferFrom() +func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error { + if err := isValidAddress(from); err != nil { + return ErrInvalidAddress + } + if err := isValidAddress(to); err != nil { + return ErrInvalidAddress + } + + if from == to { + return ErrCannotTransferToSelf + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.beforeTokenTransfer(from, to, tid, 1) + + // Check that tokenId was not transferred by `beforeTokenTransfer` + owner, err = s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.tokenApprovals.Remove(string(tid)) + fromBalance, err := s.BalanceOf(from) + if err != nil { + return err + } + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + fromBalance -= 1 + toBalance += 1 + s.balances.Set(from.String(), fromBalance) + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{from, to, tid} + emit(&event) + + s.afterTokenTransfer(from, to, tid, 1) + + return nil +} + +// Helper for Mint() and SafeMint() +func (s *basicNFT) mint(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check that tokenId was not minted by `beforeTokenTransfer` + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} + +func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool { + owner, found := s.owners.Get(string(tid)) + if !found { + return false + } + + if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) { + return true + } + + _, err := s.GetApproved(tid) + if err != nil { + return false + } + + return true +} + +// Checks if token id already exists +func (s *basicNFT) exists(tid TokenID) bool { + _, found := s.owners.Get(string(tid)) + return found +} + +func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool { + // TODO: Implementation + return true +} + +func (s *basicNFT) RenderHome() (str string) { + str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol) + str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount()) + str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size()) + + return +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/basic_nft_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/basic_nft_test.gno new file mode 100644 index 00000000..6375b030 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/basic_nft_test.gno @@ -0,0 +1,283 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +var ( + dummyNFTName = "DummyNFT" + dummyNFTSymbol = "DNFT" +) + +func TestNewBasicNFT(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") +} + +func TestName(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + name := dummy.Name() + uassert.Equal(t, dummyNFTName, name) +} + +func TestSymbol(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + symbol := dummy.Symbol() + uassert.Equal(t, dummyNFTSymbol, symbol) +} + +func TestTokenCount(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + count := dummy.TokenCount() + uassert.Equal(t, uint64(0), count) + + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("1")) + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("2")) + + count = dummy.TokenCount() + uassert.Equal(t, uint64(2), count) +} + +func TestBalanceOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + balanceAddr1, err := dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceAddr1) + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr1, TokenID("2")) + dummy.mint(addr2, TokenID("3")) + + balanceAddr1, err = dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + + balanceAddr2, err := dummy.BalanceOf(addr2) + uassert.NoError(t, err, "should not result in error") + + uassert.Equal(t, uint64(2), balanceAddr1) + uassert.Equal(t, uint64(1), balanceAddr2) +} + +func TestOwnerOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + owner, err := dummy.OwnerOf(TokenID("invalid")) + uassert.Error(t, err, "should not result in error") + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr2, TokenID("2")) + + // Checking for token id "1" + owner, err = dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) + + // Checking for token id "2" + owner, err = dummy.OwnerOf(TokenID("2")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr2.String(), owner.String()) +} + +func TestIsApprovedForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) + uassert.False(t, isApprovedForAll) +} + +func TestSetApprovalForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + isApprovedForAll := dummy.IsApprovedForAll(caller, addr) + uassert.False(t, isApprovedForAll) + + err := dummy.SetApprovalForAll(addr, true) + uassert.NoError(t, err, "should not result in error") + + isApprovedForAll = dummy.IsApprovedForAll(caller, addr) + uassert.True(t, isApprovedForAll) +} + +func TestGetApproved(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + approvedAddr, err := dummy.GetApproved(TokenID("invalid")) + uassert.Error(t, err, "should result in error") +} + +func TestApprove(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + + _, err := dummy.GetApproved(TokenID("1")) + uassert.Error(t, err, "should result in error") + + err = dummy.Approve(addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + approvedAddr, err := dummy.GetApproved(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), approvedAddr.String()) +} + +func TestTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.TransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestSafeTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.SafeTransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestMint(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + err := dummy.Mint(addr1, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr1, TokenID("2")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr2, TokenID("3")) + uassert.NoError(t, err, "should not result in error") + + // Try minting duplicate token id + err = dummy.Mint(addr2, TokenID("1")) + uassert.Error(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) +} + +func TestBurn(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(addr, TokenID("1")) + dummy.mint(addr, TokenID("2")) + + err := dummy.Burn(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.Error(t, err, "should result in error") +} + +func TestSetTokenURI(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + tokenURI := "http://example.com/token" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1, TokenID("1")) + _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) + uassert.NoError(t, derr, "should not result in error") + + // Test case: Invalid token ID + _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Retrieving TokenURI + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyTokenURI, err := dummy.TokenURI(TokenID("1")) + uassert.NoError(t, err, "TokenURI error") + uassert.Equal(t, string(tokenURI), string(dummyTokenURI)) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/errors.gno new file mode 100644 index 00000000..2d512db3 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/errors.gno @@ -0,0 +1,22 @@ +package grc721 + +import "errors" + +var ( + ErrInvalidTokenId = errors.New("invalid token id") + ErrInvalidAddress = errors.New("invalid address") + ErrTokenIdNotHasApproved = errors.New("token id not approved for anyone") + ErrApprovalToCurrentOwner = errors.New("approval to current owner") + ErrCallerIsNotOwner = errors.New("caller is not token owner") + ErrCallerNotApprovedForAll = errors.New("caller is not approved for all") + ErrCannotTransferToSelf = errors.New("cannot send transfer to self") + ErrTransferFromIncorrectOwner = errors.New("transfer from incorrect owner") + ErrTransferToNonGRC721Receiver = errors.New("transfer to non GRC721Receiver implementer") + ErrCallerIsNotOwnerOrApproved = errors.New("caller is not token owner or approved") + ErrTokenIdAlreadyExists = errors.New("token id already exists") + + // ERC721Royalty + ErrInvalidRoyaltyPercentage = errors.New("invalid royalty percentage") + ErrInvalidRoyaltyPaymentAddress = errors.New("invalid royalty paymentAddress") + ErrCannotCalculateRoyaltyAmount = errors.New("cannot calculate royalty amount") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_metadata.gno new file mode 100644 index 00000000..360f73ed --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_metadata.gno @@ -0,0 +1,95 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// metadataNFT represents an NFT with metadata extensions. +type metadataNFT struct { + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions *avl.Tree // AVL tree for storing metadata extensions +} + +// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. +var _ IGRC721MetadataOnchain = (*metadataNFT)(nil) + +// NewNFTWithMetadata creates a new basic NFT with metadata extensions. +func NewNFTWithMetadata(name string, symbol string) *metadataNFT { + // Create a new basic NFT + nft := NewBasicNFT(name, symbol) + + // Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions + return &metadataNFT{ + basicNFT: nft, + extensions: avl.NewTree(), + } +} + +// SetTokenMetadata sets metadata for a given token ID. +func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { + // Check if the caller is the owner of the token + owner, err := s.basicNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set the metadata for the token ID in the extensions AVL tree + s.extensions.Set(string(tid), metadata) + return nil +} + +// TokenMetadata retrieves metadata for a given token ID. +func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { + // Retrieve metadata from the extensions AVL tree + metadata, found := s.extensions.Get(string(tid)) + if !found { + return Metadata{}, ErrInvalidTokenId + } + + return metadata.(Metadata), nil +} + +// mint mints a new token and assigns it to the specified address. +func (s *metadataNFT) mint(to std.Address, tid TokenID) error { + // Check if the address is valid + if err := isValidAddress(to); err != nil { + return err + } + + // Check if the token ID already exists + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check if the token ID was minted by beforeTokenTransfer + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + // Increment balance of the recipient address + toBalance, err := s.basicNFT.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.basicNFT.balances.Set(to.String(), toBalance) + + // Set owner of the token ID to the recipient address + s.basicNFT.owners.Set(string(tid), to) + + // Emit transfer event + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_metadata_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_metadata_test.gno new file mode 100644 index 00000000..ad002a7c --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_metadata_test.gno @@ -0,0 +1,107 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetMetadata(t *testing.T) { + // Create a new dummy NFT with metadata + dummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + // Define addresses for testing purposes + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + // Define metadata attributes + name := "test" + description := "test" + image := "test" + imageData := "test" + externalURL := "test" + attributes := []Trait{} + backgroundColor := "test" + animationURL := "test" + youtubeURL := "test" + + // Set the original caller to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Mint a new token for addr1 + dummy.mint(addr1, TokenID("1")) + + // Set metadata for token 1 + derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if there was an error setting metadata + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + // Set the original caller to addr2 + std.TestSetOrigCaller(addr2) // addr2 + + // Try to set metadata for token 1 from addr2 (should fail) + cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Set the original caller back to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Retrieve metadata for token 1 + dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) + uassert.NoError(t, err, "Metadata error") + + // Check if metadata attributes match expected values + uassert.Equal(t, image, dummyMetadata.Image) + uassert.Equal(t, imageData, dummyMetadata.ImageData) + uassert.Equal(t, externalURL, dummyMetadata.ExternalURL) + uassert.Equal(t, description, dummyMetadata.Description) + uassert.Equal(t, name, dummyMetadata.Name) + uassert.Equal(t, len(attributes), len(dummyMetadata.Attributes)) + uassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor) + uassert.Equal(t, animationURL, dummyMetadata.AnimationURL) + uassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_royalty.gno new file mode 100644 index 00000000..9831c709 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_royalty.gno @@ -0,0 +1,78 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// royaltyNFT represents a non-fungible token (NFT) with royalty functionality. +type royaltyNFT struct { + *metadataNFT // Embedding metadataNFT for NFT functionality + tokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token + maxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale +} + +// Ensure that royaltyNFT implements the IGRC2981 interface. +var _ IGRC2981 = (*royaltyNFT)(nil) + +// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. +func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { + // Create a new NFT with metadata + nft := NewNFTWithMetadata(name, symbol) + + return &royaltyNFT{ + metadataNFT: nft, + tokenRoyaltyInfo: avl.NewTree(), + maxRoyaltyPercentage: 100, + } +} + +// SetTokenRoyalty sets the royalty information for a specific token ID. +func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error { + // Validate the payment address + if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { + return ErrInvalidRoyaltyPaymentAddress + } + + // Check if royalty percentage exceeds maxRoyaltyPercentage + if royaltyInfo.Percentage > r.maxRoyaltyPercentage { + return ErrInvalidRoyaltyPercentage + } + + // Check if the caller is the owner of the token + owner, err := r.metadataNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set royalty information for the token + r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) + + return nil +} + +// RoyaltyInfo returns the royalty information for the given token ID and sale price. +func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) { + // Retrieve royalty information for the token + val, found := r.tokenRoyaltyInfo.Get(string(tid)) + if !found { + return "", 0, ErrInvalidTokenId + } + + royaltyInfo := val.(RoyaltyInfo) + + // Calculate royalty amount + royaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) + + return royaltyInfo.PaymentAddress, royaltyAmount, nil +} + +func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + return royaltyAmount, nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_royalty_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_royalty_test.gno new file mode 100644 index 00000000..7893453a --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/grc721_royalty_test.gno @@ -0,0 +1,70 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetTokenRoyalty(t *testing.T) { + dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + paymentAddress := testutils.TestAddress("john") + percentage := uint64(10) // 10% + + salePrice := uint64(1000) + expectRoyaltyAmount := uint64(100) + + std.TestSetOrigCaller(addr1) // addr1 + + dummy.mint(addr1, TokenID("1")) + + derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, derr, ErrInvalidTokenId) + + std.TestSetOrigCaller(addr2) // addr2 + + cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Invalid payment address + aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + PaymentAddress: std.Address("###"), // invalid address + Percentage: percentage, + }) + uassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress) + + // Test case: Invalid percentage + perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: uint64(200), // over maxRoyaltyPercentage + }) + uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) + + // Test case: Retrieving Royalty Info + std.TestSetOrigCaller(addr1) // addr1 + + dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) + uassert.NoError(t, rerr, "RoyaltyInfo error") + uassert.Equal(t, paymentAddress, dummyPaymentAddress) + uassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721.gno new file mode 100644 index 00000000..ece48f8e --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721.gno @@ -0,0 +1,39 @@ +package grc721 + +import "std" + +type IGRC721 interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(tid TokenID) (std.Address, error) + SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) + SafeTransferFrom(from, to std.Address, tid TokenID) error + TransferFrom(from, to std.Address, tid TokenID) error + Approve(approved std.Address, tid TokenID) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(tid TokenID) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, tid TokenID) error +} + +type ( + TokenID string + TokenURI string +) + +type TransferEvent struct { + From std.Address + To std.Address + TokenID TokenID +} + +type ApprovalEvent struct { + Owner std.Address + Approved std.Address + TokenID TokenID +} + +type ApprovalForAllEvent struct { + Owner std.Address + Operator std.Address + Approved bool +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721_metadata.gno new file mode 100644 index 00000000..8a2be1ff --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721_metadata.gno @@ -0,0 +1,38 @@ +package grc721 + +// IGRC721CollectionMetadata describes basic information about an NFT collection. +type IGRC721CollectionMetadata interface { + Name() string // Name returns the name of the collection. + Symbol() string // Symbol returns the symbol of the collection. +} + +// IGRC721Metadata follows the Ethereum standard +type IGRC721Metadata interface { + IGRC721CollectionMetadata + TokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token. +} + +// IGRC721Metadata follows the OpenSea metadata standard +type IGRC721MetadataOnchain interface { + IGRC721CollectionMetadata + TokenMetadata(tid TokenID) (Metadata, error) +} + +type Trait struct { + DisplayType string + TraitType string + Value string +} + +// see: https://docs.opensea.io/docs/metadata-standards +type Metadata struct { + Image string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image. + ImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter. + ExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site. + Description string // Human-readable description of the item. Markdown is supported. + Name string // Name of the item. + Attributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item. + BackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended # + AnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported. + YoutubeURL string // URL to a YouTube video (only used if animation_url is not provided). +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721_royalty.gno new file mode 100644 index 00000000..c4603d63 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/igrc721_royalty.gno @@ -0,0 +1,16 @@ +package grc721 + +import "std" + +// IGRC2981 follows the Ethereum standard +type IGRC2981 interface { + // RoyaltyInfo retrieves royalty information for a tokenID and salePrice. + // It returns the payment address, royalty amount, and an error if any. + RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) +} + +// RoyaltyInfo represents royalty information for a token. +type RoyaltyInfo struct { + PaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent. + Percentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 => 10% +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/util.gno b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/util.gno new file mode 100644 index 00000000..bb6bf24d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v34/grc/grc721/util.gno @@ -0,0 +1,18 @@ +package grc721 + +import ( + "std" +) + +var zeroAddress = std.Address("") + +func isValidAddress(addr std.Address) error { + if !addr.IsValid() { + return ErrInvalidAddress + } + return nil +} + +func emit(event interface{}) { + // TODO: setup a pubsub system here? +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_metadata.gno new file mode 100644 index 00000000..388902c4 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_metadata.gno @@ -0,0 +1,38 @@ +package domain + +import ( + "time" +) + +// Trait represents a key-value pair with an optional display type for metadata attributes +type Trait struct { + DisplayType string // Optional display type (e.g., "date", "number", etc.) + TraitType string // Type of the trait (e.g., "age", "height", etc.) + Value string // Value of the trait +} + +// Metadata represents the metadata associated with a domain +type Metadata struct { + Avatar string // URL or identifier for an avatar image + RegistrationTime time.Time // The time when the domain was registered + ExpirationTime time.Time // The time when the domain will be expire + Attributes []Trait // Additional attributes of the domain + Description string // A description of the domain + ContactInfo string // Contact information for the domain owner + RenewalFee string // The fee required to renew the domain, represented as a string +} + +// NewMetadata creates a new Metadata instance +func NewMetadata(avatar, description, contactInfo, renewalFee string, + registrationTime, expirationTime time.Time, attributes []Trait, +) Metadata { + return Metadata{ + Avatar: avatar, + RegistrationTime: registrationTime, + ExpirationTime: expirationTime, + RenewalFee: renewalFee, + Attributes: attributes, + Description: description, + ContactInfo: contactInfo, + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_registry.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_registry.gno new file mode 100644 index 00000000..1c0fe450 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_registry.gno @@ -0,0 +1,236 @@ +package domain + +import ( + "std" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v35/grc/grc721" +) + +// domainRegistry represents a registry for domain names with metadata +type domainRegistry struct { + domains grc721.IGRC721 // Interface for basic NFT functionality + metadata *avl.Tree // AVL tree for storing domain metadata + expDate time.Time +} + +// DomainRegistry defines the methods for managing domain names and metadata +type DomainRegistry interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(domainName string) (std.Address, error) + SafeTransferFrom(from, to std.Address, domainName string) error + TransferFrom(from, to std.Address, domainName string) error + Approve(approved std.Address, domainName string) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(domainName string) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, domainName string) error + + RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error + SetDomainData(domainName string, metadata Metadata) error + GetDomainData(domainName string, field MetadataField) (Metadata, error) + GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) + RenewDomain(domainName string, additionalDuration time.Duration) error + GetExpirationDate(domainName string) time.Time + SetExpirationDate(domainName string, expDate time.Time) bool +} + +// NewDomainRegistry creates a new domain registry with metadata extensions +func NewDomainRegistry(name, symbol string) *domainRegistry { + registry := grc721.NewBasicNFT(name, symbol) + + return &domainRegistry{ + domains: registry, + metadata: avl.NewTree(), + } +} + +// RegisterDomain registers a new domain with the given metadata +func (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error { + err := d.domains.Mint(owner, grc721.TokenID(domainName)) + if err != nil { + return err + } + d.expDate = time.Now().Add(dur) + d.metadata.Set(domainName, metadata) + + return nil +} + +// RenewDomain extends the expiration time of a domain name +func (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error { + _, found := d.metadata.Get(domainName) + if !found { + return ErrInvalidDomainName + } + + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + // set new expiration date + d.expDate = d.expDate.Add(additionalDuration) + return nil +} + +// SetDomainData sets the metadata for a given domain name +func (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error { + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + d.metadata.Set(domainName, metadata) + return nil +} + +// GetDomainFields retrieves multiple fields of metadata for a given domain +func (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + if len(fields) == 0 { + return metadata, nil + } + + var result Metadata + for _, field := range fields { + switch field { + case FieldAvatar: + result.Avatar = metadata.Avatar + case FieldRegistrationTime: + result.RegistrationTime = metadata.RegistrationTime + case FieldExpirationTime: + result.ExpirationTime = metadata.ExpirationTime + case FieldRenewalFee: + result.RenewalFee = metadata.RenewalFee + case FieldAttributes: + result.Attributes = metadata.Attributes + case FieldDescription: + result.Description = metadata.Description + case FieldContactInfo: + result.ContactInfo = metadata.ContactInfo + default: + return Metadata{}, ErrInvalidMetadataField + } + } + + return result, nil +} + +// GetDomainData retrieves metadata for a given domain +func (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + switch field { + case FieldAvatar: + return Metadata{ + Avatar: metadata.Avatar, + }, nil + case FieldRegistrationTime: + return Metadata{ + RegistrationTime: metadata.RegistrationTime, + }, nil + case FieldExpirationTime: + return Metadata{ + ExpirationTime: metadata.ExpirationTime, + }, nil + case FieldRenewalFee: + return Metadata{ + RenewalFee: metadata.RenewalFee, + }, nil + case FieldAttributes: + return Metadata{ + Attributes: metadata.Attributes, + }, nil + case FieldDescription: + return Metadata{ + Description: metadata.Description, + }, nil + case FieldContactInfo: + return Metadata{ + ContactInfo: metadata.ContactInfo, + }, nil + default: + return Metadata{}, ErrInvalidMetadataField + } +} + +// BalanceOf returns the number of domains owned by a given address +func (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) { + return d.domains.BalanceOf(owner) +} + +// OwnerOf returns the owner of a given domain name +func (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) { + return d.domains.OwnerOf(grc721.TokenID(domainName)) +} + +// SafeTransferFrom safely transfers a domain from one address to another +func (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error { + return d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName)) +} + +// TransferFrom transfers a domain from one address to another +func (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error { + return d.domains.TransferFrom(from, to, grc721.TokenID(domainName)) +} + +// Approve grants approval to another address to manage a specific domain +func (d *domainRegistry) Approve(approved std.Address, domainName string) error { + return d.domains.Approve(approved, grc721.TokenID(domainName)) +} + +// SetApprovalForAll sets approval for an operator to manage all domains of the owner +func (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error { + return d.domains.SetApprovalForAll(operator, approved) +} + +// GetApproved returns the approved address for a specific domain +func (d *domainRegistry) GetApproved(domainName string) (std.Address, error) { + return d.domains.GetApproved(grc721.TokenID(domainName)) +} + +// IsApprovedForAll checks if an operator is approved to manage all domains of the owner +func (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool { + return d.domains.IsApprovedForAll(owner, operator) +} + +// Mint creates a new domain for a given address +func (d *domainRegistry) Mint(to std.Address, domainName string) error { + return d.domains.Mint(to, grc721.TokenID(domainName)) +} + +func (d *domainRegistry) GetExpirationDate(domainName string) time.Time { + return d.expDate +} + +func (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool { + _, found := d.metadata.Get(domainName) + if !found { + return false + } + d.expDate = expDate + return true +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_registry_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_registry_test.gno new file mode 100644 index 00000000..34d2715f --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/domain_registry_test.gno @@ -0,0 +1,378 @@ +package domain + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" + "gno.land/p/varmeta/demo/v35/grc/grc721" +) + +var ( + addr1 = testutils.TestAddress("bob") + addr2 = testutils.TestAddress("alice") +) + +func TestRegisterDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Successful Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "contact@registered.com", + }, + expectError: false, + }, + { + name: "Duplicate Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + err := registry.RegisterDomain(c.owner, c.domainName, c.metadata) + if c.expectError { + urequire.Error(t, err) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.owner, retrievedOwner) + } + }) + } +} + +func TestSetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Owner Sets Metadata", + owner: addr1, + caller: addr1, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: false, + }, + { + name: "Non-Owner Sets Metadata", + owner: addr1, + caller: addr2, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + registry.RegisterDomain(c.owner, c.domainName, c.metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + err := registry.SetDomainData(c.domainName, c.metadata) + + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + retrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar) + urequire.NoError(t, err) + urequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar) + } + }) + } +} + +func TestRenewDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + additionalTime time.Duration + expectError bool + expectedExpiry time.Time + }{ + { + name: "Successful Renewal", + owner: addr1, + caller: addr1, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: false, + }, + { + name: "Non-Owner Attempts Renewal", + owner: addr1, + caller: addr2, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A renewable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.RenewDomain(c.domainName, c.additionalTime) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + renewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime) + urequire.NoError(t, err) + // urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime)) + } + }) + } +} + +func TestGetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + field MetadataField + expectError bool + expectedVal string + }{ + { + name: "Retrieve Avatar", + domainName: "test.gno", + field: FieldAvatar, + expectError: false, + expectedVal: "avatar_url", + }, + { + name: "Invalid Domain Name", + domainName: "invalid.gno", + field: FieldAvatar, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + _, err := registry.GetDomainData(c.domainName, c.field) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + } + }) + } +} + +func TestGetDomainFields(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + fields []MetadataField + expectError bool + expected Metadata + }{ + { + name: "Retrieve Multiple Fields", + domainName: "test.gno", + fields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo}, + expectError: false, + expected: Metadata{ + Avatar: "avatar_url", + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + }, + { + name: "Invalid Domain", + domainName: "invalid.gno", + fields: []MetadataField{FieldAvatar}, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + retrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + urequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar) + urequire.Equal(t, c.expected.Description, retrievedMetadata.Description) + urequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo) + } + }) + } +} + +func TestTransferDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + newOwner std.Address + caller std.Address + domainName string + expectError bool + }{ + { + name: "Successful Transfer", + owner: addr1, + newOwner: addr2, + caller: addr1, + domainName: "transfer.gno", + expectError: false, + }, + { + name: "Non-Owner Attempts Transfer", + owner: addr1, + newOwner: addr2, + caller: addr2, + domainName: "transfer.gno", + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A transferable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.TransferFrom(c.owner, c.newOwner, c.domainName) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error()) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.newOwner, retrievedOwner) + } + }) + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/domain/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/errors.gno new file mode 100644 index 00000000..3de5d750 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/errors.gno @@ -0,0 +1,12 @@ +package domain + +import ( + "errors" +) + +var ( + ErrUnauthorized = errors.New("caller is not domain owner") + ErrInvalidDomainName = errors.New("invalid domain name") + ErrInvalidMetadataField = errors.New("invalid metadata field") + ErrInsufficientFunds = errors.New("insufficient funds for renewal") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/domain/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/domain/utils.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/utils.gno new file mode 100644 index 00000000..13e40d99 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/domain/utils.gno @@ -0,0 +1,13 @@ +package domain + +type MetadataField int + +const ( + FieldAvatar MetadataField = iota + FieldRegistrationTime + FieldRenewalFee + FieldExpirationTime + FieldAttributes + FieldDescription + FieldContactInfo +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/basic_nft.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/basic_nft.gno new file mode 100644 index 00000000..bec7338d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/basic_nft.gno @@ -0,0 +1,378 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +type basicNFT struct { + name string + symbol string + owners avl.Tree // tokenId -> OwnerAddress + balances avl.Tree // OwnerAddress -> TokenCount + tokenApprovals avl.Tree // TokenId -> ApprovedAddress + tokenURIs avl.Tree // TokenId -> URIs + operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool +} + +// Returns new basic NFT +func NewBasicNFT(name string, symbol string) *basicNFT { + return &basicNFT{ + name: name, + symbol: symbol, + + owners: avl.Tree{}, + balances: avl.Tree{}, + tokenApprovals: avl.Tree{}, + tokenURIs: avl.Tree{}, + operatorApprovals: avl.Tree{}, + } +} + +func (s *basicNFT) Name() string { return s.name } +func (s *basicNFT) Symbol() string { return s.symbol } +func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) } + +// BalanceOf returns balance of input address +func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) { + if err := isValidAddress(addr); err != nil { + return 0, err + } + + balance, found := s.balances.Get(addr.String()) + if !found { + return 0, nil + } + + return balance.(uint64), nil +} + +// OwnerOf returns owner of input token id +func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) { + owner, found := s.owners.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return owner.(std.Address), nil +} + +// TokenURI returns the URI of input token id +func (s *basicNFT) TokenURI(tid TokenID) (string, error) { + uri, found := s.tokenURIs.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return uri.(string), nil +} + +func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + // check for invalid TokenID + if !s.exists(tid) { + return false, ErrInvalidTokenId + } + + // check for the right owner + owner, err := s.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + s.tokenURIs.Set(string(tid), string(tURI)) + return true, nil +} + +// IsApprovedForAll returns true if operator is approved for all by the owner. +// Otherwise, returns false +func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool { + key := owner.String() + ":" + operator.String() + _, found := s.operatorApprovals.Get(key) + if !found { + return false + } + + return true +} + +// Approve approves the input address for particular token +func (s *basicNFT) Approve(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner == to { + return ErrApprovalToCurrentOwner + } + + caller := std.PrevRealm().Addr() + if caller != owner && !s.IsApprovedForAll(owner, caller) { + return ErrCallerIsNotOwnerOrApproved + } + + s.tokenApprovals.Set(string(tid), to.String()) + event := ApprovalEvent{owner, to, tid} + emit(&event) + + return nil +} + +// GetApproved return the approved address for token +func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) { + addr, found := s.tokenApprovals.Get(string(tid)) + if !found { + return zeroAddress, ErrTokenIdNotHasApproved + } + + return std.Address(addr.(string)), nil +} + +// SetApprovalForAll can approve the operator to operate on all tokens +func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error { + if err := isValidAddress(operator); err != nil { + return ErrInvalidAddress + } + + caller := std.PrevRealm().Addr() + return s.setApprovalForAll(caller, operator, approved) +} + +// Safely transfers `tokenId` token from `from` to `to`, checking that +// contract recipients are aware of the GRC721 protocol to prevent +// tokens from being forever locked. +func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(from, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +// Transfers `tokenId` token from `from` to `to`. +func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + return nil +} + +// Mints `tokenId` and transfers it to `to`. +func (s *basicNFT) Mint(to std.Address, tid TokenID) error { + return s.mint(to, tid) +} + +// Mints `tokenId` and transfers it to `to`. Also checks that +// contract recipients are using GRC721 protocol +func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error { + err := s.mint(to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(zeroAddress, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +func (s *basicNFT) Burn(tid TokenID) error { + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + + s.beforeTokenTransfer(owner, zeroAddress, tid, 1) + + s.tokenApprovals.Remove(string(tid)) + balance, err := s.BalanceOf(owner) + if err != nil { + return err + } + balance -= 1 + s.balances.Set(owner.String(), balance) + s.owners.Remove(string(tid)) + + event := TransferEvent{owner, zeroAddress, tid} + emit(&event) + + s.afterTokenTransfer(owner, zeroAddress, tid, 1) + + return nil +} + +/* Helper methods */ + +// Helper for SetApprovalForAll() +func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error { + if owner == operator { + return ErrApprovalToCurrentOwner + } + + key := owner.String() + ":" + operator.String() + s.operatorApprovals.Set(key, approved) + + event := ApprovalForAllEvent{owner, operator, approved} + emit(&event) + + return nil +} + +// Helper for TransferFrom() and SafeTransferFrom() +func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error { + if err := isValidAddress(from); err != nil { + return ErrInvalidAddress + } + if err := isValidAddress(to); err != nil { + return ErrInvalidAddress + } + + if from == to { + return ErrCannotTransferToSelf + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.beforeTokenTransfer(from, to, tid, 1) + + // Check that tokenId was not transferred by `beforeTokenTransfer` + owner, err = s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.tokenApprovals.Remove(string(tid)) + fromBalance, err := s.BalanceOf(from) + if err != nil { + return err + } + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + fromBalance -= 1 + toBalance += 1 + s.balances.Set(from.String(), fromBalance) + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{from, to, tid} + emit(&event) + + s.afterTokenTransfer(from, to, tid, 1) + + return nil +} + +// Helper for Mint() and SafeMint() +func (s *basicNFT) mint(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check that tokenId was not minted by `beforeTokenTransfer` + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} + +func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool { + owner, found := s.owners.Get(string(tid)) + if !found { + return false + } + + if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) { + return true + } + + _, err := s.GetApproved(tid) + if err != nil { + return false + } + + return true +} + +// Checks if token id already exists +func (s *basicNFT) exists(tid TokenID) bool { + _, found := s.owners.Get(string(tid)) + return found +} + +func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool { + // TODO: Implementation + return true +} + +func (s *basicNFT) RenderHome() (str string) { + str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol) + str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount()) + str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size()) + + return +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/basic_nft_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/basic_nft_test.gno new file mode 100644 index 00000000..6375b030 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/basic_nft_test.gno @@ -0,0 +1,283 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +var ( + dummyNFTName = "DummyNFT" + dummyNFTSymbol = "DNFT" +) + +func TestNewBasicNFT(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") +} + +func TestName(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + name := dummy.Name() + uassert.Equal(t, dummyNFTName, name) +} + +func TestSymbol(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + symbol := dummy.Symbol() + uassert.Equal(t, dummyNFTSymbol, symbol) +} + +func TestTokenCount(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + count := dummy.TokenCount() + uassert.Equal(t, uint64(0), count) + + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("1")) + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("2")) + + count = dummy.TokenCount() + uassert.Equal(t, uint64(2), count) +} + +func TestBalanceOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + balanceAddr1, err := dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceAddr1) + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr1, TokenID("2")) + dummy.mint(addr2, TokenID("3")) + + balanceAddr1, err = dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + + balanceAddr2, err := dummy.BalanceOf(addr2) + uassert.NoError(t, err, "should not result in error") + + uassert.Equal(t, uint64(2), balanceAddr1) + uassert.Equal(t, uint64(1), balanceAddr2) +} + +func TestOwnerOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + owner, err := dummy.OwnerOf(TokenID("invalid")) + uassert.Error(t, err, "should not result in error") + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr2, TokenID("2")) + + // Checking for token id "1" + owner, err = dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) + + // Checking for token id "2" + owner, err = dummy.OwnerOf(TokenID("2")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr2.String(), owner.String()) +} + +func TestIsApprovedForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) + uassert.False(t, isApprovedForAll) +} + +func TestSetApprovalForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + isApprovedForAll := dummy.IsApprovedForAll(caller, addr) + uassert.False(t, isApprovedForAll) + + err := dummy.SetApprovalForAll(addr, true) + uassert.NoError(t, err, "should not result in error") + + isApprovedForAll = dummy.IsApprovedForAll(caller, addr) + uassert.True(t, isApprovedForAll) +} + +func TestGetApproved(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + approvedAddr, err := dummy.GetApproved(TokenID("invalid")) + uassert.Error(t, err, "should result in error") +} + +func TestApprove(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + + _, err := dummy.GetApproved(TokenID("1")) + uassert.Error(t, err, "should result in error") + + err = dummy.Approve(addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + approvedAddr, err := dummy.GetApproved(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), approvedAddr.String()) +} + +func TestTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.TransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestSafeTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.SafeTransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestMint(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + err := dummy.Mint(addr1, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr1, TokenID("2")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr2, TokenID("3")) + uassert.NoError(t, err, "should not result in error") + + // Try minting duplicate token id + err = dummy.Mint(addr2, TokenID("1")) + uassert.Error(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) +} + +func TestBurn(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(addr, TokenID("1")) + dummy.mint(addr, TokenID("2")) + + err := dummy.Burn(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.Error(t, err, "should result in error") +} + +func TestSetTokenURI(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + tokenURI := "http://example.com/token" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1, TokenID("1")) + _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) + uassert.NoError(t, derr, "should not result in error") + + // Test case: Invalid token ID + _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Retrieving TokenURI + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyTokenURI, err := dummy.TokenURI(TokenID("1")) + uassert.NoError(t, err, "TokenURI error") + uassert.Equal(t, string(tokenURI), string(dummyTokenURI)) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/errors.gno new file mode 100644 index 00000000..2d512db3 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/errors.gno @@ -0,0 +1,22 @@ +package grc721 + +import "errors" + +var ( + ErrInvalidTokenId = errors.New("invalid token id") + ErrInvalidAddress = errors.New("invalid address") + ErrTokenIdNotHasApproved = errors.New("token id not approved for anyone") + ErrApprovalToCurrentOwner = errors.New("approval to current owner") + ErrCallerIsNotOwner = errors.New("caller is not token owner") + ErrCallerNotApprovedForAll = errors.New("caller is not approved for all") + ErrCannotTransferToSelf = errors.New("cannot send transfer to self") + ErrTransferFromIncorrectOwner = errors.New("transfer from incorrect owner") + ErrTransferToNonGRC721Receiver = errors.New("transfer to non GRC721Receiver implementer") + ErrCallerIsNotOwnerOrApproved = errors.New("caller is not token owner or approved") + ErrTokenIdAlreadyExists = errors.New("token id already exists") + + // ERC721Royalty + ErrInvalidRoyaltyPercentage = errors.New("invalid royalty percentage") + ErrInvalidRoyaltyPaymentAddress = errors.New("invalid royalty paymentAddress") + ErrCannotCalculateRoyaltyAmount = errors.New("cannot calculate royalty amount") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_metadata.gno new file mode 100644 index 00000000..360f73ed --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_metadata.gno @@ -0,0 +1,95 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// metadataNFT represents an NFT with metadata extensions. +type metadataNFT struct { + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions *avl.Tree // AVL tree for storing metadata extensions +} + +// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. +var _ IGRC721MetadataOnchain = (*metadataNFT)(nil) + +// NewNFTWithMetadata creates a new basic NFT with metadata extensions. +func NewNFTWithMetadata(name string, symbol string) *metadataNFT { + // Create a new basic NFT + nft := NewBasicNFT(name, symbol) + + // Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions + return &metadataNFT{ + basicNFT: nft, + extensions: avl.NewTree(), + } +} + +// SetTokenMetadata sets metadata for a given token ID. +func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { + // Check if the caller is the owner of the token + owner, err := s.basicNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set the metadata for the token ID in the extensions AVL tree + s.extensions.Set(string(tid), metadata) + return nil +} + +// TokenMetadata retrieves metadata for a given token ID. +func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { + // Retrieve metadata from the extensions AVL tree + metadata, found := s.extensions.Get(string(tid)) + if !found { + return Metadata{}, ErrInvalidTokenId + } + + return metadata.(Metadata), nil +} + +// mint mints a new token and assigns it to the specified address. +func (s *metadataNFT) mint(to std.Address, tid TokenID) error { + // Check if the address is valid + if err := isValidAddress(to); err != nil { + return err + } + + // Check if the token ID already exists + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check if the token ID was minted by beforeTokenTransfer + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + // Increment balance of the recipient address + toBalance, err := s.basicNFT.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.basicNFT.balances.Set(to.String(), toBalance) + + // Set owner of the token ID to the recipient address + s.basicNFT.owners.Set(string(tid), to) + + // Emit transfer event + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_metadata_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_metadata_test.gno new file mode 100644 index 00000000..ad002a7c --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_metadata_test.gno @@ -0,0 +1,107 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetMetadata(t *testing.T) { + // Create a new dummy NFT with metadata + dummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + // Define addresses for testing purposes + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + // Define metadata attributes + name := "test" + description := "test" + image := "test" + imageData := "test" + externalURL := "test" + attributes := []Trait{} + backgroundColor := "test" + animationURL := "test" + youtubeURL := "test" + + // Set the original caller to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Mint a new token for addr1 + dummy.mint(addr1, TokenID("1")) + + // Set metadata for token 1 + derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if there was an error setting metadata + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + // Set the original caller to addr2 + std.TestSetOrigCaller(addr2) // addr2 + + // Try to set metadata for token 1 from addr2 (should fail) + cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Set the original caller back to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Retrieve metadata for token 1 + dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) + uassert.NoError(t, err, "Metadata error") + + // Check if metadata attributes match expected values + uassert.Equal(t, image, dummyMetadata.Image) + uassert.Equal(t, imageData, dummyMetadata.ImageData) + uassert.Equal(t, externalURL, dummyMetadata.ExternalURL) + uassert.Equal(t, description, dummyMetadata.Description) + uassert.Equal(t, name, dummyMetadata.Name) + uassert.Equal(t, len(attributes), len(dummyMetadata.Attributes)) + uassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor) + uassert.Equal(t, animationURL, dummyMetadata.AnimationURL) + uassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_royalty.gno new file mode 100644 index 00000000..9831c709 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_royalty.gno @@ -0,0 +1,78 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// royaltyNFT represents a non-fungible token (NFT) with royalty functionality. +type royaltyNFT struct { + *metadataNFT // Embedding metadataNFT for NFT functionality + tokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token + maxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale +} + +// Ensure that royaltyNFT implements the IGRC2981 interface. +var _ IGRC2981 = (*royaltyNFT)(nil) + +// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. +func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { + // Create a new NFT with metadata + nft := NewNFTWithMetadata(name, symbol) + + return &royaltyNFT{ + metadataNFT: nft, + tokenRoyaltyInfo: avl.NewTree(), + maxRoyaltyPercentage: 100, + } +} + +// SetTokenRoyalty sets the royalty information for a specific token ID. +func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error { + // Validate the payment address + if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { + return ErrInvalidRoyaltyPaymentAddress + } + + // Check if royalty percentage exceeds maxRoyaltyPercentage + if royaltyInfo.Percentage > r.maxRoyaltyPercentage { + return ErrInvalidRoyaltyPercentage + } + + // Check if the caller is the owner of the token + owner, err := r.metadataNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set royalty information for the token + r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) + + return nil +} + +// RoyaltyInfo returns the royalty information for the given token ID and sale price. +func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) { + // Retrieve royalty information for the token + val, found := r.tokenRoyaltyInfo.Get(string(tid)) + if !found { + return "", 0, ErrInvalidTokenId + } + + royaltyInfo := val.(RoyaltyInfo) + + // Calculate royalty amount + royaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) + + return royaltyInfo.PaymentAddress, royaltyAmount, nil +} + +func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + return royaltyAmount, nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_royalty_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_royalty_test.gno new file mode 100644 index 00000000..7893453a --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/grc721_royalty_test.gno @@ -0,0 +1,70 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetTokenRoyalty(t *testing.T) { + dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + paymentAddress := testutils.TestAddress("john") + percentage := uint64(10) // 10% + + salePrice := uint64(1000) + expectRoyaltyAmount := uint64(100) + + std.TestSetOrigCaller(addr1) // addr1 + + dummy.mint(addr1, TokenID("1")) + + derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, derr, ErrInvalidTokenId) + + std.TestSetOrigCaller(addr2) // addr2 + + cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Invalid payment address + aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + PaymentAddress: std.Address("###"), // invalid address + Percentage: percentage, + }) + uassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress) + + // Test case: Invalid percentage + perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: uint64(200), // over maxRoyaltyPercentage + }) + uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) + + // Test case: Retrieving Royalty Info + std.TestSetOrigCaller(addr1) // addr1 + + dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) + uassert.NoError(t, rerr, "RoyaltyInfo error") + uassert.Equal(t, paymentAddress, dummyPaymentAddress) + uassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721.gno new file mode 100644 index 00000000..ece48f8e --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721.gno @@ -0,0 +1,39 @@ +package grc721 + +import "std" + +type IGRC721 interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(tid TokenID) (std.Address, error) + SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) + SafeTransferFrom(from, to std.Address, tid TokenID) error + TransferFrom(from, to std.Address, tid TokenID) error + Approve(approved std.Address, tid TokenID) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(tid TokenID) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, tid TokenID) error +} + +type ( + TokenID string + TokenURI string +) + +type TransferEvent struct { + From std.Address + To std.Address + TokenID TokenID +} + +type ApprovalEvent struct { + Owner std.Address + Approved std.Address + TokenID TokenID +} + +type ApprovalForAllEvent struct { + Owner std.Address + Operator std.Address + Approved bool +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721_metadata.gno new file mode 100644 index 00000000..8a2be1ff --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721_metadata.gno @@ -0,0 +1,38 @@ +package grc721 + +// IGRC721CollectionMetadata describes basic information about an NFT collection. +type IGRC721CollectionMetadata interface { + Name() string // Name returns the name of the collection. + Symbol() string // Symbol returns the symbol of the collection. +} + +// IGRC721Metadata follows the Ethereum standard +type IGRC721Metadata interface { + IGRC721CollectionMetadata + TokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token. +} + +// IGRC721Metadata follows the OpenSea metadata standard +type IGRC721MetadataOnchain interface { + IGRC721CollectionMetadata + TokenMetadata(tid TokenID) (Metadata, error) +} + +type Trait struct { + DisplayType string + TraitType string + Value string +} + +// see: https://docs.opensea.io/docs/metadata-standards +type Metadata struct { + Image string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image. + ImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter. + ExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site. + Description string // Human-readable description of the item. Markdown is supported. + Name string // Name of the item. + Attributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item. + BackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended # + AnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported. + YoutubeURL string // URL to a YouTube video (only used if animation_url is not provided). +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721_royalty.gno new file mode 100644 index 00000000..c4603d63 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/igrc721_royalty.gno @@ -0,0 +1,16 @@ +package grc721 + +import "std" + +// IGRC2981 follows the Ethereum standard +type IGRC2981 interface { + // RoyaltyInfo retrieves royalty information for a tokenID and salePrice. + // It returns the payment address, royalty amount, and an error if any. + RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) +} + +// RoyaltyInfo represents royalty information for a token. +type RoyaltyInfo struct { + PaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent. + Percentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 => 10% +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/util.gno b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/util.gno new file mode 100644 index 00000000..bb6bf24d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v35/grc/grc721/util.gno @@ -0,0 +1,18 @@ +package grc721 + +import ( + "std" +) + +var zeroAddress = std.Address("") + +func isValidAddress(addr std.Address) error { + if !addr.IsValid() { + return ErrInvalidAddress + } + return nil +} + +func emit(event interface{}) { + // TODO: setup a pubsub system here? +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_metadata.gno new file mode 100644 index 00000000..388902c4 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_metadata.gno @@ -0,0 +1,38 @@ +package domain + +import ( + "time" +) + +// Trait represents a key-value pair with an optional display type for metadata attributes +type Trait struct { + DisplayType string // Optional display type (e.g., "date", "number", etc.) + TraitType string // Type of the trait (e.g., "age", "height", etc.) + Value string // Value of the trait +} + +// Metadata represents the metadata associated with a domain +type Metadata struct { + Avatar string // URL or identifier for an avatar image + RegistrationTime time.Time // The time when the domain was registered + ExpirationTime time.Time // The time when the domain will be expire + Attributes []Trait // Additional attributes of the domain + Description string // A description of the domain + ContactInfo string // Contact information for the domain owner + RenewalFee string // The fee required to renew the domain, represented as a string +} + +// NewMetadata creates a new Metadata instance +func NewMetadata(avatar, description, contactInfo, renewalFee string, + registrationTime, expirationTime time.Time, attributes []Trait, +) Metadata { + return Metadata{ + Avatar: avatar, + RegistrationTime: registrationTime, + ExpirationTime: expirationTime, + RenewalFee: renewalFee, + Attributes: attributes, + Description: description, + ContactInfo: contactInfo, + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_registry.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_registry.gno new file mode 100644 index 00000000..ef81abe6 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_registry.gno @@ -0,0 +1,236 @@ +package domain + +import ( + "std" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v36/grc/grc721" +) + +// domainRegistry represents a registry for domain names with metadata +type domainRegistry struct { + domains grc721.IGRC721 // Interface for basic NFT functionality + metadata *avl.Tree // AVL tree for storing domain metadata + expDate time.Time +} + +// DomainRegistry defines the methods for managing domain names and metadata +type DomainRegistry interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(domainName string) (std.Address, error) + SafeTransferFrom(from, to std.Address, domainName string) error + TransferFrom(from, to std.Address, domainName string) error + Approve(approved std.Address, domainName string) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(domainName string) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, domainName string) error + + RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error + SetDomainData(domainName string, metadata Metadata) error + GetDomainData(domainName string, field MetadataField) (Metadata, error) + GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) + RenewDomain(domainName string, additionalDuration time.Duration) error + GetExpirationDate(domainName string) time.Time + SetExpirationDate(domainName string, expDate time.Time) bool +} + +// NewDomainRegistry creates a new domain registry with metadata extensions +func NewDomainRegistry(name, symbol string) *domainRegistry { + registry := grc721.NewBasicNFT(name, symbol) + + return &domainRegistry{ + domains: registry, + metadata: avl.NewTree(), + } +} + +// RegisterDomain registers a new domain with the given metadata +func (d *domainRegistry) RegisterDomain(owner std.Address, domainName string, metadata Metadata, dur time.Duration) error { + err := d.domains.Mint(owner, grc721.TokenID(domainName)) + if err != nil { + return err + } + d.expDate = time.Now().Add(dur) + d.metadata.Set(domainName, metadata) + + return nil +} + +// RenewDomain extends the expiration time of a domain name +func (d *domainRegistry) RenewDomain(domainName string, additionalDuration time.Duration) error { + _, found := d.metadata.Get(domainName) + if !found { + return ErrInvalidDomainName + } + + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + // set new expiration date + d.expDate = d.expDate.Add(additionalDuration) + return nil +} + +// SetDomainData sets the metadata for a given domain name +func (d *domainRegistry) SetDomainData(domainName string, metadata Metadata) error { + owner, err := d.domains.OwnerOf(grc721.TokenID(domainName)) + if err != nil { + return err + } + + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrUnauthorized + } + + d.metadata.Set(domainName, metadata) + return nil +} + +// GetDomainFields retrieves multiple fields of metadata for a given domain +func (d *domainRegistry) GetDomainFields(domainName string, fields []MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + if len(fields) == 0 { + return metadata, nil + } + + var result Metadata + for _, field := range fields { + switch field { + case FieldAvatar: + result.Avatar = metadata.Avatar + case FieldRegistrationTime: + result.RegistrationTime = metadata.RegistrationTime + case FieldExpirationTime: + result.ExpirationTime = metadata.ExpirationTime + case FieldRenewalFee: + result.RenewalFee = metadata.RenewalFee + case FieldAttributes: + result.Attributes = metadata.Attributes + case FieldDescription: + result.Description = metadata.Description + case FieldContactInfo: + result.ContactInfo = metadata.ContactInfo + default: + return Metadata{}, ErrInvalidMetadataField + } + } + + return result, nil +} + +// GetDomainData retrieves metadata for a given domain +func (d *domainRegistry) GetDomainData(domainName string, field MetadataField) (Metadata, error) { + data, found := d.metadata.Get(domainName) + if !found { + return Metadata{}, ErrInvalidDomainName + } + + metadata := data.(Metadata) + + switch field { + case FieldAvatar: + return Metadata{ + Avatar: metadata.Avatar, + }, nil + case FieldRegistrationTime: + return Metadata{ + RegistrationTime: metadata.RegistrationTime, + }, nil + case FieldExpirationTime: + return Metadata{ + ExpirationTime: metadata.ExpirationTime, + }, nil + case FieldRenewalFee: + return Metadata{ + RenewalFee: metadata.RenewalFee, + }, nil + case FieldAttributes: + return Metadata{ + Attributes: metadata.Attributes, + }, nil + case FieldDescription: + return Metadata{ + Description: metadata.Description, + }, nil + case FieldContactInfo: + return Metadata{ + ContactInfo: metadata.ContactInfo, + }, nil + default: + return Metadata{}, ErrInvalidMetadataField + } +} + +// BalanceOf returns the number of domains owned by a given address +func (d *domainRegistry) BalanceOf(owner std.Address) (uint64, error) { + return d.domains.BalanceOf(owner) +} + +// OwnerOf returns the owner of a given domain name +func (d *domainRegistry) OwnerOf(domainName string) (std.Address, error) { + return d.domains.OwnerOf(grc721.TokenID(domainName)) +} + +// SafeTransferFrom safely transfers a domain from one address to another +func (d *domainRegistry) SafeTransferFrom(from, to std.Address, domainName string) error { + return d.domains.SafeTransferFrom(from, to, grc721.TokenID(domainName)) +} + +// TransferFrom transfers a domain from one address to another +func (d *domainRegistry) TransferFrom(from, to std.Address, domainName string) error { + return d.domains.TransferFrom(from, to, grc721.TokenID(domainName)) +} + +// Approve grants approval to another address to manage a specific domain +func (d *domainRegistry) Approve(approved std.Address, domainName string) error { + return d.domains.Approve(approved, grc721.TokenID(domainName)) +} + +// SetApprovalForAll sets approval for an operator to manage all domains of the owner +func (d *domainRegistry) SetApprovalForAll(operator std.Address, approved bool) error { + return d.domains.SetApprovalForAll(operator, approved) +} + +// GetApproved returns the approved address for a specific domain +func (d *domainRegistry) GetApproved(domainName string) (std.Address, error) { + return d.domains.GetApproved(grc721.TokenID(domainName)) +} + +// IsApprovedForAll checks if an operator is approved to manage all domains of the owner +func (d *domainRegistry) IsApprovedForAll(owner, operator std.Address) bool { + return d.domains.IsApprovedForAll(owner, operator) +} + +// Mint creates a new domain for a given address +func (d *domainRegistry) Mint(to std.Address, domainName string) error { + return d.domains.Mint(to, grc721.TokenID(domainName)) +} + +func (d *domainRegistry) GetExpirationDate(domainName string) time.Time { + return d.expDate +} + +func (d *domainRegistry) SetExpirationDate(domainName string, expDate time.Time) bool { + _, found := d.metadata.Get(domainName) + if !found { + return false + } + d.expDate = expDate + return true +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_registry_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_registry_test.gno new file mode 100644 index 00000000..463ea017 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/domain_registry_test.gno @@ -0,0 +1,378 @@ +package domain + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/urequire" + "gno.land/p/varmeta/demo/v36/grc/grc721" +) + +var ( + addr1 = testutils.TestAddress("bob") + addr2 = testutils.TestAddress("alice") +) + +func TestRegisterDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Successful Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "contact@registered.com", + }, + expectError: false, + }, + { + name: "Duplicate Registration", + owner: addr1, + domainName: "registered.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A registered domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + err := registry.RegisterDomain(c.owner, c.domainName, c.metadata) + if c.expectError { + urequire.Error(t, err) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.owner, retrievedOwner) + } + }) + } +} + +func TestSetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + metadata Metadata + expectError bool + }{ + { + name: "Owner Sets Metadata", + owner: addr1, + caller: addr1, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: false, + }, + { + name: "Non-Owner Sets Metadata", + owner: addr1, + caller: addr2, + domainName: "test.gno", + metadata: Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + registry.RegisterDomain(c.owner, c.domainName, c.metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + err := registry.SetDomainData(c.domainName, c.metadata) + + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + retrievedMetadata, err := registry.GetDomainData(c.domainName, FieldAvatar) + urequire.NoError(t, err) + urequire.Equal(t, c.metadata.Avatar, retrievedMetadata.Avatar) + } + }) + } +} + +func TestRenewDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + caller std.Address + domainName string + additionalTime time.Duration + expectError bool + expectedExpiry time.Time + }{ + { + name: "Successful Renewal", + owner: addr1, + caller: addr1, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: false, + }, + { + name: "Non-Owner Attempts Renewal", + owner: addr1, + caller: addr2, + domainName: "renewable.gno", + additionalTime: 30 * 24 * time.Hour, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A renewable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.RenewDomain(c.domainName, c.additionalTime) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrUnauthorized.Error()) + } else { + urequire.NoError(t, err) + renewedMetadata, err := registry.GetDomainData(c.domainName, FieldExpirationTime) + urequire.NoError(t, err) + // urequire.True(t, renewedMetadata.ExpirationTime.After(metadata.ExpirationTime)) + } + }) + } +} + +func TestGetDomainData(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + field MetadataField + expectError bool + expectedVal string + }{ + { + name: "Retrieve Avatar", + domainName: "test.gno", + field: FieldAvatar, + expectError: false, + expectedVal: "avatar_url", + }, + { + name: "Invalid Domain Name", + domainName: "invalid.gno", + field: FieldAvatar, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + _, err := registry.GetDomainData(c.domainName, c.field) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + } + }) + } +} + +func TestGetDomainFields(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + domainName string + fields []MetadataField + expectError bool + expected Metadata + }{ + { + name: "Retrieve Multiple Fields", + domainName: "test.gno", + fields: []MetadataField{FieldAvatar, FieldDescription, FieldContactInfo}, + expectError: false, + expected: Metadata{ + Avatar: "avatar_url", + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + }, + }, + { + name: "Invalid Domain", + domainName: "invalid.gno", + fields: []MetadataField{FieldAvatar}, + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if !c.expectError { + owner := addr1 + std.TestSetRealm(std.NewUserRealm(owner)) + std.TestSetOrigCaller(owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A test domain", + ContactInfo: "gno_name_service@gno.land", + } + + err := registry.RegisterDomain(owner, c.domainName, metadata) + urequire.NoError(t, err) + } + + retrievedMetadata, err := registry.GetDomainFields(c.domainName, c.fields) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), ErrInvalidDomainName.Error()) + } else { + urequire.NoError(t, err) + urequire.Equal(t, c.expected.Avatar, retrievedMetadata.Avatar) + urequire.Equal(t, c.expected.Description, retrievedMetadata.Description) + urequire.Equal(t, c.expected.ContactInfo, retrievedMetadata.ContactInfo) + } + }) + } +} + +func TestTransferDomain(t *testing.T) { + registry := NewDomainRegistry("GNO Name Service", "GNS") + + cases := []struct { + name string + owner std.Address + newOwner std.Address + caller std.Address + domainName string + expectError bool + }{ + { + name: "Successful Transfer", + owner: addr1, + newOwner: addr2, + caller: addr1, + domainName: "transfer.gno", + expectError: false, + }, + { + name: "Non-Owner Attempts Transfer", + owner: addr1, + newOwner: addr2, + caller: addr2, + domainName: "transfer.gno", + expectError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + std.TestSetRealm(std.NewUserRealm(c.owner)) + std.TestSetOrigCaller(c.owner) + + metadata := Metadata{ + Avatar: "avatar_url", + RegistrationTime: time.Now(), + ExpirationTime: time.Now().Add(365 * 24 * time.Hour), + Description: "A transferable domain", + ContactInfo: "gno_name_service@gno.land", + } + + registry.RegisterDomain(c.owner, c.domainName, metadata) + + std.TestSetRealm(std.NewUserRealm(c.caller)) + std.TestSetOrigCaller(c.caller) + + err := registry.TransferFrom(c.owner, c.newOwner, c.domainName) + if c.expectError { + urequire.Error(t, err) + urequire.Equal(t, err.Error(), grc721.ErrTransferFromIncorrectOwner.Error()) + } else { + urequire.NoError(t, err) + + retrievedOwner, err := registry.OwnerOf(c.domainName) + urequire.NoError(t, err) + urequire.Equal(t, c.newOwner, retrievedOwner) + } + }) + } +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/domain/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/errors.gno new file mode 100644 index 00000000..3de5d750 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/errors.gno @@ -0,0 +1,12 @@ +package domain + +import ( + "errors" +) + +var ( + ErrUnauthorized = errors.New("caller is not domain owner") + ErrInvalidDomainName = errors.New("invalid domain name") + ErrInvalidMetadataField = errors.New("invalid metadata field") + ErrInsufficientFunds = errors.New("insufficient funds for renewal") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/domain/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/domain/utils.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/utils.gno new file mode 100644 index 00000000..13e40d99 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/domain/utils.gno @@ -0,0 +1,13 @@ +package domain + +type MetadataField int + +const ( + FieldAvatar MetadataField = iota + FieldRegistrationTime + FieldRenewalFee + FieldExpirationTime + FieldAttributes + FieldDescription + FieldContactInfo +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/basic_nft.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/basic_nft.gno new file mode 100644 index 00000000..bec7338d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/basic_nft.gno @@ -0,0 +1,378 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" +) + +type basicNFT struct { + name string + symbol string + owners avl.Tree // tokenId -> OwnerAddress + balances avl.Tree // OwnerAddress -> TokenCount + tokenApprovals avl.Tree // TokenId -> ApprovedAddress + tokenURIs avl.Tree // TokenId -> URIs + operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool +} + +// Returns new basic NFT +func NewBasicNFT(name string, symbol string) *basicNFT { + return &basicNFT{ + name: name, + symbol: symbol, + + owners: avl.Tree{}, + balances: avl.Tree{}, + tokenApprovals: avl.Tree{}, + tokenURIs: avl.Tree{}, + operatorApprovals: avl.Tree{}, + } +} + +func (s *basicNFT) Name() string { return s.name } +func (s *basicNFT) Symbol() string { return s.symbol } +func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) } + +// BalanceOf returns balance of input address +func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) { + if err := isValidAddress(addr); err != nil { + return 0, err + } + + balance, found := s.balances.Get(addr.String()) + if !found { + return 0, nil + } + + return balance.(uint64), nil +} + +// OwnerOf returns owner of input token id +func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) { + owner, found := s.owners.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return owner.(std.Address), nil +} + +// TokenURI returns the URI of input token id +func (s *basicNFT) TokenURI(tid TokenID) (string, error) { + uri, found := s.tokenURIs.Get(string(tid)) + if !found { + return "", ErrInvalidTokenId + } + + return uri.(string), nil +} + +func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { + // check for invalid TokenID + if !s.exists(tid) { + return false, ErrInvalidTokenId + } + + // check for the right owner + owner, err := s.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + s.tokenURIs.Set(string(tid), string(tURI)) + return true, nil +} + +// IsApprovedForAll returns true if operator is approved for all by the owner. +// Otherwise, returns false +func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool { + key := owner.String() + ":" + operator.String() + _, found := s.operatorApprovals.Get(key) + if !found { + return false + } + + return true +} + +// Approve approves the input address for particular token +func (s *basicNFT) Approve(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner == to { + return ErrApprovalToCurrentOwner + } + + caller := std.PrevRealm().Addr() + if caller != owner && !s.IsApprovedForAll(owner, caller) { + return ErrCallerIsNotOwnerOrApproved + } + + s.tokenApprovals.Set(string(tid), to.String()) + event := ApprovalEvent{owner, to, tid} + emit(&event) + + return nil +} + +// GetApproved return the approved address for token +func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) { + addr, found := s.tokenApprovals.Get(string(tid)) + if !found { + return zeroAddress, ErrTokenIdNotHasApproved + } + + return std.Address(addr.(string)), nil +} + +// SetApprovalForAll can approve the operator to operate on all tokens +func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error { + if err := isValidAddress(operator); err != nil { + return ErrInvalidAddress + } + + caller := std.PrevRealm().Addr() + return s.setApprovalForAll(caller, operator, approved) +} + +// Safely transfers `tokenId` token from `from` to `to`, checking that +// contract recipients are aware of the GRC721 protocol to prevent +// tokens from being forever locked. +func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(from, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +// Transfers `tokenId` token from `from` to `to`. +func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { + caller := std.PrevRealm().Addr() + if !s.isApprovedOrOwner(caller, tid) { + return ErrCallerIsNotOwnerOrApproved + } + + err := s.transfer(from, to, tid) + if err != nil { + return err + } + + return nil +} + +// Mints `tokenId` and transfers it to `to`. +func (s *basicNFT) Mint(to std.Address, tid TokenID) error { + return s.mint(to, tid) +} + +// Mints `tokenId` and transfers it to `to`. Also checks that +// contract recipients are using GRC721 protocol +func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error { + err := s.mint(to, tid) + if err != nil { + return err + } + + if !s.checkOnGRC721Received(zeroAddress, to, tid) { + return ErrTransferToNonGRC721Receiver + } + + return nil +} + +func (s *basicNFT) Burn(tid TokenID) error { + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + + s.beforeTokenTransfer(owner, zeroAddress, tid, 1) + + s.tokenApprovals.Remove(string(tid)) + balance, err := s.BalanceOf(owner) + if err != nil { + return err + } + balance -= 1 + s.balances.Set(owner.String(), balance) + s.owners.Remove(string(tid)) + + event := TransferEvent{owner, zeroAddress, tid} + emit(&event) + + s.afterTokenTransfer(owner, zeroAddress, tid, 1) + + return nil +} + +/* Helper methods */ + +// Helper for SetApprovalForAll() +func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error { + if owner == operator { + return ErrApprovalToCurrentOwner + } + + key := owner.String() + ":" + operator.String() + s.operatorApprovals.Set(key, approved) + + event := ApprovalForAllEvent{owner, operator, approved} + emit(&event) + + return nil +} + +// Helper for TransferFrom() and SafeTransferFrom() +func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error { + if err := isValidAddress(from); err != nil { + return ErrInvalidAddress + } + if err := isValidAddress(to); err != nil { + return ErrInvalidAddress + } + + if from == to { + return ErrCannotTransferToSelf + } + + owner, err := s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.beforeTokenTransfer(from, to, tid, 1) + + // Check that tokenId was not transferred by `beforeTokenTransfer` + owner, err = s.OwnerOf(tid) + if err != nil { + return err + } + if owner != from { + return ErrTransferFromIncorrectOwner + } + + s.tokenApprovals.Remove(string(tid)) + fromBalance, err := s.BalanceOf(from) + if err != nil { + return err + } + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + fromBalance -= 1 + toBalance += 1 + s.balances.Set(from.String(), fromBalance) + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{from, to, tid} + emit(&event) + + s.afterTokenTransfer(from, to, tid, 1) + + return nil +} + +// Helper for Mint() and SafeMint() +func (s *basicNFT) mint(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check that tokenId was not minted by `beforeTokenTransfer` + if s.exists(tid) { + return ErrTokenIdAlreadyExists + } + + toBalance, err := s.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.balances.Set(to.String(), toBalance) + s.owners.Set(string(tid), to) + + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} + +func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool { + owner, found := s.owners.Get(string(tid)) + if !found { + return false + } + + if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) { + return true + } + + _, err := s.GetApproved(tid) + if err != nil { + return false + } + + return true +} + +// Checks if token id already exists +func (s *basicNFT) exists(tid TokenID) bool { + _, found := s.owners.Get(string(tid)) + return found +} + +func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { + // TODO: Implementation +} + +func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool { + // TODO: Implementation + return true +} + +func (s *basicNFT) RenderHome() (str string) { + str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol) + str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount()) + str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size()) + + return +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/basic_nft_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/basic_nft_test.gno new file mode 100644 index 00000000..6375b030 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/basic_nft_test.gno @@ -0,0 +1,283 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/uassert" +) + +var ( + dummyNFTName = "DummyNFT" + dummyNFTSymbol = "DNFT" +) + +func TestNewBasicNFT(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") +} + +func TestName(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + name := dummy.Name() + uassert.Equal(t, dummyNFTName, name) +} + +func TestSymbol(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + symbol := dummy.Symbol() + uassert.Equal(t, dummyNFTSymbol, symbol) +} + +func TestTokenCount(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + count := dummy.TokenCount() + uassert.Equal(t, uint64(0), count) + + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("1")) + dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("2")) + + count = dummy.TokenCount() + uassert.Equal(t, uint64(2), count) +} + +func TestBalanceOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + balanceAddr1, err := dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(0), balanceAddr1) + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr1, TokenID("2")) + dummy.mint(addr2, TokenID("3")) + + balanceAddr1, err = dummy.BalanceOf(addr1) + uassert.NoError(t, err, "should not result in error") + + balanceAddr2, err := dummy.BalanceOf(addr2) + uassert.NoError(t, err, "should not result in error") + + uassert.Equal(t, uint64(2), balanceAddr1) + uassert.Equal(t, uint64(1), balanceAddr2) +} + +func TestOwnerOf(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + owner, err := dummy.OwnerOf(TokenID("invalid")) + uassert.Error(t, err, "should not result in error") + + dummy.mint(addr1, TokenID("1")) + dummy.mint(addr2, TokenID("2")) + + // Checking for token id "1" + owner, err = dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) + + // Checking for token id "2" + owner, err = dummy.OwnerOf(TokenID("2")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr2.String(), owner.String()) +} + +func TestIsApprovedForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) + uassert.False(t, isApprovedForAll) +} + +func TestSetApprovalForAll(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + isApprovedForAll := dummy.IsApprovedForAll(caller, addr) + uassert.False(t, isApprovedForAll) + + err := dummy.SetApprovalForAll(addr, true) + uassert.NoError(t, err, "should not result in error") + + isApprovedForAll = dummy.IsApprovedForAll(caller, addr) + uassert.True(t, isApprovedForAll) +} + +func TestGetApproved(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + approvedAddr, err := dummy.GetApproved(TokenID("invalid")) + uassert.Error(t, err, "should result in error") +} + +func TestApprove(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + + _, err := dummy.GetApproved(TokenID("1")) + uassert.Error(t, err, "should result in error") + + err = dummy.Approve(addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + approvedAddr, err := dummy.GetApproved(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), approvedAddr.String()) +} + +func TestTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.TransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestSafeTransferFrom(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + caller := std.PrevRealm().Addr() + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(caller, TokenID("1")) + dummy.mint(caller, TokenID("2")) + + err := dummy.SafeTransferFrom(caller, addr, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check balance of caller after transfer + balanceOfCaller, err := dummy.BalanceOf(caller) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfCaller) + + // Check balance of addr after transfer + balanceOfAddr, err := dummy.BalanceOf(addr) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, uint64(1), balanceOfAddr) + + // Check Owner of transferred Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr.String(), owner.String()) +} + +func TestMint(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + err := dummy.Mint(addr1, TokenID("1")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr1, TokenID("2")) + uassert.NoError(t, err, "should not result in error") + err = dummy.Mint(addr2, TokenID("3")) + uassert.NoError(t, err, "should not result in error") + + // Try minting duplicate token id + err = dummy.Mint(addr2, TokenID("1")) + uassert.Error(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + uassert.Equal(t, addr1.String(), owner.String()) +} + +func TestBurn(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + + dummy.mint(addr, TokenID("1")) + dummy.mint(addr, TokenID("2")) + + err := dummy.Burn(TokenID("1")) + uassert.NoError(t, err, "should not result in error") + + // Check Owner of Token id + owner, err := dummy.OwnerOf(TokenID("1")) + uassert.Error(t, err, "should result in error") +} + +func TestSetTokenURI(t *testing.T) { + dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + tokenURI := "http://example.com/token" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1, TokenID("1")) + _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) + uassert.NoError(t, derr, "should not result in error") + + // Test case: Invalid token ID + _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Retrieving TokenURI + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyTokenURI, err := dummy.TokenURI(TokenID("1")) + uassert.NoError(t, err, "TokenURI error") + uassert.Equal(t, string(tokenURI), string(dummyTokenURI)) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/errors.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/errors.gno new file mode 100644 index 00000000..2d512db3 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/errors.gno @@ -0,0 +1,22 @@ +package grc721 + +import "errors" + +var ( + ErrInvalidTokenId = errors.New("invalid token id") + ErrInvalidAddress = errors.New("invalid address") + ErrTokenIdNotHasApproved = errors.New("token id not approved for anyone") + ErrApprovalToCurrentOwner = errors.New("approval to current owner") + ErrCallerIsNotOwner = errors.New("caller is not token owner") + ErrCallerNotApprovedForAll = errors.New("caller is not approved for all") + ErrCannotTransferToSelf = errors.New("cannot send transfer to self") + ErrTransferFromIncorrectOwner = errors.New("transfer from incorrect owner") + ErrTransferToNonGRC721Receiver = errors.New("transfer to non GRC721Receiver implementer") + ErrCallerIsNotOwnerOrApproved = errors.New("caller is not token owner or approved") + ErrTokenIdAlreadyExists = errors.New("token id already exists") + + // ERC721Royalty + ErrInvalidRoyaltyPercentage = errors.New("invalid royalty percentage") + ErrInvalidRoyaltyPaymentAddress = errors.New("invalid royalty paymentAddress") + ErrCannotCalculateRoyaltyAmount = errors.New("cannot calculate royalty amount") +) diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_metadata.gno new file mode 100644 index 00000000..360f73ed --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_metadata.gno @@ -0,0 +1,95 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// metadataNFT represents an NFT with metadata extensions. +type metadataNFT struct { + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions *avl.Tree // AVL tree for storing metadata extensions +} + +// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. +var _ IGRC721MetadataOnchain = (*metadataNFT)(nil) + +// NewNFTWithMetadata creates a new basic NFT with metadata extensions. +func NewNFTWithMetadata(name string, symbol string) *metadataNFT { + // Create a new basic NFT + nft := NewBasicNFT(name, symbol) + + // Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions + return &metadataNFT{ + basicNFT: nft, + extensions: avl.NewTree(), + } +} + +// SetTokenMetadata sets metadata for a given token ID. +func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { + // Check if the caller is the owner of the token + owner, err := s.basicNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set the metadata for the token ID in the extensions AVL tree + s.extensions.Set(string(tid), metadata) + return nil +} + +// TokenMetadata retrieves metadata for a given token ID. +func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { + // Retrieve metadata from the extensions AVL tree + metadata, found := s.extensions.Get(string(tid)) + if !found { + return Metadata{}, ErrInvalidTokenId + } + + return metadata.(Metadata), nil +} + +// mint mints a new token and assigns it to the specified address. +func (s *metadataNFT) mint(to std.Address, tid TokenID) error { + // Check if the address is valid + if err := isValidAddress(to); err != nil { + return err + } + + // Check if the token ID already exists + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check if the token ID was minted by beforeTokenTransfer + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + // Increment balance of the recipient address + toBalance, err := s.basicNFT.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.basicNFT.balances.Set(to.String(), toBalance) + + // Set owner of the token ID to the recipient address + s.basicNFT.owners.Set(string(tid), to) + + // Emit transfer event + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_metadata_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_metadata_test.gno new file mode 100644 index 00000000..ad002a7c --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_metadata_test.gno @@ -0,0 +1,107 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetMetadata(t *testing.T) { + // Create a new dummy NFT with metadata + dummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + // Define addresses for testing purposes + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + // Define metadata attributes + name := "test" + description := "test" + image := "test" + imageData := "test" + externalURL := "test" + attributes := []Trait{} + backgroundColor := "test" + animationURL := "test" + youtubeURL := "test" + + // Set the original caller to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Mint a new token for addr1 + dummy.mint(addr1, TokenID("1")) + + // Set metadata for token 1 + derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if there was an error setting metadata + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, err, ErrInvalidTokenId) + + // Set the original caller to addr2 + std.TestSetOrigCaller(addr2) // addr2 + + // Try to set metadata for token 1 from addr2 (should fail) + cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Set the original caller back to addr1 + std.TestSetOrigCaller(addr1) // addr1 + + // Retrieve metadata for token 1 + dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) + uassert.NoError(t, err, "Metadata error") + + // Check if metadata attributes match expected values + uassert.Equal(t, image, dummyMetadata.Image) + uassert.Equal(t, imageData, dummyMetadata.ImageData) + uassert.Equal(t, externalURL, dummyMetadata.ExternalURL) + uassert.Equal(t, description, dummyMetadata.Description) + uassert.Equal(t, name, dummyMetadata.Name) + uassert.Equal(t, len(attributes), len(dummyMetadata.Attributes)) + uassert.Equal(t, backgroundColor, dummyMetadata.BackgroundColor) + uassert.Equal(t, animationURL, dummyMetadata.AnimationURL) + uassert.Equal(t, youtubeURL, dummyMetadata.YoutubeURL) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_royalty.gno new file mode 100644 index 00000000..9831c709 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_royalty.gno @@ -0,0 +1,78 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// royaltyNFT represents a non-fungible token (NFT) with royalty functionality. +type royaltyNFT struct { + *metadataNFT // Embedding metadataNFT for NFT functionality + tokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token + maxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale +} + +// Ensure that royaltyNFT implements the IGRC2981 interface. +var _ IGRC2981 = (*royaltyNFT)(nil) + +// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. +func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { + // Create a new NFT with metadata + nft := NewNFTWithMetadata(name, symbol) + + return &royaltyNFT{ + metadataNFT: nft, + tokenRoyaltyInfo: avl.NewTree(), + maxRoyaltyPercentage: 100, + } +} + +// SetTokenRoyalty sets the royalty information for a specific token ID. +func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error { + // Validate the payment address + if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { + return ErrInvalidRoyaltyPaymentAddress + } + + // Check if royalty percentage exceeds maxRoyaltyPercentage + if royaltyInfo.Percentage > r.maxRoyaltyPercentage { + return ErrInvalidRoyaltyPercentage + } + + // Check if the caller is the owner of the token + owner, err := r.metadataNFT.OwnerOf(tid) + if err != nil { + return err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return ErrCallerIsNotOwner + } + + // Set royalty information for the token + r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) + + return nil +} + +// RoyaltyInfo returns the royalty information for the given token ID and sale price. +func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) { + // Retrieve royalty information for the token + val, found := r.tokenRoyaltyInfo.Get(string(tid)) + if !found { + return "", 0, ErrInvalidTokenId + } + + royaltyInfo := val.(RoyaltyInfo) + + // Calculate royalty amount + royaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) + + return royaltyInfo.PaymentAddress, royaltyAmount, nil +} + +func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + return royaltyAmount, nil +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_royalty_test.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_royalty_test.gno new file mode 100644 index 00000000..7893453a --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/grc721_royalty_test.gno @@ -0,0 +1,70 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/uassert" +) + +func TestSetTokenRoyalty(t *testing.T) { + dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) + uassert.True(t, dummy != nil, "should not be nil") + + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") + + paymentAddress := testutils.TestAddress("john") + percentage := uint64(10) // 10% + + salePrice := uint64(1000) + expectRoyaltyAmount := uint64(100) + + std.TestSetOrigCaller(addr1) // addr1 + + dummy.mint(addr1, TokenID("1")) + + derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.NoError(t, derr, "Should not result in error") + + // Test case: Invalid token ID + err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, derr, ErrInvalidTokenId) + + std.TestSetOrigCaller(addr2) // addr2 + + cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + uassert.ErrorIs(t, cerr, ErrCallerIsNotOwner) + + // Test case: Invalid payment address + aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + PaymentAddress: std.Address("###"), // invalid address + Percentage: percentage, + }) + uassert.ErrorIs(t, aerr, ErrInvalidRoyaltyPaymentAddress) + + // Test case: Invalid percentage + perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: uint64(200), // over maxRoyaltyPercentage + }) + uassert.ErrorIs(t, perr, ErrInvalidRoyaltyPercentage) + + // Test case: Retrieving Royalty Info + std.TestSetOrigCaller(addr1) // addr1 + + dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) + uassert.NoError(t, rerr, "RoyaltyInfo error") + uassert.Equal(t, paymentAddress, dummyPaymentAddress) + uassert.Equal(t, expectRoyaltyAmount, dummyRoyaltyAmount) +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721.gno new file mode 100644 index 00000000..ece48f8e --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721.gno @@ -0,0 +1,39 @@ +package grc721 + +import "std" + +type IGRC721 interface { + BalanceOf(owner std.Address) (uint64, error) + OwnerOf(tid TokenID) (std.Address, error) + SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) + SafeTransferFrom(from, to std.Address, tid TokenID) error + TransferFrom(from, to std.Address, tid TokenID) error + Approve(approved std.Address, tid TokenID) error + SetApprovalForAll(operator std.Address, approved bool) error + GetApproved(tid TokenID) (std.Address, error) + IsApprovedForAll(owner, operator std.Address) bool + Mint(to std.Address, tid TokenID) error +} + +type ( + TokenID string + TokenURI string +) + +type TransferEvent struct { + From std.Address + To std.Address + TokenID TokenID +} + +type ApprovalEvent struct { + Owner std.Address + Approved std.Address + TokenID TokenID +} + +type ApprovalForAllEvent struct { + Owner std.Address + Operator std.Address + Approved bool +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721_metadata.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721_metadata.gno new file mode 100644 index 00000000..8a2be1ff --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721_metadata.gno @@ -0,0 +1,38 @@ +package grc721 + +// IGRC721CollectionMetadata describes basic information about an NFT collection. +type IGRC721CollectionMetadata interface { + Name() string // Name returns the name of the collection. + Symbol() string // Symbol returns the symbol of the collection. +} + +// IGRC721Metadata follows the Ethereum standard +type IGRC721Metadata interface { + IGRC721CollectionMetadata + TokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token. +} + +// IGRC721Metadata follows the OpenSea metadata standard +type IGRC721MetadataOnchain interface { + IGRC721CollectionMetadata + TokenMetadata(tid TokenID) (Metadata, error) +} + +type Trait struct { + DisplayType string + TraitType string + Value string +} + +// see: https://docs.opensea.io/docs/metadata-standards +type Metadata struct { + Image string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image. + ImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter. + ExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site. + Description string // Human-readable description of the item. Markdown is supported. + Name string // Name of the item. + Attributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item. + BackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended # + AnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported. + YoutubeURL string // URL to a YouTube video (only used if animation_url is not provided). +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721_royalty.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721_royalty.gno new file mode 100644 index 00000000..c4603d63 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/igrc721_royalty.gno @@ -0,0 +1,16 @@ +package grc721 + +import "std" + +// IGRC2981 follows the Ethereum standard +type IGRC2981 interface { + // RoyaltyInfo retrieves royalty information for a tokenID and salePrice. + // It returns the payment address, royalty amount, and an error if any. + RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) +} + +// RoyaltyInfo represents royalty information for a token. +type RoyaltyInfo struct { + PaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent. + Percentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 => 10% +} diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/pkg_metadata.json b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/util.gno b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/util.gno new file mode 100644 index 00000000..bb6bf24d --- /dev/null +++ b/test4.gno.land/extracted/p/varmeta/demo/v36/grc/grc721/util.gno @@ -0,0 +1,18 @@ +package grc721 + +import ( + "std" +) + +var zeroAddress = std.Address("") + +func isValidAddress(addr std.Address) error { + if !addr.IsValid() { + return ErrInvalidAddress + } + return nil +} + +func emit(event interface{}) { + // TODO: setup a pubsub system here? +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/bidding.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/bidding.gno new file mode 100644 index 00000000..35adb54b --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/bidding.gno @@ -0,0 +1,435 @@ +package registrar + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/ufmt" + // "gno.land/p/demo/mux" + "gno.land/p/demo/avl" +) + +type bidRecord struct { + DomainName string + Bidder std.Address + HashString string + Price int + StartTime time.Time + EndCommitTime time.Time + EndPriceTime time.Time + CurrentPhase actionCode + IsOpen bool +} + +var ( + BidRec *avl.Tree // bidRecord <- []bidRec + winnerRec *avl.Tree + joinedBid avl.Tree +) + +// joinedBid: address <- []domainName +func recordJoinedBid(domainName string) { + caller := std.GetOrigCaller() + dList := []string{} + data, existed := joinedBid.Get(domainName) + if !existed { + dList = []string{domainName} + joinedBid.Set(caller.String(), dList) + return + } + dList = data.([]string) + dList = append(dList, domainName) + joinedBid.Set(caller.String(), dList) + return +} + +// get the joined bids of an account +func GetJoinedBid() []string { + caller := std.GetOrigCaller().String() + data, existed := joinedBid.Get(caller) + if !existed { + return []string{} + } + list := data.([]string) + return list +} + +// get the next state of bidding session +func GetCurrentStatus(domainName string) string { + // if there is record in joinedBid -> user joined + // check for tine.Now() and startTime + now := time.Now() + caller := std.GetOrigCaller() + // find the record + data, existed := BidRec.Get(domainName) + if !existed { + if _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister { + return "hash" + } + ufmt.Println("run here") + return "dName is invalid" + } + recList := data.([]bidRecord) + rec := recList[0] + + if now.Before(rec.EndCommitTime) { + return "waiting price" + } + if now.Before(rec.EndPriceTime) && now.After(rec.EndCommitTime) { + return "price" + } + if now.After(rec.EndPriceTime) { + return "closed" + } + return "undefined" +} + +// Render() renders welcome message :D +func Render(path string) string { + return "welcome to varmeta domain name service" +} + +/* + logic should be: + 1. User click register for a dns. + -> 1.1: dns does not exists -> open bidding session + -> 1.2: dns existed -> check if open bidding session is openning or not -> if open -> new bidRecord + 2. Bidding session: + -> 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -> creat bidRecord to add to bidRec + -> 2.2: CommitPrice phase: commit price + secret key: -> if matches -> write price into bidRec ( send the amunt of coin corresponding) + -> 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button + ---> admin end bidding auction + -> 2.3.1: cooldown phase -> waiting for winner -> 2nd winner + -> 2.4: re-transfer coin from admin to user who not win the bid + the flow is: user want to register new domain name -> start new bidding session -> got the bidding session id + if other want to register to the same domain name -> bidding into this session, if not return to new state + when the session ends, need to have 2nd phase called commit -> commit their price and secret to compute the hash + -> compare the hash as in record -> accept the hash -> accept the price + *** temp approach: everytime user commit, user open a bid session if not existed, if existed -> check if this session is ended. +*/ +// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate +// the time whenever there is action to commit. + +// commit the domain name and computed hash string +func CommitHash(domainName, hashString string) bidStatus { + bStt := bidStatus{} + caller := std.GetOrigCaller() + ufmt.Println("caller: ", caller.String()) + now := time.Now() + + // update the bid record + data, existed := BidRec.Get(domainName) + + // if not existed -> create new record + if !existed { + var bidRec bidRecord + endCommitTime := now.Add(defaultCommitHashTime) + endPriceTime := endCommitTime.Add(defaultCommitPriceTime) + ufmt.Println("endCommitTime: ", endCommitTime.Format(time.RFC3339)) + ufmt.Println("endPriceTime: ", endPriceTime.Format(time.RFC3339)) + bidRec = bidRecord{ + DomainName: domainName, + Bidder: caller, + HashString: hashString, + StartTime: now, + EndCommitTime: endCommitTime, + EndPriceTime: endPriceTime, + IsOpen: true, + } + bidRecList := []bidRecord{bidRec} + BidRec.Set(domainName, bidRecList) + + // charge fee + chargeFee(fee.BidJoinFee, caller) + bStt.CurrentStatus = NewBiddingSession + bStt.ActionCode = CommitPricePhase + return bStt + } + // if existed + // TODO: Check if commit more than 1 time -> done + bidRecList := data.([]bidRecord) + startTime := bidRecList[0].StartTime + if now.After(bidRecList[0].EndCommitTime) { + panic("can not commit hash anymore") + } + for _, bR := range bidRecList { + if bR.Bidder == caller { + panic("you already commited hash") + } + } + + oldEndCommitTime := bidRecList[0].EndCommitTime + oldEndPriceTime := bidRecList[0].EndPriceTime + bidRec := bidRecord{ + DomainName: domainName, + HashString: hashString, + Bidder: caller, + StartTime: startTime, + EndCommitTime: oldEndCommitTime, + EndPriceTime: oldEndPriceTime, + IsOpen: true, + } + bidRecList = append(bidRecList, bidRec) + // Save record + BidRec.Set(domainName, bidRecList) + // charge commit hash fee + chargeFee(fee.BidJoinFee, caller) + bStt.CurrentStatus = AppendBiddingSession + bStt.ActionCode = CommitPricePhase + return bStt +} + +// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me +// commit price and secret to reveal auction session +func CommitPrice(price int, secret string, domainName string) bidStatus { + var bStt bidStatus + // compute the hash string, compare to saved hash string in record + now := time.Now() + joinedString := secret + strconv.Itoa(price) + computedHashString := Get256String(joinedString) + ufmt.Println("computed hash: ", computedHashString) + caller := std.GetOrigCaller() + data, existed := BidRec.Get(domainName) + if !existed { + panic(" domain name is invalid") + } + bidRecList := data.([]bidRecord) + winnerRec.Set(domainName, bidRecList[0]) + + ufmt.Println("time now: ", now.Format(time.RFC3339)) + ufmt.Println("end commit phase time: ", bidRecList[0].EndCommitTime.Format(time.RFC3339)) + // case commit after end - consider panic or not + if now.After(bidRecList[0].EndPriceTime) { + ufmt.Println("commit price phase is ended") + bStt.ActionCode = 0 + bStt.CurrentStatus = 0 + return bStt + } + // case commit when price phase not started + if time.Now().Before(bidRecList[0].EndCommitTime) { + ufmt.Println("commit price phase is not started yet") + bStt.ActionCode = 0 + bStt.CurrentStatus = 0 + return bStt + } + + // search for the corresponding hash + for _, bidRec := range bidRecList { + // panic because wrong price or wrong secret string + if bidRec.Bidder == caller && bidRec.HashString != computedHashString { + panic("invalid hash string") + } + // found it, update the winner price + if bidRec.Bidder == caller && bidRec.HashString == computedHashString { + data, _ := winnerRec.Get(domainName) + currentWinnerRec := data.(bidRecord) + if price > currentWinnerRec.Price && now.Before(currentWinnerRec.EndPriceTime) { + ufmt.Println("found new winner, setting up") + currentWinnerRec.Price = price + currentWinnerRec.Bidder = bidRec.Bidder + currentWinnerRec.HashString = bidRec.HashString + winnerRec.Set(domainName, currentWinnerRec) + bStt.ActionCode = ClaimPhase + return bStt + } + } + } + // if not match above case, then panic + panic("commit failed") +} + +func GetCurrentWinner(domainName string) bidRecord { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("no winner yet") + } + return data.(bidRecord) +} + +// find the highest bid in session - incase everyone commited price +func findTheWinner(domainName string) bidRecord { + var winnerBid bidRecord + data, existed := BidRec.Get(domainName) + if !existed { + panic("invalid domain name") + } + bidRecList := data.([]bidRecord) + winnerBid = bidRecList[0] + for _, bidRec := range bidRecList { + if bidRec.Price > winnerBid.Price { + winnerBid = bidRec + } + } + return winnerBid +} + +// register the domain for winner +func registerForWinner(domainName string, winnerRec bidRecord) bool { + winnerAddr := winnerRec.Bidder + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: winnerAddr, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + feeProcess(requestInfo) + return false +} + +// everyone can call EndBid() +// this EndBid checks endTime -> end the auction +func EndBid(domainName string) error { + now := time.Now() + data, existed := BidRec.Get(domainName) + if !existed { + return ufmt.Errorf("endbid: invalid domain name") + } + bidRecList := data.([]bidRecord) + firstBidRec := bidRecList[0] + if now.Before(firstBidRec.EndPriceTime) { + return ufmt.Errorf("endbid: this session can not end before the end time") + } + // change all state + for _, bidRec := range bidRecList { + bidRec.IsOpen = false + } + ok := BidRec.Set(domainName, bidRecList) + if !ok { + return ufmt.Errorf("endbid: can not change bid record state") + } + // need more conditions for findTheWinner() + findTheWinner(domainName) + return nil +} + +// register new domain with bidding process inside - if message is commit hash -> dapp need to call commit hash +func RegisterDomain(domainName string) bidStatus { + reqInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: "native", + } + + // bidStatus + result := checkRegisterState(reqInfo) + recordJoinedBid(domainName) + // if checkState get the message of which phase this domain name belongs to, then return the status + return result +} + +func checkRegisterState(req RequestInfo) bidStatus { + var bStt bidStatus + // check if domain name is regex valid + if !isValidDomain(req.WantedDomain) { + panic("invalid domain name") + } + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + panic("domain name already registered") + } + // changelogs v2: we are using sealed bidding now + // check if a bidding session is openning -> append new commit hash into record list + // both existed or not we open new bidding session + // for now we return a signal for dapps / service to know what to do next + isExisted, isOpen := checkBiddingState(req.WantedDomain) + if isExisted && isOpen { + // return commit hash signal for dapps + bStt.CurrentStatus = AppendBiddingSession + bStt.ActionCode = CommitHashPhase + return bStt + } + + // create new session + if !isExisted { + bStt.CurrentStatus = NewBiddingSession + bStt.ActionCode = CommitHashPhase + return bStt + } + + // not found in register repository but also not found in Bidding Record -> panic("error somewhere :D") + panic(ufmt.Errorf("should not happend")) + return bStt +} + +// // open a bidding session +// func openBiddingSession(domainName string, dur time.Duration) string { +// now := time.Now() +// bidRec := bidRecord{ +// DomainName: domainName, +// StartTime: now, +// } +// bidRecList := []bidRecord{bidRec} +// ok := BidRec.Set(domainName, bidRecList) +// if !ok { +// panic("can not open bidding session") +// } +// return "created bidding session" +// } + +func checkBiddingState(dName string) (isExisted bool, isOpen bool) { + data, existed := BidRec.Get(dName) + if !existed { + isExisted = false + isOpen = false + return + } + isExisted = true + recList := data.([]bidRecord) + if recList[0].IsOpen { + isOpen = true + } else { + isOpen = false + } + return isExisted, isOpen +} + +// get all the price list that joined the bid for displaying in dapp +func GetRecords(dName string) []bidRecord { + data, existed := BidRec.Get(dName) + if !existed { + panic("should not") + } + return data.([]bidRecord) +} + +// chargeFee will charge amount - send from this contract to admin +func chargeFee(amount int64, from std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", amount) + coinsToTransfer := std.NewCoins(ugnotCoin) + coins := checkCoin(from) + ufmt.Println("check balances: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// pay fee and claim the domain name if you are winner +func Claim(domainName string) bool { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("claim: invalid domain name") + } + caller := std.GetOrigCaller() + rec := data.(bidRecord) + if caller != rec.Bidder { + panic("only winner can claim") + } + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: rec.Bidder, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + chargeFee(100, caller) + feeProcess(requestInfo) + return true +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/bidding_model.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/bidding_model.gno new file mode 100644 index 00000000..194ae036 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/bidding_model.gno @@ -0,0 +1,33 @@ +package registrar + +/* + 0: StatusFailed + CurrentStatus: 1: NewBiddingSession + 2: AppendBiddingSession + 3: BiddingSessionClosed + ActionCode: 1: CommitHashPhase + 2: CommitPricePhase + 3: ClaimPhase... +*/ + +type bidStatus struct { + CurrentStatus currStatus + ActionCode actionCode +} + +type ( + currStatus int + actionCode int +) + +var ( + StatusFailed currStatus = 0 + NewBiddingSession currStatus = 1 + AppendBiddingSession currStatus = 2 + BiddingSessionClosed currStatus = 3 + + ActionFailed actionCode = 0 + CommitHashPhase actionCode = 1 + CommitPricePhase actionCode = 2 + ClaimPhase actionCode = 3 +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/errors.gno new file mode 100644 index 00000000..a186a711 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/errors.gno @@ -0,0 +1,15 @@ +package registrar + +import ( + "errors" +) + +var ( + ErrUnknown = errors.New("unknow errors") + ErrOK = errors.New("ok") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("ErrInvalidDomainName") + ErrAlreadyRegistered = errors.New("this domain is registered") + ErrCrossRealms = errors.New("cross realms function error") + ErrNotFound = errors.New("domain not found") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee.gno new file mode 100644 index 00000000..9c0eef2f --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee.gno @@ -0,0 +1,36 @@ +package registrar + +import ( + "time" +) + +// only admin can set Fee, other just can read only +type feeInfo struct { + RegisterBaseFee int64 + RenewalFee int64 + RegisterAdditionFee int64 + BidJoinFee int64 +} + +func GetRegisterFee(dName string) int64 { + return fee.RegisterBaseFee +} + +func GetRenewalFee(dName string, amount time.Duration) int64 { + return fee.RenewalFee +} + +// Admin set register fee and renewal fee +func AdminSetFee(regFee int64, renewFee int64) { + // consider logic + assertIsAdmin() + fee.RegisterBaseFee = regFee + fee.RenewalFee = renewFee +} + +// simple err check +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_checks.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_checks.gno new file mode 100644 index 00000000..4b054378 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_checks.gno @@ -0,0 +1,9 @@ +package registrar + +// import ( +// "" +// // "std" +// // "time" +// ) + + diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_native.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_native.gno new file mode 100644 index 00000000..aac57354 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_native.gno @@ -0,0 +1,52 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// admin access only +func AdminWithdraw(amount int64) { + assertIsAdmin() + thisContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + superBanker.SendCoins(thisContract, admin, coinsToTransfer) +} + +func nativeProcess() { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + caller := std.GetOrigCaller() + coins := checkCoin(caller) + ufmt.Println("check: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// RevertTransfer will revert the transaction - send amount of coin to user +func revertTransfer(userAddr std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToReturn := std.NewCoins(ugnotCoin) + ufmt.Println("return coins from contract ", bankerContract.String(), " to ", userAddr.String()) + bankerUser.SendCoins(bankerContract, userAddr, coinsToReturn) +} + +// simple check for admin call +func assertIsAdmin() { + // check if GetCallerAt 2 or 3 when deployed + caller := std.GetCallerAt(3) + err := ufmt.Sprintf("unauthorize with caller: %s\n", caller) + if caller != admin && caller != adminVar { + panic(err) + } +} + +// checking for availble coins +func checkCoin(from std.Address) std.Coins { + // caller := std.GetOrigCaller() + return bankerUser.GetCoins(from) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_token.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_token.gno new file mode 100644 index 00000000..144aa1df --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/fee_token.gno @@ -0,0 +1,25 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + "gno.land/r/varmeta/demo1/domain/vmt" +) + +// expected approved already from client -> transfer from caller to admin +func tokenProcess(dName string, callerStd std.Address) { + caller := pusers.AddressOrName(callerStd.String()) + + now := std.CurrentRealm().Addr() + nowAddr := pusers.AddressOrName(now.String()) + ufmt.Println("current realm transfer: ", now.String()) + callerAllowance := vmt.Allowance(caller, nowAddr) + callerAllowanceString := ufmt.Sprintf("%d", callerAllowance) + ufmt.Println("caller allowance ", callerAllowanceString) + + adminAddr := pusers.AddressOrName(admin.String()) + ufmt.Println("admin: ", admin.String()) + vmt.TransferFrom(caller, adminAddr, 1) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/hashstring.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/hashstring.gno new file mode 100644 index 00000000..674432a1 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/hashstring.gno @@ -0,0 +1,13 @@ +package registrar + +import ( + "crypto/sha256" + "encoding/hex" +) + +func Get256String(input string) string { + data := []byte(input) + hashed := sha256.Sum256(data) + hashedBytes := hashed[:] + return hex.EncodeToString(hashedBytes) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/metadata_wrapper.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/metadata_wrapper.gno new file mode 100644 index 00000000..f53c3cb8 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/metadata_wrapper.gno @@ -0,0 +1,40 @@ +package registrar + +import ( + "bytes" + "std" + "time" + + "gno.land/p/demo/ufmt" + + "gno.land/p/varmeta/demo/v31/domain" +) + +// Metadata wrapper +// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait) +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + createdAt := time.Now() + expTime := createdAt.Add(ttl) + return domain.NewMetadata("", name, "", "", createdAt, expTime, []domain.Trait{}) +} + +type remapMetadata struct { + Avatar string // avatar - URL or identifier for an avatar image + RegistrationTime string // regtime - The time when the domain was registered + ExpirationTime string // exptime - The time when the domain will be expire + Attributes []domain.Trait // atts - Additional attributes of the domain + Description string // des - A description of the domain + ContactInfo string // contacts - Contact information for the domain owner + RenewalFee string // renewalfee - The fee required to renew the domain, represented as a string +} + +// currently not support for arrays +func (m remapMetadata) MarshalJSON() ([]byte, error) { + json := new(bytes.Buffer) + if m.Attributes == nil { + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, "empty", m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil + } + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/models.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/models.gno new file mode 100644 index 00000000..fe15a6ba --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/models.gno @@ -0,0 +1,22 @@ +package registrar + +import ( + "std" +) + +type RequestInfo struct { + Mode string + WantedDomain string + Caller std.Address + TransInfo TransferInfo + // xxx extendTime, renew... +} +type TransferInfo struct { + From std.Address + To std.Address +} +type ExecuteResult struct { + Success bool + ResultDetails error + Message string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/prestep.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/prestep.gno new file mode 100644 index 00000000..87624921 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/prestep.gno @@ -0,0 +1,41 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v31/domain" +) + +var ( + domainStorage *avl.Tree // domainName -> std.Address + rootRegistry domain.DomainRegistry + + // fee + superBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction + bankerUser std.Banker // full access to coins sent with the transaction that called the banker + + admin std.Address // admin + adminVar std.Address // admin in server + fee feeInfo +) + +func init() { + domainStorage = avl.NewTree() + BidRec = avl.NewTree() + winnerRec = avl.NewTree() + rootRegistry = domain.NewDomainRegistry("Varmeta", "vmt") + + // fee init + admin = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" //@thinhnx + adminVar = "g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx" //@varmeta-sponsorkey + // ugnot + fee = feeInfo{ + RegisterBaseFee: 100, + RenewalFee: 100, + RegisterAdditionFee: 0, + BidJoinFee: 100, + } + superBanker = std.GetBanker(std.BankerTypeRealmSend) + bankerUser = std.GetBanker(std.BankerTypeOrigSend) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/registrar.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/registrar.gno new file mode 100644 index 00000000..358f3681 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/registrar.gno @@ -0,0 +1,154 @@ +/* +This package contains functions that will actually execute the request from user +Features: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion... +*/ +// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm. +// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session + +// currently we dont using too much panic because we dont have defer functions to revert the state of storage +package registrar + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v31/domain" +) + +// XXX: consider using panic instead of return string or errors +func Register(domainName string, mode string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: mode, + } + + regResult := executeRegister(requestInfo) + + // calling panic to stop paying fee + if !regResult.Success { + panic(regResult.ResultDetails.Error()) + } + // pay fee with panic inside + feeProcess(requestInfo) + return "Register Done" +} + +func executeRegister(req RequestInfo) ExecuteResult { + // check if domain name is regex valid + var execRes ExecuteResult + if !isValidDomain(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrInvalidDomainName + return execRes + } + + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrAlreadyRegistered + return execRes + } + + // execute register domain - mint the nft + // changelogs v2: we are using sealed bidding now + + + + caller := req.Caller + ttl := defaultExpireTime + metadata := metadataWrapper(caller, req.WantedDomain, ttl) + // create a new registry instance to save metadata and mint the NFT + errRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl) + if errRegister != nil { + execRes.Success = false + execRes.ResultDetails = ErrCrossRealms + return execRes + } + // now save caller to corressponding tree to manage + domainStorage.Set(req.WantedDomain, caller) + + execRes.Success = true + return execRes +} + +func feeProcess(req RequestInfo) { + if req.Mode == "token" { + tokenProcess(req.WantedDomain, req.Caller) + } else { + nativeProcess() + } +} + +func AlreadyRegistered(domainName string) bool { + // if can get owner -> existed + addr, err := rootRegistry.OwnerOf(domainName) + if err == nil && addr != "" { + return true + } + return false +} + +func GetOwner(domainName string) std.Address { + vl, existed := domainStorage.Get(domainName) + if !existed { + return "" + } + return vl.(std.Address) +} + +func Search(domainName string) (remapMetadata, string) { + validMetadata := remapMetadata{} + md, err := getMetadata(domainName) + if err != nil { + // return validMetadata, err.Error() + panic(err) + } + validMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339) + validMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339) + // jsonData, _ := validMetadata.MarshalJSON() + return validMetadata, "Search Success" +} + +func getMetadata(wantedDomain string) (domain.Metadata, error) { + // confirm the method? -> get all the fields if the fields slice is empty + metadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{}) + if err != nil { + return metadata, err + } + return metadata, nil +} + +// Transfer +func TransferDomain(from, to, domainName string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + } + if err := excuteTransfer(requestInfo); err != "" { + panic(err) + } + return "Transfer Done" +} + +func excuteTransfer(req RequestInfo) string { + if !AlreadyRegistered(req.WantedDomain) { + return ErrAlreadyRegistered.Error() + } + rootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain) + return "" +} + +func GetDomainName(addr string) []string { + domainList := []string{} + // search from local storage + domainStorage.Iterate("", "", func(key string, value interface{}) bool { + caller := value.(std.Address) + // not checking isExpired + if caller.String() == addr { + domainList = append(domainList, key) + } + return false + }) + return domainList +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/registrar_test.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/registrar_test.gno new file mode 100644 index 00000000..df136440 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/registrar_test.gno @@ -0,0 +1,25 @@ +package registrar + +// import ( +// "fmt" +// "std" +// "testing" +// ) + +// func TestRegisterDomain(t *testing.T) { +// tcs := []struct { +// input string +// expected string +// }{ +// {"thinhnx", "Register done"}, +// } +// for tc := range tcs { +// name := tc.input +// t.Run(name, func(t *testing.T) { +// output := Register(tc.input) +// if output != tc.expected { +// t.Errorf("Expected '%q, but got %q", tc.expected, output) +// } +// }) +// } +// } diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/utils.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/utils.gno new file mode 100644 index 00000000..9dcf7ada --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/registrar/utils.gno @@ -0,0 +1,31 @@ +/* +This check module contains function to do the checking stuffs +*/ +package registrar + +import ( + "regexp" + "time" +) + +var ( + defaultCommitHashTime = time.Second * 60 + defaultCommitPriceTime = time.Second * 60 + defaultExpireTime = time.Hour // 30 days + reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) +) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func GetExpirationDate(dName string) time.Time { + return rootRegistry.GetExpirationDate(dName) +} + +// for now, this function only let admin set +func SetExpirationDate(dName string, expDate time.Time) bool { + assertIsAdmin() + return rootRegistry.SetExpirationDate(dName, expDate) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/checks_resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/checks_resolver.gno new file mode 100644 index 00000000..2c565de1 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/checks_resolver.gno @@ -0,0 +1,27 @@ +/* +This check module contains function to do the checking stuffs +*/ +package resolver + +import ( + "regexp" + "time" + + "gno.land/r/varmeta/demo/v3/domain/registrar" +) + +// const ( +// admin std.Address = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" // -> @thinhnx +// ) + +var reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func isExpired(dName string) bool { + expDate := registrar.GetExpirationDate(dName) + return expDate.Before(time.Now()) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/errors.gno new file mode 100644 index 00000000..19709079 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/errors.gno @@ -0,0 +1,11 @@ +package resolver + +import ( + "errors" +) + +var ( + ErrNotFound = errors.New("not found") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("invalid domain name to register") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/resolver.gno new file mode 100644 index 00000000..4ed56489 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/resolver.gno @@ -0,0 +1,63 @@ +/* +The goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner. +It serves the purpose of "resolving" the ICNS Name +to the correct address (e.g "alice.gno" -> g1xxx). +*/ +// changelogs: move Register feature into this resolver package +// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result + +package resolver + +import ( + "std" + + "gno.land/r/varmeta/demo/v3/domain/registrar" +) + +type Record struct { + Owner std.Address + IsValid bool + Memo string // no more need this + Priority int +} + +// retrieve the record list to get the onchain address +func Resolve(domainName string) *Record { + if !isValidDomain(domainName) { + panic("bad domain name") + } + record := &Record{} + + owner := getOwnerFromDomainStorage(domainName) + if owner == "" { + record.Memo = "not found" + record.IsValid = false + return record + } + + if !isExpired(domainName) { + record.IsValid = true + record.Owner = owner + } else { + record.IsValid = false + } + return record +} + +func GetDomainName(addr string) []string { + return registrar.GetDomainName(addr) +} + +/* +If query in local storage not found +Query to DomainStorage by domainName -> get the registry -> use that registry to get the Owner() +and check the validation time? +*/ + +func existedInDomainStorage(domainName string) bool { + return registrar.AlreadyRegistered(domainName) +} + +func getOwnerFromDomainStorage(domainName string) std.Address { + return registrar.GetOwner(domainName) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/resolver_metadata.gno b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/resolver_metadata.gno new file mode 100644 index 00000000..7d0c023f --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v31/domain/resolver/resolver_metadata.gno @@ -0,0 +1,15 @@ +package resolver + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v3/domain" +) + +// Metadata wrapper +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + crrTime := time.Now() + expTime := crrTime.Add(ttl) + return domain.NewMetadata("", name, "", "", crrTime, expTime, []domain.Trait{}) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/bidding.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/bidding.gno new file mode 100644 index 00000000..35adb54b --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/bidding.gno @@ -0,0 +1,435 @@ +package registrar + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/ufmt" + // "gno.land/p/demo/mux" + "gno.land/p/demo/avl" +) + +type bidRecord struct { + DomainName string + Bidder std.Address + HashString string + Price int + StartTime time.Time + EndCommitTime time.Time + EndPriceTime time.Time + CurrentPhase actionCode + IsOpen bool +} + +var ( + BidRec *avl.Tree // bidRecord <- []bidRec + winnerRec *avl.Tree + joinedBid avl.Tree +) + +// joinedBid: address <- []domainName +func recordJoinedBid(domainName string) { + caller := std.GetOrigCaller() + dList := []string{} + data, existed := joinedBid.Get(domainName) + if !existed { + dList = []string{domainName} + joinedBid.Set(caller.String(), dList) + return + } + dList = data.([]string) + dList = append(dList, domainName) + joinedBid.Set(caller.String(), dList) + return +} + +// get the joined bids of an account +func GetJoinedBid() []string { + caller := std.GetOrigCaller().String() + data, existed := joinedBid.Get(caller) + if !existed { + return []string{} + } + list := data.([]string) + return list +} + +// get the next state of bidding session +func GetCurrentStatus(domainName string) string { + // if there is record in joinedBid -> user joined + // check for tine.Now() and startTime + now := time.Now() + caller := std.GetOrigCaller() + // find the record + data, existed := BidRec.Get(domainName) + if !existed { + if _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister { + return "hash" + } + ufmt.Println("run here") + return "dName is invalid" + } + recList := data.([]bidRecord) + rec := recList[0] + + if now.Before(rec.EndCommitTime) { + return "waiting price" + } + if now.Before(rec.EndPriceTime) && now.After(rec.EndCommitTime) { + return "price" + } + if now.After(rec.EndPriceTime) { + return "closed" + } + return "undefined" +} + +// Render() renders welcome message :D +func Render(path string) string { + return "welcome to varmeta domain name service" +} + +/* + logic should be: + 1. User click register for a dns. + -> 1.1: dns does not exists -> open bidding session + -> 1.2: dns existed -> check if open bidding session is openning or not -> if open -> new bidRecord + 2. Bidding session: + -> 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -> creat bidRecord to add to bidRec + -> 2.2: CommitPrice phase: commit price + secret key: -> if matches -> write price into bidRec ( send the amunt of coin corresponding) + -> 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button + ---> admin end bidding auction + -> 2.3.1: cooldown phase -> waiting for winner -> 2nd winner + -> 2.4: re-transfer coin from admin to user who not win the bid + the flow is: user want to register new domain name -> start new bidding session -> got the bidding session id + if other want to register to the same domain name -> bidding into this session, if not return to new state + when the session ends, need to have 2nd phase called commit -> commit their price and secret to compute the hash + -> compare the hash as in record -> accept the hash -> accept the price + *** temp approach: everytime user commit, user open a bid session if not existed, if existed -> check if this session is ended. +*/ +// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate +// the time whenever there is action to commit. + +// commit the domain name and computed hash string +func CommitHash(domainName, hashString string) bidStatus { + bStt := bidStatus{} + caller := std.GetOrigCaller() + ufmt.Println("caller: ", caller.String()) + now := time.Now() + + // update the bid record + data, existed := BidRec.Get(domainName) + + // if not existed -> create new record + if !existed { + var bidRec bidRecord + endCommitTime := now.Add(defaultCommitHashTime) + endPriceTime := endCommitTime.Add(defaultCommitPriceTime) + ufmt.Println("endCommitTime: ", endCommitTime.Format(time.RFC3339)) + ufmt.Println("endPriceTime: ", endPriceTime.Format(time.RFC3339)) + bidRec = bidRecord{ + DomainName: domainName, + Bidder: caller, + HashString: hashString, + StartTime: now, + EndCommitTime: endCommitTime, + EndPriceTime: endPriceTime, + IsOpen: true, + } + bidRecList := []bidRecord{bidRec} + BidRec.Set(domainName, bidRecList) + + // charge fee + chargeFee(fee.BidJoinFee, caller) + bStt.CurrentStatus = NewBiddingSession + bStt.ActionCode = CommitPricePhase + return bStt + } + // if existed + // TODO: Check if commit more than 1 time -> done + bidRecList := data.([]bidRecord) + startTime := bidRecList[0].StartTime + if now.After(bidRecList[0].EndCommitTime) { + panic("can not commit hash anymore") + } + for _, bR := range bidRecList { + if bR.Bidder == caller { + panic("you already commited hash") + } + } + + oldEndCommitTime := bidRecList[0].EndCommitTime + oldEndPriceTime := bidRecList[0].EndPriceTime + bidRec := bidRecord{ + DomainName: domainName, + HashString: hashString, + Bidder: caller, + StartTime: startTime, + EndCommitTime: oldEndCommitTime, + EndPriceTime: oldEndPriceTime, + IsOpen: true, + } + bidRecList = append(bidRecList, bidRec) + // Save record + BidRec.Set(domainName, bidRecList) + // charge commit hash fee + chargeFee(fee.BidJoinFee, caller) + bStt.CurrentStatus = AppendBiddingSession + bStt.ActionCode = CommitPricePhase + return bStt +} + +// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me +// commit price and secret to reveal auction session +func CommitPrice(price int, secret string, domainName string) bidStatus { + var bStt bidStatus + // compute the hash string, compare to saved hash string in record + now := time.Now() + joinedString := secret + strconv.Itoa(price) + computedHashString := Get256String(joinedString) + ufmt.Println("computed hash: ", computedHashString) + caller := std.GetOrigCaller() + data, existed := BidRec.Get(domainName) + if !existed { + panic(" domain name is invalid") + } + bidRecList := data.([]bidRecord) + winnerRec.Set(domainName, bidRecList[0]) + + ufmt.Println("time now: ", now.Format(time.RFC3339)) + ufmt.Println("end commit phase time: ", bidRecList[0].EndCommitTime.Format(time.RFC3339)) + // case commit after end - consider panic or not + if now.After(bidRecList[0].EndPriceTime) { + ufmt.Println("commit price phase is ended") + bStt.ActionCode = 0 + bStt.CurrentStatus = 0 + return bStt + } + // case commit when price phase not started + if time.Now().Before(bidRecList[0].EndCommitTime) { + ufmt.Println("commit price phase is not started yet") + bStt.ActionCode = 0 + bStt.CurrentStatus = 0 + return bStt + } + + // search for the corresponding hash + for _, bidRec := range bidRecList { + // panic because wrong price or wrong secret string + if bidRec.Bidder == caller && bidRec.HashString != computedHashString { + panic("invalid hash string") + } + // found it, update the winner price + if bidRec.Bidder == caller && bidRec.HashString == computedHashString { + data, _ := winnerRec.Get(domainName) + currentWinnerRec := data.(bidRecord) + if price > currentWinnerRec.Price && now.Before(currentWinnerRec.EndPriceTime) { + ufmt.Println("found new winner, setting up") + currentWinnerRec.Price = price + currentWinnerRec.Bidder = bidRec.Bidder + currentWinnerRec.HashString = bidRec.HashString + winnerRec.Set(domainName, currentWinnerRec) + bStt.ActionCode = ClaimPhase + return bStt + } + } + } + // if not match above case, then panic + panic("commit failed") +} + +func GetCurrentWinner(domainName string) bidRecord { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("no winner yet") + } + return data.(bidRecord) +} + +// find the highest bid in session - incase everyone commited price +func findTheWinner(domainName string) bidRecord { + var winnerBid bidRecord + data, existed := BidRec.Get(domainName) + if !existed { + panic("invalid domain name") + } + bidRecList := data.([]bidRecord) + winnerBid = bidRecList[0] + for _, bidRec := range bidRecList { + if bidRec.Price > winnerBid.Price { + winnerBid = bidRec + } + } + return winnerBid +} + +// register the domain for winner +func registerForWinner(domainName string, winnerRec bidRecord) bool { + winnerAddr := winnerRec.Bidder + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: winnerAddr, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + feeProcess(requestInfo) + return false +} + +// everyone can call EndBid() +// this EndBid checks endTime -> end the auction +func EndBid(domainName string) error { + now := time.Now() + data, existed := BidRec.Get(domainName) + if !existed { + return ufmt.Errorf("endbid: invalid domain name") + } + bidRecList := data.([]bidRecord) + firstBidRec := bidRecList[0] + if now.Before(firstBidRec.EndPriceTime) { + return ufmt.Errorf("endbid: this session can not end before the end time") + } + // change all state + for _, bidRec := range bidRecList { + bidRec.IsOpen = false + } + ok := BidRec.Set(domainName, bidRecList) + if !ok { + return ufmt.Errorf("endbid: can not change bid record state") + } + // need more conditions for findTheWinner() + findTheWinner(domainName) + return nil +} + +// register new domain with bidding process inside - if message is commit hash -> dapp need to call commit hash +func RegisterDomain(domainName string) bidStatus { + reqInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: "native", + } + + // bidStatus + result := checkRegisterState(reqInfo) + recordJoinedBid(domainName) + // if checkState get the message of which phase this domain name belongs to, then return the status + return result +} + +func checkRegisterState(req RequestInfo) bidStatus { + var bStt bidStatus + // check if domain name is regex valid + if !isValidDomain(req.WantedDomain) { + panic("invalid domain name") + } + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + panic("domain name already registered") + } + // changelogs v2: we are using sealed bidding now + // check if a bidding session is openning -> append new commit hash into record list + // both existed or not we open new bidding session + // for now we return a signal for dapps / service to know what to do next + isExisted, isOpen := checkBiddingState(req.WantedDomain) + if isExisted && isOpen { + // return commit hash signal for dapps + bStt.CurrentStatus = AppendBiddingSession + bStt.ActionCode = CommitHashPhase + return bStt + } + + // create new session + if !isExisted { + bStt.CurrentStatus = NewBiddingSession + bStt.ActionCode = CommitHashPhase + return bStt + } + + // not found in register repository but also not found in Bidding Record -> panic("error somewhere :D") + panic(ufmt.Errorf("should not happend")) + return bStt +} + +// // open a bidding session +// func openBiddingSession(domainName string, dur time.Duration) string { +// now := time.Now() +// bidRec := bidRecord{ +// DomainName: domainName, +// StartTime: now, +// } +// bidRecList := []bidRecord{bidRec} +// ok := BidRec.Set(domainName, bidRecList) +// if !ok { +// panic("can not open bidding session") +// } +// return "created bidding session" +// } + +func checkBiddingState(dName string) (isExisted bool, isOpen bool) { + data, existed := BidRec.Get(dName) + if !existed { + isExisted = false + isOpen = false + return + } + isExisted = true + recList := data.([]bidRecord) + if recList[0].IsOpen { + isOpen = true + } else { + isOpen = false + } + return isExisted, isOpen +} + +// get all the price list that joined the bid for displaying in dapp +func GetRecords(dName string) []bidRecord { + data, existed := BidRec.Get(dName) + if !existed { + panic("should not") + } + return data.([]bidRecord) +} + +// chargeFee will charge amount - send from this contract to admin +func chargeFee(amount int64, from std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", amount) + coinsToTransfer := std.NewCoins(ugnotCoin) + coins := checkCoin(from) + ufmt.Println("check balances: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// pay fee and claim the domain name if you are winner +func Claim(domainName string) bool { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("claim: invalid domain name") + } + caller := std.GetOrigCaller() + rec := data.(bidRecord) + if caller != rec.Bidder { + panic("only winner can claim") + } + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: rec.Bidder, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + chargeFee(100, caller) + feeProcess(requestInfo) + return true +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/bidding_model.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/bidding_model.gno new file mode 100644 index 00000000..194ae036 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/bidding_model.gno @@ -0,0 +1,33 @@ +package registrar + +/* + 0: StatusFailed + CurrentStatus: 1: NewBiddingSession + 2: AppendBiddingSession + 3: BiddingSessionClosed + ActionCode: 1: CommitHashPhase + 2: CommitPricePhase + 3: ClaimPhase... +*/ + +type bidStatus struct { + CurrentStatus currStatus + ActionCode actionCode +} + +type ( + currStatus int + actionCode int +) + +var ( + StatusFailed currStatus = 0 + NewBiddingSession currStatus = 1 + AppendBiddingSession currStatus = 2 + BiddingSessionClosed currStatus = 3 + + ActionFailed actionCode = 0 + CommitHashPhase actionCode = 1 + CommitPricePhase actionCode = 2 + ClaimPhase actionCode = 3 +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/errors.gno new file mode 100644 index 00000000..a186a711 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/errors.gno @@ -0,0 +1,15 @@ +package registrar + +import ( + "errors" +) + +var ( + ErrUnknown = errors.New("unknow errors") + ErrOK = errors.New("ok") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("ErrInvalidDomainName") + ErrAlreadyRegistered = errors.New("this domain is registered") + ErrCrossRealms = errors.New("cross realms function error") + ErrNotFound = errors.New("domain not found") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee.gno new file mode 100644 index 00000000..9c0eef2f --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee.gno @@ -0,0 +1,36 @@ +package registrar + +import ( + "time" +) + +// only admin can set Fee, other just can read only +type feeInfo struct { + RegisterBaseFee int64 + RenewalFee int64 + RegisterAdditionFee int64 + BidJoinFee int64 +} + +func GetRegisterFee(dName string) int64 { + return fee.RegisterBaseFee +} + +func GetRenewalFee(dName string, amount time.Duration) int64 { + return fee.RenewalFee +} + +// Admin set register fee and renewal fee +func AdminSetFee(regFee int64, renewFee int64) { + // consider logic + assertIsAdmin() + fee.RegisterBaseFee = regFee + fee.RenewalFee = renewFee +} + +// simple err check +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_checks.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_checks.gno new file mode 100644 index 00000000..4b054378 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_checks.gno @@ -0,0 +1,9 @@ +package registrar + +// import ( +// "" +// // "std" +// // "time" +// ) + + diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_native.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_native.gno new file mode 100644 index 00000000..aac57354 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_native.gno @@ -0,0 +1,52 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// admin access only +func AdminWithdraw(amount int64) { + assertIsAdmin() + thisContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + superBanker.SendCoins(thisContract, admin, coinsToTransfer) +} + +func nativeProcess() { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + caller := std.GetOrigCaller() + coins := checkCoin(caller) + ufmt.Println("check: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// RevertTransfer will revert the transaction - send amount of coin to user +func revertTransfer(userAddr std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToReturn := std.NewCoins(ugnotCoin) + ufmt.Println("return coins from contract ", bankerContract.String(), " to ", userAddr.String()) + bankerUser.SendCoins(bankerContract, userAddr, coinsToReturn) +} + +// simple check for admin call +func assertIsAdmin() { + // check if GetCallerAt 2 or 3 when deployed + caller := std.GetCallerAt(3) + err := ufmt.Sprintf("unauthorize with caller: %s\n", caller) + if caller != admin && caller != adminVar { + panic(err) + } +} + +// checking for availble coins +func checkCoin(from std.Address) std.Coins { + // caller := std.GetOrigCaller() + return bankerUser.GetCoins(from) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_token.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_token.gno new file mode 100644 index 00000000..144aa1df --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/fee_token.gno @@ -0,0 +1,25 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + "gno.land/r/varmeta/demo1/domain/vmt" +) + +// expected approved already from client -> transfer from caller to admin +func tokenProcess(dName string, callerStd std.Address) { + caller := pusers.AddressOrName(callerStd.String()) + + now := std.CurrentRealm().Addr() + nowAddr := pusers.AddressOrName(now.String()) + ufmt.Println("current realm transfer: ", now.String()) + callerAllowance := vmt.Allowance(caller, nowAddr) + callerAllowanceString := ufmt.Sprintf("%d", callerAllowance) + ufmt.Println("caller allowance ", callerAllowanceString) + + adminAddr := pusers.AddressOrName(admin.String()) + ufmt.Println("admin: ", admin.String()) + vmt.TransferFrom(caller, adminAddr, 1) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/hashstring.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/hashstring.gno new file mode 100644 index 00000000..674432a1 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/hashstring.gno @@ -0,0 +1,13 @@ +package registrar + +import ( + "crypto/sha256" + "encoding/hex" +) + +func Get256String(input string) string { + data := []byte(input) + hashed := sha256.Sum256(data) + hashedBytes := hashed[:] + return hex.EncodeToString(hashedBytes) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/metadata_wrapper.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/metadata_wrapper.gno new file mode 100644 index 00000000..12e5e409 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/metadata_wrapper.gno @@ -0,0 +1,40 @@ +package registrar + +import ( + "bytes" + "std" + "time" + + "gno.land/p/demo/ufmt" + + "gno.land/p/varmeta/demo/v32/domain" +) + +// Metadata wrapper +// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait) +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + createdAt := time.Now() + expTime := createdAt.Add(ttl) + return domain.NewMetadata("", name, "", "", createdAt, expTime, []domain.Trait{}) +} + +type remapMetadata struct { + Avatar string // avatar - URL or identifier for an avatar image + RegistrationTime string // regtime - The time when the domain was registered + ExpirationTime string // exptime - The time when the domain will be expire + Attributes []domain.Trait // atts - Additional attributes of the domain + Description string // des - A description of the domain + ContactInfo string // contacts - Contact information for the domain owner + RenewalFee string // renewalfee - The fee required to renew the domain, represented as a string +} + +// currently not support for arrays +func (m remapMetadata) MarshalJSON() ([]byte, error) { + json := new(bytes.Buffer) + if m.Attributes == nil { + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, "empty", m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil + } + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/models.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/models.gno new file mode 100644 index 00000000..fe15a6ba --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/models.gno @@ -0,0 +1,22 @@ +package registrar + +import ( + "std" +) + +type RequestInfo struct { + Mode string + WantedDomain string + Caller std.Address + TransInfo TransferInfo + // xxx extendTime, renew... +} +type TransferInfo struct { + From std.Address + To std.Address +} +type ExecuteResult struct { + Success bool + ResultDetails error + Message string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/prestep.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/prestep.gno new file mode 100644 index 00000000..66be9e9c --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/prestep.gno @@ -0,0 +1,41 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v32/domain" +) + +var ( + domainStorage *avl.Tree // domainName -> std.Address + rootRegistry domain.DomainRegistry + + // fee + superBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction + bankerUser std.Banker // full access to coins sent with the transaction that called the banker + + admin std.Address // admin + adminVar std.Address // admin in server + fee feeInfo +) + +func init() { + domainStorage = avl.NewTree() + BidRec = avl.NewTree() + winnerRec = avl.NewTree() + rootRegistry = domain.NewDomainRegistry("Varmeta", "vmt") + + // fee init + admin = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" //@thinhnx + adminVar = "g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx" //@varmeta-sponsorkey + // ugnot + fee = feeInfo{ + RegisterBaseFee: 100, + RenewalFee: 100, + RegisterAdditionFee: 0, + BidJoinFee: 100, + } + superBanker = std.GetBanker(std.BankerTypeRealmSend) + bankerUser = std.GetBanker(std.BankerTypeOrigSend) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/registrar.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/registrar.gno new file mode 100644 index 00000000..45222544 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/registrar.gno @@ -0,0 +1,154 @@ +/* +This package contains functions that will actually execute the request from user +Features: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion... +*/ +// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm. +// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session + +// currently we dont using too much panic because we dont have defer functions to revert the state of storage +package registrar + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v32/domain" +) + +// XXX: consider using panic instead of return string or errors +func Register(domainName string, mode string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: mode, + } + + regResult := executeRegister(requestInfo) + + // calling panic to stop paying fee + if !regResult.Success { + panic(regResult.ResultDetails.Error()) + } + // pay fee with panic inside + feeProcess(requestInfo) + return "Register Done" +} + +func executeRegister(req RequestInfo) ExecuteResult { + // check if domain name is regex valid + var execRes ExecuteResult + if !isValidDomain(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrInvalidDomainName + return execRes + } + + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrAlreadyRegistered + return execRes + } + + // execute register domain - mint the nft + // changelogs v2: we are using sealed bidding now + + + + caller := req.Caller + ttl := defaultExpireTime + metadata := metadataWrapper(caller, req.WantedDomain, ttl) + // create a new registry instance to save metadata and mint the NFT + errRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl) + if errRegister != nil { + execRes.Success = false + execRes.ResultDetails = ErrCrossRealms + return execRes + } + // now save caller to corressponding tree to manage + domainStorage.Set(req.WantedDomain, caller) + + execRes.Success = true + return execRes +} + +func feeProcess(req RequestInfo) { + if req.Mode == "token" { + tokenProcess(req.WantedDomain, req.Caller) + } else { + nativeProcess() + } +} + +func AlreadyRegistered(domainName string) bool { + // if can get owner -> existed + addr, err := rootRegistry.OwnerOf(domainName) + if err == nil && addr != "" { + return true + } + return false +} + +func GetOwner(domainName string) std.Address { + vl, existed := domainStorage.Get(domainName) + if !existed { + return "" + } + return vl.(std.Address) +} + +func Search(domainName string) (remapMetadata, string) { + validMetadata := remapMetadata{} + md, err := getMetadata(domainName) + if err != nil { + // return validMetadata, err.Error() + panic(err) + } + validMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339) + validMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339) + // jsonData, _ := validMetadata.MarshalJSON() + return validMetadata, "Search Success" +} + +func getMetadata(wantedDomain string) (domain.Metadata, error) { + // confirm the method? -> get all the fields if the fields slice is empty + metadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{}) + if err != nil { + return metadata, err + } + return metadata, nil +} + +// Transfer +func TransferDomain(from, to, domainName string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + } + if err := excuteTransfer(requestInfo); err != "" { + panic(err) + } + return "Transfer Done" +} + +func excuteTransfer(req RequestInfo) string { + if !AlreadyRegistered(req.WantedDomain) { + return ErrAlreadyRegistered.Error() + } + rootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain) + return "" +} + +func GetDomainName(addr string) []string { + domainList := []string{} + // search from local storage + domainStorage.Iterate("", "", func(key string, value interface{}) bool { + caller := value.(std.Address) + // not checking isExpired + if caller.String() == addr { + domainList = append(domainList, key) + } + return false + }) + return domainList +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/registrar_test.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/registrar_test.gno new file mode 100644 index 00000000..df136440 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/registrar_test.gno @@ -0,0 +1,25 @@ +package registrar + +// import ( +// "fmt" +// "std" +// "testing" +// ) + +// func TestRegisterDomain(t *testing.T) { +// tcs := []struct { +// input string +// expected string +// }{ +// {"thinhnx", "Register done"}, +// } +// for tc := range tcs { +// name := tc.input +// t.Run(name, func(t *testing.T) { +// output := Register(tc.input) +// if output != tc.expected { +// t.Errorf("Expected '%q, but got %q", tc.expected, output) +// } +// }) +// } +// } diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/utils.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/utils.gno new file mode 100644 index 00000000..9dcf7ada --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/registrar/utils.gno @@ -0,0 +1,31 @@ +/* +This check module contains function to do the checking stuffs +*/ +package registrar + +import ( + "regexp" + "time" +) + +var ( + defaultCommitHashTime = time.Second * 60 + defaultCommitPriceTime = time.Second * 60 + defaultExpireTime = time.Hour // 30 days + reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) +) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func GetExpirationDate(dName string) time.Time { + return rootRegistry.GetExpirationDate(dName) +} + +// for now, this function only let admin set +func SetExpirationDate(dName string, expDate time.Time) bool { + assertIsAdmin() + return rootRegistry.SetExpirationDate(dName, expDate) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/checks_resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/checks_resolver.gno new file mode 100644 index 00000000..b997582e --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/checks_resolver.gno @@ -0,0 +1,27 @@ +/* +This check module contains function to do the checking stuffs +*/ +package resolver + +import ( + "regexp" + "time" + + "gno.land/r/varmeta/demo/v32/domain/registrar" +) + +// const ( +// admin std.Address = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" // -> @thinhnx +// ) + +var reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func isExpired(dName string) bool { + expDate := registrar.GetExpirationDate(dName) + return expDate.Before(time.Now()) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/errors.gno new file mode 100644 index 00000000..19709079 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/errors.gno @@ -0,0 +1,11 @@ +package resolver + +import ( + "errors" +) + +var ( + ErrNotFound = errors.New("not found") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("invalid domain name to register") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/resolver.gno new file mode 100644 index 00000000..c3f896ed --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/resolver.gno @@ -0,0 +1,63 @@ +/* +The goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner. +It serves the purpose of "resolving" the ICNS Name +to the correct address (e.g "alice.gno" -> g1xxx). +*/ +// changelogs: move Register feature into this resolver package +// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result + +package resolver + +import ( + "std" + + "gno.land/r/varmeta/demo/v32/domain/registrar" +) + +type Record struct { + Owner std.Address + IsValid bool + Memo string // no more need this + Priority int +} + +// retrieve the record list to get the onchain address +func Resolve(domainName string) *Record { + if !isValidDomain(domainName) { + panic("bad domain name") + } + record := &Record{} + + owner := getOwnerFromDomainStorage(domainName) + if owner == "" { + record.Memo = "not found" + record.IsValid = false + return record + } + + if !isExpired(domainName) { + record.IsValid = true + record.Owner = owner + } else { + record.IsValid = false + } + return record +} + +func GetDomainName(addr string) []string { + return registrar.GetDomainName(addr) +} + +/* +If query in local storage not found +Query to DomainStorage by domainName -> get the registry -> use that registry to get the Owner() +and check the validation time? +*/ + +func existedInDomainStorage(domainName string) bool { + return registrar.AlreadyRegistered(domainName) +} + +func getOwnerFromDomainStorage(domainName string) std.Address { + return registrar.GetOwner(domainName) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/resolver_metadata.gno b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/resolver_metadata.gno new file mode 100644 index 00000000..29cd367f --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v32/domain/resolver/resolver_metadata.gno @@ -0,0 +1,15 @@ +package resolver + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v32/domain" +) + +// Metadata wrapper +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + crrTime := time.Now() + expTime := crrTime.Add(ttl) + return domain.NewMetadata("", name, "", "", crrTime, expTime, []domain.Trait{}) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/bidding.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/bidding.gno new file mode 100644 index 00000000..35adb54b --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/bidding.gno @@ -0,0 +1,435 @@ +package registrar + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/ufmt" + // "gno.land/p/demo/mux" + "gno.land/p/demo/avl" +) + +type bidRecord struct { + DomainName string + Bidder std.Address + HashString string + Price int + StartTime time.Time + EndCommitTime time.Time + EndPriceTime time.Time + CurrentPhase actionCode + IsOpen bool +} + +var ( + BidRec *avl.Tree // bidRecord <- []bidRec + winnerRec *avl.Tree + joinedBid avl.Tree +) + +// joinedBid: address <- []domainName +func recordJoinedBid(domainName string) { + caller := std.GetOrigCaller() + dList := []string{} + data, existed := joinedBid.Get(domainName) + if !existed { + dList = []string{domainName} + joinedBid.Set(caller.String(), dList) + return + } + dList = data.([]string) + dList = append(dList, domainName) + joinedBid.Set(caller.String(), dList) + return +} + +// get the joined bids of an account +func GetJoinedBid() []string { + caller := std.GetOrigCaller().String() + data, existed := joinedBid.Get(caller) + if !existed { + return []string{} + } + list := data.([]string) + return list +} + +// get the next state of bidding session +func GetCurrentStatus(domainName string) string { + // if there is record in joinedBid -> user joined + // check for tine.Now() and startTime + now := time.Now() + caller := std.GetOrigCaller() + // find the record + data, existed := BidRec.Get(domainName) + if !existed { + if _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister { + return "hash" + } + ufmt.Println("run here") + return "dName is invalid" + } + recList := data.([]bidRecord) + rec := recList[0] + + if now.Before(rec.EndCommitTime) { + return "waiting price" + } + if now.Before(rec.EndPriceTime) && now.After(rec.EndCommitTime) { + return "price" + } + if now.After(rec.EndPriceTime) { + return "closed" + } + return "undefined" +} + +// Render() renders welcome message :D +func Render(path string) string { + return "welcome to varmeta domain name service" +} + +/* + logic should be: + 1. User click register for a dns. + -> 1.1: dns does not exists -> open bidding session + -> 1.2: dns existed -> check if open bidding session is openning or not -> if open -> new bidRecord + 2. Bidding session: + -> 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -> creat bidRecord to add to bidRec + -> 2.2: CommitPrice phase: commit price + secret key: -> if matches -> write price into bidRec ( send the amunt of coin corresponding) + -> 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button + ---> admin end bidding auction + -> 2.3.1: cooldown phase -> waiting for winner -> 2nd winner + -> 2.4: re-transfer coin from admin to user who not win the bid + the flow is: user want to register new domain name -> start new bidding session -> got the bidding session id + if other want to register to the same domain name -> bidding into this session, if not return to new state + when the session ends, need to have 2nd phase called commit -> commit their price and secret to compute the hash + -> compare the hash as in record -> accept the hash -> accept the price + *** temp approach: everytime user commit, user open a bid session if not existed, if existed -> check if this session is ended. +*/ +// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate +// the time whenever there is action to commit. + +// commit the domain name and computed hash string +func CommitHash(domainName, hashString string) bidStatus { + bStt := bidStatus{} + caller := std.GetOrigCaller() + ufmt.Println("caller: ", caller.String()) + now := time.Now() + + // update the bid record + data, existed := BidRec.Get(domainName) + + // if not existed -> create new record + if !existed { + var bidRec bidRecord + endCommitTime := now.Add(defaultCommitHashTime) + endPriceTime := endCommitTime.Add(defaultCommitPriceTime) + ufmt.Println("endCommitTime: ", endCommitTime.Format(time.RFC3339)) + ufmt.Println("endPriceTime: ", endPriceTime.Format(time.RFC3339)) + bidRec = bidRecord{ + DomainName: domainName, + Bidder: caller, + HashString: hashString, + StartTime: now, + EndCommitTime: endCommitTime, + EndPriceTime: endPriceTime, + IsOpen: true, + } + bidRecList := []bidRecord{bidRec} + BidRec.Set(domainName, bidRecList) + + // charge fee + chargeFee(fee.BidJoinFee, caller) + bStt.CurrentStatus = NewBiddingSession + bStt.ActionCode = CommitPricePhase + return bStt + } + // if existed + // TODO: Check if commit more than 1 time -> done + bidRecList := data.([]bidRecord) + startTime := bidRecList[0].StartTime + if now.After(bidRecList[0].EndCommitTime) { + panic("can not commit hash anymore") + } + for _, bR := range bidRecList { + if bR.Bidder == caller { + panic("you already commited hash") + } + } + + oldEndCommitTime := bidRecList[0].EndCommitTime + oldEndPriceTime := bidRecList[0].EndPriceTime + bidRec := bidRecord{ + DomainName: domainName, + HashString: hashString, + Bidder: caller, + StartTime: startTime, + EndCommitTime: oldEndCommitTime, + EndPriceTime: oldEndPriceTime, + IsOpen: true, + } + bidRecList = append(bidRecList, bidRec) + // Save record + BidRec.Set(domainName, bidRecList) + // charge commit hash fee + chargeFee(fee.BidJoinFee, caller) + bStt.CurrentStatus = AppendBiddingSession + bStt.ActionCode = CommitPricePhase + return bStt +} + +// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me +// commit price and secret to reveal auction session +func CommitPrice(price int, secret string, domainName string) bidStatus { + var bStt bidStatus + // compute the hash string, compare to saved hash string in record + now := time.Now() + joinedString := secret + strconv.Itoa(price) + computedHashString := Get256String(joinedString) + ufmt.Println("computed hash: ", computedHashString) + caller := std.GetOrigCaller() + data, existed := BidRec.Get(domainName) + if !existed { + panic(" domain name is invalid") + } + bidRecList := data.([]bidRecord) + winnerRec.Set(domainName, bidRecList[0]) + + ufmt.Println("time now: ", now.Format(time.RFC3339)) + ufmt.Println("end commit phase time: ", bidRecList[0].EndCommitTime.Format(time.RFC3339)) + // case commit after end - consider panic or not + if now.After(bidRecList[0].EndPriceTime) { + ufmt.Println("commit price phase is ended") + bStt.ActionCode = 0 + bStt.CurrentStatus = 0 + return bStt + } + // case commit when price phase not started + if time.Now().Before(bidRecList[0].EndCommitTime) { + ufmt.Println("commit price phase is not started yet") + bStt.ActionCode = 0 + bStt.CurrentStatus = 0 + return bStt + } + + // search for the corresponding hash + for _, bidRec := range bidRecList { + // panic because wrong price or wrong secret string + if bidRec.Bidder == caller && bidRec.HashString != computedHashString { + panic("invalid hash string") + } + // found it, update the winner price + if bidRec.Bidder == caller && bidRec.HashString == computedHashString { + data, _ := winnerRec.Get(domainName) + currentWinnerRec := data.(bidRecord) + if price > currentWinnerRec.Price && now.Before(currentWinnerRec.EndPriceTime) { + ufmt.Println("found new winner, setting up") + currentWinnerRec.Price = price + currentWinnerRec.Bidder = bidRec.Bidder + currentWinnerRec.HashString = bidRec.HashString + winnerRec.Set(domainName, currentWinnerRec) + bStt.ActionCode = ClaimPhase + return bStt + } + } + } + // if not match above case, then panic + panic("commit failed") +} + +func GetCurrentWinner(domainName string) bidRecord { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("no winner yet") + } + return data.(bidRecord) +} + +// find the highest bid in session - incase everyone commited price +func findTheWinner(domainName string) bidRecord { + var winnerBid bidRecord + data, existed := BidRec.Get(domainName) + if !existed { + panic("invalid domain name") + } + bidRecList := data.([]bidRecord) + winnerBid = bidRecList[0] + for _, bidRec := range bidRecList { + if bidRec.Price > winnerBid.Price { + winnerBid = bidRec + } + } + return winnerBid +} + +// register the domain for winner +func registerForWinner(domainName string, winnerRec bidRecord) bool { + winnerAddr := winnerRec.Bidder + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: winnerAddr, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + feeProcess(requestInfo) + return false +} + +// everyone can call EndBid() +// this EndBid checks endTime -> end the auction +func EndBid(domainName string) error { + now := time.Now() + data, existed := BidRec.Get(domainName) + if !existed { + return ufmt.Errorf("endbid: invalid domain name") + } + bidRecList := data.([]bidRecord) + firstBidRec := bidRecList[0] + if now.Before(firstBidRec.EndPriceTime) { + return ufmt.Errorf("endbid: this session can not end before the end time") + } + // change all state + for _, bidRec := range bidRecList { + bidRec.IsOpen = false + } + ok := BidRec.Set(domainName, bidRecList) + if !ok { + return ufmt.Errorf("endbid: can not change bid record state") + } + // need more conditions for findTheWinner() + findTheWinner(domainName) + return nil +} + +// register new domain with bidding process inside - if message is commit hash -> dapp need to call commit hash +func RegisterDomain(domainName string) bidStatus { + reqInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: "native", + } + + // bidStatus + result := checkRegisterState(reqInfo) + recordJoinedBid(domainName) + // if checkState get the message of which phase this domain name belongs to, then return the status + return result +} + +func checkRegisterState(req RequestInfo) bidStatus { + var bStt bidStatus + // check if domain name is regex valid + if !isValidDomain(req.WantedDomain) { + panic("invalid domain name") + } + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + panic("domain name already registered") + } + // changelogs v2: we are using sealed bidding now + // check if a bidding session is openning -> append new commit hash into record list + // both existed or not we open new bidding session + // for now we return a signal for dapps / service to know what to do next + isExisted, isOpen := checkBiddingState(req.WantedDomain) + if isExisted && isOpen { + // return commit hash signal for dapps + bStt.CurrentStatus = AppendBiddingSession + bStt.ActionCode = CommitHashPhase + return bStt + } + + // create new session + if !isExisted { + bStt.CurrentStatus = NewBiddingSession + bStt.ActionCode = CommitHashPhase + return bStt + } + + // not found in register repository but also not found in Bidding Record -> panic("error somewhere :D") + panic(ufmt.Errorf("should not happend")) + return bStt +} + +// // open a bidding session +// func openBiddingSession(domainName string, dur time.Duration) string { +// now := time.Now() +// bidRec := bidRecord{ +// DomainName: domainName, +// StartTime: now, +// } +// bidRecList := []bidRecord{bidRec} +// ok := BidRec.Set(domainName, bidRecList) +// if !ok { +// panic("can not open bidding session") +// } +// return "created bidding session" +// } + +func checkBiddingState(dName string) (isExisted bool, isOpen bool) { + data, existed := BidRec.Get(dName) + if !existed { + isExisted = false + isOpen = false + return + } + isExisted = true + recList := data.([]bidRecord) + if recList[0].IsOpen { + isOpen = true + } else { + isOpen = false + } + return isExisted, isOpen +} + +// get all the price list that joined the bid for displaying in dapp +func GetRecords(dName string) []bidRecord { + data, existed := BidRec.Get(dName) + if !existed { + panic("should not") + } + return data.([]bidRecord) +} + +// chargeFee will charge amount - send from this contract to admin +func chargeFee(amount int64, from std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", amount) + coinsToTransfer := std.NewCoins(ugnotCoin) + coins := checkCoin(from) + ufmt.Println("check balances: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// pay fee and claim the domain name if you are winner +func Claim(domainName string) bool { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("claim: invalid domain name") + } + caller := std.GetOrigCaller() + rec := data.(bidRecord) + if caller != rec.Bidder { + panic("only winner can claim") + } + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: rec.Bidder, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + chargeFee(100, caller) + feeProcess(requestInfo) + return true +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/bidding_model.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/bidding_model.gno new file mode 100644 index 00000000..194ae036 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/bidding_model.gno @@ -0,0 +1,33 @@ +package registrar + +/* + 0: StatusFailed + CurrentStatus: 1: NewBiddingSession + 2: AppendBiddingSession + 3: BiddingSessionClosed + ActionCode: 1: CommitHashPhase + 2: CommitPricePhase + 3: ClaimPhase... +*/ + +type bidStatus struct { + CurrentStatus currStatus + ActionCode actionCode +} + +type ( + currStatus int + actionCode int +) + +var ( + StatusFailed currStatus = 0 + NewBiddingSession currStatus = 1 + AppendBiddingSession currStatus = 2 + BiddingSessionClosed currStatus = 3 + + ActionFailed actionCode = 0 + CommitHashPhase actionCode = 1 + CommitPricePhase actionCode = 2 + ClaimPhase actionCode = 3 +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/errors.gno new file mode 100644 index 00000000..a186a711 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/errors.gno @@ -0,0 +1,15 @@ +package registrar + +import ( + "errors" +) + +var ( + ErrUnknown = errors.New("unknow errors") + ErrOK = errors.New("ok") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("ErrInvalidDomainName") + ErrAlreadyRegistered = errors.New("this domain is registered") + ErrCrossRealms = errors.New("cross realms function error") + ErrNotFound = errors.New("domain not found") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee.gno new file mode 100644 index 00000000..9c0eef2f --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee.gno @@ -0,0 +1,36 @@ +package registrar + +import ( + "time" +) + +// only admin can set Fee, other just can read only +type feeInfo struct { + RegisterBaseFee int64 + RenewalFee int64 + RegisterAdditionFee int64 + BidJoinFee int64 +} + +func GetRegisterFee(dName string) int64 { + return fee.RegisterBaseFee +} + +func GetRenewalFee(dName string, amount time.Duration) int64 { + return fee.RenewalFee +} + +// Admin set register fee and renewal fee +func AdminSetFee(regFee int64, renewFee int64) { + // consider logic + assertIsAdmin() + fee.RegisterBaseFee = regFee + fee.RenewalFee = renewFee +} + +// simple err check +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_checks.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_checks.gno new file mode 100644 index 00000000..4b054378 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_checks.gno @@ -0,0 +1,9 @@ +package registrar + +// import ( +// "" +// // "std" +// // "time" +// ) + + diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_native.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_native.gno new file mode 100644 index 00000000..aac57354 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_native.gno @@ -0,0 +1,52 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// admin access only +func AdminWithdraw(amount int64) { + assertIsAdmin() + thisContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + superBanker.SendCoins(thisContract, admin, coinsToTransfer) +} + +func nativeProcess() { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + caller := std.GetOrigCaller() + coins := checkCoin(caller) + ufmt.Println("check: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// RevertTransfer will revert the transaction - send amount of coin to user +func revertTransfer(userAddr std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToReturn := std.NewCoins(ugnotCoin) + ufmt.Println("return coins from contract ", bankerContract.String(), " to ", userAddr.String()) + bankerUser.SendCoins(bankerContract, userAddr, coinsToReturn) +} + +// simple check for admin call +func assertIsAdmin() { + // check if GetCallerAt 2 or 3 when deployed + caller := std.GetCallerAt(3) + err := ufmt.Sprintf("unauthorize with caller: %s\n", caller) + if caller != admin && caller != adminVar { + panic(err) + } +} + +// checking for availble coins +func checkCoin(from std.Address) std.Coins { + // caller := std.GetOrigCaller() + return bankerUser.GetCoins(from) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_token.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_token.gno new file mode 100644 index 00000000..144aa1df --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/fee_token.gno @@ -0,0 +1,25 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + "gno.land/r/varmeta/demo1/domain/vmt" +) + +// expected approved already from client -> transfer from caller to admin +func tokenProcess(dName string, callerStd std.Address) { + caller := pusers.AddressOrName(callerStd.String()) + + now := std.CurrentRealm().Addr() + nowAddr := pusers.AddressOrName(now.String()) + ufmt.Println("current realm transfer: ", now.String()) + callerAllowance := vmt.Allowance(caller, nowAddr) + callerAllowanceString := ufmt.Sprintf("%d", callerAllowance) + ufmt.Println("caller allowance ", callerAllowanceString) + + adminAddr := pusers.AddressOrName(admin.String()) + ufmt.Println("admin: ", admin.String()) + vmt.TransferFrom(caller, adminAddr, 1) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/hashstring.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/hashstring.gno new file mode 100644 index 00000000..674432a1 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/hashstring.gno @@ -0,0 +1,13 @@ +package registrar + +import ( + "crypto/sha256" + "encoding/hex" +) + +func Get256String(input string) string { + data := []byte(input) + hashed := sha256.Sum256(data) + hashedBytes := hashed[:] + return hex.EncodeToString(hashedBytes) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/metadata_wrapper.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/metadata_wrapper.gno new file mode 100644 index 00000000..5f99642d --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/metadata_wrapper.gno @@ -0,0 +1,40 @@ +package registrar + +import ( + "bytes" + "std" + "time" + + "gno.land/p/demo/ufmt" + + "gno.land/p/varmeta/demo/v33/domain" +) + +// Metadata wrapper +// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait) +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + createdAt := time.Now() + expTime := createdAt.Add(ttl) + return domain.NewMetadata("", name, "", "", createdAt, expTime, []domain.Trait{}) +} + +type remapMetadata struct { + Avatar string // avatar - URL or identifier for an avatar image + RegistrationTime string // regtime - The time when the domain was registered + ExpirationTime string // exptime - The time when the domain will be expire + Attributes []domain.Trait // atts - Additional attributes of the domain + Description string // des - A description of the domain + ContactInfo string // contacts - Contact information for the domain owner + RenewalFee string // renewalfee - The fee required to renew the domain, represented as a string +} + +// currently not support for arrays +func (m remapMetadata) MarshalJSON() ([]byte, error) { + json := new(bytes.Buffer) + if m.Attributes == nil { + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, "empty", m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil + } + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/models.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/models.gno new file mode 100644 index 00000000..fe15a6ba --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/models.gno @@ -0,0 +1,22 @@ +package registrar + +import ( + "std" +) + +type RequestInfo struct { + Mode string + WantedDomain string + Caller std.Address + TransInfo TransferInfo + // xxx extendTime, renew... +} +type TransferInfo struct { + From std.Address + To std.Address +} +type ExecuteResult struct { + Success bool + ResultDetails error + Message string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/prestep.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/prestep.gno new file mode 100644 index 00000000..0b261d6d --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/prestep.gno @@ -0,0 +1,41 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v33/domain" +) + +var ( + domainStorage *avl.Tree // domainName -> std.Address + rootRegistry domain.DomainRegistry + + // fee + superBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction + bankerUser std.Banker // full access to coins sent with the transaction that called the banker + + admin std.Address // admin + adminVar std.Address // admin in server + fee feeInfo +) + +func init() { + domainStorage = avl.NewTree() + BidRec = avl.NewTree() + winnerRec = avl.NewTree() + rootRegistry = domain.NewDomainRegistry("Varmeta", "vmt") + + // fee init + admin = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" //@thinhnx + adminVar = "g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx" //@varmeta-sponsorkey + // ugnot + fee = feeInfo{ + RegisterBaseFee: 100, + RenewalFee: 100, + RegisterAdditionFee: 0, + BidJoinFee: 100, + } + superBanker = std.GetBanker(std.BankerTypeRealmSend) + bankerUser = std.GetBanker(std.BankerTypeOrigSend) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/registrar.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/registrar.gno new file mode 100644 index 00000000..c3a8d656 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/registrar.gno @@ -0,0 +1,152 @@ +/* +This package contains functions that will actually execute the request from user +Features: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion... +*/ +// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm. +// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session + +// currently we dont using too much panic because we dont have defer functions to revert the state of storage +package registrar + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v33/domain" +) + +// XXX: consider using panic instead of return string or errors +func Register(domainName string, mode string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: mode, + } + + regResult := executeRegister(requestInfo) + + // calling panic to stop paying fee + if !regResult.Success { + panic(regResult.ResultDetails.Error()) + } + // pay fee with panic inside + feeProcess(requestInfo) + return "Register Done" +} + +func executeRegister(req RequestInfo) ExecuteResult { + // check if domain name is regex valid + var execRes ExecuteResult + if !isValidDomain(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrInvalidDomainName + return execRes + } + + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrAlreadyRegistered + return execRes + } + + // execute register domain - mint the nft + // changelogs v2: we are using sealed bidding now + + caller := req.Caller + ttl := defaultExpireTime + metadata := metadataWrapper(caller, req.WantedDomain, ttl) + // create a new registry instance to save metadata and mint the NFT + errRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl) + if errRegister != nil { + execRes.Success = false + execRes.ResultDetails = ErrCrossRealms + return execRes + } + // now save caller to corressponding tree to manage + domainStorage.Set(req.WantedDomain, caller) + + execRes.Success = true + return execRes +} + +func feeProcess(req RequestInfo) { + if req.Mode == "token" { + tokenProcess(req.WantedDomain, req.Caller) + } else { + nativeProcess() + } +} + +func AlreadyRegistered(domainName string) bool { + // if can get owner -> existed + addr, err := rootRegistry.OwnerOf(domainName) + if err == nil && addr != "" { + return true + } + return false +} + +func GetOwner(domainName string) std.Address { + vl, existed := domainStorage.Get(domainName) + if !existed { + return "" + } + return vl.(std.Address) +} + +func Search(domainName string) (remapMetadata, string) { + validMetadata := remapMetadata{} + md, err := getMetadata(domainName) + if err != nil { + // return validMetadata, err.Error() + panic(err) + } + validMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339) + validMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339) + // jsonData, _ := validMetadata.MarshalJSON() + return validMetadata, "Search Success" +} + +func getMetadata(wantedDomain string) (domain.Metadata, error) { + // confirm the method? -> get all the fields if the fields slice is empty + metadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{}) + if err != nil { + return metadata, err + } + return metadata, nil +} + +// Transfer +func TransferDomain(from, to, domainName string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + } + if err := excuteTransfer(requestInfo); err != "" { + panic(err) + } + return "Transfer Done" +} + +func excuteTransfer(req RequestInfo) string { + if !AlreadyRegistered(req.WantedDomain) { + return ErrAlreadyRegistered.Error() + } + rootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain) + return "" +} + +func GetDomainName(addr string) []string { + domainList := []string{} + // search from local storage + domainStorage.Iterate("", "", func(key string, value interface{}) bool { + caller := value.(std.Address) + // not checking isExpired + if caller.String() == addr { + domainList = append(domainList, key) + } + return false + }) + return domainList +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/registrar_test.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/registrar_test.gno new file mode 100644 index 00000000..df136440 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/registrar_test.gno @@ -0,0 +1,25 @@ +package registrar + +// import ( +// "fmt" +// "std" +// "testing" +// ) + +// func TestRegisterDomain(t *testing.T) { +// tcs := []struct { +// input string +// expected string +// }{ +// {"thinhnx", "Register done"}, +// } +// for tc := range tcs { +// name := tc.input +// t.Run(name, func(t *testing.T) { +// output := Register(tc.input) +// if output != tc.expected { +// t.Errorf("Expected '%q, but got %q", tc.expected, output) +// } +// }) +// } +// } diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/utils.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/utils.gno new file mode 100644 index 00000000..49578643 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/registrar/utils.gno @@ -0,0 +1,39 @@ +/* +This check module contains function to do the checking stuffs +*/ +package registrar + +import ( + "regexp" + "time" +) + +var ( + defaultCommitHashTime = time.Second * 60 + defaultCommitPriceTime = time.Second * 60 + defaultExpireTime = time.Hour // 30 days + reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) +) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func GetExpirationDate(dName string) time.Time { + return rootRegistry.GetExpirationDate(dName) +} + +// for now, this function only let admin set +func SetExpirationDate(dName string, expDate time.Time) bool { + assertIsAdmin() + return rootRegistry.SetExpirationDate(dName, expDate) +} + +func SetCommitPhaseTime(duration int) { + defaultCommitHashTime = time.Duration(duration) * time.Second +} + +func SetCommitPriceTime(duration int) { + defaultCommitPriceTime = time.Duration(duration) * time.Second +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/checks_resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/checks_resolver.gno new file mode 100644 index 00000000..3278a2ff --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/checks_resolver.gno @@ -0,0 +1,27 @@ +/* +This check module contains function to do the checking stuffs +*/ +package resolver + +import ( + "regexp" + "time" + + "gno.land/r/varmeta/demo/v33/domain/registrar" +) + +// const ( +// admin std.Address = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" // -> @thinhnx +// ) + +var reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func isExpired(dName string) bool { + expDate := registrar.GetExpirationDate(dName) + return expDate.Before(time.Now()) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/errors.gno new file mode 100644 index 00000000..19709079 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/errors.gno @@ -0,0 +1,11 @@ +package resolver + +import ( + "errors" +) + +var ( + ErrNotFound = errors.New("not found") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("invalid domain name to register") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/resolver.gno new file mode 100644 index 00000000..b5ff5811 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/resolver.gno @@ -0,0 +1,63 @@ +/* +The goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner. +It serves the purpose of "resolving" the ICNS Name +to the correct address (e.g "alice.gno" -> g1xxx). +*/ +// changelogs: move Register feature into this resolver package +// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result + +package resolver + +import ( + "std" + + "gno.land/r/varmeta/demo/v33/domain/registrar" +) + +type Record struct { + Owner std.Address + IsValid bool + Memo string // no more need this + Priority int +} + +// retrieve the record list to get the onchain address +func Resolve(domainName string) *Record { + if !isValidDomain(domainName) { + panic("bad domain name") + } + record := &Record{} + + owner := getOwnerFromDomainStorage(domainName) + if owner == "" { + record.Memo = "not found" + record.IsValid = false + return record + } + + if !isExpired(domainName) { + record.IsValid = true + record.Owner = owner + } else { + record.IsValid = false + } + return record +} + +func GetDomainName(addr string) []string { + return registrar.GetDomainName(addr) +} + +/* +If query in local storage not found +Query to DomainStorage by domainName -> get the registry -> use that registry to get the Owner() +and check the validation time? +*/ + +func existedInDomainStorage(domainName string) bool { + return registrar.AlreadyRegistered(domainName) +} + +func getOwnerFromDomainStorage(domainName string) std.Address { + return registrar.GetOwner(domainName) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/resolver_metadata.gno b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/resolver_metadata.gno new file mode 100644 index 00000000..418ffe54 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v33/domain/resolver/resolver_metadata.gno @@ -0,0 +1,15 @@ +package resolver + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v33/domain" +) + +// Metadata wrapper +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + crrTime := time.Now() + expTime := crrTime.Add(ttl) + return domain.NewMetadata("", name, "", "", crrTime, expTime, []domain.Trait{}) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/bidding.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/bidding.gno new file mode 100644 index 00000000..bcce748a --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/bidding.gno @@ -0,0 +1,454 @@ +package registrar + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/ufmt" + // "gno.land/p/demo/mux" + "gno.land/p/demo/avl" +) + +type bidRecord struct { + DomainName string + Bidder std.Address + HashString string + Price int + StartTime time.Time + EndCommitTime time.Time + EndPriceTime time.Time + CurrentPhase actionCode + IsOpen bool +} + +var ( + BidRec *avl.Tree // bidRecord <- []bidRec + winnerRec *avl.Tree + joinedBid avl.Tree +) + +// joinedBid: address <- []domainName +func recordJoinedBid(domainName string) { + caller := std.GetOrigCaller() + dList := []string{} + data, existed := joinedBid.Get(domainName) + if !existed { + dList = []string{domainName} + joinedBid.Set(caller.String(), dList) + return + } + dList = data.([]string) + dList = append(dList, domainName) + joinedBid.Set(caller.String(), dList) + return +} + +// get the joined bids of an account +// want to return both name and status +func GetJoinedBid() []singleStatus { + caller := std.GetOrigCaller().String() + data, existed := joinedBid.Get(caller) + if !existed { + return []singleStatus{} + } + list := data.([]string) + listStatus := []singleStatus{} + for _, dName := range list { + stt := GetCurrentStatus(dName) + singleStt := singleStatus{ + DomainName: dName, + Status: stt, + } + listStatus = append(listStatus, singleStt) + } + return listStatus +} + +// get the next state of bidding session +// XXX note commit price status + if time is expired and not commited yet +// status clear +func GetCurrentStatus(domainName string) string { + // if there is record in joinedBid -> user joined + // check for tine.Now() and startTime + now := time.Now() + caller := std.GetOrigCaller() + // find the record + data, existed := BidRec.Get(domainName) + if !existed { + // no record in bidRec yet -> not commited -> check if user started auction or not - if yes: new auction + if _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister { + return "new auction" + } + return "invalid" + } + + // commited yet + recList := data.([]bidRecord) + rec := recList[0] + + if now.Before(rec.EndCommitTime) { + if rec.HashString != "" { + return "commited hash" + } else { + return "hash" + } + } + + if now.Before(rec.EndPriceTime) && now.After(rec.EndCommitTime) { + return "price" + } + if now.After(rec.EndPriceTime) { + return "closed" + } + return "undefined" +} + +// Render() renders welcome message :D +func Render(path string) string { + return "welcome to varmeta domain name service" +} + +/* + logic should be: + 1. User click register for a dns. + -> 1.1: dns does not exists -> open bidding session + -> 1.2: dns existed -> check if open bidding session is openning or not -> if open -> new bidRecord + 2. Bidding session: + -> 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -> creat bidRecord to add to bidRec + -> 2.2: CommitPrice phase: commit price + secret key: -> if matches -> write price into bidRec ( send the amunt of coin corresponding) + -> 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button + ---> admin end bidding auction + -> 2.3.1: cooldown phase -> waiting for winner -> 2nd winner + -> 2.4: re-transfer coin from admin to user who not win the bid + the flow is: user want to register new domain name -> start new bidding session -> got the bidding session id + if other want to register to the same domain name -> bidding into this session, if not return to new state + when the session ends, need to have 2nd phase called commit -> commit their price and secret to compute the hash + -> compare the hash as in record -> accept the hash -> accept the price + *** temp approach: everytime user commit, user open a bid session if not existed, if existed -> check if this session is ended. +*/ +// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate +// the time whenever there is action to commit. + +// commit the domain name and computed hash string +func CommitHash(domainName, hashString string) bidStatus { + bStt := bidStatus{} + caller := std.GetOrigCaller() + ufmt.Println("caller: ", caller.String()) + now := time.Now() + + // update the bid record + data, existed := BidRec.Get(domainName) + + // if not existed -> create new record + if !existed { + var bidRec bidRecord + endCommitTime := now.Add(defaultCommitHashTime) + endPriceTime := endCommitTime.Add(defaultCommitPriceTime) + ufmt.Println("endCommitTime: ", endCommitTime.Format(time.RFC3339)) + ufmt.Println("endPriceTime: ", endPriceTime.Format(time.RFC3339)) + bidRec = bidRecord{ + DomainName: domainName, + Bidder: caller, + HashString: hashString, + StartTime: now, + EndCommitTime: endCommitTime, + EndPriceTime: endPriceTime, + IsOpen: true, + } + bidRecList := []bidRecord{bidRec} + BidRec.Set(domainName, bidRecList) + + // charge fee + chargeFee(fee.BidJoinFee, caller) + bStt.CurrentStatus = NewBiddingSession + bStt.ActionCode = CommitPricePhase + return bStt + } + // if existed + // TODO: Check if commit more than 1 time -> done + bidRecList := data.([]bidRecord) + startTime := bidRecList[0].StartTime + if now.After(bidRecList[0].EndCommitTime) { + panic("can not commit hash anymore") + } + for _, bR := range bidRecList { + if bR.Bidder == caller { + panic("you already commited hash") + } + } + + oldEndCommitTime := bidRecList[0].EndCommitTime + oldEndPriceTime := bidRecList[0].EndPriceTime + bidRec := bidRecord{ + DomainName: domainName, + HashString: hashString, + Bidder: caller, + StartTime: startTime, + EndCommitTime: oldEndCommitTime, + EndPriceTime: oldEndPriceTime, + IsOpen: true, + } + bidRecList = append(bidRecList, bidRec) + // Save record + BidRec.Set(domainName, bidRecList) + // charge commit hash fee + chargeFee(fee.BidJoinFee, caller) + bStt.CurrentStatus = AppendBiddingSession + bStt.ActionCode = CommitPricePhase + return bStt +} + +// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me +// commit price and secret to reveal auction session +func CommitPrice(price int, secret string, domainName string) bidStatus { + var bStt bidStatus + // compute the hash string, compare to saved hash string in record + now := time.Now() + joinedString := secret + strconv.Itoa(price) + computedHashString := Get256String(joinedString) + ufmt.Println("computed hash: ", computedHashString) + caller := std.GetOrigCaller() + data, existed := BidRec.Get(domainName) + if !existed { + panic(" domain name is invalid") + } + bidRecList := data.([]bidRecord) + winnerRec.Set(domainName, bidRecList[0]) + + ufmt.Println("time now: ", now.Format(time.RFC3339)) + ufmt.Println("end commit phase time: ", bidRecList[0].EndCommitTime.Format(time.RFC3339)) + // case commit after end - consider panic or not + if now.After(bidRecList[0].EndPriceTime) { + ufmt.Println("commit price phase is ended") + bStt.ActionCode = 0 + bStt.CurrentStatus = 0 + return bStt + } + // case commit when price phase not started + if time.Now().Before(bidRecList[0].EndCommitTime) { + ufmt.Println("commit price phase is not started yet") + bStt.ActionCode = 0 + bStt.CurrentStatus = 0 + return bStt + } + + // search for the corresponding hash + for _, bidRec := range bidRecList { + // panic because wrong price or wrong secret string + if bidRec.Bidder == caller && bidRec.HashString != computedHashString { + panic("invalid hash string") + } + // found it, update the winner price + if bidRec.Bidder == caller && bidRec.HashString == computedHashString { + data, _ := winnerRec.Get(domainName) + currentWinnerRec := data.(bidRecord) + if price > currentWinnerRec.Price && now.Before(currentWinnerRec.EndPriceTime) { + ufmt.Println("found new winner, setting up") + currentWinnerRec.Price = price + currentWinnerRec.Bidder = bidRec.Bidder + currentWinnerRec.HashString = bidRec.HashString + winnerRec.Set(domainName, currentWinnerRec) + bStt.ActionCode = ClaimPhase + return bStt + } + } + } + // if not match above case, then panic + panic("commit failed") +} + +func GetCurrentWinner(domainName string) bidRecord { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("no winner yet") + } + return data.(bidRecord) +} + +// find the highest bid in session - incase everyone commited price +func findTheWinner(domainName string) bidRecord { + var winnerBid bidRecord + data, existed := BidRec.Get(domainName) + if !existed { + panic("invalid domain name") + } + bidRecList := data.([]bidRecord) + winnerBid = bidRecList[0] + for _, bidRec := range bidRecList { + if bidRec.Price > winnerBid.Price { + winnerBid = bidRec + } + } + return winnerBid +} + +// register the domain for winner +func registerForWinner(domainName string, winnerRec bidRecord) bool { + winnerAddr := winnerRec.Bidder + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: winnerAddr, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + feeProcess(requestInfo) + return false +} + +// everyone can call EndBid() +// this EndBid checks endTime -> end the auction +func EndBid(domainName string) error { + now := time.Now() + data, existed := BidRec.Get(domainName) + if !existed { + return ufmt.Errorf("endbid: invalid domain name") + } + bidRecList := data.([]bidRecord) + firstBidRec := bidRecList[0] + if now.Before(firstBidRec.EndPriceTime) { + return ufmt.Errorf("endbid: this session can not end before the end time") + } + // change all state + for _, bidRec := range bidRecList { + bidRec.IsOpen = false + } + ok := BidRec.Set(domainName, bidRecList) + if !ok { + return ufmt.Errorf("endbid: can not change bid record state") + } + // need more conditions for findTheWinner() + findTheWinner(domainName) + return nil +} + +// register new domain with bidding process inside - if message is commit hash -> dapp need to call commit hash +func RegisterDomain(domainName string) bidStatus { + reqInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: "native", + } + + // bidStatus + result := checkRegisterState(reqInfo) + recordJoinedBid(domainName) + // if checkState get the message of which phase this domain name belongs to, then return the status + return result +} + +func checkRegisterState(req RequestInfo) bidStatus { + var bStt bidStatus + // check if domain name is regex valid + if !isValidDomain(req.WantedDomain) { + panic("invalid domain name") + } + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + panic("domain name already registered") + } + // changelogs v2: we are using sealed bidding now + // check if a bidding session is openning -> append new commit hash into record list + // both existed or not we open new bidding session + // for now we return a signal for dapps / service to know what to do next + isExisted, isOpen := checkBiddingState(req.WantedDomain) + if isExisted && isOpen { + // return commit hash signal for dapps + bStt.CurrentStatus = AppendBiddingSession + bStt.ActionCode = CommitHashPhase + return bStt + } + + // create new session + if !isExisted { + bStt.CurrentStatus = NewBiddingSession + bStt.ActionCode = CommitHashPhase + return bStt + } + + // not found in register repository but also not found in Bidding Record -> panic("error somewhere :D") + panic(ufmt.Errorf("should not happend")) + return bStt +} + +// // open a bidding session +// func openBiddingSession(domainName string, dur time.Duration) string { +// now := time.Now() +// bidRec := bidRecord{ +// DomainName: domainName, +// StartTime: now, +// } +// bidRecList := []bidRecord{bidRec} +// ok := BidRec.Set(domainName, bidRecList) +// if !ok { +// panic("can not open bidding session") +// } +// return "created bidding session" +// } + +func checkBiddingState(dName string) (isExisted bool, isOpen bool) { + data, existed := BidRec.Get(dName) + if !existed { + isExisted = false + isOpen = false + return + } + isExisted = true + recList := data.([]bidRecord) + if recList[0].IsOpen { + isOpen = true + } else { + isOpen = false + } + return isExisted, isOpen +} + +// get all the price list that joined the bid for displaying in dapp +func GetRecords(dName string) []bidRecord { + data, existed := BidRec.Get(dName) + if !existed { + panic("should not") + } + return data.([]bidRecord) +} + +// chargeFee will charge amount - send from this contract to admin +func chargeFee(amount int64, from std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", amount) + coinsToTransfer := std.NewCoins(ugnotCoin) + coins := checkCoin(from) + ufmt.Println("check balances: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// pay fee and claim the domain name if you are winner +func Claim(domainName string) bool { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("claim: invalid domain name") + } + caller := std.GetOrigCaller() + rec := data.(bidRecord) + if caller != rec.Bidder { + panic("only winner can claim") + } + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: rec.Bidder, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + chargeFee(100, caller) + feeProcess(requestInfo) + return true +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/bidding_model.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/bidding_model.gno new file mode 100644 index 00000000..cede7c19 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/bidding_model.gno @@ -0,0 +1,38 @@ +package registrar + +/* + 0: StatusFailed + CurrentStatus: 1: NewBiddingSession + 2: AppendBiddingSession + 3: BiddingSessionClosed + ActionCode: 1: CommitHashPhase + 2: CommitPricePhase + 3: ClaimPhase... +*/ + +type bidStatus struct { + CurrentStatus currStatus + ActionCode actionCode +} + +type ( + currStatus int + actionCode int +) + +var ( + StatusFailed currStatus = 0 + NewBiddingSession currStatus = 1 + AppendBiddingSession currStatus = 2 + BiddingSessionClosed currStatus = 3 + + ActionFailed actionCode = 0 + CommitHashPhase actionCode = 1 + CommitPricePhase actionCode = 2 + ClaimPhase actionCode = 3 +) + +type singleStatus struct { + DomainName string + Status string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/errors.gno new file mode 100644 index 00000000..a186a711 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/errors.gno @@ -0,0 +1,15 @@ +package registrar + +import ( + "errors" +) + +var ( + ErrUnknown = errors.New("unknow errors") + ErrOK = errors.New("ok") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("ErrInvalidDomainName") + ErrAlreadyRegistered = errors.New("this domain is registered") + ErrCrossRealms = errors.New("cross realms function error") + ErrNotFound = errors.New("domain not found") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee.gno new file mode 100644 index 00000000..9c0eef2f --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee.gno @@ -0,0 +1,36 @@ +package registrar + +import ( + "time" +) + +// only admin can set Fee, other just can read only +type feeInfo struct { + RegisterBaseFee int64 + RenewalFee int64 + RegisterAdditionFee int64 + BidJoinFee int64 +} + +func GetRegisterFee(dName string) int64 { + return fee.RegisterBaseFee +} + +func GetRenewalFee(dName string, amount time.Duration) int64 { + return fee.RenewalFee +} + +// Admin set register fee and renewal fee +func AdminSetFee(regFee int64, renewFee int64) { + // consider logic + assertIsAdmin() + fee.RegisterBaseFee = regFee + fee.RenewalFee = renewFee +} + +// simple err check +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_checks.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_checks.gno new file mode 100644 index 00000000..4b054378 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_checks.gno @@ -0,0 +1,9 @@ +package registrar + +// import ( +// "" +// // "std" +// // "time" +// ) + + diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_native.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_native.gno new file mode 100644 index 00000000..aac57354 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_native.gno @@ -0,0 +1,52 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// admin access only +func AdminWithdraw(amount int64) { + assertIsAdmin() + thisContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + superBanker.SendCoins(thisContract, admin, coinsToTransfer) +} + +func nativeProcess() { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + caller := std.GetOrigCaller() + coins := checkCoin(caller) + ufmt.Println("check: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// RevertTransfer will revert the transaction - send amount of coin to user +func revertTransfer(userAddr std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToReturn := std.NewCoins(ugnotCoin) + ufmt.Println("return coins from contract ", bankerContract.String(), " to ", userAddr.String()) + bankerUser.SendCoins(bankerContract, userAddr, coinsToReturn) +} + +// simple check for admin call +func assertIsAdmin() { + // check if GetCallerAt 2 or 3 when deployed + caller := std.GetCallerAt(3) + err := ufmt.Sprintf("unauthorize with caller: %s\n", caller) + if caller != admin && caller != adminVar { + panic(err) + } +} + +// checking for availble coins +func checkCoin(from std.Address) std.Coins { + // caller := std.GetOrigCaller() + return bankerUser.GetCoins(from) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_token.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_token.gno new file mode 100644 index 00000000..144aa1df --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/fee_token.gno @@ -0,0 +1,25 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + "gno.land/r/varmeta/demo1/domain/vmt" +) + +// expected approved already from client -> transfer from caller to admin +func tokenProcess(dName string, callerStd std.Address) { + caller := pusers.AddressOrName(callerStd.String()) + + now := std.CurrentRealm().Addr() + nowAddr := pusers.AddressOrName(now.String()) + ufmt.Println("current realm transfer: ", now.String()) + callerAllowance := vmt.Allowance(caller, nowAddr) + callerAllowanceString := ufmt.Sprintf("%d", callerAllowance) + ufmt.Println("caller allowance ", callerAllowanceString) + + adminAddr := pusers.AddressOrName(admin.String()) + ufmt.Println("admin: ", admin.String()) + vmt.TransferFrom(caller, adminAddr, 1) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/hashstring.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/hashstring.gno new file mode 100644 index 00000000..674432a1 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/hashstring.gno @@ -0,0 +1,13 @@ +package registrar + +import ( + "crypto/sha256" + "encoding/hex" +) + +func Get256String(input string) string { + data := []byte(input) + hashed := sha256.Sum256(data) + hashedBytes := hashed[:] + return hex.EncodeToString(hashedBytes) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/metadata_wrapper.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/metadata_wrapper.gno new file mode 100644 index 00000000..ab7cff70 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/metadata_wrapper.gno @@ -0,0 +1,40 @@ +package registrar + +import ( + "bytes" + "std" + "time" + + "gno.land/p/demo/ufmt" + + "gno.land/p/varmeta/demo/v34/domain" +) + +// Metadata wrapper +// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait) +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + createdAt := time.Now() + expTime := createdAt.Add(ttl) + return domain.NewMetadata("", name, "", "", createdAt, expTime, []domain.Trait{}) +} + +type remapMetadata struct { + Avatar string // avatar - URL or identifier for an avatar image + RegistrationTime string // regtime - The time when the domain was registered + ExpirationTime string // exptime - The time when the domain will be expire + Attributes []domain.Trait // atts - Additional attributes of the domain + Description string // des - A description of the domain + ContactInfo string // contacts - Contact information for the domain owner + RenewalFee string // renewalfee - The fee required to renew the domain, represented as a string +} + +// currently not support for arrays +func (m remapMetadata) MarshalJSON() ([]byte, error) { + json := new(bytes.Buffer) + if m.Attributes == nil { + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, "empty", m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil + } + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/models.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/models.gno new file mode 100644 index 00000000..fe15a6ba --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/models.gno @@ -0,0 +1,22 @@ +package registrar + +import ( + "std" +) + +type RequestInfo struct { + Mode string + WantedDomain string + Caller std.Address + TransInfo TransferInfo + // xxx extendTime, renew... +} +type TransferInfo struct { + From std.Address + To std.Address +} +type ExecuteResult struct { + Success bool + ResultDetails error + Message string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/prestep.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/prestep.gno new file mode 100644 index 00000000..33a5a6c9 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/prestep.gno @@ -0,0 +1,41 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v34/domain" +) + +var ( + domainStorage *avl.Tree // domainName -> std.Address + rootRegistry domain.DomainRegistry + + // fee + superBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction + bankerUser std.Banker // full access to coins sent with the transaction that called the banker + + admin std.Address // admin + adminVar std.Address // admin in server + fee feeInfo +) + +func init() { + domainStorage = avl.NewTree() + BidRec = avl.NewTree() + winnerRec = avl.NewTree() + rootRegistry = domain.NewDomainRegistry("Varmeta", "vmt") + + // fee init + admin = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" //@thinhnx + adminVar = "g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx" //@varmeta-sponsorkey + // ugnot + fee = feeInfo{ + RegisterBaseFee: 100, + RenewalFee: 100, + RegisterAdditionFee: 0, + BidJoinFee: 100, + } + superBanker = std.GetBanker(std.BankerTypeRealmSend) + bankerUser = std.GetBanker(std.BankerTypeOrigSend) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/registrar.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/registrar.gno new file mode 100644 index 00000000..d454e125 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/registrar.gno @@ -0,0 +1,152 @@ +/* +This package contains functions that will actually execute the request from user +Features: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion... +*/ +// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm. +// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session + +// currently we dont using too much panic because we dont have defer functions to revert the state of storage +package registrar + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v34/domain" +) + +// XXX: consider using panic instead of return string or errors +func Register(domainName string, mode string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: mode, + } + + regResult := executeRegister(requestInfo) + + // calling panic to stop paying fee + if !regResult.Success { + panic(regResult.ResultDetails.Error()) + } + // pay fee with panic inside + feeProcess(requestInfo) + return "Register Done" +} + +func executeRegister(req RequestInfo) ExecuteResult { + // check if domain name is regex valid + var execRes ExecuteResult + if !isValidDomain(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrInvalidDomainName + return execRes + } + + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrAlreadyRegistered + return execRes + } + + // execute register domain - mint the nft + // changelogs v2: we are using sealed bidding now + + caller := req.Caller + ttl := defaultExpireTime + metadata := metadataWrapper(caller, req.WantedDomain, ttl) + // create a new registry instance to save metadata and mint the NFT + errRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl) + if errRegister != nil { + execRes.Success = false + execRes.ResultDetails = ErrCrossRealms + return execRes + } + // now save caller to corressponding tree to manage + domainStorage.Set(req.WantedDomain, caller) + + execRes.Success = true + return execRes +} + +func feeProcess(req RequestInfo) { + if req.Mode == "token" { + tokenProcess(req.WantedDomain, req.Caller) + } else { + nativeProcess() + } +} + +func AlreadyRegistered(domainName string) bool { + // if can get owner -> existed + addr, err := rootRegistry.OwnerOf(domainName) + if err == nil && addr != "" { + return true + } + return false +} + +func GetOwner(domainName string) std.Address { + vl, existed := domainStorage.Get(domainName) + if !existed { + return "" + } + return vl.(std.Address) +} + +func Search(domainName string) (remapMetadata, string) { + validMetadata := remapMetadata{} + md, err := getMetadata(domainName) + if err != nil { + // return validMetadata, err.Error() + panic(err) + } + validMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339) + validMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339) + // jsonData, _ := validMetadata.MarshalJSON() + return validMetadata, "Search Success" +} + +func getMetadata(wantedDomain string) (domain.Metadata, error) { + // confirm the method? -> get all the fields if the fields slice is empty + metadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{}) + if err != nil { + return metadata, err + } + return metadata, nil +} + +// Transfer +func TransferDomain(from, to, domainName string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + } + if err := excuteTransfer(requestInfo); err != "" { + panic(err) + } + return "Transfer Done" +} + +func excuteTransfer(req RequestInfo) string { + if !AlreadyRegistered(req.WantedDomain) { + return ErrAlreadyRegistered.Error() + } + rootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain) + return "" +} + +func GetDomainName(addr string) []string { + domainList := []string{} + // search from local storage + domainStorage.Iterate("", "", func(key string, value interface{}) bool { + caller := value.(std.Address) + // not checking isExpired + if caller.String() == addr { + domainList = append(domainList, key) + } + return false + }) + return domainList +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/registrar_test.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/registrar_test.gno new file mode 100644 index 00000000..df136440 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/registrar_test.gno @@ -0,0 +1,25 @@ +package registrar + +// import ( +// "fmt" +// "std" +// "testing" +// ) + +// func TestRegisterDomain(t *testing.T) { +// tcs := []struct { +// input string +// expected string +// }{ +// {"thinhnx", "Register done"}, +// } +// for tc := range tcs { +// name := tc.input +// t.Run(name, func(t *testing.T) { +// output := Register(tc.input) +// if output != tc.expected { +// t.Errorf("Expected '%q, but got %q", tc.expected, output) +// } +// }) +// } +// } diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/utils.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/utils.gno new file mode 100644 index 00000000..49578643 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/registrar/utils.gno @@ -0,0 +1,39 @@ +/* +This check module contains function to do the checking stuffs +*/ +package registrar + +import ( + "regexp" + "time" +) + +var ( + defaultCommitHashTime = time.Second * 60 + defaultCommitPriceTime = time.Second * 60 + defaultExpireTime = time.Hour // 30 days + reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) +) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func GetExpirationDate(dName string) time.Time { + return rootRegistry.GetExpirationDate(dName) +} + +// for now, this function only let admin set +func SetExpirationDate(dName string, expDate time.Time) bool { + assertIsAdmin() + return rootRegistry.SetExpirationDate(dName, expDate) +} + +func SetCommitPhaseTime(duration int) { + defaultCommitHashTime = time.Duration(duration) * time.Second +} + +func SetCommitPriceTime(duration int) { + defaultCommitPriceTime = time.Duration(duration) * time.Second +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/checks_resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/checks_resolver.gno new file mode 100644 index 00000000..56ceabb8 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/checks_resolver.gno @@ -0,0 +1,27 @@ +/* +This check module contains function to do the checking stuffs +*/ +package resolver + +import ( + "regexp" + "time" + + "gno.land/r/varmeta/demo/v34/domain/registrar" +) + +// const ( +// admin std.Address = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" // -> @thinhnx +// ) + +var reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func isExpired(dName string) bool { + expDate := registrar.GetExpirationDate(dName) + return expDate.Before(time.Now()) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/errors.gno new file mode 100644 index 00000000..19709079 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/errors.gno @@ -0,0 +1,11 @@ +package resolver + +import ( + "errors" +) + +var ( + ErrNotFound = errors.New("not found") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("invalid domain name to register") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/resolver.gno new file mode 100644 index 00000000..6480a89c --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/resolver.gno @@ -0,0 +1,63 @@ +/* +The goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner. +It serves the purpose of "resolving" the ICNS Name +to the correct address (e.g "alice.gno" -> g1xxx). +*/ +// changelogs: move Register feature into this resolver package +// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result + +package resolver + +import ( + "std" + + "gno.land/r/varmeta/demo/v34/domain/registrar" +) + +type Record struct { + Owner std.Address + IsValid bool + Memo string // no more need this + Priority int +} + +// retrieve the record list to get the onchain address +func Resolve(domainName string) *Record { + if !isValidDomain(domainName) { + panic("bad domain name") + } + record := &Record{} + + owner := getOwnerFromDomainStorage(domainName) + if owner == "" { + record.Memo = "not found" + record.IsValid = false + return record + } + + if !isExpired(domainName) { + record.IsValid = true + record.Owner = owner + } else { + record.IsValid = false + } + return record +} + +func GetDomainName(addr string) []string { + return registrar.GetDomainName(addr) +} + +/* +If query in local storage not found +Query to DomainStorage by domainName -> get the registry -> use that registry to get the Owner() +and check the validation time? +*/ + +func existedInDomainStorage(domainName string) bool { + return registrar.AlreadyRegistered(domainName) +} + +func getOwnerFromDomainStorage(domainName string) std.Address { + return registrar.GetOwner(domainName) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/resolver_metadata.gno b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/resolver_metadata.gno new file mode 100644 index 00000000..364cd3ee --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v34/domain/resolver/resolver_metadata.gno @@ -0,0 +1,15 @@ +package resolver + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v34/domain" +) + +// Metadata wrapper +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + crrTime := time.Now() + expTime := crrTime.Add(ttl) + return domain.NewMetadata("", name, "", "", crrTime, expTime, []domain.Trait{}) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/bidding.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/bidding.gno new file mode 100644 index 00000000..f9e833ec --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/bidding.gno @@ -0,0 +1,452 @@ +package registrar + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/ufmt" + // "gno.land/p/demo/mux" + "gno.land/p/demo/avl" +) + +type bidRecord struct { + DomainName string + Bidder std.Address + HashString string + Price int + StartTime time.Time + EndCommitTime time.Time + EndPriceTime time.Time + CurrentPhase actionCode + IsOpen bool +} + +var ( + BidRec *avl.Tree // bidRecord <- []bidRec + winnerRec *avl.Tree + joinedBid avl.Tree +) + +// joinedBid: address <- []domainName +func recordJoinedBid(domainName string) { + caller := std.GetOrigCaller() + dList := []string{} + data, existed := joinedBid.Get(caller.String()) + if !existed { + dList = []string{domainName} + joinedBid.Set(caller.String(), dList) + return + } + dList = data.([]string) + dList = append(dList, domainName) + joinedBid.Set(caller.String(), dList) + return +} + +// get the joined bids of an account +// want to return both name and status +func GetJoinedBid() []singleStatus { + caller := std.GetOrigCaller().String() + data, existed := joinedBid.Get(caller) + if !existed { + return []singleStatus{} + } + list := data.([]string) + listStatus := []singleStatus{} + for _, dName := range list { + stt := "" + owner := GetOwner(dName) + if owner != "" { + stt = "owned by " + owner.String() + } else { + stt = GetCurrentStatus(dName) + } + singleStt := singleStatus{ + DomainName: dName, + Status: stt, + } + listStatus = append(listStatus, singleStt) + } + return listStatus +} + +// get the next state of bidding session +// XXX note commit price status + if time is expired and not commited yet +// status clear +func GetCurrentStatus(domainName string) string { + // if there is record in joinedBid -> user joined + // check for tine.Now() and startTime + now := time.Now() + caller := std.GetOrigCaller() + // find the record + data, existed := BidRec.Get(domainName) + if !existed { + // no record in bidRec yet -> not commited -> check if user started auction or not - if yes: new auction + if _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister { + return "new auction" + } + return "invalid" + } + + // commited yet + recList := data.([]bidRecord) + rec := recList[0] + + if now.Before(rec.EndCommitTime) { + if rec.HashString != "" { + return "commited hash" + } else { + return "hash" + } + } + + if now.Before(rec.EndPriceTime) && now.After(rec.EndCommitTime) { + return "price" + } + if now.After(rec.EndPriceTime) { + return "closed" + } + return "undefined" +} + +// Render() renders welcome message :D +func Render(path string) string { + return "welcome to varmeta domain name service" +} + +/* + logic should be: + 1. User click register for a dns. + -> 1.1: dns does not exists -> open bidding session + -> 1.2: dns existed -> check if open bidding session is openning or not -> if open -> new bidRecord + 2. Bidding session: + -> 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -> creat bidRecord to add to bidRec + -> 2.2: CommitPrice phase: commit price + secret key: -> if matches -> write price into bidRec ( send the amunt of coin corresponding) + -> 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button + ---> admin end bidding auction + -> 2.3.1: cooldown phase -> waiting for winner -> 2nd winner + -> 2.4: re-transfer coin from admin to user who not win the bid + the flow is: user want to register new domain name -> start new bidding session -> got the bidding session id + if other want to register to the same domain name -> bidding into this session, if not return to new state + when the session ends, need to have 2nd phase called commit -> commit their price and secret to compute the hash + -> compare the hash as in record -> accept the hash -> accept the price + *** temp approach: everytime user commit, user open a bid session if not existed, if existed -> check if this session is ended. +*/ +// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate +// the time whenever there is action to commit. + +// commit the domain name and computed hash string +func CommitHash(domainName, hashString string) string { + caller := std.GetOrigCaller() + ufmt.Println("caller: ", caller.String()) + now := time.Now() + + // update the bid record + data, existed := BidRec.Get(domainName) + + // if not existed -> create new record + if !existed { + // for further getStatus + recordJoinedBid(domainName) + + var bidRec bidRecord + endCommitTime := now.Add(defaultCommitHashTime) + endPriceTime := endCommitTime.Add(defaultCommitPriceTime) + ufmt.Println("endCommitTime: ", endCommitTime.Format(time.RFC3339)) + ufmt.Println("endPriceTime: ", endPriceTime.Format(time.RFC3339)) + bidRec = bidRecord{ + DomainName: domainName, + Bidder: caller, + HashString: hashString, + StartTime: now, + EndCommitTime: endCommitTime, + EndPriceTime: endPriceTime, + IsOpen: true, + } + bidRecList := []bidRecord{bidRec} + BidRec.Set(domainName, bidRecList) + + // charge fee + chargeFee(fee.BidJoinFee, caller) + return "new session" + } + // if existed + // TODO: Check if commit more than 1 time -> done + bidRecList := data.([]bidRecord) + startTime := bidRecList[0].StartTime + if now.After(bidRecList[0].EndCommitTime) { + panic("can not commit hash anymore") + } + for _, bR := range bidRecList { + if bR.Bidder == caller { + panic("you already commited hash") + } + } + + oldEndCommitTime := bidRecList[0].EndCommitTime + oldEndPriceTime := bidRecList[0].EndPriceTime + bidRec := bidRecord{ + DomainName: domainName, + HashString: hashString, + Bidder: caller, + StartTime: startTime, + EndCommitTime: oldEndCommitTime, + EndPriceTime: oldEndPriceTime, + IsOpen: true, + } + bidRecList = append(bidRecList, bidRec) + // Save record + BidRec.Set(domainName, bidRecList) + // charge commit hash fee + chargeFee(fee.BidJoinFee, caller) + return "existed" +} + +// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me +// commit price and secret to reveal auction session +func CommitPrice(price int, secret string, domainName string) string { + // compute the hash string, compare to saved hash string in record + now := time.Now() + joinedString := secret + strconv.Itoa(price) + computedHashString := Get256String(joinedString) + ufmt.Println("computed hash: ", computedHashString) + caller := std.GetOrigCaller() + data, existed := BidRec.Get(domainName) + if !existed { + panic(" domain name is invalid") + } + bidRecList := data.([]bidRecord) + winnerRec.Set(domainName, bidRecList[0]) + + ufmt.Println("time now: ", now.Format(time.RFC3339)) + ufmt.Println("end commit phase time: ", bidRecList[0].EndCommitTime.Format(time.RFC3339)) + // case commit after end - consider panic or not + if now.After(bidRecList[0].EndPriceTime) { + ufmt.Println("commit price phase is ended") + return "ended" + } + // case commit when price phase not started + if time.Now().Before(bidRecList[0].EndCommitTime) { + ufmt.Println("commit price phase is not started yet") + return "not started yet" + } + + // search for the corresponding hash + for _, bidRec := range bidRecList { + // panic because wrong price or wrong secret string + if bidRec.Bidder == caller && bidRec.HashString != computedHashString { + panic("invalid hash string") + } + // found it, update the winner price + if bidRec.Bidder == caller && bidRec.HashString == computedHashString { + data, _ := winnerRec.Get(domainName) + currentWinnerRec := data.(bidRecord) + if price > currentWinnerRec.Price && now.Before(currentWinnerRec.EndPriceTime) { + ufmt.Println("found new winner, setting up") + currentWinnerRec.Price = price + currentWinnerRec.Bidder = bidRec.Bidder + currentWinnerRec.HashString = bidRec.HashString + winnerRec.Set(domainName, currentWinnerRec) + return "claim" + } + } + } + // if not match above case, then panic + panic("commit failed") +} + +func GetCurrentWinner(domainName string) bidRecord { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("no winner yet") + } + return data.(bidRecord) +} + +// find the highest bid in session - incase everyone commited price +func findTheWinner(domainName string) bidRecord { + var winnerBid bidRecord + data, existed := BidRec.Get(domainName) + if !existed { + panic("invalid domain name") + } + bidRecList := data.([]bidRecord) + winnerBid = bidRecList[0] + for _, bidRec := range bidRecList { + if bidRec.Price > winnerBid.Price { + winnerBid = bidRec + } + } + return winnerBid +} + +// register the domain for winner +func registerForWinner(domainName string, winnerRec bidRecord) bool { + winnerAddr := winnerRec.Bidder + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: winnerAddr, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + feeProcess(requestInfo) + return false +} + +// everyone can call EndBid() +// this EndBid checks endTime -> end the auction +func EndBid(domainName string) error { + now := time.Now() + data, existed := BidRec.Get(domainName) + if !existed { + return ufmt.Errorf("endbid: invalid domain name") + } + bidRecList := data.([]bidRecord) + firstBidRec := bidRecList[0] + if now.Before(firstBidRec.EndPriceTime) { + return ufmt.Errorf("endbid: this session can not end before the end time") + } + // change all state + for _, bidRec := range bidRecList { + bidRec.IsOpen = false + } + ok := BidRec.Set(domainName, bidRecList) + if !ok { + return ufmt.Errorf("endbid: can not change bid record state") + } + // need more conditions for findTheWinner() + findTheWinner(domainName) + return nil +} + +// register new domain with bidding process inside - if message is commit hash -> dapp need to call commit hash +// func RegisterDomain(domainName string) bidStatus { +// reqInfo := RequestInfo{ +// WantedDomain: domainName, +// Caller: std.PrevRealm().Addr(), +// Mode: "native", +// } + +// // bidStatus +// result := checkRegisterState(reqInfo) +// recordJoinedBid(domainName) +// // if checkState get the message of which phase this domain name belongs to, then return the status +// return result +// } + +// func checkRegisterState(req RequestInfo) bidStatus { +// var bStt bidStatus +// // check if domain name is regex valid +// if !isValidDomain(req.WantedDomain) { +// panic("invalid domain name") +// } +// // check if dName is registered +// if AlreadyRegistered(req.WantedDomain) { +// panic("domain name already registered") +// } +// // changelogs v2: we are using sealed bidding now +// // check if a bidding session is openning -> append new commit hash into record list +// // both existed or not we open new bidding session +// // for now we return a signal for dapps / service to know what to do next +// isExisted, isOpen := checkBiddingState(req.WantedDomain) +// if isExisted && isOpen { +// // return commit hash signal for dapps +// bStt.CurrentStatus = AppendBiddingSession +// bStt.ActionCode = CommitHashPhase +// return bStt +// } + +// // create new session +// if !isExisted { +// bStt.CurrentStatus = NewBiddingSession +// bStt.ActionCode = CommitHashPhase +// return bStt +// } + +// // not found in register repository but also not found in Bidding Record -> panic("error somewhere :D") +// panic(ufmt.Errorf("should not happend")) +// return bStt +// } + +// // open a bidding session +// func openBiddingSession(domainName string, dur time.Duration) string { +// now := time.Now() +// bidRec := bidRecord{ +// DomainName: domainName, +// StartTime: now, +// } +// bidRecList := []bidRecord{bidRec} +// ok := BidRec.Set(domainName, bidRecList) +// if !ok { +// panic("can not open bidding session") +// } +// return "created bidding session" +// } + +func checkBiddingState(dName string) (isExisted bool, isOpen bool) { + data, existed := BidRec.Get(dName) + if !existed { + isExisted = false + isOpen = false + return + } + isExisted = true + recList := data.([]bidRecord) + if recList[0].IsOpen { + isOpen = true + } else { + isOpen = false + } + return isExisted, isOpen +} + +// get all the price list that joined the bid for displaying in dapp +func GetRecords(dName string) []bidRecord { + data, existed := BidRec.Get(dName) + if !existed { + panic("should not") + } + return data.([]bidRecord) +} + +// chargeFee will charge amount - send from this contract to admin +func chargeFee(amount int64, from std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", amount) + coinsToTransfer := std.NewCoins(ugnotCoin) + coins := checkCoin(from) + ufmt.Println("check balances: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// pay fee and claim the domain name if you are winner +func Claim(domainName string) bool { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("claim: invalid domain name") + } + caller := std.GetOrigCaller() + rec := data.(bidRecord) + if caller != rec.Bidder { + panic("only winner can claim") + } + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: rec.Bidder, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + chargeFee(100, caller) + feeProcess(requestInfo) + return true +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/bidding_model.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/bidding_model.gno new file mode 100644 index 00000000..cede7c19 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/bidding_model.gno @@ -0,0 +1,38 @@ +package registrar + +/* + 0: StatusFailed + CurrentStatus: 1: NewBiddingSession + 2: AppendBiddingSession + 3: BiddingSessionClosed + ActionCode: 1: CommitHashPhase + 2: CommitPricePhase + 3: ClaimPhase... +*/ + +type bidStatus struct { + CurrentStatus currStatus + ActionCode actionCode +} + +type ( + currStatus int + actionCode int +) + +var ( + StatusFailed currStatus = 0 + NewBiddingSession currStatus = 1 + AppendBiddingSession currStatus = 2 + BiddingSessionClosed currStatus = 3 + + ActionFailed actionCode = 0 + CommitHashPhase actionCode = 1 + CommitPricePhase actionCode = 2 + ClaimPhase actionCode = 3 +) + +type singleStatus struct { + DomainName string + Status string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/errors.gno new file mode 100644 index 00000000..a186a711 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/errors.gno @@ -0,0 +1,15 @@ +package registrar + +import ( + "errors" +) + +var ( + ErrUnknown = errors.New("unknow errors") + ErrOK = errors.New("ok") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("ErrInvalidDomainName") + ErrAlreadyRegistered = errors.New("this domain is registered") + ErrCrossRealms = errors.New("cross realms function error") + ErrNotFound = errors.New("domain not found") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee.gno new file mode 100644 index 00000000..9c0eef2f --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee.gno @@ -0,0 +1,36 @@ +package registrar + +import ( + "time" +) + +// only admin can set Fee, other just can read only +type feeInfo struct { + RegisterBaseFee int64 + RenewalFee int64 + RegisterAdditionFee int64 + BidJoinFee int64 +} + +func GetRegisterFee(dName string) int64 { + return fee.RegisterBaseFee +} + +func GetRenewalFee(dName string, amount time.Duration) int64 { + return fee.RenewalFee +} + +// Admin set register fee and renewal fee +func AdminSetFee(regFee int64, renewFee int64) { + // consider logic + assertIsAdmin() + fee.RegisterBaseFee = regFee + fee.RenewalFee = renewFee +} + +// simple err check +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_checks.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_checks.gno new file mode 100644 index 00000000..4b054378 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_checks.gno @@ -0,0 +1,9 @@ +package registrar + +// import ( +// "" +// // "std" +// // "time" +// ) + + diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_native.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_native.gno new file mode 100644 index 00000000..aac57354 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_native.gno @@ -0,0 +1,52 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// admin access only +func AdminWithdraw(amount int64) { + assertIsAdmin() + thisContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + superBanker.SendCoins(thisContract, admin, coinsToTransfer) +} + +func nativeProcess() { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + caller := std.GetOrigCaller() + coins := checkCoin(caller) + ufmt.Println("check: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// RevertTransfer will revert the transaction - send amount of coin to user +func revertTransfer(userAddr std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToReturn := std.NewCoins(ugnotCoin) + ufmt.Println("return coins from contract ", bankerContract.String(), " to ", userAddr.String()) + bankerUser.SendCoins(bankerContract, userAddr, coinsToReturn) +} + +// simple check for admin call +func assertIsAdmin() { + // check if GetCallerAt 2 or 3 when deployed + caller := std.GetCallerAt(3) + err := ufmt.Sprintf("unauthorize with caller: %s\n", caller) + if caller != admin && caller != adminVar { + panic(err) + } +} + +// checking for availble coins +func checkCoin(from std.Address) std.Coins { + // caller := std.GetOrigCaller() + return bankerUser.GetCoins(from) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_token.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_token.gno new file mode 100644 index 00000000..144aa1df --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/fee_token.gno @@ -0,0 +1,25 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + "gno.land/r/varmeta/demo1/domain/vmt" +) + +// expected approved already from client -> transfer from caller to admin +func tokenProcess(dName string, callerStd std.Address) { + caller := pusers.AddressOrName(callerStd.String()) + + now := std.CurrentRealm().Addr() + nowAddr := pusers.AddressOrName(now.String()) + ufmt.Println("current realm transfer: ", now.String()) + callerAllowance := vmt.Allowance(caller, nowAddr) + callerAllowanceString := ufmt.Sprintf("%d", callerAllowance) + ufmt.Println("caller allowance ", callerAllowanceString) + + adminAddr := pusers.AddressOrName(admin.String()) + ufmt.Println("admin: ", admin.String()) + vmt.TransferFrom(caller, adminAddr, 1) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/hashstring.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/hashstring.gno new file mode 100644 index 00000000..674432a1 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/hashstring.gno @@ -0,0 +1,13 @@ +package registrar + +import ( + "crypto/sha256" + "encoding/hex" +) + +func Get256String(input string) string { + data := []byte(input) + hashed := sha256.Sum256(data) + hashedBytes := hashed[:] + return hex.EncodeToString(hashedBytes) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/metadata_wrapper.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/metadata_wrapper.gno new file mode 100644 index 00000000..444a2f47 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/metadata_wrapper.gno @@ -0,0 +1,40 @@ +package registrar + +import ( + "bytes" + "std" + "time" + + "gno.land/p/demo/ufmt" + + "gno.land/p/varmeta/demo/v35/domain" +) + +// Metadata wrapper +// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait) +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + createdAt := time.Now() + expTime := createdAt.Add(ttl) + return domain.NewMetadata("", name, "", "", createdAt, expTime, []domain.Trait{}) +} + +type remapMetadata struct { + Avatar string // avatar - URL or identifier for an avatar image + RegistrationTime string // regtime - The time when the domain was registered + ExpirationTime string // exptime - The time when the domain will be expire + Attributes []domain.Trait // atts - Additional attributes of the domain + Description string // des - A description of the domain + ContactInfo string // contacts - Contact information for the domain owner + RenewalFee string // renewalfee - The fee required to renew the domain, represented as a string +} + +// currently not support for arrays +func (m remapMetadata) MarshalJSON() ([]byte, error) { + json := new(bytes.Buffer) + if m.Attributes == nil { + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, "empty", m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil + } + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/models.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/models.gno new file mode 100644 index 00000000..fe15a6ba --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/models.gno @@ -0,0 +1,22 @@ +package registrar + +import ( + "std" +) + +type RequestInfo struct { + Mode string + WantedDomain string + Caller std.Address + TransInfo TransferInfo + // xxx extendTime, renew... +} +type TransferInfo struct { + From std.Address + To std.Address +} +type ExecuteResult struct { + Success bool + ResultDetails error + Message string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/prestep.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/prestep.gno new file mode 100644 index 00000000..1694525e --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/prestep.gno @@ -0,0 +1,41 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v35/domain" +) + +var ( + domainStorage *avl.Tree // domainName -> std.Address + rootRegistry domain.DomainRegistry + + // fee + superBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction + bankerUser std.Banker // full access to coins sent with the transaction that called the banker + + admin std.Address // admin + adminVar std.Address // admin in server + fee feeInfo +) + +func init() { + domainStorage = avl.NewTree() + BidRec = avl.NewTree() + winnerRec = avl.NewTree() + rootRegistry = domain.NewDomainRegistry("Varmeta", "vmt") + + // fee init + admin = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" //@thinhnx + adminVar = "g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx" //@varmeta-sponsorkey + // ugnot + fee = feeInfo{ + RegisterBaseFee: 100, + RenewalFee: 100, + RegisterAdditionFee: 0, + BidJoinFee: 100, + } + superBanker = std.GetBanker(std.BankerTypeRealmSend) + bankerUser = std.GetBanker(std.BankerTypeOrigSend) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/registrar.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/registrar.gno new file mode 100644 index 00000000..ec342632 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/registrar.gno @@ -0,0 +1,152 @@ +/* +This package contains functions that will actually execute the request from user +Features: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion... +*/ +// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm. +// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session + +// currently we dont using too much panic because we dont have defer functions to revert the state of storage +package registrar + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v35/domain" +) + +// XXX: consider using panic instead of return string or errors +func Register(domainName string, mode string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: mode, + } + + regResult := executeRegister(requestInfo) + + // calling panic to stop paying fee + if !regResult.Success { + panic(regResult.ResultDetails.Error()) + } + // pay fee with panic inside + feeProcess(requestInfo) + return "Register Done" +} + +func executeRegister(req RequestInfo) ExecuteResult { + // check if domain name is regex valid + var execRes ExecuteResult + if !isValidDomain(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrInvalidDomainName + return execRes + } + + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrAlreadyRegistered + return execRes + } + + // execute register domain - mint the nft + // changelogs v2: we are using sealed bidding now + + caller := req.Caller + ttl := defaultExpireTime + metadata := metadataWrapper(caller, req.WantedDomain, ttl) + // create a new registry instance to save metadata and mint the NFT + errRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl) + if errRegister != nil { + execRes.Success = false + execRes.ResultDetails = ErrCrossRealms + return execRes + } + // now save caller to corressponding tree to manage + domainStorage.Set(req.WantedDomain, caller) + + execRes.Success = true + return execRes +} + +func feeProcess(req RequestInfo) { + if req.Mode == "token" { + tokenProcess(req.WantedDomain, req.Caller) + } else { + nativeProcess() + } +} + +func AlreadyRegistered(domainName string) bool { + // if can get owner -> existed + addr, err := rootRegistry.OwnerOf(domainName) + if err == nil && addr != "" { + return true + } + return false +} + +func GetOwner(domainName string) std.Address { + vl, existed := domainStorage.Get(domainName) + if !existed { + return "" + } + return vl.(std.Address) +} + +func Search(domainName string) (remapMetadata, string) { + validMetadata := remapMetadata{} + md, err := getMetadata(domainName) + if err != nil { + // return validMetadata, err.Error() + panic(err) + } + validMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339) + validMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339) + // jsonData, _ := validMetadata.MarshalJSON() + return validMetadata, "Search Success" +} + +func getMetadata(wantedDomain string) (domain.Metadata, error) { + // confirm the method? -> get all the fields if the fields slice is empty + metadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{}) + if err != nil { + return metadata, err + } + return metadata, nil +} + +// Transfer +func TransferDomain(from, to, domainName string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + } + if err := excuteTransfer(requestInfo); err != "" { + panic(err) + } + return "Transfer Done" +} + +func excuteTransfer(req RequestInfo) string { + if !AlreadyRegistered(req.WantedDomain) { + return ErrAlreadyRegistered.Error() + } + rootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain) + return "" +} + +func GetDomainName(addr string) []string { + domainList := []string{} + // search from local storage + domainStorage.Iterate("", "", func(key string, value interface{}) bool { + caller := value.(std.Address) + // not checking isExpired + if caller.String() == addr { + domainList = append(domainList, key) + } + return false + }) + return domainList +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/registrar_test.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/registrar_test.gno new file mode 100644 index 00000000..df136440 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/registrar_test.gno @@ -0,0 +1,25 @@ +package registrar + +// import ( +// "fmt" +// "std" +// "testing" +// ) + +// func TestRegisterDomain(t *testing.T) { +// tcs := []struct { +// input string +// expected string +// }{ +// {"thinhnx", "Register done"}, +// } +// for tc := range tcs { +// name := tc.input +// t.Run(name, func(t *testing.T) { +// output := Register(tc.input) +// if output != tc.expected { +// t.Errorf("Expected '%q, but got %q", tc.expected, output) +// } +// }) +// } +// } diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/utils.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/utils.gno new file mode 100644 index 00000000..49578643 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/registrar/utils.gno @@ -0,0 +1,39 @@ +/* +This check module contains function to do the checking stuffs +*/ +package registrar + +import ( + "regexp" + "time" +) + +var ( + defaultCommitHashTime = time.Second * 60 + defaultCommitPriceTime = time.Second * 60 + defaultExpireTime = time.Hour // 30 days + reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) +) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func GetExpirationDate(dName string) time.Time { + return rootRegistry.GetExpirationDate(dName) +} + +// for now, this function only let admin set +func SetExpirationDate(dName string, expDate time.Time) bool { + assertIsAdmin() + return rootRegistry.SetExpirationDate(dName, expDate) +} + +func SetCommitPhaseTime(duration int) { + defaultCommitHashTime = time.Duration(duration) * time.Second +} + +func SetCommitPriceTime(duration int) { + defaultCommitPriceTime = time.Duration(duration) * time.Second +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/checks_resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/checks_resolver.gno new file mode 100644 index 00000000..23f87613 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/checks_resolver.gno @@ -0,0 +1,27 @@ +/* +This check module contains function to do the checking stuffs +*/ +package resolver + +import ( + "regexp" + "time" + + "gno.land/r/varmeta/demo/v35/domain/registrar" +) + +// const ( +// admin std.Address = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" // -> @thinhnx +// ) + +var reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func isExpired(dName string) bool { + expDate := registrar.GetExpirationDate(dName) + return expDate.Before(time.Now()) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/errors.gno new file mode 100644 index 00000000..19709079 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/errors.gno @@ -0,0 +1,11 @@ +package resolver + +import ( + "errors" +) + +var ( + ErrNotFound = errors.New("not found") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("invalid domain name to register") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/resolver.gno new file mode 100644 index 00000000..cfb9a09d --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/resolver.gno @@ -0,0 +1,63 @@ +/* +The goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner. +It serves the purpose of "resolving" the ICNS Name +to the correct address (e.g "alice.gno" -> g1xxx). +*/ +// changelogs: move Register feature into this resolver package +// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result + +package resolver + +import ( + "std" + + "gno.land/r/varmeta/demo/v35/domain/registrar" +) + +type Record struct { + Owner std.Address + IsValid bool + Memo string // no more need this + Priority int +} + +// retrieve the record list to get the onchain address +func Resolve(domainName string) *Record { + if !isValidDomain(domainName) { + panic("bad domain name") + } + record := &Record{} + + owner := getOwnerFromDomainStorage(domainName) + if owner == "" { + record.Memo = "not found" + record.IsValid = false + return record + } + + if !isExpired(domainName) { + record.IsValid = true + record.Owner = owner + } else { + record.IsValid = false + } + return record +} + +func GetDomainName(addr string) []string { + return registrar.GetDomainName(addr) +} + +/* +If query in local storage not found +Query to DomainStorage by domainName -> get the registry -> use that registry to get the Owner() +and check the validation time? +*/ + +func existedInDomainStorage(domainName string) bool { + return registrar.AlreadyRegistered(domainName) +} + +func getOwnerFromDomainStorage(domainName string) std.Address { + return registrar.GetOwner(domainName) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/resolver_metadata.gno b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/resolver_metadata.gno new file mode 100644 index 00000000..474cfc57 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v35/domain/resolver/resolver_metadata.gno @@ -0,0 +1,15 @@ +package resolver + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v35/domain" +) + +// Metadata wrapper +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + crrTime := time.Now() + expTime := crrTime.Add(ttl) + return domain.NewMetadata("", name, "", "", crrTime, expTime, []domain.Trait{}) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/bidding.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/bidding.gno new file mode 100644 index 00000000..e7c24cde --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/bidding.gno @@ -0,0 +1,450 @@ +package registrar + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/ufmt" + // "gno.land/p/demo/mux" + "gno.land/p/demo/avl" +) + +type bidRecord struct { + DomainName string + Bidder std.Address + HashString string + Price int + StartTime time.Time + EndCommitTime time.Time + EndPriceTime time.Time + CurrentPhase actionCode + IsOpen bool +} + +var ( + BidRec *avl.Tree // bidRecord <- []bidRec + winnerRec *avl.Tree + joinedBid avl.Tree +) + +// joinedBid: address <- []domainName +func recordJoinedBid(domainName string) { + caller := std.GetOrigCaller() + dList := []string{} + data, existed := joinedBid.Get(caller.String()) + if !existed { + dList = []string{domainName} + joinedBid.Set(caller.String(), dList) + return + } + dList = data.([]string) + dList = append(dList, domainName) + joinedBid.Set(caller.String(), dList) + return +} + +// get the joined bids of an account +// want to return both name and status +func GetJoinedBid() []singleStatus { + caller := std.GetOrigCaller().String() + data, existed := joinedBid.Get(caller) + if !existed { + return []singleStatus{} + } + list := data.([]string) + listStatus := []singleStatus{} + for _, dName := range list { + stt := GetCurrentStatus(dName) + singleStt := singleStatus{ + DomainName: dName, + Status: stt, + } + listStatus = append(listStatus, singleStt) + } + return listStatus +} + +// get the next state of bidding session +// XXX note commit price status + if time is expired and not commited yet +// status clear +func GetCurrentStatus(domainName string) string { + // if there is record in joinedBid -> user joined + // check for tine.Now() and startTime + now := time.Now() + owner := GetOwner(domainName) + if owner != "" { + return "owned by " + owner.String() + } + caller := std.GetOrigCaller() + // find the record + data, existed := BidRec.Get(domainName) + if !existed { + // no record in bidRec yet -> not commited -> check if user started auction or not - if yes: new auction + if _, existedInRegister := joinedBid.Get(caller.String()); existedInRegister { + return "new auction" + } + return "domain name is free" + } + + // commited yet + recList := data.([]bidRecord) + rec := recList[0] + + if now.Before(rec.EndCommitTime) { + if rec.HashString != "" { + return "commited hash" + } else { + return "hash" + } + } + + if now.Before(rec.EndPriceTime) && now.After(rec.EndCommitTime) { + return "price" + } + if now.After(rec.EndPriceTime) { + return "closed" + } + return "undefined" +} + +// Render() renders welcome message :D +func Render(path string) string { + return "welcome to varmeta domain name service" +} + +/* + logic should be: + 1. User click register for a dns. + -> 1.1: dns does not exists -> open bidding session + -> 1.2: dns existed -> check if open bidding session is openning or not -> if open -> new bidRecord + 2. Bidding session: + -> 2.1: CommitHash phase: commit hashed string (price || secret string) + send joining fee -> creat bidRecord to add to bidRec + -> 2.2: CommitPrice phase: commit price + secret key: -> if matches -> write price into bidRec ( send the amunt of coin corresponding) + -> 2.3(?): after CommitPrice phase done, get the winner and register domain for winner by a `Claim` button + ---> admin end bidding auction + -> 2.3.1: cooldown phase -> waiting for winner -> 2nd winner + -> 2.4: re-transfer coin from admin to user who not win the bid + the flow is: user want to register new domain name -> start new bidding session -> got the bidding session id + if other want to register to the same domain name -> bidding into this session, if not return to new state + when the session ends, need to have 2nd phase called commit -> commit their price and secret to compute the hash + -> compare the hash as in record -> accept the hash -> accept the price + *** temp approach: everytime user commit, user open a bid session if not existed, if existed -> check if this session is ended. +*/ +// in commit hash phase, we calcute the time stamp of phase 1, phase 2. by this, we not need to recalculate +// the time whenever there is action to commit. + +// commit the domain name and computed hash string +func CommitHash(domainName, hashString string) string { + caller := std.GetOrigCaller() + ufmt.Println("caller: ", caller.String()) + now := time.Now() + + // update the bid record + data, existed := BidRec.Get(domainName) + + // if not existed -> create new record + if !existed { + // for further getStatus + recordJoinedBid(domainName) + + var bidRec bidRecord + endCommitTime := now.Add(defaultCommitHashTime) + endPriceTime := endCommitTime.Add(defaultCommitPriceTime) + ufmt.Println("endCommitTime: ", endCommitTime.Format(time.RFC3339)) + ufmt.Println("endPriceTime: ", endPriceTime.Format(time.RFC3339)) + bidRec = bidRecord{ + DomainName: domainName, + Bidder: caller, + HashString: hashString, + StartTime: now, + EndCommitTime: endCommitTime, + EndPriceTime: endPriceTime, + IsOpen: true, + } + bidRecList := []bidRecord{bidRec} + BidRec.Set(domainName, bidRecList) + + // charge fee + chargeFee(fee.BidJoinFee, caller) + return "new session" + } + // if existed + // TODO: Check if commit more than 1 time -> done + bidRecList := data.([]bidRecord) + startTime := bidRecList[0].StartTime + if now.After(bidRecList[0].EndCommitTime) { + panic("can not commit hash anymore") + } + for _, bR := range bidRecList { + if bR.Bidder == caller { + panic("you already commited hash") + } + } + + oldEndCommitTime := bidRecList[0].EndCommitTime + oldEndPriceTime := bidRecList[0].EndPriceTime + bidRec := bidRecord{ + DomainName: domainName, + HashString: hashString, + Bidder: caller, + StartTime: startTime, + EndCommitTime: oldEndCommitTime, + EndPriceTime: oldEndPriceTime, + IsOpen: true, + } + bidRecList = append(bidRecList, bidRec) + // Save record + BidRec.Set(domainName, bidRecList) + // charge commit hash fee + chargeFee(fee.BidJoinFee, caller) + return "existed" +} + +// for now we dont use panic because this will cause the permanent time.Now() stuck. IDK why // XXX fix me +// commit price and secret to reveal auction session +func CommitPrice(price int, secret string, domainName string) string { + // compute the hash string, compare to saved hash string in record + now := time.Now() + joinedString := secret + strconv.Itoa(price) + computedHashString := Get256String(joinedString) + ufmt.Println("computed hash: ", computedHashString) + caller := std.GetOrigCaller() + data, existed := BidRec.Get(domainName) + if !existed { + panic(" domain name is invalid") + } + bidRecList := data.([]bidRecord) + winnerRec.Set(domainName, bidRecList[0]) + + ufmt.Println("time now: ", now.Format(time.RFC3339)) + ufmt.Println("end commit phase time: ", bidRecList[0].EndCommitTime.Format(time.RFC3339)) + // case commit after end - consider panic or not + if now.After(bidRecList[0].EndPriceTime) { + ufmt.Println("commit price phase is ended") + return "ended" + } + // case commit when price phase not started + if time.Now().Before(bidRecList[0].EndCommitTime) { + ufmt.Println("commit price phase is not started yet") + return "not started yet" + } + + // search for the corresponding hash + for _, bidRec := range bidRecList { + // panic because wrong price or wrong secret string + if bidRec.Bidder == caller && bidRec.HashString != computedHashString { + panic("invalid hash string") + } + // found it, update the winner price + if bidRec.Bidder == caller && bidRec.HashString == computedHashString { + data, _ := winnerRec.Get(domainName) + currentWinnerRec := data.(bidRecord) + if price > currentWinnerRec.Price && now.Before(currentWinnerRec.EndPriceTime) { + ufmt.Println("found new winner, setting up") + currentWinnerRec.Price = price + currentWinnerRec.Bidder = bidRec.Bidder + currentWinnerRec.HashString = bidRec.HashString + winnerRec.Set(domainName, currentWinnerRec) + return "claim" + } + } + } + // if not match above case, then panic + panic("commit failed") +} + +func GetCurrentWinner(domainName string) bidRecord { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("no winner yet") + } + return data.(bidRecord) +} + +// find the highest bid in session - incase everyone commited price +func findTheWinner(domainName string) bidRecord { + var winnerBid bidRecord + data, existed := BidRec.Get(domainName) + if !existed { + panic("invalid domain name") + } + bidRecList := data.([]bidRecord) + winnerBid = bidRecList[0] + for _, bidRec := range bidRecList { + if bidRec.Price > winnerBid.Price { + winnerBid = bidRec + } + } + return winnerBid +} + +// register the domain for winner +func registerForWinner(domainName string, winnerRec bidRecord) bool { + winnerAddr := winnerRec.Bidder + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: winnerAddr, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + feeProcess(requestInfo) + return false +} + +// everyone can call EndBid() +// this EndBid checks endTime -> end the auction +func EndBid(domainName string) error { + now := time.Now() + data, existed := BidRec.Get(domainName) + if !existed { + return ufmt.Errorf("endbid: invalid domain name") + } + bidRecList := data.([]bidRecord) + firstBidRec := bidRecList[0] + if now.Before(firstBidRec.EndPriceTime) { + return ufmt.Errorf("endbid: this session can not end before the end time") + } + // change all state + for _, bidRec := range bidRecList { + bidRec.IsOpen = false + } + ok := BidRec.Set(domainName, bidRecList) + if !ok { + return ufmt.Errorf("endbid: can not change bid record state") + } + // need more conditions for findTheWinner() + findTheWinner(domainName) + return nil +} + +// register new domain with bidding process inside - if message is commit hash -> dapp need to call commit hash +// func RegisterDomain(domainName string) bidStatus { +// reqInfo := RequestInfo{ +// WantedDomain: domainName, +// Caller: std.PrevRealm().Addr(), +// Mode: "native", +// } + +// // bidStatus +// result := checkRegisterState(reqInfo) +// recordJoinedBid(domainName) +// // if checkState get the message of which phase this domain name belongs to, then return the status +// return result +// } + +// func checkRegisterState(req RequestInfo) bidStatus { +// var bStt bidStatus +// // check if domain name is regex valid +// if !isValidDomain(req.WantedDomain) { +// panic("invalid domain name") +// } +// // check if dName is registered +// if AlreadyRegistered(req.WantedDomain) { +// panic("domain name already registered") +// } +// // changelogs v2: we are using sealed bidding now +// // check if a bidding session is openning -> append new commit hash into record list +// // both existed or not we open new bidding session +// // for now we return a signal for dapps / service to know what to do next +// isExisted, isOpen := checkBiddingState(req.WantedDomain) +// if isExisted && isOpen { +// // return commit hash signal for dapps +// bStt.CurrentStatus = AppendBiddingSession +// bStt.ActionCode = CommitHashPhase +// return bStt +// } + +// // create new session +// if !isExisted { +// bStt.CurrentStatus = NewBiddingSession +// bStt.ActionCode = CommitHashPhase +// return bStt +// } + +// // not found in register repository but also not found in Bidding Record -> panic("error somewhere :D") +// panic(ufmt.Errorf("should not happend")) +// return bStt +// } + +// // open a bidding session +// func openBiddingSession(domainName string, dur time.Duration) string { +// now := time.Now() +// bidRec := bidRecord{ +// DomainName: domainName, +// StartTime: now, +// } +// bidRecList := []bidRecord{bidRec} +// ok := BidRec.Set(domainName, bidRecList) +// if !ok { +// panic("can not open bidding session") +// } +// return "created bidding session" +// } + +func checkBiddingState(dName string) (isExisted bool, isOpen bool) { + data, existed := BidRec.Get(dName) + if !existed { + isExisted = false + isOpen = false + return + } + isExisted = true + recList := data.([]bidRecord) + if recList[0].IsOpen { + isOpen = true + } else { + isOpen = false + } + return isExisted, isOpen +} + +// get all the price list that joined the bid for displaying in dapp +func GetRecords(dName string) []bidRecord { + data, existed := BidRec.Get(dName) + if !existed { + panic("should not") + } + return data.([]bidRecord) +} + +// chargeFee will charge amount - send from this contract to admin +func chargeFee(amount int64, from std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", amount) + coinsToTransfer := std.NewCoins(ugnotCoin) + coins := checkCoin(from) + ufmt.Println("check balances: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// pay fee and claim the domain name if you are winner +func Claim(domainName string) bool { + data, existed := winnerRec.Get(domainName) + if !existed { + panic("claim: invalid domain name") + } + caller := std.GetOrigCaller() + rec := data.(bidRecord) + if caller != rec.Bidder { + panic("only winner can claim") + } + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: rec.Bidder, + Mode: "native", + } + result := executeRegister(requestInfo) + if !result.Success { + panic(result.ResultDetails.Error()) + } + // register done. Now charge the fee + chargeFee(100, caller) + feeProcess(requestInfo) + return true +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/bidding_model.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/bidding_model.gno new file mode 100644 index 00000000..cede7c19 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/bidding_model.gno @@ -0,0 +1,38 @@ +package registrar + +/* + 0: StatusFailed + CurrentStatus: 1: NewBiddingSession + 2: AppendBiddingSession + 3: BiddingSessionClosed + ActionCode: 1: CommitHashPhase + 2: CommitPricePhase + 3: ClaimPhase... +*/ + +type bidStatus struct { + CurrentStatus currStatus + ActionCode actionCode +} + +type ( + currStatus int + actionCode int +) + +var ( + StatusFailed currStatus = 0 + NewBiddingSession currStatus = 1 + AppendBiddingSession currStatus = 2 + BiddingSessionClosed currStatus = 3 + + ActionFailed actionCode = 0 + CommitHashPhase actionCode = 1 + CommitPricePhase actionCode = 2 + ClaimPhase actionCode = 3 +) + +type singleStatus struct { + DomainName string + Status string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/errors.gno new file mode 100644 index 00000000..a186a711 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/errors.gno @@ -0,0 +1,15 @@ +package registrar + +import ( + "errors" +) + +var ( + ErrUnknown = errors.New("unknow errors") + ErrOK = errors.New("ok") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("ErrInvalidDomainName") + ErrAlreadyRegistered = errors.New("this domain is registered") + ErrCrossRealms = errors.New("cross realms function error") + ErrNotFound = errors.New("domain not found") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee.gno new file mode 100644 index 00000000..9c0eef2f --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee.gno @@ -0,0 +1,36 @@ +package registrar + +import ( + "time" +) + +// only admin can set Fee, other just can read only +type feeInfo struct { + RegisterBaseFee int64 + RenewalFee int64 + RegisterAdditionFee int64 + BidJoinFee int64 +} + +func GetRegisterFee(dName string) int64 { + return fee.RegisterBaseFee +} + +func GetRenewalFee(dName string, amount time.Duration) int64 { + return fee.RenewalFee +} + +// Admin set register fee and renewal fee +func AdminSetFee(regFee int64, renewFee int64) { + // consider logic + assertIsAdmin() + fee.RegisterBaseFee = regFee + fee.RenewalFee = renewFee +} + +// simple err check +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_checks.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_checks.gno new file mode 100644 index 00000000..4b054378 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_checks.gno @@ -0,0 +1,9 @@ +package registrar + +// import ( +// "" +// // "std" +// // "time" +// ) + + diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_native.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_native.gno new file mode 100644 index 00000000..aac57354 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_native.gno @@ -0,0 +1,52 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" +) + +// admin access only +func AdminWithdraw(amount int64) { + assertIsAdmin() + thisContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + superBanker.SendCoins(thisContract, admin, coinsToTransfer) +} + +func nativeProcess() { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToTransfer := std.NewCoins(ugnotCoin) + caller := std.GetOrigCaller() + coins := checkCoin(caller) + ufmt.Println("check: ", coins) + ufmt.Println("send from contract ", bankerContract.String(), " to admin ", admin.String(), " amount: ", ugnotCoin) + bankerUser.SendCoins(bankerContract, admin, coinsToTransfer) +} + +// RevertTransfer will revert the transaction - send amount of coin to user +func revertTransfer(userAddr std.Address) { + bankerContract := std.CurrentRealm().Addr() + ugnotCoin := std.NewCoin("ugnot", fee.RegisterBaseFee) + coinsToReturn := std.NewCoins(ugnotCoin) + ufmt.Println("return coins from contract ", bankerContract.String(), " to ", userAddr.String()) + bankerUser.SendCoins(bankerContract, userAddr, coinsToReturn) +} + +// simple check for admin call +func assertIsAdmin() { + // check if GetCallerAt 2 or 3 when deployed + caller := std.GetCallerAt(3) + err := ufmt.Sprintf("unauthorize with caller: %s\n", caller) + if caller != admin && caller != adminVar { + panic(err) + } +} + +// checking for availble coins +func checkCoin(from std.Address) std.Coins { + // caller := std.GetOrigCaller() + return bankerUser.GetCoins(from) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_token.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_token.gno new file mode 100644 index 00000000..144aa1df --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/fee_token.gno @@ -0,0 +1,25 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/ufmt" + pusers "gno.land/p/demo/users" + "gno.land/r/varmeta/demo1/domain/vmt" +) + +// expected approved already from client -> transfer from caller to admin +func tokenProcess(dName string, callerStd std.Address) { + caller := pusers.AddressOrName(callerStd.String()) + + now := std.CurrentRealm().Addr() + nowAddr := pusers.AddressOrName(now.String()) + ufmt.Println("current realm transfer: ", now.String()) + callerAllowance := vmt.Allowance(caller, nowAddr) + callerAllowanceString := ufmt.Sprintf("%d", callerAllowance) + ufmt.Println("caller allowance ", callerAllowanceString) + + adminAddr := pusers.AddressOrName(admin.String()) + ufmt.Println("admin: ", admin.String()) + vmt.TransferFrom(caller, adminAddr, 1) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/hashstring.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/hashstring.gno new file mode 100644 index 00000000..674432a1 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/hashstring.gno @@ -0,0 +1,13 @@ +package registrar + +import ( + "crypto/sha256" + "encoding/hex" +) + +func Get256String(input string) string { + data := []byte(input) + hashed := sha256.Sum256(data) + hashedBytes := hashed[:] + return hex.EncodeToString(hashedBytes) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/metadata_wrapper.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/metadata_wrapper.gno new file mode 100644 index 00000000..2823ef1c --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/metadata_wrapper.gno @@ -0,0 +1,40 @@ +package registrar + +import ( + "bytes" + "std" + "time" + + "gno.land/p/demo/ufmt" + + "gno.land/p/varmeta/demo/v36/domain" +) + +// Metadata wrapper +// func NewMetadata(avatar, description, contactInfo, renewalFee string, registrationTime, expirationTime time.Time, attributes []Trait) +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + createdAt := time.Now() + expTime := createdAt.Add(ttl) + return domain.NewMetadata("", name, "", "", createdAt, expTime, []domain.Trait{}) +} + +type remapMetadata struct { + Avatar string // avatar - URL or identifier for an avatar image + RegistrationTime string // regtime - The time when the domain was registered + ExpirationTime string // exptime - The time when the domain will be expire + Attributes []domain.Trait // atts - Additional attributes of the domain + Description string // des - A description of the domain + ContactInfo string // contacts - Contact information for the domain owner + RenewalFee string // renewalfee - The fee required to renew the domain, represented as a string +} + +// currently not support for arrays +func (m remapMetadata) MarshalJSON() ([]byte, error) { + json := new(bytes.Buffer) + if m.Attributes == nil { + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, "empty", m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil + } + json.WriteString(ufmt.Sprintf(`{"avatar": %s, "regtime": %s, "exptime": %s, "atts": %s, "des": %s, "contacts": %s, "renewalfee": %s}`, m.Avatar, m.RegistrationTime, m.ExpirationTime, m.Attributes[0], m.Description, m.ContactInfo, m.RenewalFee)) + return json.Bytes(), nil +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/models.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/models.gno new file mode 100644 index 00000000..fe15a6ba --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/models.gno @@ -0,0 +1,22 @@ +package registrar + +import ( + "std" +) + +type RequestInfo struct { + Mode string + WantedDomain string + Caller std.Address + TransInfo TransferInfo + // xxx extendTime, renew... +} +type TransferInfo struct { + From std.Address + To std.Address +} +type ExecuteResult struct { + Success bool + ResultDetails error + Message string +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/prestep.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/prestep.gno new file mode 100644 index 00000000..9eea7566 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/prestep.gno @@ -0,0 +1,41 @@ +package registrar + +import ( + "std" + + "gno.land/p/demo/avl" + "gno.land/p/varmeta/demo/v36/domain" +) + +var ( + domainStorage *avl.Tree // domainName -> std.Address + rootRegistry domain.DomainRegistry + + // fee + superBanker std.Banker // full access to coins that the realm itself owns, including the ones sent with the transaction + bankerUser std.Banker // full access to coins sent with the transaction that called the banker + + admin std.Address // admin + adminVar std.Address // admin in server + fee feeInfo +) + +func init() { + domainStorage = avl.NewTree() + BidRec = avl.NewTree() + winnerRec = avl.NewTree() + rootRegistry = domain.NewDomainRegistry("Varmeta", "vmt") + + // fee init + admin = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" //@thinhnx + adminVar = "g1p3ylc5w42lrt5345eh7h5l9gcd7qpeyvcl5qjx" //@varmeta-sponsorkey + // ugnot + fee = feeInfo{ + RegisterBaseFee: 100, + RenewalFee: 100, + RegisterAdditionFee: 0, + BidJoinFee: 100, + } + superBanker = std.GetBanker(std.BankerTypeRealmSend) + bankerUser = std.GetBanker(std.BankerTypeOrigSend) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/registrar.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/registrar.gno new file mode 100644 index 00000000..86a7b5cd --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/registrar.gno @@ -0,0 +1,152 @@ +/* +This package contains functions that will actually execute the request from user +Features: Domain Registration, Domain Renewal, Domain Transfer, Domain Deletion... +*/ +// changelogs 1: move fee mgnt to registrar module, in oder to manage the coins sent from user to realm. +// changelogs 2: v2 - added sealed bidding logic - with default time xxx mins for each session + +// currently we dont using too much panic because we dont have defer functions to revert the state of storage +package registrar + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v36/domain" +) + +// XXX: consider using panic instead of return string or errors +func Register(domainName string, mode string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + Mode: mode, + } + + regResult := executeRegister(requestInfo) + + // calling panic to stop paying fee + if !regResult.Success { + panic(regResult.ResultDetails.Error()) + } + // pay fee with panic inside + feeProcess(requestInfo) + return "Register Done" +} + +func executeRegister(req RequestInfo) ExecuteResult { + // check if domain name is regex valid + var execRes ExecuteResult + if !isValidDomain(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrInvalidDomainName + return execRes + } + + // check if dName is registered + if AlreadyRegistered(req.WantedDomain) { + execRes.Success = false + execRes.ResultDetails = ErrAlreadyRegistered + return execRes + } + + // execute register domain - mint the nft + // changelogs v2: we are using sealed bidding now + + caller := req.Caller + ttl := defaultExpireTime + metadata := metadataWrapper(caller, req.WantedDomain, ttl) + // create a new registry instance to save metadata and mint the NFT + errRegister := rootRegistry.RegisterDomain(caller, req.WantedDomain, metadata, ttl) + if errRegister != nil { + execRes.Success = false + execRes.ResultDetails = ErrCrossRealms + return execRes + } + // now save caller to corressponding tree to manage + domainStorage.Set(req.WantedDomain, caller) + + execRes.Success = true + return execRes +} + +func feeProcess(req RequestInfo) { + if req.Mode == "token" { + tokenProcess(req.WantedDomain, req.Caller) + } else { + nativeProcess() + } +} + +func AlreadyRegistered(domainName string) bool { + // if can get owner -> existed + addr, err := rootRegistry.OwnerOf(domainName) + if err == nil && addr != "" { + return true + } + return false +} + +func GetOwner(domainName string) std.Address { + vl, existed := domainStorage.Get(domainName) + if !existed { + return "" + } + return vl.(std.Address) +} + +func Search(domainName string) (remapMetadata, string) { + validMetadata := remapMetadata{} + md, err := getMetadata(domainName) + if err != nil { + // return validMetadata, err.Error() + panic(err) + } + validMetadata.RegistrationTime = md.RegistrationTime.Format(time.RFC3339) + validMetadata.ExpirationTime = md.ExpirationTime.Format(time.RFC3339) + // jsonData, _ := validMetadata.MarshalJSON() + return validMetadata, "Search Success" +} + +func getMetadata(wantedDomain string) (domain.Metadata, error) { + // confirm the method? -> get all the fields if the fields slice is empty + metadata, err := rootRegistry.GetDomainFields(wantedDomain, []domain.MetadataField{}) + if err != nil { + return metadata, err + } + return metadata, nil +} + +// Transfer +func TransferDomain(from, to, domainName string) string { + requestInfo := RequestInfo{ + WantedDomain: domainName, + Caller: std.PrevRealm().Addr(), + } + if err := excuteTransfer(requestInfo); err != "" { + panic(err) + } + return "Transfer Done" +} + +func excuteTransfer(req RequestInfo) string { + if !AlreadyRegistered(req.WantedDomain) { + return ErrAlreadyRegistered.Error() + } + rootRegistry.TransferFrom(req.TransInfo.From, req.TransInfo.To, req.WantedDomain) + return "" +} + +func GetDomainName(addr string) []string { + domainList := []string{} + // search from local storage + domainStorage.Iterate("", "", func(key string, value interface{}) bool { + caller := value.(std.Address) + // not checking isExpired + if caller.String() == addr { + domainList = append(domainList, key) + } + return false + }) + return domainList +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/registrar_test.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/registrar_test.gno new file mode 100644 index 00000000..df136440 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/registrar_test.gno @@ -0,0 +1,25 @@ +package registrar + +// import ( +// "fmt" +// "std" +// "testing" +// ) + +// func TestRegisterDomain(t *testing.T) { +// tcs := []struct { +// input string +// expected string +// }{ +// {"thinhnx", "Register done"}, +// } +// for tc := range tcs { +// name := tc.input +// t.Run(name, func(t *testing.T) { +// output := Register(tc.input) +// if output != tc.expected { +// t.Errorf("Expected '%q, but got %q", tc.expected, output) +// } +// }) +// } +// } diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/utils.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/utils.gno new file mode 100644 index 00000000..49578643 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/registrar/utils.gno @@ -0,0 +1,39 @@ +/* +This check module contains function to do the checking stuffs +*/ +package registrar + +import ( + "regexp" + "time" +) + +var ( + defaultCommitHashTime = time.Second * 60 + defaultCommitPriceTime = time.Second * 60 + defaultExpireTime = time.Hour // 30 days + reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) +) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func GetExpirationDate(dName string) time.Time { + return rootRegistry.GetExpirationDate(dName) +} + +// for now, this function only let admin set +func SetExpirationDate(dName string, expDate time.Time) bool { + assertIsAdmin() + return rootRegistry.SetExpirationDate(dName, expDate) +} + +func SetCommitPhaseTime(duration int) { + defaultCommitHashTime = time.Duration(duration) * time.Second +} + +func SetCommitPriceTime(duration int) { + defaultCommitPriceTime = time.Duration(duration) * time.Second +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/checks_resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/checks_resolver.gno new file mode 100644 index 00000000..e5df77f9 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/checks_resolver.gno @@ -0,0 +1,27 @@ +/* +This check module contains function to do the checking stuffs +*/ +package resolver + +import ( + "regexp" + "time" + + "gno.land/r/varmeta/demo/v36/domain/registrar" +) + +// const ( +// admin std.Address = "g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9" // -> @thinhnx +// ) + +var reName = regexp.MustCompile(`^[a-zA-Z0-9]{1,124}\.gno$`) + +// check for registering process +func isValidDomain(d string) bool { + return reName.MatchString(d) +} + +func isExpired(dName string) bool { + expDate := registrar.GetExpirationDate(dName) + return expDate.Before(time.Now()) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/errors.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/errors.gno new file mode 100644 index 00000000..19709079 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/errors.gno @@ -0,0 +1,11 @@ +package resolver + +import ( + "errors" +) + +var ( + ErrNotFound = errors.New("not found") + ErrBadCall = errors.New("bad call") + ErrInvalidDomainName = errors.New("invalid domain name to register") +) diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/pkg_metadata.json b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/pkg_metadata.json new file mode 100644 index 00000000..f7c3b6e7 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/pkg_metadata.json @@ -0,0 +1 @@ +{"creator":"g162jgpk4740r6a7g53cgz9ahxqtyuekgqchw6w9","deposit":""} \ No newline at end of file diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/resolver.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/resolver.gno new file mode 100644 index 00000000..2f1535d8 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/resolver.gno @@ -0,0 +1,63 @@ +/* +The goal of the Resolver contract is keep track of the address for each ICNS name in a stateful manner. +It serves the purpose of "resolving" the ICNS Name +to the correct address (e.g "alice.gno" -> g1xxx). +*/ +// changelogs: move Register feature into this resolver package +// changelogs2: Removed local storage of resolver as cache, and every querires we query to registrar to get the result + +package resolver + +import ( + "std" + + "gno.land/r/varmeta/demo/v36/domain/registrar" +) + +type Record struct { + Owner std.Address + IsValid bool + Memo string // no more need this + Priority int +} + +// retrieve the record list to get the onchain address +func Resolve(domainName string) *Record { + if !isValidDomain(domainName) { + panic("bad domain name") + } + record := &Record{} + + owner := getOwnerFromDomainStorage(domainName) + if owner == "" { + record.Memo = "not found" + record.IsValid = false + return record + } + + if !isExpired(domainName) { + record.IsValid = true + record.Owner = owner + } else { + record.IsValid = false + } + return record +} + +func GetDomainName(addr string) []string { + return registrar.GetDomainName(addr) +} + +/* +If query in local storage not found +Query to DomainStorage by domainName -> get the registry -> use that registry to get the Owner() +and check the validation time? +*/ + +func existedInDomainStorage(domainName string) bool { + return registrar.AlreadyRegistered(domainName) +} + +func getOwnerFromDomainStorage(domainName string) std.Address { + return registrar.GetOwner(domainName) +} diff --git a/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/resolver_metadata.gno b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/resolver_metadata.gno new file mode 100644 index 00000000..6d13f477 --- /dev/null +++ b/test4.gno.land/extracted/r/varmeta/demo/v36/domain/resolver/resolver_metadata.gno @@ -0,0 +1,15 @@ +package resolver + +import ( + "std" + "time" + + "gno.land/p/varmeta/demo/v36/domain" +) + +// Metadata wrapper +func metadataWrapper(owner std.Address, name string, ttl time.Duration) domain.Metadata { + crrTime := time.Now() + expTime := crrTime.Add(ttl) + return domain.NewMetadata("", name, "", "", crrTime, expTime, []domain.Trait{}) +} diff --git a/test4.gno.land/metadata.json b/test4.gno.land/metadata.json index a88a01d5..67192df5 100644 --- a/test4.gno.land/metadata.json +++ b/test4.gno.land/metadata.json @@ -1,3 +1,3 @@ { - "latest_block_height": 1853076 + "latest_block_height": 1884749 }