> For the complete documentation index, see [llms.txt](https://emeditweb.gitbook.io/pulsar-stellar-sdk/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://emeditweb.gitbook.io/pulsar-stellar-sdk/sdk-reference/contracts.md).

# Contracts and bindings

Two jobs live here. `buildContractCall` assembles an invocation. The `as*Event` family, with `parseTopics` and `scValToNative`, gives the call-oriented, binding-shaped view of an event, as opposed to the wire-faithful `DecodedEvent`. See [Wire-faithful vs call-oriented](/pulsar-stellar-sdk/concepts/wire-faithful-vs-call-oriented.md) for why both views exist.

## `buildContractCall(options)`

```ts
buildContractCall(options: ContractCallOptions): Transaction
```

Builds an unsigned, **unprepared** contract call transaction. It is synchronous and touches no network.

**The returned transaction is not submittable as it stands.** An unprepared Soroban transaction carries an empty footprint and only the base fee. It must go through `server.prepareTransaction(tx)` first, which runs the simulation that attaches the resource footprint and the real fee. The full flow is build, prepare, sign, submit:

```ts
import { buildContractCall } from '@pulsar-stellar/sdk';
import { Account, Address, Networks, nativeToScVal, rpc } from '@stellar/stellar-sdk';

const server = new rpc.Server('https://soroban-testnet.stellar.org');
const account = await server.getAccount(callerAddress);

const tx = buildContractCall({
  account,
  contractId,
  method: 'transfer',
  args: [
    new Address(from).toScVal(),
    new Address(to).toScVal(),
    nativeToScVal(100n, { type: 'i128' }),
  ],
  networkPassphrase: Networks.TESTNET,
});

const prepared = await server.prepareTransaction(tx);
prepared.sign(keypair);
await server.sendTransaction(prepared);
```

* **Returns** an unsigned, unprepared `Transaction` (`@stellar/stellar-sdk`).
* **Throws** [`PulsarValidationError`](/pulsar-stellar-sdk/sdk-reference/errors.md#pulsarvalidationerror) if the contract ID, method, arguments, or account are unusable, with the underlying SDK error as `cause` where there is one.

### `ContractCallOptions`

```ts
interface ContractCallOptions {
  readonly account: Account;              // copied before use, never mutated
  readonly contractId: string;
  readonly method: string;                // must not be empty
  readonly args?: readonly xdr.ScVal[];   // already ScVal; see below
  readonly networkPassphrase: string;     // must not be empty
  readonly fee?: string;                  // stroops; defaults to '100'
  readonly timeoutSeconds?: number;       // defaults to DEFAULT_CALL_TIMEOUT_SECONDS
}
```

`account` is copied before use, because `TransactionBuilder.build()` increments the sequence number of whatever `Account` it is handed. Copying keeps the caller's account untouched, so two calls built from one account do not silently claim two different sequence numbers (ADR-027).

`args` must already be `ScVal`, built with `nativeToScVal` or `Address`. Passing a plain string produces a malformed operation with no error from Soroban, so the SDK checks each argument and throws a `PulsarValidationError` naming the bad index.

## `DEFAULT_CALL_TIMEOUT_SECONDS`

```ts
const DEFAULT_CALL_TIMEOUT_SECONDS = 30;
```

The transaction timeout `buildContractCall` uses when `timeoutSeconds` is omitted.

## The `as*Event` family

```ts
asInitializeEvent(event: DecodedEvent): InitializeEvent | null
asDepositEvent(event: DecodedEvent): DepositEvent | null
asWithdrawEvent(event: DecodedEvent): WithdrawEvent | null
asTransferEvent(event: DecodedEvent): TransferEvent | null
asAdminChangeEvent(event: DecodedEvent): AdminChangeEvent | null
asEmitCustomEvent(event: DecodedEvent): EmitCustomEvent | null
```

Each converts a wire `DecodedEvent` into the binding shape for one showcase event, or returns `null` when the event is not that type. The conversion is opt-in and never implicit: `DecodedEvent` stays faithful to the wire, and a consumer who wants the binding's shape asks for it.

Each helper matches the **wire symbol** and the **topic count**. The name alone would accept an event carrying the right symbol with the wrong arity. Matching the wire symbol (lowercase `transfer`, not the binding's `Transfer`) is exactly what a hand-rolled bridge gets wrong, because the case differs and the match silently never fires.

```ts
import { asTransferEvent } from '@pulsar-stellar/sdk';

for (const event of page.items) {
  const transfer = asTransferEvent(event);
  if (transfer === null) continue;
  const { from, to, amount } = transfer.data; // amount is a bigint
}
```

### Binding types

```ts
interface BindingEvent<Name extends string, Data> { readonly name: Name; readonly data: Data; }

type InitializeEvent  = BindingEvent<'Initialize',  { admin: string }>;
type DepositEvent     = BindingEvent<'Deposit',     { from: string; amount: bigint }>;
type WithdrawEvent    = BindingEvent<'Withdraw',    { to: string; amount: bigint }>;
type TransferEvent    = BindingEvent<'Transfer',    { from: string; to: string; amount: bigint }>;
type AdminChangeEvent = BindingEvent<'AdminChange', { new_admin: string; old_admin: string }>;
type EmitCustomEvent  = BindingEvent<'EmitCustom',  { tag: string; payload: Uint8Array }>;
```

Notes that catch people out:

* **`amount` is a `bigint`** in the binding shape. The helper converts the wire `i128` string for you.
* **`Transfer` topic order is `from` then `to`**, fixed by SEP-41.
* **`AdminChange` is asymmetric**: `new_admin` is a topic (so you can filter on it), `old_admin` is the data.
* **`EmitCustom`'s wire symbol is `custom`**, not `emit_custom`, and `payload` is a `Uint8Array`, not a Node `Buffer`. If you hand it to a generated binding that expects `Buffer`, write `Buffer.from(payload)`.

## `parseTopics(topics)`

```ts
parseTopics(topics: readonly xdr.ScVal[]): unknown[]
```

Decodes topics into plain JavaScript values using Stellar's `scValToNative`. This is the ergonomic view, for inspection, logging, and ad-hoc scripting. It returns `unknown[]` on purpose, so a caller looks at what came back before using it.

**It is lossy**, and the losses are Stellar's, not this project's:

* A map with duplicate keys keeps only the last entry, with no error.
* A contract instance is not converted; you get the raw XDR struct.
* Wide integers come back as `bigint`.
* Bytes come back as a `Buffer`.

Use `parseTopics` when the values are ordinary and convenience matters. Use [`decodeTopics`](/pulsar-stellar-sdk/sdk-reference/decoding.md#decodetopicstopics) when the result is stored, compared, or sent anywhere, because that is where a dropped map entry or a rounded integer does damage.

## `scValToNative`

```ts
scValToNative(value: xdr.ScVal): any
```

Stellar's own `ScVal`-to-native converter, re-exported so a consumer needs one import rather than two. It is a pass-through with no wrapping, and it carries Stellar's behavior, including the losses listed for `parseTopics`. For faithful single-value decoding, use [`decodeScVal`](/pulsar-stellar-sdk/sdk-reference/decoding.md#decodescvalvalue).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://emeditweb.gitbook.io/pulsar-stellar-sdk/sdk-reference/contracts.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
