Merge pull request 'tron driver removed' (#357) from chain-356 into main
Reviewed-on: #357
This commit was merged in pull request #357.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/fx/storage
|
module github.com/tech/sendico/fx/storage
|
||||||
|
|
||||||
go 1.25.3
|
go 1.25.6
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,46 @@
|
|||||||
# Config file for Air in TOML format
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
root = "./../.."
|
|
||||||
tmp_dir = "tmp"
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
cmd = "go build -o app -ldflags \"-X 'github.com/tech/sendico/gateway/chain/internal/appversion.BuildUser=$(whoami)' -X 'github.com/tech/sendico/gateway/chain/internal/appversion.Version=$APP_V' -X 'github.com/tech/sendico/gateway/chain/internal/appversion.Branch=$BUILD_BRANCH' -X 'github.com/tech/sendico/gateway/chain/internal/appversion.Revision=$GIT_REV' -X 'github.com/tech/sendico/gateway/chain/internal/appversion.BuildDate=$(date)'\""
|
args_bin = []
|
||||||
bin = "./app"
|
entrypoint = "./tmp/main"
|
||||||
full_bin = "./app --debug --config.file=config.yml"
|
cmd = "go build -o ./tmp/main ."
|
||||||
include_ext = ["go", "yaml", "yml"]
|
delay = 1000
|
||||||
exclude_dir = ["gateway/chain/tmp", "pkg/.git", "gateway/chain/env"]
|
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||||
exclude_regex = ["_test\\.go"]
|
exclude_file = []
|
||||||
exclude_unchanged = true
|
exclude_regex = ["_test.go", "_templ.go"]
|
||||||
follow_symlink = true
|
exclude_unchanged = false
|
||||||
log = "air.log"
|
follow_symlink = false
|
||||||
delay = 0
|
full_bin = ""
|
||||||
stop_on_error = true
|
include_dir = []
|
||||||
send_interrupt = true
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
kill_delay = 500
|
include_file = []
|
||||||
args_bin = []
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
[log]
|
poll = false
|
||||||
time = false
|
poll_interval = 0
|
||||||
|
post_cmd = []
|
||||||
|
pre_cmd = []
|
||||||
|
rerun = false
|
||||||
|
rerun_delay = 500
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = false
|
||||||
|
|
||||||
[color]
|
[color]
|
||||||
main = "magenta"
|
app = ""
|
||||||
watcher = "cyan"
|
build = "yellow"
|
||||||
build = "yellow"
|
main = "magenta"
|
||||||
runner = "green"
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
main_only = false
|
||||||
|
time = false
|
||||||
|
|
||||||
[misc]
|
[misc]
|
||||||
clean_on_exit = true
|
clean_on_exit = false
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
|
keep_scroll = true
|
||||||
|
|||||||
1
api/gateway/chain/.gitignore
vendored
1
api/gateway/chain/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
internal/generated
|
internal/generated
|
||||||
.gocache
|
.gocache
|
||||||
app
|
app
|
||||||
|
tmp
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
chainasset "github.com/tech/sendico/pkg/chain"
|
chainasset "github.com/tech/sendico/pkg/chain"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
pmodel "github.com/tech/sendico/pkg/model"
|
||||||
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
|
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
||||||
@@ -18,6 +19,7 @@ import (
|
|||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
)
|
)
|
||||||
|
|
||||||
const chainConnectorID = "chain"
|
const chainConnectorID = "chain"
|
||||||
@@ -149,17 +151,17 @@ func (c *chainGatewayClient) ListManagedWallets(ctx context.Context, req *chainv
|
|||||||
ctx, cancel := c.callContext(ctx)
|
ctx, cancel := c.callContext(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
assetString := ""
|
assetString := ""
|
||||||
ownerRef := ""
|
|
||||||
orgRef := ""
|
orgRef := ""
|
||||||
var page *paginationv1.CursorPageRequest
|
var page *paginationv1.CursorPageRequest
|
||||||
|
var ownerRefFilter *wrapperspb.StringValue
|
||||||
if req != nil {
|
if req != nil {
|
||||||
assetString = chainasset.AssetString(req.GetAsset())
|
assetString = chainasset.AssetString(req.GetAsset())
|
||||||
ownerRef = strings.TrimSpace(req.GetOwnerRef())
|
|
||||||
orgRef = strings.TrimSpace(req.GetOrganizationRef())
|
orgRef = strings.TrimSpace(req.GetOrganizationRef())
|
||||||
|
ownerRefFilter = req.GetOwnerRefFilter()
|
||||||
page = req.GetPage()
|
page = req.GetPage()
|
||||||
}
|
}
|
||||||
resp, err := c.client.ListAccounts(ctx, &connectorv1.ListAccountsRequest{
|
resp, err := c.client.ListAccounts(ctx, &connectorv1.ListAccountsRequest{
|
||||||
OwnerRef: ownerRef,
|
OwnerRefFilter: ownerRefFilter,
|
||||||
OrganizationRef: orgRef,
|
OrganizationRef: orgRef,
|
||||||
Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET,
|
Kind: connectorv1.AccountKind_CHAIN_MANAGED_WALLET,
|
||||||
Asset: assetString,
|
Asset: assetString,
|
||||||
@@ -448,6 +450,7 @@ func operationFromTransfer(req *chainv1.SubmitTransferRequest) (*connectorv1.Ope
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
op.To = to
|
op.To = to
|
||||||
|
setOperationRolesFromMetadata(op, req.GetMetadata())
|
||||||
return op, nil
|
return op, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,6 +468,22 @@ func destinationToParty(dest *chainv1.TransferDestination) (*connectorv1.Operati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setOperationRolesFromMetadata(op *connectorv1.Operation, metadata map[string]string) {
|
||||||
|
if op == nil || len(metadata) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if raw := strings.TrimSpace(metadata[pmodel.MetadataKeyFromRole]); raw != "" {
|
||||||
|
if role, ok := pmodel.Parse(raw); ok && role != "" {
|
||||||
|
op.FromRole = pmodel.ToProto(role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw := strings.TrimSpace(metadata[pmodel.MetadataKeyToRole]); raw != "" {
|
||||||
|
if role, ok := pmodel.Parse(raw); ok && role != "" {
|
||||||
|
op.ToRole = pmodel.ToProto(role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func transferFromReceipt(req *chainv1.SubmitTransferRequest, receipt *connectorv1.OperationReceipt) *chainv1.Transfer {
|
func transferFromReceipt(req *chainv1.SubmitTransferRequest, receipt *connectorv1.OperationReceipt) *chainv1.Transfer {
|
||||||
transfer := &chainv1.Transfer{}
|
transfer := &chainv1.Transfer{}
|
||||||
if req != nil {
|
if req != nil {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
||||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stubConnectorClient struct {
|
type stubConnectorClient struct {
|
||||||
@@ -53,7 +54,7 @@ func TestListManagedWallets_ForwardsOrganizationRef(t *testing.T) {
|
|||||||
|
|
||||||
_, err := client.ListManagedWallets(context.Background(), &chainv1.ListManagedWalletsRequest{
|
_, err := client.ListManagedWallets(context.Background(), &chainv1.ListManagedWalletsRequest{
|
||||||
OrganizationRef: "org-1",
|
OrganizationRef: "org-1",
|
||||||
OwnerRef: "owner-1",
|
OwnerRefFilter: wrapperspb.String("owner-1"),
|
||||||
Asset: &chainv1.Asset{
|
Asset: &chainv1.Asset{
|
||||||
Chain: chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
|
Chain: chainv1.ChainNetwork_CHAIN_NETWORK_ETHEREUM_MAINNET,
|
||||||
TokenSymbol: "USDC",
|
TokenSymbol: "USDC",
|
||||||
@@ -62,6 +63,6 @@ func TestListManagedWallets_ForwardsOrganizationRef(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, stub.listReq)
|
require.NotNil(t, stub.listReq)
|
||||||
require.Equal(t, "org-1", stub.listReq.GetOrganizationRef())
|
require.Equal(t, "org-1", stub.listReq.GetOrganizationRef())
|
||||||
require.Equal(t, "owner-1", stub.listReq.GetOwnerRef())
|
require.Equal(t, "owner-1", stub.listReq.GetOwnerRefFilter().GetValue())
|
||||||
require.Equal(t, connectorv1.AccountKind_CHAIN_MANAGED_WALLET, stub.listReq.GetKind())
|
require.Equal(t, connectorv1.AccountKind_CHAIN_MANAGED_WALLET, stub.listReq.GetKind())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
pmodel "github.com/tech/sendico/pkg/model"
|
||||||
"github.com/tech/sendico/pkg/payments/rail"
|
"github.com/tech/sendico/pkg/payments/rail"
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||||
@@ -106,7 +107,7 @@ func (g *chainRailGateway) Send(ctx context.Context, req rail.TransferRequest) (
|
|||||||
Amount: amountValue,
|
Amount: amountValue,
|
||||||
},
|
},
|
||||||
Fees: fees,
|
Fees: fees,
|
||||||
Metadata: cloneMetadata(req.Metadata),
|
Metadata: transferMetadataWithRoles(req.Metadata, req.FromRole, req.ToRole),
|
||||||
ClientReference: strings.TrimSpace(req.ClientReference),
|
ClientReference: strings.TrimSpace(req.ClientReference),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -254,6 +255,26 @@ func railMoneyFromProto(m *moneyv1.Money) *rail.Money {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func transferMetadataWithRoles(metadata map[string]string, fromRole, toRole pmodel.AccountRole) map[string]string {
|
||||||
|
result := cloneMetadata(metadata)
|
||||||
|
if strings.TrimSpace(string(fromRole)) != "" {
|
||||||
|
if result == nil {
|
||||||
|
result = map[string]string{}
|
||||||
|
}
|
||||||
|
result[pmodel.MetadataKeyFromRole] = strings.TrimSpace(string(fromRole))
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(toRole)) != "" {
|
||||||
|
if result == nil {
|
||||||
|
result = map[string]string{}
|
||||||
|
}
|
||||||
|
result[pmodel.MetadataKeyToRole] = strings.TrimSpace(string(toRole))
|
||||||
|
}
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func cloneMetadata(input map[string]string) map[string]string {
|
func cloneMetadata(input map[string]string) map[string]string {
|
||||||
if len(input) == 0 {
|
if len(input) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
75
api/gateway/chain/config.dev.yml
Normal file
75
api/gateway/chain/config.dev.yml
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
runtime:
|
||||||
|
shutdown_timeout_seconds: 15
|
||||||
|
|
||||||
|
grpc:
|
||||||
|
network: tcp
|
||||||
|
address: ":50070"
|
||||||
|
advertise_host: "dev-chain-gateway"
|
||||||
|
enable_reflection: true
|
||||||
|
enable_health: true
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
address: ":9406"
|
||||||
|
|
||||||
|
database:
|
||||||
|
driver: mongodb
|
||||||
|
settings:
|
||||||
|
host_env: CHAIN_GATEWAY_MONGO_HOST
|
||||||
|
port_env: CHAIN_GATEWAY_MONGO_PORT
|
||||||
|
database_env: CHAIN_GATEWAY_MONGO_DATABASE
|
||||||
|
user_env: CHAIN_GATEWAY_MONGO_USER
|
||||||
|
password_env: CHAIN_GATEWAY_MONGO_PASSWORD
|
||||||
|
auth_source_env: CHAIN_GATEWAY_MONGO_AUTH_SOURCE
|
||||||
|
replica_set_env: CHAIN_GATEWAY_MONGO_REPLICA_SET
|
||||||
|
|
||||||
|
messaging:
|
||||||
|
driver: NATS
|
||||||
|
settings:
|
||||||
|
url_env: NATS_URL
|
||||||
|
host_env: NATS_HOST
|
||||||
|
port_env: NATS_PORT
|
||||||
|
username_env: NATS_USER
|
||||||
|
password_env: NATS_PASSWORD
|
||||||
|
broker_name: Chain Gateway Service
|
||||||
|
max_reconnects: 10
|
||||||
|
reconnect_wait: 5
|
||||||
|
buffer_size: 1024
|
||||||
|
|
||||||
|
chains:
|
||||||
|
- name: arbitrum_sepolia
|
||||||
|
chain_id: 421614
|
||||||
|
native_token: ETH
|
||||||
|
rpc_url_env: CHAIN_GATEWAY_RPC_URL
|
||||||
|
|
||||||
|
gas_topup_policy:
|
||||||
|
buffer_percent: 0.20
|
||||||
|
min_native_balance: 0.002
|
||||||
|
rounding_unit: 0.001
|
||||||
|
max_topup: 0.02
|
||||||
|
|
||||||
|
tokens:
|
||||||
|
# Test USDT (official test deployment)
|
||||||
|
- symbol: USDT
|
||||||
|
contract: "0x5c6b6d1f2f2f6d7b8a9c3e4f5a6b7c8d9e0f1234"
|
||||||
|
|
||||||
|
# Test USDC (Circle test deployment)
|
||||||
|
- symbol: USDC
|
||||||
|
contract: "0x75faf114eafb1bdbe2f0316df893fd58ce46aa4d"
|
||||||
|
|
||||||
|
service_wallet:
|
||||||
|
chain: arbitrum_sepolia
|
||||||
|
address_env: CHAIN_GATEWAY_SERVICE_WALLET_ADDRESS
|
||||||
|
private_key_env: CHAIN_GATEWAY_SERVICE_WALLET_KEY
|
||||||
|
|
||||||
|
key_management:
|
||||||
|
driver: vault
|
||||||
|
settings:
|
||||||
|
address: "http://dev-vault:8200"
|
||||||
|
token_env: VAULT_TOKEN
|
||||||
|
namespace: ""
|
||||||
|
mount_path: kv
|
||||||
|
key_prefix: gateway/chain/wallets
|
||||||
|
|
||||||
|
cache:
|
||||||
|
wallet_balance_ttl_seconds: 120
|
||||||
|
rpc_request_timeout_seconds: 15
|
||||||
@@ -36,23 +36,25 @@ messaging:
|
|||||||
buffer_size: 1024
|
buffer_size: 1024
|
||||||
|
|
||||||
chains:
|
chains:
|
||||||
- name: tron_mainnet
|
- name: arbitrum_one
|
||||||
chain_id: 728126428 # 0x2b6653dc
|
chain_id: 42161
|
||||||
native_token: TRX
|
native_token: ETH
|
||||||
rpc_url_env: CHAIN_GATEWAY_RPC_URL
|
rpc_url_env: CHAIN_GATEWAY_RPC_URL
|
||||||
|
|
||||||
gas_topup_policy:
|
gas_topup_policy:
|
||||||
buffer_percent: 0.10
|
buffer_percent: 0.15
|
||||||
min_native_balance_trx: 10
|
min_native_balance: 0.005
|
||||||
rounding_unit_trx: 1
|
rounding_unit: 0.001
|
||||||
max_topup_trx: 100
|
max_topup: 0.05
|
||||||
|
|
||||||
tokens:
|
tokens:
|
||||||
- symbol: USDT
|
- symbol: USDT
|
||||||
contract: "0xa614f803b6fd780986a42c78ec9c7f77e6ded13c"
|
contract: "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9"
|
||||||
- symbol: USDC
|
- symbol: USDC
|
||||||
contract: "0x3487b63d30b5b2c87fb7ffa8bcfade38eaac1abe"
|
contract: "0xaf88d065e77c8cc2239327c5edb3a432268e5831"
|
||||||
|
|
||||||
service_wallet:
|
service_wallet:
|
||||||
chain: tron_mainnet
|
chain: arbitrum_one
|
||||||
address_env: CHAIN_GATEWAY_SERVICE_WALLET_ADDRESS
|
address_env: CHAIN_GATEWAY_SERVICE_WALLET_ADDRESS
|
||||||
private_key_env: CHAIN_GATEWAY_SERVICE_WALLET_KEY
|
private_key_env: CHAIN_GATEWAY_SERVICE_WALLET_KEY
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/tech/sendico/gateway/chain
|
module github.com/tech/sendico/gateway/chain
|
||||||
|
|
||||||
go 1.25.3
|
go 1.25.6
|
||||||
|
|
||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260124092617-829590d2c921 // indirect
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260128015922-c6a88330dfcd // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||||
@@ -86,5 +86,5 @@ require (
|
|||||||
golang.org/x/sys v0.40.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
|||||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260124092617-829590d2c921 h1:NxRnjiL8BBFLCnsDv18a20vb1d34TUiiZtdJGqpj3xs=
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260128015922-c6a88330dfcd h1:Q1TFWVLXoK7DoPIIBE7K0lDScjlxcRI0IUxjrm3yZ8A=
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260124092617-829590d2c921/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260128015922-c6a88330dfcd/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
||||||
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
||||||
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@@ -362,8 +362,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d h1:xXzuihhT3gL/ntduUZwHECzAn57E8dA6l8SOtYWdD8Q=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260122232226-8e98ce8d340d/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -77,9 +77,9 @@ type gasTopUpPolicyConfig struct {
|
|||||||
|
|
||||||
type gasTopUpRuleConfig struct {
|
type gasTopUpRuleConfig struct {
|
||||||
BufferPercent float64 `yaml:"buffer_percent"`
|
BufferPercent float64 `yaml:"buffer_percent"`
|
||||||
MinNativeBalanceTRX float64 `yaml:"min_native_balance_trx"`
|
MinNativeBalanceTRX float64 `yaml:"min_native_balance"`
|
||||||
RoundingUnitTRX float64 `yaml:"rounding_unit_trx"`
|
RoundingUnitTRX float64 `yaml:"rounding_unit"`
|
||||||
MaxTopUpTRX float64 `yaml:"max_topup_trx"`
|
MaxTopUpTRX float64 `yaml:"max_topup"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create initialises the chain gateway server implementation.
|
// Create initialises the chain gateway server implementation.
|
||||||
@@ -308,13 +308,13 @@ func parseGasTopUpRule(chainName, label string, cfg gasTopUpRuleConfig) (gateway
|
|||||||
return gatewayshared.GasTopUpRule{}, true, merrors.InvalidArgument(fmt.Sprintf("chain %s gas_topup_policy %s buffer_percent must be >= 0", chainName, label))
|
return gatewayshared.GasTopUpRule{}, true, merrors.InvalidArgument(fmt.Sprintf("chain %s gas_topup_policy %s buffer_percent must be >= 0", chainName, label))
|
||||||
}
|
}
|
||||||
if cfg.MinNativeBalanceTRX < 0 {
|
if cfg.MinNativeBalanceTRX < 0 {
|
||||||
return gatewayshared.GasTopUpRule{}, true, merrors.InvalidArgument(fmt.Sprintf("chain %s gas_topup_policy %s min_native_balance_trx must be >= 0", chainName, label))
|
return gatewayshared.GasTopUpRule{}, true, merrors.InvalidArgument(fmt.Sprintf("chain %s gas_topup_policy %s min_native_balance must be >= 0", chainName, label))
|
||||||
}
|
}
|
||||||
if cfg.RoundingUnitTRX <= 0 {
|
if cfg.RoundingUnitTRX <= 0 {
|
||||||
return gatewayshared.GasTopUpRule{}, true, merrors.InvalidArgument(fmt.Sprintf("chain %s gas_topup_policy %s rounding_unit_trx must be > 0", chainName, label))
|
return gatewayshared.GasTopUpRule{}, true, merrors.InvalidArgument(fmt.Sprintf("chain %s gas_topup_policy %s rounding_unit must be > 0", chainName, label))
|
||||||
}
|
}
|
||||||
if cfg.MaxTopUpTRX <= 0 {
|
if cfg.MaxTopUpTRX <= 0 {
|
||||||
return gatewayshared.GasTopUpRule{}, true, merrors.InvalidArgument(fmt.Sprintf("chain %s gas_topup_policy %s max_topup_trx must be > 0", chainName, label))
|
return gatewayshared.GasTopUpRule{}, true, merrors.InvalidArgument(fmt.Sprintf("chain %s gas_topup_policy %s max_topup must be > 0", chainName, label))
|
||||||
}
|
}
|
||||||
return gatewayshared.GasTopUpRule{
|
return gatewayshared.GasTopUpRule{
|
||||||
BufferPercent: decimal.NewFromFloat(cfg.BufferPercent),
|
BufferPercent: decimal.NewFromFloat(cfg.BufferPercent),
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/shopspring/decimal"
|
"github.com/shopspring/decimal"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/commands/wallet"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/commands/wallet"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/evm"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/evm"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/tron"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
"github.com/tech/sendico/gateway/chain/storage/model"
|
||||||
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
||||||
@@ -48,12 +47,12 @@ func (c *computeGasTopUpCommand) Execute(ctx context.Context, req *chainv1.Compu
|
|||||||
return gsresponse.InvalidArgument[chainv1.ComputeGasTopUpResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("estimated_total_fee is required"))
|
return gsresponse.InvalidArgument[chainv1.ComputeGasTopUpResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("estimated_total_fee is required"))
|
||||||
}
|
}
|
||||||
|
|
||||||
topUp, capHit, decision, nativeBalance, walletModel, err := computeGasTopUp(ctx, c.deps, walletRef, estimatedFee)
|
topUp, capHit, nativeBalance, walletModel, err := computeGasTopUp(ctx, c.deps, walletRef, estimatedFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gsresponse.Auto[chainv1.ComputeGasTopUpResponse](c.deps.Logger, mservice.ChainGateway, err)
|
return gsresponse.Auto[chainv1.ComputeGasTopUpResponse](c.deps.Logger, mservice.ChainGateway, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logDecision(c.deps.Logger, walletRef, estimatedFee, nativeBalance, topUp, capHit, decision, walletModel)
|
logDecision(c.deps.Logger, walletRef, estimatedFee, nativeBalance, topUp, capHit, walletModel)
|
||||||
|
|
||||||
return gsresponse.Success(&chainv1.ComputeGasTopUpResponse{
|
return gsresponse.Success(&chainv1.ComputeGasTopUpResponse{
|
||||||
TopupAmount: topUp,
|
TopupAmount: topUp,
|
||||||
@@ -105,12 +104,12 @@ func (c *ensureGasTopUpCommand) Execute(ctx context.Context, req *chainv1.Ensure
|
|||||||
return gsresponse.InvalidArgument[chainv1.EnsureGasTopUpResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("estimated_total_fee is required"))
|
return gsresponse.InvalidArgument[chainv1.EnsureGasTopUpResponse](c.deps.Logger, mservice.ChainGateway, merrors.InvalidArgument("estimated_total_fee is required"))
|
||||||
}
|
}
|
||||||
|
|
||||||
topUp, capHit, decision, nativeBalance, walletModel, err := computeGasTopUp(ctx, c.deps, targetWalletRef, estimatedFee)
|
topUp, capHit, nativeBalance, walletModel, err := computeGasTopUp(ctx, c.deps, targetWalletRef, estimatedFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gsresponse.Auto[chainv1.EnsureGasTopUpResponse](c.deps.Logger, mservice.ChainGateway, err)
|
return gsresponse.Auto[chainv1.EnsureGasTopUpResponse](c.deps.Logger, mservice.ChainGateway, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logDecision(c.deps.Logger, targetWalletRef, estimatedFee, nativeBalance, topUp, capHit, decision, walletModel)
|
logDecision(c.deps.Logger, targetWalletRef, estimatedFee, nativeBalance, topUp, capHit, walletModel)
|
||||||
|
|
||||||
if topUp == nil || strings.TrimSpace(topUp.GetAmount()) == "" {
|
if topUp == nil || strings.TrimSpace(topUp.GetAmount()) == "" {
|
||||||
return gsresponse.Success(&chainv1.EnsureGasTopUpResponse{
|
return gsresponse.Success(&chainv1.EnsureGasTopUpResponse{
|
||||||
@@ -145,46 +144,42 @@ func (c *ensureGasTopUpCommand) Execute(ctx context.Context, req *chainv1.Ensure
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeGasTopUp(ctx context.Context, deps Deps, walletRef string, estimatedFee *moneyv1.Money) (*moneyv1.Money, bool, *tron.GasTopUpDecision, *moneyv1.Money, *model.ManagedWallet, error) {
|
func computeGasTopUp(ctx context.Context, deps Deps, walletRef string, estimatedFee *moneyv1.Money) (*moneyv1.Money, bool, *moneyv1.Money, *model.ManagedWallet, error) {
|
||||||
walletRef = strings.TrimSpace(walletRef)
|
walletRef = strings.TrimSpace(walletRef)
|
||||||
estimatedFee = shared.CloneMoney(estimatedFee)
|
estimatedFee = shared.CloneMoney(estimatedFee)
|
||||||
walletModel, err := deps.Storage.Wallets().Get(ctx, walletRef)
|
walletModel, err := deps.Storage.Wallets().Get(ctx, walletRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, nil, nil, nil, err
|
return nil, false, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
networkKey := strings.ToLower(strings.TrimSpace(walletModel.Network))
|
networkKey := strings.ToLower(strings.TrimSpace(walletModel.Network))
|
||||||
|
if strings.HasPrefix(networkKey, "tron") {
|
||||||
|
return nil, false, nil, nil, merrors.InvalidArgument("tron networks must use the tron gateway")
|
||||||
|
}
|
||||||
|
|
||||||
networkCfg, ok := deps.Networks.Network(networkKey)
|
networkCfg, ok := deps.Networks.Network(networkKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false, nil, nil, nil, merrors.InvalidArgument("unsupported chain for wallet")
|
return nil, false, nil, nil, merrors.InvalidArgument("unsupported chain for wallet")
|
||||||
}
|
}
|
||||||
|
|
||||||
nativeBalance, err := nativeBalanceForWallet(ctx, deps, walletModel)
|
nativeBalance, err := nativeBalanceForWallet(ctx, deps, walletModel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, nil, nil, nil, err
|
return nil, false, nil, nil, err
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(networkKey, "tron") {
|
|
||||||
topUp, decision, err := tron.ComputeGasTopUp(networkCfg, walletModel, estimatedFee, nativeBalance)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, nil, nil, nil, err
|
|
||||||
}
|
|
||||||
return topUp, decision.CapHit, &decision, nativeBalance, walletModel, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if networkCfg.GasTopUpPolicy != nil {
|
if networkCfg.GasTopUpPolicy != nil {
|
||||||
topUp, capHit, err := evm.ComputeGasTopUp(networkCfg, walletModel, estimatedFee, nativeBalance)
|
topUp, capHit, err := evm.ComputeGasTopUp(networkCfg, walletModel, estimatedFee, nativeBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, nil, nil, nil, err
|
return nil, false, nil, nil, err
|
||||||
}
|
}
|
||||||
return topUp, capHit, nil, nativeBalance, walletModel, nil
|
return topUp, capHit, nativeBalance, walletModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
topUp, err := defaultGasTopUp(estimatedFee, nativeBalance)
|
topUp, err := defaultGasTopUp(estimatedFee, nativeBalance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, nil, nil, nil, err
|
return nil, false, nil, nil, err
|
||||||
}
|
}
|
||||||
return topUp, false, nil, nativeBalance, walletModel, nil
|
return topUp, false, nativeBalance, walletModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nativeBalanceForWallet(ctx context.Context, deps Deps, walletModel *model.ManagedWallet) (*moneyv1.Money, error) {
|
func nativeBalanceForWallet(ctx context.Context, deps Deps, walletModel *model.ManagedWallet) (*moneyv1.Money, error) {
|
||||||
@@ -241,7 +236,7 @@ func defaultGasTopUp(estimatedFee *moneyv1.Money, currentBalance *moneyv1.Money)
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func logDecision(logger mlogger.Logger, walletRef string, estimatedFee *moneyv1.Money, nativeBalance *moneyv1.Money, topUp *moneyv1.Money, capHit bool, decision *tron.GasTopUpDecision, walletModel *model.ManagedWallet) {
|
func logDecision(logger mlogger.Logger, walletRef string, estimatedFee *moneyv1.Money, nativeBalance *moneyv1.Money, topUp *moneyv1.Money, capHit bool, walletModel *model.ManagedWallet) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -255,19 +250,6 @@ func logDecision(logger mlogger.Logger, walletRef string, estimatedFee *moneyv1.
|
|||||||
if walletModel != nil {
|
if walletModel != nil {
|
||||||
fields = append(fields, zap.String("network", strings.TrimSpace(walletModel.Network)))
|
fields = append(fields, zap.String("network", strings.TrimSpace(walletModel.Network)))
|
||||||
}
|
}
|
||||||
if decision != nil {
|
|
||||||
fields = append(fields,
|
|
||||||
zap.String("estimated_total_fee_trx", decision.EstimatedFeeTRX.String()),
|
|
||||||
zap.String("current_native_balance_trx", decision.CurrentBalanceTRX.String()),
|
|
||||||
zap.String("required_trx", decision.RequiredTRX.String()),
|
|
||||||
zap.String("buffered_required_trx", decision.BufferedRequiredTRX.String()),
|
|
||||||
zap.String("min_balance_topup_trx", decision.MinBalanceTopUpTRX.String()),
|
|
||||||
zap.String("raw_topup_trx", decision.RawTopUpTRX.String()),
|
|
||||||
zap.String("rounded_topup_trx", decision.RoundedTopUpTRX.String()),
|
|
||||||
zap.String("topup_trx", decision.TopUpTRX.String()),
|
|
||||||
zap.String("operation_type", decision.OperationType),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
logger.Info("Gas top-up decision", fields...)
|
logger.Info("Gas top-up decision", fields...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,10 @@ func (c *getManagedWalletCommand) Execute(ctx context.Context, req *chainv1.GetM
|
|||||||
wallet, err := c.deps.Storage.Wallets().Get(ctx, walletRef)
|
wallet, err := c.deps.Storage.Wallets().Get(ctx, walletRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
c.deps.Logger.Warn("Not found", zap.String("wallet_ref", walletRef))
|
// Missing wallet is normal when checking external addresses; avoid warning noise.
|
||||||
return gsresponse.NotFound[chainv1.GetManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
|
c.deps.Logger.Debug("Not found", zap.String("wallet_ref", walletRef))
|
||||||
|
quietLogger := c.deps.Logger.WithOptions(zap.IncreaseLevel(zap.ErrorLevel))
|
||||||
|
return gsresponse.NotFound[chainv1.GetManagedWalletResponse](quietLogger, mservice.ChainGateway, err)
|
||||||
}
|
}
|
||||||
c.deps.Logger.Warn("Storage get failed", zap.Error(err), zap.String("wallet_ref", walletRef))
|
c.deps.Logger.Warn("Storage get failed", zap.Error(err), zap.String("wallet_ref", walletRef))
|
||||||
return gsresponse.Auto[chainv1.GetManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
|
return gsresponse.Auto[chainv1.GetManagedWalletResponse](c.deps.Logger, mservice.ChainGateway, err)
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ func (c *listManagedWalletsCommand) Execute(ctx context.Context, req *chainv1.Li
|
|||||||
filter := model.ManagedWalletFilter{}
|
filter := model.ManagedWalletFilter{}
|
||||||
if req != nil {
|
if req != nil {
|
||||||
filter.OrganizationRef = strings.TrimSpace(req.GetOrganizationRef())
|
filter.OrganizationRef = strings.TrimSpace(req.GetOrganizationRef())
|
||||||
filter.OwnerRef = strings.TrimSpace(req.GetOwnerRef())
|
if req.GetOwnerRefFilter() != nil {
|
||||||
|
ownerRef := strings.TrimSpace(req.GetOwnerRefFilter().GetValue())
|
||||||
|
filter.OwnerRefFilter = &ownerRef
|
||||||
|
}
|
||||||
if asset := req.GetAsset(); asset != nil {
|
if asset := req.GetAsset(); asset != nil {
|
||||||
filter.Network, _ = shared.ChainKeyFromEnum(asset.GetChain())
|
filter.Network, _ = shared.ChainKeyFromEnum(asset.GetChain())
|
||||||
filter.TokenSymbol = strings.TrimSpace(asset.GetTokenSymbol())
|
filter.TokenSymbol = strings.TrimSpace(asset.GetTokenSymbol())
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (s *Service) ListAccounts(ctx context.Context, req *connectorv1.ListAccount
|
|||||||
}
|
}
|
||||||
resp, err := s.ListManagedWallets(ctx, &chainv1.ListManagedWalletsRequest{
|
resp, err := s.ListManagedWallets(ctx, &chainv1.ListManagedWalletsRequest{
|
||||||
OrganizationRef: strings.TrimSpace(req.GetOrganizationRef()),
|
OrganizationRef: strings.TrimSpace(req.GetOrganizationRef()),
|
||||||
OwnerRef: strings.TrimSpace(req.GetOwnerRef()),
|
OwnerRefFilter: req.GetOwnerRefFilter(),
|
||||||
Asset: asset,
|
Asset: asset,
|
||||||
Page: req.GetPage(),
|
Page: req.GetPage(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ func NativeBalance(ctx context.Context, deps driver.Deps, network shared.Network
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("On-chain native balance fetched",
|
logger.Debug("On-chain native balance fetched",
|
||||||
append(logFields,
|
append(logFields,
|
||||||
zap.String("balance_raw", bal.String()),
|
zap.String("balance_raw", bal.String()),
|
||||||
)...,
|
)...,
|
||||||
@@ -421,8 +421,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
|||||||
nonce, err := client.PendingNonceAt(ctx, sourceAddress)
|
nonce, err := client.PendingNonceAt(ctx, sourceAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to fetch nonce", zap.Error(err),
|
logger.Warn("Failed to fetch nonce", zap.Error(err),
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
zap.String("transfer_ref", transfer.TransferRef), zap.String("wallet_ref", source.WalletRef),
|
||||||
zap.String("wallet_ref", source.WalletRef),
|
|
||||||
)
|
)
|
||||||
return "", executorInternal("failed to fetch nonce", err)
|
return "", executorInternal("failed to fetch nonce", err)
|
||||||
}
|
}
|
||||||
@@ -430,8 +429,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
|||||||
gasPrice, err := client.SuggestGasPrice(ctx)
|
gasPrice, err := client.SuggestGasPrice(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to suggest gas price", zap.Error(err),
|
logger.Warn("Failed to suggest gas price", zap.Error(err),
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
zap.String("transfer_ref", transfer.TransferRef), zap.String("network", network.Name),
|
||||||
zap.String("network", network.Name),
|
|
||||||
)
|
)
|
||||||
return "", executorInternal("failed to suggest gas price", err)
|
return "", executorInternal("failed to suggest gas price", err)
|
||||||
}
|
}
|
||||||
@@ -479,8 +477,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
|||||||
decimals, err := erc20Decimals(ctx, rpcClient, tokenAddress)
|
decimals, err := erc20Decimals(ctx, rpcClient, tokenAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to read token decimals", zap.Error(err),
|
logger.Warn("Failed to read token decimals", zap.Error(err),
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
zap.String("transfer_ref", transfer.TransferRef), zap.String("contract", contract),
|
||||||
zap.String("contract", contract),
|
|
||||||
)
|
)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -488,8 +485,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
|||||||
amountInt, err := toBaseUnits(amount.Amount, decimals)
|
amountInt, err := toBaseUnits(amount.Amount, decimals)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to convert amount to base units", zap.Error(err),
|
logger.Warn("Failed to convert amount to base units", zap.Error(err),
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
zap.String("transfer_ref", transfer.TransferRef), zap.String("amount", amount.Amount),
|
||||||
zap.String("amount", amount.Amount),
|
|
||||||
)
|
)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -522,8 +518,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
|||||||
signedTx, err := deps.KeyManager.SignTransaction(ctx, source.KeyReference, tx, chainID)
|
signedTx, err := deps.KeyManager.SignTransaction(ctx, source.KeyReference, tx, chainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to sign transaction", zap.Error(err),
|
logger.Warn("Failed to sign transaction", zap.Error(err),
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
zap.String("transfer_ref", transfer.TransferRef), zap.String("wallet_ref", source.WalletRef),
|
||||||
zap.String("wallet_ref", source.WalletRef),
|
|
||||||
)
|
)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -536,10 +531,8 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
|||||||
}
|
}
|
||||||
|
|
||||||
txHash := signedTx.Hash().Hex()
|
txHash := signedTx.Hash().Hex()
|
||||||
logger.Info("Transaction submitted",
|
logger.Info("Transaction submitted", zap.String("transfer_ref", transfer.TransferRef),
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
zap.String("tx_hash", txHash), zap.String("network", network.Name),
|
||||||
zap.String("tx_hash", txHash),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return txHash, nil
|
return txHash, nil
|
||||||
@@ -578,31 +571,25 @@ func AwaitConfirmation(ctx context.Context, deps driver.Deps, network shared.Net
|
|||||||
if errors.Is(err, ethereum.NotFound) {
|
if errors.Is(err, ethereum.NotFound) {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
logger.Debug("Transaction not yet mined",
|
logger.Debug("Transaction not yet mined", zap.String("tx_hash", txHash),
|
||||||
zap.String("tx_hash", txHash),
|
|
||||||
zap.String("network", network.Name),
|
zap.String("network", network.Name),
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
logger.Warn("Context cancelled while awaiting confirmation",
|
logger.Warn("Context cancelled while awaiting confirmation", zap.String("tx_hash", txHash),
|
||||||
zap.String("tx_hash", txHash),
|
|
||||||
zap.String("network", network.Name),
|
zap.String("network", network.Name),
|
||||||
)
|
)
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.Warn("Failed to fetch transaction receipt",
|
logger.Warn("Failed to fetch transaction receipt", zap.Error(err),
|
||||||
zap.String("tx_hash", txHash),
|
zap.String("tx_hash", txHash), zap.String("network", network.Name),
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
)
|
||||||
return nil, executorInternal("failed to fetch transaction receipt", err)
|
return nil, executorInternal("failed to fetch transaction receipt", err)
|
||||||
}
|
}
|
||||||
logger.Info("Transaction confirmed",
|
logger.Info("Transaction confirmed", zap.String("tx_hash", txHash),
|
||||||
zap.String("tx_hash", txHash),
|
zap.String("network", network.Name), zap.Uint64("status", receipt.Status),
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
|
zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
|
||||||
zap.Uint64("status", receipt.Status),
|
|
||||||
)
|
)
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,193 +0,0 @@
|
|||||||
package tron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tronHexPrefix = "0x"
|
|
||||||
|
|
||||||
var base58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
|
|
||||||
|
|
||||||
func normalizeAddress(address string) (string, error) {
|
|
||||||
trimmed := strings.TrimSpace(address)
|
|
||||||
if trimmed == "" {
|
|
||||||
return "", merrors.InvalidArgument("address is required")
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(trimmed, tronHexPrefix) || isHexString(trimmed) {
|
|
||||||
return hexToBase58(trimmed)
|
|
||||||
}
|
|
||||||
decoded, err := base58Decode(trimmed)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := validateChecksum(decoded); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return base58Encode(decoded), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rpcAddress(address string) (string, error) {
|
|
||||||
trimmed := strings.TrimSpace(address)
|
|
||||||
if trimmed == "" {
|
|
||||||
return "", merrors.InvalidArgument("address is required")
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(trimmed, tronHexPrefix) || isHexString(trimmed) {
|
|
||||||
return normalizeHexRPC(trimmed)
|
|
||||||
}
|
|
||||||
return base58ToHex(trimmed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hexToBase58(address string) (string, error) {
|
|
||||||
bytesAddr, err := parseHexAddress(address)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
payload := append(bytesAddr, checksum(bytesAddr)...)
|
|
||||||
return base58Encode(payload), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func base58ToHex(address string) (string, error) {
|
|
||||||
decoded, err := base58Decode(address)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := validateChecksum(decoded); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return tronHexPrefix + hex.EncodeToString(decoded[1:21]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseHexAddress(address string) ([]byte, error) {
|
|
||||||
trimmed := strings.TrimPrefix(strings.TrimSpace(address), tronHexPrefix)
|
|
||||||
if trimmed == "" {
|
|
||||||
return nil, merrors.InvalidArgument("address is required")
|
|
||||||
}
|
|
||||||
if len(trimmed)%2 == 1 {
|
|
||||||
trimmed = "0" + trimmed
|
|
||||||
}
|
|
||||||
decoded, err := hex.DecodeString(trimmed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, merrors.InvalidArgument("invalid hex address")
|
|
||||||
}
|
|
||||||
switch len(decoded) {
|
|
||||||
case 20:
|
|
||||||
prefixed := make([]byte, 21)
|
|
||||||
prefixed[0] = 0x41
|
|
||||||
copy(prefixed[1:], decoded)
|
|
||||||
return prefixed, nil
|
|
||||||
case 21:
|
|
||||||
if decoded[0] != 0x41 {
|
|
||||||
return nil, merrors.InvalidArgument("invalid tron address prefix")
|
|
||||||
}
|
|
||||||
return decoded, nil
|
|
||||||
default:
|
|
||||||
return nil, merrors.InvalidArgument(fmt.Sprintf("invalid tron address length %d", len(decoded)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeHexRPC(address string) (string, error) {
|
|
||||||
decoded, err := parseHexAddress(address)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return tronHexPrefix + hex.EncodeToString(decoded[1:21]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateChecksum(decoded []byte) error {
|
|
||||||
if len(decoded) != 25 {
|
|
||||||
return merrors.InvalidArgument("invalid tron address length")
|
|
||||||
}
|
|
||||||
payload := decoded[:21]
|
|
||||||
expected := checksum(payload)
|
|
||||||
if !bytes.Equal(expected, decoded[21:]) {
|
|
||||||
return merrors.InvalidArgument("invalid tron address checksum")
|
|
||||||
}
|
|
||||||
if payload[0] != 0x41 {
|
|
||||||
return merrors.InvalidArgument("invalid tron address prefix")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checksum(payload []byte) []byte {
|
|
||||||
first := sha256.Sum256(payload)
|
|
||||||
second := sha256.Sum256(first[:])
|
|
||||||
return second[:4]
|
|
||||||
}
|
|
||||||
|
|
||||||
func base58Encode(input []byte) string {
|
|
||||||
if len(input) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
x := new(big.Int).SetBytes(input)
|
|
||||||
base := big.NewInt(58)
|
|
||||||
zero := big.NewInt(0)
|
|
||||||
mod := new(big.Int)
|
|
||||||
|
|
||||||
encoded := make([]byte, 0, len(input))
|
|
||||||
for x.Cmp(zero) > 0 {
|
|
||||||
x.DivMod(x, base, mod)
|
|
||||||
encoded = append(encoded, base58Alphabet[mod.Int64()])
|
|
||||||
}
|
|
||||||
for _, b := range input {
|
|
||||||
if b != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
encoded = append(encoded, base58Alphabet[0])
|
|
||||||
}
|
|
||||||
reverse(encoded)
|
|
||||||
return string(encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
func base58Decode(input string) ([]byte, error) {
|
|
||||||
result := big.NewInt(0)
|
|
||||||
base := big.NewInt(58)
|
|
||||||
|
|
||||||
for i := 0; i < len(input); i++ {
|
|
||||||
idx := bytes.IndexByte(base58Alphabet, input[i])
|
|
||||||
if idx < 0 {
|
|
||||||
return nil, merrors.InvalidArgument("invalid base58 address")
|
|
||||||
}
|
|
||||||
result.Mul(result, base)
|
|
||||||
result.Add(result, big.NewInt(int64(idx)))
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded := result.Bytes()
|
|
||||||
zeroCount := 0
|
|
||||||
for zeroCount < len(input) && input[zeroCount] == base58Alphabet[0] {
|
|
||||||
zeroCount++
|
|
||||||
}
|
|
||||||
if zeroCount > 0 {
|
|
||||||
decoded = append(make([]byte, zeroCount), decoded...)
|
|
||||||
}
|
|
||||||
return decoded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func reverse(data []byte) {
|
|
||||||
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isHexString(value string) bool {
|
|
||||||
trimmed := strings.TrimPrefix(strings.TrimSpace(value), tronHexPrefix)
|
|
||||||
if trimmed == "" {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
package tron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/evm"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
|
||||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Driver implements Tron-specific behavior, including address conversion.
|
|
||||||
type Driver struct {
|
|
||||||
logger mlogger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(logger mlogger.Logger) *Driver {
|
|
||||||
return &Driver{logger: logger.Named("tron")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Name() string {
|
|
||||||
return "tron"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) FormatAddress(address string) (string, error) {
|
|
||||||
d.logger.Debug("Format address", zap.String("address", address))
|
|
||||||
normalized, err := normalizeAddress(address)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Format address failed", zap.String("address", address), zap.Error(err))
|
|
||||||
}
|
|
||||||
return normalized, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) NormalizeAddress(address string) (string, error) {
|
|
||||||
d.logger.Debug("Normalize address", zap.String("address", address))
|
|
||||||
normalized, err := normalizeAddress(address)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Normalize address failed", zap.String("address", address), zap.Error(err))
|
|
||||||
}
|
|
||||||
return normalized, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) Balance(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet) (*moneyv1.Money, error) {
|
|
||||||
if wallet == nil {
|
|
||||||
return nil, merrors.InvalidArgument("wallet is required")
|
|
||||||
}
|
|
||||||
d.logger.Debug("Balance request", zap.String("wallet_ref", wallet.WalletRef), zap.String("network", network.Name))
|
|
||||||
rpcAddr, err := rpcAddress(wallet.DepositAddress)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Balance address conversion failed", zap.Error(err),
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("address", wallet.DepositAddress),
|
|
||||||
)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
driverDeps := deps
|
|
||||||
driverDeps.Logger = d.logger
|
|
||||||
result, err := evm.Balance(ctx, driverDeps, network, wallet, rpcAddr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Balance failed", zap.Error(err),
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
)
|
|
||||||
} else if result != nil {
|
|
||||||
d.logger.Debug("Balance result",
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.String("amount", result.Amount),
|
|
||||||
zap.String("currency", result.Currency),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) NativeBalance(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet) (*moneyv1.Money, error) {
|
|
||||||
if wallet == nil {
|
|
||||||
return nil, merrors.InvalidArgument("wallet is required")
|
|
||||||
}
|
|
||||||
d.logger.Debug("Native balance request", zap.String("wallet_ref", wallet.WalletRef), zap.String("network", network.Name))
|
|
||||||
rpcAddr, err := rpcAddress(wallet.DepositAddress)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Native balance address conversion failed", zap.Error(err),
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("address", wallet.DepositAddress),
|
|
||||||
)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
driverDeps := deps
|
|
||||||
driverDeps.Logger = d.logger
|
|
||||||
result, err := evm.NativeBalance(ctx, driverDeps, network, wallet, rpcAddr)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Native balance failed", zap.Error(err),
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
)
|
|
||||||
} else if result != nil {
|
|
||||||
d.logger.Debug("Native balance result",
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.String("amount", result.Amount),
|
|
||||||
zap.String("currency", result.Currency),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) EstimateFee(ctx context.Context, deps driver.Deps, network shared.Network, wallet *model.ManagedWallet, destination string, amount *moneyv1.Money) (*moneyv1.Money, error) {
|
|
||||||
if wallet == nil {
|
|
||||||
return nil, merrors.InvalidArgument("wallet is required")
|
|
||||||
}
|
|
||||||
if amount == nil {
|
|
||||||
return nil, merrors.InvalidArgument("amount is required")
|
|
||||||
}
|
|
||||||
d.logger.Debug("Estimate fee request",
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.String("destination", destination),
|
|
||||||
)
|
|
||||||
rpcFrom, err := rpcAddress(wallet.DepositAddress)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Estimate fee address conversion failed", zap.Error(err),
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("address", wallet.DepositAddress),
|
|
||||||
)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rpcTo, err := rpcAddress(destination)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Estimate fee destination conversion failed", zap.Error(err),
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("destination", destination),
|
|
||||||
)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if rpcFrom == rpcTo {
|
|
||||||
return &moneyv1.Money{
|
|
||||||
Currency: nativeCurrency(network),
|
|
||||||
Amount: "0",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
driverDeps := deps
|
|
||||||
driverDeps.Logger = d.logger
|
|
||||||
result, err := evm.EstimateFee(ctx, driverDeps, network, wallet, rpcFrom, rpcTo, amount)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Estimate fee failed", zap.Error(err),
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.String("from_address", wallet.DepositAddress),
|
|
||||||
zap.String("from_rpc", rpcFrom),
|
|
||||||
zap.String("to_address", destination),
|
|
||||||
zap.String("to_rpc", rpcTo),
|
|
||||||
)
|
|
||||||
} else if result != nil {
|
|
||||||
d.logger.Debug("Estimate fee result",
|
|
||||||
zap.String("wallet_ref", wallet.WalletRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.String("amount", result.Amount),
|
|
||||||
zap.String("currency", result.Currency),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Network, transfer *model.Transfer, source *model.ManagedWallet, destination string) (string, error) {
|
|
||||||
if source == nil {
|
|
||||||
return "", merrors.InvalidArgument("source wallet is required")
|
|
||||||
}
|
|
||||||
d.logger.Debug("Submit transfer request",
|
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.String("destination", destination),
|
|
||||||
)
|
|
||||||
rpcFrom, err := rpcAddress(source.DepositAddress)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Submit transfer address conversion failed", zap.Error(err),
|
|
||||||
zap.String("wallet_ref", source.WalletRef),
|
|
||||||
zap.String("address", source.DepositAddress),
|
|
||||||
)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
rpcTo, err := rpcAddress(destination)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Submit transfer destination conversion failed", zap.Error(err),
|
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
|
||||||
zap.String("destination", destination),
|
|
||||||
)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
driverDeps := deps
|
|
||||||
driverDeps.Logger = d.logger
|
|
||||||
txHash, err := evm.SubmitTransfer(ctx, driverDeps, network, transfer, source, rpcFrom, rpcTo)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Submit transfer failed", zap.Error(err),
|
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
d.logger.Debug("Submit transfer result",
|
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.String("tx_hash", txHash),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return txHash, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Driver) AwaitConfirmation(ctx context.Context, deps driver.Deps, network shared.Network, txHash string) (*types.Receipt, error) {
|
|
||||||
d.logger.Debug("Awaiting confirmation",
|
|
||||||
zap.String("tx_hash", txHash),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
)
|
|
||||||
driverDeps := deps
|
|
||||||
driverDeps.Logger = d.logger
|
|
||||||
receipt, err := evm.AwaitConfirmation(ctx, driverDeps, network, txHash)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Warn("Awaiting of confirmation failed", zap.Error(err),
|
|
||||||
zap.String("tx_hash", txHash),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
)
|
|
||||||
} else if receipt != nil {
|
|
||||||
d.logger.Debug("Await confirmation result",
|
|
||||||
zap.String("tx_hash", txHash),
|
|
||||||
zap.String("network", network.Name),
|
|
||||||
zap.Uint64("block_number", receipt.BlockNumber.Uint64()),
|
|
||||||
zap.Uint64("status", receipt.Status),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return receipt, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func nativeCurrency(network shared.Network) string {
|
|
||||||
currency := strings.ToUpper(strings.TrimSpace(network.NativeToken))
|
|
||||||
if currency == "" {
|
|
||||||
currency = strings.ToUpper(strings.TrimSpace(network.Name))
|
|
||||||
}
|
|
||||||
return currency
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ driver.Driver = (*Driver)(nil)
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package tron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
|
||||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEstimateFeeSelfTransferReturnsZero(t *testing.T) {
|
|
||||||
logger := zap.NewNop()
|
|
||||||
d := New(logger)
|
|
||||||
wallet := &model.ManagedWallet{
|
|
||||||
WalletRef: "wallet_ref",
|
|
||||||
DepositAddress: "TGBDXEg9rxSqGFJDcb889zqTjDwx1bmLRF",
|
|
||||||
}
|
|
||||||
network := shared.Network{
|
|
||||||
Name: "tron_mainnet",
|
|
||||||
NativeToken: "TRX",
|
|
||||||
}
|
|
||||||
amount := &moneyv1.Money{Currency: "TRX", Amount: "1000000"}
|
|
||||||
|
|
||||||
fee, err := d.EstimateFee(context.Background(), driver.Deps{}, network, wallet, wallet.DepositAddress, amount)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, fee)
|
|
||||||
require.Equal(t, "TRX", fee.GetCurrency())
|
|
||||||
require.Equal(t, "0", fee.GetAmount())
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
package tron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
|
||||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var tronBaseUnitFactor = decimal.NewFromInt(1_000_000)
|
|
||||||
|
|
||||||
// GasTopUpDecision captures the applied policy inputs and outputs (in TRX units).
|
|
||||||
type GasTopUpDecision struct {
|
|
||||||
CurrentBalanceTRX decimal.Decimal
|
|
||||||
EstimatedFeeTRX decimal.Decimal
|
|
||||||
RequiredTRX decimal.Decimal
|
|
||||||
BufferedRequiredTRX decimal.Decimal
|
|
||||||
MinBalanceTopUpTRX decimal.Decimal
|
|
||||||
RawTopUpTRX decimal.Decimal
|
|
||||||
RoundedTopUpTRX decimal.Decimal
|
|
||||||
TopUpTRX decimal.Decimal
|
|
||||||
CapHit bool
|
|
||||||
OperationType string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComputeGasTopUp applies the network policy to decide a TRX top-up amount.
|
|
||||||
func ComputeGasTopUp(network shared.Network, wallet *model.ManagedWallet, estimatedFee *moneyv1.Money, currentBalance *moneyv1.Money) (*moneyv1.Money, GasTopUpDecision, error) {
|
|
||||||
decision := GasTopUpDecision{}
|
|
||||||
if wallet == nil {
|
|
||||||
return nil, decision, merrors.InvalidArgument("wallet is required")
|
|
||||||
}
|
|
||||||
if estimatedFee == nil || strings.TrimSpace(estimatedFee.GetAmount()) == "" || strings.TrimSpace(estimatedFee.GetCurrency()) == "" {
|
|
||||||
return nil, decision, merrors.InvalidArgument("estimated fee is required")
|
|
||||||
}
|
|
||||||
if currentBalance == nil || strings.TrimSpace(currentBalance.GetAmount()) == "" || strings.TrimSpace(currentBalance.GetCurrency()) == "" {
|
|
||||||
return nil, decision, merrors.InvalidArgument("current native balance is required")
|
|
||||||
}
|
|
||||||
if network.GasTopUpPolicy == nil {
|
|
||||||
return nil, decision, merrors.InvalidArgument("gas top-up policy is not configured")
|
|
||||||
}
|
|
||||||
|
|
||||||
nativeCurrency := strings.TrimSpace(network.NativeToken)
|
|
||||||
if nativeCurrency == "" {
|
|
||||||
nativeCurrency = strings.ToUpper(strings.TrimSpace(network.Name))
|
|
||||||
}
|
|
||||||
if !strings.EqualFold(nativeCurrency, estimatedFee.GetCurrency()) {
|
|
||||||
return nil, decision, merrors.InvalidArgument(fmt.Sprintf("estimated fee currency mismatch (expected %s)", nativeCurrency))
|
|
||||||
}
|
|
||||||
if !strings.EqualFold(nativeCurrency, currentBalance.GetCurrency()) {
|
|
||||||
return nil, decision, merrors.InvalidArgument(fmt.Sprintf("native balance currency mismatch (expected %s)", nativeCurrency))
|
|
||||||
}
|
|
||||||
|
|
||||||
estimatedTRX, err := tronToTRX(estimatedFee)
|
|
||||||
if err != nil {
|
|
||||||
return nil, decision, err
|
|
||||||
}
|
|
||||||
currentTRX, err := tronToTRX(currentBalance)
|
|
||||||
if err != nil {
|
|
||||||
return nil, decision, err
|
|
||||||
}
|
|
||||||
|
|
||||||
isContract := strings.TrimSpace(wallet.ContractAddress) != ""
|
|
||||||
rule, ok := network.GasTopUpPolicy.Rule(isContract)
|
|
||||||
if !ok {
|
|
||||||
return nil, decision, merrors.InvalidArgument("gas top-up policy is not configured")
|
|
||||||
}
|
|
||||||
if rule.RoundingUnit.LessThanOrEqual(decimal.Zero) {
|
|
||||||
return nil, decision, merrors.InvalidArgument("gas top-up rounding unit must be > 0")
|
|
||||||
}
|
|
||||||
if rule.MaxTopUp.LessThanOrEqual(decimal.Zero) {
|
|
||||||
return nil, decision, merrors.InvalidArgument("gas top-up max top-up must be > 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
required := estimatedTRX.Sub(currentTRX)
|
|
||||||
if required.IsNegative() {
|
|
||||||
required = decimal.Zero
|
|
||||||
}
|
|
||||||
bufferedRequired := required.Mul(decimal.NewFromInt(1).Add(rule.BufferPercent))
|
|
||||||
|
|
||||||
minBalanceTopUp := rule.MinNativeBalance.Sub(currentTRX)
|
|
||||||
if minBalanceTopUp.IsNegative() {
|
|
||||||
minBalanceTopUp = decimal.Zero
|
|
||||||
}
|
|
||||||
|
|
||||||
rawTopUp := bufferedRequired
|
|
||||||
if minBalanceTopUp.GreaterThan(rawTopUp) {
|
|
||||||
rawTopUp = minBalanceTopUp
|
|
||||||
}
|
|
||||||
|
|
||||||
roundedTopUp := decimal.Zero
|
|
||||||
if rawTopUp.IsPositive() {
|
|
||||||
roundedTopUp = rawTopUp.Div(rule.RoundingUnit).Ceil().Mul(rule.RoundingUnit)
|
|
||||||
}
|
|
||||||
|
|
||||||
topUp := roundedTopUp
|
|
||||||
capHit := false
|
|
||||||
if topUp.GreaterThan(rule.MaxTopUp) {
|
|
||||||
topUp = rule.MaxTopUp
|
|
||||||
capHit = true
|
|
||||||
}
|
|
||||||
|
|
||||||
decision = GasTopUpDecision{
|
|
||||||
CurrentBalanceTRX: currentTRX,
|
|
||||||
EstimatedFeeTRX: estimatedTRX,
|
|
||||||
RequiredTRX: required,
|
|
||||||
BufferedRequiredTRX: bufferedRequired,
|
|
||||||
MinBalanceTopUpTRX: minBalanceTopUp,
|
|
||||||
RawTopUpTRX: rawTopUp,
|
|
||||||
RoundedTopUpTRX: roundedTopUp,
|
|
||||||
TopUpTRX: topUp,
|
|
||||||
CapHit: capHit,
|
|
||||||
OperationType: operationType(isContract),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !topUp.IsPositive() {
|
|
||||||
return nil, decision, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
baseUnits := topUp.Mul(tronBaseUnitFactor).Ceil().Truncate(0)
|
|
||||||
return &moneyv1.Money{
|
|
||||||
Currency: strings.ToUpper(nativeCurrency),
|
|
||||||
Amount: baseUnits.StringFixed(0),
|
|
||||||
}, decision, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tronToTRX(amount *moneyv1.Money) (decimal.Decimal, error) {
|
|
||||||
value, err := decimal.NewFromString(strings.TrimSpace(amount.GetAmount()))
|
|
||||||
if err != nil {
|
|
||||||
return decimal.Zero, err
|
|
||||||
}
|
|
||||||
return value.Div(tronBaseUnitFactor), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func operationType(contract bool) string {
|
|
||||||
if contract {
|
|
||||||
return "trc20"
|
|
||||||
}
|
|
||||||
return "native"
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
package tron
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/shopspring/decimal"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
|
||||||
"github.com/tech/sendico/gateway/chain/storage/model"
|
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestComputeGasTopUp_BalanceSufficient(t *testing.T) {
|
|
||||||
network := tronNetwork(defaultPolicy())
|
|
||||||
wallet := &model.ManagedWallet{}
|
|
||||||
|
|
||||||
topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("5"), tronMoney("30"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Nil(t, topUp)
|
|
||||||
require.True(t, decision.TopUpTRX.IsZero())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestComputeGasTopUp_BufferedRequired(t *testing.T) {
|
|
||||||
network := tronNetwork(defaultPolicy())
|
|
||||||
wallet := &model.ManagedWallet{}
|
|
||||||
|
|
||||||
topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("50"), tronMoney("10"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, topUp)
|
|
||||||
require.Equal(t, "46000000", topUp.GetAmount())
|
|
||||||
require.Equal(t, "TRX", topUp.GetCurrency())
|
|
||||||
require.Equal(t, "46", decision.TopUpTRX.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestComputeGasTopUp_MinBalanceBinding(t *testing.T) {
|
|
||||||
network := tronNetwork(defaultPolicy())
|
|
||||||
wallet := &model.ManagedWallet{}
|
|
||||||
|
|
||||||
topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("5"), tronMoney("1"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, topUp)
|
|
||||||
require.Equal(t, "19000000", topUp.GetAmount())
|
|
||||||
require.Equal(t, "19", decision.TopUpTRX.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestComputeGasTopUp_RoundsUp(t *testing.T) {
|
|
||||||
policy := shared.GasTopUpPolicy{
|
|
||||||
Default: shared.GasTopUpRule{
|
|
||||||
BufferPercent: decimal.NewFromFloat(0),
|
|
||||||
MinNativeBalance: decimal.NewFromFloat(0),
|
|
||||||
RoundingUnit: decimal.NewFromFloat(1),
|
|
||||||
MaxTopUp: decimal.NewFromFloat(100),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
network := tronNetwork(&policy)
|
|
||||||
wallet := &model.ManagedWallet{}
|
|
||||||
|
|
||||||
topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("1.1"), tronMoney("0"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, topUp)
|
|
||||||
require.Equal(t, "2000000", topUp.GetAmount())
|
|
||||||
require.Equal(t, "2", decision.TopUpTRX.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestComputeGasTopUp_CapHit(t *testing.T) {
|
|
||||||
policy := shared.GasTopUpPolicy{
|
|
||||||
Default: shared.GasTopUpRule{
|
|
||||||
BufferPercent: decimal.NewFromFloat(0),
|
|
||||||
MinNativeBalance: decimal.NewFromFloat(0),
|
|
||||||
RoundingUnit: decimal.NewFromFloat(1),
|
|
||||||
MaxTopUp: decimal.NewFromFloat(10),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
network := tronNetwork(&policy)
|
|
||||||
wallet := &model.ManagedWallet{}
|
|
||||||
|
|
||||||
topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("100"), tronMoney("0"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, topUp)
|
|
||||||
require.Equal(t, "10000000", topUp.GetAmount())
|
|
||||||
require.True(t, decision.CapHit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestComputeGasTopUp_MinBalanceWhenRequiredZero(t *testing.T) {
|
|
||||||
network := tronNetwork(defaultPolicy())
|
|
||||||
wallet := &model.ManagedWallet{}
|
|
||||||
|
|
||||||
topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("0"), tronMoney("5"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, topUp)
|
|
||||||
require.Equal(t, "15000000", topUp.GetAmount())
|
|
||||||
require.Equal(t, "15", decision.TopUpTRX.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestComputeGasTopUp_ContractPolicyOverride(t *testing.T) {
|
|
||||||
policy := shared.GasTopUpPolicy{
|
|
||||||
Default: shared.GasTopUpRule{
|
|
||||||
BufferPercent: decimal.NewFromFloat(0.1),
|
|
||||||
MinNativeBalance: decimal.NewFromFloat(10),
|
|
||||||
RoundingUnit: decimal.NewFromFloat(1),
|
|
||||||
MaxTopUp: decimal.NewFromFloat(100),
|
|
||||||
},
|
|
||||||
Contract: &shared.GasTopUpRule{
|
|
||||||
BufferPercent: decimal.NewFromFloat(0.5),
|
|
||||||
MinNativeBalance: decimal.NewFromFloat(5),
|
|
||||||
RoundingUnit: decimal.NewFromFloat(1),
|
|
||||||
MaxTopUp: decimal.NewFromFloat(100),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
network := tronNetwork(&policy)
|
|
||||||
wallet := &model.ManagedWallet{ContractAddress: "0xcontract"}
|
|
||||||
|
|
||||||
topUp, decision, err := ComputeGasTopUp(network, wallet, tronMoney("10"), tronMoney("0"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, topUp)
|
|
||||||
require.Equal(t, "15000000", topUp.GetAmount())
|
|
||||||
require.Equal(t, "15", decision.TopUpTRX.String())
|
|
||||||
require.Equal(t, "trc20", decision.OperationType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultPolicy() *shared.GasTopUpPolicy {
|
|
||||||
return &shared.GasTopUpPolicy{
|
|
||||||
Default: shared.GasTopUpRule{
|
|
||||||
BufferPercent: decimal.NewFromFloat(0.15),
|
|
||||||
MinNativeBalance: decimal.NewFromFloat(20),
|
|
||||||
RoundingUnit: decimal.NewFromFloat(1),
|
|
||||||
MaxTopUp: decimal.NewFromFloat(500),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tronNetwork(policy *shared.GasTopUpPolicy) shared.Network {
|
|
||||||
return shared.Network{
|
|
||||||
Name: "tron_mainnet",
|
|
||||||
NativeToken: "TRX",
|
|
||||||
GasTopUpPolicy: policy,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tronMoney(trx string) *moneyv1.Money {
|
|
||||||
value, _ := decimal.NewFromString(trx)
|
|
||||||
baseUnits := value.Mul(tronBaseUnitFactor).Truncate(0)
|
|
||||||
return &moneyv1.Money{
|
|
||||||
Currency: "TRX",
|
|
||||||
Amount: baseUnits.StringFixed(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/arbitrum"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/arbitrum"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/ethereum"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/ethereum"
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/driver/tron"
|
|
||||||
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
"github.com/tech/sendico/gateway/chain/internal/service/gateway/shared"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
@@ -63,7 +62,7 @@ func (r *Registry) Driver(network string) (driver.Driver, error) {
|
|||||||
func resolveDriver(logger mlogger.Logger, network string) (driver.Driver, error) {
|
func resolveDriver(logger mlogger.Logger, network string) (driver.Driver, error) {
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(network, "tron"):
|
case strings.HasPrefix(network, "tron"):
|
||||||
return tron.New(logger), nil
|
return nil, merrors.InvalidArgument("tron networks must use the tron gateway, not chain gateway")
|
||||||
case strings.HasPrefix(network, "arbitrum"):
|
case strings.HasPrefix(network, "arbitrum"):
|
||||||
return arbitrum.New(logger), nil
|
return arbitrum.New(logger), nil
|
||||||
case strings.HasPrefix(network, "ethereum"):
|
case strings.HasPrefix(network, "ethereum"):
|
||||||
|
|||||||
@@ -329,9 +329,12 @@ func (w *inMemoryWallets) List(ctx context.Context, filter model.ManagedWalletFi
|
|||||||
if filter.OrganizationRef != "" && !strings.EqualFold(wallet.OrganizationRef, filter.OrganizationRef) {
|
if filter.OrganizationRef != "" && !strings.EqualFold(wallet.OrganizationRef, filter.OrganizationRef) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if filter.OwnerRef != "" && !strings.EqualFold(wallet.OwnerRef, filter.OwnerRef) {
|
if filter.OwnerRefFilter != nil {
|
||||||
|
ownerRef := strings.TrimSpace(*filter.OwnerRefFilter)
|
||||||
|
if !strings.EqualFold(wallet.OwnerRef, ownerRef) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if filter.Network != "" && !strings.EqualFold(wallet.Network, filter.Network) {
|
if filter.Network != "" && !strings.EqualFold(wallet.Network, filter.Network) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,11 @@ func (*WalletBalance) Collection() string {
|
|||||||
// ManagedWalletFilter describes list filters.
|
// ManagedWalletFilter describes list filters.
|
||||||
type ManagedWalletFilter struct {
|
type ManagedWalletFilter struct {
|
||||||
OrganizationRef string
|
OrganizationRef string
|
||||||
OwnerRef 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
|
Network string
|
||||||
TokenSymbol string
|
TokenSymbol string
|
||||||
Cursor string
|
Cursor string
|
||||||
|
|||||||
@@ -156,9 +156,10 @@ func (w *Wallets) List(ctx context.Context, filter model.ManagedWalletFilter) (*
|
|||||||
query = query.Filter(repository.Field("organizationRef"), org)
|
query = query.Filter(repository.Field("organizationRef"), org)
|
||||||
fields = append(fields, zap.String("organization_ref", org))
|
fields = append(fields, zap.String("organization_ref", org))
|
||||||
}
|
}
|
||||||
if owner := strings.TrimSpace(filter.OwnerRef); owner != "" {
|
if filter.OwnerRefFilter != nil {
|
||||||
query = query.Filter(repository.Field("ownerRef"), owner)
|
ownerRef := strings.TrimSpace(*filter.OwnerRefFilter)
|
||||||
fields = append(fields, zap.String("owner_ref", owner))
|
query = query.Filter(repository.Field("ownerRef"), ownerRef)
|
||||||
|
fields = append(fields, zap.String("owner_ref_filter", ownerRef))
|
||||||
}
|
}
|
||||||
if network := strings.TrimSpace(filter.Network); network != "" {
|
if network := strings.TrimSpace(filter.Network); network != "" {
|
||||||
normalized := strings.ToLower(network)
|
normalized := strings.ToLower(network)
|
||||||
|
|||||||
Reference in New Issue
Block a user