Skip to content

#157 feat(core): decode Stellar Asset Contract (SAC) balances FIXED - #265

Merged
AbdulmalikAlayande merged 2 commits into
TegoLabs:mainfrom
veloura-dev:#157-feat(core)--decode-Stellar-Asset-Contract-(SAC)-balances-FIX
Jun 29, 2026
Merged

AbdulmalikAlayande merged 2 commits into
TegoLabs:mainfrom
veloura-dev:#157-feat(core)--decode-Stellar-Asset-Contract-(SAC)-balances-FIX

Conversation

@veloura-dev

Copy link
Copy Markdown
Contributor

FINDINGS

  • Standardized SAC Balance Key Layout: In Soroban, standard Stellar Asset Contracts (SAC) store account balances under persistent storage using a Rust enum key DataKey::Balance(Address). When serialized to XDR (xdr.ScVal), this enum variant is represented as an scvVec containing two elements: Symbol("Balance") and Address(address).
  • SAC Storage Map Structure: Unlike custom token contracts that might store balances as simple integers (i128), built-in Soroban SAC instances (soroban-env-host) store address balances as Soroban maps (scvMap). This map layout contains key-value pairs for:
    • amount (stored as an i128 integer)
    • authorized (stored as a bool flag indicating whether the trustline/account is authorized to transact)
    • clawback (stored as a bool flag indicating whether clawback is enabled)
  • Deterministic Contract Isolation: Standard SAC contracts on Soroban run under the executable type contractExecutableStellarAsset. Checking this property via getContractInstanceEntry allows sorokeep to isolate standard SAC contracts and fail gracefully when attempting to run SAC inspections on non-SAC contracts (e.g., custom contractExecutableWasm contracts).
  • RPC Storage Value Separation: Previously, sorokeep's RPC client (getEntryTTLs) stripped out raw XDR values (entry.val) from responses to keep application models lightweight. To inspect token balances without modifying existing TTL architectures, a dedicated storage entry query method (getContractStorageEntries) was required.

2. Fix Features

  • Custom SAC Layout Parser (parseSacBalance):

    • Built a specialized parser in src/core/inspect.ts that takes raw on-chain base64 XDR storage values (valXdr) and safely unpacks the Soroban map layout (scvMap).
    • Automatically extracts the amount (handling 128-bit signed integer decoding), authorized status, and clawback status into clean JavaScript/TypeScript records.
  • Balance Slot Auto-Locator (buildSacBalanceKeyXdr):

    • Implemented an auto-locator function that accepts a contract ID and any standard Stellar address (account G... or contract C...).
    • Automatically constructs the standard persistent LedgerKeyContractData XDR string for DataKey::Balance(Address), eliminating the need for developers to manually craft base64 XDR keys.
  • Dynamic Decimals Decoder & Precision Formatter (formatTokenBalance & getSacDecimals):

    • Added getSacDecimals to StellarRpcClient to dynamically inspect token decimals on-chain via simulation of the decimals() view method (with safe fallback to standard 7 decimals).
    • Implemented formatTokenBalance using bigint arithmetic to convert raw stroops into exact decimal strings (e.g., 10500000 stroops with 7 decimals -> "1.05"), avoiding JavaScript floating-point rounding inaccuracies.
  • Graceful Failure Guard on Non-SAC Contracts (inspectContract):

    • Implemented strict contract validation logic. If the inspected contract instance returns an executable type other than contractExecutableStellarAsset, the inspection fails gracefully with an explicit error message ("Contract ... is not a standard Stellar Asset Contract (SAC)") rather than crashing or throwing unhandled XDR decoding exceptions.
  • CLI Shortcut Command (sorokeep inspect):

    • Added the sorokeep inspect <contractId> command to the CLI with the --entry option.
    • Supported the --entry balance:<address> shortcut to auto-locate balance storage slots and render rich terminal outputs detailing decoded balances, authorization flags, clawback settings, and live entry TTL ledgers.
  • Public Programmatic Library Surface (src/lib.ts):

    • Re-exported inspectContract, parseSacBalance, buildSacBalanceKeyXdr, formatTokenBalance, and all associated TypeScript interfaces in src/lib.ts so external Node.js tools and scripts can leverage the SAC inspection engine directly.

CLOSE #157

@coderabbitai

coderabbitai Bot commented Jun 27, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added an inspect CLI command to display contract/network metadata plus storage entries and token balances.
    • Added support for inspecting multiple --entry values with network/RPC selection and improved TTL reporting.
    • Expanded the Node.js public API with inspection utilities (e.g., contract inspection, SAC balance decoding/formatting).
  • Bug Fixes

    • Improved inspection error handling, including clearer failure output and consistent non-zero exit behavior.
    • Added more robust SAC balance decoding and TTL/status handling when on-chain values are missing.
  • Tests

    • Added Vitest coverage for the inspect command, core SAC inspection/formatting utilities, and updated public API export checks.

Walkthrough

Adds a new inspect CLI subcommand and core SAC inspection module for querying on-chain storage, decoding balances, computing TTL metadata, and exposing the inspection helpers through the CLI and library exports.

Changes

SAC Inspect Feature

Layer / File(s) Summary
RPC client methods and data types
src/rpc/client.ts, src/core/inspect.ts (types)
Adds ContractStorageEntryResult, getContractStorageEntries, and getSacDecimals to StellarRpcClient; defines InspectOptions, InspectEntryInfo, and InspectResult types.
Core inspection logic and SAC helpers
src/core/inspect.ts
Implements formatTokenBalance, parseSacBalance, buildSacBalanceKeyXdr, and inspectContract; verifies SAC executable type, maps balance:<address> inputs to storage key XDRs, bulk-fetches entries, classifies TTL, and decodes balances.
CLI command registration and public exports
src/commands/inspect.ts, src/index.ts, src/lib.ts
Registers inspect <contractId> with --entry, --network, --rpc-url options; wires it into the program; re-exports inspection utilities from lib.ts.
Tests
tests/core/inspect.test.ts, tests/commands/inspect.test.ts, tests/lib.test.ts
Unit tests for helpers and integration mocks for inspectContract; CLI failure/success path tests; lib smoke-test assertions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #157 — This PR implements SAC balance decoding and the inspect --entry balance:<address> flow described in the issue.
  • #155 — This PR adds the inspect CLI command and the storage-entry inspection path referenced by the issue.

Poem

🐇 I hopped through XDR and ledger light,
Found balances sparkling clear tonight.
With inspect I peek and read,
TTLs bloom where keys succeed—
A cozy SAC quest, tidy and bright ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main change: decoding SAC balances in the core inspection flow.
Description check ✅ Passed The description is clearly related to the implemented SAC inspection and CLI/library changes.
Linked Issues check ✅ Passed The PR matches the issue goals by adding SAC balance parsing, the inspect CLI shortcut, decimal formatting, non-SAC failure handling, and tests.
Out of Scope Changes check ✅ Passed The changes stay focused on SAC inspection support and related tests, with no obvious unrelated additions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@drips-wave

drips-wave Bot commented Jun 27, 2026

Copy link
Copy Markdown

@veloura-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/inspect.ts`:
- Around line 21-26: The inspect command is eagerly calling getDatabase() before
it needs any local metadata, which makes `sorokeep inspect` fail in read-only or
unwritable environments. Update the inspect flow in inspect.ts so
inspectContract can be invoked with an optional DB handle, and only open the
SQLite database when a local record is actually needed; keep the existing
fallback behavior to options.network, options.rpcUrl, and the default testnet
path when no DB data is available.

In `@src/core/inspect.ts`:
- Around line 146-147: The network and RPC resolution in inspect should
prioritize explicit CLI input over stored contract metadata. Update the logic in
the inspect flow around the contract lookup so options.network and
options.rpcUrl are used first, with contract?.network and contract?.rpc_url only
as fallbacks, and keep the default to "testnet" when neither is provided. Verify
the behavior in the code that computes network and rpcUrl so a contract existing
in the DB does not override user-supplied --network or --rpc-url values.
- Around line 213-249: Treat decode errors as data issues rather than absence:
in the inspect flow around the foundEntry/parseSacBalance handling, stop
returning a zeroed balance when an entry exists but valXdr is missing or fails
to decode. Update the balance branch so item.type === "balance" either
propagates an error/undecodable state or otherwise preserves that the entry was
found but unreadable, using foundEntry, parseSacBalance, and the returned
balance object to distinguish “not found” from “present but undecodable.”
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 752343be-9f73-434d-842b-703b846a93c7

📥 Commits

Reviewing files that changed from the base of the PR and between d4f7b90 and 6632232.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • src/commands/inspect.ts
  • src/core/inspect.ts
  • src/index.ts
  • src/lib.ts
  • src/rpc/client.ts
  • tests/commands/inspect.test.ts
  • tests/core/inspect.test.ts
  • tests/lib.test.ts
📜 Review details
🔇 Additional comments (2)
src/rpc/client.ts (1)

43-46: LGTM!

Also applies to: 268-328

src/core/inspect.ts (1)

1-129: LGTM!

Also applies to: 152-210, 250-283

Comment thread src/commands/inspect.ts
Comment thread src/core/inspect.ts
Comment thread src/core/inspect.ts
@gitguardian

gitguardian Bot commented Jun 27, 2026 •

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 6 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
- - Generic High Entropy Secret 3dd17ce tests/core/vault.test.ts View secret
- - Generic High Entropy Secret 3dd17ce tests/core/vault.test.ts View secret
- - Generic High Entropy Secret 3dd17ce tests/core/vault.test.ts View secret
- - Generic High Entropy Secret b59bef4 tests/core/vault.test.ts View secret
- - Generic High Entropy Secret b59bef4 tests/core/vault.test.ts View secret
- - Generic High Entropy Secret b59bef4 tests/core/vault.test.ts View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@veloura-dev

Copy link
Copy Markdown
Contributor Author

@AbdulmalikAlayande please review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/rpc/client.ts (1)

302-303: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Wrap these RPC calls in executeWithRetry.

Both new inspect paths bypass the retry wrapper, so a single timeout/429/5xx aborts inspect instead of using the backoff policy added for RPC calls.

Suggested fix
-        const response = await this.server.getLedgerEntries(...keys);
+        const response = await executeWithRetry(() => this.server.getLedgerEntries(...keys));
...
-            const sim = await this.server.simulateTransaction(tx);
+            const sim = await executeWithRetry(() => this.server.simulateTransaction(tx));

Also applies to: 342-342

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/rpc/client.ts` around lines 302 - 303, The new inspect RPC paths in the
client currently call this.server.getLedgerEntries directly and bypass the retry
policy, so wrap these calls in executeWithRetry just like the other RPC methods.
Update the inspect flow in client.ts around the getLedgerEntries-based logic
(and the other affected inspect path noted in the review) so transient
timeout/429/5xx failures are retried with the existing backoff behavior rather
than failing immediately.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/rpc/client.ts`:
- Around line 302-303: The new inspect RPC paths in the client currently call
this.server.getLedgerEntries directly and bypass the retry policy, so wrap these
calls in executeWithRetry just like the other RPC methods. Update the inspect
flow in client.ts around the getLedgerEntries-based logic (and the other
affected inspect path noted in the review) so transient timeout/429/5xx failures
are retried with the existing backoff behavior rather than failing immediately.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f5f99294-ff80-45d3-b84e-8cf16563713c

📥 Commits

Reviewing files that changed from the base of the PR and between 6632232 and f6f5184.

📒 Files selected for processing (3)
  • src/index.ts
  • src/lib.ts
  • src/rpc/client.ts
📜 Review details
⚠️ CI failures not shown inline (1)

GitHub Check: GitGuardian Security Checks: 3 secrets uncovered!

Conclusion: failure

View job details

#### 3 secrets were uncovered from the scan of 43 commits in your pull request. ❌
Please have a look to GitGuardian findings and remediate in order to secure your code.
Since your pull request originates from a forked repository, GitGuardian is not able to associate the secrets uncovered with secret incidents on your GitGuardian dashboard.
Skipping this check run and merging your pull request will create secret incidents on your GitGuardian dashboard.
### 🔎 Detected hardcoded secrets in your pull request
-   Pull request `#289`: `feat/issue-110-webhook-delivery-channel` 👉 `main` (1 commit)
-   Pull request `#288`: `feat/issue-115-extend-footprint-ttl-op` 👉 `main` (1 commit)
-   Pull request `#287`: `feat/issue-119-restore-command` 👉 `main` (1 commit)
-   Pull request `#286`: `feat/cli-custom-schema` 👉 `main` (1 commit)
-   Pull request `#285`: `feat/scval-json-translator` 👉 `main` (1 commit)
-   Pull request `#283`: `feat/alerts-add-list-remove-tests` 👉 `main` (2 commits)
-   Pull request `#282`: `feat/auto-extension-monitor-integration` 👉 `main` (1 commit)
-   Pull request `#281`: `feat/alerts-test-command` 👉 `main` (1 commit)
-   Pull request `#272`: `main` 👉 `main` (2 commits)
-   Pull request `#271`: `feature/dynamic-key-discovery` 👉 `main` (1 commit)
-   Pull request `#270`: `#145-feat(core)--integrate-HashiCorp-Vault-for-key-retrieval-FIX` 👉 `main` (1 commit)
-   Pull request `#269`: `feat/simulation-traffic-cache` 👉 `main` (5 commits)
-   Pull request `#268`: `feat/secure-keychain-storage` 👉 `main` (3 commits)
-   Pull request `#267`: `feat/instance-storage-scanner` 👉 `main` (2 commits)
-   Pull request `#266`: `feat/budget-exhaustion-alerts` 👉 `main` (1 commit)
-   Pull request `#265`: `#157-feat(core)--decode-Stellar-Asset-Contract-(SAC)-balances-FIX` 👉 `main` (2 commits)
-   Pull request `#263`: `fix/issue-137-simulation-failures` 👉 `main` (1 commit)
-   Pull request `#261`: `feat-poll-interval-overrides` 👉 `main` (2 commits)
-   Pull request `#256`: `feat/154-ledger-key-...
🔇 Additional comments (4)
src/index.ts (1)

15-15: LGTM!

Also applies to: 43-45

src/lib.ts (1)

19-23: LGTM!

src/rpc/client.ts (2)

15-41: LGTM!


72-72: LGTM!

@AbdulmalikAlayande
AbdulmalikAlayande merged commit 156d642 into TegoLabs:main Jun 29, 2026
1 of 2 checks passed
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.

feat(core): decode Stellar Asset Contract (SAC) balances

2 participants