Skip to content

Commit 0336c41

Browse files
authored
feat(contract): add occurrence param to Spec.findEvent (#1574)
1 parent 22b013a commit 0336c41

4 files changed

Lines changed: 66 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ A breaking change will get clearly marked in this log.
1010
- `authorizeEntry` / `authorizeInvocation` signing callbacks now receive the 32-byte signing payload (`hash(preimage.toXDR())`) as a second argument alongside the preimage, so signers — including HSMs and remote signers that only accept a digest — never have to re-derive it. Existing single-argument callbacks are unaffected ([#1532](https://github.com/stellar/js-stellar-sdk/issues/1532)).
1111
- `authorizeEntry` / `authorizeInvocation` now support non-Ed25519 signers: the signing callback may return `{ signatureScVal: xdr.ScVal, address?: string }`, and the given `ScVal` is written verbatim as the credentials' signature — no Ed25519 verification, no `{public_key, signature}` map, no `scvVec` wrapping. This lets smart-wallet / custom-account contracts (whose `__check_auth` expects its own signature structure) use the helper instead of hand-rolling preimage construction and credential assembly. The optional `address` routes the signature to a specific credential node, like `forAddress` ([#1530](https://github.com/stellar/js-stellar-sdk/issues/1530)).
1212
- `contract.Signer`: an interface pairing an `address` with the SEP-43 `signTransaction` and optional `signAuthEntry` methods, plus `contract.KeypairSigner`, a `Keypair`-backed implementation. The `signTransaction` and `signAuthEntry` options — on `ClientOptions`, `MethodOptions`, and `AssembledTransaction`'s `sign` / `signAndSend` / `signAuthEntries` — now accept a `Signer` or a bare `Keypair` in addition to a callback. Adds the `contract.SignTransactionLike` and `contract.SignAuthEntryLike` types. When `signAuthEntries` gets a `Signer` or `Keypair`, its default target `address` is now the signer's own address rather than `publicKey`. Existing callbacks work unchanged; one type-only caveat: the option fields are no longer plain function types, so derive callback shapes from `contract.SignTransaction` / `contract.SignAuthEntry` instead of the option field ([#1567](https://github.com/stellar/js-stellar-sdk/pull/1567), closes [#1462](https://github.com/stellar/js-stellar-sdk/issues/1462) and [#1063](https://github.com/stellar/js-stellar-sdk/issues/1063)).
13-
- `contract.Spec` now reads SEP-48 event declarations: `events()` and `findEvent(name)` list a contract's declared events, `parseEvent(topics, data)` decodes a fired event into `{ name, data }`, with topic-carried params merged into `data` (returns `undefined` when nothing matches), and `eventTopicFilter(name, topicValues?)` builds a `getEvents` filter row, with `"*"` for any topic param left unset. Generated client bindings gain a typed `<Name>Event` interface per event, a `ContractEvent` union, a `parseEvent()` method, and per-event `<name>EventFilter()` methods. Generated helper names receive a numeric suffix when needed to avoid a contract-method collision. Adds the `contract.ParsedEvent` type ([#1556](https://github.com/stellar/js-stellar-sdk/pull/1556), [#1565](https://github.com/stellar/js-stellar-sdk/pull/1565)).
13+
- `contract.Spec` now reads SEP-48 event declarations: `events()` and `findEvent(name, occurrence?)` list a contract's declared events, `parseEvent(topics, data)` decodes a fired event into `{ name, data }`, with topic-carried params merged into `data` (returns `undefined` when nothing matches), and `eventTopicFilter(name, topicValues?, occurrence?)` builds a `getEvents` filter row, with `"*"` for any topic param left unset. Generated client bindings gain a typed `<Name>Event` interface per event, a `ContractEvent` union, a `parseEvent()` method, and per-event `<name>EventFilter()` methods. A contract may declare the same event name more than once (composed modules each emitting their own `transfer`); each declaration gets its own interface and filter method, and `occurrence` — a 0-based index in declaration order — selects among them. Generated names receive a numeric suffix when needed to avoid a collision, and `stellar-sdk bindings` warns about duplicate declarations and renames. Adds the `contract.ParsedEvent` type ([#1556](https://github.com/stellar/js-stellar-sdk/pull/1556), [#1565](https://github.com/stellar/js-stellar-sdk/pull/1565), [#1572](https://github.com/stellar/js-stellar-sdk/pull/1572)).
1414

1515
### Fixed
1616
- `Spec.scValToNative` now handles contract values typed as `Val`

docs/reference/contracts-client.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,7 @@ class Spec {
10601060
events(): ScSpecEventV0[];
10611061
eventTopicFilter(name: string, topicValues?: Record<string, any>, occurrence?: number): string[];
10621062
findEntry(name: string): ScSpecEntry;
1063-
findEvent(name: string): ScSpecEventV0 | undefined;
1063+
findEvent(name: string, occurrence?: number): ScSpecEventV0 | undefined;
10641064
funcArgsToScVals(name: string, args: object): ScVal[];
10651065
funcResToNative(name: string, val_or_base64: string | ScVal): any;
10661066
funcs(): ScSpecFunctionV0[];
@@ -1219,7 +1219,7 @@ a single topic filter row
12191219
const topics = contractSpec.eventTopicFilter('transfer', { to: someAddress });
12201220
```
12211221

1222-
**Source:** [src/contract/spec.ts:1307](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L1307)
1222+
**Source:** [src/contract/spec.ts:1312](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L1312)
12231223

12241224
### `spec.findEntry(name)`
12251225

@@ -1243,7 +1243,7 @@ the entry
12431243

12441244
**Source:** [src/contract/spec.ts:658](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L658)
12451245

1246-
### `spec.findEvent(name)`
1246+
### `spec.findEvent(name, occurrence)`
12471247

12481248
Finds the XDR event spec for the given event name.
12491249

@@ -1252,17 +1252,24 @@ returns `undefined` so callers can probe a contract for an event without
12521252
wrapping the call in a `try`.
12531253

12541254
```ts
1255-
findEvent(name: string): ScSpecEventV0 | undefined;
1255+
findEvent(name: string, occurrence?: number): ScSpecEventV0 | undefined;
12561256
```
12571257

12581258
**Parameters**
12591259

12601260
- **`name`**`string` (required) — the name of the event
1261+
- **`occurrence`**`number` (optional) — (optional) 0-based index among same-named events, in
1262+
declaration order, for contracts that declare the same event name
1263+
more than once (defaults to the first)
12611264

12621265
**Returns**
12631266

12641267
the event spec, or `undefined` if the contract declares no event
1265-
with that name
1268+
with that name (at that occurrence)
1269+
1270+
**Throws**
1271+
1272+
- if `occurrence` is not a non-negative integer
12661273

12671274
**Example**
12681275

@@ -1272,7 +1279,7 @@ if (contractSpec.findEvent("transfer")) {
12721279
}
12731280
```
12741281

1275-
**Source:** [src/contract/spec.ts:1246](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L1246)
1282+
**Source:** [src/contract/spec.ts:1251](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L1251)
12761283

12771284
### `spec.funcArgsToScVals(name, args)`
12781285

@@ -1395,7 +1402,7 @@ the converted JSON schema
13951402

13961403
- if the contract spec is invalid
13971404

1398-
**Source:** [src/contract/spec.ts:1331](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L1331)
1405+
**Source:** [src/contract/spec.ts:1336](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L1336)
13991406

14001407
### `spec.nativeToScVal(val, ty)`
14011408

@@ -1459,7 +1466,7 @@ if (parsed) {
14591466
}
14601467
```
14611468

1462-
**Source:** [src/contract/spec.ts:1278](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L1278)
1469+
**Source:** [src/contract/spec.ts:1283](https://github.com/stellar/js-stellar-sdk/blob/main/src/contract/spec.ts#L1283)
14631470

14641471
### `spec.scValStrToNative(scv, typeDef)`
14651472

src/contract/spec.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,8 +1233,13 @@ export class Spec {
12331233
* wrapping the call in a `try`.
12341234
*
12351235
* @param name - the name of the event
1236+
* @param occurrence - (optional) 0-based index among same-named events, in
1237+
* declaration order, for contracts that declare the same event name
1238+
* more than once (defaults to the first)
12361239
* @returns the event spec, or `undefined` if the contract declares no event
1237-
* with that name
1240+
* with that name (at that occurrence)
1241+
*
1242+
* @throws if `occurrence` is not a non-negative integer
12381243
*
12391244
* @example
12401245
* ```ts
@@ -1243,8 +1248,8 @@ export class Spec {
12431248
* }
12441249
* ```
12451250
*/
1246-
findEvent(name: string): xdr.ScSpecEventV0 | undefined {
1247-
return findEventImpl(this.entries, name);
1251+
findEvent(name: string, occurrence?: number): xdr.ScSpecEventV0 | undefined {
1252+
return findEventImpl(this.entries, name, occurrence);
12481253
}
12491254

12501255
/**

test/unit/spec/event_spec.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,48 @@ describe("Spec events", () => {
8181
expect(() => spec.eventTopicFilter("nope")).toThrow(/no such event: nope/);
8282
});
8383

84+
it("findEvent selects among same-named events by occurrence", () => {
85+
// Composed contracts can declare the same event name more than once, with
86+
// different params; `occurrence` picks one in declaration order.
87+
const first = new xdr.ScSpecEventV0({
88+
doc: "",
89+
lib: "",
90+
name: "transfer",
91+
prefixTopics: ["transfer"],
92+
params: [param("amount", i128Type, DATA)],
93+
dataFormat: xdr.ScSpecEventDataFormat.scSpecEventDataFormatSingleValue(),
94+
});
95+
const second = new xdr.ScSpecEventV0({
96+
doc: "",
97+
lib: "",
98+
name: "transfer",
99+
prefixTopics: ["transfer"],
100+
params: [param("count", u32Type, DATA)],
101+
dataFormat: xdr.ScSpecEventDataFormat.scSpecEventDataFormatSingleValue(),
102+
});
103+
const spec = new Spec([entryFor(first), entryFor(second)]);
104+
105+
// No occurrence means the first declaration, as before.
106+
expect(spec.findEvent("transfer")?.params()[0].name().toString()).toBe(
107+
"amount",
108+
);
109+
expect(spec.findEvent("transfer", 0)?.params()[0].name().toString()).toBe(
110+
"amount",
111+
);
112+
expect(spec.findEvent("transfer", 1)?.params()[0].name().toString()).toBe(
113+
"count",
114+
);
115+
116+
// Past the last declaration is a miss, not an error — same as an
117+
// undeclared name.
118+
expect(spec.findEvent("transfer", 2)).toBeUndefined();
119+
120+
// A nonsensical occurrence is a caller bug, so it throws rather than
121+
// quietly returning undefined.
122+
expect(() => spec.findEvent("transfer", -1)).toThrow(/invalid occurrence/);
123+
expect(() => spec.findEvent("transfer", 1.5)).toThrow(/invalid occurrence/);
124+
});
125+
84126
it("parses singleValue data format events, round-tripping natives", () => {
85127
const event = new xdr.ScSpecEventV0({
86128
doc: "",

0 commit comments

Comments
 (0)