Skip to content

Shielded TRC20

Shielded TRC20 is only available on Nile Testnet for now.

A ShieldedTRC20 wrapper can be acquired by get_contract_as_shielded_trc20 call.

A full set of shielded address keys can be acquired by generate_zkey call.

Generate shielded address

>>> from tronpy import Tron
>>> client = Tron(network='nile')

>>> client.generate_zkey()
{'ak': 'd06c283d7c9a189710c323058a27d9c96e4b21fbd7a0fbabecb5d605d1190fe0',
'ask': 'e6268e981533004ea6ce05dcb68d74258962142e8e10246d22f07f2ef08d8b0c',
'd': '1c1348babd32e8b1fb556b',
'ivk': '4f8757ec009325e3a4ead1988986cc6ad078951caa6e948534d4a3ca77e64903',
'nk': '82e3f4028a019f8e2510e95f2109e9ce0401ceb37c3f8288b84a9c796c9f7913',
'nsk': '298b8ef945c1aa04a81fe04c5cd44ca674c6393053078bc6b3200414544ac109',
'ovk': 'd82520ba6aedd623ff831ca89bba9cc9d78a44eabf4d5099922386a7e5ee4dc1',
'payment_address': 'ztron1rsf53w4axt5tr764dvh035tsgfmwjga66v3g9dulazq2jzxnk63t2dupvl7ufca0fnjdj98xqqg',
'pkD': '2ef8d1704276e923bad32282b79fe880a908d3b6a2b5378167fdc4e3af4ce4d9',
'sk': '.........................omitted.......................'}

Transfer shielded TRC20 tokens

There are 3 types of shielded transfer:

  • mint: from T-address to z-address
  • transfer: from z-address to z-address
  • burn: from z-address to T-address

To use the ShieldedTRC20 wrapper:

TEkQTDyZmbY6hngxdAsxzxy9r3bUNhRjdS is the ShieldedTRC20 of JST token(TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3) on Nile, which can be acquired via Nile Testnet Faucet. The scaling factor is 18.

client = Tron(network="nile")
shielded_trc20 = client.get_contract_as_shielded_trc20("TEkQTDyZmbY6hngxdAsxzxy9r3bUNhRjdS")

taddr = "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8"
priv_key = PrivateKey(bytes.fromhex("................omitted.............................."))

# check allowance
print("Allowance:", shielded_trc20.trc20.functions.allowance(taddr, shielded_trc20.shielded.contract_address))

# or approve transferFrom
# shielded_trc20.trc20.functions.approve(taddr, 1 * 10 ** 18).with_owner(taddr).build().sign(priv_key).broadcast().wait()

txn = shielded_trc20.mint(taddr, zkey["payment_address"], 1 * 10**18, "The Memo").fee_limit(5_000_000).build().sign(priv_key)
print(txn.broadcast().wait())

# from z-addr to z-addr
# note is acquired via scan_incoming_notes, where is_spent == False
note = {
    "index": 0,
    "is_spent": False,
    "note": {
        "memo": "546865204d656d6f",
        "payment_address": "ztron1dt62eetht5e50rm5g......omitted......v7hmskm7rhx0dv5gyec8sn",
        "rcm": "fd39a0c1e298d88ad35893d4......omitted......57bffefdd076a521d70d",
        "value": 1,
    },
    "position": 0,
    "txid": "e191f1114cbd8cbe43452b2c3141326ae4c2aba22a82ba195c9573895cbbfd84",
}

# transfer value unit is in shielded token, original TRC20 divided by scaling factor
txn = (
    shielded_trc20.transfer(zkey1, note, (zkey2["payment_address"], 1, "oh yeah"))
    .with_owner("TJRabPrwbZy45sbavfcjinPJC18kjpRTv8")
    .fee_limit(5_000000)
    .build()
    .sign(priv_key)
)
print(txn.txid)
print(txn.broadcast().result())

# from z-addr to T-addr
# note is acquired via scan_incoming_notes, where is_spent == False

note = {
    "note": {
        "payment_address": "ztron1nf7ducycjl4nm4507jmv......omitted......9j66kczw0n4r7wlvkrfjtcz",
        "memo": "6f682079656168",
        "value": 1,
        "rcm": "86ced3a38ddf7e2bfa4f8......omitted......958ec860ff63da7f4608",
    },
    "txid": "f9f8e9368d5ff34d4a923c46a8c53eb648a02da99089d6d60a6a3b57a27f9add",
    "index": 0,
    "position": 1,
    "is_spent": False,
}

txn = (
    shielded_trc20.burn(zkey2, note, "TVjsyZ7fYF3qLF6BQgPmTEZy1xrNNyVAAA")
    .with_owner("TJRabPrwbZy45sbavfcjinPJC18kjpRTv8")
    .fee_limit(5_000000)
    .build()
    .sign(priv_key)
)
print(txn.txid)
print(txn.broadcast().result())

Scan notes

notes = shielded_trc20.scan_incoming_notes(zkey, 6587490)

# or
notes = shielded_trc20.scan_incoming_notes(zkey, client.get_latest_block_number() - 100)

API reference

ShieldedTRC20

ShieldedTRC20(contract: Contract)

Shielded TRC20 Wrapper.

Methods:

Attributes:

Source code in tronpy/contract.py
488
489
490
491
492
493
494
495
496
def __init__(self, contract: Contract):
    self.shielded = contract
    """Thi shielded TRC20 contract."""

    self._client = contract._client

    # lazy properties
    self._trc20 = None
    self._scale_factor = None

scale_factor property

scale_factor: int

Scaling factor of the shielded contract.

shielded instance-attribute

shielded = contract

Thi shielded TRC20 contract.

trc20 property

trc20: Contract

The corresponding TRC20 contract.

burn

burn(
    zkey: dict, note: dict, *to: tuple[str, int] | tuple[str, int, str]
) -> TransactionBuilder

Burn, transfer from z-address to T-address.

Source code in tronpy/contract.py
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
def burn(self, zkey: dict, note: dict, *to: tuple[str, int] | tuple[str, int, str]) -> "tronpy.tron.TransactionBuilder":
    """Burn, transfer from z-address to T-address."""
    spends = []
    alpha = self.get_rcm()
    root, path = self.get_path(note.get("position", 0))
    if note.get("is_spent", False):
        raise DoubleSpending
    spends.append({"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)})
    change_amount = 0
    receives = []
    to_addr = None
    to_amount = 0
    to_memo = ""
    if not to:
        raise ValueError("burn must have a output")
    for receive in to:
        addr = receive[0]
        amount = receive[1]
        memo = receive[2] if len(receive) == 3 else ""

        if addr.startswith("ztron1"):
            change_amount += amount
            rcm = self.get_rcm()
            receives = [{"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}}]
        else:
            # assume T-address
            to_addr = addr
            to_amount = amount
            to_memo = memo

    if note["note"]["value"] * self.scale_factor - change_amount * self.scale_factor != to_amount:
        raise ValueError("Balance amount is wrong")

    payload = {
        "ask": zkey["ask"],
        "nsk": zkey["nsk"],
        "ovk": zkey["ovk"],
        "shielded_spends": spends,
        "shielded_receives": receives,
        "to_amount": str(to_amount),
        "transparent_to_address": to_hex_address(to_addr),
        "shielded_TRC20_contract_address": to_hex_address(self.shielded.contract_address),
    }

    ret = self._client.provider.make_request("wallet/createshieldedcontractparameters", payload)
    self._client._handle_api_error(ret)
    parameter = ret["trigger_contract_input"]
    function_signature = self.shielded.functions.burn.function_signature_hash
    txn = self._client.trx._build_transaction(
        "TriggerSmartContract",
        {
            "owner_address": "410000000000000000000000000000000000000000",
            "contract_address": to_hex_address(self.shielded.contract_address),
            "data": function_signature + parameter,
        },
        method=self.shielded.functions.burn,
    )
    if to_memo:
        txn = txn.memo(to_memo)
    return txn

is_note_spent

is_note_spent(zkey: dict, note: dict) -> bool

Is a note spent.

Source code in tronpy/contract.py
720
721
722
723
724
725
726
727
728
729
730
731
def is_note_spent(self, zkey: dict, note: dict) -> bool:
    """Is a note spent."""
    payload = dict(note)
    payload["shielded_TRC20_contract_address"] = to_hex_address(self.shielded.contract_address)
    if "position" not in note:
        payload["position"] = 0
    payload["ak"] = zkey["ak"]
    payload["nk"] = zkey["nk"]

    ret = self._client.provider.make_request("wallet/isshieldedtrc20contractnotespent", payload)

    return ret.get("is_spent", None)

mint

mint(taddr: str, zaddr: str, amount: int, memo: str = '') -> TransactionBuilder

Mint, transfer from T-address to z-address.

Source code in tronpy/contract.py
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
def mint(self, taddr: str, zaddr: str, amount: int, memo: str = "") -> "tronpy.tron.TransactionBuilder":
    """Mint, transfer from T-address to z-address."""
    rcm = self.get_rcm()
    payload = {
        "from_amount": str(amount),
        "shielded_receives": {
            "note": {
                "value": amount // self.scale_factor,
                "payment_address": zaddr,
                "rcm": rcm,
                "memo": memo.encode().hex(),
            }
        },
        "shielded_TRC20_contract_address": to_hex_address(self.shielded.contract_address),
    }

    ret = self._client.provider.make_request("wallet/createshieldedcontractparameters", payload)
    self._client._handle_api_error(ret)
    parameter = ret["trigger_contract_input"]
    function_signature = self.shielded.functions.mint.function_signature_hash
    return self._client.trx._build_transaction(
        "TriggerSmartContract",
        {
            "owner_address": to_hex_address(taddr),
            "contract_address": to_hex_address(self.shielded.contract_address),
            "data": function_signature + parameter,
        },
        method=self.shielded.functions.mint,
    )

scan_incoming_notes

scan_incoming_notes(
    zkey: dict, start_block_number: int, end_block_number: int = None
) -> list

Scan incoming notes using ivk, ak, nk.

Source code in tronpy/contract.py
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
def scan_incoming_notes(self, zkey: dict, start_block_number: int, end_block_number: int = None) -> list:
    """Scan incoming notes using ivk, ak, nk."""
    if end_block_number is None:
        end_block_number = start_block_number + 1000
    payload = {
        "start_block_index": start_block_number,
        "end_block_index": end_block_number,
        "shielded_TRC20_contract_address": to_hex_address(self.shielded.contract_address),
        "ivk": zkey["ivk"],
        "ak": zkey["ak"],
        "nk": zkey["nk"],
    }
    ret = self._client.provider.make_request("wallet/scanshieldedtrc20notesbyivk", payload)
    self._client._handle_api_error(ret)
    return self._fix_notes(ret.get("noteTxs", []))

scan_outgoing_notes

scan_outgoing_notes(
    zkey_or_ovk: dict | str, start_block_number: int, end_block_number: int = None
) -> list

Scan outgoing notes using ovk.

Source code in tronpy/contract.py
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
def scan_outgoing_notes(self, zkey_or_ovk: dict | str, start_block_number: int, end_block_number: int = None) -> list:
    """Scan outgoing notes using ovk."""
    if end_block_number is None:
        end_block_number = start_block_number + 1000

    ovk = zkey_or_ovk
    if isinstance(zkey_or_ovk, (dict,)):
        ovk = zkey_or_ovk["ovk"]

    payload = {
        "start_block_index": start_block_number,
        "end_block_index": end_block_number,
        "shielded_TRC20_contract_address": to_hex_address(self.shielded.contract_address),
        "ovk": ovk,
    }
    ret = self._client.provider.make_request("wallet/scanshieldedtrc20notesbyovk", payload)
    self._client._handle_api_error(ret)
    return ret.get("noteTxs", [])

transfer

transfer(
    zkey: dict, notes: list | dict, *to: tuple[str, int] | tuple[str, int, str]
) -> TransactionBuilder

Transfer from z-address to z-address.

Source code in tronpy/contract.py
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
def transfer(
    self,
    zkey: dict,
    notes: list | dict,
    *to: tuple[str, int] | tuple[str, int, str],
) -> "tronpy.tron.TransactionBuilder":
    """Transfer from z-address to z-address."""
    if isinstance(notes, (dict,)):
        notes = [notes]

    if not 1 <= len(notes) <= 2:
        raise ValueError("transfer must have 1 or 2 notes")

    spends = []
    spend_amount = 0
    for note in notes:
        if note.get("is_spent", False):
            raise DoubleSpending
        alpha = self.get_rcm()
        root, path = self.get_path(note.get("position", 0))
        spends.append({"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)})
        spend_amount += note["note"]["value"]

    receives = []
    receive_amount = 0
    for recv in to:
        addr = recv[0]
        amount = recv[1]
        receive_amount += amount
        memo = recv[2] if len(recv) == 3 else ""

        rcm = self.get_rcm()

        receives.append({"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}})

    if spend_amount != receive_amount:
        raise ValueError("spend amount is not equal to receive amount")

    payload = {
        "ask": zkey["ask"],
        "nsk": zkey["nsk"],
        "ovk": zkey["ovk"],
        "shielded_spends": spends,
        "shielded_receives": receives,
        "shielded_TRC20_contract_address": to_hex_address(self.shielded.contract_address),
    }
    ret = self._client.provider.make_request("wallet/createshieldedcontractparameters", payload)
    self._client._handle_api_error(ret)
    parameter = ret["trigger_contract_input"]
    function_signature = self.shielded.functions.transfer.function_signature_hash
    return self._client.trx._build_transaction(
        "TriggerSmartContract",
        {
            "owner_address": "0000000000000000000000000000000000000000",
            "contract_address": to_hex_address(self.shielded.contract_address),
            "data": function_signature + parameter,
        },
        method=self.shielded.functions.transfer,
    )