Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/handler/transact.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func NewTransaction(route *echo.Echo, service service.Transaction) Transaction {
}

func (t transaction) Transact(c echo.Context) error {
var body model.ExecutionRequest
var body model.PrecisionSafeExecutionRequest
err := c.Bind(&body)
if err != nil {
LogStringError(c, err, "transact: execute bind")
Expand Down
51 changes: 51 additions & 0 deletions pkg/internal/common/precision.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package common

import (
"strconv"

"github.com/String-xyz/string-api/pkg/model"
)

func QuoteToPrecise(imprecise model.Quote) model.PrecisionSafeQuote {
res := model.PrecisionSafeQuote{
Timestamp: imprecise.Timestamp,
BaseUSD: strconv.FormatFloat(imprecise.BaseUSD, 'f', 2, 64),
GasUSD: strconv.FormatFloat(imprecise.GasUSD, 'f', 2, 64),
TokenUSD: strconv.FormatFloat(imprecise.TokenUSD, 'f', 2, 64),
ServiceUSD: strconv.FormatFloat(imprecise.ServiceUSD, 'f', 2, 64),
TotalUSD: strconv.FormatFloat(imprecise.TotalUSD, 'f', 2, 64),
}
return res
}

func QuoteToImprecise(precise model.PrecisionSafeQuote) model.Quote {
res := model.Quote{
Timestamp: precise.Timestamp,
}
res.BaseUSD, _ = strconv.ParseFloat(precise.BaseUSD, 64)
res.GasUSD, _ = strconv.ParseFloat(precise.GasUSD, 64)
res.TokenUSD, _ = strconv.ParseFloat(precise.TokenUSD, 64)
res.ServiceUSD, _ = strconv.ParseFloat(precise.ServiceUSD, 64)
res.TotalUSD, _ = strconv.ParseFloat(precise.TotalUSD, 64)
return res
}

func ExecutionRequestToPrecise(imprecise model.ExecutionRequest) model.PrecisionSafeExecutionRequest {
res := model.PrecisionSafeExecutionRequest{
TransactionRequest: imprecise.TransactionRequest,
PrecisionSafeQuote: QuoteToPrecise(imprecise.Quote),
Signature: imprecise.Signature,
CardToken: imprecise.CardToken,
}
return res
}

func ExecutionRequestToImprecise(precise model.PrecisionSafeExecutionRequest) model.ExecutionRequest {
res := model.ExecutionRequest{
TransactionRequest: precise.TransactionRequest,
Quote: QuoteToImprecise(precise.PrecisionSafeQuote),
Signature: precise.Signature,
CardToken: precise.CardToken,
}
return res
}
18 changes: 17 additions & 1 deletion pkg/model/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,26 @@ type ExecutionRequest struct {
CardToken string `json:"cardToken"`
}

type PrecisionSafeQuote struct {
Timestamp int64 `json:"timestamp"`
BaseUSD string `json:"baseUSD"`
GasUSD string `json:"gasUSD"`
TokenUSD string `json:"tokenUSD"`
ServiceUSD string `json:"serviceUSD"`
TotalUSD string `json:"totalUSD"`
}

type PrecisionSafeExecutionRequest struct {
TransactionRequest
PrecisionSafeQuote
Signature string `json:"signature"`
CardToken string `json:"cardToken"`
}

// User will pass this in for a quote and receive Execution Parameters
type TransactionRequest struct {
UserAddress string `json:"userAddress"` // Used to keep track of user ie "0x44A4b9E2A69d86BA382a511f845CbF2E31286770"
ChainId int `json:"chainId"` // Chain ID to execute on e.g. 80000
ChainId uint64 `json:"chainId"` // Chain ID to execute on e.g. 80000
CxAddr string `json:"contractAddress"` // Address of contract ie "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
CxFunc string `json:"contractFunction"` // Function declaration ie "mintTo(address) payable"
CxReturn string `json:"contractReturn"` // Function return type ie "uint256"
Expand Down
15 changes: 15 additions & 0 deletions pkg/service/cost.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package service

import (
"math"
"math/big"
"os"
"time"
Expand Down Expand Up @@ -110,8 +111,18 @@ func (c cost) EstimateTransaction(p EstimationParams, chain Chain) (model.Quote,
gasInUSD = 0.01
}

// Round up to nearest cent
transactionCost = centCeiling(transactionCost)
gasInUSD = centCeiling(gasInUSD)
tokenCost = centCeiling(tokenCost)
serviceFee = centCeiling(serviceFee)

// sum total
totalUSD := transactionCost + gasInUSD + tokenCost + serviceFee

// Round that up as well to account for any floating imprecision
totalUSD = centCeiling(totalUSD)

// Fill out CostEstimate and return
return model.Quote{
Timestamp: timestamp,
Expand All @@ -123,6 +134,10 @@ func (c cost) EstimateTransaction(p EstimationParams, chain Chain) (model.Quote,
}, nil
}

func centCeiling(value float64) float64 {
return math.Ceil(value*100) / 100
}

func (c cost) getExternalAPICallInterval(rateLimitPerMinute float64, uniqueEntries uint32) int64 {
return int64(float64(60*rateLimitPerMinute) / rateLimitPerMinute)
}
Expand Down
66 changes: 36 additions & 30 deletions pkg/service/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import (
)

type Transaction interface {
Quote(d model.TransactionRequest) (model.ExecutionRequest, error)
Execute(e model.ExecutionRequest, userId string, deviceId string, ip string) (model.TransactionReceipt, error)
Quote(d model.TransactionRequest) (model.PrecisionSafeExecutionRequest, error)
Execute(e model.PrecisionSafeExecutionRequest, userId string, deviceId string, ip string) (model.TransactionReceipt, error)
}

type TransactionRepos struct {
Expand Down Expand Up @@ -55,27 +55,28 @@ func NewTransaction(repos repository.Repositories, redis store.RedisStore, unit2
}

type transactionProcessingData struct {
userId *string
user *model.User
deviceId *string
ip *string
executor *Executor
processingFeeAsset *model.Asset
transactionModel *model.Transaction
chain *Chain
executionRequest *model.ExecutionRequest
cardAuthorization *AuthorizedCharge
cardCapture *payments.CapturesResponse
preBalance *float64
recipientWalletId *string
txId *string
cumulativeValue *big.Int
trueGas *uint64
userId *string
user *model.User
deviceId *string
ip *string
executor *Executor
processingFeeAsset *model.Asset
transactionModel *model.Transaction
chain *Chain
executionRequest *model.ExecutionRequest
precisionSafeExecutionRequest *model.PrecisionSafeExecutionRequest
cardAuthorization *AuthorizedCharge
cardCapture *payments.CapturesResponse
preBalance *float64
recipientWalletId *string
txId *string
cumulativeValue *big.Int
trueGas *uint64
}

func (t transaction) Quote(d model.TransactionRequest) (model.ExecutionRequest, error) {
func (t transaction) Quote(d model.TransactionRequest) (model.PrecisionSafeExecutionRequest, error) {
// TODO: use prefab service to parse d and fill out known params
res := model.ExecutionRequest{TransactionRequest: d}
res := model.PrecisionSafeExecutionRequest{TransactionRequest: d}
// chain, err := model.ChainInfo(uint64(d.ChainId))
chain, err := ChainInfo(uint64(d.ChainId), t.repos.Network, t.repos.Asset)
if err != nil {
Expand All @@ -91,7 +92,7 @@ func (t transaction) Quote(d model.TransactionRequest) (model.ExecutionRequest,
if err != nil {
return res, common.StringError(err)
}
res.Quote = estimateUSD
res.PrecisionSafeQuote = common.QuoteToPrecise(estimateUSD)
executor.Close()

// Sign entire payload
Expand All @@ -108,9 +109,9 @@ func (t transaction) Quote(d model.TransactionRequest) (model.ExecutionRequest,
return res, nil
}

func (t transaction) Execute(e model.ExecutionRequest, userId string, deviceId string, ip string) (res model.TransactionReceipt, err error) {
func (t transaction) Execute(e model.PrecisionSafeExecutionRequest, userId string, deviceId string, ip string) (res model.TransactionReceipt, err error) {
t.getStringInstrumentsAndUserId()
p := transactionProcessingData{executionRequest: &e, userId: &userId, deviceId: &deviceId, ip: &ip}
p := transactionProcessingData{precisionSafeExecutionRequest: &e, executionRequest: &model.ExecutionRequest{}, userId: &userId, deviceId: &deviceId, ip: &ip}

// Pre-flight transaction setup
p, err = t.transactionSetup(p)
Expand Down Expand Up @@ -153,7 +154,7 @@ func (t transaction) transactionSetup(p transactionProcessingData) (transactionP
p.user = &user

// Pull chain info needed for execution from repository
chain, err := ChainInfo(uint64(p.executionRequest.ChainId), t.repos.Network, t.repos.Asset)
chain, err := ChainInfo(p.precisionSafeExecutionRequest.ChainId, t.repos.Network, t.repos.Asset)
if err != nil {
return p, common.StringError(err)
}
Expand All @@ -167,7 +168,7 @@ func (t transaction) transactionSetup(p transactionProcessingData) (transactionP
p.transactionModel = &transactionModel

updateDB := &model.TransactionUpdates{}
processingFeeAsset, err := t.populateInitialTxModelData(*p.executionRequest, updateDB)
processingFeeAsset, err := t.populateInitialTxModelData(*p.precisionSafeExecutionRequest, updateDB)
p.processingFeeAsset = &processingFeeAsset
if err != nil {
return p, common.StringError(err)
Expand Down Expand Up @@ -196,7 +197,7 @@ func (t transaction) transactionSetup(p transactionProcessingData) (transactionP

func (t transaction) safetyCheck(p transactionProcessingData) (transactionProcessingData, error) {
// Test the Tx and update model status
estimateUSD, estimateETH, err := t.testTransaction(*p.executor, p.executionRequest.TransactionRequest, *p.chain, false)
estimateUSD, estimateETH, err := t.testTransaction(*p.executor, p.precisionSafeExecutionRequest.TransactionRequest, *p.chain, false)
if err != nil {
return p, common.StringError(err)
}
Expand All @@ -206,14 +207,15 @@ func (t transaction) safetyCheck(p transactionProcessingData) (transactionProces
}

// Verify the Quote and update model status
_, err = verifyQuote(*p.executionRequest, estimateUSD)
_, err = verifyQuote(*p.precisionSafeExecutionRequest, estimateUSD)
if err != nil {
return p, common.StringError(err)
}
err = t.updateTransactionStatus("Quote Verified", p.transactionModel.Id)
if err != nil {
return p, common.StringError(err)
}
*p.executionRequest = common.ExecutionRequestToImprecise(*p.precisionSafeExecutionRequest)

// Get current balance of primary token
preBalance, err := (*p.executor).GetBalance()
Expand Down Expand Up @@ -442,7 +444,7 @@ func (t transaction) postProcess(p transactionProcessingData) {
}
}

func (t transaction) populateInitialTxModelData(e model.ExecutionRequest, m *model.TransactionUpdates) (model.Asset, error) {
func (t transaction) populateInitialTxModelData(e model.PrecisionSafeExecutionRequest, m *model.TransactionUpdates) (model.Asset, error) {
txType := "fiat-to-crypto"
m.Type = &txType
// TODO populate transactionModel.Tags with key-val pairs for Unit21
Expand Down Expand Up @@ -510,7 +512,7 @@ func (t transaction) testTransaction(executor Executor, request model.Transactio
return res, eth, nil
}

func verifyQuote(e model.ExecutionRequest, newEstimate model.Quote) (bool, error) {
func verifyQuote(e model.PrecisionSafeExecutionRequest, newEstimate model.Quote) (bool, error) {
// Null out values which have changed since payload was signed
dataToValidate := e
dataToValidate.Signature = ""
Expand All @@ -529,7 +531,11 @@ func verifyQuote(e model.ExecutionRequest, newEstimate model.Quote) (bool, error
if newEstimate.Timestamp-e.Timestamp > 20 {
return false, common.StringError(errors.New("verifyQuote: quote expired"))
}
if newEstimate.TotalUSD > e.TotalUSD {
quotedTotal, err := strconv.ParseFloat(e.TotalUSD, 64)
if err != nil {
return false, common.StringError(err)
}
if newEstimate.TotalUSD > quotedTotal {
return false, common.StringError(errors.New("verifyQuote: price too volatile"))
}
return true, nil
Expand Down