2025-11-07 15:07:35 +07:00

812 lines
23 KiB
Go

package wallet
import (
"cmp"
context "context"
"crypto/rand"
"crypto/sha256"
"fmt"
"slices"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcutil/base58"
"github.com/cosmos/go-bip39"
"github.com/phamminh0811/private-grpc/crypto"
"github.com/phamminh0811/private-grpc/nockchain"
)
const (
WitnessWordsCount = 55
SignatureWordsCount = 31
SeedWordsCount = 13
BaseFee = 1 << 15
)
type GprcHandler struct {
nockchain.UnimplementedWalletServiceServer
client NockchainClient
}
func NewGprcHandler(client NockchainClient) GprcHandler {
return GprcHandler{
client: client,
}
}
func (h *GprcHandler) Keygen(ctx context.Context, req *nockchain.KeygenRequest) (*nockchain.KeygenResponse, error) {
var entropy [32]byte
_, err := rand.Read(entropy[:]) // Fill the slice with random bytes
if err != nil {
return nil, err
}
var salt [16]byte
_, err = rand.Read(salt[:])
if err != nil {
return nil, err
}
argonBytes := crypto.DeriveKey(0, entropy[:], salt[:], nil, nil, 6, 786432, 4, 32)
slices.Reverse(argonBytes)
mnemonic, err := bip39.NewMnemonic(argonBytes)
if err != nil {
return nil, fmt.Errorf("failed to generate mnemonic: %v", err)
}
masterKey, err := crypto.MasterKeyFromSeed(mnemonic)
if err != nil {
return nil, err
}
privBytes := append([]byte{0x00}, masterKey.PrivateKey...)
address := ""
if req.Version == 0 {
address = base58.Encode(masterKey.PublicKey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
return &nockchain.KeygenResponse{
Seed: mnemonic,
PrivateKey: base58.Encode(masterKey.PrivateKey),
Address: address,
ChainCode: base58.Encode(masterKey.ChainCode),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, int(req.Version), crypto.KeyType_PRIVATE)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, int(req.Version), crypto.KeyType_PUBLIC)),
Version: req.Version,
}, nil
}
func (h *GprcHandler) ImportKeys(ctx context.Context, req *nockchain.ImportKeysRequest) (*nockchain.ImportKeysResponse, error) {
switch req.ImportType {
case nockchain.ImportType_UNDEFINED:
return nil, fmt.Errorf("invalid import type")
case nockchain.ImportType_EXTENDED_KEY:
// metadata layout: [version][depth][parent-fp][index][chain-code][key-data][checksum]
data := base58.Decode(req.Key)
switch {
case strings.HasPrefix(req.Key, "zprv"):
if len(data) != 83 {
return nil, fmt.Errorf("invalid extended private key length: %d (expected 83)", len(data))
}
if data[46] != 0x00 {
return nil, fmt.Errorf("invalid private key prefix at byte 46: 0x%02x (expected 0x00)", data[46])
}
hash := sha256.Sum256(data[:79])
hash = sha256.Sum256(hash[:])
if !slices.Equal(hash[:4], data[79:]) {
return nil, fmt.Errorf("invalid checksum")
}
chainCode := make([]byte, 32)
copy(chainCode, data[14:46])
privateKey := make([]byte, 32)
copy(privateKey, data[47:79])
masterKey, err := crypto.MasterKeyFromPrivKey(chainCode, privateKey)
if err != nil {
return nil, err
}
privBytes := append([]byte{0x00}, masterKey.PrivateKey...)
address := ""
if req.Version == 0 {
address = base58.Encode(masterKey.PublicKey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
return &nockchain.ImportKeysResponse{
Seed: "",
PrivateKey: base58.Encode(masterKey.PrivateKey),
Address: address,
ChainCode: base58.Encode(masterKey.ChainCode),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, int(req.Version), crypto.KeyType_PRIVATE)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, int(req.Version), crypto.KeyType_PUBLIC)),
Version: req.Version,
}, nil
case strings.HasPrefix(req.Key, "zpub"):
if len(data) != 147 {
return nil, fmt.Errorf("invalid extended public key length: %d (expected 147)", len(data))
}
hash := sha256.Sum256(data[:143])
hash = sha256.Sum256(hash[:])
if !slices.Equal(hash[:4], data[143:]) {
return nil, fmt.Errorf("invalid checksum")
}
chainCode := make([]byte, 32)
copy(chainCode, data[14:46])
publicKey := make([]byte, 97)
copy(publicKey, data[46:143])
address := ""
if req.Version == 0 {
address = base58.Encode(publicKey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(publicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
return &nockchain.ImportKeysResponse{
Seed: "",
PrivateKey: "",
Address: address,
ChainCode: base58.Encode(chainCode),
ImportPrivateKey: "",
ImportPublicKey: base58.Encode(crypto.SerializeExtend(chainCode, publicKey, int(req.Version), crypto.KeyType_PUBLIC)),
Version: req.Version,
}, nil
default:
return nil, fmt.Errorf("invalid extended key")
}
case nockchain.ImportType_MASTER_PRIVKEY:
splits := strings.Split(req.Key, ",")
if len(splits) != 2 {
return nil, fmt.Errorf("master key must be in [chain_code],[key] format")
}
chainCode := base58.Decode(splits[0])
if len(chainCode) != 32 {
return nil, fmt.Errorf("invalid chain code length: %d, must be 32", len(chainCode))
}
key := base58.Decode(splits[1])
if len(key) != 32 {
return nil, fmt.Errorf("invalid priv key length: %d, must be 32", len(key))
}
masterKey, err := crypto.MasterKeyFromPrivKey(chainCode, key)
if err != nil {
return nil, err
}
privBytes := append([]byte{0x00}, masterKey.PrivateKey...)
address := ""
if req.Version == 0 {
address = base58.Encode(masterKey.PublicKey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
return &nockchain.ImportKeysResponse{
Seed: "",
PrivateKey: base58.Encode(masterKey.PrivateKey),
Address: address,
ChainCode: base58.Encode(masterKey.ChainCode),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, int(req.Version), crypto.KeyType_PRIVATE)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, int(req.Version), crypto.KeyType_PUBLIC)),
Version: req.Version,
}, nil
case nockchain.ImportType_SEEDPHRASE:
masterKey, err := crypto.MasterKeyFromSeed(req.Key)
if err != nil {
return nil, err
}
privBytes := append([]byte{0x00}, masterKey.PrivateKey...)
address := ""
if req.Version == 0 {
address = base58.Encode(masterKey.PublicKey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
return &nockchain.ImportKeysResponse{
Seed: "",
PrivateKey: base58.Encode(masterKey.PrivateKey),
Address: address,
ChainCode: base58.Encode(masterKey.ChainCode),
ImportPrivateKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, privBytes, int(req.Version), crypto.KeyType_PRIVATE)),
ImportPublicKey: base58.Encode(crypto.SerializeExtend(masterKey.ChainCode, masterKey.PublicKey, int(req.Version), crypto.KeyType_PUBLIC)),
Version: req.Version,
}, nil
case nockchain.ImportType_WATCH_ONLY:
pubkey := base58.Decode(req.Key)
address := ""
if req.Version == 0 {
address = base58.Encode(pubkey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(pubkey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
return &nockchain.ImportKeysResponse{
Seed: "",
PrivateKey: "",
Address: address,
ChainCode: "",
ImportPrivateKey: "",
ImportPublicKey: "",
Version: req.Version,
}, nil
default:
return nil, fmt.Errorf("invalid import type")
}
}
func (h *GprcHandler) DeriveChild(ctx context.Context, req *nockchain.DeriveChildRequest) (*nockchain.DeriveChildResponse, error) {
data := base58.Decode(req.ImportedKey)
index := req.Index
if index > 1<<32 {
return nil, fmt.Errorf("child index %d out of range, child indices are capped to values between [0, 2^32)", index)
}
if req.Hardened {
index += 1 << 31
}
switch {
case strings.HasPrefix(req.ImportedKey, "zprv"):
if len(data) != 83 {
return nil, fmt.Errorf("invalid extended private key length: %d (expected 83)", len(data))
}
if data[46] != 0x00 {
return nil, fmt.Errorf("invalid private key prefix at byte 46: 0x%02x (expected 0x00)", data[46])
}
hash := sha256.Sum256(data[:79])
hash = sha256.Sum256(hash[:])
if !slices.Equal(hash[:4], data[79:]) {
return nil, fmt.Errorf("invalid checksum")
}
chainCode := make([]byte, 32)
copy(chainCode, data[14:46])
privateKey := make([]byte, 32)
copy(privateKey, data[47:79])
masterKey, err := crypto.MasterKeyFromPrivKey(chainCode, privateKey)
if err != nil {
return nil, err
}
childKey, err := masterKey.DeriveChild(index)
if err != nil {
return nil, err
}
address := ""
if req.Version == 0 {
address = base58.Encode(childKey.PublicKey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(childKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
return &nockchain.DeriveChildResponse{
Address: address,
PrivateKey: base58.Encode(childKey.PrivateKey),
ChainCode: base58.Encode(childKey.ChainCode),
Version: req.Version,
}, nil
case strings.HasPrefix(req.ImportedKey, "zpub"):
if len(data) != 147 {
return nil, fmt.Errorf("invalid extended public key length: %d (expected 145)", len(data))
}
hash := sha256.Sum256(data[:143])
hash = sha256.Sum256(hash[:])
if !slices.Equal(hash[:4], data[143:]) {
return nil, fmt.Errorf("invalid checksum")
}
chainCode := make([]byte, 32)
copy(chainCode, data[14:46])
publicKey := make([]byte, 97)
copy(publicKey, data[46:143])
masterKey := crypto.MasterKey{
PublicKey: publicKey,
ChainCode: chainCode,
PrivateKey: []byte{},
}
childKey, err := masterKey.DeriveChild(index)
if err != nil {
return nil, err
}
address := ""
if req.Version == 0 {
address = base58.Encode(childKey.PublicKey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(childKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
return &nockchain.DeriveChildResponse{
Address: address,
PrivateKey: "",
ChainCode: base58.Encode(childKey.ChainCode),
Version: req.Version,
}, nil
default:
return nil, fmt.Errorf("invalid extended key")
}
}
// - `names` - Comma-separated list of note name pairs in format "[first last]"
// Example: "[first1 last1],[first2 last2]"
//
// - `recipients` Comma-separated list of transaction output, formatted as "<recipient1>:<amount1>,<recipient1>:<amount1>"
//
// - `fee` - Transaction fee to be subtracted from one of the input notes
func (h *GprcHandler) CreateTx(ctx context.Context, req *nockchain.CreateTxRequest) (*nockchain.CreateTxResponse, error) {
nnames := []*nockchain.NockchainName{}
names := strings.Split(req.Names, ",")
notesV0 := make([]*nockchain.NockchainNoteV0, len(names))
notesV1 := make([]*nockchain.NockchainNoteV1, len(names))
for _, name := range names {
name = strings.TrimSpace(name)
if strings.HasPrefix(name, "[") && strings.HasSuffix(name, "]") {
inner := name[1 : len(name)-1]
part := strings.Split(inner, " ")
if len(part) == 2 {
nnames = append(nnames, &nockchain.NockchainName{
First: part[0],
Last: part[1],
})
}
}
}
specs := strings.Split(req.Recipients, ",")
if len(specs) == 0 {
return nil, fmt.Errorf("at least one output must be provided")
}
if len(specs) > 1 {
return nil, fmt.Errorf("multiple outputs are not supported yet, provide a single <pkh>:<amount> pair")
}
spec := specs[0]
pairs := strings.Split(spec, ":")
if len(pairs) != 2 {
return nil, fmt.Errorf("%s", "Invalid output spec "+spec+", expected <pkh>:<amount>")
}
gift, err := strconv.ParseUint(strings.TrimSpace(pairs[1]), 10, 64)
if err != nil {
return nil, err
}
masterKey, err := crypto.MasterKeyFromSeed(req.Seed)
if err != nil {
return nil, err
}
if !req.IsMasterKey {
index := req.Index
if index > 1<<32 {
return nil, fmt.Errorf("child index %d out of range, child indices are capped to values between [0, 2^32)", index)
}
if req.Hardened {
index += 1 << 31
}
childKey, err := masterKey.DeriveChild(index)
if err != nil {
return nil, err
}
masterKey = &childKey
}
masterPkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(masterPkPoint)
recipent := &nockchain.NockchainLock{
KeysRequired: 1,
Pubkeys: []string{strings.TrimSpace(pairs[0])},
}
if req.Version == 0 && req.RefundAddress == "" {
return nil, fmt.Errorf("need to specify a refund address if spending from v0 notes")
}
refundAddr := req.RefundAddress
if refundAddr == "" {
refundAddr = crypto.Tip5HashToBase58(pkHash)
}
refundLock := &nockchain.NockchainLock{
KeysRequired: 1,
Pubkeys: []string{refundAddr},
}
ownerLock := &nockchain.NockchainLock{
KeysRequired: 1,
Pubkeys: []string{crypto.Tip5HashToBase58(pkHash)},
}
ownerHash := HashLock(ownerLock)
// Scan key to get notes
var scanReq *nockchain.GetBalanceRequest
switch req.Version {
case 0:
scanReq = &nockchain.GetBalanceRequest{
Selector: &nockchain.GetBalanceRequest_Address{
Address: base58.Encode(masterKey.PublicKey),
},
}
case 1:
firstName := crypto.Tip5RehashTenCell(crypto.Tip5Zero, ownerHash)
scanReq = &nockchain.GetBalanceRequest{
Selector: &nockchain.GetBalanceRequest_FirstName{
FirstName: crypto.Tip5HashToBase58(firstName),
},
}
default:
return nil, fmt.Errorf("unsuport version")
}
masterKeyScan, err := h.client.WalletGetBalance(scanReq)
if err != nil {
return nil, err
}
if len(masterKeyScan.Notes) != 0 {
for _, note := range masterKeyScan.Notes {
firstName := crypto.Tip5HashToBase58([5]uint64{
note.Name.First.Belt_1.Value,
note.Name.First.Belt_2.Value,
note.Name.First.Belt_3.Value,
note.Name.First.Belt_4.Value,
note.Name.First.Belt_5.Value,
})
lastName := crypto.Tip5HashToBase58([5]uint64{
note.Name.Last.Belt_1.Value,
note.Name.Last.Belt_2.Value,
note.Name.Last.Belt_3.Value,
note.Name.Last.Belt_4.Value,
note.Name.Last.Belt_5.Value,
})
name := "[" + firstName + " " + lastName + "]"
if i := slices.Index(names, name); i != -1 {
balanceEntry := ParseBalanceEntry(note)
switch req.Version {
case 0:
notesV0[i] = balanceEntry.GetV0()
case 1:
notesV1[i] = balanceEntry.GetV1()
}
}
}
}
giftRemaining := gift
feeRemaining := req.Fee
wordCount := 0
for i := 0; i < len(names); i++ {
switch req.Version {
case 0:
if notesV0[i] == nil {
return nil, fmt.Errorf("notes scanned is missing at name: %s", names[i])
}
case 1:
if notesV1[i] == nil {
return nil, fmt.Errorf("notes scanned is missing at name: %s", names[i])
}
}
}
notesV0Sort := make([]*nockchain.NockchainNoteV0, len(notesV0))
copy(notesV0Sort, notesV0)
notesV1Sort := make([]*nockchain.NockchainNoteV1, len(notesV1))
copy(notesV1Sort, notesV1)
switch req.Version {
case 0:
slices.SortFunc(notesV0Sort, func(note1, note2 *nockchain.NockchainNoteV0) int {
return cmp.Compare(note2.Asset, note1.Asset)
})
case 1:
slices.SortFunc(notesV1Sort, func(note1, note2 *nockchain.NockchainNoteV1) int {
return cmp.Compare(note2.Assets, note1.Assets)
})
}
spends := []*nockchain.NockchainNamedSpend{}
for i := 0; i < len(names); i++ {
asset := uint64(0)
switch req.Version {
case 0:
asset = notesV0Sort[i].Asset
case 1:
asset = notesV1Sort[i].Assets
}
giftPortion := min(giftRemaining, asset)
feeAvailable := asset - giftPortion
feePortion := min(feeRemaining, feeAvailable)
if giftPortion == 0 && feePortion == 0 {
continue
}
giftRemaining = giftRemaining - giftPortion
feeRemaining = feeRemaining - feePortion
refund := asset - giftPortion - feePortion
if refund == 0 && giftPortion == 0 {
continue
}
var parentHash [5]uint64
switch req.Version {
case 0:
parentHash, err = HashNoteV0(notesV0Sort[i])
if err != nil {
return nil, err
}
case 1:
parentHash = HashNoteV1(notesV1Sort[i])
}
lockRoot := HashLock(recipent)
seeds := []*nockchain.NockchainSeed{}
if giftPortion != 0 {
wordCount += SeedWordsCount
seeds = append(seeds, &nockchain.NockchainSeed{
OutputSource: nil,
LockRoot: crypto.Tip5HashToBase58(lockRoot),
NoteData: recipent,
Gift: giftPortion,
ParentHash: crypto.Tip5HashToBase58(parentHash),
})
}
if refund != 0 {
wordCount += SeedWordsCount
lockRoot := HashLock(refundLock)
seeds = append(seeds, &nockchain.NockchainSeed{
OutputSource: nil,
LockRoot: crypto.Tip5HashToBase58(lockRoot),
NoteData: refundLock,
Gift: refund,
ParentHash: crypto.Tip5HashToBase58(parentHash),
})
}
msg, err := HashMsg(seeds, feePortion)
if err != nil {
return nil, err
}
// sign
chalT8, sigT8, err := ComputeSig(*masterKey, msg)
if err != nil {
return nil, err
}
sigs := []*nockchain.NockchainSignature{
{
Pubkey: base58.Encode(masterKey.PublicKey),
Chal: chalT8[:],
Sig: sigT8[:],
},
}
switch req.Version {
case 0:
spend := nockchain.NockchainSpendV0{
Signatures: nil,
Seeds: seeds,
Fee: feePortion,
}
spend.Signatures = sigs
wordCount += SignatureWordsCount
nameIdx := -1
for j := 0; j < len(names); j++ {
if nnames[j].Last == notesV0Sort[i].Name.Last {
nameIdx = j
break
}
}
spends = append(spends, &nockchain.NockchainNamedSpend{
Name: nnames[nameIdx],
SpendKind: &nockchain.NockchainNamedSpend_Legacy{
Legacy: &spend,
},
})
case 1:
spend := nockchain.NockchainSpendV1{
Witness: nil,
Seeds: seeds,
Fee: feePortion,
}
spend.Witness = []*nockchain.NockchainWitness{
{
Lmp: &nockchain.NockchainLockMerkleProof{
SpendCondition: ownerLock,
Axis: 1,
MerkleRoot: crypto.Tip5HashToBase58(ownerHash),
},
Pkh: sigs,
},
}
wordCount += WitnessWordsCount
nameIdx := -1
for j := 0; j < len(names); j++ {
if nnames[j].Last == notesV1Sort[i].Name.Last {
nameIdx = j
break
}
}
spends = append(spends, &nockchain.NockchainNamedSpend{
Name: nnames[nameIdx],
SpendKind: &nockchain.NockchainNamedSpend_Witness{
Witness: &spend,
},
})
}
}
if giftRemaining != 0 || feeRemaining != 0 {
return nil, fmt.Errorf("insufficient funds to pay fee and gift")
}
if wordCount*BaseFee > int(req.Fee) {
return nil, fmt.Errorf("min fee not met, this transaction requires at least: %d", wordCount*BaseFee)
}
rawTx := nockchain.RawTx{
TxId: "",
Version: 1,
NamedSpends: spends,
}
txId, err := ComputeTxId(spends, req.Version)
if err != nil {
return nil, err
}
rawTx.TxId = crypto.Tip5HashToBase58(txId)
return &nockchain.CreateTxResponse{
RawTx: &rawTx,
}, nil
}
func (h *GprcHandler) Scan(ctx context.Context, req *nockchain.ScanRequest) (*nockchain.ScanResponse, error) {
scanData := []*nockchain.ScanData{}
keyBytes := base58.Decode(req.MasterPubkey)
chainCode := base58.Decode(req.ChainCode)
masterKey := crypto.MasterKey{
PublicKey: keyBytes,
ChainCode: chainCode,
PrivateKey: []byte{},
}
address := ""
if req.Version == 0 {
address = base58.Encode(masterKey.PublicKey)
} else {
pkPoint, err := crypto.CheetaPointFromBytes(masterKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
address = crypto.Tip5HashToBase58(pkHash)
}
masterKeyScan, err := h.client.WalletGetBalance(&nockchain.GetBalanceRequest{
Selector: &nockchain.GetBalanceRequest_Address{
Address: address,
},
})
if err != nil {
return nil, err
}
if len(masterKeyScan.Notes) != 0 {
scanData = append(scanData, &nockchain.ScanData{
Pubkey: req.MasterPubkey,
Data: masterKeyScan,
})
}
for i := uint64(0); i < req.SearchDepth; i++ {
childKey, err := masterKey.DeriveChild(i)
if err != nil {
continue
}
pkPoint, err := crypto.CheetaPointFromBytes(childKey.PublicKey)
if err != nil {
return nil, err
}
pkHash := HashPubkey(pkPoint)
ownerLock := &nockchain.NockchainLock{
KeysRequired: 1,
Pubkeys: []string{crypto.Tip5HashToBase58(pkHash)},
}
ownerHash := HashLock(ownerLock)
var scanReq *nockchain.GetBalanceRequest
switch req.Version {
case 0:
scanReq = &nockchain.GetBalanceRequest{
Selector: &nockchain.GetBalanceRequest_Address{
Address: base58.Encode(childKey.PublicKey),
},
}
case 1:
firstName := crypto.Tip5RehashTenCell(crypto.Tip5Zero, ownerHash)
scanReq = &nockchain.GetBalanceRequest{
Selector: &nockchain.GetBalanceRequest_FirstName{
FirstName: crypto.Tip5HashToBase58(firstName),
},
}
default:
return nil, fmt.Errorf("unsuport version")
}
childKeyScan, err := h.client.WalletGetBalance(scanReq)
if err != nil {
continue
}
if len(childKeyScan.Notes) != 0 {
scanData = append(scanData, &nockchain.ScanData{
Pubkey: base58.Encode(childKey.PublicKey),
Data: childKeyScan,
})
}
}
return &nockchain.ScanResponse{
ScanData: scanData,
}, nil
}
func (h *GprcHandler) WalletGetBalance(_ context.Context, req *nockchain.GetBalanceRequest) (*nockchain.GetBalanceResponse, error) {
data, err := h.client.WalletGetBalance(req)
return &nockchain.GetBalanceResponse{
Data: data,
}, err
}
func (h *GprcHandler) WalletSendTransaction(_ context.Context, req *nockchain.SendTransactionRequest) (*nockchain.SendTransactionResponse, error) {
resp, err := h.client.WalletSendTransaction(req.RawTx)
return &nockchain.SendTransactionResponse{
Response: resp,
}, err
}
func (h *GprcHandler) TransactionAccepted(_ context.Context, req *nockchain.TransactionAcceptedRequest) (*nockchain.TransactionAcceptedResponse, error) {
return h.client.TxAccepted(req.TxId.Hash)
}