Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ A breaking change will get clearly marked in this log.
## Unreleased

### Added
- Protocol 28 (CAP-0085): externally managed contract executables. The vendored `next`-channel XDR now includes the `SCV_EXECUTABLE_TAG` `SCVal` arm, the `CONTRACT_EXECUTABLE_EXTERNAL_REF` `ContractExecutable` variant, and the `ContractExecutableExternalRef` struct. Encode/decode support is via the generated codec; no new hand-written SDK helpers were added.
- `rpc.Server.queryContract<T>(contractId, method, args?, networkPassphrase?)`: a one-line read-only contract call that builds a client, simulates the method, and returns `{ result, isReadCall }` — the spec-decoded return value plus whether this specific call is a signature-free read that wrote no state (per-call, reflecting the given `args`). No manual transaction assembly, signing, or submission. Works for both Wasm contracts and built-in Stellar Asset Contracts (SACs) ([#1502](https://github.com/stellar/js-stellar-sdk/pull/1502)).
- `rpc.Server.getContractMethods(contractId, networkPassphrase?)`: lists a contract's callable methods and their signatures (name, inputs, outputs, and doc string) for discovery and tooling, without invoking or simulating anything. Adds the `Api.ContractMethod` and `Api.ContractMethodInput` types ([#1502](https://github.com/stellar/js-stellar-sdk/pull/1502)).
- `rpc.Server.getContractInstance(contractId)`: returns a contract's `xdr.ScContractInstance` (its executable and instance storage) ([#1501](https://github.com/stellar/js-stellar-sdk/pull/1501)).
Expand Down
17 changes: 14 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Protocol 28 / CAP-0085 (externally managed contract executables) pre-release.
# The `next` channel pins stellar-xdr#308 head (76218a99) and enables
# CAP_0085_EXECUTABLE_REF via `stellar-xdr xfile preprocess` before xdrgen
# (the Ruby xdrgen used here does not understand #ifdef). CAP_0085_EXECUTABLE_REF
# is gated to `next` only: the new SCVal/ContractExecutable arms are not enabled
# on `curr` until protocol 28 ships, so `curr` deliberately stays pinned at
# 68fa1ac5 with no features.
XDR_BASE_URL_CURR=https://github.com/stellar/stellar-xdr/raw/68fa1ac55692f68ad2a2ca549d0a283273554439
XDR_BASE_LOCAL_CURR=xdr/curr
XDR_FEATURES_CURR=
XDR_FILES_CURR= \
Stellar-SCP.x \
Stellar-ledger-entries.x \
Expand All @@ -15,8 +23,9 @@ XDR_FILES_CURR= \
Stellar-exporter.x
XDR_FILES_LOCAL_CURR=$(addprefix xdr/curr/,$(XDR_FILES_CURR))

XDR_BASE_URL_NEXT=https://github.com/stellar/stellar-xdr/raw/68fa1ac55692f68ad2a2ca549d0a283273554439
XDR_BASE_URL_NEXT=https://github.com/stellar/stellar-xdr/raw/76218a994f8c5ba752cba368080fb2f89843ad3c
XDR_BASE_LOCAL_NEXT=xdr/next
XDR_FEATURES_NEXT=CAP_0085_EXECUTABLE_REF
XDR_FILES_NEXT= \
Stellar-SCP.x \
Stellar-ledger-entries.x \
Expand Down Expand Up @@ -48,6 +57,7 @@ src/base/generated/curr_generated.js: $(XDR_FILES_LOCAL_CURR)
gem specific_install https://github.com/stellar/xdrgen.git -b $(XDRGEN_COMMIT) && \
xdrgen --language javascript --namespace curr --output src/base/generated $^ \
'
python3 scripts/post-process-generated.py $@

src/base/generated/next_generated.js: $(XDR_FILES_LOCAL_NEXT)
mkdir -p $(dir $@)
Expand All @@ -57,6 +67,7 @@ src/base/generated/next_generated.js: $(XDR_FILES_LOCAL_NEXT)
gem specific_install https://github.com/stellar/xdrgen.git -b $(XDRGEN_COMMIT) && \
xdrgen --language javascript --namespace next --output src/base/generated $^ \
'
python3 scripts/post-process-generated.py $@

src/base/generated/curr.d.ts: src/base/generated/curr_generated.js
docker run -it --rm -v $$PWD:/wd -w / --entrypoint /bin/sh node:22-alpine -c '\
Expand Down Expand Up @@ -92,12 +103,12 @@ clean:
$(XDR_FILES_LOCAL_CURR):
mkdir -p $(dir $@)
curl -L -o $@ $(XDR_BASE_URL_CURR)/$(notdir $@)
stellar-xdr xfile preprocess --features "$(XDR_FEATURES)" $@ > $@.pp && mv -f $@.pp $@
stellar-xdr xfile preprocess --features "$(XDR_FEATURES_CURR)" $@ > $@.pp && mv -f $@.pp $@

$(XDR_FILES_LOCAL_NEXT):
mkdir -p $(dir $@)
curl -L -o $@ $(XDR_BASE_URL_NEXT)/$(notdir $@)
stellar-xdr xfile preprocess --features "$(XDR_FEATURES)" $@ > $@.pp && mv -f $@.pp $@
stellar-xdr xfile preprocess --features "$(XDR_FEATURES_NEXT)" $@ > $@.pp && mv -f $@.pp $@
reset-xdr:
rm -f xdr/*/*.x
rm -f src/base/generated/*.js
Expand Down
54 changes: 54 additions & 0 deletions scripts/post-process-generated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Post-process xdrgen JS output to inline xdr.const values at usage sites.

xdrgen master emits `xdr.const("NAME", N);` to register a constant in the
xdr namespace, then uses the bare identifier later (`xdr.string(NAME)`).
But `@stellar/js-xdr`'s TypeBuilder.const() does not put NAME into JS scope,
so the bare identifier ReferenceErrors at runtime.

Injecting `var NAME = N;` at the IIFE top fixes runtime but gets DCE'd by
terser in the production browser dist. The robust fix is to inline the
literal at each usage site so there's no identifier for terser to drop.

The `xdr.const("NAME", N);` declaration itself is left untouched: the NAME
there is a string literal (preceded by a quote), so the negative lookbehind
below skips it. Constants only referenced via `xdr.lookup("NAME")` string
lookups are likewise untouched.
"""
import re
import pathlib
import sys


def inline_consts(path: pathlib.Path) -> int:
s = path.read_text()
consts = dict(re.findall(
r'xdr\.const\("([A-Z][A-Z0-9_]+)",\s*(0x[0-9a-fA-F]+|\d+)\);', s
))
n_replaced = 0
for name, value in consts.items():
# Replace bare identifier (not preceded by quote or word char,
# not followed by quote or word char). This skips string literals
# like "NAME" and xdr.lookup("NAME"), so the xdr.const(...)
# declaration's string name is preserved.
new_s, count = re.subn(
rf'(?<![\w"\'$]){re.escape(name)}(?![\w"\'$])',
value,
s,
)
if count > 0:
s = new_s
n_replaced += count
path.write_text(s)
return n_replaced


if __name__ == "__main__":
files = sys.argv[1:] or [
"src/base/generated/curr_generated.js",
"src/base/generated/next_generated.js",
]
for f in files:
p = pathlib.Path(f)
n = inline_consts(p)
print(f"{f}: inlined {n} bare-identifier const reference(s)")
68 changes: 63 additions & 5 deletions src/base/generated/next.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1975,7 +1975,8 @@ export namespace xdr {
| "scvAddress"
| "scvContractInstance"
| "scvLedgerKeyContractInstance"
| "scvLedgerKeyNonce";
| "scvLedgerKeyNonce"
| "scvExecutableTag";

readonly value:
| 0
Expand All @@ -1999,7 +2000,8 @@ export namespace xdr {
| 18
| 19
| 20
| 21;
| 21
| 22;

static scvBool(): ScValType;

Expand Down Expand Up @@ -2044,6 +2046,8 @@ export namespace xdr {
static scvLedgerKeyContractInstance(): ScValType;

static scvLedgerKeyNonce(): ScValType;

static scvExecutableTag(): ScValType;
}

class ScErrorType {
Expand Down Expand Up @@ -2119,13 +2123,18 @@ export namespace xdr {
}

class ContractExecutableType {
readonly name: "contractExecutableWasm" | "contractExecutableStellarAsset";
readonly name:
| "contractExecutableWasm"
| "contractExecutableStellarAsset"
| "contractExecutableExternalRef";

readonly value: 0 | 1;
readonly value: 0 | 1 | 2;

static contractExecutableWasm(): ContractExecutableType;

static contractExecutableStellarAsset(): ContractExecutableType;

static contractExecutableExternalRef(): ContractExecutableType;
}

class ScAddressType {
Expand Down Expand Up @@ -9508,6 +9517,43 @@ export namespace xdr {
static validateXDR(input: string, format: "hex" | "base64"): boolean;
}

class ContractExecutableExternalRef {
constructor(attributes: {
executableOwner: ScAddress;
tag: string | Buffer;
});

executableOwner(value?: ScAddress): ScAddress;

tag(value?: string | Buffer): string | Buffer;

toXDR(format?: "raw"): Buffer;

toXDR(format: "hex" | "base64"): string;

static read(io: Buffer): ContractExecutableExternalRef;

static write(value: ContractExecutableExternalRef, io: Buffer): void;

static isValid(value: ContractExecutableExternalRef): boolean;

static toXDR(value: ContractExecutableExternalRef): Buffer;

static fromXDR(
input: Buffer,
format?: "raw",
): ContractExecutableExternalRef;

static fromXDR(
input: string,
format: "hex" | "base64",
): ContractExecutableExternalRef;

static validateXDR(input: Buffer, format?: "raw"): boolean;

static validateXDR(input: string, format: "hex" | "base64"): boolean;
}

class ScNonceKey {
constructor(attributes: { nonce: Int64 });

Expand Down Expand Up @@ -15621,11 +15667,19 @@ export namespace xdr {

wasmHash(value?: Buffer): Buffer;

externalRef(
value?: ContractExecutableExternalRef,
): ContractExecutableExternalRef;

static contractExecutableWasm(value: Buffer): ContractExecutable;

static contractExecutableStellarAsset(): ContractExecutable;

value(): Buffer | void;
static contractExecutableExternalRef(
value: ContractExecutableExternalRef,
): ContractExecutable;

value(): Buffer | ContractExecutableExternalRef | void;

toXDR(format?: "raw"): Buffer;

Expand Down Expand Up @@ -15742,6 +15796,8 @@ export namespace xdr {

nonceKey(value?: ScNonceKey): ScNonceKey;

executableTag(value?: string | Buffer): string | Buffer;

static scvBool(value: boolean): ScVal;

static scvVoid(): ScVal;
Expand Down Expand Up @@ -15786,6 +15842,8 @@ export namespace xdr {

static scvLedgerKeyNonce(value: ScNonceKey): ScVal;

static scvExecutableTag(value: string | Buffer): ScVal;

value():
| boolean
| ScError
Expand Down
Loading
Loading