Compare commits
2 Commits
f2c9685eb1
...
d027f2deda
| Author | SHA1 | Date | |
|---|---|---|---|
| d027f2deda | |||
|
|
ba5a3312b5 |
@@ -2,6 +2,9 @@ package orchestrator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
chainclient "github.com/tech/sendico/gateway/chain/client"
|
||||||
"github.com/tech/sendico/pkg/discovery"
|
"github.com/tech/sendico/pkg/discovery"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -48,7 +51,7 @@ func (e *gatewayCryptoExecutor) ExecuteCrypto(ctx context.Context, req sexec.Ste
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
amount, err := sourceAmount(req.Payment)
|
amount, err := sourceAmount(req.Payment, action)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -90,6 +93,12 @@ func (e *gatewayCryptoExecutor) ExecuteCrypto(ctx context.Context, req sexec.Ste
|
|||||||
return nil, refsErr
|
return nil, refsErr
|
||||||
}
|
}
|
||||||
step.ExternalRefs = refs
|
step.ExternalRefs = refs
|
||||||
|
|
||||||
|
if action == discovery.RailOperationSend {
|
||||||
|
if err := e.submitWalletFeeTransfer(ctx, req, client, gateway, sourceWalletRef, operationRef, idempotencyKey); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
step.State = agg.StepStateCompleted
|
step.State = agg.StepStateCompleted
|
||||||
step.FailureCode = ""
|
step.FailureCode = ""
|
||||||
step.FailureMsg = ""
|
step.FailureMsg = ""
|
||||||
@@ -161,11 +170,24 @@ func sourceManagedWalletRef(payment *agg.Payment) (string, error) {
|
|||||||
return ref, nil
|
return ref, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sourceAmount(payment *agg.Payment) (*moneyv1.Money, error) {
|
func sourceAmount(payment *agg.Payment, action model.RailOperation) (*moneyv1.Money, error) {
|
||||||
if payment == nil {
|
if payment == nil {
|
||||||
return nil, merrors.InvalidArgument("crypto send: payment is required")
|
return nil, merrors.InvalidArgument("crypto send: payment is required")
|
||||||
}
|
}
|
||||||
money := effectiveSourceAmount(payment)
|
var money *paymenttypes.Money
|
||||||
|
switch action {
|
||||||
|
case discovery.RailOperationFee:
|
||||||
|
resolved, ok, err := walletFeeAmount(payment)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, merrors.InvalidArgument("crypto send: wallet fee amount is required")
|
||||||
|
}
|
||||||
|
money = resolved
|
||||||
|
default:
|
||||||
|
money = effectiveSourceAmount(payment)
|
||||||
|
}
|
||||||
if money == nil {
|
if money == nil {
|
||||||
return nil, merrors.InvalidArgument("crypto send: source amount is required")
|
return nil, merrors.InvalidArgument("crypto send: source amount is required")
|
||||||
}
|
}
|
||||||
@@ -180,6 +202,64 @@ func sourceAmount(payment *agg.Payment) (*moneyv1.Money, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *gatewayCryptoExecutor) submitWalletFeeTransfer(
|
||||||
|
ctx context.Context,
|
||||||
|
req sexec.StepRequest,
|
||||||
|
client chainclient.Client,
|
||||||
|
gateway *model.GatewayInstanceDescriptor,
|
||||||
|
sourceWalletRef string,
|
||||||
|
operationRef string,
|
||||||
|
idempotencyKey string,
|
||||||
|
) error {
|
||||||
|
if req.Payment == nil {
|
||||||
|
return merrors.InvalidArgument("crypto send: payment is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
feeAmount, ok, err := walletFeeAmount(req.Payment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
destination, err := e.resolveDestination(req.Payment, discovery.RailOperationFee)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
feeMoney := &moneyv1.Money{
|
||||||
|
Amount: strings.TrimSpace(feeAmount.GetAmount()),
|
||||||
|
Currency: strings.TrimSpace(feeAmount.GetCurrency()),
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.SubmitTransfer(ctx, &chainv1.SubmitTransferRequest{
|
||||||
|
IdempotencyKey: strings.TrimSpace(idempotencyKey) + ":fee",
|
||||||
|
OrganizationRef: req.Payment.OrganizationRef.Hex(),
|
||||||
|
SourceWalletRef: sourceWalletRef,
|
||||||
|
Destination: destination,
|
||||||
|
Amount: feeMoney,
|
||||||
|
OperationRef: strings.TrimSpace(operationRef) + ":fee",
|
||||||
|
IntentRef: strings.TrimSpace(req.Payment.IntentSnapshot.Ref),
|
||||||
|
PaymentRef: strings.TrimSpace(req.Payment.PaymentRef),
|
||||||
|
Metadata: transferMetadata(req.Step),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp == nil || resp.GetTransfer() == nil {
|
||||||
|
return merrors.Internal("crypto send: fee transfer response is missing")
|
||||||
|
}
|
||||||
|
if _, err := transferExternalRefs(resp.GetTransfer(), firstNonEmpty(
|
||||||
|
strings.TrimSpace(req.Step.InstanceID),
|
||||||
|
strings.TrimSpace(gateway.InstanceID),
|
||||||
|
strings.TrimSpace(req.Step.Gateway),
|
||||||
|
strings.TrimSpace(gateway.ID),
|
||||||
|
)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func effectiveSourceAmount(payment *agg.Payment) *paymenttypes.Money {
|
func effectiveSourceAmount(payment *agg.Payment) *paymenttypes.Money {
|
||||||
if payment == nil {
|
if payment == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -190,6 +270,77 @@ func effectiveSourceAmount(payment *agg.Payment) *paymenttypes.Money {
|
|||||||
return payment.IntentSnapshot.Amount
|
return payment.IntentSnapshot.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func walletFeeAmount(payment *agg.Payment) (*paymenttypes.Money, bool, error) {
|
||||||
|
if payment == nil || payment.QuoteSnapshot == nil || len(payment.QuoteSnapshot.FeeLines) == 0 {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCurrency := ""
|
||||||
|
if source := effectiveSourceAmount(payment); source != nil {
|
||||||
|
sourceCurrency = strings.TrimSpace(source.Currency)
|
||||||
|
}
|
||||||
|
|
||||||
|
total := decimal.Zero
|
||||||
|
currency := ""
|
||||||
|
for i, line := range payment.QuoteSnapshot.FeeLines {
|
||||||
|
if !isWalletDebitFeeLine(line) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
money := line.GetMoney()
|
||||||
|
if money == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lineCurrency := strings.TrimSpace(money.GetCurrency())
|
||||||
|
if lineCurrency == "" {
|
||||||
|
return nil, false, merrors.InvalidArgument(fmt.Sprintf("crypto send: fee_lines[%d].money.currency is required", i))
|
||||||
|
}
|
||||||
|
if sourceCurrency != "" && !strings.EqualFold(sourceCurrency, lineCurrency) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if currency == "" {
|
||||||
|
currency = lineCurrency
|
||||||
|
} else if !strings.EqualFold(currency, lineCurrency) {
|
||||||
|
return nil, false, merrors.InvalidArgument("crypto send: wallet fee currency mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
amountRaw := strings.TrimSpace(money.GetAmount())
|
||||||
|
amount, err := decimal.NewFromString(amountRaw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, merrors.InvalidArgument(fmt.Sprintf("crypto send: fee_lines[%d].money.amount is invalid", i))
|
||||||
|
}
|
||||||
|
if amount.Sign() < 0 {
|
||||||
|
amount = amount.Neg()
|
||||||
|
}
|
||||||
|
if amount.Sign() == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
total = total.Add(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if total.Sign() <= 0 {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
return &paymenttypes.Money{
|
||||||
|
Amount: total.String(),
|
||||||
|
Currency: strings.ToUpper(strings.TrimSpace(currency)),
|
||||||
|
}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWalletDebitFeeLine(line *paymenttypes.FeeLine) bool {
|
||||||
|
if line == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if line.GetSide() != paymenttypes.EntrySideDebit {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
meta := line.Meta
|
||||||
|
if len(meta) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.EqualFold(strings.TrimSpace(meta["fee_target"]), "wallet")
|
||||||
|
}
|
||||||
|
|
||||||
func (e *gatewayCryptoExecutor) resolveDestination(payment *agg.Payment, action model.RailOperation) (*chainv1.TransferDestination, error) {
|
func (e *gatewayCryptoExecutor) resolveDestination(payment *agg.Payment, action model.RailOperation) (*chainv1.TransferDestination, error) {
|
||||||
if payment == nil {
|
if payment == nil {
|
||||||
return nil, merrors.InvalidArgument("crypto send: payment is required")
|
return nil, merrors.InvalidArgument("crypto send: payment is required")
|
||||||
|
|||||||
@@ -195,6 +195,245 @@ func TestGatewayCryptoExecutor_ExecuteCrypto_MissingCardRoute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGatewayCryptoExecutor_ExecuteCrypto_SubmitsWalletFeeTransferOnSend(t *testing.T) {
|
||||||
|
orgID := bson.NewObjectID()
|
||||||
|
|
||||||
|
submitRequests := make([]*chainv1.SubmitTransferRequest, 0, 2)
|
||||||
|
client := &chainclient.Fake{
|
||||||
|
SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
|
||||||
|
submitRequests = append(submitRequests, req)
|
||||||
|
switch len(submitRequests) {
|
||||||
|
case 1:
|
||||||
|
return &chainv1.SubmitTransferResponse{
|
||||||
|
Transfer: &chainv1.Transfer{
|
||||||
|
TransferRef: "trf-principal",
|
||||||
|
OperationRef: "op-principal",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
case 2:
|
||||||
|
return &chainv1.SubmitTransferResponse{
|
||||||
|
Transfer: &chainv1.Transfer{
|
||||||
|
TransferRef: "trf-fee",
|
||||||
|
OperationRef: "op-fee",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected transfer submission call %d", len(submitRequests))
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resolver := &fakeGatewayInvokeResolver{client: client}
|
||||||
|
registry := &fakeGatewayRegistry{
|
||||||
|
items: []*model.GatewayInstanceDescriptor{
|
||||||
|
{
|
||||||
|
ID: "crypto_rail_gateway_arbitrum_sepolia",
|
||||||
|
InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
|
||||||
|
Rail: discovery.RailCrypto,
|
||||||
|
InvokeURI: "grpc://crypto-gateway",
|
||||||
|
IsEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
executor := &gatewayCryptoExecutor{
|
||||||
|
gatewayInvokeResolver: resolver,
|
||||||
|
gatewayRegistry: registry,
|
||||||
|
cardGatewayRoutes: map[string]CardGatewayRoute{
|
||||||
|
paymenttypes.DefaultCardsGatewayID: {FundingAddress: "TUA_DEST", FeeAddress: "TUA_FEE"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := sexec.StepRequest{
|
||||||
|
Payment: &agg.Payment{
|
||||||
|
OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
|
||||||
|
PaymentRef: "payment-1",
|
||||||
|
IdempotencyKey: "idem-1",
|
||||||
|
IntentSnapshot: model.PaymentIntent{
|
||||||
|
Ref: "intent-1",
|
||||||
|
Source: model.PaymentEndpoint{
|
||||||
|
Type: model.EndpointTypeManagedWallet,
|
||||||
|
ManagedWallet: &model.ManagedWalletEndpoint{
|
||||||
|
ManagedWalletRef: "wallet-src",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Destination: model.PaymentEndpoint{
|
||||||
|
Type: model.EndpointTypeCard,
|
||||||
|
Card: &model.CardEndpoint{Pan: "4111111111111111"},
|
||||||
|
},
|
||||||
|
Amount: &paymenttypes.Money{Amount: "10", Currency: "USDT"},
|
||||||
|
},
|
||||||
|
QuoteSnapshot: &model.PaymentQuoteSnapshot{
|
||||||
|
DebitAmount: &paymenttypes.Money{Amount: "10.000000", Currency: "USDT"},
|
||||||
|
FeeLines: []*paymenttypes.FeeLine{
|
||||||
|
{
|
||||||
|
Money: &paymenttypes.Money{Amount: "0.70", Currency: "USDT"},
|
||||||
|
LineType: paymenttypes.PostingLineTypeFee,
|
||||||
|
Side: paymenttypes.EntrySideDebit,
|
||||||
|
Meta: map[string]string{"fee_target": "wallet"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: &paymenttypes.QuoteRouteSpecification{
|
||||||
|
Hops: []*paymenttypes.QuoteRouteHop{
|
||||||
|
{Index: 1, Rail: "CRYPTO", Gateway: "crypto_rail_gateway_arbitrum_sepolia", InstanceID: "crypto_rail_gateway_arbitrum_sepolia", Role: paymenttypes.QuoteRouteHopRoleSource},
|
||||||
|
{Index: 4, Rail: "CARD", Gateway: paymenttypes.DefaultCardsGatewayID, InstanceID: paymenttypes.DefaultCardsGatewayID, Role: paymenttypes.QuoteRouteHopRoleDestination},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Step: xplan.Step{
|
||||||
|
StepRef: "hop_1_crypto_send",
|
||||||
|
StepCode: "hop.1.crypto.send",
|
||||||
|
Action: discovery.RailOperationSend,
|
||||||
|
Rail: discovery.RailCrypto,
|
||||||
|
Gateway: "crypto_rail_gateway_arbitrum_sepolia",
|
||||||
|
InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
|
||||||
|
},
|
||||||
|
StepExecution: agg.StepExecution{
|
||||||
|
StepRef: "hop_1_crypto_send",
|
||||||
|
StepCode: "hop.1.crypto.send",
|
||||||
|
Attempt: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := executor.ExecuteCrypto(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecuteCrypto returned error: %v", err)
|
||||||
|
}
|
||||||
|
if out == nil {
|
||||||
|
t.Fatal("expected output")
|
||||||
|
}
|
||||||
|
if got, want := len(submitRequests), 2; got != want {
|
||||||
|
t.Fatalf("submit transfer calls mismatch: got=%d want=%d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
principalReq := submitRequests[0]
|
||||||
|
if got, want := principalReq.GetAmount().GetAmount(), "10.000000"; got != want {
|
||||||
|
t.Fatalf("principal amount mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := principalReq.GetDestination().GetExternalAddress(), "TUA_DEST"; got != want {
|
||||||
|
t.Fatalf("principal destination mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
feeReq := submitRequests[1]
|
||||||
|
if got, want := feeReq.GetAmount().GetAmount(), "0.7"; got != want {
|
||||||
|
t.Fatalf("fee amount mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := feeReq.GetAmount().GetCurrency(), "USDT"; got != want {
|
||||||
|
t.Fatalf("fee currency mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := feeReq.GetDestination().GetExternalAddress(), "TUA_FEE"; got != want {
|
||||||
|
t.Fatalf("fee destination mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := feeReq.GetOperationRef(), "payment-1:hop_1_crypto_send:fee"; got != want {
|
||||||
|
t.Fatalf("fee operation_ref mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := feeReq.GetIdempotencyKey(), "idem-1:hop_1_crypto_send:fee"; got != want {
|
||||||
|
t.Fatalf("fee idempotency_key mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatewayCryptoExecutor_ExecuteCrypto_FeeActionUsesWalletFeeAmount(t *testing.T) {
|
||||||
|
orgID := bson.NewObjectID()
|
||||||
|
|
||||||
|
var submitReq *chainv1.SubmitTransferRequest
|
||||||
|
client := &chainclient.Fake{
|
||||||
|
SubmitTransferFn: func(_ context.Context, req *chainv1.SubmitTransferRequest) (*chainv1.SubmitTransferResponse, error) {
|
||||||
|
submitReq = req
|
||||||
|
return &chainv1.SubmitTransferResponse{
|
||||||
|
Transfer: &chainv1.Transfer{
|
||||||
|
TransferRef: "trf-fee",
|
||||||
|
OperationRef: "op-fee",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resolver := &fakeGatewayInvokeResolver{client: client}
|
||||||
|
registry := &fakeGatewayRegistry{
|
||||||
|
items: []*model.GatewayInstanceDescriptor{
|
||||||
|
{
|
||||||
|
ID: "crypto_rail_gateway_arbitrum_sepolia",
|
||||||
|
InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
|
||||||
|
Rail: discovery.RailCrypto,
|
||||||
|
InvokeURI: "grpc://crypto-gateway",
|
||||||
|
IsEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
executor := &gatewayCryptoExecutor{
|
||||||
|
gatewayInvokeResolver: resolver,
|
||||||
|
gatewayRegistry: registry,
|
||||||
|
cardGatewayRoutes: map[string]CardGatewayRoute{
|
||||||
|
paymenttypes.DefaultCardsGatewayID: {FundingAddress: "TUA_DEST", FeeAddress: "TUA_FEE"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := sexec.StepRequest{
|
||||||
|
Payment: &agg.Payment{
|
||||||
|
OrganizationBoundBase: pm.OrganizationBoundBase{OrganizationRef: orgID},
|
||||||
|
PaymentRef: "payment-1",
|
||||||
|
IdempotencyKey: "idem-1",
|
||||||
|
IntentSnapshot: model.PaymentIntent{
|
||||||
|
Ref: "intent-1",
|
||||||
|
Source: model.PaymentEndpoint{
|
||||||
|
Type: model.EndpointTypeManagedWallet,
|
||||||
|
ManagedWallet: &model.ManagedWalletEndpoint{
|
||||||
|
ManagedWalletRef: "wallet-src",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Destination: model.PaymentEndpoint{
|
||||||
|
Type: model.EndpointTypeCard,
|
||||||
|
Card: &model.CardEndpoint{Pan: "4111111111111111"},
|
||||||
|
},
|
||||||
|
Amount: &paymenttypes.Money{Amount: "10", Currency: "USDT"},
|
||||||
|
},
|
||||||
|
QuoteSnapshot: &model.PaymentQuoteSnapshot{
|
||||||
|
DebitAmount: &paymenttypes.Money{Amount: "10.000000", Currency: "USDT"},
|
||||||
|
FeeLines: []*paymenttypes.FeeLine{
|
||||||
|
{
|
||||||
|
Money: &paymenttypes.Money{Amount: "0.70", Currency: "USDT"},
|
||||||
|
LineType: paymenttypes.PostingLineTypeFee,
|
||||||
|
Side: paymenttypes.EntrySideDebit,
|
||||||
|
Meta: map[string]string{"fee_target": "wallet"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: &paymenttypes.QuoteRouteSpecification{
|
||||||
|
Hops: []*paymenttypes.QuoteRouteHop{
|
||||||
|
{Index: 1, Rail: "CRYPTO", Gateway: "crypto_rail_gateway_arbitrum_sepolia", InstanceID: "crypto_rail_gateway_arbitrum_sepolia", Role: paymenttypes.QuoteRouteHopRoleSource},
|
||||||
|
{Index: 4, Rail: "CARD", Gateway: paymenttypes.DefaultCardsGatewayID, InstanceID: paymenttypes.DefaultCardsGatewayID, Role: paymenttypes.QuoteRouteHopRoleDestination},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Step: xplan.Step{
|
||||||
|
StepRef: "hop_1_crypto_fee",
|
||||||
|
StepCode: "hop.1.crypto.fee",
|
||||||
|
Action: discovery.RailOperationFee,
|
||||||
|
Rail: discovery.RailCrypto,
|
||||||
|
Gateway: "crypto_rail_gateway_arbitrum_sepolia",
|
||||||
|
InstanceID: "crypto_rail_gateway_arbitrum_sepolia",
|
||||||
|
},
|
||||||
|
StepExecution: agg.StepExecution{
|
||||||
|
StepRef: "hop_1_crypto_fee",
|
||||||
|
StepCode: "hop.1.crypto.fee",
|
||||||
|
Attempt: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := executor.ExecuteCrypto(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecuteCrypto returned error: %v", err)
|
||||||
|
}
|
||||||
|
if submitReq == nil {
|
||||||
|
t.Fatal("expected transfer submission")
|
||||||
|
}
|
||||||
|
if got, want := submitReq.GetAmount().GetAmount(), "0.7"; got != want {
|
||||||
|
t.Fatalf("fee amount mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := submitReq.GetDestination().GetExternalAddress(), "TUA_FEE"; got != want {
|
||||||
|
t.Fatalf("fee destination mismatch: got=%q want=%q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fakeGatewayInvokeResolver struct {
|
type fakeGatewayInvokeResolver struct {
|
||||||
lastInvokeURI string
|
lastInvokeURI string
|
||||||
client chainclient.Client
|
client chainclient.Client
|
||||||
|
|||||||
Reference in New Issue
Block a user