1313# limitations under the License.
1414
1515import ast
16+ import hashlib
1617import re
1718from collections import defaultdict
1819from types import ModuleType
2324from hathor .conf .settings import HathorSettings
2425from hathor .crypto .util import decode_address , get_address_from_public_key_bytes
2526from hathor .daa import DifficultyAdjustmentAlgorithm
26- from hathor .dag_builder .builder import NC_DEPOSIT_KEY , NC_WITHDRAWAL_KEY , DAGBuilder , DAGNode
27+ from hathor .dag_builder .builder import (
28+ NC_DEPOSIT_KEY ,
29+ NC_TRANSFER_INPUT_KEY ,
30+ NC_TRANSFER_OUTPUT_KEY ,
31+ NC_WITHDRAWAL_KEY ,
32+ DAGBuilder ,
33+ DAGNode ,
34+ )
2735from hathor .dag_builder .types import DAGNodeType , VertexResolverType , WalletFactoryType
2836from hathor .dag_builder .utils import get_literal , is_literal
2937from hathor .nanocontracts import Blueprint , OnChainBlueprint
3442from hathor .transaction import BaseTransaction , Block , Transaction
3543from hathor .transaction .base_transaction import TxInput , TxOutput
3644from hathor .transaction .headers .nano_header import ADDRESS_LEN_BYTES
45+ from hathor .transaction .headers .transfer_header import TxTransferInput , TxTransferOutput
3746from hathor .transaction .scripts .p2pkh import P2PKH
3847from hathor .transaction .token_creation_tx import TokenCreationTransaction
3948from hathor .wallet import BaseWallet , HDWallet , KeyPair
@@ -229,6 +238,7 @@ def create_vertex_token(self, node: DAGNode) -> TokenCreationTransaction:
229238 vertex .token_symbol = node .name
230239 vertex .timestamp = self .get_min_timestamp (node )
231240 self .add_nano_header_if_needed (node , vertex )
241+ self .add_transfer_header_if_needed (node , vertex )
232242 self .sign_all_inputs (vertex , node = node )
233243 if 'weight' in node .attrs :
234244 vertex .weight = float (node .attrs ['weight' ])
@@ -253,6 +263,7 @@ def create_vertex_block(self, node: DAGNode) -> Block:
253263
254264 blk = Block (parents = parents , outputs = outputs )
255265 self .add_nano_header_if_needed (node , blk )
266+ self .add_transfer_header_if_needed (node , blk )
256267 blk .timestamp = self .get_min_timestamp (node ) + self ._settings .AVG_TIME_BETWEEN_BLOCKS
257268 blk .get_height = lambda : height # type: ignore[method-assign]
258269 blk .update_hash () # the next call fails is blk.hash is None
@@ -302,6 +313,65 @@ def _get_next_nc_seqnum(self, nc_pubkey: bytes) -> int:
302313 self ._next_nc_seqnum [address ] = cur + 1
303314 return cur
304315
316+ def _get_token_index (self , token_name : str , node : DAGNode , vertex : BaseTransaction ) -> None :
317+ token_index = 0
318+ if token_name != 'HTR' :
319+ assert isinstance (vertex , Transaction )
320+ token_creation_tx = self ._vertices [token_name ]
321+ if token_creation_tx .hash not in vertex .tokens :
322+ vertex .tokens .append (token_creation_tx .hash )
323+ token_index = 1 + vertex .tokens .index (token_creation_tx .hash )
324+ return token_index
325+
326+ def add_transfer_header_if_needed (self , node : DAGNode , vertex : BaseTransaction ) -> None :
327+ inputs = node .get_attr_list (NC_TRANSFER_INPUT_KEY )
328+ outputs = node .get_attr_list (NC_TRANSFER_OUTPUT_KEY )
329+
330+ if not inputs and not outputs :
331+ return
332+
333+ transfer_inputs : list [TxTransferInput ] = []
334+ for wallet_name , token_name , amount in inputs :
335+ wallet = self .get_wallet (wallet_name )
336+ assert isinstance (wallet , HDWallet )
337+ privkey = wallet .get_key_at_index (0 )
338+ pubkey_bytes = privkey .sec ()
339+ address = get_address_from_public_key_bytes (pubkey_bytes )
340+ token_index = self ._get_token_index (token_name , node , vertex )
341+
342+ sighash_data = vertex .get_sighash_all_data ()
343+ sighash_data_hash = hashlib .sha256 (sighash_data ).digest ()
344+ signature = privkey .sign (sighash_data_hash )
345+ script = P2PKH .create_input_data (public_key_bytes = pubkey_bytes , signature = signature )
346+
347+ transfer_inputs .append (TxTransferInput (
348+ address = address ,
349+ amount = amount ,
350+ token_index = token_index ,
351+ script = script ,
352+ ))
353+
354+ transfer_outputs : list [TxTransferOutput ] = []
355+ for wallet_name , token_name , amount in outputs :
356+ wallet = self .get_wallet (wallet_name )
357+ assert isinstance (wallet , HDWallet )
358+ privkey = wallet .get_key_at_index (0 )
359+ pubkey_bytes = privkey .sec ()
360+ address = get_address_from_public_key_bytes (pubkey_bytes )
361+ token_index = self ._get_token_index (token_name , node , vertex )
362+ transfer_outputs .append (TxTransferOutput (
363+ address = address ,
364+ amount = amount ,
365+ token_index = token_index ,
366+ ))
367+
368+ from hathor .transaction .headers import TransferHeader
369+ transfer_header = TransferHeader (
370+ inputs = transfer_inputs ,
371+ outputs = transfer_outputs ,
372+ )
373+ vertex .headers .append (transfer_header )
374+
305375 def add_nano_header_if_needed (self , node : DAGNode , vertex : BaseTransaction ) -> None :
306376 if 'nc_id' not in node .attrs :
307377 return
@@ -405,6 +475,7 @@ def create_vertex_on_chain_blueprint(self, node: DAGNode) -> OnChainBlueprint:
405475 assert len (block_parents ) == 0
406476 ocb = OnChainBlueprint (parents = txs_parents , inputs = inputs , outputs = outputs , tokens = tokens )
407477 self .add_nano_header_if_needed (node , ocb )
478+ self .add_transfer_header_if_needed (node , ocb )
408479 code_attr = node .get_attr_str ('ocb_code' )
409480
410481 if is_literal (code_attr ):
@@ -453,6 +524,7 @@ def create_vertex_transaction(self, node: DAGNode, *, cls: type[Transaction] = T
453524 tx = cls (parents = txs_parents , inputs = inputs , outputs = outputs , tokens = tokens )
454525 tx .timestamp = self .get_min_timestamp (node )
455526 self .add_nano_header_if_needed (node , tx )
527+ self .add_transfer_header_if_needed (node , tx )
456528 self .sign_all_inputs (tx , node = node )
457529 if 'weight' in node .attrs :
458530 tx .weight = float (node .attrs ['weight' ])
0 commit comments