Compare commits
2 Commits
33647a0f3d
...
dedde76dd7
| Author | SHA1 | Date | |
|---|---|---|---|
| dedde76dd7 | |||
|
|
9e747e7251 |
11
api/pkg/db/chainassets/assets.go
Normal file
11
api/pkg/db/chainassets/assets.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package chainassets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB interface {
|
||||||
|
Resolve(ctx context.Context, chainAsset model.ChainAssetKey) (*model.ChainAssetDescription, error)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"github.com/tech/sendico/pkg/auth"
|
"github.com/tech/sendico/pkg/auth"
|
||||||
"github.com/tech/sendico/pkg/db/account"
|
"github.com/tech/sendico/pkg/db/account"
|
||||||
|
"github.com/tech/sendico/pkg/db/chainassets"
|
||||||
"github.com/tech/sendico/pkg/db/confirmation"
|
"github.com/tech/sendico/pkg/db/confirmation"
|
||||||
mongoimpl "github.com/tech/sendico/pkg/db/internal/mongo"
|
mongoimpl "github.com/tech/sendico/pkg/db/internal/mongo"
|
||||||
"github.com/tech/sendico/pkg/db/invitation"
|
"github.com/tech/sendico/pkg/db/invitation"
|
||||||
@@ -22,6 +23,8 @@ type Factory interface {
|
|||||||
NewRefreshTokensDB() (refreshtokens.DB, error)
|
NewRefreshTokensDB() (refreshtokens.DB, error)
|
||||||
NewConfirmationsDB() (confirmation.DB, error)
|
NewConfirmationsDB() (confirmation.DB, error)
|
||||||
|
|
||||||
|
NewChainAsstesDB() (chainassets.DB, error)
|
||||||
|
|
||||||
NewAccountDB() (account.DB, error)
|
NewAccountDB() (account.DB, error)
|
||||||
NewOrganizationDB() (organization.DB, error)
|
NewOrganizationDB() (organization.DB, error)
|
||||||
NewInvitationsDB() (invitation.DB, error)
|
NewInvitationsDB() (invitation.DB, error)
|
||||||
|
|||||||
73
api/pkg/db/internal/mongo/chainassetsdb/db.go
Normal file
73
api/pkg/db/internal/mongo/chainassetsdb/db.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package chainassetsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
ri "github.com/tech/sendico/pkg/db/repository/index"
|
||||||
|
"github.com/tech/sendico/pkg/db/template"
|
||||||
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChainAssetsDB struct {
|
||||||
|
template.DBImp[*model.ChainAssetDescription]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(logger mlogger.Logger, db *mongo.Database) (*ChainAssetsDB, error) {
|
||||||
|
p := &ChainAssetsDB{
|
||||||
|
DBImp: *template.Create[*model.ChainAssetDescription](logger, mservice.ChainAssets, db),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Canonical lookup: enforce single (chain, tokenSymbol)
|
||||||
|
if err := p.Repository.CreateIndex(&ri.Definition{
|
||||||
|
Name: "idx_chain_symbol",
|
||||||
|
Unique: true,
|
||||||
|
Keys: []ri.Key{
|
||||||
|
{Field: "asset.chain", Sort: ri.Asc},
|
||||||
|
{Field: "asset.tokenSymbol", Sort: ri.Asc},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
p.Logger.Error("failed index (chain, symbol) unique", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Prevent duplicate contracts inside the same chain, but only when contract exists
|
||||||
|
if err := p.Repository.CreateIndex(&ri.Definition{
|
||||||
|
Name: "idx_chain_contract_unique",
|
||||||
|
Unique: true,
|
||||||
|
Sparse: true,
|
||||||
|
Keys: []ri.Key{
|
||||||
|
{Field: "asset.chain", Sort: ri.Asc},
|
||||||
|
{Field: "asset.contractAddress", Sort: ri.Asc},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
p.Logger.Error("failed index (chain, contract) unique", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Fast contract lookup, skip docs without contractAddress (native assets)
|
||||||
|
if err := p.Repository.CreateIndex(&ri.Definition{
|
||||||
|
Name: "idx_contract_lookup",
|
||||||
|
Sparse: true,
|
||||||
|
Keys: []ri.Key{
|
||||||
|
{Field: "asset.contractAddress", Sort: ri.Asc},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
p.Logger.Error("failed index contract lookup", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) List assets per chain
|
||||||
|
if err := p.Repository.CreateIndex(&ri.Definition{
|
||||||
|
Name: "idx_chain_list",
|
||||||
|
Keys: []ri.Key{
|
||||||
|
{Field: "asset.chain", Sort: ri.Asc},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
p.Logger.Error("failed index chain list", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
18
api/pkg/db/internal/mongo/chainassetsdb/resolve.go
Normal file
18
api/pkg/db/internal/mongo/chainassetsdb/resolve.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package chainassetsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/db/repository"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (db *ChainAssetsDB) Resolve(ctx context.Context, chainAsset model.ChainAssetKey) (*model.ChainAssetDescription, error) {
|
||||||
|
var assetDescription model.ChainAssetDescription
|
||||||
|
assetField := repository.Field("asset")
|
||||||
|
q := repository.Query().And(
|
||||||
|
repository.Query().Filter(assetField.Dot("chain"), chainAsset.Chain),
|
||||||
|
repository.Query().Filter(assetField.Dot("tokenSymbol"), chainAsset.TokenSymbol),
|
||||||
|
)
|
||||||
|
return &assetDescription, db.DBImp.FindOne(ctx, q, &assetDescription)
|
||||||
|
}
|
||||||
@@ -10,8 +10,10 @@ import (
|
|||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/tech/sendico/pkg/auth"
|
"github.com/tech/sendico/pkg/auth"
|
||||||
"github.com/tech/sendico/pkg/db/account"
|
"github.com/tech/sendico/pkg/db/account"
|
||||||
|
"github.com/tech/sendico/pkg/db/chainassets"
|
||||||
"github.com/tech/sendico/pkg/db/confirmation"
|
"github.com/tech/sendico/pkg/db/confirmation"
|
||||||
"github.com/tech/sendico/pkg/db/internal/mongo/accountdb"
|
"github.com/tech/sendico/pkg/db/internal/mongo/accountdb"
|
||||||
|
"github.com/tech/sendico/pkg/db/internal/mongo/chainassetsdb"
|
||||||
"github.com/tech/sendico/pkg/db/internal/mongo/confirmationdb"
|
"github.com/tech/sendico/pkg/db/internal/mongo/confirmationdb"
|
||||||
"github.com/tech/sendico/pkg/db/internal/mongo/invitationdb"
|
"github.com/tech/sendico/pkg/db/internal/mongo/invitationdb"
|
||||||
"github.com/tech/sendico/pkg/db/internal/mongo/organizationdb"
|
"github.com/tech/sendico/pkg/db/internal/mongo/organizationdb"
|
||||||
@@ -312,6 +314,10 @@ func collectReplicaHosts(configuredHosts []string, replicaSet, defaultPort, host
|
|||||||
return hosts
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) NewChainAsstesDB() (chainassets.DB, error) {
|
||||||
|
return chainassetsdb.Create(db.logger, db.db())
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) Permissions() auth.Provider {
|
func (db *DB) Permissions() auth.Provider {
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ func (r *MongoRepository) CreateIndex(def *ri.Definition) error {
|
|||||||
if def.PartialFilter != nil {
|
if def.PartialFilter != nil {
|
||||||
opts.SetPartialFilterExpression(def.PartialFilter.BuildQuery())
|
opts.SetPartialFilterExpression(def.PartialFilter.BuildQuery())
|
||||||
}
|
}
|
||||||
|
if def.Sparse {
|
||||||
|
opts.SetSparse(def.Sparse)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := r.collection.Indexes().CreateOne(
|
_, err := r.collection.Indexes().CreateOne(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type Key struct {
|
|||||||
type Definition struct {
|
type Definition struct {
|
||||||
Keys []Key // mandatory, at least one element
|
Keys []Key // mandatory, at least one element
|
||||||
Unique bool // unique constraint?
|
Unique bool // unique constraint?
|
||||||
|
Sparse bool // sparse?
|
||||||
TTL *int32 // seconds; nil means “no TTL”
|
TTL *int32 // seconds; nil means “no TTL”
|
||||||
Name string // optional explicit name
|
Name string // optional explicit name
|
||||||
PartialFilter builder.Query // optional: partialFilterExpression for conditional indexes
|
PartialFilter builder.Query // optional: partialFilterExpression for conditional indexes
|
||||||
|
|||||||
26
api/pkg/model/chainasset.go
Normal file
26
api/pkg/model/chainasset.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tech/sendico/pkg/db/storable"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChainAssetKey struct {
|
||||||
|
Chain ChainNetwork `bson:"chain" json:"chain" yaml:"chain" mapstructure:"chain"`
|
||||||
|
TokenSymbol string `bson:"tokenSymbol" json:"tokenSymbol" yaml:"tokenSymbol" mapstructure:"tokenSymbol"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChainAsset struct {
|
||||||
|
ChainAssetKey `bson:",inline" json:",inline"`
|
||||||
|
ContractAddress *string `bson:"contractAddress,omitempty" json:"contractAddress,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChainAssetDescription struct {
|
||||||
|
storable.Storable `bson:",inline" json:",inline"`
|
||||||
|
Describable `bson:",inline" json:",inline"`
|
||||||
|
Asset ChainAsset `bson:"asset" json:"asset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Collection(*ChainAssetDescription) mservice.Type {
|
||||||
|
return mservice.ChainAssets
|
||||||
|
}
|
||||||
11
api/pkg/model/chains.go
Normal file
11
api/pkg/model/chains.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
type ChainNetwork string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChainNetworkARB ChainNetwork = "arbitrum_one"
|
||||||
|
ChainNetworkEthMain ChainNetwork = "ethereum_mainnet"
|
||||||
|
ChainNetworkTronMain ChainNetwork = "tron_mainnet"
|
||||||
|
ChainNetworkTronNile ChainNetwork = "tron_nile"
|
||||||
|
ChainNetworkUnspecified ChainNetwork = "unspecified"
|
||||||
|
)
|
||||||
@@ -5,50 +5,51 @@ import "github.com/tech/sendico/pkg/merrors"
|
|||||||
type Type = string
|
type Type = string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Accounts Type = "accounts" // Represents user accounts in the system
|
Accounts Type = "accounts" // Represents user accounts in the system
|
||||||
Confirmations Type = "confirmations" // Represents confirmation code flows
|
Confirmations Type = "confirmations" // Represents confirmation code flows
|
||||||
Amplitude Type = "amplitude" // Represents analytics integration with Amplitude
|
Amplitude Type = "amplitude" // Represents analytics integration with Amplitude
|
||||||
Discovery Type = "discovery" // Represents service discovery registry
|
Discovery Type = "discovery" // Represents service discovery registry
|
||||||
Site Type = "site" // Represents public site endpoints
|
Site Type = "site" // Represents public site endpoints
|
||||||
Changes Type = "changes" // Tracks changes made to resources
|
Changes Type = "changes" // Tracks changes made to resources
|
||||||
Clients Type = "clients" // Represents client information
|
Clients Type = "clients" // Represents client information
|
||||||
ChainGateway Type = "chain_gateway" // Represents chain gateway microservice
|
ChainGateway Type = "chain_gateway" // Represents chain gateway microservice
|
||||||
MntxGateway Type = "mntx_gateway" // Represents Monetix gateway microservice
|
MntxGateway Type = "mntx_gateway" // Represents Monetix gateway microservice
|
||||||
PaymentGateway Type = "payment_gateway" // Represents payment gateway microservice
|
PaymentGateway Type = "payment_gateway" // Represents payment gateway microservice
|
||||||
FXOracle Type = "fx_oracle" // Represents FX oracle microservice
|
FXOracle Type = "fx_oracle" // Represents FX oracle microservice
|
||||||
FeePlans Type = "fee_plans" // Represents fee plans microservice
|
FeePlans Type = "fee_plans" // Represents fee plans microservice
|
||||||
FilterProjects Type = "filter_projects" // Represents comments on tasks or other resources
|
FilterProjects Type = "filter_projects" // Represents comments on tasks or other resources
|
||||||
Invitations Type = "invitations" // Represents invitations sent to users
|
Invitations Type = "invitations" // Represents invitations sent to users
|
||||||
Invoices Type = "invoices" // Represents invoices
|
Invoices Type = "invoices" // Represents invoices
|
||||||
Logo Type = "logo" // Represents logos for organizations or projects
|
Logo Type = "logo" // Represents logos for organizations or projects
|
||||||
Ledger Type = "ledger" // Represents ledger microservice
|
Ledger Type = "ledger" // Represents ledger microservice
|
||||||
LedgerAccounts Type = "ledger_accounts" // Represents ledger accounts microservice
|
LedgerAccounts Type = "ledger_accounts" // Represents ledger accounts microservice
|
||||||
LedgerBalances Type = "ledger_balances" // Represents ledger account balances microservice
|
LedgerBalances Type = "ledger_balances" // Represents ledger account balances microservice
|
||||||
LedgerEntries Type = "ledger_journal_entries" // Represents ledger journal entries microservice
|
LedgerEntries Type = "ledger_journal_entries" // Represents ledger journal entries microservice
|
||||||
LedgerOutbox Type = "ledger_outbox" // Represents ledger outbox microservice
|
LedgerOutbox Type = "ledger_outbox" // Represents ledger outbox microservice
|
||||||
LedgerParties Type = "ledger_parties" // Represents ledger account owner parties microservice
|
LedgerParties Type = "ledger_parties" // Represents ledger account owner parties microservice
|
||||||
LedgerPlines Type = "ledger_posting_lines" // Represents ledger journal posting lines microservice
|
LedgerPlines Type = "ledger_posting_lines" // Represents ledger journal posting lines microservice
|
||||||
PaymentOrchestrator Type = "payment_orchestrator" // Represents payment orchestration microservice
|
PaymentOrchestrator Type = "payment_orchestrator" // Represents payment orchestration microservice
|
||||||
ChainWallets Type = "chain_wallets" // Represents managed chain wallets
|
ChainAssets Type = "chain_assets" // Represents managed chain assets
|
||||||
ChainWalletBalances Type = "chain_wallet_balances" // Represents managed chain wallet balances
|
ChainWallets Type = "chain_wallets" // Represents managed chain wallets
|
||||||
ChainTransfers Type = "chain_transfers" // Represents chain transfers
|
ChainWalletBalances Type = "chain_wallet_balances" // Represents managed chain wallet balances
|
||||||
ChainDeposits Type = "chain_deposits" // Represents chain deposits
|
ChainTransfers Type = "chain_transfers" // Represents chain transfers
|
||||||
Notifications Type = "notifications" // Represents notifications sent to users
|
ChainDeposits Type = "chain_deposits" // Represents chain deposits
|
||||||
Organizations Type = "organizations" // Represents organizations in the system
|
Notifications Type = "notifications" // Represents notifications sent to users
|
||||||
Payments Type = "payments" // Represents payments service
|
Organizations Type = "organizations" // Represents organizations in the system
|
||||||
PaymentRoutes Type = "payment_routes" // Represents payment routing definitions
|
Payments Type = "payments" // Represents payments service
|
||||||
|
PaymentRoutes Type = "payment_routes" // Represents payment routing definitions
|
||||||
PaymentPlanTemplates Type = "payment_plan_templates" // Represents payment plan templates
|
PaymentPlanTemplates Type = "payment_plan_templates" // Represents payment plan templates
|
||||||
PaymentMethods Type = "payment_methods" // Represents payment methods service
|
PaymentMethods Type = "payment_methods" // Represents payment methods service
|
||||||
Permissions Type = "permissions" // Represents permissiosns service
|
Permissions Type = "permissions" // Represents permissiosns service
|
||||||
Policies Type = "policies" // Represents access control policies
|
Policies Type = "policies" // Represents access control policies
|
||||||
PolicyAssignements Type = "policy_assignments" // Represents policy assignments database
|
PolicyAssignements Type = "policy_assignments" // Represents policy assignments database
|
||||||
Recipients Type = "recipients" // Represents payment recipients
|
Recipients Type = "recipients" // Represents payment recipients
|
||||||
RefreshTokens Type = "refresh_tokens" // Represents refresh tokens for authentication
|
RefreshTokens Type = "refresh_tokens" // Represents refresh tokens for authentication
|
||||||
Roles Type = "roles" // Represents roles in access control
|
Roles Type = "roles" // Represents roles in access control
|
||||||
Storage Type = "storage" // Represents statuses of tasks or projects
|
Storage Type = "storage" // Represents statuses of tasks or projects
|
||||||
Tenants Type = "tenants" // Represents tenants managed in the system
|
Tenants Type = "tenants" // Represents tenants managed in the system
|
||||||
Wallets Type = "wallets" // Represents workflows for tasks or projects
|
Wallets Type = "wallets" // Represents workflows for tasks or projects
|
||||||
Workflows Type = "workflows" // Represents workflows for tasks or projects
|
Workflows Type = "workflows" // Represents workflows for tasks or projects
|
||||||
)
|
)
|
||||||
|
|
||||||
func StringToSType(s string) (Type, error) {
|
func StringToSType(s string) (Type, error) {
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ message Account {
|
|||||||
string asset = 3; // canonical asset string (USD, ETH, USDT-TRC20)
|
string asset = 3; // canonical asset string (USD, ETH, USDT-TRC20)
|
||||||
AccountState state = 4;
|
AccountState state = 4;
|
||||||
string label = 5;
|
string label = 5;
|
||||||
string owner_ref = 6;
|
string owner_ref = 6; // optional account_ref; empty means organization-owned
|
||||||
google.protobuf.Struct provider_details = 7;
|
google.protobuf.Struct provider_details = 7;
|
||||||
google.protobuf.Timestamp created_at = 8;
|
google.protobuf.Timestamp created_at = 8;
|
||||||
google.protobuf.Timestamp updated_at = 9;
|
google.protobuf.Timestamp updated_at = 9;
|
||||||
@@ -188,7 +188,7 @@ message OpenAccountRequest {
|
|||||||
AccountKind kind = 2;
|
AccountKind kind = 2;
|
||||||
string asset = 3; // canonical asset string (USD, ETH, USDT-TRC20)
|
string asset = 3; // canonical asset string (USD, ETH, USDT-TRC20)
|
||||||
string label = 4;
|
string label = 4;
|
||||||
string owner_ref = 5;
|
string owner_ref = 5; // optional account_ref; empty means organization-owned
|
||||||
google.protobuf.Struct params = 6;
|
google.protobuf.Struct params = 6;
|
||||||
string correlation_id = 7;
|
string correlation_id = 7;
|
||||||
string parent_intent_id = 8;
|
string parent_intent_id = 8;
|
||||||
@@ -212,6 +212,7 @@ message ListAccountsRequest {
|
|||||||
AccountKind kind = 2;
|
AccountKind kind = 2;
|
||||||
string asset = 3; // canonical asset string (USD, ETH, USDT-TRC20)
|
string asset = 3; // canonical asset string (USD, ETH, USDT-TRC20)
|
||||||
common.pagination.v1.CursorPageRequest page = 4;
|
common.pagination.v1.CursorPageRequest page = 4;
|
||||||
|
string organization_ref = 5; // optional org scope (preferred over owner_ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListAccountsResponse {
|
message ListAccountsResponse {
|
||||||
|
|||||||
@@ -70,14 +70,15 @@ message PostingLine {
|
|||||||
|
|
||||||
message CreateAccountRequest {
|
message CreateAccountRequest {
|
||||||
string organization_ref = 1;
|
string organization_ref = 1;
|
||||||
string account_code = 2;
|
string owner_ref = 2;
|
||||||
AccountType account_type = 3;
|
string account_code = 3;
|
||||||
string currency = 4;
|
AccountType account_type = 4;
|
||||||
AccountStatus status = 5;
|
string currency = 5;
|
||||||
bool allow_negative = 6;
|
AccountStatus status = 6;
|
||||||
bool is_settlement = 7;
|
bool allow_negative = 7;
|
||||||
map<string, string> metadata = 8;
|
bool is_settlement = 8;
|
||||||
common.describable.v1.Describable describable = 9;
|
map<string, string> metadata = 9;
|
||||||
|
common.describable.v1.Describable describable = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateAccountResponse {
|
message CreateAccountResponse {
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ type CreateLedgerAccount struct {
|
|||||||
AllowNegative bool `json:"allowNegative,omitempty"`
|
AllowNegative bool `json:"allowNegative,omitempty"`
|
||||||
IsSettlement bool `json:"isSettlement,omitempty"`
|
IsSettlement bool `json:"isSettlement,omitempty"`
|
||||||
Metadata map[string]string `json:"metadata,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 {
|
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
|
var sr srequest.Signup
|
||||||
if err := json.NewDecoder(r.Body).Decode(&sr); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&sr); err != nil {
|
||||||
a.logger.Warn("Failed to decode signup request", zap.Error(err))
|
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))
|
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{
|
req := &chainv1.CreateManagedWalletRequest{
|
||||||
IdempotencyKey: uuid.NewString(),
|
IdempotencyKey: uuid.NewString(),
|
||||||
OrganizationRef: org.ID.Hex(),
|
OrganizationRef: org.ID.Hex(),
|
||||||
OwnerRef: org.ID.Hex(),
|
|
||||||
Describable: &describablev1.Describable{
|
Describable: &describablev1.Describable{
|
||||||
Name: sr.CryptoWallet.Name,
|
Name: sr.CryptoWallet.Name,
|
||||||
Description: sr.CryptoWallet.Description,
|
Description: sr.CryptoWallet.Description,
|
||||||
},
|
},
|
||||||
Asset: a.chainAsset,
|
Asset: a.chainAsset,
|
||||||
Metadata: map[string]string{
|
Metadata: map[string]string{
|
||||||
"source": "signup",
|
"source": "signup",
|
||||||
"login": sr.Account.Login,
|
"login": sr.Account.Login,
|
||||||
|
|||||||
@@ -56,25 +56,28 @@ func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token
|
|||||||
}
|
}
|
||||||
|
|
||||||
var describable *describablev1.Describable
|
var describable *describablev1.Describable
|
||||||
if payload.Describable != nil {
|
name := strings.TrimSpace(payload.Describable.Name)
|
||||||
name := strings.TrimSpace(payload.Describable.Name)
|
var description *string
|
||||||
var description *string
|
if payload.Describable.Description != nil {
|
||||||
if payload.Describable.Description != nil {
|
trimmed := strings.TrimSpace(*payload.Describable.Description)
|
||||||
trimmed := strings.TrimSpace(*payload.Describable.Description)
|
if trimmed != "" {
|
||||||
if trimmed != "" {
|
description = &trimmed
|
||||||
description = &trimmed
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if name != "" || description != nil {
|
}
|
||||||
describable = &describablev1.Describable{
|
if name != "" || description != nil {
|
||||||
Name: name,
|
describable = &describablev1.Describable{
|
||||||
Description: description,
|
Name: name,
|
||||||
}
|
Description: description,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var ownerRef string
|
||||||
|
if !payload.IsOrgWallet {
|
||||||
|
ownerRef = account.ID.Hex()
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := a.client.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
|
resp, err := a.client.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
|
||||||
OrganizationRef: orgRef.Hex(),
|
OrganizationRef: orgRef.Hex(),
|
||||||
|
OwnerRef: ownerRef,
|
||||||
AccountCode: payload.AccountCode,
|
AccountCode: payload.AccountCode,
|
||||||
AccountType: accountType,
|
AccountType: accountType,
|
||||||
Currency: payload.Currency,
|
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) {
|
func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAccount, error) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
payload := &srequest.CreateLedgerAccount{}
|
payload := srequest.CreateLedgerAccount{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
return nil, merrors.InvalidArgument("invalid payload: " + err.Error())
|
return nil, merrors.InvalidArgument("invalid payload: " + err.Error())
|
||||||
}
|
}
|
||||||
payload.AccountCode = strings.TrimSpace(payload.AccountCode)
|
payload.AccountCode = strings.TrimSpace(payload.AccountCode)
|
||||||
payload.Currency = strings.ToUpper(strings.TrimSpace(payload.Currency))
|
payload.Currency = strings.ToUpper(strings.TrimSpace(payload.Currency))
|
||||||
if payload.Describable != nil {
|
payload.Describable.Name = strings.TrimSpace(payload.Describable.Name)
|
||||||
payload.Describable.Name = strings.TrimSpace(payload.Describable.Name)
|
if payload.Describable.Description != nil {
|
||||||
if payload.Describable.Description != nil {
|
trimmed := strings.TrimSpace(*payload.Describable.Description)
|
||||||
trimmed := strings.TrimSpace(*payload.Describable.Description)
|
if trimmed == "" {
|
||||||
if trimmed == "" {
|
payload.Describable.Description = nil
|
||||||
payload.Describable.Description = nil
|
} else {
|
||||||
} else {
|
payload.Describable.Description = &trimmed
|
||||||
payload.Describable.Description = &trimmed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if payload.Describable.Name == "" && payload.Describable.Description == nil {
|
|
||||||
payload.Describable = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(payload.Metadata) == 0 {
|
if len(payload.Metadata) == 0 {
|
||||||
@@ -121,7 +119,7 @@ func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAc
|
|||||||
if err := payload.Validate(); err != nil {
|
if err := payload.Validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return payload, nil
|
return &payload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapLedgerAccountType(accountType srequest.LedgerAccountType) (ledgerv1.AccountType, error) {
|
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"
|
chaingatewayclient "github.com/tech/sendico/gateway/chain/client"
|
||||||
api "github.com/tech/sendico/pkg/api/http"
|
api "github.com/tech/sendico/pkg/api/http"
|
||||||
"github.com/tech/sendico/pkg/auth"
|
"github.com/tech/sendico/pkg/auth"
|
||||||
|
"github.com/tech/sendico/pkg/db/chainassets"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
@@ -28,9 +29,11 @@ type WalletAPI struct {
|
|||||||
wph mutil.ParamHelper
|
wph mutil.ParamHelper
|
||||||
walletsPermissionRef primitive.ObjectID
|
walletsPermissionRef primitive.ObjectID
|
||||||
balancesPermissionRef primitive.ObjectID
|
balancesPermissionRef primitive.ObjectID
|
||||||
|
assets chainassets.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
type chainWalletClient interface {
|
type chainWalletClient interface {
|
||||||
|
CreateManagedWallet(ctx context.Context, req *chainv1.CreateManagedWalletRequest) (*chainv1.CreateManagedWalletResponse, error)
|
||||||
ListManagedWallets(ctx context.Context, req *chainv1.ListManagedWalletsRequest) (*chainv1.ListManagedWalletsResponse, error)
|
ListManagedWallets(ctx context.Context, req *chainv1.ListManagedWalletsRequest) (*chainv1.ListManagedWalletsResponse, error)
|
||||||
GetWalletBalance(ctx context.Context, req *chainv1.GetWalletBalanceRequest) (*chainv1.GetWalletBalanceResponse, error)
|
GetWalletBalance(ctx context.Context, req *chainv1.GetWalletBalanceRequest) (*chainv1.GetWalletBalanceResponse, error)
|
||||||
Close() error
|
Close() error
|
||||||
@@ -55,6 +58,12 @@ func CreateAPI(apiCtx eapi.API) (*WalletAPI, error) {
|
|||||||
wph: mutil.CreatePH(mservice.Wallets),
|
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)
|
walletsPolicy, err := apiCtx.Permissions().GetPolicyDescription(context.Background(), mservice.ChainWallets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Warn("Failed to fetch chain wallets permission policy description", zap.Error(err))
|
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.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.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
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user