Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mint - Price and PaymentToken (CAM ONLY) #42

Merged
merged 11 commits into from
Sep 29, 2024
146 changes: 142 additions & 4 deletions examples/booking/mintnbuy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"math/big"
"time"

typesv2 "buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go/cmp/types/v2"
"google.golang.org/protobuf/types/known/emptypb"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
Expand All @@ -17,6 +20,17 @@ import (
"github.com/chain4travel/camino-messenger-contracts/go/contracts/bookingtoken"
)

var zeroAddress = common.HexToAddress("0x0000000000000000000000000000000000000000")

// https://columbus.caminoscan.com/token/0x5b1c852dad36854B0dFFF61d2C13F108D8E01975
// https://caminoscan.com/token/0x026816DF82F78882DaC9370a35c497C254Ebd88E
var eurshToken = common.HexToAddress("0x5b1c852dad36854B0dFFF61d2C13F108D8E01975")

var polygonToken = common.HexToAddress("0x0000000000000000000000000000000000001010")

// https://polygonscan.com/token/0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6
var wBtcToken = common.HexToAddress("0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6") // not on Camino

// Simple usage example for the BookingService
func main() {
logger, err := zap.NewDevelopment()
Expand Down Expand Up @@ -71,8 +85,131 @@ func main() {
// expiration timestamp
expiration := big.NewInt(time.Now().Add(time.Hour).Unix())

var paymentToken common.Address = zeroAddress
var bigIntPrice *big.Int
var price *typesv2.Price
// https://polygonscan.com/unitconverter
// ### Simple Price type message Price
//
// Value of the price, this should be an integer converted to string.
//
// This field is a string intentionally. Because the currency can be a crypto
// currency, we need a reliable way to represent big integers as most of the crypto
// currencies have 18 decimals precision.
//
// Definition of the price message: The combination of "value" and "decimals" fields
// express always the value of the currency, not of the fraction of the currency [
// ETH not wei, CAM and not aCAM, BTC and not Satoshi, EUR not EUR-Cents ] Be aware
// that partners should not do rounding with crypto currencies.
//
// price
price := big.NewInt(0)
// Example implementations: off-chain payment of 100 € or 100 $:
// value=10000
// decimals=2
// iso_currency=EUR or USD

priceEUR := &typesv2.Price{
Value: "10000",
Decimals: 2,
Currency: &typesv2.Currency{
Currency: &typesv2.Currency_IsoCurrency{
IsoCurrency: typesv2.IsoCurrency_ISO_CURRENCY_EUR,
},
},
}

// On-chain payment of 100.65 EURSH
// value=10065
// decimals=2
// contract_address=0x...
// this currency has 5 decimals on Columbus and conclusively to create the
// transaction value, 10065 must be divided by 10^2 = 100.65 EURSH and created in
// its smallest fraction by multiplying 100.65 EURSH * 10^5 => 10065000 (example
// conversion to bigint without losing accuracy: bigint(10065) * 10^(5-2))

priceEURSH := &typesv2.Price{
Value: "10065",
Decimals: 2,
Currency: &typesv2.Currency{
Currency: &typesv2.Currency_TokenCurrency{
TokenCurrency: &typesv2.TokenCurrency{
ContractAddress: eurshToken.Hex(),
},
},
},
}

// TODO: call decimals on eurshToken (should get 5)

// On-chain payment of 0.0065 BTC
// value=65
// decimals=4
// contract_address=0x... Using
//
// the contract address, we get the decimals decimals and the currency name or
// abbreviation: 8 decimals & WBTC Because we see 4 decimals specified in the
// message we divide 65 by 10^4 == 0.0065 WBTC (for showing in the front-end UIs)
//
// This currency has 8 decimals on-chain and conclusively to use the value of
// 0.0065 for on-chain operations must be converted to big integer as bigint(65) *
// 10^(8-4) == 650000

priceBTC := &typesv2.Price{
Value: "65",
Decimals: 4,
Currency: &typesv2.Currency{
Currency: &typesv2.Currency_TokenCurrency{
TokenCurrency: &typesv2.TokenCurrency{},
},
},
}
// On-chain payment of 1 nCAM value=1 decimals=9 this currency has denominator 18 on
//
// Columbus and conclusively to mint the value of 1 nCam must be divided by 10^9 =
// 0.000000001 CAM and minted in its smallest fraction by multiplying 0.000000001 *
// 10^18 => 1000000000 aCAM

priceCAM := &typesv2.Price{
Value: "1",
Decimals: 9,
Currency: &typesv2.Currency{
Currency: &typesv2.Currency_NativeToken{
NativeToken: &emptypb.Empty{},
},
},
}

sugar.Infof("%v %v %v %v", priceEUR, priceEURSH, priceBTC, priceCAM)
sugar.Infof("%v", price)

// bigIntPrice, _ = bs.ConvertPriceToBigInt(*priceEURSH, int32(5))
// bigIntPrice, _ = bs.ConvertPriceToBigInt(*priceCAM, int32(18))

paymentToken = zeroAddress
bigIntPrice = big.NewInt(0)
// price = priceEURSH
// price = priceBTC // case of unsupported token?
price = priceCAM

switch price.Currency.Currency.(type) {
case *typesv2.Currency_NativeToken:
bigIntPrice, err = bs.ConvertPriceToBigInt(price, int32(18)) //CAM uses 18 decimals
if err != nil {
sugar.Errorf("Failed to convert price to big.Int: %v", err)
return
}
sugar.Infof("Converted the price big.Int: %v", bigIntPrice)
paymentToken = zeroAddress
case *typesv2.Currency_TokenCurrency:
// Add logic to handle TokenCurrency
// if contract address is zeroAddress, then it is native token
sugar.Infof("TokenCurrency not supported yet")
return
case *typesv2.Currency_IsoCurrency:
// Add logic to handle IsoCurrency
sugar.Infof("IsoCurrency not supported yet")
return
}

// Mint a new booking token
//
Expand All @@ -83,12 +220,13 @@ func main() {
// Under normal circumstances the reservedFor address should be another CM
// Account address, generally the distributor's CM account address. And the
// distributor should buy the token.

mintTx, err := bs.MintBookingToken(
cmAccountAddr, // reservedFor address
tokenURI,
expiration,
price,
common.HexToAddress("0x0000000000000000000000000000000000000000"), // payment token
bigIntPrice,
paymentToken,
)
if err != nil {
sugar.Fatalf("Failed to mint booking token: %v", err)
Expand All @@ -107,7 +245,7 @@ func main() {
event, err := bt.ParseTokenReserved(*mLog)
if err == nil {
tokenID = event.TokenId
sugar.Infof("[TokenReserved] TokenID: %s ReservedFor: %s Price: %s, PaymentToken: %s", event.TokenId, event.ReservedFor, event.Price, event.PaymentToken)
sugar.Infof("[TokenReserved] TokenID: %s ReservedFor: %s Price: %s, PaymentToken: %s, TokenId: %s", event.TokenId, event.ReservedFor, event.Price, event.PaymentToken, tokenID)
}
}

Expand Down
25 changes: 22 additions & 3 deletions examples/rpc/partner-plugin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,37 @@ func (p *partnerPlugin) Mint(ctx context.Context, _ *bookv2.MintRequest) (*bookv
md.Stamp(fmt.Sprintf("%s-%s", "ext-system", "response"))
log.Printf("Responding to request: %s (Mint)", md.RequestID)

// On-chain payment of 1 nCAM value=1 decimals=9 this currency has denominator 18 on
//
// Columbus and conclusively to mint the value of 1 nCam must be divided by 10^9 =
// 0.000000001 CAM and minted in its smallest fraction by multiplying 0.000000001 *
// 10^18 => 1000000000 aCAM
response := bookv2.MintResponse{
MintId: &typesv1.UUID{Value: md.RequestID},
BuyableUntil: &timestamppb.Timestamp{
Seconds: time.Now().Add(5 * time.Minute).Unix(),
},
Price: &typesv2.Price{
Value: "100",
Decimals: 0,
Value: "1",
Decimals: 9,
Currency: &typesv2.Currency{
Currency: &typesv2.Currency_NativeToken{},
Currency: &typesv2.Currency_NativeToken{
NativeToken: &emptypb.Empty{},
},
},
},
/*
ISO CURRENCY EXAMPLE:
Price: &typesv1.Price{
Value: "10000",
Decimals: 2,
Currency: &typesv1.Currency{
Currency: &typesv1.Currency_IsoCurrency{
IsoCurrency: typesv1.IsoCurrency_ISO_CURRENCY_EUR,
},
},
},
*/
BookingTokenId: uint64(123456),
ValidationId: &typesv1.UUID{Value: "123456"},
BookingTokenUri: "https://example.com/booking-token",
Expand Down
33 changes: 29 additions & 4 deletions internal/messaging/response_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,40 @@ func (h *evmResponseHandler) mint(
reservedFor common.Address,
uri string,
expiration *big.Int,
_ *typesv2.Price,
price *typesv2.Price,
) (string, *big.Int, error) {
// TODO: VjeTurk: handle price and paymentToken
bigIntPrice := big.NewInt(0)
paymentToken := zeroAddress
var err error

// TODO:
// (in booking package)
// define paymentToken from currency
// if TokenCurrency get paymentToken contract and call decimals()
// calculate the price in big int without loosing precision

switch price.Currency.Currency.(type) {
case *typesv2.Currency_NativeToken:
bigIntPrice, err = h.bookingService.ConvertPriceToBigInt(price, int32(18)) // CAM uses 18 decimals
if err != nil {
return "", nil, err
}
paymentToken = zeroAddress
case *typesv2.Currency_TokenCurrency:
// Add logic to handle TokenCurrency
// if contract address is zeroAddress, then it is native token
return "", nil, fmt.Errorf("TokenCurrency not supported yet")
case *typesv2.Currency_IsoCurrency:
// Add logic to handle IsoCurrency
return "", nil, fmt.Errorf("IsoCurrency not supported yet")
}

tx, err := h.bookingService.MintBookingToken(
reservedFor,
uri,
expiration,
big.NewInt(0),
zeroAddress)
bigIntPrice,
paymentToken)
if err != nil {
return "", nil, err
}
Expand Down
19 changes: 19 additions & 0 deletions pkg/booking/booking.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"math/big"
"strings"

typesv2 "buf.build/gen/go/chain4travel/camino-messenger-protocol/protocolbuffers/go/cmp/types/v2"
"github.com/chain4travel/camino-messenger-contracts/go/contracts/cmaccount"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -126,3 +127,21 @@ func (bs *Service) BuyBookingToken(
bs.logger.Infof("BuyBookingToken tx sent: %s", tx.Hash().Hex())
return tx, nil
}

// convertPriceToBigInt converts the price to its integer representation
func (bs *Service) ConvertPriceToBigInt(price *typesv2.Price, totalDecimals int32) (*big.Int, error) {
// Convert the value string to a big.Int
valueBigInt := new(big.Int)
_, ok := valueBigInt.SetString(price.Value, 10)
if !ok {
return nil, fmt.Errorf("failed to convert value to big.Int: %s", price.Value)
}

// Calculate the multiplier as 10^(totalDecimals - price.Decimals)
multiplier := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(totalDecimals-price.Decimals)), nil)

// Multiply the value by the multiplier
result := new(big.Int).Mul(valueBigInt, multiplier)

return result, nil
}