Skip to content

Commit

Permalink
Merge pull request #93 from wregulski/wregulski/mapi-arc-replacement
Browse files Browse the repository at this point in the history
feat: arc api mode implementation
  • Loading branch information
mrz1836 committed Jun 12, 2023
2 parents f484e75 + 93d0727 commit 91208a6
Show file tree
Hide file tree
Showing 39 changed files with 1,883 additions and 860 deletions.
9 changes: 9 additions & 0 deletions apis/arc/fee_quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Package arc provides the API structures for the ARC service
package arc

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

// FeePayload is the unmarshalled version of the payload envelope
type FeePayload struct {
Fees *bt.Fee `json:"fees"`
}
17 changes: 17 additions & 0 deletions apis/arc/policy_quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package arc

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

// Policy is the unmarshalled version of the payload envelope
type Policy struct {
MaxScriptSizePolicy uint32 `json:"maxscriptsizepolicy"`
MaxTxSigOpsCount uint32 `json:"maxtxsigopscount"`
MaxTxSizePolicy uint32 `json:"maxtxsizepolicy"`
MiningFee *bt.Fee `json:"miningFee"`
}

// PolicyQuoteModel is the unmarshalled version of the payload envelope
type PolicyQuoteModel struct {
Policy Policy `json:"policy"`
Timestamp string `json:"timestamp"`
}
14 changes: 14 additions & 0 deletions apis/arc/query_transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package arc

import "time"

// QueryTxModel is the unmarshalled version of the payload envelope
type QueryTxModel struct {
BlockHash string `json:"blockHash,omitempty"`
BlockHeight int64 `json:"blockHeight,omitempty"`
// TODO: Specify the type - currently no information on this in the docs
ExtraInfo struct{} `json:"extraInfo,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
TxStatus TxStatus `json:"txStatus,omitempty"`
TxID string `json:"txid,omitempty"`
}
15 changes: 15 additions & 0 deletions apis/arc/submit_transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package arc

import "time"

// SubmitTxModel is the unmarshalled version of the payload envelope
type SubmitTxModel struct {
BlockHash string `json:"blockHash,omitempty"`
BlockHeight int64 `json:"blockHeight,omitempty"`
ExtraInfo string `json:"extraInfo,omitempty"`
Status int `json:"status,omitempty"`
Timestamp time.Time `json:"timestamp,omitempty"`
Title string `json:"title,omitempty"`
TxStatus TxStatus `json:"txStatus,omitempty"`
TxID string `json:"txid,omitempty"`
}
77 changes: 77 additions & 0 deletions apis/arc/tx_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package arc

// TxStatus is the status of the transaction
type TxStatus string

// List of statuses available here: https://github.com/bitcoin-sv/arc
const (
// Unknown contains value for unknown status
Unknown TxStatus = "UNKNOWN" // 0
// Queued contains value for queued status
Queued TxStatus = "QUEUED" // 1
// Received contains value for received status
Received TxStatus = "RECEIVED" // 2
// Stored contains value for stored status
Stored TxStatus = "STORED" // 3
// AnnouncedToNetwork contains value for announced to network status
AnnouncedToNetwork TxStatus = "ANNOUNCED_TO_NETWORK" // 4
// RequestedByNetwork contains value for requested by network status
RequestedByNetwork TxStatus = "REQUESTED_BY_NETWORK" // 5
// SentToNetwork contains value for sent to network status
SentToNetwork TxStatus = "SENT_TO_NETWORK" // 6
// AcceptedByNetwork contains value for accepted by network status
AcceptedByNetwork TxStatus = "ACCEPTED_BY_NETWORK" // 7
// SeenOnNetwork contains value for seen on network status
SeenOnNetwork TxStatus = "SEEN_ON_NETWORK" // 8
// Mined contains value for mined status
Mined TxStatus = "MINED" // 9
// Confirmed contains value for confirmed status
Confirmed TxStatus = "CONFIRMED" // 108
// Rejected contains value for rejected status
Rejected TxStatus = "REJECTED" // 109
)

// String returns the string representation of the TxStatus
func (s TxStatus) String() string {
statuses := map[TxStatus]string{
Unknown: "UNKNOWN",
Queued: "QUEUED",
Received: "RECEIVED",
Stored: "STORED",
AnnouncedToNetwork: "ANNOUNCED_TO_NETWORK",
RequestedByNetwork: "REQUESTED_BY_NETWORK",
SentToNetwork: "SENT_TO_NETWORK",
AcceptedByNetwork: "ACCEPTED_BY_NETWORK",
SeenOnNetwork: "SEEN_ON_NETWORK",
Mined: "MINED",
Confirmed: "CONFIRMED",
Rejected: "REJECTED",
}

if status, ok := statuses[s]; ok {
return status
}

return "Can't parse status"
}

// MapTxStatusToInt maps the TxStatus to an int value
func MapTxStatusToInt(status TxStatus) (int, bool) {
waitForStatusMap := map[TxStatus]int{
Unknown: 0,
Queued: 1,
Received: 2,
Stored: 3,
AnnouncedToNetwork: 4,
RequestedByNetwork: 5,
SentToNetwork: 6,
AcceptedByNetwork: 7,
SeenOnNetwork: 8,
Mined: 9,
Confirmed: 108,
Rejected: 109,
}

value, ok := waitForStatusMap[status]
return value, ok
}
175 changes: 175 additions & 0 deletions apis/mapi/fee_quote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
// Package mapi provides the API structures for the mAPI service
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
}
Loading

0 comments on commit 91208a6

Please sign in to comment.