Skip to content

Commit

Permalink
feat: moves models to specific directories
Browse files Browse the repository at this point in the history
  • Loading branch information
wregulski committed Jun 1, 2023
1 parent 02be828 commit 969b04a
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 292 deletions.
174 changes: 174 additions & 0 deletions apis/mapi/fee_quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package mapi

import (
"fmt"
"strings"

"github.com/libsv/go-bt/v2"
)

const (

// FeeTypeData is the key corresponding to the data rate
FeeTypeData = "data"

// FeeTypeStandard is the key corresponding to the standard rate
FeeTypeStandard = "standard"

// FeeCategoryMining is the category corresponding to the mining rate
FeeCategoryMining = "mining"

// FeeCategoryRelay is the category corresponding to the relay rate
FeeCategoryRelay = "relay"
)

/*
Example feeQuote response from Merchant API:
{
"payload": "{\"apiVersion\":\"1.4.0\",\"timestamp\":\"2020-10-07T21:13:04.335Z\",\"expiryTime\":\"2020-10-07T21:23:04.335Z\",\"minerId\":\"0211ccfc29e3058b770f3cf3eb34b0b2fd2293057a994d4d275121be4151cdf087\",\"currentHighestBlockHash\":\"000000000000000000edb30c3bbbc8e6a07e522e85522e6a213f7e933e6e2d8d\",\"currentHighestBlockHeight\":655874,\"minerReputation\":null,\"fees\":[{\"feeType\":\"standard\",\"miningFee\":{\"satoshis\":500,\"bytes\":1000},\"relayFee\":{\"satoshis\":250,\"bytes\":1000}},{\"feeType\":\"data\",\"miningFee\":{\"satoshis\":500,\"bytes\":1000},\"relayFee\":{\"satoshis\":250,\"bytes\":1000}}]}",
"signature": "304402206443bea5bdd98a16e23eb61c36b4b998bd68ceb9c84983c7e695e267b21a30440220191571e9b9632c8337d9196723ca20eefa63966ef6360170db0e57a04047453f",
"publicKey": "0211ccfc29e3058b770f3cf3eb34b0b2fd2293057a994d4d275121be4151cdf087",
"encoding": "UTF-8",
"mimetype": "application/json"
}
*/

/*
Example FeeQuoteResponse.Payload (unmarshalled):
{
"apiVersion": "1.4.0",
"timestamp": "2020-10-07T21:13:04.335Z",
"expiryTime": "2020-10-07T21:23:04.335Z",
"minerId": "0211ccfc29e3058b770f3cf3eb34b0b2fd2293057a994d4d275121be4151cdf087",
"currentHighestBlockHash": "000000000000000000edb30c3bbbc8e6a07e522e85522e6a213f7e933e6e2d8d",
"currentHighestBlockHeight": 655874,
"fees": [
{
"feeType": "standard",
"miningFee": {
"satoshis": 500,
"bytes": 1000
},
"relayFee": {
"satoshis": 250,
"bytes": 1000
}
},
{
"feeType": "data",
"miningFee": {
"satoshis": 500,
"bytes": 1000
},
"relayFee": {
"satoshis": 250,
"bytes": 1000
}
}
]
}
*/

// FeePayload is the unmarshalled version of the payload envelope
type FeePayload struct {
FeePayloadFields
Fees []*bt.Fee `json:"fees"`
}

type (

// rawFeePayload is the unmarshalled version of the payload envelope
RawFeePayload struct {
FeePayloadFields
Callbacks []*PolicyCallback `json:"callbacks"` // IP addresses of double-spend notification servers such as mAPI reference implementation
Fees []*feeObj `json:"fees"`
}

// feePayloadFields are the same fields in both payloads
FeePayloadFields struct {
APIVersion string `json:"apiVersion"`
Timestamp string `json:"timestamp"`
ExpirationTime string `json:"expiryTime"`
MinerID string `json:"minerId"`
CurrentHighestBlockHash string `json:"currentHighestBlockHash"`
CurrentHighestBlockHeight uint64 `json:"currentHighestBlockHeight"`
MinerReputation interface{} `json:"minerReputation"` // Not sure what this value is
}

// feeUnit displays the amount of Satoshis needed
// for a specific amount of Bytes in a transaction
// see https://github.com/bitcoin-sv-specs/brfc-merchantapi#expanded-payload-1
FeeUnit struct {
Satoshis int `json:"satoshis"` // Fee in satoshis of the amount of Bytes
Bytes int `json:"bytes"` // Number of bytes that the Fee covers
}

// feeObj displays the MiningFee as well as the RelayFee for a specific
// FeeType, for example 'standard' or 'data'
// see https://github.com/bitcoin-sv-specs/brfc-merchantapi#expanded-payload-1
feeObj struct {
FeeType string `json:"feeType"` // standard || data
MiningFee FeeUnit `json:"miningFee"`
RelayFee FeeUnit `json:"relayFee"` // Fee for retaining Tx in secondary mempool
}
)

// CalculateFee will return the fee for the given txBytes
// Type: "FeeTypeData" or "FeeTypeStandard"
// Category: "FeeCategoryMining" or "FeeCategoryRelay"
//
// If no fee is found or fee is 0, returns 1 & error
//
// Spec: https://github.com/bitcoin-sv-specs/brfc-misc/tree/master/feespec#deterministic-transaction-fee-calculation-dtfc
func (f *FeePayload) CalculateFee(feeCategory, feeType string, txBytes uint64) (uint64, error) {

// Valid feeType?
if !strings.EqualFold(feeType, FeeTypeData) && !strings.EqualFold(feeType, FeeTypeStandard) {
return 0, fmt.Errorf("feeType %s is not recognized", feeType)
} else if !strings.EqualFold(feeCategory, FeeCategoryMining) && !strings.EqualFold(feeCategory, FeeCategoryRelay) {
return 0, fmt.Errorf("feeCategory %s is not recognized", feeCategory)
}

// Loop all fee types looking for feeType (data or standard)
for _, fee := range f.Fees {

// Detect the type (data or standard)
if string(fee.FeeType) != feeType {
continue
}

// Multiply & Divide
var calcFee uint64
if strings.EqualFold(feeCategory, FeeCategoryMining) {
calcFee = (uint64(fee.MiningFee.Satoshis) * txBytes) / uint64(fee.MiningFee.Bytes)
} else {
calcFee = (uint64(fee.RelayFee.Satoshis) * txBytes) / uint64(fee.RelayFee.Bytes)
}

// Check for zero
if calcFee != 0 {
return calcFee, nil
}

// If txBytes is zero this error will occur
return 1, fmt.Errorf("warning: fee calculation was 0")
}

// No fee type found in the slice of fees
return 1, fmt.Errorf("feeType %s is not found in fees", feeType)
}

// GetFee will return the fee associated to the type (standard, data)
func (f *FeePayload) GetFee(feeType string) *bt.Fee {

// Loop the fees for the given type
for index, fee := range f.Fees {
if string(fee.FeeType) == feeType {
return f.Fees[index]
}
}

return nil
}
111 changes: 111 additions & 0 deletions apis/mapi/policy_quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package mapi

/*
Example PolicyQuoteResponse.Payload (unmarshalled):
{
"apiVersion": "1.4.0",
"timestamp": "2021-11-12T13:17:47.7498672Z",
"expiryTime": "2021-11-12T13:27:47.7498672Z",
"minerId": "030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
"currentHighestBlockHash": "45628be2fe616167b7da399ab63455e60ffcf84147730f4af4affca90c7d437e",
"currentHighestBlockHeight": 234,
"fees": [
{
"feeType": "standard",
"miningFee": {
"satoshis": 500,
"bytes": 1000
},
"relayFee": {
"satoshis": 250,
"bytes": 1000
}
},
{
"feeType": "data",
"miningFee": {
"satoshis": 500,
"bytes": 1000
},
"relayFee": {
"satoshis": 250,
"bytes": 1000
}
}
],
"callbacks": [
{
"ipAddress": "123.456.789.123"
}
],
"policies": {
"skipscriptflags": [
"MINIMALDATA",
"DERSIG",
"NULLDUMMY",
"DISCOURAGE_UPGRADABLE_NOPS",
"CLEANSTACK"
],
"maxtxsizepolicy": 99999,
"datacarriersize": 100000,
"maxscriptsizepolicy": 100000,
"maxscriptnumlengthpolicy": 100000,
"maxstackmemoryusagepolicy": 10000000,
"limitancestorcount": 1000,
"limitcpfpgroupmemberscount": 10,
"acceptnonstdoutputs": true,
"datacarrier": true,
"maxstdtxvalidationduration": 99,
"maxnonstdtxvalidationduration": 100,
"minconsolidationfactor": 10,
"maxconsolidationinputscriptsize": 100,
"minconfconsolidationinput": 10,
"acceptnonstdconsolidationinput": false
}
}
*/

// PolicyPayload is the unmarshalled version of the payload envelope
type PolicyPayload struct {
FeePayload // Inherit the same structure as the fee payload
Callbacks []*PolicyCallback `json:"callbacks"` // IP addresses of double-spend notification servers such as mAPI reference implementation
Policies *Policy `json:"policies"` // values of miner policies as configured by the mAPI reference implementation administrator
}

// ScriptFlag is a flag used in the policy quote
type ScriptFlag string

// All known script flags
const (
FlagCleanStack ScriptFlag = "CLEANSTACK"
FlagDerSig ScriptFlag = "DERSIG"
FlagDiscourageUpgradableNops ScriptFlag = "DISCOURAGE_UPGRADABLE_NOPS"
FlagMinimalData ScriptFlag = "MINIMALDATA"
FlagNullDummy ScriptFlag = "NULLDUMMY"
)

// Policy is the struct of a policy (from policy quote response)
type Policy struct {
AcceptNonStdOutputs bool `json:"acceptnonstdoutputs"`
DataCarrier bool `json:"datacarrier"`
DataCarrierSize uint32 `json:"datacarriersize"`
LimitAncestorCount uint32 `json:"limitancestorcount"`
LimitCpfpGroupMembersCount uint32 `json:"limitcpfpgroupmemberscount"`
MaxNonStdTxValidationDuration uint32 `json:"maxnonstdtxvalidationduration"`
MaxScriptNumLengthPolicy uint32 `json:"maxscriptnumlengthpolicy"`
MaxScriptSizePolicy uint32 `json:"maxscriptsizepolicy"`
MaxStackMemoryUsagePolicy uint64 `json:"maxstackmemoryusagepolicy"`
MaxStdTxValidationDuration uint32 `json:"maxstdtxvalidationduration"`
MaxTxSizePolicy uint32 `json:"maxtxsizepolicy"`
SkipScriptFlags []ScriptFlag `json:"skipscriptflags"`
MaxConsolidationFactor uint32 `json:"minconsolidationfactor"`
MaxConsolidationInputScriptSize uint32 `json:"maxconsolidationinputscriptsize"`
MinConfConsolidationInput uint32 `json:"minconfconsolidationinput"`
AcceptNonStdConsolidationInput bool `json:"acceptnonstdconsolidationinput"`
}

// PolicyCallback is the callback address
type PolicyCallback struct {
IPAddress string `json:"ipAddress"`
}
3 changes: 2 additions & 1 deletion examples/best_quote/best_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"

"github.com/tonicpow/go-minercraft"
"github.com/tonicpow/go-minercraft/apis/mapi"
)

func main() {
Expand All @@ -19,7 +20,7 @@ func main() {

// Fetch quotes from all miners
var response *minercraft.FeeQuoteResponse
response, err = client.BestQuote(context.Background(), minercraft.FeeCategoryMining, minercraft.FeeTypeData)
response, err = client.BestQuote(context.Background(), mapi.FeeCategoryMining, mapi.FeeTypeData)
if err != nil {
log.Fatalf("error occurred: %s", err.Error())
}
Expand Down
3 changes: 2 additions & 1 deletion examples/calculate_fee/calculate_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"

"github.com/tonicpow/go-minercraft"
"github.com/tonicpow/go-minercraft/apis/mapi"
)

func main() {
Expand All @@ -29,7 +30,7 @@ func main() {

// Get the fee for a specific tx size (for mining and for data)
var fee uint64
if fee, err = response.Quote.CalculateFee(minercraft.FeeCategoryMining, minercraft.FeeTypeStandard, txSizeInBytes); err != nil {
if fee, err = response.Quote.CalculateFee(mapi.FeeCategoryMining, mapi.FeeTypeStandard, txSizeInBytes); err != nil {
log.Fatalf("error occurred: %s", err.Error())
}

Expand Down
3 changes: 2 additions & 1 deletion examples/fee_quote/fee_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"

"github.com/tonicpow/go-minercraft"
"github.com/tonicpow/go-minercraft/apis/mapi"
)

func main() {
Expand All @@ -29,7 +30,7 @@ func main() {

// Get the fee for a specific tx size (for mining and for data)
var fee uint64
if fee, err = response.Quote.CalculateFee(minercraft.FeeCategoryMining, minercraft.FeeTypeData, txSizeInBytes); err != nil {
if fee, err = response.Quote.CalculateFee(mapi.FeeCategoryMining, mapi.FeeTypeData, txSizeInBytes); err != nil {
log.Fatalf("error occurred: %s", err.Error())
}

Expand Down
4 changes: 2 additions & 2 deletions examples/policy_quote/policy_quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import (
func main() {

// Create a new client
client, err := minercraft.NewClient(nil, nil, "", nil, nil)
client, err := minercraft.NewClient(nil, nil, minercraft.MAPI, nil, nil)
if err != nil {
log.Fatalf("error occurred: %s", err.Error())
}

// Select the miner
miner := client.MinerByName(minercraft.MinerTaal)
miner := client.MinerByName(minercraft.MinerGorillaPool)

// Get a policy quote from a miner
var response *minercraft.PolicyQuoteResponse
Expand Down
2 changes: 1 addition & 1 deletion examples/submit_transaction/submit_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func main() {

// Create a new client
client, err := minercraft.NewClient(nil, nil, "", nil, nil)
client, err := minercraft.NewClient(nil, nil, minercraft.MAPI, nil, nil)
if err != nil {
log.Fatalf("error occurred: %s", err.Error())
}
Expand Down
Loading

0 comments on commit 969b04a

Please sign in to comment.