Added ownership reference + wallet creation methods
This commit is contained in:
@@ -33,7 +33,8 @@ type CreateLedgerAccount struct {
|
||||
AllowNegative bool `json:"allowNegative,omitempty"`
|
||||
IsSettlement bool `json:"isSettlement,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Describable *model.Describable `json:"describable,omitempty"`
|
||||
Describable model.Describable `json:"describable"`
|
||||
IsOrgWallet bool `json:"isOrgWallet"`
|
||||
}
|
||||
|
||||
func (r *CreateLedgerAccount) Validate() error {
|
||||
|
||||
9
api/server/interface/api/srequest/wallet.go
Normal file
9
api/server/interface/api/srequest/wallet.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package srequest
|
||||
|
||||
import "github.com/tech/sendico/pkg/model"
|
||||
|
||||
type CreateWallet struct {
|
||||
Description model.Describable `json:"description"`
|
||||
IsOrgWallet bool `json:"isOrgWallet"`
|
||||
Asset model.ChainAssetKey `json:"asset"`
|
||||
}
|
||||
45
api/server/internal/mutil/proto/chain.go
Normal file
45
api/server/internal/mutil/proto/chain.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
)
|
||||
|
||||
func Network2Proto(network model.ChainNetwork) (chainv1.ChainNetwork, error) {
|
||||
switch network {
|
||||
case model.ChainNetworkARB:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_ARBITRUM_ONE, nil
|
||||
case model.ChainNetworkEthMain:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET, nil
|
||||
case model.ChainNetworkTronMain:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_MAINNET, nil
|
||||
case model.ChainNetworkTronNile:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_TRON_NILE, nil
|
||||
case model.ChainNetworkUnspecified:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED, nil
|
||||
default:
|
||||
return chainv1.ChainNetwork_CHAIN_NETWORK_UNSPECIFIED, merrors.InvalidArgument(fmt.Sprintf("Unkwnown chain network value '%s'", network), "network")
|
||||
}
|
||||
}
|
||||
|
||||
func Asset2Proto(asset *model.ChainAsset) (*chainv1.Asset, error) {
|
||||
if asset == nil {
|
||||
return nil, merrors.InvalidArgument("Asset must be provided", "asset")
|
||||
}
|
||||
netw, err := Network2Proto(asset.Chain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var contract string
|
||||
if asset.ContractAddress != nil {
|
||||
contract = *asset.ContractAddress
|
||||
}
|
||||
return &chainv1.Asset{
|
||||
Chain: netw,
|
||||
TokenSymbol: asset.TokenSymbol,
|
||||
ContractAddress: contract,
|
||||
}, nil
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func (a *AccountAPI) signup(r *http.Request) http.HandlerFunc {
|
||||
var sr srequest.Signup
|
||||
if err := json.NewDecoder(r.Body).Decode(&sr); err != nil {
|
||||
a.logger.Warn("Failed to decode signup request", zap.Error(err))
|
||||
return response.BadRequest(a.logger, a.Name(), "", err.Error())
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
sr.Account.Login = strings.ToLower(strings.TrimSpace(sr.Account.Login))
|
||||
@@ -252,12 +252,11 @@ func (a *AccountAPI) openOrgWallet(ctx context.Context, org *model.Organization,
|
||||
req := &chainv1.CreateManagedWalletRequest{
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
OrganizationRef: org.ID.Hex(),
|
||||
OwnerRef: org.ID.Hex(),
|
||||
Describable: &describablev1.Describable{
|
||||
Name: sr.CryptoWallet.Name,
|
||||
Description: sr.CryptoWallet.Description,
|
||||
},
|
||||
Asset: a.chainAsset,
|
||||
Asset: a.chainAsset,
|
||||
Metadata: map[string]string{
|
||||
"source": "signup",
|
||||
"login": sr.Account.Login,
|
||||
|
||||
@@ -56,25 +56,28 @@ func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token
|
||||
}
|
||||
|
||||
var describable *describablev1.Describable
|
||||
if payload.Describable != nil {
|
||||
name := strings.TrimSpace(payload.Describable.Name)
|
||||
var description *string
|
||||
if payload.Describable.Description != nil {
|
||||
trimmed := strings.TrimSpace(*payload.Describable.Description)
|
||||
if trimmed != "" {
|
||||
description = &trimmed
|
||||
}
|
||||
name := strings.TrimSpace(payload.Describable.Name)
|
||||
var description *string
|
||||
if payload.Describable.Description != nil {
|
||||
trimmed := strings.TrimSpace(*payload.Describable.Description)
|
||||
if trimmed != "" {
|
||||
description = &trimmed
|
||||
}
|
||||
if name != "" || description != nil {
|
||||
describable = &describablev1.Describable{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
if name != "" || description != nil {
|
||||
describable = &describablev1.Describable{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
var ownerRef string
|
||||
if !payload.IsOrgWallet {
|
||||
ownerRef = account.ID.Hex()
|
||||
}
|
||||
|
||||
resp, err := a.client.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
OwnerRef: ownerRef,
|
||||
AccountCode: payload.AccountCode,
|
||||
AccountType: accountType,
|
||||
Currency: payload.Currency,
|
||||
@@ -95,24 +98,19 @@ func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token
|
||||
func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAccount, error) {
|
||||
defer r.Body.Close()
|
||||
|
||||
payload := &srequest.CreateLedgerAccount{}
|
||||
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
||||
payload := srequest.CreateLedgerAccount{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
return nil, merrors.InvalidArgument("invalid payload: " + err.Error())
|
||||
}
|
||||
payload.AccountCode = strings.TrimSpace(payload.AccountCode)
|
||||
payload.Currency = strings.ToUpper(strings.TrimSpace(payload.Currency))
|
||||
if payload.Describable != nil {
|
||||
payload.Describable.Name = strings.TrimSpace(payload.Describable.Name)
|
||||
if payload.Describable.Description != nil {
|
||||
trimmed := strings.TrimSpace(*payload.Describable.Description)
|
||||
if trimmed == "" {
|
||||
payload.Describable.Description = nil
|
||||
} else {
|
||||
payload.Describable.Description = &trimmed
|
||||
}
|
||||
}
|
||||
if payload.Describable.Name == "" && payload.Describable.Description == nil {
|
||||
payload.Describable = nil
|
||||
payload.Describable.Name = strings.TrimSpace(payload.Describable.Name)
|
||||
if payload.Describable.Description != nil {
|
||||
trimmed := strings.TrimSpace(*payload.Describable.Description)
|
||||
if trimmed == "" {
|
||||
payload.Describable.Description = nil
|
||||
} else {
|
||||
payload.Describable.Description = &trimmed
|
||||
}
|
||||
}
|
||||
if len(payload.Metadata) == 0 {
|
||||
@@ -121,7 +119,7 @@ func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAc
|
||||
if err := payload.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload, nil
|
||||
return &payload, nil
|
||||
}
|
||||
|
||||
func mapLedgerAccountType(accountType srequest.LedgerAccountType) (ledgerv1.AccountType, error) {
|
||||
|
||||
98
api/server/internal/server/walletapiimp/create.go
Normal file
98
api/server/internal/server/walletapiimp/create.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package walletapiimp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
"github.com/tech/sendico/server/interface/api/srequest"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||
ast "github.com/tech/sendico/server/internal/mutil/proto"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *WalletAPI) create(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||
orgRef, err := a.oph.GetRef(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to parse organization reference for wallet list", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||
}
|
||||
|
||||
var sr srequest.CreateWallet
|
||||
if err := json.NewDecoder(r.Body).Decode(&sr); err != nil {
|
||||
a.logger.Warn("Failed to decode wallet creation request request", zap.Error(err), mzap.StorableRef(account))
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
res, err := a.enf.Enforce(ctx, a.walletsPermissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionCreate)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to check chain wallet access permissions", zap.Error(err), mutil.PLog(a.oph, r), mzap.StorableRef(account))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if !res {
|
||||
a.logger.Debug("Access denied when listing organization wallets", mutil.PLog(a.oph, r), mzap.StorableRef(account))
|
||||
return response.AccessDenied(a.logger, a.Name(), "wallets creation permission denied")
|
||||
}
|
||||
|
||||
asset, err := a.assets.Resolve(ctx, sr.Asset)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to resolve asset", zap.Error(err), mzap.StorableRef(account),
|
||||
zap.String("chain", string(sr.Asset.Chain)), zap.String("token", sr.Asset.TokenSymbol))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
if a.chainGateway == nil {
|
||||
return response.Internal(a.logger, mservice.ChainGateway, merrors.Internal("chain gateway client is not configured"))
|
||||
}
|
||||
|
||||
var ownerRef string
|
||||
if !sr.IsOrgWallet {
|
||||
ownerRef = account.ID.Hex()
|
||||
}
|
||||
passet, err := ast.Asset2Proto(&asset.Asset)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to convert asset to proto asset", zap.Error(err),
|
||||
mzap.StorableRef(asset), mzap.StorableRef(account))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
req := &chainv1.CreateManagedWalletRequest{
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
OwnerRef: ownerRef,
|
||||
Describable: &describablev1.Describable{
|
||||
Name: sr.Description.Name,
|
||||
Description: sr.Description.Description,
|
||||
},
|
||||
Asset: passet,
|
||||
Metadata: map[string]string{
|
||||
"source": "create",
|
||||
"login": account.Login,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := a.chainGateway.CreateManagedWallet(ctx, req)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create managed wallet", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), mzap.StorableRef(account))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if resp == nil || resp.Wallet == nil || strings.TrimSpace(resp.Wallet.WalletRef) == "" {
|
||||
return response.Auto(a.logger, a.Name(), merrors.Internal("chain gateway returned empty wallet reference"))
|
||||
}
|
||||
|
||||
a.logger.Info("Managed wallet created for organization", mzap.ObjRef("organization_ref", orgRef),
|
||||
zap.String("wallet_ref", resp.Wallet.WalletRef), mzap.StorableRef(account))
|
||||
|
||||
return sresponse.Success(a.logger, token)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
chaingatewayclient "github.com/tech/sendico/gateway/chain/client"
|
||||
api "github.com/tech/sendico/pkg/api/http"
|
||||
"github.com/tech/sendico/pkg/auth"
|
||||
"github.com/tech/sendico/pkg/db/chainassets"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
@@ -28,9 +29,11 @@ type WalletAPI struct {
|
||||
wph mutil.ParamHelper
|
||||
walletsPermissionRef primitive.ObjectID
|
||||
balancesPermissionRef primitive.ObjectID
|
||||
assets chainassets.DB
|
||||
}
|
||||
|
||||
type chainWalletClient interface {
|
||||
CreateManagedWallet(ctx context.Context, req *chainv1.CreateManagedWalletRequest) (*chainv1.CreateManagedWalletResponse, error)
|
||||
ListManagedWallets(ctx context.Context, req *chainv1.ListManagedWalletsRequest) (*chainv1.ListManagedWalletsResponse, error)
|
||||
GetWalletBalance(ctx context.Context, req *chainv1.GetWalletBalanceRequest) (*chainv1.GetWalletBalanceResponse, error)
|
||||
Close() error
|
||||
@@ -55,6 +58,12 @@ func CreateAPI(apiCtx eapi.API) (*WalletAPI, error) {
|
||||
wph: mutil.CreatePH(mservice.Wallets),
|
||||
}
|
||||
|
||||
var err error
|
||||
if p.assets, err = apiCtx.DBFactory().NewChainAsstesDB(); err != nil {
|
||||
p.logger.Warn("Failed to create asstes db", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
walletsPolicy, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.ChainWallets)
|
||||
if err != nil {
|
||||
p.logger.Warn("Failed to fetch chain wallets permission policy description", zap.Error(err))
|
||||
@@ -81,6 +90,7 @@ func CreateAPI(apiCtx eapi.API) (*WalletAPI, error) {
|
||||
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listWallets)
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.wph.AddRef(p.oph.AddRef("/"))+"/balance", api.Get, p.getWalletBalance)
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Post, p.create)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user