> For the complete documentation index, see [llms.txt](https://docs.squidrouter.exchange/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.squidrouter.exchange/api-and-sdk-integration/chain-integration-guides/hedera-integration.md).

# Hedera Integration

Integrate cross-chain swaps from Hedera to EVM chains using Squid. Hedera support is powered by [**Squid Intents**](/api-and-sdk-integration/coral-intent-swaps.md) — Squid's intent-based execution protocol for fast, solver-driven cross-chain settlement.

> **Squid Intents must be enabled on your integrator ID.** Reach out to the Squid team to request Squid Intents access before integrating with Hedera.

***

## Hedera Parameters

| Parameter    | Value |
| ------------ | ----- |
| **Chain ID** | `295` |

### Supported Tokens

| Token    | Address                                      |
| -------- | -------------------------------------------- |
| **USDC** | `0xD71CdfD2b92CCb4c6B7cB26cfBa6584cA848C34a` |

***

## Key Concepts

### Transaction Type: `DEPOSIT_ADDRESS_WITH_SIGNATURE`

Hedera routes use the `DEPOSIT_ADDRESS_WITH_SIGNATURE` transaction type. Unlike standard EVM routes, this type:

* **Does not require ERC-20 approval** — skip the `approve()` call entirely
* **Requires signing an order hash** — the route response includes a `signatureRequired` field containing an order hash that must be signed by the sender's wallet
* **Returns a deposit address** — funds are sent directly to a deposit address via a standard EVM transaction

### Order Hash Signing

When the route response has `transactionRequest.type === 'DEPOSIT_ADDRESS_WITH_SIGNATURE'`, the `transactionRequest.signatureRequired` field contains the order hash. Sign this hash using `signer.signMessage()` to produce the `depositTxVerificationSignature`:

```typescript
const orderHash = transactionRequest.signatureRequired;
const depositTxVerificationSignature = await signer.signMessage(orderHash);
```

> **Important:** The Squid indexer expects the signature of the literal UTF-8 hex string, not raw bytes. Use `signMessage()` (which applies EIP-191 prefix) directly on the order hash string.

### Token Association Polling

Hedera uses the Hedera Token Service (HTS) which requires tokens to be explicitly associated with accounts before they can receive transfers. When Squid creates a deposit address for your route, it must complete a token association on the Hedera network. This process is asynchronous and takes a few seconds.

Poll `estimateGas` on the transaction before executing to confirm the token association is complete:

```typescript
for (let i = 0; i < 15; i++) {
  try {
    await provider.estimateGas({
      from: signer.address,
      to: transactionRequest.target,
      data: transactionRequest.data,
      value: transactionRequest.value,
    });
    console.log("Token association complete. Proceeding...");
    break;
  } catch (e) {
    if (i === 14) {
      console.warn("Polling timed out. Attempting execution anyway...");
    } else {
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }
}
```

If the token has not yet been associated, `estimateGas` will throw an HTS system error (e.g., `TOKEN_NOT_ASSOCIATED_TO_ACCOUNT`). The loop retries up to 15 times with a 1-second delay (\~15 seconds total).

***

## API Integration

### Route from Hedera (Hedera → EVM)

To route from Hedera to an EVM chain, set `fromChain` to `295` and provide an EVM-compatible Hedera address.

#### Full Working Example

```typescript
import { ethers } from "ethers";
import axios from "axios";
import * as dotenv from "dotenv";
dotenv.config();

const privateKey: string = process.env.PRIVATE_KEY!;
const integratorId: string = process.env.INTEGRATOR_ID!;
const FROM_CHAIN_RPC: string = process.env.FROM_CHAIN_RPC_ENDPOINT!;

// Define chain and token parameters
const fromChainId = "295"; // Hedera Mainnet
const fromToken = "0xD71CdfD2b92CCb4c6B7cB26cfBa6584cA848C34a"; // USDC on Hedera
const fromAmount = "2000";

const toChainId = "8453"; // Base (or any supported EVM chain)
const toToken = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; // USDC on Base

// Set up provider and signer
const provider = new ethers.JsonRpcProvider(FROM_CHAIN_RPC);
const signer = new ethers.Wallet(privateKey, provider);

// Get route from Squid API
const getRoute = async (params: any) => {
  const result = await axios.post(
    "https://v2.api.squidrouter.exchange/v2/route",
    params,
    {
      headers: {
        "x-integrator-id": integratorId,
        "Content-Type": "application/json",
      },
    }
  );
  const requestId = result.headers["x-request-id"];
  return { data: result.data, requestId };
};

// Get transaction status
const getStatus = async (params: any) => {
  const result = await axios.get("https://v2.api.squidrouter.exchange/v2/status", {
    params,
    headers: {
      "x-integrator-id": integratorId,
    },
  });
  return result.data;
};

(async () => {
  const params = {
    fromAddress: signer.address,
    fromChain: fromChainId,
    fromToken: fromToken,
    fromAmount: fromAmount,
    toChain: toChainId,
    toToken: toToken,
    toAddress: signer.address,
    quoteOnly: false,
  };

  console.log("Parameters:", params);

  // 1. Get the route
  const routeResult = await getRoute(params);
  const route = routeResult.data.route;
  const requestId = routeResult.requestId;

  // Extract quoteId (required for Squid Intents status tracking)
  const quoteId =
    route?.estimate?.actions?.[0]?.coralV2Order?.quoteId ||
    route?.estimate?.quoteId ||
    route?.quoteId ||
    routeResult.data?.quoteId;

  console.log("Route received. Expected output:", route.estimate.toAmount);
  console.log("quoteId:", quoteId);

  const transactionRequest = route.transactionRequest;
  let depositTxVerificationSignature: string | undefined;

  // 2. Handle DEPOSIT_ADDRESS_WITH_SIGNATURE route type
  if (
    transactionRequest.type === "DEPOSIT_ADDRESS_WITH_SIGNATURE" ||
    transactionRequest.routeType === "DEPOSIT_ADDRESS_WITH_SIGNATURE"
  ) {
    console.log("Route type is DEPOSIT_ADDRESS_WITH_SIGNATURE, skipping ERC-20 approval...");

    // Sign the order hash
    const orderHash = transactionRequest.signatureRequired;
    if (orderHash) {
      depositTxVerificationSignature = await signer.signMessage(orderHash);
      console.log("Generated deposit verification signature");
    }
  }

  // 3. Poll estimateGas to confirm token association
  console.log("Verifying Hedera token association on deposit address...");
  for (let i = 0; i < 15; i++) {
    try {
      await provider.estimateGas({
        from: signer.address,
        to: transactionRequest.target,
        data: transactionRequest.data,
        value: transactionRequest.value,
      });
      console.log("Token association confirmed. Proceeding with swap...");
      break;
    } catch (e) {
      if (i === 14) {
        console.warn("Token association polling timed out. Attempting execution anyway...");
      } else {
        await new Promise((resolve) => setTimeout(resolve, 1000));
      }
    }
  }

  // 4. Execute the transaction
  const tx = await signer.sendTransaction({
    to: transactionRequest.target,
    data: transactionRequest.data,
    value: transactionRequest.value,
    gasPrice: transactionRequest.gasPrice,
    gasLimit: transactionRequest.gasLimit,
  });
  console.log("Transaction broadcasted:", tx.hash);
  const txReceipt = await tx.wait();
  console.log("Transaction confirmed!");

  // 5. Poll status until complete
  const statusParams: any = {
    transactionId: txReceipt!.hash,
    requestId: requestId,
    fromChainId: fromChainId,
    toChainId: toChainId,
    bridgeType: "rfq",
    quoteId: quoteId,
  };

  // Attach the deposit verification signature for status tracking
  if (depositTxVerificationSignature) {
    statusParams.depositTxVerificationSignature = depositTxVerificationSignature;
  }

  const completedStatuses = ["success", "partial_success", "needs_gas", "not_found"];
  const maxRetries = 100;
  let retryCount = 0;
  let status;

  do {
    try {
      status = await getStatus(statusParams);
      console.log(`Status: ${status.squidTransactionStatus}`);
    } catch (error: any) {
      if (error.response && error.response.status === 404) {
        retryCount++;
        if (retryCount >= maxRetries) {
          console.error("Max retries reached.");
          break;
        }
        console.log("Transaction not found. Retrying in 5s...");
        await new Promise((resolve) => setTimeout(resolve, 5000));
        continue;
      }
      throw error;
    }

    if (status && !completedStatuses.includes(status.squidTransactionStatus)) {
      await new Promise((resolve) => setTimeout(resolve, 5000));
    }
  } while (!status || !completedStatuses.includes(status.squidTransactionStatus));

  console.log("Swap complete!");
  console.log(`Track on Squid Scanner: https://scan.squidrouter.exchange/tx/${txReceipt!.hash}`);
})();
```

***

## SDK Integration

The Squid SDK handles much of the Hedera-specific logic automatically. For `DEPOSIT_ADDRESS_WITH_SIGNATURE` routes, `executeRoute()` will:

* Automatically sign the order hash (`signatureRequired`)
* Execute the deposit transaction
* Return the `depositTxVerificationSignature` in the response

You are still responsible for:

* Polling `estimateGas` to wait for token association before calling `executeRoute()`
* Passing the `depositTxVerificationSignature` and `quoteId` to the status endpoint

### Full Working Example

```typescript
import { Squid } from "@0xsquid/sdk";
import { ethers } from "ethers";
import * as dotenv from "dotenv";
dotenv.config();

const privateKey: string = process.env.PRIVATE_KEY!;
const integratorId: string = process.env.INTEGRATOR_ID!;
const FROM_CHAIN_RPC: string = process.env.FROM_CHAIN_RPC_ENDPOINT!;

// Define chain and token parameters
const fromChainId = "295"; // Hedera
const fromToken = "0xD71CdfD2b92CCb4c6B7cB26cfBa6584cA848C34a"; // USDC on Hedera
const fromAmount = "2000";

const toChainId = "42161"; // Arbitrum
const toToken = "0xaf88d065e77c8cc2239327c5edb3a432268e5831"; // USDC on Arbitrum

// Set up provider and signer
const provider = new ethers.JsonRpcProvider(FROM_CHAIN_RPC);
const signer = new ethers.Wallet(privateKey, provider);

// Initialize Squid SDK
const getSDK = (): Squid => {
  const squid = new Squid({
    baseUrl: "https://v2.api.squidrouter.exchange",
    integratorId: integratorId,
  });
  return squid;
};

(async () => {
  const squid = getSDK();
  await squid.init();
  console.log("Squid SDK initialized");

  // 1. Get the route
  const params = {
    fromAddress: signer.address,
    fromChain: fromChainId,
    fromToken: fromToken,
    fromAmount: fromAmount,
    toChain: toChainId,
    toToken: toToken,
    toAddress: signer.address,
  };

  const { route, requestId } = await squid.getRoute(params);

  // Extract quoteId (required for Squid Intents status tracking)
  const quoteId =
    (route as any).estimate?.actions?.[0]?.coralV2Order?.quoteId ||
    (route as any).estimate?.quoteId ||
    (route as any).quoteId;

  console.log("Route received. Expected output:", route.estimate.toAmount);

  // 2. Poll estimateGas to confirm token association
  if (route.transactionRequest) {
    const txReq = route.transactionRequest as any;
    console.log("Verifying Hedera token association on deposit address...");
    for (let i = 0; i < 15; i++) {
      try {
        await provider.estimateGas({
          from: signer.address,
          to: txReq.target,
          data: txReq.data,
          value: txReq.value,
        });
        console.log("Token association confirmed. Proceeding...");
        break;
      } catch (e) {
        if (i === 14) {
          console.warn("Polling timed out. Executing anyway...");
        } else {
          await new Promise((resolve) => setTimeout(resolve, 1000));
        }
      }
    }
  }

  // 3. Execute the route (SDK handles orderHash signing automatically)
  const response = await squid.executeRoute({
    signer: signer as any,
    route,
  });

  // 4. Extract transaction hash and deposit verification signature
  let txHash = "unknown";
  let depositTxVerificationSignature: string | undefined;

  if (response && typeof response === "object") {
    if ("hash" in response) {
      txHash = response.hash as string;
      await (response as any).wait?.();
    } else if ("transactionHash" in response) {
      txHash = (response as any).transactionHash;
    }

    if ("depositTxVerificationSignature" in response) {
      depositTxVerificationSignature = (response as any).depositTxVerificationSignature;
    }
  }

  console.log(`Transaction confirmed: ${txHash}`);
  console.log(`Track on Squid Scanner: https://scan.squidrouter.exchange/tx/${txHash}`);

  // 5. Poll status until complete
  const statusParams: any = {
    transactionId: txHash,
    requestId: requestId,
    integratorId: integratorId,
    fromChainId: fromChainId,
    toChainId: toChainId,
    bridgeType: "rfq",
    quoteId: quoteId,
  };

  if (depositTxVerificationSignature) {
    statusParams.depositTxVerificationSignature = depositTxVerificationSignature;
  }

  const completedStatuses = ["success", "partial_success", "needs_gas", "not_found"];
  const maxRetries = 100;
  let retryCount = 0;
  let status;

  do {
    try {
      await new Promise((resolve) => setTimeout(resolve, 5000));
      status = await squid.getStatus(statusParams);
      console.log(`Status: ${status.squidTransactionStatus}`);
    } catch (error: unknown) {
      if (
        error instanceof Error &&
        (error as any).response?.status === 404
      ) {
        retryCount++;
        if (retryCount >= maxRetries) {
          console.error("Max retries reached.");
          break;
        }
        console.log("Transaction not found. Retrying...");
        continue;
      }
      throw error;
    }
  } while (
    !status ||
    !completedStatuses.includes(status.squidTransactionStatus as string)
  );

  console.log("Swap complete!");
})();
```

***

## Status Tracking

Hedera routes are powered by Squid Intents, which **requires** both `quoteId` and `depositTxVerificationSignature` for status polling.

| Parameter                        | Description                                                 |
| -------------------------------- | ----------------------------------------------------------- |
| `transactionId`                  | The deposit transaction hash                                |
| `fromChainId`                    | `295` (Hedera)                                              |
| `toChainId`                      | The destination chain ID                                    |
| `quoteId`                        | Returned in the route response — required for Squid Intents |
| `depositTxVerificationSignature` | The signed order hash — required for Hedera routes          |
| `bridgeType`                     | `rfq`                                                       |

```typescript
const statusParams = {
  transactionId: txHash,
  fromChainId: "295",
  toChainId: toChainId,
  quoteId: quoteId,
  depositTxVerificationSignature: depositTxVerificationSignature,
  bridgeType: "rfq",
};

const status = await axios.get("https://v2.api.squidrouter.exchange/v2/status", {
  params: statusParams,
  headers: { "x-integrator-id": "<your-integrator-id>" },
});
```

> **Important:** A Squid Intents transaction will not be trackable unless status is polled with the `quoteId`. See the [Integrating Squid Intents](/api-and-sdk-integration/coral-intent-swaps/integrating-squid-intents.md) guide for full status polling details.

***

## Code Examples

### API Examples

| Route        | Example                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------- |
| Hedera → EVM | [hederaToArbitrumSwap](https://github.com/0xsquid/examples/tree/main/V2/api/hederaToArbitrumSwap) |

### SDK Examples

| Route        | Example                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------- |
| Hedera → EVM | [hederaToArbitrumSwap](https://github.com/0xsquid/examples/tree/main/V2/sdk/hederaToArbitrumSwap) |

***

## Important Notes

* **EVM-compatible wallets only** — Hedera integration uses Hedera's EVM-compatible layer. You need an Ethereum-style private key and can interact with Hedera using standard `ethers.js` tooling and a Hedera JSON-RPC endpoint.
* **No ERC-20 approval needed** — `DEPOSIT_ADDRESS_WITH_SIGNATURE` routes bypass the standard approval flow. Do not call `approve()` for these routes.
* **Token association is asynchronous** — Always poll `estimateGas` before sending the deposit transaction to ensure the deposit address has completed HTS token association. Skipping this step may result in a failed transaction.
* **Order hash signing is mandatory** — The `signatureRequired` field must be signed and the resulting `depositTxVerificationSignature` must be passed to the `/v2/status` endpoint for transaction tracking.
* **`quoteId` is required for status calls** — All Squid Intents transactions (including Hedera routes) require the `quoteId` parameter when polling the status endpoint.

For a full reference of all transaction types across all chains, see the [Transaction Types](/api-and-sdk-integration/key-concepts/transaction-types.md) page.


---

# 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:

```
GET https://docs.squidrouter.exchange/api-and-sdk-integration/chain-integration-guides/hedera-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
