11import * as Monaco from 'npm:monaco-editor' ;
2- import Bitcoin from '../platform/Bitcoin/Bitcoin.ts' ;
2+ import Bitcoin , { Esplora } from '../platform/Bitcoin/Bitcoin.ts' ;
33import Html from '../library/Html.ts' ;
44import Wasm from './wasm.ts' ;
55import scrollTo from 'npm:animated-scroll-to' ;
@@ -19,7 +19,6 @@ type User = ReturnType<typeof User>;
1919const chain = Bitcoin . LiquidTestnet ( ) ; // Chain handle (initialized once)
2020const nonSecret = ( n : number ) => new Uint8Array ( new Array ( 32 ) . fill ( n ) ) ;
2121const usersByName = { } ;
22- const usersByPubkey = { } ;
2322
2423/** Launch the editor and user views. */
2524export default function App ( {
@@ -79,12 +78,7 @@ const userSelectors = (host = document) =>
7978 host . querySelectorAll ( 'select.pick-user' ) as unknown as HTMLSelectElement [ ] ;
8079
8180const addUser = ( ...[ name , options ] : Parameters < typeof User > ) : User => {
82- if ( usersByName [ name ] ) throw new Error ( `user already exists: ${ name } ` ) ;
8381 const user = User ( name , options ) ;
84- const pubkey = user . pubkey ;
85- if ( usersByPubkey [ pubkey ] ) throw new Error ( `pubkey already exists: ${ name } : ${ pubkey } ` )
86- usersByName [ name ] = user ;
87- usersByPubkey [ pubkey ] = user ;
8882 user . balance ( ) . then ( value => console . debug ( user , value ) ) ;
8983 return user ;
9084}
@@ -313,14 +307,16 @@ const ProgramEditor = ({
313307 users = [ ] ,
314308 errors = Html ( [ 'pre.compile-errors.collapsible' ] ) . firstChild as HTMLElement ,
315309 params = Wasm . params ( source ) ,
316- fields = Object . entries ( params ) . map ( ProgramField ( { id, users, kind : 'param:: ' } ) ) ,
310+ fields = Object . entries ( params ) . map ( ProgramField ( { id, users, kind : 'Parameter: ' } ) ) ,
317311 compiler = Wasm . compiler ( { chain : chain . ID , genesis : chain . GENESIS } ) ,
318312 button = CompileButton ( id , { params, compiler, source, errors } ) ,
319313 stage1 = ProgramForm ( 'simf-compile' , SelectChain ( ) , ...fields , button ) ,
320314 amount = Label ( 'Commit amount:' , [ 'input.balance[type="number"]' , { value : '2345' } ] ) ,
321- sender = SelectSender ( { users } ) ,
315+ sender = SelectSender ( { chain , users } ) ,
322316 commit = CommitButton ( id , { users, params, compiler, source, errors, sender : sender . select } ) ,
323- stage2 = ProgramForm ( 'simf-commit' , [ 'label' , [ 'strong' , 'Sender:' ] , sender . select ] , [ 'div.row' , amount , sender . balance ] , commit ) ,
317+ title2 = [ 'label' , [ 'strong' , 'Fund program from:' ] , sender . select , sender . utxos ] ,
318+ //utxos2 = UtxoList({ esplora: chain.esplora }),
319+ stage2 = ProgramForm ( 'simf-commit' , title2 , [ 'div.row.gap' , amount , sender . balance ] , commit ) ,
324320} ) => Field ( id ) . open ( open ) . content ( TextArea ( id , source ) ) . content ( errors )
325321 . sidebar ( [ 'div.phase-form' , stage1 ] )
326322 . sidebar ( [ 'div.phase-form' , stage2 ] )
@@ -338,7 +334,7 @@ const ProgramForm = (id: string, ...rest: unknown[]) =>
338334 [ `div.program-form.col.grow#${ id } ` , ...rest ] ;
339335
340336const InputAmount = ( id : string , name = 'Amount:' ) =>
341- [ 'label.gap ' , [ 'strong' , name ] , [ 'input' , { id } ] ] ;
337+ [ 'label' , [ 'strong' , name ] , [ 'input' , { id } ] ] ;
342338
343339const SelectPubkey = ( id : string , {
344340 users = [ ] ,
@@ -375,9 +371,11 @@ const SelectPubkeyX = (id: string, {
375371} = { } ) => update ( ) ;
376372
377373const SelectSender = ( {
374+ chain = null ,
378375 users = [ ] ,
379376 name = 'Sender:' ,
380377 balance = InputBalance ( { label : [ 'strong' , 'Sender balance:' ] , p2wpkh : users [ 0 ] ?. p2wpkh } ) ,
378+ utxos = UtxoList ( { esplora : chain . esplora } ) ,
381379 view = Html ( [ 'div.col.select-sender' , Label ( name , [ 'select.pick-user' ] ) , balance ] ) ,
382380 input = view . querySelector ( 'input' ) ,
383381 select = Object . assign ( view . querySelector ( 'select' ) , { onchange : ( ) => update ( ) } ) ,
@@ -388,8 +386,11 @@ const SelectSender = ({
388386 select . value = pubkey ;
389387 const sender = users . find ( x => x . pubkey === pubkey ) ;
390388 console . log ( { sender, users, pubkey} ) ;
391- if ( sender ) getBalances ( sender . p2wpkh ) . then ( value => input . value = value ) ;
392- return { name, balance, view, input, select, update } ;
389+ if ( sender ) Promise . all ( [
390+ getBalances ( sender . p2wpkh ) . then ( value => input . value = value ) ,
391+ utxos . load ( sender . p2wpkh ) ,
392+ ] )
393+ return { name, balance, view, input, select, utxos, update } ;
393394 } ,
394395} = { } ) => update ( ) ;
395396
@@ -399,7 +400,7 @@ const InputBalance = ({
399400 label = [ 'strong' , 'Balance:' ] ,
400401 input = [ `input.balance[balance=${ p2wpkh } ]` , { disabled : true , value } ]
401402} = { } ) => {
402- return Html ( [ 'label.gap ' , label , input ] ) . firstChild ;
403+ return Html ( [ 'label.grow ' , label , input ] ) . firstChild ;
403404}
404405
405406const collectParams = ( id : string , params ) => {
@@ -418,25 +419,56 @@ const CompileButton = (id: string, {
418419 chain = Bitcoin . LiquidTestnet ,
419420 genesis = chain . GENESIS , // TODO autofetch from block 0
420421 compiler = Wasm . compiler ( { chain : chain . ID , genesis } ) ,
422+ balance = Html ( [ 'input[disabled]' ] ) . firstChild as HTMLInputElement ,
423+ utxos = UtxoList ( { esplora : chain . esplora } ) ,
421424 button = Button ( 'compile' , ( ) => compile ( ) ) ,
422425 errors = Html ( [ 'pre.compile-errors.collapsible' ] ) . firstChild as HTMLElement ,
423- input = Html ( [ 'input' , { placeholder : 'compile to get P2TR' } ] ) . firstChild as HTMLInputElement ,
424- view = Html ( [ 'label.col.gap.align-stretch' , [ 'label.justify-between.gap' ,
425- [ 'div.row.gap.align-center.justify-between' , [ 'strong' , 'Program address (P2TR):' ] , button ] , input ] , errors ] ) . firstChild ,
426+ input = Html ( [ 'input' , { placeholder : 'provide parameters and compile to get P2TR' } ] ) . firstChild as HTMLInputElement ,
427+ label = [ 'strong' , 'Program address (P2TR):' ] ,
428+ title = [ 'div.row.gap.align-center.justify-between' , label , button ] ,
429+ view = Html ( [ 'label.col.gap.align-stretch' , [ 'label.align-end' , title , input ] , utxos , errors ] ) . firstChild ,
426430 compile = ( ) => ErrorBoundaryAsync ( errors , async ( ) => {
427431 //const { default: Wasm } = await import('./wasm.ts');
428432 errors . innerText = '' ;
429433 errors . style . display = 'none' ;
430- const args = collectParams ( id , params ) ;
431- const program = compiler . compile ( source , { args } ) ;
432- const address = program . toJSON ( ) . p2tr ;
433- input . value = address ;
434- const balance = await getBalances ( address ) ;
435- console . debug ( 'Balance of' , address , 'is' , balance ) ;
436- document . querySelector ( '#simf-commit .balance' ) . value = String ( balance ) ;
434+ const args = collectParams ( id , params ) ;
435+ const prog = compiler . compile ( source , { args } ) ;
436+ const p2tr = prog . toJSON ( ) . p2tr ;
437+ input . value = p2tr ;
438+ return await Promise . all ( [
439+ utxos . load ( p2tr ) ,
440+ getBalances ( p2tr ) . then ( value => balance . value = String ( value ) ) ,
441+ ] )
437442 } )
438443} = { } ) => Object . assign ( view , { compile } ) as HTMLLabelElement & { compile : typeof compile } ;
439444
445+ const UtxoList = ( {
446+ esplora,
447+ address = null ,
448+ utxos = [ ] ,
449+ view = Html ( [ 'ul.utxos' ] ) . firstChild as HTMLElement ,
450+ state = ( ) => ( { address, utxos, load } ) ,
451+ load = ( addr = address ) => {
452+ address = addr ;
453+ if ( ! address ) return view ;
454+ console . debug ( 'Loading UTXOs for' , address ) ;
455+ view . innerText = 'Loading UTXOs...' ;
456+ return Object . assign ( ErrorBoundaryAsync ( view , initUtxoList ) , state ( ) ) ;
457+ async function initUtxoList ( ) {
458+ const utxos = await esplora . getAddressUtxos ( addr ) ;
459+ if ( utxos . length === 0 ) {
460+ view . innerText = 'No balance here. Send some funds!' ;
461+ } else {
462+ view . innerText = '' ;
463+ for ( const utxo of utxos ) {
464+ Html . append ( view , Html ( [ 'li' , 'UTXO ' , [ 'strong' , String ( utxo . value ) ] , [ 'code' , utxo . txid ] ] ) ) ;
465+ }
466+ }
467+ return view ;
468+ }
469+ }
470+ } ) => Object . assign ( view , state ( ) ) ;
471+
440472const CommitButton = ( id : string , {
441473 users = [ ] ,
442474 source = null ,
@@ -449,14 +481,14 @@ const CommitButton = (id: string, {
449481 view = Html ( [ 'label' , [ 'strong' , 'Commit TX:' ] , button ] ) . firstChild ,
450482 sender = null as HTMLInputElement ,
451483 commit = ( ) => ErrorBoundaryAsync ( errors , async ( ) => {
452- const args = collectParams ( id , params ) ;
453- const program = compiler . compile ( source , { args } ) ;
454- const address = program . toJSON ( ) . p2tr ;
484+ const args = collectParams ( id , params ) ;
485+ const prog = compiler . compile ( source , { args } ) ;
486+ const addr = prog . toJSON ( ) . p2tr ;
455487 const pubkey = sender . value ;
456488 const user = users . find ( x => x . pubkey === pubkey ) ;
457489 console . log ( { sender, pubkey, user, users} )
458490 if ( ! user ) throw new Error ( `not our pubkey: ${ user } ` ) ;
459- const utxos = await chain ( ) . esplora . getAddressUtxos ( user . p2wpkh ) as unknown [ ] ;
491+ const utxos = await chain ( ) . esplora . getAddressUtxos ( addr ) as unknown [ ] ;
460492 if ( utxos . length < 1 ) throw new Error ( `fund the address first: ${ user . p2wpkh } ` )
461493 } ) ,
462494} = { } ) => Object . assign ( view , { commit } ) as HTMLLabelElement & { commit : typeof commit } ;
0 commit comments