Skip to content

Conversation

@curryxbo
Copy link
Contributor

@curryxbo curryxbo commented Dec 26, 2025

Summary by CodeRabbit

  • New Features

    • Optional external transaction signing with exposed signing mode, signer address and chain ID; runtime choice between external signer or local key.
  • Configuration

    • New external-sign options (enable, address, app ID, chain, URL, RSA key); private-key is optional when external signing enabled and validated.
  • Improvements

    • Routed send flow for external/local signing, buffered gas estimation, unified send-and-wait behavior and clearer logging.
  • Documentation

    • README rebranded and updated for token price oracle, external signing, and env-based setup.

✏️ Tip: You can customize this high-level summary in your review settings.

@curryxbo curryxbo requested a review from a team as a code owner December 26, 2025 09:03
@curryxbo curryxbo requested review from r3aker86 and removed request for a team December 26, 2025 09:03
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 26, 2025

📝 Walkthrough

Walkthrough

L2 client constructor now accepts the full config and supports external signing; a new Signer type implements external-sign transaction creation and signing; CLI flags and config fields for external signing added; tx sending routes to local or external sign paths; externalsign dependency added.

Changes

Cohort / File(s) Change summary
Client: constructor & fields
token-price-oracle/client/l2_client.go
NewL2Client now accepts *config.Config; added signer *Signer and externalSign bool fields; added IsExternalSign(), GetSigner(), GetChainID(); conditional init for external vs local signing and logging.
Client: signer implementation
token-price-oracle/client/sign.go
New Signer type with NewSigner(...), Sign, IsExternalSign, GetFromAddress, CreateAndSignTx; builds unsigned London DynamicFeeTx, fetches nonce/tip/baseFee, estimates gas (1.5x buffer), requests external signature, returns signed tx.
CLI / startup
token-price-oracle/cmd/main.go
L2 client constructor updated to pass cfg (NewL2Client(l2RPC, cfg)) instead of raw private key.
Config & flags
token-price-oracle/config/config.go, token-price-oracle/flags/flags.go
Added config fields and CLI flags for external signing (ExternalSign, ExternalSignAddress, ExternalSignAppid, ExternalSignChain, ExternalSignUrl, ExternalSignRsaPriv); validation enforces either external-sign settings or a private key.
Tx sending / updater
token-price-oracle/updater/tx_manager.go
SendTransaction routes to sendWithLocalSign or sendWithExternalSign; sendWithLocalSign does local sign+send with gas estimation; sendWithExternalSign builds unsigned tx (NoSend), delegates signing to Signer, sends raw signed tx, and polls for receipt; logging updated.
Dependencies
token-price-oracle/go.mod
Added github.com/morph-l2/externalsign v0.3.1 and indirect github.com/go-resty/resty/v2; removed obsolete indirect.
Imports / logging
token-price-oracle/client/..., token-price-oracle/updater/...
Updated imports to include externalsign, log, core/types; added logs for signer initialization and external vs local send flows.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Updater as TxManager
  participant Client as L2Client
  participant Signer as Signer
  participant RPC as Ethereum RPC
  participant ExtSvc as ExternalSign Service

  Note over Updater,Client: External-sign enabled send flow
  Updater->>Client: SendTransaction(to, calldata)
  Client->>Signer: CreateAndSignTx(ctx, client, to, calldata)
  Signer->>RPC: eth_getTransactionCount (nonce)
  Signer->>RPC: eth_maxPriorityFeePerGas (tip)
  RPC-->>Signer: tip & header (baseFee)
  Signer->>RPC: eth_estimateGas (NoSend simulation)
  RPC-->>Signer: gas estimate
  Signer-->>Signer: compute fee caps & gas limit (1.5x buffer)
  Signer->>ExtSvc: request external signature (unsigned DynamicFeeTx)
  ExtSvc-->>Signer: signed raw transaction
  Signer-->>Client: signed *types.Transaction
  Client->>RPC: eth_sendRawTransaction (signed)
  RPC-->>Client: tx hash
  Client->>Updater: return tx hash
  Updater->>RPC: poll eth_getTransactionReceipt until mined / timeout
  RPC-->>Updater: receipt
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Add nonce checking #845: Modifies tx_manager send flow (nonce checks, NoSend + gas buffer); closely related to new sendWithLocalSign/sendWithExternalSign changes.
  • Alt fee oracle #809: Changes L2 client and signing-related code; overlaps with L2Client constructor and signer integration here.

Suggested reviewers

  • r3aker86
  • twcctop
  • FletcherMan

Poem

"I hopped through code with whiskers bright,
Flags and keys arranged just right,
An external signer hummed a tune,
It stamped my tx beneath the moon 🥕
— Rabbit dev"

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add external sign' accurately describes the main feature introduced in this pull request - external signing functionality across multiple components.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
token-price-oracle/client/sign.go (1)

85-97: Consider adding Value parameter support for ETH transfers.

The CreateAndSignTx function creates a DynamicFeeTx without a Value field, which means it cannot be used for transactions that need to send ETH. If this is intentional for this oracle use case (contract calls only), consider adding a comment to clarify. Otherwise, add a value *big.Int parameter.

token-price-oracle/flags/flags.go (1)

181-184: PrivateKeyFlag is in requiredFlags but no longer has Required: true.

The PrivateKeyFlag was updated to remove Required: true (since it's not needed when external signing is enabled), but it's still listed in requiredFlags. This is misleading for documentation purposes. Consider moving it to optionalFlags since the actual validation is handled in config.go.

🔎 Proposed fix
 var requiredFlags = []cli.Flag{
 	L2EthRPCFlag,
-	PrivateKeyFlag,
 }

 var optionalFlags = []cli.Flag{
+	PrivateKeyFlag,
 	TxnPerBatchFlag,
token-price-oracle/config/config.go (1)

227-240: Validation logic is correct, but consider reducing information in error messages.

The validation correctly enforces that either all external sign fields are provided or the private key is required. However, the error message on lines 232-233 exposes the actual values of cfg.ExternalSignAddress, cfg.ExternalSignUrl, etc. While these aren't secrets, consider using a boolean indicator instead of printing potentially empty values.

🔎 Proposed improvement
-		return nil, fmt.Errorf("external sign is enabled but missing required config: address=%s, url=%s, appid=%s, chain=%s",
-			cfg.ExternalSignAddress, cfg.ExternalSignUrl, cfg.ExternalSignAppid, cfg.ExternalSignChain)
+		var missing []string
+		if cfg.ExternalSignAddress == "" {
+			missing = append(missing, "address")
+		}
+		if cfg.ExternalSignUrl == "" {
+			missing = append(missing, "url")
+		}
+		if cfg.ExternalSignAppid == "" {
+			missing = append(missing, "appid")
+		}
+		if cfg.ExternalSignChain == "" {
+			missing = append(missing, "chain")
+		}
+		if cfg.ExternalSignRsaPriv == "" {
+			missing = append(missing, "rsa-priv")
+		}
+		return nil, fmt.Errorf("external sign is enabled but missing required config: %v", missing)
token-price-oracle/client/l2_client.go (1)

152-155: Consider returning a copy of chainID to prevent external mutation.

GetChainID() returns the internal *big.Int pointer directly. While big.Int is typically used immutably, a caller could potentially mutate it. Consider returning a copy for defensive programming.

🔎 Proposed fix
 // GetChainID returns the chain ID
 func (c *L2Client) GetChainID() *big.Int {
-	return c.chainID
+	return new(big.Int).Set(c.chainID)
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71d75d9 and 9c763ee.

📒 Files selected for processing (6)
  • token-price-oracle/client/l2_client.go
  • token-price-oracle/client/sign.go
  • token-price-oracle/cmd/main.go
  • token-price-oracle/config/config.go
  • token-price-oracle/flags/flags.go
  • token-price-oracle/updater/tx_manager.go
🧰 Additional context used
🧬 Code graph analysis (4)
token-price-oracle/cmd/main.go (1)
token-price-oracle/client/l2_client.go (1)
  • NewL2Client (27-102)
token-price-oracle/config/config.go (1)
token-price-oracle/flags/flags.go (6)
  • ExternalSignFlag (144-148)
  • ExternalSignAddressFlag (150-154)
  • ExternalSignAppidFlag (156-160)
  • ExternalSignChainFlag (162-166)
  • ExternalSignUrlFlag (168-172)
  • ExternalSignRsaPrivFlag (174-178)
token-price-oracle/client/l2_client.go (2)
token-price-oracle/client/sign.go (2)
  • Signer (17-24)
  • NewSigner (27-60)
token-price-oracle/config/config.go (1)
  • Config (53-88)
token-price-oracle/client/sign.go (1)
token-price-oracle/client/l2_client.go (1)
  • L2Client (18-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (11)
token-price-oracle/client/sign.go (2)

62-73: The Sign method only supports external signing, which may cause confusion.

The method name Sign and struct name Signer suggest general signing capability, but the implementation returns an error for local signing. This is by design per the comment on line 62, but callers might be confused. The error message correctly directs users to use bind.TransactOpts for local signing.


111-119: Gas fee cap calculation follows standard EIP-1559 best practices.

The calculation tip + 2*baseFee provides headroom for base fee increases across blocks, which is the recommended approach for reliable transaction inclusion.

token-price-oracle/flags/flags.go (1)

143-178: External sign flags are well-defined with consistent naming conventions.

The new flags follow the existing patterns for naming (external-sign-*) and environment variables (TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_*).

token-price-oracle/cmd/main.go (1)

107-111: Constructor signature change looks correct.

The NewL2Client call now passes the full config object, enabling the client to determine signing mode internally. This is a clean API change that encapsulates the signing configuration.

token-price-oracle/config/config.go (1)

68-75: External sign configuration fields are properly defined.

The struct fields align with the corresponding flags and provide all necessary configuration for external signing.

token-price-oracle/updater/tx_manager.go (2)

82-134: External signing flow is well-structured.

The sendWithExternalSign method correctly:

  1. Validates the signer is initialized
  2. Builds an unsigned transaction to extract calldata
  3. Delegates signing to the external signer
  4. Sends the signed transaction
  5. Waits for receipt with appropriate timeout

The flow is consistent with the local signing path.


35-39: Clean branching between local and external signing modes.

The SendTransaction method cleanly delegates to the appropriate signing implementation based on configuration.

token-price-oracle/client/l2_client.go (4)

52-77: External signing initialization is well-implemented.

The external signing path correctly:

  1. Parses the RSA private key
  2. Creates a new Signer with all required configuration
  3. Sets up TransactOpts with NoSend=true for building unsigned transactions
  4. Logs initialization details

78-99: Local signing initialization handles the 0x prefix correctly.

The code properly strips the optional 0x prefix from the private key hex string before parsing. The boundary check len(cfg.PrivateKey) > 2 prevents index out of bounds errors.


114-130: GetOpts returns a defensive copy to prevent shared state issues.

This is a good practice to avoid concurrent modification issues when multiple goroutines use the transaction options.


33-38: Deferred cleanup pattern is correctly implemented.

The defer block correctly closes the client on failure. All error assignments within the function scope update the same err variable using :=, ensuring the cleanup check if err != nil catches errors from operations at lines 41, 54, 84, and 90. The early return at line 30 happens before the defer is established, so the pattern is safe.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
token-price-oracle/client/l2_client.go (1)

127-141: Shallow copy may not prevent concurrent modification issues.

The GetOpts() method performs a shallow copy, which means pointer fields (Nonce, Value, GasPrice, GasFeeCap, GasTipCap) still reference the same underlying *big.Int values. If callers mutate these values (e.g., opts.Nonce.Add(...)), concurrent goroutines could experience race conditions.

While typical usage often replaces these pointers rather than mutating them, the shallow copy doesn't fully "prevent shared state issues" as the comment claims.

🔎 Options to address this issue

Option 1: Deep copy big.Int fields (recommended if concurrent access is expected)

 func (c *L2Client) GetOpts() *bind.TransactOpts {
-	// Return a copy to prevent shared state issues
-	return &bind.TransactOpts{
+	opts := &bind.TransactOpts{
 		From:     c.opts.From,
-		Nonce:    c.opts.Nonce,
 		Signer:   c.opts.Signer,
-		Value:    c.opts.Value,
-		GasPrice: c.opts.GasPrice,
-		GasFeeCap: c.opts.GasFeeCap,
-		GasTipCap: c.opts.GasTipCap,
 		GasLimit:  c.opts.GasLimit,
 		Context:   c.opts.Context,
 		NoSend:    c.opts.NoSend,
 	}
+	
+	// Deep copy big.Int pointers
+	if c.opts.Nonce != nil {
+		opts.Nonce = new(big.Int).Set(c.opts.Nonce)
+	}
+	if c.opts.Value != nil {
+		opts.Value = new(big.Int).Set(c.opts.Value)
+	}
+	if c.opts.GasPrice != nil {
+		opts.GasPrice = new(big.Int).Set(c.opts.GasPrice)
+	}
+	if c.opts.GasFeeCap != nil {
+		opts.GasFeeCap = new(big.Int).Set(c.opts.GasFeeCap)
+	}
+	if c.opts.GasTipCap != nil {
+		opts.GasTipCap = new(big.Int).Set(c.opts.GasTipCap)
+	}
+	
+	return opts
 }

Option 2: Update the comment to reflect actual behavior

-	// Return a copy to prevent shared state issues
+	// Return a shallow copy of transaction options
+	// Note: Pointer fields (Nonce, Value, GasPrice, etc.) reference the same underlying values
+	// Callers should not mutate these fields directly
 	return &bind.TransactOpts{

Option 3: Add synchronization if concurrent access is required

Consider using a sync.RWMutex to protect access to c.opts if concurrent reads/writes are expected.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c763ee and 081d71a.

📒 Files selected for processing (1)
  • token-price-oracle/client/l2_client.go
🧰 Additional context used
🧬 Code graph analysis (1)
token-price-oracle/client/l2_client.go (2)
token-price-oracle/client/sign.go (2)
  • Signer (17-24)
  • NewSigner (27-60)
token-price-oracle/config/config.go (1)
  • Config (53-88)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (4)
token-price-oracle/client/l2_client.go (4)

19-25: LGTM: Struct changes support dual signing modes.

The addition of signer and externalSign fields appropriately supports both external and local signing modes.


89-110: LGTM: Local signing initialization is solid.

The local signing path properly handles:

  • Defensive stripping of "0x" prefix
  • Appropriate error handling and wrapping
  • Standard go-ethereum key derivation
  • Clear logging

153-166: LGTM: Accessor methods are clean and well-documented.

The new accessor methods appropriately expose the signing mode, signer, and chain ID for downstream usage.


75-77: NoSend flag and external signing flow are properly implemented.

Verification confirms the code correctly handles all three concerns:

  1. NoSend flag respected: Set to true (lines 49, 92) for transaction building/gas estimation and never allows direct sending in the external signing path
  2. Transaction re-signed before broadcast: Line 110 explicitly calls signer.CreateAndSignTx() to create and sign the transaction
  3. No unsafe code paths: Only the signedTx result from the external signer (line 110) is broadcast via SendTransaction (line 116); the original unsigned transaction is never sent

The implementation safely separates unsigned transaction building with NoSend=true from the external signing and broadcast steps.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
token-price-oracle/go.mod (1)

3-3: Ensure team compatibility with Go 1.24.0 and review breaking changes.

Go 1.24.0 was officially released February 11, 2025, and is a stable version under the Go 1 compatibility promise. However, verify that:

  • All developers and CI/CD environments use this version consistently
  • Your module's code is tested against 1.24.0, as minor breaking changes exist:
    • Generic type aliases behavior has changed (flag will be removed in 1.25)
    • cgo-generated type receiver restrictions are now enforced
    • go vet analyzers may surface new diagnostics
    • testing.AllocsPerRun now panics if parallel tests are running
  • You have reviewed the Go 1.24 release notes if this version is above your minimum requirement
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 081d71a and b866039.

⛔ Files ignored due to path filters (1)
  • token-price-oracle/go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • token-price-oracle/go.mod
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (1)
token-price-oracle/go.mod (1)

39-39: v2.13.1 is a legitimate version with no known security vulnerabilities.

The dependency on github.com/go-resty/resty/v2 v2.13.1 is safe to use. Earlier CVE-2023-45286 (race condition causing request-body disclosure) affected versions up to v2.10.0 and was fixed in v2.11.0, so v2.13.1 is not affected. No other public CVE entries exist for this version.

Note: v2.13.1 is several minor versions behind the latest release (v2.17.1 as of Dec 15, 2025). Consider updating to the latest version during the next dependency maintenance cycle if no compatibility constraints prevent it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
token-price-oracle/updater/tx_manager.go (1)

101-106: Nil check for tx.To() properly addresses past review feedback.

The contract creation guard is now in place, preventing potential nil pointer dereference.

🧹 Nitpick comments (1)
token-price-oracle/config/config.go (1)

246-249: Weak RSA private key validation may cause confusing downstream errors.

The current check only verifies that the string contains "RSA PRIVATE KEY", which doesn't confirm the key is actually parseable. Consider parsing the key at config time to fail fast with a clear error message.

🔎 Proposed enhancement using crypto/x509
+import (
+	"crypto/x509"
+	"encoding/pem"
+)
 		// Validate RSA private key format
-		if !strings.Contains(cfg.ExternalSignRsaPriv, "RSA PRIVATE KEY") {
-			return nil, fmt.Errorf("invalid RSA private key format (must contain 'RSA PRIVATE KEY')")
+		block, _ := pem.Decode([]byte(cfg.ExternalSignRsaPriv))
+		if block == nil || !strings.Contains(block.Type, "RSA PRIVATE KEY") {
+			return nil, fmt.Errorf("invalid RSA private key format: failed to decode PEM block")
+		}
+		if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
+			// Try PKCS8 format as fallback
+			if _, err := x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
+				return nil, fmt.Errorf("invalid RSA private key: failed to parse key: %w", err)
+			}
 		}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b866039 and 325aca4.

📒 Files selected for processing (2)
  • token-price-oracle/config/config.go
  • token-price-oracle/updater/tx_manager.go
🧰 Additional context used
🧬 Code graph analysis (2)
token-price-oracle/updater/tx_manager.go (2)
bindings/hardhat/types.go (1)
  • Receipt (32-46)
ops/l2-genesis/eth/types.go (1)
  • Data (123-123)
token-price-oracle/config/config.go (1)
token-price-oracle/flags/flags.go (6)
  • ExternalSignFlag (144-148)
  • ExternalSignAddressFlag (150-154)
  • ExternalSignAppidFlag (156-160)
  • ExternalSignChainFlag (162-166)
  • ExternalSignUrlFlag (168-172)
  • ExternalSignRsaPrivFlag (174-178)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (7)
token-price-oracle/config/config.go (3)

68-75: LGTM on the new Config fields.

The fields are appropriately typed and follow the naming conventions consistent with the corresponding flags in flags/flags.go.


96-103: Config loading looks correct.

The external sign fields are loaded from their corresponding flags following the established pattern.


227-255: Good validation structure for the signing mode mutual exclusivity.

The validation correctly enforces that either all external sign fields are provided, or a private key is required. The address and URL format checks are appropriate.

token-price-oracle/updater/tx_manager.go (4)

31-39: Clean routing pattern for signing mode selection.

The mutex lock ensures thread-safety for both signing paths, and the routing logic is straightforward.


41-80: LGTM on the local signing flow.

The refactored method preserves the original gas estimation (1.5x multiplier) and transaction sending logic with appropriate logging.


128-136: Consistent receipt waiting behavior across both signing paths.

Both local and external signing paths use the same timeout (60s) and poll interval (2s), ensuring predictable behavior regardless of signing mode.


82-137: The external signing path already applies gas estimation logic comparable to the local path. Both the local signing flow (lines 55-58) and CreateAndSignTx in sign.go estimate gas and apply a 1.5x multiplier (50% buffer): gas * 3 / 2. No action required.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
token-price-oracle/config/config.go (2)

68-74: Consider following Go naming conventions for acronyms.

The fields ExternalSignUrl and ExternalSignRsaPriv should use all-caps for acronyms per Go conventions: ExternalSignURL and ExternalSignRSAPriv.

🔎 Proposed fix
 	// External sign
 	ExternalSign        bool
 	ExternalSignAddress string
 	ExternalSignAppid   string
 	ExternalSignChain   string
-	ExternalSignUrl     string
-	ExternalSignRsaPriv string
+	ExternalSignURL     string
+	ExternalSignRSAPriv string

Note: This change would also require updates to the corresponding flag names in LoadConfig and validation logic.


228-239: Consider adding format validation for URL and RSA private key.

While the non-empty checks and hex address validation are good, additional validation would help catch configuration errors earlier:

  • Parse and validate ExternalSignUrl using url.Parse to ensure it's a valid URL
  • Validate ExternalSignRsaPriv format (e.g., PEM format validation)
🔎 Example validation additions

Add this import at the top:

 import (
 	"fmt"
+	"net/url"
 	"morph-l2/bindings/predeploys"

Then enhance the validation:

 	// Validate external sign config
 	if cfg.ExternalSign {
 		if cfg.ExternalSignAddress == "" || cfg.ExternalSignUrl == "" ||
 			cfg.ExternalSignAppid == "" || cfg.ExternalSignChain == "" ||
 			cfg.ExternalSignRsaPriv == "" {
 			return nil, fmt.Errorf("external sign is enabled but missing required config: address=%s, url=%s, appid=%s, chain=%s",
 				cfg.ExternalSignAddress, cfg.ExternalSignUrl, cfg.ExternalSignAppid, cfg.ExternalSignChain)
 		}
 
 		// Validate address format
 		if !common.IsHexAddress(cfg.ExternalSignAddress) {
 			return nil, fmt.Errorf("invalid external sign address format: %s", cfg.ExternalSignAddress)
 		}
+
+		// Validate URL format
+		if _, err := url.Parse(cfg.ExternalSignUrl); err != nil {
+			return nil, fmt.Errorf("invalid external sign URL format: %s: %w", cfg.ExternalSignUrl, err)
+		}
 	} else {

Note: RSA key validation would require additional crypto libraries and depends on the expected format.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 325aca4 and 39fd0de.

📒 Files selected for processing (1)
  • token-price-oracle/config/config.go
🧰 Additional context used
🧬 Code graph analysis (1)
token-price-oracle/config/config.go (1)
token-price-oracle/flags/flags.go (6)
  • ExternalSignFlag (144-148)
  • ExternalSignAddressFlag (150-154)
  • ExternalSignAppidFlag (156-160)
  • ExternalSignChainFlag (162-166)
  • ExternalSignUrlFlag (168-172)
  • ExternalSignRsaPrivFlag (174-178)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (1)
token-price-oracle/config/config.go (1)

96-102: LGTM!

Configuration loading is straightforward and correctly retrieves all external signing parameters from CLI flags.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
token-price-oracle/updater/tx_manager.go (1)

154-156: Consider logging nonce for consistency with local sign path.

The local sign log (line 97) includes nonce which is useful for debugging transaction ordering issues. The external sign log omits this. If signedTx.Nonce() is available, consider adding it for observability parity.

🔎 Proposed fix
 	log.Info("Transaction sent (external sign)",
 		"tx_hash", signedTx.Hash().Hex(),
+		"nonce", signedTx.Nonce(),
 		"gas_limit", signedTx.Gas())
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39fd0de and e362de2.

📒 Files selected for processing (1)
  • token-price-oracle/updater/tx_manager.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
token-price-oracle/updater/tx_manager.go (2)

36-40: Clean routing logic for signing paths.

The branching based on IsExternalSign() is straightforward and maintains clear separation between the two signing flows.


42-110: LGTM!

The local signing flow is well-structured with appropriate pending transaction checks, gas estimation with 1.5x buffer, and clear logging.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
token-price-oracle/updater/tx_manager.go (2)

44-69: Consider extracting duplicate pending transaction check logic.

The pending transaction check logic (lines 46-69 and 121-144) is nearly identical in both sendWithLocalSign and sendWithExternalSign. This duplication increases maintenance burden and risk of divergence.

🔎 Proposed refactor

Extract a helper method:

+// checkPendingTransactions returns an error if there are pending transactions
+func (m *TxManager) checkPendingTransactions(ctx context.Context, fromAddr common.Address) error {
+	confirmedNonce, err := m.l2Client.GetClient().NonceAt(ctx, fromAddr, nil)
+	if err != nil {
+		return fmt.Errorf("failed to get confirmed nonce: %w", err)
+	}
+
+	pendingNonce, err := m.l2Client.GetClient().PendingNonceAt(ctx, fromAddr)
+	if err != nil {
+		return fmt.Errorf("failed to get pending nonce: %w", err)
+	}
+
+	if pendingNonce > confirmedNonce {
+		log.Warn("Found pending transactions, skipping this round",
+			"address", fromAddr.Hex(),
+			"confirmed_nonce", confirmedNonce,
+			"pending_nonce", pendingNonce,
+			"pending_count", pendingNonce-confirmedNonce)
+		return fmt.Errorf("pending transactions exist (confirmed: %d, pending: %d)", confirmedNonce, pendingNonce)
+	}
+
+	log.Info("No pending transactions, proceeding to send",
+		"address", fromAddr.Hex(),
+		"nonce", confirmedNonce)
+	return nil
+}

Then use it in both methods:

if err := m.checkPendingTransactions(ctx, fromAddr); err != nil {
    return nil, err
}

Also applies to: 119-144


181-183: Consider logging the nonce for external sign transactions.

The local sign path logs the nonce (line 97), but the external sign path omits it. Including the nonce would improve debugging consistency.

🔎 Proposed fix
 	log.Info("Transaction sent (external sign)",
 		"tx_hash", signedTx.Hash().Hex(),
+		"nonce", signedTx.Nonce(),
 		"gas_limit", signedTx.Gas())
token-price-oracle/client/l2_client.go (2)

94-97: Consider using strings.TrimPrefix for cleaner 0x stripping.

The manual prefix check works but could be more idiomatic.

🔎 Proposed fix
+import "strings"
+
 	} else {
 		// Local private key mode
-		privateKeyHex := cfg.PrivateKey
-		if len(cfg.PrivateKey) > 2 && cfg.PrivateKey[:2] == "0x" {
-			privateKeyHex = cfg.PrivateKey[2:]
-		}
+		privateKeyHex := strings.TrimPrefix(cfg.PrivateKey, "0x")
 		key, err := crypto.HexToECDSA(privateKeyHex)

Note: strings is already commonly imported elsewhere, and TrimPrefix handles the case where the prefix doesn't exist gracefully.


166-168: Consider returning a copy of chainID to prevent external mutation.

GetChainID() returns the internal *big.Int directly. Since big.Int is mutable, external callers could inadvertently modify the client's chain ID.

🔎 Proposed fix
 // GetChainID returns the chain ID
 func (c *L2Client) GetChainID() *big.Int {
-	return c.chainID
+	return new(big.Int).Set(c.chainID)
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e362de2 and 36856e4.

📒 Files selected for processing (3)
  • token-price-oracle/client/l2_client.go
  • token-price-oracle/config/config.go
  • token-price-oracle/updater/tx_manager.go
🧰 Additional context used
🧬 Code graph analysis (3)
token-price-oracle/client/l2_client.go (1)
token-price-oracle/client/sign.go (2)
  • Signer (17-24)
  • NewSigner (27-60)
token-price-oracle/updater/tx_manager.go (2)
bindings/hardhat/types.go (1)
  • Receipt (32-46)
ops/l2-genesis/eth/types.go (1)
  • Data (123-123)
token-price-oracle/config/config.go (1)
token-price-oracle/flags/flags.go (6)
  • ExternalSignFlag (144-148)
  • ExternalSignAddressFlag (150-154)
  • ExternalSignAppidFlag (156-160)
  • ExternalSignChainFlag (162-166)
  • ExternalSignUrlFlag (168-172)
  • ExternalSignRsaPrivFlag (174-178)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🔇 Additional comments (7)
token-price-oracle/config/config.go (2)

68-74: LGTM - External signing configuration fields are well-structured.

The new configuration fields cover all necessary parameters for external signing: enable flag, address, app ID, chain identifier, URL, and RSA private key.


227-245: Validation logic is comprehensive and secure.

Good improvements:

  • All five required fields are checked when ExternalSign is enabled
  • Address format validation using common.IsHexAddress
  • RSA private key presence is logged as boolean (rsa_priv_set=%t) rather than exposing the actual key value - this is a security-conscious approach
  • Mutual exclusivity enforced: private key required when external sign is disabled
token-price-oracle/updater/tx_manager.go (2)

36-40: Clean routing between signing modes.

The branching logic clearly separates the two signing paths while maintaining the mutex lock for thread safety.


159-161: Good addition of nil check for contract creation transactions.

This addresses the potential nil pointer dereference that was flagged in previous reviews.

token-price-oracle/client/l2_client.go (3)

73-87: Well-documented placeholder signer with clear safety constraints.

The extensive comments (lines 73-78) and the CRITICAL note on line 81 clearly document why the placeholder signer exists and its safety guarantees. The NoSend: true setting prevents accidental broadcast, and tx_manager.go only extracts To() and Data() from the placeholder transaction before creating a properly signed one.

This addresses the previous review concern about the placeholder signer.


53-68: External signing initialization relies on config-level validation.

The code assumes cfg.ExternalSignRsaPriv and other fields are valid because validation now happens in config/config.go (lines 227-245). This is a clean separation of concerns.


156-168: LGTM - Clean accessor methods.

The new accessor methods (IsExternalSign, GetSigner, GetChainID) provide proper encapsulation and allow tx_manager.go to query the signing mode and access the signer as needed.

@curryxbo curryxbo changed the title WIP:Add external sign Add external sign Dec 30, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
token-price-oracle/README.md (2)

92-94: Add language identifier to fenced code block.

The formula block should specify a language identifier for consistency with Markdown best practices.

🔎 Proposed fix
- ```
+ ```text
  priceRatio = tokenScale × tokenPriceUSD × 10^(18 - tokenDecimals) / ethPriceUSD
- ```
+ ```

145-161: Add language identifier to project structure tree block.

The directory tree block should specify a language identifier for consistency with Markdown best practices.

🔎 Proposed fix
- ```
+ ```text
  token-price-oracle/
  ├── cmd/              # Entry point
  ├── flags/            # CLI flags definition
  ├── config/           # Configuration loading
  ├── client/           # Client wrappers
  │   ├── l2_client.go  # L2 chain client
  │   ├── price_feed.go # Price feed interface
  │   ├── bitget_sdk.go # Bitget API client
  │   └── sign.go       # External signing
  ├── updater/          # Update logic
  │   ├── token_price.go # Price updater
  │   ├── tx_manager.go  # Transaction manager
  │   └── factory.go     # Factory methods
  ├── metrics/          # Prometheus metrics
  └── README.md         # This document
- ```
+ ```
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36856e4 and e5e3122.

📒 Files selected for processing (1)
  • token-price-oracle/README.md
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
token-price-oracle/README.md

92-92: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


145-145: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
token-price-oracle/README.md (1)

15-53: Add Quick Start example for external signing mode.

The Quick Start section (lines 15–53) demonstrates only local signing mode. Since the documentation recommends external signing for production (line 84), users should see a complete example for external signing as well. This would help operators quickly adopt the recommended approach.

Consider adding a collapsible or separate subsection showing environment variables for external signing mode, similar to the local signing example at lines 17–32.

🔎 Proposed addition
 > **Note**: `PRIVATE_KEY` is only required when using local signing mode. For production, use [External Signing](#external-signing-recommended-for-production) instead.
 
+### Environment Variables (External Signing Mode)
+
+```bash
+# Required
+export TOKEN_PRICE_ORACLE_L2_ETH_RPC="https://rpc.morphl2.io"
+export TOKEN_PRICE_ORACLE_EXTERNAL_SIGN="true"
+export TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_ADDRESS="0x..."
+export TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_APPID="your-app-id"
+export TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_URL="https://external-sign-service.example.com"
+export TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_RSA_PRIV="-----BEGIN RSA PRIVATE KEY-----..."
+export TOKEN_PRICE_ORACLE_BITGET_API_BASE_URL="https://api.bitget.com"
+export TOKEN_PRICE_ORACLE_TOKEN_MAPPING_BITGET="1:BTCUSDT,2:ETHUSDT"
+
+# Optional
+export TOKEN_PRICE_ORACLE_EXTERNAL_SIGN_CHAIN="morphl2"
+export TOKEN_PRICE_ORACLE_PRICE_UPDATE_INTERVAL="1m"
+export TOKEN_PRICE_ORACLE_PRICE_THRESHOLD="100"  # 1% (100 bps)
+export TOKEN_PRICE_ORACLE_METRICS_SERVER_ENABLE="true"
+export TOKEN_PRICE_ORACLE_METRICS_PORT="6060"
+export TOKEN_PRICE_ORACLE_LOG_LEVEL="info"
+```
+
 ### Build and Run
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5e3122 and 179056b.

📒 Files selected for processing (1)
  • token-price-oracle/README.md
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
token-price-oracle/README.md

99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


152-152: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)

Uses: ../bindings/bindings (project root contract bindings)
### Price Ratio Formula

```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Specify language for fenced code block.

The formula block is missing a language specifier. Since it's mathematical notation, use plain text formatting or add a language hint.

🔎 Proposed fix
-```
+```text
 priceRatio = tokenScale × tokenPriceUSD × 10^(18 - tokenDecimals) / ethPriceUSD
-```
+```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

99-99: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In token-price-oracle/README.md around line 99 the fenced code block with the
formula lacks a language specifier; update the closing and opening fence to
include a hint such as ```text (or ```plain) and ensure the formula is wrapped
between matching fences so it renders as a plain-text code block: replace the
current triple-backtick fences with fenced code block markers that include the
chosen language tag and keep the formula line unchanged.

## Project Structure
## Testing
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Specify language for fenced code block.

The directory tree is missing a language specifier. Use text or leave it plain for tree structures.

🔎 Proposed fix
-```
+```text
 token-price-oracle/
 ├── cmd/              # Entry point
 ├── flags/            # CLI flags definition
@@ ... @@
 └── README.md         # This document
-```
+```
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

152-152: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In token-price-oracle/README.md around line 152, the fenced code block for the
directory tree lacks a language specifier; update the opening fence to specify a
language such as "text" (i.e. change ``` to ```text) and ensure the closing
fence remains ``` so the tree is rendered correctly as plain text.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants