|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "encoding/hex" |
| 6 | + "errors" |
| 7 | + "fmt" |
| 8 | + "strings" |
| 9 | + |
| 10 | + "github.com/SundaeSwap-finance/kugo" |
| 11 | + "github.com/blinklabs-io/vpn-indexer/internal/config" |
| 12 | + "github.com/blinklabs-io/vpn-indexer/internal/database" |
| 13 | +) |
| 14 | + |
| 15 | +func getKupoClient() (*kugo.Client, error) { |
| 16 | + cfg := config.GetConfig() |
| 17 | + if cfg.TxBuilder.KupoUrl == "" { |
| 18 | + return nil, errors.New("no kupo url provided") |
| 19 | + } |
| 20 | + k := kugo.New(kugo.WithEndpoint(cfg.TxBuilder.KupoUrl)) |
| 21 | + if k == nil { |
| 22 | + return nil, fmt.Errorf("failed kupo client: %s", cfg.TxBuilder.KupoUrl) |
| 23 | + } |
| 24 | + return k, nil |
| 25 | +} |
| 26 | + |
| 27 | +func loadReferenceFromKugoClient(ctx context.Context) (database.Reference, error) { |
| 28 | + cfg := config.GetConfig() |
| 29 | + if strings.TrimSpace(cfg.TxBuilder.KupoUrl) == "" { |
| 30 | + return database.Reference{}, errors.New("kupo url not configured") |
| 31 | + } |
| 32 | + k, err := getKupoClient() |
| 33 | + if err != nil { |
| 34 | + return database.Reference{}, err |
| 35 | + } |
| 36 | + |
| 37 | + if tok := strings.TrimSpace(cfg.Indexer.ReferenceToken); tok != "" { |
| 38 | + if ref, err := refByKugoMatches(ctx, k, tok); err == nil { |
| 39 | + return ref, nil |
| 40 | + } |
| 41 | + } |
| 42 | + if addr := strings.TrimSpace(cfg.Indexer.ScriptAddress); addr != "" { |
| 43 | + return refByKugoMatches(ctx, k, addr) |
| 44 | + } |
| 45 | + |
| 46 | + return database.Reference{}, errors.New("neither reference token nor script address configured") |
| 47 | +} |
| 48 | + |
| 49 | +func refByKugoMatches(ctx context.Context, k *kugo.Client, pattern string) (database.Reference, error) { |
| 50 | + // Query matches by pattern (address). |
| 51 | + matches, err := k.Matches(ctx, kugo.Pattern(pattern)) |
| 52 | + if err != nil { |
| 53 | + return database.Reference{}, fmt.Errorf("kupo matches: %w", err) |
| 54 | + } |
| 55 | + if len(matches) == 0 { |
| 56 | + return database.Reference{}, fmt.Errorf("no matches for pattern %q", pattern) |
| 57 | + } |
| 58 | + match := matches[0] |
| 59 | + |
| 60 | + // Decode transaction_id (bytes to hex) |
| 61 | + txid, err := hex.DecodeString(parseHex(match.TransactionID)) |
| 62 | + if err != nil { |
| 63 | + return database.Reference{}, fmt.Errorf("txid decode: %w", err) |
| 64 | + } |
| 65 | + |
| 66 | + // Acquire datum CBOR using kugo only |
| 67 | + var datumCBOR []byte |
| 68 | + switch { |
| 69 | + case strings.TrimSpace(getDatumHash(match)) != "": |
| 70 | + hexStr, derr := k.Datum(ctx, parseHex(getDatumHash(match))) |
| 71 | + if derr != nil { |
| 72 | + return database.Reference{}, fmt.Errorf("fetch datum by hash: %w", derr) |
| 73 | + } |
| 74 | + datumCBOR, err = hex.DecodeString(parseHex(hexStr)) |
| 75 | + if err != nil { |
| 76 | + return database.Reference{}, fmt.Errorf("datum decode: %w", err) |
| 77 | + } |
| 78 | + default: |
| 79 | + return database.Reference{}, errors.New("reference utxo has no datum (no bytes/hash)") |
| 80 | + } |
| 81 | + |
| 82 | + // Decode reference plans and regions from datumCBOR |
| 83 | + plans, regions, err := decodeRefDatumFlexible(datumCBOR) |
| 84 | + if err != nil { |
| 85 | + return database.Reference{}, fmt.Errorf("decode ref datum: %w", err) |
| 86 | + } |
| 87 | + |
| 88 | + // Map to database.Reference expected by BuildSignupTx |
| 89 | + refPrices := make([]database.ReferencePrice, 0, len(plans)) |
| 90 | + for _, p := range plans { |
| 91 | + refPrices = append(refPrices, database.ReferencePrice{ |
| 92 | + Duration: p.Duration, |
| 93 | + Price: p.Price, |
| 94 | + }) |
| 95 | + } |
| 96 | + refRegions := make([]database.ReferenceRegion, 0, len(regions)) |
| 97 | + for _, r := range regions { |
| 98 | + refRegions = append(refRegions, database.ReferenceRegion{Name: r}) |
| 99 | + } |
| 100 | + |
| 101 | + return database.Reference{ |
| 102 | + TxId: txid, |
| 103 | + OutputIdx: match.OutputIndex, |
| 104 | + Prices: refPrices, |
| 105 | + Regions: refRegions, |
| 106 | + }, nil |
| 107 | +} |
| 108 | + |
| 109 | +func getDatumHash(m kugo.Match) string { |
| 110 | + return m.DatumHash |
| 111 | +} |
| 112 | + |
| 113 | +func parseHex(s string) string { |
| 114 | + s = strings.TrimSpace(s) |
| 115 | + s = strings.TrimPrefix(s, "0x") |
| 116 | + s = strings.TrimPrefix(s, "0X") |
| 117 | + return s |
| 118 | +} |
0 commit comments