move api/server to api/edge/bff
This commit is contained in:
203
api/edge/bff/internal/server/walletapiimp/create.go
Normal file
203
api/edge/bff/internal/server/walletapiimp/create.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package walletapiimp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/discovery"
|
||||
"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"
|
||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/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"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
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, bson.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.discovery == nil {
|
||||
return response.Internal(a.logger, mservice.ChainGateway, merrors.Internal("discovery client is not configured"))
|
||||
}
|
||||
|
||||
// Find gateway for this network
|
||||
lookupCtx, cancel := context.WithTimeout(ctx, discoveryLookupTimeout)
|
||||
defer cancel()
|
||||
|
||||
lookupResp, err := a.discovery.Lookup(lookupCtx)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to lookup discovery registry", zap.Error(err))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
// Find gateway that handles this network
|
||||
networkName := strings.ToLower(string(asset.Asset.Chain))
|
||||
gateway := findGatewayForNetwork(lookupResp.Gateways, networkName)
|
||||
if gateway == nil {
|
||||
a.logger.Warn("No gateway found for network",
|
||||
zap.String("network", networkName),
|
||||
zap.String("chain", string(sr.Asset.Chain)))
|
||||
return response.Auto(a.logger, a.Name(), merrors.InvalidArgument("no gateway available for network: "+networkName))
|
||||
}
|
||||
a.logger.Debug("Selected gateway for wallet creation",
|
||||
zap.String("organization_ref", orgRef.Hex()),
|
||||
zap.String("network", networkName),
|
||||
zap.String("gateway_id", gateway.ID),
|
||||
zap.String("gateway_network", gateway.Network),
|
||||
zap.String("invoke_uri", gateway.InvokeURI))
|
||||
|
||||
var ownerRef string
|
||||
if sr.OwnerRef != nil && !sr.OwnerRef.IsZero() {
|
||||
ownerRef = sr.OwnerRef.Hex()
|
||||
}
|
||||
|
||||
// Build params for connector OpenAccount
|
||||
params := map[string]interface{}{
|
||||
"organization_ref": orgRef.Hex(),
|
||||
"network": networkName,
|
||||
"token_symbol": asset.Asset.TokenSymbol,
|
||||
"contract_address": asset.Asset.ContractAddress,
|
||||
}
|
||||
if sr.Description.Description != nil {
|
||||
params["description"] = *sr.Description.Description
|
||||
}
|
||||
params["metadata"] = map[string]interface{}{
|
||||
"source": "create",
|
||||
"login": account.Login,
|
||||
}
|
||||
|
||||
paramsStruct, _ := structpb.NewStruct(params)
|
||||
assetString := networkName + "-" + asset.Asset.TokenSymbol
|
||||
|
||||
req := &connectorv1.OpenAccountRequest{
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET,
|
||||
Asset: assetString,
|
||||
OwnerRef: ownerRef,
|
||||
Label: sr.Description.Name,
|
||||
Params: paramsStruct,
|
||||
}
|
||||
|
||||
// Connect to gateway and create wallet
|
||||
walletRef, err := a.createWalletOnGateway(ctx, *gateway, req)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create managed wallet", zap.Error(err),
|
||||
mzap.ObjRef("organization_ref", orgRef), mzap.StorableRef(account),
|
||||
zap.String("gateway_id", gateway.ID), zap.String("network", gateway.Network))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
a.logger.Info("Managed wallet created for organization", mzap.ObjRef("organization_ref", orgRef),
|
||||
zap.String("wallet_ref", walletRef), mzap.StorableRef(account),
|
||||
zap.String("gateway_id", gateway.ID), zap.String("network", gateway.Network))
|
||||
a.rememberWalletRoute(ctx, orgRef.Hex(), walletRef, networkName, gateway.ID)
|
||||
a.rememberWalletRoute(ctx, orgRef.Hex(), walletRef, gateway.Network, gateway.ID)
|
||||
a.logger.Debug("Persisted wallet route after wallet creation",
|
||||
zap.String("organization_ref", orgRef.Hex()),
|
||||
zap.String("wallet_ref", walletRef),
|
||||
zap.String("network", networkName),
|
||||
zap.String("gateway_id", gateway.ID))
|
||||
|
||||
return sresponse.Success(a.logger, token)
|
||||
}
|
||||
|
||||
func findGatewayForNetwork(gateways []discovery.GatewaySummary, network string) *discovery.GatewaySummary {
|
||||
network = strings.ToLower(strings.TrimSpace(network))
|
||||
for _, gw := range gateways {
|
||||
if !strings.EqualFold(gw.Rail, cryptoRail) || !gw.Healthy || strings.TrimSpace(gw.InvokeURI) == "" {
|
||||
continue
|
||||
}
|
||||
// Check if gateway network matches
|
||||
gwNetwork := strings.ToLower(strings.TrimSpace(gw.Network))
|
||||
if gwNetwork == network {
|
||||
return &gw
|
||||
}
|
||||
// Also check if network starts with gateway network prefix (e.g., "tron" matches "tron_mainnet")
|
||||
if strings.HasPrefix(network, gwNetwork) || strings.HasPrefix(gwNetwork, network) {
|
||||
return &gw
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *WalletAPI) createWalletOnGateway(ctx context.Context, gateway discovery.GatewaySummary, req *connectorv1.OpenAccountRequest) (string, error) {
|
||||
// Create connection with timeout
|
||||
dialCtx, cancel := context.WithTimeout(ctx, a.dialTimeout)
|
||||
defer cancel()
|
||||
|
||||
var dialOpts []grpc.DialOption
|
||||
if a.insecure {
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
} else {
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))
|
||||
}
|
||||
|
||||
conn, err := grpc.DialContext(dialCtx, gateway.InvokeURI, dialOpts...)
|
||||
if err != nil {
|
||||
return "", merrors.InternalWrap(err, "dial gateway")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
client := connectorv1.NewConnectorServiceClient(conn)
|
||||
|
||||
// Call with timeout
|
||||
callCtx, callCancel := context.WithTimeout(ctx, a.callTimeout)
|
||||
defer callCancel()
|
||||
|
||||
resp, err := client.OpenAccount(callCtx, req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.GetError() != nil {
|
||||
return "", merrors.Internal(resp.GetError().GetMessage())
|
||||
}
|
||||
|
||||
account := resp.GetAccount()
|
||||
if account == nil || account.GetRef() == nil {
|
||||
return "", merrors.Internal("gateway returned empty account")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(account.GetRef().GetAccountId()), nil
|
||||
}
|
||||
Reference in New Issue
Block a user