mirror of
https://github.com/AthanorLabs/atomic-swap.git
synced 2026-01-09 14:18:03 -05:00
128 lines
4.2 KiB
Go
128 lines
4.2 KiB
Go
// Copyright 2023 The AthanorLabs/atomic-swap Authors
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
package coins
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/cockroachdb/apd/v3"
|
|
)
|
|
|
|
// ExchangeRate defines an exchange rate between ETH and XMR.
|
|
// It is defined as the ratio of ETH:XMR that the node wishes to provide.
|
|
// ie. an ExchangeRate of 0.1 means that the node considers 1 ETH = 10 XMR.
|
|
type ExchangeRate apd.Decimal
|
|
|
|
// CalcExchangeRate computes and returns an exchange rate using ETH and XRM prices. The
|
|
// price can be relative to USD, bitcoin or something else, but both values should be
|
|
// relative to the same alternate currency.
|
|
func CalcExchangeRate(xmrPrice *apd.Decimal, ethPrice *apd.Decimal) (*ExchangeRate, error) {
|
|
rate := new(apd.Decimal)
|
|
_, err := decimalCtx.Quo(rate, xmrPrice, ethPrice)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if rate, err = roundToDecimalPlace(rate, MaxExchangeRateDecimals); err != nil {
|
|
return nil, err
|
|
}
|
|
return ToExchangeRate(rate), nil
|
|
}
|
|
|
|
// ToExchangeRate casts an *apd.Decimal to *ExchangeRate
|
|
func ToExchangeRate(rate *apd.Decimal) *ExchangeRate {
|
|
return (*ExchangeRate)(rate)
|
|
}
|
|
|
|
// Decimal casts *ExchangeRate to *apd.Decimal
|
|
func (r *ExchangeRate) Decimal() *apd.Decimal {
|
|
return (*apd.Decimal)(r)
|
|
}
|
|
|
|
// UnmarshalText hands off JSON decoding to apd.Decimal
|
|
func (r *ExchangeRate) UnmarshalText(b []byte) error {
|
|
err := r.Decimal().UnmarshalText(b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ValidatePositive("exchangeRate", MaxExchangeRateDecimals, r.Decimal())
|
|
}
|
|
|
|
// MarshalText hands off JSON encoding to apd.Decimal
|
|
func (r *ExchangeRate) MarshalText() ([]byte, error) {
|
|
return r.Decimal().MarshalText()
|
|
}
|
|
|
|
// ToXMR converts an ETH amount to an XMR amount with the given exchange rate.
|
|
// If the calculated value would have fractional piconeros, an error is
|
|
// returned.
|
|
func (r *ExchangeRate) ToXMR(ethAssetAmt EthAssetAmount) (*apd.Decimal, error) {
|
|
xmrAmt := new(apd.Decimal)
|
|
_, err := decimalCtx.Quo(xmrAmt, ethAssetAmt.AsStd(), r.Decimal())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ExceedsDecimals(xmrAmt, NumMoneroDecimals) {
|
|
errMsg := fmt.Sprintf(
|
|
"%s %s / %s exceeds XMR's %d decimal precision",
|
|
ethAssetAmt.AsStdString(), ethAssetAmt.StdSymbol(), r, NumMoneroDecimals,
|
|
)
|
|
suggestedAltAmt := calcAltNumeratorAmount(ethAssetAmt.NumStdDecimals(), NumMoneroDecimals, r.Decimal(), xmrAmt)
|
|
if suggestedAltAmt != nil {
|
|
errMsg = fmt.Sprintf("%s, try %s", errMsg, suggestedAltAmt.Text('f'))
|
|
}
|
|
return nil, errors.New(errMsg)
|
|
}
|
|
|
|
return xmrAmt, nil
|
|
}
|
|
|
|
// ToETH converts an XMR amount to an ETH amount with the given exchange rate.
|
|
// If the calculated result would have fractional wei, an error is returned.
|
|
func (r *ExchangeRate) ToETH(xmrAmount *apd.Decimal) (*apd.Decimal, error) {
|
|
ethAmt := new(apd.Decimal)
|
|
_, err := decimalCtx.Mul(ethAmt, xmrAmount, r.Decimal())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Assuming the xmrAmount was capped at 12 decimal places and the exchange
|
|
// rate was capped at 6 decimal places, you can't generate more than 18
|
|
// decimal places below, so the error below can't happen.
|
|
if ExceedsDecimals(ethAmt, NumEtherDecimals) {
|
|
err := fmt.Errorf("%s XMR * %s exceeds ETH's %d decimal precision",
|
|
xmrAmount.Text('f'), r, NumEtherDecimals)
|
|
return nil, err
|
|
}
|
|
|
|
return ethAmt, nil
|
|
}
|
|
|
|
// ToERC20Amount converts an XMR amount to a token amount in standard units with
|
|
// the given exchange rate. If the result requires more decimal places than the
|
|
// token allows, an error is returned.
|
|
func (r *ExchangeRate) ToERC20Amount(xmrAmount *apd.Decimal, token *ERC20TokenInfo) (*apd.Decimal, error) {
|
|
erc20Amount := new(apd.Decimal)
|
|
_, err := decimalCtx.Mul(erc20Amount, xmrAmount, r.Decimal())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ExceedsDecimals(erc20Amount, token.NumDecimals) {
|
|
// We could have a suggested value to try, like we have in ToXMR(...),
|
|
// but since this is multiplication and not division, the end user
|
|
// probably doesn't need the hint.
|
|
err := fmt.Errorf("%s XMR * %s exceeds token's %d decimal precision",
|
|
xmrAmount.Text('f'), r, token.NumDecimals)
|
|
return nil, err
|
|
}
|
|
|
|
return NewTokenAmountFromDecimals(erc20Amount, token).AsStd(), nil
|
|
}
|
|
|
|
func (r *ExchangeRate) String() string {
|
|
return r.Decimal().Text('f')
|
|
}
|