Skip to content

Commit 54627bb

Browse files
sameh-faroukclaude
andcommitted
fix(bridge): harden tests, fix Go bugs A/B/C/F/H, clean up pallet and docs
Test improvements: - Extract sendStellarPayment to shared bridge_helpers.js (DRY) - Add MV6a below-minimum withdraw test (parity with SV test6) - Increase MV5 batch size from 3 to 5 (match SV test2) - Fix markTestPhase early-return bug with finally blocks - Remove dead waitForValReady function - Add bridge_instrumentation.js for event collection and analysis Go bridge fixes (stellar.go): - Issue A: fix && to || in deposit asset filter (creditedEffect check) - Issue B: add per-payment asset validation with warning log for non-TFT - Issue C: fix || to && in StatBridgeAccount balance filter - Issue F: fix return nil,nil to continue in op loop (highest severity) - Issue H: reuse HTTP client in StellarWallet instead of creating per-call Pallet cleanup: - Collapse redundant ensure!/get into single .ok_or() read in set_stellar_burn_transaction_executed Docs: - Fix deposit fee "10 TFT" to "1 TFT" in bridging, multinode, single_node Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3599c64 commit 54627bb

11 files changed

Lines changed: 6759 additions & 564 deletions

File tree

bridge/docs/bridging.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This document will explain how you can transfer TFT from TF Chain to Stellar and
1111

1212
## Stellar to TF Chain
1313

14-
Transfer the TFT from your Stellar wallet to bridge wallet address that you configured. A depositfee will be taken (10 TFT on mainnet/testnet by default), so make sure you send a larger amount than the fee.
14+
Transfer the TFT from your Stellar wallet to bridge wallet address that you configured. A depositfee will be taken (1 TFT on mainnet/testnet by default), so make sure you send a larger amount than the fee.
1515

1616
### Transfer to TF Chain
1717

@@ -29,7 +29,7 @@ To deposit to a TF Grid object, this object **must** exists. If the object is no
2929
## TF Chain to Stellar
3030

3131
Browse to https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Ftfchain.grid.tf#/extrinsics (for mainnet), select tftBridgeModule and extrinsic: `swap_to_stellar()`. Provide your stellar target address and amount and sign it with your account holding the tft balance.
32-
Again, a withdrawfee will be taken (10 TFT on mainnet/testnet by default), so make sure you send a larger amount than the fee.
32+
Again, a withdrawfee will be taken (1 TFT on mainnet/testnet by default), so make sure you send a larger amount than the fee.
3333

3434
The amount withdrawn from TF Chain will be sent to your Stellar wallet.
3535

bridge/docs/multinode.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,4 +177,4 @@ Now construct a memo message indicating which twin you will deposit to: "twin_TW
177177
./stellar-utils transfer 50 "twin_1" GAYJSBPBQ3J32CZZ72OM3GZP646KSVD3V5QB3WBJSSGPYHYS5MZSS4Z6 --secret SDGRCA63GSP4MSASFAWX5FORTS6ATQMK63YL6ZMF7YIFEJVBTLJDJA3M
178178
```
179179

180-
Now you should have received the tokens minus the depositfee on your account on tfchain (the default depositfee is 10 TFT).
180+
Now you should have received the tokens minus the depositfee on your account on tfchain (the default depositfee is 1 TFT).

bridge/docs/single_node.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,4 @@ Now construct a memo message indicating which twin you will deposit to: "twin_TW
107107
./stellar-utils transfer GAYJSBPBQ3J32CZZ72OM3GZP646KSVD3V5QB3WBJSSGPYHYS5MZSS4Z6 50 "twin_1" --secret SDGRCA63GSP4MSASFAWX5FORTS6ATQMK63YL6ZMF7YIFEJVBTLJDJA3M
108108
```
109109

110-
Now you should have received the tokens minus the depositfee on your account on tfchain (the default depositfee is 10 TFT).
110+
Now you should have received the tokens minus the depositfee on your account on tfchain (the default depositfee is 1 TFT).

bridge/tfchain_bridge/pkg/stellar/stellar.go

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ type StellarWallet struct {
4949
// differ in test/dev environments that use a custom issuer.
5050
resolvedAssetCode string
5151
resolvedAssetIssuer string
52+
httpClient *http.Client
5253
}
5354

5455
type TraceIdKey struct{}
@@ -65,6 +66,24 @@ func NewStellarWallet(ctx context.Context, config *pkg.StellarConfig) (*StellarW
6566
config: config,
6667
}
6768

69+
retryClient := retryablehttp.NewClient()
70+
retryClient.RetryMax = 3
71+
retryClient.RetryWaitMin = 2 * time.Second
72+
retryClient.RetryWaitMax = 5 * time.Second
73+
retryClient.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
74+
if ctx.Err() != nil {
75+
return false, ctx.Err()
76+
}
77+
if err != nil {
78+
return true, nil
79+
}
80+
if resp.StatusCode == 429 || (resp.StatusCode >= 500 && resp.StatusCode <= 599) {
81+
return true, nil
82+
}
83+
return false, nil
84+
}
85+
w.httpClient = retryClient.StandardClient()
86+
6887
account, err := w.getAccountDetails(config.StellarBridgeAccount)
6988
if err != nil {
7089
return nil, err
@@ -642,7 +661,7 @@ func (w *StellarWallet) processTransaction(tx hProtocol.Transaction) ([]MintEven
642661
}
643662

644663
creditedEffect := effect.(horizoneffects.AccountCredited)
645-
if creditedEffect.Code != asset[0] && creditedEffect.Issuer != asset[1] {
664+
if creditedEffect.Code != asset[0] || creditedEffect.Issuer != asset[1] {
646665
continue
647666
}
648667

@@ -654,14 +673,28 @@ func (w *StellarWallet) processTransaction(tx hProtocol.Transaction) ([]MintEven
654673
senders := make(map[string]*big.Int)
655674
for _, op := range ops.Embedded.Records {
656675
if op.GetType() != "payment" {
657-
return nil, nil
676+
continue
658677
}
659678

660679
PaymentOperation := op.(operations.Payment)
661680
if PaymentOperation.To != w.config.StellarBridgeAccount || PaymentOperation.From == w.config.StellarBridgeAccount {
662681
continue
663682
}
664683

684+
// Reject non-TFT payments — log as suspicious
685+
if PaymentOperation.Code != asset[0] || PaymentOperation.Issuer != asset[1] {
686+
logger.Warn().
687+
Str("event_action", "non_tft_payment_rejected").
688+
Str("event_kind", "alert").
689+
Str("from", PaymentOperation.From).
690+
Str("asset_code", PaymentOperation.Code).
691+
Str("asset_issuer", PaymentOperation.Issuer).
692+
Str("amount", PaymentOperation.Amount).
693+
Str("tx_hash", PaymentOperation.TransactionHash).
694+
Msg("non-TFT payment to bridge detected — skipping")
695+
continue
696+
}
697+
665698
parsedAmount, err := amount.ParseInt64(PaymentOperation.Amount)
666699
if err != nil {
667700
continue
@@ -749,30 +782,7 @@ func (w *StellarWallet) getHorizonClient() (*horizonclient.Client, error) {
749782
if w.config.StellarHorizonUrl != "" {
750783
client = &horizonclient.Client{HorizonURL: w.config.StellarHorizonUrl}
751784
}
752-
753-
// custom HTTP client with retry logic
754-
retryClient := retryablehttp.NewClient()
755-
retryClient.RetryMax = 3
756-
retryClient.RetryWaitMin = 2 * time.Second
757-
retryClient.RetryWaitMax = 5 * time.Second
758-
759-
retryClient.CheckRetry = func(ctx context.Context, resp *http.Response, err error) (bool, error) {
760-
if ctx.Err() != nil {
761-
return false, ctx.Err()
762-
}
763-
764-
if err != nil {
765-
return true, nil
766-
}
767-
768-
if resp.StatusCode == 429 || (resp.StatusCode >= 500 && resp.StatusCode <= 599) {
769-
return true, nil
770-
}
771-
772-
return false, nil
773-
}
774-
775-
client.HTTP = retryClient.StandardClient()
785+
client.HTTP = w.httpClient
776786

777787
return client, nil
778788
}
@@ -817,7 +827,7 @@ func (w *StellarWallet) StatBridgeAccount() (string, error) {
817827
asset := w.getAssetCodeAndIssuer()
818828

819829
for _, balance := range acc.Balances {
820-
if balance.Code == asset[0] || balance.Issuer == asset[1] {
830+
if balance.Code == asset[0] && balance.Issuer == asset[1] {
821831
return balance.Balance, nil
822832
}
823833
}

scripts/bridge_helpers.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
const fs = require('fs')
1212
const https = require('https')
13+
const StellarSdk = require('@stellar/stellar-sdk')
1314

1415
// ─── Logging & test result helpers ──────────────────────────────────────────
1516

@@ -92,6 +93,38 @@ async function waitForAccount (address, server, retries = 12) {
9293
throw new Error(`Account ${address} not found after ${retries} attempts`)
9394
}
9495

96+
/**
97+
* Send a Stellar payment (TFT asset) with optional memo.
98+
* @param {object} horizon - Horizon.Server instance
99+
* @param {string} issuerAddress - TFT issuer public key
100+
* @param {string} networkPassphrase - Stellar network passphrase
101+
* @param {string} fromSecret - Sender's Stellar secret key
102+
* @param {string} toAddress - Destination Stellar public key
103+
* @param {string|number} amount - Amount to send (string for Stellar precision)
104+
* @param {string|null} [memo=null] - Optional text memo (e.g. "twin_<id>")
105+
* @returns {Promise<object>} Horizon submit result (includes .hash)
106+
*/
107+
async function sendStellarPayment (horizon, issuerAddress, networkPassphrase, fromSecret, toAddress, amount, memo = null) {
108+
const kp = StellarSdk.Keypair.fromSecret(fromSecret)
109+
const TFTAsset = new StellarSdk.Asset('TFT', issuerAddress)
110+
const acc = await horizon.loadAccount(kp.publicKey())
111+
112+
const builder = new StellarSdk.TransactionBuilder(acc, {
113+
fee: '1000',
114+
networkPassphrase
115+
}).addOperation(StellarSdk.Operation.payment({
116+
destination: toAddress,
117+
asset: TFTAsset,
118+
amount: String(amount)
119+
})).setTimeout(30)
120+
121+
if (memo) builder.addMemo(StellarSdk.Memo.text(String(memo)))
122+
123+
const tx = builder.build()
124+
tx.sign(kp)
125+
return horizon.submitTransaction(tx)
126+
}
127+
95128
// ─── Polling helper ─────────────────────────────────────────────────────────
96129

97130
/**
@@ -169,6 +202,7 @@ module.exports = {
169202
friendbot,
170203
waitForAccount,
171204
waitUntil,
205+
sendStellarPayment,
172206
swapToStellar,
173207
TFT,
174208
TFT_DECIMALS

0 commit comments

Comments
 (0)