new tron gateway
This commit is contained in:
54
api/gateway/tron/storage/model/deposit.go
Normal file
54
api/gateway/tron/storage/model/deposit.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
)
|
||||
|
||||
type DepositStatus string
|
||||
|
||||
const (
|
||||
DepositStatusPending DepositStatus = "pending"
|
||||
DepositStatusConfirmed DepositStatus = "confirmed"
|
||||
DepositStatusFailed DepositStatus = "failed"
|
||||
)
|
||||
|
||||
// Deposit records an inbound transfer observed on-chain.
|
||||
type Deposit struct {
|
||||
storable.Base `bson:",inline" json:",inline"`
|
||||
|
||||
DepositRef string `bson:"depositRef" json:"depositRef"`
|
||||
WalletRef string `bson:"walletRef" json:"walletRef"`
|
||||
Network string `bson:"network" json:"network"`
|
||||
TokenSymbol string `bson:"tokenSymbol" json:"tokenSymbol"`
|
||||
ContractAddress string `bson:"contractAddress" json:"contractAddress"`
|
||||
Amount *moneyv1.Money `bson:"amount" json:"amount"`
|
||||
SourceAddress string `bson:"sourceAddress" json:"sourceAddress"`
|
||||
TxHash string `bson:"txHash" json:"txHash"`
|
||||
BlockID string `bson:"blockId,omitempty" json:"blockId,omitempty"`
|
||||
Status DepositStatus `bson:"status" json:"status"`
|
||||
ObservedAt time.Time `bson:"observedAt" json:"observedAt"`
|
||||
RecordedAt time.Time `bson:"recordedAt" json:"recordedAt"`
|
||||
LastStatusAt time.Time `bson:"lastStatusAt" json:"lastStatusAt"`
|
||||
}
|
||||
|
||||
// Collection implements storable.Storable.
|
||||
func (*Deposit) Collection() string {
|
||||
return mservice.ChainDeposits
|
||||
}
|
||||
|
||||
// Normalize standardizes case-sensitive fields.
|
||||
func (d *Deposit) Normalize() {
|
||||
d.DepositRef = strings.TrimSpace(d.DepositRef)
|
||||
d.WalletRef = strings.TrimSpace(d.WalletRef)
|
||||
d.Network = strings.TrimSpace(strings.ToLower(d.Network))
|
||||
d.TokenSymbol = strings.TrimSpace(strings.ToUpper(d.TokenSymbol))
|
||||
d.ContractAddress = strings.TrimSpace(strings.ToLower(d.ContractAddress))
|
||||
d.SourceAddress = strings.TrimSpace(strings.ToLower(d.SourceAddress))
|
||||
d.TxHash = strings.TrimSpace(strings.ToLower(d.TxHash))
|
||||
d.BlockID = strings.TrimSpace(d.BlockID)
|
||||
}
|
||||
93
api/gateway/tron/storage/model/transfer.go
Normal file
93
api/gateway/tron/storage/model/transfer.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
)
|
||||
|
||||
type TransferStatus string
|
||||
|
||||
const (
|
||||
TransferStatusPending TransferStatus = "pending"
|
||||
TransferStatusSigning TransferStatus = "signing"
|
||||
TransferStatusSubmitted TransferStatus = "submitted"
|
||||
TransferStatusConfirmed TransferStatus = "confirmed"
|
||||
TransferStatusFailed TransferStatus = "failed"
|
||||
TransferStatusCancelled TransferStatus = "cancelled"
|
||||
)
|
||||
|
||||
// ServiceFee represents a fee component applied to a transfer.
|
||||
type ServiceFee struct {
|
||||
FeeCode string `bson:"feeCode" json:"feeCode"`
|
||||
Amount *moneyv1.Money `bson:"amount" json:"amount"`
|
||||
Description string `bson:"description,omitempty" json:"description,omitempty"`
|
||||
}
|
||||
|
||||
type TransferDestination struct {
|
||||
ManagedWalletRef string `bson:"managedWalletRef,omitempty" json:"managedWalletRef,omitempty"`
|
||||
ExternalAddress string `bson:"externalAddress,omitempty" json:"externalAddress,omitempty"`
|
||||
ExternalAddressOriginal string `bson:"externalAddressOriginal,omitempty" json:"externalAddressOriginal,omitempty"`
|
||||
Memo string `bson:"memo,omitempty" json:"memo,omitempty"`
|
||||
}
|
||||
|
||||
// Transfer models an on-chain transfer orchestrated by the gateway.
|
||||
type Transfer struct {
|
||||
storable.Base `bson:",inline" json:",inline"`
|
||||
|
||||
TransferRef string `bson:"transferRef" json:"transferRef"`
|
||||
IdempotencyKey string `bson:"idempotencyKey" json:"idempotencyKey"`
|
||||
OrganizationRef string `bson:"organizationRef" json:"organizationRef"`
|
||||
SourceWalletRef string `bson:"sourceWalletRef" json:"sourceWalletRef"`
|
||||
Destination TransferDestination `bson:"destination" json:"destination"`
|
||||
Network string `bson:"network" json:"network"`
|
||||
TokenSymbol string `bson:"tokenSymbol" json:"tokenSymbol"`
|
||||
ContractAddress string `bson:"contractAddress" json:"contractAddress"`
|
||||
RequestedAmount *moneyv1.Money `bson:"requestedAmount" json:"requestedAmount"`
|
||||
NetAmount *moneyv1.Money `bson:"netAmount" json:"netAmount"`
|
||||
Fees []ServiceFee `bson:"fees,omitempty" json:"fees,omitempty"`
|
||||
Status TransferStatus `bson:"status" json:"status"`
|
||||
TxHash string `bson:"txHash,omitempty" json:"txHash,omitempty"`
|
||||
FailureReason string `bson:"failureReason,omitempty" json:"failureReason,omitempty"`
|
||||
ClientReference string `bson:"clientReference,omitempty" json:"clientReference,omitempty"`
|
||||
LastStatusAt time.Time `bson:"lastStatusAt" json:"lastStatusAt"`
|
||||
}
|
||||
|
||||
// Collection implements storable.Storable.
|
||||
func (*Transfer) Collection() string {
|
||||
return mservice.ChainTransfers
|
||||
}
|
||||
|
||||
// TransferFilter describes the parameters for listing transfers.
|
||||
type TransferFilter struct {
|
||||
SourceWalletRef string
|
||||
DestinationWalletRef string
|
||||
Status TransferStatus
|
||||
Cursor string
|
||||
Limit int32
|
||||
}
|
||||
|
||||
// TransferList contains paginated transfer results.
|
||||
type TransferList struct {
|
||||
Items []*Transfer
|
||||
NextCursor string
|
||||
}
|
||||
|
||||
// Normalize trims strings for consistent indexes.
|
||||
func (t *Transfer) Normalize() {
|
||||
t.TransferRef = strings.TrimSpace(t.TransferRef)
|
||||
t.IdempotencyKey = strings.TrimSpace(t.IdempotencyKey)
|
||||
t.OrganizationRef = strings.TrimSpace(t.OrganizationRef)
|
||||
t.SourceWalletRef = strings.TrimSpace(t.SourceWalletRef)
|
||||
t.Network = strings.TrimSpace(strings.ToLower(t.Network))
|
||||
t.TokenSymbol = strings.TrimSpace(strings.ToUpper(t.TokenSymbol))
|
||||
t.ContractAddress = strings.TrimSpace(strings.ToLower(t.ContractAddress))
|
||||
t.Destination.ManagedWalletRef = strings.TrimSpace(t.Destination.ManagedWalletRef)
|
||||
t.Destination.ExternalAddress = normalizeWalletAddress(t.Destination.ExternalAddress)
|
||||
t.Destination.ExternalAddressOriginal = strings.TrimSpace(t.Destination.ExternalAddressOriginal)
|
||||
t.Destination.Memo = strings.TrimSpace(t.Destination.Memo)
|
||||
t.ClientReference = strings.TrimSpace(t.ClientReference)
|
||||
}
|
||||
50
api/gateway/tron/storage/model/transfer_test.go
Normal file
50
api/gateway/tron/storage/model/transfer_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTransferNormalizePreservesBase58ExternalAddress(t *testing.T) {
|
||||
address := "TGBDXEg9rxSqGFJDcb889zqTjDwx1bmLRF"
|
||||
transfer := &Transfer{
|
||||
IdempotencyKey: "idemp",
|
||||
TransferRef: "ref",
|
||||
OrganizationRef: "org",
|
||||
SourceWalletRef: "wallet",
|
||||
Network: "tron_mainnet",
|
||||
TokenSymbol: "USDT",
|
||||
Destination: TransferDestination{
|
||||
ExternalAddress: address,
|
||||
ExternalAddressOriginal: address,
|
||||
},
|
||||
}
|
||||
|
||||
transfer.Normalize()
|
||||
|
||||
if transfer.Destination.ExternalAddress != address {
|
||||
t.Fatalf("expected external address to preserve case, got %q", transfer.Destination.ExternalAddress)
|
||||
}
|
||||
if transfer.Destination.ExternalAddressOriginal != address {
|
||||
t.Fatalf("expected external address original to preserve case, got %q", transfer.Destination.ExternalAddressOriginal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransferNormalizeLowercasesHexExternalAddress(t *testing.T) {
|
||||
address := "0xAABBCCDDEEFF00112233445566778899AABBCCDD"
|
||||
transfer := &Transfer{
|
||||
Destination: TransferDestination{
|
||||
ExternalAddress: address,
|
||||
ExternalAddressOriginal: address,
|
||||
},
|
||||
}
|
||||
|
||||
transfer.Normalize()
|
||||
|
||||
if transfer.Destination.ExternalAddress != strings.ToLower(address) {
|
||||
t.Fatalf("expected hex external address to be lowercased, got %q", transfer.Destination.ExternalAddress)
|
||||
}
|
||||
if transfer.Destination.ExternalAddressOriginal != address {
|
||||
t.Fatalf("expected external address original to preserve case, got %q", transfer.Destination.ExternalAddressOriginal)
|
||||
}
|
||||
}
|
||||
134
api/gateway/tron/storage/model/wallet.go
Normal file
134
api/gateway/tron/storage/model/wallet.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/storable"
|
||||
pkgmodel "github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
)
|
||||
|
||||
type ManagedWalletStatus string
|
||||
|
||||
const (
|
||||
ManagedWalletStatusActive ManagedWalletStatus = "active"
|
||||
ManagedWalletStatusSuspended ManagedWalletStatus = "suspended"
|
||||
ManagedWalletStatusClosed ManagedWalletStatus = "closed"
|
||||
)
|
||||
|
||||
// ManagedWallet represents a user-controlled on-chain wallet managed by the service.
|
||||
type ManagedWallet struct {
|
||||
storable.Base `bson:",inline" json:",inline"`
|
||||
pkgmodel.Describable `bson:",inline" json:",inline"`
|
||||
|
||||
IdempotencyKey string `bson:"idempotencyKey" json:"idempotencyKey"`
|
||||
WalletRef string `bson:"walletRef" json:"walletRef"`
|
||||
OrganizationRef string `bson:"organizationRef" json:"organizationRef"`
|
||||
OwnerRef string `bson:"ownerRef" json:"ownerRef"`
|
||||
Network string `bson:"network" json:"network"`
|
||||
TokenSymbol string `bson:"tokenSymbol" json:"tokenSymbol"`
|
||||
ContractAddress string `bson:"contractAddress" json:"contractAddress"`
|
||||
DepositAddress string `bson:"depositAddress" json:"depositAddress"`
|
||||
KeyReference string `bson:"keyReference,omitempty" json:"keyReference,omitempty"`
|
||||
Status ManagedWalletStatus `bson:"status" json:"status"`
|
||||
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// Collection implements storable.Storable.
|
||||
func (*ManagedWallet) Collection() string {
|
||||
return mservice.ChainWallets
|
||||
}
|
||||
|
||||
// WalletBalance captures computed wallet balances.
|
||||
type WalletBalance struct {
|
||||
storable.Base `bson:",inline" json:",inline"`
|
||||
|
||||
WalletRef string `bson:"walletRef" json:"walletRef"`
|
||||
Available *moneyv1.Money `bson:"available" json:"available"`
|
||||
NativeAvailable *moneyv1.Money `bson:"nativeAvailable,omitempty" json:"nativeAvailable,omitempty"`
|
||||
PendingInbound *moneyv1.Money `bson:"pendingInbound,omitempty" json:"pendingInbound,omitempty"`
|
||||
PendingOutbound *moneyv1.Money `bson:"pendingOutbound,omitempty" json:"pendingOutbound,omitempty"`
|
||||
CalculatedAt time.Time `bson:"calculatedAt" json:"calculatedAt"`
|
||||
}
|
||||
|
||||
// Collection implements storable.Storable.
|
||||
func (*WalletBalance) Collection() string {
|
||||
return mservice.ChainWalletBalances
|
||||
}
|
||||
|
||||
// ManagedWalletFilter describes list filters.
|
||||
type ManagedWalletFilter struct {
|
||||
OrganizationRef string
|
||||
// OwnerRefFilter is a 3-state filter:
|
||||
// - nil: no filter on owner_ref (return all)
|
||||
// - pointer to empty string: filter for wallets where owner_ref is empty
|
||||
// - pointer to a value: filter for wallets where owner_ref matches
|
||||
OwnerRefFilter *string
|
||||
Network string
|
||||
TokenSymbol string
|
||||
Cursor string
|
||||
Limit int32
|
||||
}
|
||||
|
||||
// ManagedWalletList contains paginated wallet results.
|
||||
type ManagedWalletList struct {
|
||||
Items []ManagedWallet
|
||||
NextCursor string
|
||||
}
|
||||
|
||||
// Normalize trims string fields for consistent indexing.
|
||||
func (m *ManagedWallet) Normalize() {
|
||||
m.IdempotencyKey = strings.TrimSpace(m.IdempotencyKey)
|
||||
m.WalletRef = strings.TrimSpace(m.WalletRef)
|
||||
m.OrganizationRef = strings.TrimSpace(m.OrganizationRef)
|
||||
m.OwnerRef = strings.TrimSpace(m.OwnerRef)
|
||||
m.Name = strings.TrimSpace(m.Name)
|
||||
if m.Description != nil {
|
||||
desc := strings.TrimSpace(*m.Description)
|
||||
if desc == "" {
|
||||
m.Description = nil
|
||||
} else {
|
||||
m.Description = &desc
|
||||
}
|
||||
}
|
||||
m.Network = strings.TrimSpace(strings.ToLower(m.Network))
|
||||
m.TokenSymbol = strings.TrimSpace(strings.ToUpper(m.TokenSymbol))
|
||||
m.ContractAddress = strings.TrimSpace(strings.ToLower(m.ContractAddress))
|
||||
m.DepositAddress = normalizeWalletAddress(m.DepositAddress)
|
||||
m.KeyReference = strings.TrimSpace(m.KeyReference)
|
||||
}
|
||||
|
||||
// Normalize trims wallet balance identifiers.
|
||||
func (b *WalletBalance) Normalize() {
|
||||
b.WalletRef = strings.TrimSpace(b.WalletRef)
|
||||
}
|
||||
|
||||
func normalizeWalletAddress(address string) string {
|
||||
trimmed := strings.TrimSpace(address)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
if isHexAddress(trimmed) {
|
||||
return strings.ToLower(trimmed)
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func isHexAddress(value string) bool {
|
||||
trimmed := strings.TrimPrefix(strings.TrimSpace(value), "0x")
|
||||
if len(trimmed) != 40 && len(trimmed) != 42 {
|
||||
return false
|
||||
}
|
||||
for _, r := range trimmed {
|
||||
switch {
|
||||
case r >= '0' && r <= '9':
|
||||
case r >= 'a' && r <= 'f':
|
||||
case r >= 'A' && r <= 'F':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user