1+ import { Constr , Data , fromHex , toHex , TxComplete , TxHash , UTxO } from "lucid-cardano" ;
2+ import { Hydra } from "./hydra" ;
3+
4+ import * as ed25519 from "@noble/ed25519" ;
5+ import { blake2b } from "@noble/hashes/blake2b" ;
6+ import { sha512 } from "@noble/hashes/sha512" ;
7+ import { Keys } from "../hooks/useKeys" ;
8+ ed25519 . etc . sha512Sync = ( ...m ) => sha512 ( ed25519 . etc . concatBytes ( ...m ) ) ;
9+
10+ export class HydraMultiplayer {
11+ keys : Keys ;
12+ hydra : Hydra ;
13+ myIP : number = 0 ;
14+ latestUTxO : UTxO | null = null ;
15+ packetQueue : Packet [ ] = [ ] ;
16+
17+ constructor ( keys : Keys , url : string ) {
18+ this . keys = keys ;
19+ this . hydra = new Hydra ( url , 100 ) ;
20+ this . hydra . onTxSeen = this . onTxSeen ;
21+ }
22+
23+ public setIP ( ip : number ) {
24+ this . myIP = ip ;
25+ }
26+
27+ public async selectUTxO ( ) : Promise < void > {
28+ if ( ! ! this . latestUTxO ) {
29+ return ;
30+ }
31+ let utxos = await this . hydra . getUtxos ( this . keys . address ! ) ;
32+ this . latestUTxO = utxos [ 0 ] ;
33+ }
34+
35+ public async sendPacket ( to : number , from : number , data : Uint8Array ) : Promise < void > {
36+ this . packetQueue . push ( { to, from, data } ) ;
37+ await this . sendPacketQueue ( ) ;
38+ }
39+
40+ public async sendPacketQueue ( ) : Promise < void > {
41+ if ( this . packetQueue . length == 0 ) {
42+ return ;
43+ }
44+ await this . selectUTxO ( ) ;
45+ let datum = encodePackets ( this . packetQueue ) ;
46+
47+ let [ newUTxO , tx ] = buildTx ( this . latestUTxO ! , this . keys , datum ) ;
48+ await this . hydra . submitTx ( tx ) ;
49+ this . latestUTxO = newUTxO ;
50+ this . packetQueue = [ ] ;
51+ }
52+
53+ public onTxSeen ( _txId : TxHash , tx : TxComplete ) : void {
54+ // TODO: tolerate other txs here
55+ const output = tx . txComplete . body ( ) . outputs ( ) . get ( 0 ) ;
56+ const packetsRaw = output ?. datum ( ) ?. as_data ( ) ?. get ( ) . to_bytes ( ) ;
57+ if ( ! packetsRaw ) {
58+ return ;
59+ }
60+ const packets = decodePackets ( packetsRaw ) ;
61+ for ( const packet of packets ) {
62+ if ( packet . to == this . myIP ) {
63+ let buf = window . Module . _malloc ! ( packet . data . length ) ;
64+ window . Module . HEAPU8 ! . set ( packet . data , buf ) ;
65+ window . Module . _ReceivePacket ! ( packet . from , buf , packet . data . length ) ;
66+ window . Module . _free ! ( buf ) ;
67+ }
68+ }
69+ }
70+ }
71+
72+ interface Packet {
73+ to : number ,
74+ from : number ,
75+ data : Uint8Array ,
76+ }
77+
78+ function encodePackets ( packets : Packet [ ] ) : string {
79+ return Data . to (
80+ packets . map (
81+ ( { to, from, data } ) => new Constr ( 0 , [ BigInt ( to ) , BigInt ( from ) , toHex ( data ) ] )
82+ )
83+ ) ;
84+ }
85+
86+ function decodePackets ( raw : Uint8Array ) : Packet [ ] {
87+ const packets = Data . from ( toHex ( raw ) ) as Constr < Data > [ ] ;
88+ return packets . map ( ( packet ) => {
89+ let [ to , from , data ] = packet . fields ;
90+ return {
91+ to : Number ( to ) ,
92+ from : Number ( from ) ,
93+ data : fromHex ( data as string ) ,
94+ }
95+ } ) ;
96+ }
97+
98+
99+ const buildTx = (
100+ inputUtxo : UTxO ,
101+ keys : Keys ,
102+ datum : string ,
103+ ) : [ UTxO , string ] => {
104+ // Hand-roll transaction creation for more performance
105+ const datumLength = datum . length / 2 ;
106+ let datumLengthHex = datumLength . toString ( 16 ) ;
107+ if ( datumLengthHex . length % 2 !== 0 ) {
108+ datumLengthHex = "0" + datumLengthHex ;
109+ }
110+ const lengthLengthTag = 57 + datumLengthHex . length / 2 ;
111+ console . log ( datumLengthHex ) ;
112+ const txBodyByHand =
113+ `a3` + // Prefix
114+ `0081825820${ inputUtxo . txHash } 0${ inputUtxo . outputIndex } ` + // One input
115+ `0181a300581d60${ keys . publicKeyHashHex ! } 018200a0028201d818${ lengthLengthTag } ${ datumLengthHex } ${ datum } ` + // Output to users PKH
116+ `0200` ; // No fee
117+
118+ const txId = toHex (
119+ blake2b ( fromHex ( txBodyByHand ) , { dkLen : 256 / 8 } ) ,
120+ ) ;
121+ const signature = toHex ( ed25519 . sign ( txId , keys . privateKeyBytes ! ) ) ;
122+
123+ const witnessSetByHand = `a10081825820${ keys . publicKeyHex ! } 5840${ signature } ` ; // just signed by the user
124+ const txByHand = `84${ txBodyByHand } ${ witnessSetByHand } f5f6` ;
125+
126+ const newUtxo : UTxO = {
127+ txHash : txId ,
128+ outputIndex : 0 ,
129+ address : keys . address ! ,
130+ assets : { lovelace : 0n } ,
131+ datumHash : null ,
132+ datum : datum ,
133+ scriptRef : null ,
134+ } ;
135+
136+ return [ newUtxo , txByHand ] ;
137+ } ;
0 commit comments