Skip to content

chore(root): publish modules - #9956

Closed
davidkaplanbitgo wants to merge 58 commits into
rel/latestfrom
master
Closed

davidkaplanbitgo wants to merge 58 commits into
rel/latestfrom
master

Conversation

@davidkaplanbitgo

Copy link
Copy Markdown
Contributor

No description provided.

noegutierrez5 and others added 30 commits September 30, 2026 08:45
Adds an optional, failure-isolated progressCallback to changePassword()
so callers (e.g. bitgo-retail's account-password dialog) can surface
truthful indeterminate progress through v1/v2 keychain re-encryption
and a finalizing phase, instead of a silent multi-minute operation.

- sdk-core: KeychainPasswordUpdateProgress/-Callback on
  UpdatePasswordOptions; v1 and v2 updatePassword() emit one
  updated/skipped outcome per nonfatal keychain, observer exceptions
  are isolated and never affect rotation results or abort on fatal
  errors.
- sdk-api: PasswordRotationProgress/-Callback on ChangePasswordOptions;
  BitGoAPI.changePassword() aggregates lower-level outcomes into a
  single keychains/started -> keychains/updated* -> finalizing/started
  -> finalizing/completed lifecycle with monotonic counters, without
  changing the existing batching/legacy POST fallback contract.
- Unit tests cover v1/v2 outcome ordering, missing-encryptedPrv and
  known-decrypt-failure skips, unchanged fatal-error propagation,
  event-order/counter assertions, callback-throws isolation, and the
  batching-check-failure / batching-POST-failure / retry contracts.

Ticket: WCN-2084
…progress

Completes the WCN-2084 progress contract by giving the password-rotation
callback a truthful denominator and fixing the counting unit.

sdk-core: capture `encryptedTotalCount` from the first v2 key-list page and
pass it through every progress event. Records without an `encryptedPrv` no
longer emit progress at all.

sdk-api: forward `total` from the lower-level keychain callbacks into the
aggregated `PasswordRotationProgress` events.

Denominator decision (the choice WCN-2084's scope doc left to product/SDK
owners): option B, "encrypted wallet keychains". The unit is keychain records
that hold encrypted user key material, matching the backend's
`encryptedTotalCount`, which counts `users[]` entries with `encryptedPrv` set.

The alternative, "records inspected", was rejected: it would count placeholder
key records that no rotation ever touches. Emitting progress for those while
the backend excluded them made `completed` overshoot `total` — an account with
50 key records of which 32 are encrypted rendered "50 of 32". Records that do
hold ciphertext but fail to decrypt still emit `skipped`, so the counter always
reaches the denominator exactly.

Tests: v2 key without `encryptedPrv` emits nothing and keeps `total` intact;
existing ordering, pagination, decrypt-failure, callback-exception, and
finalization coverage unchanged.

Ticket: WCN-2084
The changePassword progress counters are shared across the v1 and v2 sets,
but the total came only from the v2 list's encryptedTotalCount — a mixed
account with 2 SJCL and 32 v2 keychains finished the counter at 34 of 32.

The v1 set has no server-provided count, but its size is knowable client
side: every /user/encrypted entry emits exactly one progress event, so the
returned re-encryption map's size is the v1 contribution. Captured after the
v1 pass and added to every v2 event's total, so the mixed account above
completes at 34 of 34, exactly.

v1 events stay indeterminate: surfacing the client-side v1 count mid-flight
would make the denominator jump the moment v2's server count arrives, and a
denominator that moves is worse than none. When the backend supplies no
encryptedTotalCount the total stays undefined — never fabricated.

Test: the 2 v1 + 32 v2 account now asserts v1 events indeterminate, v2
events at total 34, and the final event at completed 34 of total 34; the
total-forwarding test accounts for the combined denominator.

Ticket: WCN-2084
Review follow-up: an account with only v1 keychains never receives a
v2 event, so the total stayed undefined for the whole rotation and the
observer could never render "n of n". After the v2 walk completes with
zero events, emit one terminal keychains/updated event carrying the
client-known v1 set size as the denominator (2 of 2), immediately
before finalizing.

Guarded on v2EventCount === 0 so mixed accounts keep the single stable
total from encryptedTotalCount — surfacing the v1 size unconditionally
would make the denominator jump mid-rotation (2 of 2, then 34 of 34).

Also rebases onto latest master, where the safe-mode password rotation
landed in sdk-core (safeId on UpdatePasswordOptions, batch persistence
via PUT /api/v2/key/bulk). The UpdatePasswordOptions conflict resolves
additively: progressCallback remains a legacy-mode observer and the
safe-mode walk does not invoke it.

Local suites: sdk-api bitgoAPI 74 passing; my keychain progress tests
in modules/bitgo v1 (2) and v2 (4) passing. Pre-existing master-side
failures in modules/bitgo's own keychain suites are unchanged by this
diff and do not run in PR CI.

Ticket: WCN-2084
Review direction (WCN-2084): the total is the generic list count
(totalCount from the wallet-platform companion PR), so the walk emits
skipped for listed records with no serialized encryptedPrv instead of
silently dropping them — completed reaches the total on any account
composition, and the SDK no longer needs to know which records the
serializer suppresses. Renames the list-result field to totalCount.

Ticket: WCN-2084
Document encryptionVersion and progressCallback on changePassword,
state that safe mode never invokes the callback, and drop a design
label from one comment and one test title. No behavior change.

Ticket: WCN-2084
The message-signing policy (DEFI-653) parks signMessage/signTypedStructuredData
txRequests behind a pending approval, but the SDK's message-sign paths never
inspected the txRequest state: they ran all MPC signing rounds against a parked
request and failed at the final send with a wrapped
'Expected transaction request to be in state pendingDelivery' error, leaving
the UI unable to recover the pending approval id. After approval, the only
retry path (signAndSendTxRequest) signs through the transaction path and
crashes on message-only requests (transactions[0].unsignedTx of undefined).

- signMessageTss/signTypedDataTss: after create/fetch, resolve the parked
  request as data instead of attempting to sign, mirroring the tx flow's
  early return in prebuildAndSignTransaction
- SignedMessage: now a discriminated union on the wallet-platform txRequest
  state — { state: 'pendingApproval'; pendingApprovalId: string } |
  { state: 'delivered'; txHash: string; signature: string } — so consumers
  narrow on state instead of inferring from field presence
- the surfaced state is derived from the server-reported txRequest state
  (the signing flow ends with a fresh getTxRequest, which lands after
  wallet-platform combines and delivers); any state other than 'delivered'
  at that point throws instead of surfacing a hardcoded value
- assert pendingApprovalId on the parked branches of signMessageTss/
  signTypedDataTss, matching signAndSendMessageTxRequest
- new signAndSendMessageTxRequest({ txRequestId, walletPassphrase }): signs a
  message txRequest through the message path (WP-persisted messageEncoded as
  bufferToSign), for resuming a parked request after approval; resolves with
  the pendingApproval variant if the request is still parked; full
  txRequests only — lite is deprecated and receives no new features; hot
  wallets only (server-held encryptedPrv) — cold/external-signer wallets
  resume via wallet.signMessage with txRequestId and prv
- rethrow the original signing errors instead of wrapping them in
  'failed to sign message/typed data ...' so downstream callers can classify
  ApiResponseError results (e.g. the TxRequestPendingApprovalError 409)
- sdk-coin-xdc: signXdcKycMessage fails loudly when the KYC sign request is
  parked behind a pending approval instead of returning an unusable
  signature-less result

BREAKING CHANGE: SignedMessage is now a discriminated union on state.
Consumers that previously read txHash or signature unguarded
must narrow on state === 'delivered' first:

  const result = await wallet.signMessage(params);
  if (result.state === 'pendingApproval') { /* show approval UI */ }
  else { const { txHash, signature } = result; }

Refs: DEFI-1014
Forward explicit SOL single-asset consolidation mode to wallet-platform.

Ticket: CHALO-1755
Add createOfflineKeyGenRound2Share to EddsaMPCv2Utils in sdk-core, the
second of the three offline signer methods for the EdDSA MPCv2
external-signer key generation flow.

The method decrypts the persisted round-1 session, restores the DKG
state, verifies and deserializes all 3 incoming round-1 messages (own,
counterParty, BitGo) via MPSComms.verifyMpsMessage, calls
dkg.handleIncomingMessages to produce ownMsg2, signs it with the GPG
private key, and returns { signedMsg2, encryptedRound2Session }.

The round-1 session blob is bound to the caller via validateAdata
before decryption: the optional partyId param (defaulting to USER,
matching createOfflineKeyGenRound1Share) is validated against the
envelope's adata, so a blob encrypted for the other source fails
loudly instead of silently resuming the WASM DKG as the wrong party.

Also fix DKG.restoreSession in sdk-lib-mpc to be async and load WASM
before restoring state (mirrors DSG.restoreSession), which is required
so that handleIncomingMessages works on a restored session.

Ticket: WCI-890
…nalSigner

- Add eddsaMPCv2Callbacks to GenerateWalletWithExternalSignerOptions in iWallets.ts
- Update hasMpcCallbacks so createKeychainCallback + MPCv2 callbacks is rejected
- Add the EdDSA MPCv2 routing branch in generateMpcWalletWithExternalSigner:
  dispatches to EddsaMPCv2Utils.createKeychainsWithExternalSigner when
  eddsaMPCv2Callbacks is set; rejects when both eddsaMPCv2Callbacks and
  eddsaCallbacks are provided; rejects when neither is provided; rejects
  MPC algorithms without external-signer support (e.g. redpallas) instead
  of silently routing them to the EdDSA path
- Use sinon.stub on the EddsaMPCv2Utils prototype in tests instead of raw
  assignment + delete, which removed the real method (merged via WCI-916)
  from the prototype for the rest of the test run
- Rebased onto latest master

Ticket: WCI-917
V1TransactionBuilder gains fromVersionedData(envelope) (converts the
client-supplied index-based envelope to address-based instructions
internally via toV1TransactionInstruction), transactionConfig() coerces
unset fields to null, and injectV1NonceAdvanceInstruction + the
envelope-instruction bridge are public SDK exports — so the WP consumes
the builder directly instead of carrying the conversion/injection
helpers server-side.

Refs: CHALO-1758
problem/goal: clients need authorizeTrustline transaction support.
On Stellar, setTrustLineFlags (allow trust) is a low-security
operation governed by the account's low threshold, so BitGo is
tightening XLM wallet lowThreshold from 1 to 2 signatures to prevent
a single client signature from bypassing approval security. The SDK
must whitelist lowThreshold (=2) on accountConfig verification so
platform-built transactions can be verified, and surface the
threshold in explainTransaction output.

implementation details: AccountConfigVerifyParams gains an optional
lowThreshold, restricted to exactly 2 (mirroring the platform build
restriction; med/high/master remain excluded); the accountConfig type
guard accepts it; verifyAccountConfigOperations rejects any value
other than 2 and compares the parsed setOptions op's lowThreshold
against the request; explainTransaction parses lowThreshold into the
accountConfig operation.

verification: npx mocha test/unit/xlm.ts --grep accountConfig passes
(24 tests including lowThreshold match/mismatch/restriction and
explain parsing); tsc --noEmit clean.

Ticket: CSHLD-1925
Problem/goal:
createToken and the token map builders skip any coin already present in
the static coin map, so an AMS payload for an existing token can never
refresh its modifiable fields (features, fullName, decimalPlaces)
without a statics release. Goal: let AMS update known tokens while the
fields that define the asset stay immutable.

Implementation details:
- CoinMap.replace only swaps when the old and new records are both
  tokens and agree on id, symbol (prefix + suffix), contract address and
  alias. Otherwise the existing record is kept. Coins not yet in the map
  are still added.
- createToken no longer returns the stale static record. For a known
  token it constructs the token, calls coins.replace, and returns the
  record the map holds. A config that fails token validation keeps the
  existing record instead of throwing. The initializer switch moved into
  a private constructToken.
- createTokenMapUsingConfigDetails mirrors a refreshed token into its
  rebuilt map. The two trimmed-config gates now only skip known base
  chains; known tokens flow through the refresh path.

Verification:
Statics unit suite: 39324 passing, 0 failing. eslint on the changed
files: 0 errors. New replace tests cover a matching swap with index
lookups by name, id and contract address, and keeping the existing
record on a differing symbol, contract address, alias or id and for base
coins. The old precedence tests were rewritten to the new contract and
the AMS fixture suite restores the global coin map afterwards.

Ticket: CSHLD-863
problem/goal: verifyAccountConfigOperations compared only setFlags,
clearFlags, and lowThreshold. A rebuilt tx resubmitted for approval
could keep those identical while adding masterWeight, medThreshold,
highThreshold, signer, homeDomain, or inflationDest; the wallet
co-signers satisfy highThreshold automatically, so an injected
signer:{attacker,weight:3} would execute under an unchanged approval
and grant the attacker full control of the account.

implementation details: verifyAccountConfigOperations now rejects any
setOptions op carrying a field outside {setFlags, clearFlags,
lowThreshold} — the platform build layer never produces those fields,
so any occurrence is rejected on sight instead of comparing old vs
new values (which could pass if both sides carried the field).

verification: npx mocha test/unit/xlm.ts --grep accountConfig passes
(25 tests, including a new case injecting signer/masterWeight/
medThreshold/highThreshold/homeDomain alongside a matching
lowThreshold and asserting rejection).

Ticket: CSHLD-1925
chore(root): publish modules
Add RecoveryProviderError alongside ErrorNoInputToRecover and retype
empty-wallet throws and provider-failure paths across UTXO, EVM, XLM,
 EOS, SOL, and IOTA so a recovery scanner can distinguish the two by type

Ticket: WCN-2892
feat: honest empty vs provider-failed in recover()
…s-indication-for-login-password-rotation

feat(sdk-api,sdk-core): add password rotation progress callback with truthful keychain total
…messages

feat(sdk-core): sign EdDSA safe MPS VRF keygen messages like the HSM
This is a follow-up to PR #9932
(#9932)
which introduced honest "empty vs provider-failed" classification in
recover(). This commit closes the loop by applying the same fix to all
17 remaining coin modules (and their abstract bases) that still
conflated the two.

Key changes across SUI, ADA, NEAR, POLYX, DOT, Substrate base, Cosmos
base (and subclasses), XTZ, ICP, VET, HBAR, TRX, ALGO, TON, RUNE, STX,
and XRP:
- Retype verifiably-empty throws to ErrorNoInputToRecover
- Wrap provider query boundaries in RecoveryProviderError
- Convert scan loop empty string-match checks to ErrorNoInputToRecover
- Add type-based negative assertions and provider-failure tests per coin

Ticket: WCN-3036
problem/goal: parity gap with the wallet-platform round-2 review fixes
(CSHLD-1925): the platform re-approval gate now rejects per-op source
redirects, envelope fee swaps, and timeBounds swaps, but the SDK's
unsupported setOptions field list omitted op-level source — a rebuilt
tx with source:<attacker> would still pass verifyAccountConfigOperations
while every verified field matched.

implementation details: 'source' joins the unsupported setOptions
fields rejected on sight; the lowThreshold error JSON-stringifies the
got-value so wrong-typed input is distinguishable from the valid
number.

verification: npx mocha test/unit/xlm.ts --grep accountConfig passes
(25 tests, including the new op-source injection case); tsc --noEmit
clean. Note: fee/timeBounds parity does not map to this layer — verify
compares tx-vs-params intent, not old-vs-new txs; the platform
re-approval gate owns that comparison.

Ticket: CSHLD-1925
…into-generate-wallet-with-external-signer

feat(sdk-core): wire eddsaMPCv2Callbacks into generateWalletWithExternalSigner
…und2share

feat(sdk-core): add createOfflineKeyGenRound2Share to EddsaMPCv2Utils
prajwalu142 and others added 27 commits October 12, 2026 15:49
feat: PR from asset-metadata-service for token onboardings
Add createOfflineKeyGenFinalizeShare to EddsaMPCv2Utils in sdk-core,
completing the three offline signer methods for the EdDSA MPCv2
external-signer key generation flow (WCI-888 plan).

The method decrypts the persisted round-2 session, restores the DKG
state, reconstructs the own round-2 message from the persisted payload,
verifies and deserializes the incoming BitGo and counterParty round-2
messages via MPSComms.verifyMpsMessage, feeds all three into
dkg.handleIncomingMessages (asserting no output messages remain),
asserts dkg.getCommonKeychain() matches the bitgoCommonKeychain
returned by WP round 2 (commonPublicKeychain), and returns
{ commonKeychain, keyShare } with the full key share base64-encoded.

The round-2 session blob is bound to the caller via validateAdata
before decryption: the optional partyId param (defaulting to USER,
matching the other keygen methods) is validated against the envelope's
adata, so a blob encrypted for the other source fails loudly instead
of silently finalizing the DKG as the wrong party.

Note: unlike the WCI-891 draft signature, this method does not take
encryptedUserGpgPrvKey — finalize only verifies peer messages with
their public keys and never signs, so the GPG private key is never
decrypted on this path.

Depends on the async DKG.restoreSession (WASM load) and
createOfflineKeyGenRound2Share from the stacked WCI-890 branch.

Ticket: WCI-891
…und2share

feat(sdk-core): add createOfflineKeyGenFinalizeShare to EddsaMPCv2Utils (reland)
Structured-params surface (Option B): V1PaymentBuilder builds a v1
(SIMD-0296/0385) native SOL payment envelope from sender + recipients/amounts
— the caller supplies fields, never instructions. Named after the payment
intent it serves (WP's 'transfer' refers to tokens). Inherits AdvanceNonceAccount
injection, message compile, and message-signer-order signature assembly from
V1TransactionBuilder. Exposed via TransactionBuilderFactory.getV1PaymentBuilder().

Refs: CHALO-1603
…builder

feat(sdk-coin-sol): V1TransferBuilder for server-side v1 transfers
The ERC-7984 wrapper mints floor(amount / rate()) and reverts only when
that quotient overflows uint64. assertAmountTimesRateFitsUint64 instead
rejected amounts whose amount * rate product exceeded uint64, so
buildWrapCalldata and Erc7984Token.verifyWrapTransaction refused to wrap
anything above ~1.8e-8 tokens for rate > 1 pairs (hteth:ctest1, rate
1e12). Rate 1 pairs were unaffected because both bounds coincide there.

Rename the helper to assertWrapAmountFitsUint64, switch it to
floor(amount / rate), and correct the doc comments. Tests cover 1 full
18-decimal token at rate 1e12 and the exact uint64 boundary.

Ticket: CHALO-1793
problem/goal: preBuildTransaction picks build params with
buildParamKeys from the BuildParams codec before POSTing to the
platform's /tx/build. The codec whitelisted the other XLM
accountConfig keys (type, flags, clearFlags, trustlines) but not
lowThreshold, so a client-side lowThreshold change was stripped before
the request left the SDK and the platform's coin-scoped validation
never saw it.

implementation details: add lowThreshold: t.unknown to the BuildParams
codec next to the other XLM accountConfig keys. The platform pins it
to exactly 2 and merges the coin-scoped whitelist on /tx/build
(wallet-platform CSHLD-1925).

verification: npx mocha test/unit/bitgo/wallet/BuildParams.ts passes
(9 tests, including a new case asserting lowThreshold survives encode
while unknown params are stripped).

Ticket: CSHLD-1925
…params-codec

CSHLD-1925 fix(sdk-core): pass lowThreshold through the client BuildParams codec
Go Accounts needs the OFC version of teuroc, the test ERC-20
counterpart of euroc, so test environments can mirror the existing
production euroc OFC support. Adds a tofcerc20 definition for
ofcteuroc backed by teuroc with 6 decimals, matching the existing
teuroc terc20 and ofceuroc mainnet OFC definitions.

Ticket: COINS-1796
Session-Id: cffd4af2-44bc-43d6-aeb2-4fea5b9f84e2
Task-Id: d7b1e8fc-9c40-49e9-b487-ea9e48d46823
problem/goal
Onboard JPY Coin (JPYC) testnet ERC-20 tokens on Sepolia (sepeth:jpyc) and
Polygon Amoy (tpolygon:jpyc) plus their OFC variants under ticket CECHO-2428.
Per JPYC's official docs the testnet contract address is the same as mainnet
on both networks.

implementation details
- Added 'sepeth:jpyc' and 'tpolygon:jpyc' to UnderlyingAsset enum in base.ts.
- Registered 'sepeth:jpyc' (name 'JPY Coin', 18 decimals, contract
  0xe7c3d8c9a439fede00d2600032d5db0be71c3c29) on Sepolia in erc20Coins.ts
  with ACCOUNT_COIN_DEFAULT_FEATURES.
- Registered 'tpolygon:jpyc' (name 'JPY Coin', 18 decimals, same contract) in
  polygonTokens.ts with POLYGON_TOKEN_FEATURES, mirroring mainnet
  polygon:jpyc.
- Registered 'ofcsepeth:jpyc' and 'ofctpolygon:jpyc' OFC tokens in
  ofcErc20Coins.ts and ofcCoins.ts.
- Added 'sepeth:jpyc' to testnetPrefixExceptions in
  tokenNamingConvention.ts and 'ofctpolygon:jpyc' to knownExceptions in
  ofcTokenNaming.ts to comply with naming conventions (fullName mirrors
  mainnet, matching recent onboarding precedent).

verification- how it was verified
- Verified contract on Sepolia via sepolia.etherscan.io: JPY Coin (JPYC),
  18 decimals; Amoy deployment confirmed via polygonscan token page.
- UUID uniqueness checked across the repository for all four registrations.
- yarn run unit-test --scope @bitgo/statics: 39,686 passing, 0 failing.
- eslint on all changed files: 0 errors.
- Registry smoke test via tsx: all 4 tokens resolve in statics CoinMap with
  decimals 18 and testnet network type.

Ticket: CECHO-2428
The ERC-7984 shield approve leg is signed through the chain coin, whose
generic checks never decode calldata, so nothing client-side confirmed
spender == wrapper and amount == the entered amount before signing.

Add decodeApproveCalldata (strict: approve selector, exactly 68 bytes)
and, for type wrapApprove, have verifyTssTransaction require that the tx
targets the pair's underlying ERC-20 with zero value and carries
approve(wrapper, wrapParams.amount). Any deviation, a missing wrapParams,
or an unregistered pair fails closed.

Replace the wrapApprove test that used txHex 0x (no calldata checked)
with cases for a valid approve, wrong spender, mutated amount, non-approve
selector, trailing bytes, wrong target and missing wrapParams.

Ticket: CHALO-1794

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…kens-to-onboard

feat(statics): onboard jpyc testnet tokens on sepolia and polygon amoy
feat(statics): onboard ofcteuroc testnet OFC coin
…ields

feat: allow updating already-registered coins via replaceExistingCoin
AllocationFactory_Allocate for native Canton Coin (Amulet) creates an
AmuletAllocation, not a DvpLegAllocation or AllocationV2, so the parser
found no sender, receiver or amount and failed. Read the identical
allocation.transferLeg record from AmuletAllocation too.

Also make the native check exact (instrument id Amulet administered by
a DSO party) instead of a substring match on admin:id, so an unrelated
token whose name contains "Amulet" is no longer parsed as the base
coin, and throw a clear error for an unsupported instrument instead of
a TypeError.

Ticket: CHALO-1775

Co-Authored-By: Claude <noreply@anthropic.com>
Add a native AllocationFactory_Allocate prepared transaction captured on
BitGo testnet and cover it in the parser and the allocation allocate
builder: sender, receiver and amount from AmuletAllocation, no token, and
base-coin inputs and outputs.

Ticket: CHALO-1775

Co-Authored-By: Claude <noreply@anthropic.com>
The builder's setTransaction does not populate inputs and outputs, so
the native base-coin assertion read an undefined entry. Call
loadInputsAndOutputs first, as the raw-parse path does.

Ticket: CHALO-1775

Co-Authored-By: Claude <noreply@anthropic.com>
The RealFi issuer renamed its Cardano stablecoin pair: "RealFi USDr"
is now "RealFi USDrf" and "RealFi sUSDr" is now "RealFi sUSDrf".
BitGoJS still displayed the old names, so wallets showed a stale
product name to customers holding these tokens.

Update the fullName display metadata for every statics definition of
the renamed tokens: ada:usdr and ada:susdr, their testnet variants
tada:usdr and tada:susdr, and the OFC wrappers ofcada:usdr,
ofcada:susdr, ofctada:usdr and ofctada:susdr. Also update the staking
suite's describe label. Coin names (ada:usdr), symbols (USDr), policy
IDs, asset names and decimals are unchanged, so wallet addresses,
balances and API integrations are unaffected.

Ticket: CSHLD-1936
Session-Id: 9a5c6829-2548-45ab-9949-9aac35530c95
Task-Id: 8db036e4-fb87-4f29-a568-ebd0db826602
Follow-up to the wrapApprove calldata assertion (#9949).

1. pendingApproval re-sign (recreateTxRequest -> signTxRequest) carries no
   txParams, so resolveEffectiveTxParams built them from the persisted
   intent without wrapParams and verifyWrapApproveTransaction rejected
   valid approve calldata. Derive wrapParams from the wrapApprove / wrap
   intent's tokenName and amount, the same way stakingRequestId is
   propagated.

2. The assertion only ran when the declared type was wrapApprove, and on
   the re-sign path that type is server-persisted, so a compromised WP
   could relabel a mutated approve(wrapper, amount) as another type. Force
   any approve(address,uint256) sent to a registered ERC-7984 underlying
   whose spender is that pair's wrapper through the wrapApprove check.
   Approvals to other spenders and every other call are untouched. A hex
   without the approve selector returns before any parse, and a tx the
   builder cannot parse is skipped so no new failure is introduced.

Also route the wrapApprove precondition failures through
TxIntentMismatchRecipientError and test the native-value branch.

Ticket: CHALO-1794
fix(statics): rename RealFi USDr tokens to USDrf
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Unit tests are failing on Node 26.x (Current release line, non-blocking). This is not an LTS version yet, so it does not block merge, but it signals an incompatibility to fix before Node 26.x becomes LTS.

View run

This branch was previously deployed

1 inactive deployment
beta-alpha-release — 704f4de1 Deployed Oct 13, 2026 by prajwalu142 via Publish Release #3139
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.