Skip to content

SPIKE: Protocol 27 — CAP-71 + CAP-83 XDR regen#972

Draft
sisuresh wants to merge 4 commits into
stellar:masterfrom
sisuresh:cap-71-p27
Draft

SPIKE: Protocol 27 — CAP-71 + CAP-83 XDR regen#972
sisuresh wants to merge 4 commits into
stellar:masterfrom
sisuresh:cap-71-p27

Conversation

@sisuresh
Copy link
Copy Markdown

@sisuresh sisuresh commented May 28, 2026

⚠️ This is an experiment, not a real protocol-bump PR. It was opened from a pod-fsr#33 spike to render live CAP-71 / CAP-83 (Protocol 27) transactions in stellar-laboratory against a local Quickstart Protocol 27 build. It is not intended to be merged as-is — it bundles several workarounds that should be replaced by proper upstream fixes before merging. Filed as a reference point for the workflow.

What this changes

  • Makefile: bump XDR_BASE_URL_{CURR,NEXT} to 5187e69 (the Protocol 27 .x with CAP-71 + CAP-83). Add a `stellar-xdr xfile preprocess --features CAP_0071,CAP_0083` step before the Ruby xdrgen invocation, because Ruby xdrgen does not parse `#ifdef CAP_` directives — stellar/rs-stellar-xdr#503 is the canonical preprocessor. Drop `docker run -it` (breaks non-TTY). Switch the dts-xdr container from `node:alpine` to `node:lts-alpine` and `apk add yarn` (newer alpine no longer bundles yarn).

  • Source post-process (the experimental workaround): inline each `xdr.const("", )` value at every bare-identifier usage site (`xdr.string(SCSYMBOL_LIMIT)` → `xdr.string(32)`). xdrgen master no longer emits the `var = ;` declarations the released v15.0.0 had, and `@stellar/js-xdr`'s `TypeBuilder.const()` doesn't inject the identifier into the calling scope. The obvious patch (re-inject the `var` decls) works in unminified dev builds but terser tree-shakes them out of the production browser dist, leaving bare identifiers at runtime. Inlining the literal is the only fix that survives minification.

  • Regenerated `src/generated/{curr,next}_generated.js` + `types/curr.d.ts` with CAP-71-02 (`SOROBAN_CREDENTIALS_ADDRESS_V2`, `HashIdPreimageSorobanAuthorizationWithAddress`), CAP-71-01 (`SorobanAddressCredentialsWithDelegates`, `SorobanDelegateSignature`), and CAP-83 (`STELLAR_VALUE_EMPTY_TX_SET`).

Verification

Decoded a live CAP-71-01 envelope all the way through `xdr.TransactionEnvelope.fromXDR` → `sorobanCredentialsAddressWithDelegates` → root contract address → 1 `SorobanDelegateSignature` → `scvVec` delegate sig, in both Node and the browser-bundled (terser-minified) `dist/stellar-base.min.js`. Then end-to-end through js-stellar-sdklaboratory to render the transaction view in lab.

Why this should not be merged as-is

  • The inline-const post-process is a downstream workaround for an upstream issue in xdrgen master + `@stellar/js-xdr`. The real fix is in those tools: either xdrgen should re-emit the explicit `var` declarations the v15.0.0 release had, or `TypeBuilder.const()` should attach the identifier to a scope terser can see (e.g., `globalThis`). This PR's regen pipeline is a stopgap.
  • The Makefile container swap (`node:alpine` → `node:lts-alpine`) is incidental; a proper fix would pin a specific tag.
  • The XDR commit (`5187e69`) is a pre-release stellar-xdr SHA; the eventual real bump would land after Protocol 27 freeze.

Related experiments

Source: pod-fsr#33 — full-stack CAP spike workflow exploration.

sisuresh added 2 commits May 27, 2026 22:36
- Bump XDR_BASE_URL_{CURR,NEXT} to 5187e69 (CAP-71+CAP-83 .x).
- Add 'stellar-xdr xfile preprocess --features CAP_0071,CAP_0083' step
  before Ruby xdrgen (Ruby xdrgen does not parse #ifdef; rs-stellar-xdr stellar#503
  is the canonical preprocessor).
- Drop 'docker run -it' for CI-friendliness; switch dts-xdr base to
  node:lts-alpine + install yarn.
- Regenerated src/generated/curr_generated.js + next_generated.js +
  types/curr.d.ts with CAP-71-02 (SOROBAN_CREDENTIALS_ADDRESS_V2,
  HashIdPreimageSorobanAuthorizationWithAddress), CAP-71-01
  (SorobanAddressCredentialsWithDelegates, SorobanDelegateSignature),
  and CAP-83 (STELLAR_VALUE_EMPTY_TX_SET).

Used to e2e-test the CAP-71 tx rendering in stellar-laboratory.
The xdrgen master JS generator no longer emits explicit `var` decls for
xdr.const, but @stellar/js-xdr's TypeBuilder.const() does not inject the
identifier into the calling scope. Bare references like
`xdr.string(SCSYMBOL_LIMIT)` then ReferenceError at runtime.

This commit re-injects them in src/generated/{curr,next}_generated.js and
documents the post-process step in the Makefile. Restores the runtime
behavior of the released v15.0.0 (which had explicit `var` decls in its
published output).
sisuresh added 2 commits May 28, 2026 13:15
The prior commit (4e362da) injected 'var <NAME> = <value>;' at the top of
each generated IIFE so xdrgen master's bare-identifier 'xdr.string(NAME)'
pattern would resolve. That works in unminified dev builds but terser DCE
strips the var declarations from the production browser dist, leaving the
bare identifiers unbound (ReferenceError: SCSYMBOL_LIMIT is not defined
inside dist/stellar-base.min.js at runtime).

This commit:

1. Moves the post-process from an unreadable Makefile one-liner to
   scripts/post-process-generated.py.

2. Switches the approach: instead of injecting 17 var declarations
   (which terser drops), substitute every bare-identifier usage with
   the literal value (xdr.string(SCSYMBOL_LIMIT) -> xdr.string(32)).
   There's no identifier for terser to drop.

3. As a side effect, the diff against upstream/master shrinks: only
   constants that are actually referenced as bare identifiers
   downstream (SCSYMBOL_LIMIT, SC_SPEC_DOC_LIMIT) appear in the diff
   now. Constants like MASK_ACCOUNT_FLAGS that are only referenced via
   xdr.lookup("NAME") string lookups stay untouched.

Long-term fix belongs upstream — either xdrgen should re-emit explicit
var declarations the way the v15.0.0 release did, or
@stellar/js-xdr's TypeBuilder.const() should attach the identifier to
a scope terser can see. This post-process is a stopgap.
…er uses

Looking at upstream/master's curr_generated.js, the maintainers patch
the file by hand after every regen with:

  // Workaround for stellar/xdrgen#152
  const SCSYMBOL_LIMIT = 32;
  const SC_SPEC_DOC_LIMIT = 1024;

inserted at the top of the IIFE. The bare 'xdr.string(SCSYMBOL_LIMIT)'
patterns xdrgen master emits then resolve to those IIFE-scope const
declarations at runtime. This is the same pattern upstream uses; the
inline-consts post-process I added in d00b392 was unnecessary scope.

This commit:

1. Drops scripts/post-process-generated.py (added in d00b392).
2. Restores the Makefile to just curl + 'stellar-xdr xfile preprocess'
   (no post-process-generated target).
3. Regenerates src/generated/{curr,next}_generated.js cleanly and
   inserts the 2-line workaround block at the IIFE top, mirroring
   upstream master.

Net effect: the PR diff shrinks substantially. The only changes beyond
the CAP-71+83 XDR types are now:
- Makefile: bump XDR_BASE_URL_{CURR,NEXT}, add 'stellar-xdr xfile
  preprocess' step before xdrgen, drop 'docker run -it', switch
  dts-xdr container to node:lts-alpine + apk add yarn.
- The 2-line xdrgen#152 workaround block (mirroring upstream).
Comment on lines 597 to +600
case SOROBAN_CREDENTIALS_ADDRESS:
SorobanAddressCredentials address;
case SOROBAN_CREDENTIALS_ADDRESS_V2:
SorobanAddressCredentials addressV2;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm confused about what makes V2 actually a V2 if it's using the exact same struct type for that branch. Is this a generator bug?

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