@@ -21,6 +21,9 @@ import (
2121 "strings"
2222
2323 "github.com/blinklabs-io/gouroboros/cbor"
24+ "github.com/blinklabs-io/gouroboros/ledger/byron"
25+ "github.com/blinklabs-io/gouroboros/ledger/common"
26+ "github.com/blinklabs-io/gouroboros/ledger/shelley"
2427)
2528
2629const (
@@ -534,14 +537,115 @@ func (e *InsufficientCollateral) Error() string {
534537 )
535538}
536539
540+ // ScriptsNotPaidUtxo represents the ScriptsNotPaidUTxO error from cardano-ledger.
541+ // Haskell: ScriptsNotPaidUTxO !(UTxO era) where UTxO era = Map TxIn TxOut
542+ // CBOR: [14, utxo_map]
537543type ScriptsNotPaidUtxo struct {
538544 UtxoFailureErrorBase
539- // TODO: determine content/structure of this value (#847)
540- Value cbor.Value
545+ Utxos []common.Utxo // Each Utxo contains Id (input) and Output
546+ }
547+
548+ func (e * ScriptsNotPaidUtxo ) MarshalCBOR () ([]byte , error ) {
549+ // Set the struct tag for consistency
550+ e .Type = UtxoFailureScriptsNotPaidUtxo
551+
552+ utxoMap := make (
553+ map [common.TransactionInput ]common.TransactionOutput ,
554+ len (e .Utxos ),
555+ )
556+ for _ , u := range e .Utxos {
557+ // Return error for nil entries instead of silently skipping
558+ if u .Id == nil || u .Output == nil {
559+ return nil , errors .New (
560+ "ScriptsNotPaidUtxo: cannot marshal UTxO with nil Id or Output" ,
561+ )
562+ }
563+ utxoMap [u .Id ] = u .Output
564+ }
565+ arr := []any {UtxoFailureScriptsNotPaidUtxo , utxoMap }
566+ return cbor .Encode (arr )
567+ }
568+
569+ func (e * ScriptsNotPaidUtxo ) UnmarshalCBOR (data []byte ) error {
570+ type tScriptsNotPaidUtxo struct {
571+ cbor.StructAsArray
572+ ConstructorIdx uint64
573+ UtxoMapCbor cbor.RawMessage
574+ }
575+ var tmp tScriptsNotPaidUtxo
576+ if _ , err := cbor .Decode (data , & tmp ); err != nil {
577+ return fmt .Errorf ("failed to decode ScriptsNotPaidUtxo: %w" , err )
578+ }
579+
580+ if tmp .ConstructorIdx != UtxoFailureScriptsNotPaidUtxo {
581+ return fmt .Errorf (
582+ "ScriptsNotPaidUtxo: expected constructor index %d, got %d" ,
583+ UtxoFailureScriptsNotPaidUtxo ,
584+ tmp .ConstructorIdx ,
585+ )
586+ }
587+
588+ // Set the struct tag to match the decoded constructor
589+ e .Type = UtxoFailureScriptsNotPaidUtxo
590+
591+ // For era-agnostic decoding, we need to handle the map structure carefully
592+ // Since we can't use cbor.RawMessage as map keys, we'll decode to a concrete type first
593+ // and then convert to era-agnostic types. Try different era input types until one works.
594+
595+ // Try Shelley-family transaction inputs first (most common from Shelley onwards)
596+ var shelleyUtxoMap map [shelley.ShelleyTransactionInput ]cbor.RawMessage
597+ if _ , err := cbor .Decode (tmp .UtxoMapCbor , & shelleyUtxoMap ); err == nil {
598+ // Successfully decoded as Shelley-family inputs
599+ e .Utxos = make ([]common.Utxo , 0 , len (shelleyUtxoMap ))
600+ for input , outputCbor := range shelleyUtxoMap {
601+ // Decode output using era-agnostic function (handles all eras)
602+ output , err := NewTransactionOutputFromCbor (outputCbor )
603+ if err != nil {
604+ return fmt .Errorf (
605+ "failed to decode transaction output: %w" ,
606+ err ,
607+ )
608+ }
609+
610+ e .Utxos = append (e .Utxos , common.Utxo {
611+ Id : input ,
612+ Output : output ,
613+ })
614+ }
615+ return nil
616+ }
617+
618+ // Try Byron transaction inputs (for Byron era)
619+ var byronUtxoMap map [byron.ByronTransactionInput ]cbor.RawMessage
620+ if _ , err := cbor .Decode (tmp .UtxoMapCbor , & byronUtxoMap ); err == nil {
621+ // Successfully decoded as Byron inputs
622+ e .Utxos = make ([]common.Utxo , 0 , len (byronUtxoMap ))
623+ for input , outputCbor := range byronUtxoMap {
624+ // Decode output using era-agnostic function (handles all eras)
625+ output , err := NewTransactionOutputFromCbor (outputCbor )
626+ if err != nil {
627+ return fmt .Errorf (
628+ "failed to decode transaction output: %w" ,
629+ err ,
630+ )
631+ }
632+
633+ e .Utxos = append (e .Utxos , common.Utxo {
634+ Id : input ,
635+ Output : output ,
636+ })
637+ }
638+ return nil
639+ }
640+
641+ // If both failed, return an error
642+ return errors .New (
643+ "failed to decode UTxO map as either Shelley-family or Byron transaction inputs" ,
644+ )
541645}
542646
543647func (e * ScriptsNotPaidUtxo ) Error () string {
544- return fmt .Sprintf ("ScriptsNotPaidUtxo (%v )" , e . Value . Value ( ))
648+ return fmt .Sprintf ("ScriptsNotPaidUtxo (%d UTxOs )" , len ( e . Utxos ))
545649}
546650
547651type ExUnitsTooBigUtxo struct {
0 commit comments