@@ -26,6 +26,12 @@ import type { RoundReceipt } from "./receipt.js";
2626import { validateEncryptedBlob } from "./encrypted-blob.js" ;
2727import { networkFingerprint } from "./receipt.js" ;
2828import type { TransactionSubmitter } from "./submitter.js" ;
29+ import {
30+ evaluatePreflight ,
31+ classifyPreflightBuildError ,
32+ type PreflightOperation ,
33+ type PreflightResult ,
34+ } from "./preflight.js" ;
2935import {
3036 SubRosaClientConfigError ,
3137 SubRosaMissingReturnValueError ,
@@ -71,6 +77,10 @@ export interface SubRosaClientConfig {
7177 * @internal Testing hook: override the poll-loop sleep function.
7278 */
7379 _sleep ?: ( ms : number ) => Promise < void > ;
80+ /**
81+ * @internal Testing hook: inject a mock Soroban RPC server for simulation.
82+ */
83+ _server ?: rpc . Server ;
7484}
7585
7686export type ClearingRuleTag = ClearingRule [ "tag" ] ;
@@ -174,6 +184,7 @@ export class SubRosaClient {
174184 allowHttp,
175185 ...( source ? { publicKey : source } : { } ) ,
176186 ...( signer ? { signTransaction : signer . signTransaction } : { } ) ,
187+ ...( config . _server ? { server : config . _server } : { } ) ,
177188 } ) ;
178189 }
179190
@@ -348,6 +359,117 @@ export class SubRosaClient {
348359 await this . #sendUnwrap( tx ) ;
349360 }
350361
362+ // ── Preflight simulation (no signing/submission) ─────────────────────
363+
364+ async #preflight< T > (
365+ operation : PreflightOperation ,
366+ buildTx : ( ) => Promise < AssembledTransaction < Result < T > > > ,
367+ ) : Promise < PreflightResult < T > > {
368+ try {
369+ const tx = await buildTx ( ) ;
370+ return evaluatePreflight ( operation , tx ) ;
371+ } catch ( error ) {
372+ if ( error instanceof SubRosaClientConfigError ) {
373+ throw error ;
374+ }
375+ return {
376+ ok : false ,
377+ operation,
378+ error : classifyPreflightBuildError ( operation , error ) ,
379+ } ;
380+ }
381+ }
382+
383+ /** Simulate `createRound` without signing or submitting. */
384+ preflightCreateRound ( params : CreateRoundParams ) : Promise < PreflightResult < bigint > > {
385+ return this . #preflight( "create_round" , ( ) => {
386+ const operator = params . operator ?? this . #requireSource( "operator" ) ;
387+ const clearing_rule = {
388+ tag : params . clearingRule ?? "HighestBid" ,
389+ values : undefined ,
390+ } as ClearingRule ;
391+ return this . contract . create_round ( {
392+ operator,
393+ item_ref : toBuffer ( params . itemRef ) ,
394+ reveal_round : toBigInt ( params . revealRound ) ,
395+ clearing_rule,
396+ commit_deadline : toBigInt ( params . commitDeadline ) ,
397+ reveal_deadline : toBigInt ( params . revealDeadline ) ,
398+ auditor_pubkey : toBuffer ( params . auditorPubkey ) ,
399+ } ) ;
400+ } ) ;
401+ }
402+
403+ /** Simulate `commit` without signing or submitting. */
404+ preflightCommit ( params : CommitParams ) : Promise < PreflightResult < void > > {
405+ return this . #preflight( "commit" , ( ) => {
406+ const bidder = params . bidder ?? this . #requireSource( "bidder" ) ;
407+ return this . contract . commit ( {
408+ round_id : toBigInt ( params . roundId ) ,
409+ bidder,
410+ commitment : toBuffer ( params . sealed . commitment ) ,
411+ ciphertext : toBuffer ( params . sealed . ciphertext ) ,
412+ escrow : params . escrow ,
413+ auditor_blob : toBuffer ( params . sealed . auditorBlob ) ,
414+ } ) ;
415+ } ) ;
416+ }
417+
418+ /** Simulate `openReveal` without signing or submitting. */
419+ preflightOpenReveal (
420+ roundId : number | bigint ,
421+ drandSignature : Uint8Array ,
422+ ) : Promise < PreflightResult < void > > {
423+ return this . #preflight( "open_reveal" , ( ) =>
424+ this . contract . open_reveal ( {
425+ round_id : toBigInt ( roundId ) ,
426+ drand_signature : toBuffer ( drandSignature ) ,
427+ } ) ,
428+ ) ;
429+ }
430+
431+ /** Simulate `reveal` without signing or submitting. */
432+ preflightReveal ( params : RevealParams ) : Promise < PreflightResult < void > > {
433+ return this . #preflight( "reveal" , ( ) =>
434+ this . contract . reveal ( {
435+ round_id : toBigInt ( params . roundId ) ,
436+ bidder : params . bidder ,
437+ value : params . value ,
438+ nonce : toBuffer ( params . nonce ) ,
439+ } ) ,
440+ ) ;
441+ }
442+
443+ /** Simulate `clear` without signing or submitting. */
444+ async preflightClear (
445+ roundId : number | bigint ,
446+ ) : Promise < PreflightResult < string | undefined > > {
447+ const result = await this . #preflight< string | null | undefined > ( "clear" , ( ) =>
448+ this . contract . clear ( { round_id : toBigInt ( roundId ) } ) ,
449+ ) ;
450+ if ( ! result . ok ) {
451+ return result ;
452+ }
453+ return {
454+ ...result ,
455+ result : result . result ?? undefined ,
456+ } ;
457+ }
458+
459+ /** Simulate `settle` without signing or submitting. */
460+ preflightSettle ( roundId : number | bigint ) : Promise < PreflightResult < void > > {
461+ return this . #preflight( "settle" , ( ) =>
462+ this . contract . settle ( { round_id : toBigInt ( roundId ) } ) ,
463+ ) ;
464+ }
465+
466+ /** Simulate `void` without signing or submitting. */
467+ preflightVoid ( roundId : number | bigint ) : Promise < PreflightResult < void > > {
468+ return this . #preflight( "void" , ( ) =>
469+ this . contract . void ( { round_id : toBigInt ( roundId ) } ) ,
470+ ) ;
471+ }
472+
351473 // ── Read-only views (simulation only; no signing/submission) ───────────
352474
353475 async getRound ( roundId : number | bigint ) : Promise < Round > {
0 commit comments