Truy cập IDE SmartPy
Đầu tiên, hãy mở IDE trực tuyến SmartPy tại https://smartpy.io/ide/. Đây là nền tảng mà chúng tôi sẽ sử dụng để viết, kiểm tra và triển khai các hợp đồng thông minh của mình.
Khởi tạo mẫu FA1.2
Nhấp vào “Mẫu theo loại” trên thanh bên trái và chọn “FA1.2”. Một tab mới sẽ mở ra với mẫu hợp đồng FA1.2. Đây là hợp đồng sẵn sàng sử dụng theo tiêu chuẩn FA1.2.
Tìm hiểu mẫu FA1.2
Mẫu này có chức năng cơ bản dành cho mã thông báo có thể thay thế, bao gồm chuyển mã thông báo, phê duyệt chuyển khoản, kiểm tra số dư và xem tổng nguồn cung cấp mã thông báo. Hợp đồng cũng bao gồm chức năng bổ sung để đúc và đốt token cũng như quản lý quản trị.
Nghiên cứu mẫu này và đảm bảo bạn hiểu các chức năng của nó. Sẽ không sao nếu bạn không hiểu mọi thứ vào thời điểm này, nhưng hãy cố gắng hiểu khái quát về các hoạt động mà hợp đồng này có thể thực hiện.
Ví dụ: bạn có thể sao chép mã từ mẫu trên SmartPy IDE hoặc ở đây bên dưới:
Python 
 # Fungible Assets - FA12 
 # Lấy cảm hứng từ https://gitlab.com/tzip/tzip/blob/master/A/FA1.2.md 
 import smartpy as sp 
 # Siêu dữ liệu bên dưới chỉ là một ví dụ, nó đóng vai trò là cơ sở, 
 # nội dung được sử dụng để xây dựng siêu dữ liệu JSON mà người dùng 
 # có thể sao chép và tải lên IPFS.
TZIP16_Metadata_Base = {
    "name": "SmartPy FA1.2 Token Template",
    "description": "Example Template for an FA1.2 Contract from SmartPy",
    "authors": ["SmartPy Dev Team"],
    "homepage": "https://smartpy.io",
    "interfaces": ["TZIP-007-2021-04-17", "TZIP-016-2021-04-17"],
}
@sp.module 
 def m(): 
 lớp AdminInterface(sp.Contract): 
 @sp.private(with_storage="read-only") 
 def is_administrator_(self, sender): 
 sp .cast(sp.sender, sp.address) 
 """Không chuẩn, có thể được xác định lại thông qua kế thừa."""
            trả về Đúng 
 lớp CommonInterface(AdminInterface): 
 def __init__(self): 
 AdminInterface.__init__(bản thân)
            self.data.balances = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.address, 
 sp.record(approvals=sp.map[sp.address, sp.nat], số dư=sp.nat),
                ], 
 ) 
 self.data.total_supply = 0 
 self.data.token_metadata = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.nat, 
 sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes]), 
 ], 
 ) 
 self.data.metadata = sp.cast(
                sp.big_map(), 
 sp.big_map[sp.string, sp.bytes], 
 ) 
 self.data.balances = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.address, 
 sp.record(approvals=sp.map[sp.address, sp.nat], số dư=sp.nat),
                ], 
 ) 
 self.data.total_supply = 0 
 self.data.token_metadata = sp.cast(
                sp.big_map(), 
 sp.big_map[ 
 sp.nat, 
 sp.record(token_id=sp.nat, token_info=sp.map[sp.string, sp.bytes]), 
 ], 
 ) 
 self.data.metadata = sp.cast(
                sp.big_map(), 
 sp.big_map[sp.string, sp.bytes], 
 ) 
 @sp.private(with_storage="read-only") 
 def is_paused_(self): 
 """Không chuẩn, có thể được xác định lại thông qua kế thừa."""
            trả về Sai 
 lớp Fa1_2(CommonInterface): 
 def __init__(self, siêu dữ liệu, sổ cái, token_metadata): 
 """ 
 thông số token_metadata: https://gitlab.com/tzip/tzip/-/blob/master/ đề xuất/tzip-12/tzip-12.md#token-metadata
            Siêu dữ liệu dành riêng cho mã thông báo được lưu trữ/trình bày dưới dạng loại giá trị Michelson (byte chuỗi bản đồ).
            Một số khóa được đặt trước và xác định trước: 
 - "" : Phải tương ứng với URI TZIP-016 trỏ đến biểu diễn JSON của siêu dữ liệu mã thông báo.
            - "name" : Phải là chuỗi UTF-8 cung cấp "tên hiển thị" cho mã thông báo.
            - "biểu tượng" : Phải là chuỗi UTF-8 cho mã định danh ngắn của mã thông báo (ví dụ: XTZ, EUR,…). 
 - "số thập phân" : Phải là số nguyên (được chuyển đổi thành chuỗi UTF-8 ở dạng thập phân) 
 xác định vị trí của dấu thập phân trong số dư mã thông báo cho mục đích hiển thị.
            thông số Contract_metadata: https://gitlab.com/tzip/tzip/-/blob/master/proposals/tzip-16/tzip-16.md 
 """ 
 CommonInterface.__init__(self)
            self.data.metadata = siêu dữ liệu 
 self.data.token_metadata = sp.big_map(
                {0: sp.record(token_id=0, token_info=token_metadata)}
            ) 
 cho chủ sở hữu trong ledger.items():
                self.data.balances[owner.key] = owner.value 
 self.data.total_supply += owner.value.balance 
 # TODO: Kích hoạt khi tính năng này được triển khai.
            # self.init_metadata("siêu dữ liệu", siêu dữ liệu) 
 @sp.entrypoint 
 chuyển def(self, param): 
 sp.cast( 
 param, 
 sp.record(from_=sp.address, to_=sp.địa chỉ, value=sp.nat).layout(
                    ("from_ as from", ("to_ as to", "value")) 
 ), 
 ) 
 Balance_from = self.data.balances.get(
                thông số.from_, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ) 
 Balance_to = self.data.balances.get(
                thông số.to_, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ) 
 Balance_from.balance = sp.as_nat(
                Balance_from.balance - param.value, error="FA1.2_In EnoughBalance" 
 ) 
 Balance_to.balance += param.value 
 nếu không self.is_administrator_(sp.sender):
                khẳng định không phải self.is_paused_(), "FA1.2_Tạm dừng"
                nếu tham số.from_ != sp.sender: 
 số dư_from.approvals[sp.sender] = sp.as_nat(
                        Balance_from.approvals[sp.sender] - param.value,
                        error="FA1.2_NotAllowed", 
 ) 
 self.data.balances[param.from_] = Balance_from 
 self.data.balances[param.to_] = Balance_to 
 @sp.entrypoint 
 xác nhận phê duyệt(self, param): 
 sp.cast( 
 param, 
 sp.record(spender=sp.address, value=sp.nat).layout(
                    ("người chi tiêu", "giá trị") 
 ), 
 ) 
 khẳng định không phải self.is_paused_(), "FA1.2_Tạm dừng"
            chi tiêu_balance = self.data.balances.get(
                sp.sender, mặc định=sp.record(balance=0, phê duyệt={}) 
 ) 
 đã được phê duyệt = chi tiêu_balance.approvals.get(param.spender, default=0) 
 xác nhận ( 
 đã được phê duyệt == 0 hoặc param.value == 0 
 ), "FA1.2_UnsafeAllowanceChange"
            chi tiêu_balance.approvals[param.spender] = param.value 
 self.data.balances[sp.sender] = chi tiêu_balance 
 @sp.entrypoint 
 def getBalance(self, param): 
 (địa chỉ, gọi lại) = param 
 result = self.data.balances.get(
                địa chỉ, mặc định=sp.record(balance=0, phê duyệt={}) 
 ).balance 
 sp.transfer(result, sp.tez(0), callback) 
 @sp.entrypoint 
 def getAllowance(self, param): 
 (args, callback) = param 
 kết quả = self.data.balances.get(
                args.owner, mặc định=sp.record(cân bằng=0, phê duyệt={}) 
 ).approvals.get(args.spender, default=0) 
 sp.transfer(result, sp.tez(0), callback) 
 @sp.entrypoint 
 def getTotalSupply(self, param): 
 sp.cast(param, sp.pair[sp.unit, sp.contract[sp.nat]])
            sp.transfer(self.data.total_supply, sp.tez(0), sp.snd(param)) 
 @sp.offchain_view() 
 def token_metadata(self, token_id): 
 """Trả về URI siêu dữ liệu mã thông báo cho mã thông báo đã cho. (token_id phải là 0)."""
            sp.cast(token_id, sp.nat) 
 trả về self.data.token_metadata[token_id]
    ########## 
 # Mixins # 
 ##########
    class Admin(sp.Contract):
        def __init__(self, administrator):
            self.data.administrator = administrator
        @sp.private(with_storage="read-only")
        def is_administrator_(self, sender):
            return sender == self.data.administrator
        @sp.entrypoint
        def setAdministrator(self, params):
            sp.cast(params, sp.address)
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.administrator = params
        @sp.entrypoint()
        def getAdministrator(self, param):
            sp.cast(param, sp.pair[sp.unit, sp.contract[sp.address]])
            sp.transfer(self.data.administrator, sp.tez(0), sp.snd(param))
        @sp.onchain_view()
        def get_administrator(self):
            return self.data.administrator
    class Pause(AdminInterface):
        def __init__(self):
            AdminInterface.__init__(self)
            self.data.paused = False
        @sp.private(with_storage="read-only")
        def is_paused_(self):
            return self.data.paused
        @sp.entrypoint
        def setPause(self, param):
            sp.cast(param, sp.bool)
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.paused = param
    class Mint(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)
        @sp.entrypoint
        def mint(self, param):
            sp.cast(param, sp.record(address=sp.address, value=sp.nat))
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            receiver_balance = self.data.balances.get(
                param.address, default=sp.record(balance=0, approvals={})
            )
            receiver_balance.balance += param.value
            self.data.balances[param.address] = receiver_balance
            self.data.total_supply += param.value
    class Burn(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)
        @sp.entrypoint
        def burn(self, param):
            sp.cast(param, sp.record(address=sp.address, value=sp.nat))
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            receiver_balance = self.data.balances.get(
                param.address, default=sp.record(balance=0, approvals={})
            )
            receiver_balance.balance = sp.as_nat(
                receiver_balance.balance - param.value,
                error="FA1.2_InsufficientBalance",
            )
            self.data.balances[param.address] = receiver_balance
            self.data.total_supply = sp.as_nat(self.data.total_supply - param.value)
    class ChangeMetadata(CommonInterface):
        def __init__(self):
            CommonInterface.__init__(self)
        @sp.entrypoint
        def update_metadata(self, key, value):
            """An entrypoint to allow the contract metadata to be updated."""
            assert self.is_administrator_(sp.sender), "Fa1.2_NotAdmin"
            self.data.metadata[key] = value
    ########## 
 # Kiểm tra # 
 ########## 
 lớp Fa1_2TestFull(Quản trị viên, Tạm dừng, Fa1_2, Mint, Ghi, ChangeMetadata): 
 def __init__(self, quản trị viên, siêu dữ liệu, sổ cái, token_metadata): 
 ChangeMetadata.__init__(bản thân)
            Đốt cháy.__init__(bản thân)
            Cây bạc hà.__init__(bản thân)
            Pháp1_2.__init__(bản thân, siêu dữ liệu, sổ cái, token_metadata) 
 Tạm dừng.__init__(bản thân)
            Quản trị viên.__init__(bản thân, quản trị viên) 
 lớp Viewer_nat(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(Không có, sp.option[sp.nat])
        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 
 class Viewer_address(sp.Contract): 
 def __init__(self): 
 self.data.last = sp.cast(Không có, sp.option[sp.address])
        @sp.entrypoint 
 def target(self, params): 
 self.data.last = sp.Some(params) 
 nếu "templates" không có trong __name__: 
 @sp.add_test(name="FA12") 
 kiểm tra def(): 
 sc = sp.test_scenario(m)
        sc.h1("Mẫu FA1.2 - Nội dung có thể thay thế") 
 # sp.test_account tạo cặp khóa ED25519 một cách xác định: 
 quản trị viên = sp.test_account("Quản trị viên")
        alice = sp.test_account("Alice")
        bob = sp.test_account("Robert")
        # Hãy hiển thị các tài khoản: 
 sc.h1("Tài khoản") 
 sc.show([admin, alice, bob]) 
 sc.h1("Contract") 
 token_metadata = {
            "decimals": sp.utils.bytes_of_string("18"),  # Mandatory by the spec
            "name": sp.utils.bytes_of_string("My Great Token"),  # Recommended
            "symbol": sp.utils.bytes_of_string("MGT"),  # Recommended
            # Extra fields
            "icon": sp.utils.bytes_of_string(
                "https://smartpy.io/static/img/logo-only.svg"
            ),
        }
        Contract_metadata = sp.utils. siêu dữ liệu_of_url(
            "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 
 c1 = m.Fa1_2TestFull( 
 quản trị viên=admin.address, 
 siêu dữ liệu=contract_metadata, 
 token_metadata=token_metadata, 
 sổ cái={}, 
 ) 
 sc += c1 
 sc .h1("Chế độ xem ngoại tuyến - token_metadata") 
 sc.verify_equal( 
 sp.View(c1, "token_metadata")(0), 
 sp.record( 
 token_id=0, 
 token_info=sp.map(
                    {
                        "decimals": sp.utils.bytes_of_string("18"),
                        "name": sp.utils.bytes_of_string("My Great Token"),
                        "symbol": sp.utils.bytes_of_string("MGT"),
                        "icon": sp.utils.bytes_of_string(
                            "https://smartpy.io/static/img/logo-only.svg"
                        ),
                    }
                ), 
 ), 
 ) 
 sc.h1("Cố gắng cập nhật siêu dữ liệu") 
 sc.verify( 
 c1.data.metadata[""]
            == sp.utils.bytes_of_string(
                "ipfs://QmaiAUj1FFNGYTu8rLBjc3eeN9cSKwaF8EGMBNDmhzPNFd" 
 ) 
 ) 
 c1.update_metadata(key="", value=sp.bytes("0x00")).run(sender=admin) 
 sc.verify(c1.data.metadata[ ""] == sp.bytes("0x00")) 
 sc.h1("Entrypoints") 
 sc.h2("Quản trị viên đúc một vài xu") 
 c1.mint(address=alice.address, value=12).run (người gửi=quản trị viên)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        c1.mint(address=alice.address, value=3).run(sender=admin)
        sc.h2("Alice chuyển cho Bob") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(sender=alice)
        sc.verify(c1.data.balances[alice.address].balance == 14) 
 sc.h2("Bob cố gắng chuyển từ Alice nhưng anh ấy không được cô ấy chấp thuận") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            sender=bob, valid=False 
 ) 
 sc.h2("Alice chấp thuận việc chuyển Bob và Bob") 
 c1.approve(spender=bob.address, value=5).run(sender=alice)
        c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(sender=bob)
        sc.h2("Bob cố gắng chuyển quá mức từ Alice") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            người gửi=bob, hợp lệ=False 
 ) 
 sc.h2("Quản trị viên đốt mã thông báo Bob") 
 c1.burn(address=bob.address, value=1).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Alice cố đốt mã thông báo Bob") 
 c1.burn(address=bob.address, value=1).run(người gửi=alice, valid=False) 
 sc.h2("Quản trị viên tạm dừng hợp đồng và Alice không thể chuyển tiếp nữa") 
 c1.setPause(True).run(sender=admin)
        c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=4).run(
            người gửi=alice, valid=False 
 ) 
 sc.verify(c1.data.balances[alice.address].balance == 10) 
 sc.h2("Truyền quản trị viên trong khi tạm dừng") 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=1).run(sender=admin)
        sc.h2("Quản trị viên hủy tạm dừng hợp đồng và cho phép chuyển khoản") 
 c1.setPause(False).run(sender=admin)
        sc.verify(c1.data.balances[alice.address].balance == 9) 
 c1.transfer(from_=alice.address, to_=bob.địa chỉ, value=1).run(sender=alice)
        sc.verify(c1.data.total_supply == 17) 
 sc.verify(c1.data.balances[alice.address].balance == 8) 
 sc.verify(c1.data.balances[bob.address].balance == 9) 
 sc.h1("Lượt xem") 
 sc.h2("Số dư") 
 view_balance = m.Viewer_nat() 
 sc += view_balance 
 target = sp.contract(sp.TNat, view_balance.address, "mục tiêu").open_some() 
 c1.getBalance((alice.address, mục tiêu)) 
 sc.verify_equal(view_balance.data.last, sp.some(8)) 
 sc.h2("Quản trị viên") 
 view_administrator = m.Viewer_address() 
 sc += view_administrator 
 target = sp.contract(
            sp.TAĐịa chỉ, view_administrator.address, "đích" 
 ).open_some() 
 c1.getAdministrator((sp.unit, target)) 
 sc.verify_equal(view_administrator.data.last, sp.some(admin.address))
        sc.h2("Tổng cung") 
 view_totalSupply = m.Viewer_nat() 
 sc += view_totalSupply 
 target = sp.contract(sp.TNat, view_totalSupply.address, "target").open_some() 
 c1.getTotalSupply((sp.unit, target)) 
 sc.verify_equal(view_totalSupply.data.last, sp.some(17)) 
 sc.h2("Allowance") 
 view_allowance = m.Viewer_nat() 
 sc += view_allowance 
 target = sp.contract(sp.TNat, view_allowance.address, "target").open_some() 
 c1.getAllowance((sp.record(owner=alice.address, chi tiêu=bob.address), target)) 
 sc.verify_equal(view_allowance.data.last, sp.some(1))

Hợp đồng FA1.2 ban đầu có chức năng cơ bản như chuyển mã thông báo, phê duyệt chuyển khoản, kiểm tra số dư và xem tổng nguồn cung cấp mã thông báo. Bây giờ chúng tôi sẽ nâng cao chức năng này.
Chúc mừng! Bạn đã tạo mã thông báo có thể thay thế đầu tiên của mình trên Tezos bằng tiêu chuẩn FA1.2!
Trong bài học tiếp theo, chúng ta sẽ học cách tương tác với hợp đồng token mà chúng ta vừa tạo. Điều này sẽ bao gồm chuyển mã thông báo, phê duyệt chuyển mã thông báo và kiểm tra số dư mã thông báo cũng như tổng nguồn cung. Giữ nguyên!