feat: spending policy, contract state snapshots, multi-sig execution, wallet security audit - #529
Merged
Just-Bamford merged 10 commits intoAug 29, 2026
Conversation
…trols Applications had no way to enforce spending policy before a transaction was signed or submitted. This adds SpendingPolicyEngine, which evaluates a request against configured limits and returns a structured decision. Supports per-transaction, daily and monthly limits configured per asset via setSpendingLimit(asset, amount, period), destination allow/deny restrictions, and approval thresholds requiring N distinct approvers. Concurrency: evaluate() reserves capacity at decision time rather than at submission time. Authorized and pending-approval records both count toward cumulative windows, so two concurrent requests cannot each independently fit under one ceiling. rejectRequest() and markFailed() release the reservation; markCompleted() retains it. Amounts are compared as scaled BigInts at stroop precision so values beyond the IEEE-754 safe integer range keep full precision. Daily and monthly windows are computed in UTC. Duplicate approvals from the same identity are rejected so a single approver cannot satisfy a multi-approver requirement alone. The engine records only decisions made through this SDK instance; it does not observe on-chain activity, so limits constrain the application rather than the account itself. Documented on the class.
…ueries Reproducing a past contract state required capturing it by hand. This adds ContractStateHistory, a registry of SDK-managed snapshots keyed by contract and ledger sequence. Each snapshot carries contract id, ledger reference, capture timestamp and a deterministic state fingerprint. The fingerprint is an FNV-1a digest over a canonical JSON encoding that sorts object keys at every depth, so structurally equal states always fingerprint identically regardless of key insertion order. It detects drift and makes comparison cheap; it is not collision-resistant and is documented as unsuitable as a security boundary. pinContractState(contractId, version) pins a contract to a captured ledger. Pinning or querying a ledger that was never captured fails with an explicit CONTRACT_READ_FAILED rather than silently resolving the nearest version or returning empty state, so callers can distinguish "unavailable" from "no state". Scope is documented on the class: this stores what was captured through it and cannot reconstruct arbitrary historical ledger state, which lives outside RPC retention windows. compareSnapshots reports added, removed and changed entries and rejects comparisons across different contracts. Captured state is copied and frozen so later mutation of the caller's object cannot invalidate a stored snapshot. Complements the existing label-keyed contractSnapshot.ts and stateSnapshots.ts, which capture live state over RPC and carry no version or integrity metadata.
N-of-M contract authorization required callers to coordinate signature collection and assemble the final envelope by hand. This adds MultiSigContractExecution, which separates preparation, signing-request creation, signature collection, validation and submission. The canonical signing payload is the transaction hash for the request's network passphrase — the same payload the Stellar protocol signs — so no independent signing format is introduced. Signatures are verified with Keypair.verify before being accepted, matching the verification approach already used in submitTransaction.ts. Because the payload is network-bound, a request cannot be replayed against a different network or a modified transaction body. Signatures that fail verification are rejected and contribute no weight, so an invalid signature can never advance the threshold. Duplicate submissions from the same signer are rejected, preventing one signer from satisfying a multi-signer threshold alone. Thresholds accrue by signer weight rather than signer count, and a threshold exceeding total declared weight is rejected at creation time as unreachable. Expired requests can neither collect signatures nor execute, even when the threshold was met before expiry. Executing marks the request executed so the same authorization cannot be assembled twice. execute() returns the assembled signed XDR for submitTransaction(); the workflow never signs or submits on the caller's behalf.
Applications had no consistent way to judge whether a wallet adapter supports expected security properties or whether a connection carries known risk. This adds auditWalletSecurity, which returns a structured report rather than a single opaque number. The report exposes every contributing factor with its severity, confidence and exact score penalty, so callers can act on the underlying evidence. The 0-100 score is derived deterministically from a fixed severity-to-penalty table: identical inputs always produce an identical score, and the score always equals the base of 100 plus the sum of the factor deltas. Assessment covers declared adapter capabilities, adapter availability, authentication state, connection origin (HTTPS, localhost HTTP, plain HTTP and unparseable origins are distinguished), and vulnerability data from a caller-supplied source. Unknown is never treated as safe. With no configured source, or a source whose knownWallets does not cover this wallet, vulnerability status is reported as unknown, penalized, and vulnerabilityDataAvailable is false. An unknown adapter version causes version-scoped advisories to be treated as applicable rather than excluded. Undated and stale sources are penalized. A clean result states explicitly that absence of a report is not proof of safety. The score's limits are documented on the function: it reflects only what was observable at audit time, capabilities are self-declared by the adapter and are not verified, and the number is not a safety guarantee.
…and-state-workflows
…nd-state-workflows # Conflicts: # src/index.ts
…y-and-state-workflows # Conflicts: # src/index.ts # src/soroban/index.ts
…state-workflows # Conflicts: # src/index.ts
|
@Johnalex-hub 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! 🚀 |
…workflows Resolve barrel export conflicts in src/index.ts and src/transaction/index.ts. Both sides were purely additive re-export blocks appended to the same region of each barrel, so the resolution keeps both: - HEAD: spending policy, contract state history, multi-sig execution, wallet security audit exports. - upstream/main (Sorokit#528): fee forecasting, dependency graph, portfolio aggregation, SDK diagnostics exports. No symbol collisions between the two sets. Verified: tsc error count unchanged from the pre-merge baseline (22, all pre-existing), and all 310 tests across both sides' new suites pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds four independent SDK features. Each was developed on its own branch and merged here; detail is in the individual commit messages.
src/transaction/spendingPolicy.ts) — per-transaction/daily/monthly limits per asset, destination restrictions, multi-approver thresholds. Capacity is reserved at evaluation time so concurrent requests cannot bypass a shared ceiling.src/soroban/contractStateHistory.ts) — versioned snapshots with deterministic fingerprints,pinContractState, integrity checks, comparison. Uncaptured ledgers report an explicit error rather than empty state.src/soroban/multiSigExecution.ts) — N-of-M workflow over the canonical transaction hash; signatures verified withKeypair.verify, no new signing format. Invalid and duplicate signatures never advance the threshold.src/wallet/securityAudit.ts) — structured report exposing every scoring factor. Unknown vulnerability data is penalized, never treated as safe.All four are exported from their module barrel and
src/index.ts.Verification: 158 new tests, all passing.
tsc --noEmitreports 22 errors — identical to the pre-existing count onmain, none in this code. Lint clean, build succeeds. The remaining suite failures (freighter, lobstr, logger, priceSubscriptions, scheduler, soroban) are pre-existing onmainand untouched here.Closes #520
Closes #521
Closes #522
Closes #524