> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Request tracing

> Correlate Portal SDK requests with your own logs using the X-Portal-Trace-Id header.

Starting in `7.3.0`, the iOS SDK attaches an `X-Portal-Trace-Id` header to every HTTP request it makes. Portal's logs are indexed by that value, so a single trace ID lets you — and Portal support — follow one user-facing action across every request it produced.

## Overview

A trace ID is a lowercased UUID v4 sent as the `X-Portal-Trace-Id` request header.

You do not have to do anything to get one: the SDK generates a trace ID for every request. What you gain by supplying your own is **correlation** — the same value appears in your logs and in Portal's, so a support ticket goes from "a signing request failed sometime Tuesday" to an exact record.

## Automatic tracing

The header is added in three places inside the SDK, so every request path is covered:

| Request path         | Where the header is set                                                                            |
| -------------------- | -------------------------------------------------------------------------------------------------- |
| `PortalAPIRequest`   | `init(url:method:payload:bearerToken:traceId:)` seeds the header from `traceId`, or generates one. |
| `PortalRequests`     | `createBaseRequest` adds the header, and backfills it if a custom request omitted it.              |
| Legacy `HttpRequest` | Backfills the header when it is not already present.                                               |

<Note>
  iOS attaches the header **unconditionally** — there is no host allowlist. Requests to custom RPC endpoints you configure with `withRpcConfig` carry it too, because they are built with the same `PortalAPIRequest` type. The value is an opaque UUID and contains no client, wallet, or key data, but be aware that a third-party or self-hosted RPC provider will see it. This differs from the Android SDK, which filters the header to Portal-owned hosts.
</Note>

## Supplying your own trace ID

Three public entry points accept a trace ID. Wherever you leave it `nil`, the SDK generates one for you.

### Provider requests

`RequestOptions` carries the trace ID for `portal.request(...)` and everything built on top of it:

```swift theme={null}
public struct RequestOptions: Codable {
  public var signatureApprovalMemo: String? = nil
  public var sponsorGas: Bool? = nil
  public var traceId: String? = nil

  public init(
    signatureApprovalMemo: String? = nil,
    sponsorGas: Bool? = nil,
    traceId: String? = nil
  )
}
```

For requests that need a signature, `RequestOptions.traceId` is also forwarded into MPC signing metadata as `reqId`. That means the same value tags both the HTTP request and the MPC operation it triggers — one lookup covers the API call and the signing work behind it. That is the payoff, and it is not something you can reconstruct after the fact.

### Sending assets

`SendAssetParams` gained a `traceId`:

```swift theme={null}
public struct SendAssetParams: Codable {
  public let to: String
  public let amount: String
  public let token: String
  public let signatureApprovalMemo: String?
  public var sponsorGas: Bool?
  public var traceId: String?

  public init(
    to: String,
    amount: String,
    token: String,
    signatureApprovalMemo: String? = nil,
    sponsorGas: Bool? = nil,
    traceId: String? = nil
  )
}
```

### Raw signing

`rawSign` takes the trace ID as an argument. Unlike the other two, it is **not** defaulted — on this overload you must pass a value, even if that value is `nil`:

```swift theme={null}
public func rawSign(
  message: String,
  chainId: String,
  signatureApprovalMemo: String? = nil,
  traceId: String?
) async throws -> PortalProviderResult
```

```swift theme={null}
import PortalSwift

let traceId = generateTraceId()

do {
  let response = try await portal.rawSign(
    message: "74657374", // Must be in hex format — "test" is "74657374" in hex.
    chainId: "eip155:11155111",
    signatureApprovalMemo: "Sign login challenge",
    traceId: traceId
  )

  if let signature = response.result as? String {
    print("✅ Signed with trace ID \(traceId): \(signature)")
  }
} catch {
  print("❌ Signing failed with trace ID \(traceId): \(error)")
}
```

<Note>
  Existing calls keep compiling. `PortalProtocol` ships back-compatible extensions for `rawSign(message:chainId:)` and `rawSign(message:chainId:signatureApprovalMemo:)`, both of which pass `traceId: nil`.
</Note>

## Multi-step flows share one ID

When an SDK operation makes several requests, all of them carry the same trace ID. This is what makes tracing worth using — you get one handle for the whole flow instead of one per request.

| Operation                                                                                             | Trace ID source                         |
| ----------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `sendAsset` — build, sign, broadcast                                                                  | `SendAssetParams.traceId`, or generated |
| `portal.evmAccountType.upgradeTo7702` — status, build authorization list, raw sign, build transaction | Always generated internally             |
| MPC generate, backup, recover, eject                                                                  | Always generated internally             |

<Warning>
  MPC and EIP-7702 operations do **not** accept a trace ID. They generate one per operation and share it across their sub-requests, but you cannot supply your own. If you need to correlate an MPC flow with your own logs, see [Reading the ID back](#reading-the-id-back).
</Warning>

<Tip>
  Generate one trace ID per user-facing action — "send USDC", "sign in" — not per request. A trace ID that maps one-to-one onto HTTP requests tells you nothing you did not already know.
</Tip>

## Reading the id back

`PortalMpcBackupResponse` gained a `traceId` property. It is the only place the SDK hands a generated trace ID back to you:

```swift theme={null}
public struct PortalMpcBackupResponse {
  public let cipherText: String
  public let shareIds: [String]
  public let traceId: String

  public init(cipherText: String, shareIds: [String], traceId: String)
}
```

That type is returned by `PortalMpcProtocol.backup(_:usingProgressCallback:)`. Note that `portal.backupWallet(_:usingProgressCallback:)` returns a `(cipherText:storageCallback:)` tuple and does **not** surface the trace ID — so if you go through `Portal` rather than `PortalMpc` directly, the backup trace ID is not available to you.

<Note>
  No other response type exposes a trace ID. For every flow other than `PortalMpc.backup`, the way to know the trace ID is to supply it yourself.
</Note>

## Helpers

Two symbols are public:

```swift theme={null}
/// The trace ID header used by connect-api (client + custodian APIs).
public let PORTAL_TRACE_ID_HEADER = "X-Portal-Trace-Id"

/// Generates a UUID v4 trace ID for request correlation.
public func generateTraceId() -> String
```

Use `generateTraceId()` rather than rolling your own. It is the same generator the SDK uses internally, and it produces the lowercase UUID v4 format that Portal's logs expect.

Generate the ID once, log it, then thread it through the whole action:

```swift theme={null}
import PortalSwift

let traceId = generateTraceId()
print("Starting transfer — portalTraceId: \(traceId)")

do {
  let response = try await portal.sendAsset(
    chainId: "eip155:11155111",
    params: SendAssetParams(
      to: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
      amount: "0.01",
      token: "NATIVE",
      traceId: traceId
    )
  )

  print("Transfer submitted — portalTraceId: \(traceId), txHash: \(response.txHash)")
} catch {
  print("Transfer failed — portalTraceId: \(traceId), error: \(error)")
}
```

The same ID reaches `buildEip155Transaction`, the `eth_sendTransaction` provider request, and the MPC signing operation behind it.

For a provider request, pass it through `RequestOptions`:

```swift theme={null}
import PortalSwift

let chainId = "eip155:11155111"
let traceId = generateTraceId()

do {
  let transactionParam = BuildTransactionParam(
    to: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    token: "USDC",
    amount: "1"
  )

  let txDetails = try await portal.buildEip155Transaction(
    chainId: chainId,
    params: transactionParam
  )

  let response = try await portal.request(
    chainId: chainId,
    method: .eth_sendTransaction,
    params: [txDetails.transaction],
    options: RequestOptions(signatureApprovalMemo: "Send USDC", traceId: traceId)
  )

  guard let txHash = response.result as? String else {
    print("Unexpected response — portalTraceId: \(traceId)")
    return
  }

  print("✅ Transaction hash: \(txHash) — portalTraceId: \(traceId)")
} catch {
  print("❌ Request failed — portalTraceId: \(traceId), error: \(error)")
}
```

<Warning>
  `portal.buildEip155Transaction(chainId:params:)` does not accept a trace ID, so in the example above the build request carries a generated one and only the `eth_sendTransaction` leg carries yours. If you want a single trace ID across build, sign, and broadcast, use `sendAsset` with `SendAssetParams(traceId:)` instead of building and requesting separately.
</Warning>

See [Configure log level](./configure-log-level) if you also want the SDK's own output alongside your trace IDs.

## Sharing a trace id with Portal support

Log the trace ID next to your own request identifier, so you can look up either from the other. When something fails, include the trace ID in your support ticket or bug report — it is the fastest path from a user complaint to the exact requests Portal handled.

<Warning>
  A trace ID is not a secret, but it is a correlation handle. Do not log it in a system that stores user PII under a different retention policy than your Portal logs — that turns a debugging aid into a way to join two datasets you meant to keep separate.
</Warning>

## Signature changes in 7.3.0

Threading `traceId` through the SDK changed several protocol requirements.

<Note>
  If you **call** Portal's APIs, `7.3.0` requires no changes — every new parameter is either defaulted or paired with a back-compatible extension. If you **implement** any Portal protocol — most commonly a hand-rolled test mock — several gained new requirements.
</Note>

| Protocol                          | Change                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PortalProtocol`                  | `rawSign(message:chainId:signatureApprovalMemo:traceId:)` — `traceId` is **not defaulted** on the requirement. Also gained `var ramps: Ramps { get }`, for [on/off-ramp](/integrations/On-Off-Ramp/noah) support.                                                                                                                                                                                                                                                                                                                                                          |
| `PortalApiProtocol`               | `traceId:` added to `eject`, `getClient`, `getClientCipherText`, `prepareEject`, `refreshClient`, `updateShareStatus`, `storeClientCipherText`, `getWalletCapabilities`, `buildEip155Transaction`, `buildSolanaTransaction`, `buildBitcoinP2wpkhTransaction`, and `broadcastBitcoinP2wpkhTransaction` — each with a back-compatible extension. Also gained `var noah: PortalNoahApiProtocol { get }` for [Noah](/integrations/On-Off-Ramp/noah), and `generatePreGeneratedShares(metadataStr:traceId:)`, used by the pre-generated wallet [feature flag](./feature-flags). |
| `PortalEvmAccountTypeApiProtocol` | `traceId:` added to `getStatus`, `buildAuthorizationList`, and `buildAuthorizationTransaction`, each with a back-compatible extension. See [Upgrading to EIP-7702](./evm-account-type).                                                                                                                                                                                                                                                                                                                                                                                    |
| `EvmAccountTypePortalDependency`  | `rawSign(...)` gained `traceId: String?`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `PortalSignerProtocol`            | `sign(...)` gained `reqId: String?`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |

Two call-site changes are genuinely breaking:

**`PortalApi.init` gained a parameter.** `enclaveMPCHost` was inserted as the **third** parameter, before `provider:`. It is defaulted, so labelled calls are unaffected — but if you construct `PortalApi` directly and pass arguments positionally, check your call site.

```swift theme={null}
// 7.2.x
let api = PortalApi(
  apiKey: "YOUR_CLIENT_API_KEY",
  apiHost: "api.portalhq.io",
  provider: provider
)

// 7.3.0
let api = PortalApi(
  apiKey: "YOUR_CLIENT_API_KEY",
  apiHost: "api.portalhq.io",
  enclaveMPCHost: "mpc-client.portalhq.io",
  provider: provider
)
```

**`PortalMpcBackupResponse` gained a memberwise init.** `init(cipherText:shareIds:traceId:)` replaces the two-argument form. Test fixtures that build this type need the extra argument:

```swift theme={null}
// 7.2.x
let response = PortalMpcBackupResponse(
  cipherText: "cipher-text",
  shareIds: ["share-id"]
)

// 7.3.0
let response = PortalMpcBackupResponse(
  cipherText: "cipher-text",
  shareIds: ["share-id"],
  traceId: generateTraceId()
)
```

## Next Steps

* Review the [generateTraceId reference](../reference/generatetraceid)
* Learn about [signing transactions](./sign-a-transaction)
* Read [Send tokens](./send-tokens) for the full `sendAsset` flow
* Turn on SDK logging with [Configure log level](./configure-log-level)
* Check [Troubleshooting tips](./troubleshooting-tips) before filing a ticket
