> ## Documentation Index
> Fetch the complete documentation index at: https://ixoworld-mintlify-0aee4309.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Session signing with SignX

> Sign transactions in browser apps without per-action wallet confirmations using the SignX SDK and the user's Impacts X mobile wallet.

The IXO `@ixo/signx-sdk` orchestrates mobile-to-web authentication and transaction signing through QR code scanning by the Impacts X mobile wallet. A web app generates a QR code, the user scans it with their Impacts X app, and the SDK long-polls a relayer server until the wallet returns either a login payload or a signed-and-broadcast transaction. Once a transaction session is open, additional transactions are added to the same session without re-scanning the QR — the wallet activates the next transaction in sequence on each broadcast.

This is the publicly documented signing path. There is no in-browser private key — every signature comes from the user's mobile wallet.

## Before you start

You need:

* A web app (React or other) running over HTTPS.
* A reachable [`ixo-message-relayer`](https://github.com/ixofoundation/ixo-message-relayer) endpoint, or an IXO-operated equivalent for your network — see [Networks and endpoints](/reference/networks-and-endpoints).
* The Impacts X mobile app installed by your user ([App Store](https://apps.apple.com/app/impacts-x/id6444948058) / [Play Store](https://play.google.com/store/apps/details?id=com.ixo.mobile)).
* `@ixo/signx-sdk` and `@ixo/impactxclient-sdk` installed:

```bash theme={null}
npm install @ixo/signx-sdk @ixo/impactxclient-sdk
```

## Initialize the SignX client

```ts theme={null}
import { SignX } from "@ixo/signx-sdk";

const signXClient = new SignX({
  endpoint: "https://your-message-relayer.example.com",
  sitename: "Your dApp",
  network: "mainnet",
});
```

`network` is one of `mainnet | testnet | devnet`. The `sitename` is shown to the user inside the Impacts X app on every confirmation.

## Log the user in

`login()` returns the QR payload to render and starts long-polling for the wallet's response. Subscribe to `SIGN_X_LOGIN_SUCCESS` and `SIGN_X_LOGIN_ERROR` to receive the result.

```ts theme={null}
const loginRequest = await signXClient.login({ matrix: true });

signXClient.on("SIGN_X_LOGIN_SUCCESS", (event) => {
  const { name, address, pubKey, did, algo, matrix } = event.data;
  // persist the user; pubKey is hex-encoded, decode if you need bytes.
});

signXClient.on("SIGN_X_LOGIN_ERROR", (error) => {
  // user cancelled, polling timed out, or the wallet returned an error.
});
```

Pass `{ useDeeplink: true }` on mobile devices so the SDK opens the Impacts X app via deeplink before polling — this prevents some mobile browsers from cancelling the long-polling request when the user navigates away.

## Sign and broadcast a transaction

Build the transaction body using the `@ixo/impactxclient-sdk` registry, then pass the hex-encoded `txBody` to `signXClient.transact()`. The wallet signs the transaction and broadcasts it on the user's behalf.

```ts theme={null}
import { createRegistry, ixo } from "@ixo/impactxclient-sdk";
import { toHex } from "@cosmjs/encoding";

const registry = createRegistry();

const messages = [
  {
    typeUrl: "/ixo.entity.v1beta1.MsgCreateEntity",
    value: ixo.entity.v1beta1.MsgCreateEntity.fromPartial({
      ownerAddress: user.address,
      entityType: "asset",
      context: [],
      verification: [],
      controller: [],
      service: [],
      linkedResource: [],
      accordedRight: [],
      linkedEntity: [],
      linkedClaim: [],
    }),
  },
];

const txBodyHex = toHex(registry.encodeTxBody({ messages, memo: "" }));

const transactRequest = await signXClient.transact({
  address: user.address,
  did: user.did,
  pubkey: user.pubKey,
  timestamp: new Date().toISOString(),
  transactions: [{ sequence: 1, txBodyHex }],
});

signXClient.on("SIGN_X_TRANSACT_SUCCESS", (event) => {
  // event.data.transactionHash, event.data.code (0 on success)
});

signXClient.on("SIGN_X_TRANSACT_ERROR", (error) => {
  // wallet rejected, broadcast failed, or session timed out.
});
```

If `transactRequest.sessionHash` is present a new session was opened — render a QR with `transactRequest`. If it is absent the transaction was added to the existing session — call `signXClient.pollNextTransaction()` and update the UI to indicate the next signature is pending in the wallet.

The wallet returns the chain's broadcast result (`code`, `transactionHash`, …) directly through `SIGN_X_TRANSACT_SUCCESS`.

## Manage the session

Every transaction extends the session's `validUntil` timestamp. Inspect `signXClient.transactSessionHash` to know whether a session is active and `signXClient.transactSequence` for the current sequence the wallet is being asked to sign.

```ts theme={null}
window.addEventListener("beforeunload", () => {
  signXClient.stopPolling("Page closing", "SIGN_X_TRANSACT_ERROR");
});
```

Sessions are intentionally not persisted across browser refresh — `transactSessionHash` lives in memory only. After a refresh, the next `transact()` call starts a new session and renders a fresh QR.

## Transaction-type restrictions

Restricting which message types a session can sign is enforced **on the wallet** — the Impacts X app inspects each transaction body against the user's saved policy before signing. The web SDK does not impose its own filter. To restrict the messages a session can sign in your dApp, build the txBody only for the message types your flow needs and refuse to call `transact()` for anything else; pair this with smart-account authenticators (see [Manage smart accounts](/guides/dev/smart-accounts)) for an on-chain enforcement layer.

When you build a `MsgGrant` (`cosmos.authz.v1beta1.MsgGrant`) with a `GenericAuthorization`, the `msg` field is the **fully qualified message URL**, e.g. `/ixo.entity.v1beta1.MsgCreateEntity`. The same convention applies everywhere transaction filtering is configured.

## Verify the result

`SIGN_X_TRANSACT_SUCCESS` returns the transaction hash directly. To independently verify, query `cosmos.tx.v1beta1.GetTx` on the [gRPC gateway API](/api-reference/grpc-gateway-api) with that hash, or fetch the transaction from the [Blocksync GraphQL API](/api-reference/blocksync-graphql-api).

## Troubleshooting

<AccordionGroup>
  <Accordion title="QR code never resolves" icon="qrcode">
    Confirm `endpoint` points to a reachable Message Relayer for the same `network` as the user's wallet, and that `network` matches the chain the user logged in on. Check the relayer is healthy.
  </Accordion>

  <Accordion title="Long-polling cancelled on mobile" icon="mobile">
    Mobile browsers may cancel ongoing requests when navigating to deeplinks. Pass `useDeeplink: true` to `login`, `matrixLogin`, `dataPass`, and as the third argument to `transact` so the SDK opens the deeplink first and waits 300 ms before polling.
  </Accordion>

  <Accordion title="Wallet rejected the transaction" icon="ban">
    The wallet may reject if the message type is outside the user's signing policy, the chain ID does not match, or the user manually denies. `SIGN_X_TRANSACT_ERROR` carries the reason. Re-issue the transaction after the user adjusts their policy.
  </Accordion>

  <Accordion title="Session expired mid-flow" icon="clock">
    `transactSessionHash` is cleared when the relayer reports the session expired. The next `transact()` call starts a new session — surface this to the user (a fresh QR is needed). Call `stopPolling(message, event, false)` to stop polling without clearing the session if you want to retain it.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="SignX SDK reference" icon="key" href="/sdk-reference/signx-sdk">
    Methods, events, and types.
  </Card>

  <Card title="SignX SDK README" icon="github" href="https://github.com/ixofoundation/ixo-signx#readme">
    Source of truth for the SDK.
  </Card>

  <Card title="Smart accounts" icon="user-shield" href="/guides/dev/smart-accounts">
    Add on-chain enforcement of which message types may be signed.
  </Card>

  <Card title="Manage authorization" icon="lock" href="/guides/dev/authz">
    Combine SignX with Cosmos `x/authz` and `x/feegrant`.
  </Card>
</CardGroup>
