diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8f9ae1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# OS:- +.DS_Store + +# Editors:- +.idea +.vscode + +# Env:- +.env +.env.prod + +# Dependencies:- +vendor diff --git a/README.md b/README.md new file mode 100644 index 0000000..a236b2d --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Mochi SDK for Go + +The Mochi SDK for Go is a library that provides easy access to the Mochi API from your Go applications. It simplifies the process of making API requests, handling authentication, and processing responses. Use this SDK to integrate Mochi's functionality into your Go projects effortlessly. + +## Features + +- [x] GetApplicationBalances: Retrieve token balances for your Mochi application. +- [x] RequestPayment: Request a payment from a user. +- [x] Transfer: Transfer tokens from your application to a list of user. + +## Installation + +To use the Mochi SDK in your Go project, you can simply install it using: + +```bash +go get github.com/consolelabs/mochi-sdk +``` + + +## Authorization +From MochiPay, you will receive an application ID, application name, and API key. You can use these to create a new MochiPay client. The client will be used to make requests to the Mochi API. +```go + config := &mochipay.Config{ + ApplicationID: "", + ApplicationName: "", + APIKey: "", + } +``` + +## Examples +Here's a simple example of how to use the YourAPI SDK: +### `GetApplicationBalances()` +```go +package main + +import ( + "fmt" + + "github.com/consolelabs/mochi-go-sdk/mochipay" +) + +func main() { + config := &mochipay.Config{ + ApplicationID: "", + ApplicationName: "", + APIKey: "", + } + + client := mochipay.NewClient(config) + balances, err := client.GetApplicationBalances() + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Println("Balances:") + for _, balance := range balances { + fmt.Printf("Token ID: %s, Amount: %s\n", balance.TokenID, balance.Amount) + } +} + +``` + diff --git a/example/mochipay/main.go b/example/mochipay/main.go new file mode 100644 index 0000000..6f918b3 --- /dev/null +++ b/example/mochipay/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + + "github.com/consolelabs/mochi-go-sdk/mochipay" +) + +func main() { + config := &mochipay.Config{ + ApplicationID: "", + ApplicationName: "", + APIKey: "", + } + + client := mochipay.NewClient(config) + balances, err := client.GetApplicationBalances() + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Println("Balances:") + for _, balance := range balances { + fmt.Printf("Token ID: %s, Amount: %s\n", balance.TokenID, balance.Amount) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..74d352f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/consolelabs/mochi-go-sdk + +go 1.20 diff --git a/mochipay/application-balance.go b/mochipay/application-balance.go new file mode 100644 index 0000000..d56ab4f --- /dev/null +++ b/mochipay/application-balance.go @@ -0,0 +1,57 @@ +package mochipay + +import ( + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "time" +) + +func (c *Client) GetApplicationBalances() ([]TokenBalance, error) { + client := &http.Client{} + requestURL := fmt.Sprintf("%s/api/v1/applications/%v/balances", c.cfg.BaseURL, c.cfg.ApplicationID) + request, err := http.NewRequest(http.MethodGet, requestURL, nil) + if err != nil { + return nil, err + } + + messageHeader := strconv.FormatInt(time.Now().Unix(), 10) + privateKey, err := hex.DecodeString(c.cfg.APIKey) + signature := ed25519.Sign(privateKey, []byte(messageHeader)) + + request.Header.Add("X-Message", messageHeader) + request.Header.Add("X-Application", c.cfg.ApplicationName) + request.Header.Add("X-Signature", hex.EncodeToString(signature)) + + resp, err := client.Do(request) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + resBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + var errMsg ErrorMessage + if err := json.Unmarshal(resBody, &errMsg); err != nil { + return nil, errors.New("invalid decoded, error " + err.Error()) + } + return nil, errors.New("invalid call, code " + strconv.Itoa(resp.StatusCode) + " " + errMsg.Msg) + } + + var respData TokenBalanceResponses + + if err := json.Unmarshal(resBody, &respData); err != nil { + return nil, errors.New("invalid decoded, error " + err.Error()) + } + + return respData.Data, nil +} diff --git a/mochipay/client.go b/mochipay/client.go new file mode 100644 index 0000000..d4acc74 --- /dev/null +++ b/mochipay/client.go @@ -0,0 +1,19 @@ +package mochipay + +type Client struct { + cfg *Config +} + +func NewClient(cfg *Config) APIClient { + if cfg.BaseURL == "" { + if cfg.IsPreview { + cfg.BaseURL = DefaultPreviewBaseURL + } else { + cfg.BaseURL = DefaultProdBaseURL + } + } + + return &Client{ + cfg: cfg, + } +} diff --git a/mochipay/config.go b/mochipay/config.go new file mode 100644 index 0000000..04c67f6 --- /dev/null +++ b/mochipay/config.go @@ -0,0 +1,14 @@ +package mochipay + +const ( + DefaultPreviewBaseURL = "https://api-preview.mochi-pay.console.so" + DefaultProdBaseURL = "https://api.mochi-pay.console.so" +) + +type Config struct { + BaseURL string + ApplicationID string + ApplicationName string + APIKey string + IsPreview bool +} diff --git a/mochipay/interface.go b/mochipay/interface.go new file mode 100644 index 0000000..368e51d --- /dev/null +++ b/mochipay/interface.go @@ -0,0 +1,7 @@ +package mochipay + +type APIClient interface { + GetApplicationBalances() ([]TokenBalance, error) + RequestPayment(req *PaymentRequest) error + Transfer(req *TransferRequest) ([]TransactionResult, error) +} diff --git a/mochipay/model.go b/mochipay/model.go new file mode 100644 index 0000000..5c2fcea --- /dev/null +++ b/mochipay/model.go @@ -0,0 +1,76 @@ +package mochipay + +import "time" + +type ErrorMessage struct { + Msg string `json:"msg"` +} + +type TokenBalanceResponses struct { + Data []TokenBalance `json:"data"` +} + +type TokenBalance struct { + ID string `json:"id"` + ProfileID string `json:"profile_id"` + TokenID string `json:"token_id"` + Amount string `json:"amount"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Token Token `json:"token"` +} + +type Token struct { + ID string `json:"id"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimal int `json:"decimal"` + ChainID string `json:"chain_id"` + Native bool `json:"native"` + Address string `json:"address"` + Icon string `json:"icon"` + CoinGeckoID string `json:"coin_gecko_id"` + Price float64 `json:"price"` + Chain Chain `json:"chain"` +} + +type Chain struct { + ID string `json:"id"` + ChainID string `json:"chain_id"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Rpc string `json:"rpc"` + Explorer string `json:"explorer"` + Icon string `json:"icon"` + IsEvm bool `json:"is_evm"` +} + +type PaymentRequest struct { + UserProfileID string `json:"user_profile_id"` + TokenAmount string `json:"token_amount"` + TokenID string `json:"token_id"` + Description string `json:"description"` + CallbackURL string `json:"callback_url"` +} + +type TransactionResult struct { + Timestamp int64 `json:"timestamp"` + TransactionID int64 `json:"tx_id"` + RecipientID string `json:"recipient_id"` + Amount string `json:"amount"` + TransactionFee string `json:"tx_fee"` + Status string `json:"status"` + References string `json:"references"` +} + +type TransactionResponse struct { + Data []TransactionResult `json:"data"` +} + +type TransferRequest struct { + RecipientIDs []string `json:"recipient_ids"` + Amounts []string `json:"amounts"` + TokenID string `json:"token_id"` + References string `json:"references"` + Description string `json:"description"` +} diff --git a/mochipay/request-payment.go b/mochipay/request-payment.go new file mode 100644 index 0000000..d63b662 --- /dev/null +++ b/mochipay/request-payment.go @@ -0,0 +1,57 @@ +package mochipay + +import ( + "bytes" + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "time" +) + +func (c *Client) RequestPayment(req *PaymentRequest) error { + requestBody, err := json.Marshal(req) + if err != nil { + return err + } + + var client = &http.Client{} + requestURL := fmt.Sprintf("%s/api/v1/applications/%v/requests", c.cfg.BaseURL, c.cfg.ApplicationID) + request, err := http.NewRequest(http.MethodPost, requestURL, bytes.NewBuffer(requestBody)) + if err != nil { + return err + } + + messageHeader := strconv.FormatInt(time.Now().Unix(), 10) + privateKey, err := hex.DecodeString(c.cfg.APIKey) + signature := ed25519.Sign(privateKey, []byte(messageHeader)) + + request.Header.Add("X-Message", messageHeader) + request.Header.Add("X-Application", c.cfg.ApplicationName) + request.Header.Add("X-Signature", hex.EncodeToString(signature)) + + resp, err := client.Do(request) + if err != nil { + return err + } + + defer resp.Body.Close() + resBody, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + var errMsg ErrorMessage + if err := json.Unmarshal(resBody, &errMsg); err != nil { + return errors.New("invalid decoded, error " + err.Error()) + } + return errors.New("invalid call, code " + strconv.Itoa(resp.StatusCode) + " " + errMsg.Msg) + } + + return nil +} diff --git a/mochipay/transfer.go b/mochipay/transfer.go new file mode 100644 index 0000000..b3839c0 --- /dev/null +++ b/mochipay/transfer.go @@ -0,0 +1,60 @@ +package mochipay + +import ( + "bytes" + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "time" +) + +func (c *Client) Transfer(req *TransferRequest) ([]TransactionResult, error) { + requestBody, err := json.Marshal(req) + if err != nil { + return nil, err + } + + var client = &http.Client{} + request, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/applications/%v/transfer", c.cfg.BaseURL, c.cfg.ApplicationID), bytes.NewBuffer(requestBody)) + if err != nil { + return nil, err + } + + messageHeader := strconv.FormatInt(time.Now().Unix(), 10) + privateKey, err := hex.DecodeString(c.cfg.APIKey) + signature := ed25519.Sign(privateKey, []byte(messageHeader)) + request.Header.Add("X-Message", messageHeader) + request.Header.Add("X-Application", "Guess Game") + request.Header.Add("X-Signature", hex.EncodeToString(signature)) + + resp, err := client.Do(request) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + resBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + var errMsg ErrorMessage + if err := json.Unmarshal(resBody, &errMsg); err != nil { + return nil, errors.New("invalid decoded, error " + err.Error()) + } + return nil, errors.New("invalid call, code " + strconv.Itoa(resp.StatusCode) + " " + errMsg.Msg) + } + + var respData TransactionResponse + if err := json.Unmarshal(resBody, &respData); err != nil { + return nil, errors.New("invalid decoded, error " + err.Error()) + } + + return respData.Data, nil +}