# C# SDK (.NET and Unity)

> The LayerHop NuGet package is an async C# client for .NET 6+ and Unity 2021.3+ (netstandard2.1, Newtonsoft.Json), IL2CPP-safe, with typed models, order watching and webhook verification.

Source: https://docs.layerhop.com/sdks/csharp

`LayerHop` is the C# client for the LayerHop [REST API](/api-reference), for **.NET 6+** and **Unity 2021.3+**, mirroring the [Go](/sdks/go) and [TypeScript](/sdk) SDKs. It targets `netstandard2.1`, its only dependency is `Newtonsoft.Json` 13 (which Unity ships as `com.unity.nuget.newtonsoft-json`), every method is an `async Task` that takes a `CancellationToken`, and you can inject your own `HttpClient`.

> The SDK never holds keys. It returns **unsigned** transactions; your wallet integration (Nethereum, Solnet, the Solana Unity SDK, Reown AppKit for Unity, a KMS…) signs and broadcasts them.

## How do I install it?

```bash
dotnet add package LayerHop --version 0.6.0
```

For Unity, see [How do I install it in Unity?](#how-do-i-install-it-in-unity). The source lives in [`layerhop/layerhop-csharp`](https://github.com/layerhop/layerhop-csharp).

## Quickstart

Amounts are **integer strings in base units** (`"100000000"` = 100 USDC).

```csharp
using LayerHop;

using var lh = new LayerHopClient(new LayerHopOptions { Integrator = "myapp" }); // ApiKey, BaseUrl

// 1. Quote
var q = await lh.QuoteAsync(new QuoteParams
{
    SrcChain = "base", SrcToken = "USDC", Amount = "100000000",
    DstChain = "solana", DstToken = "SOL",
    Sender = "0xYourEvmAddress", Recipient = "YourSolanaAddress",
});

// 2. Build the unsigned transaction
var tx = await lh.BuildTxAsync(q.Best.QuoteId);

// 3. Sign + send with YOUR wallet
string txHash = tx.IsEvm
    ? await SignEvm(tx.AsEvm())   // send Approval first when set, then the tx, then ExtraTxs in order
    : await SignSvm(tx.AsSvm());  // base64 SerializedTx; SubmitViaApi → sign only, then lh.SubmitAsync(..)

// 4. Report the broadcast
await lh.ReportSubmittedAsync(q.Best.QuoteId, txHash);

// 5. Watch until filled / refunded / failed
await foreach (var st in lh.WatchOrder(q.Best.Provider, txHash))
    Console.WriteLine(st.State);
```

`WatchOrderAsync(provider, id, onChange: s => …)` does the same and returns the final status; both throw `LayerHopWatchTimeoutException` after 30 minutes by default. Gas on arrival: set `GasOnArrival = true` or `GasOnArrivalUsd = 5` and check `q.Best.GasOnArrival` ([Gas on arrival](/guides/gas-on-arrival)).

## How do I install it in Unity?

In **Window > Package Manager > + > Add package from git URL…** enter:

```bash
https://github.com/layerhop/layerhop-csharp.git?path=src/LayerHop#v0.6.0
```

or add `"com.layerhop.sdk": "https://github.com/layerhop/layerhop-csharp.git?path=src/LayerHop#v0.6.0"` to `Packages/manifest.json`. Unity resolves the `com.unity.nuget.newtonsoft-json` dependency itself, and the auto-referenced `LayerHop` assembly definition lets your scripts `using LayerHop;` right away.

```csharp
using LayerHop;
using UnityEngine;

public class SwapButton : MonoBehaviour
{
    private readonly LayerHopClient _lh = new LayerHopClient(new LayerHopOptions { Integrator = "mygame" });

    public async void OnClick()
    {
        try
        {
            var q = await _lh.QuoteAsync(new QuoteParams
            {
                SrcChain = "solana", SrcToken = "USDC", Amount = "5000000",
                DstChain = "solana", DstToken = "SOL",
                Sender = PlayerWallet.Address, Recipient = PlayerWallet.Address,
            });
            var tx = (await _lh.BuildTxAsync(q.Best.QuoteId)).AsSvm();
            // Hand tx.SerializedTx to your wallet integration to sign and send, then:
            // await _lh.ReportSubmittedAsync(q.Best.QuoteId, signature);
        }
        catch (LayerHopApiException e)
        {
            Debug.LogWarning($"{e.Code}: {e.ApiMessage} ({e.RequestId})");
        }
    }

    private void OnDestroy() => _lh.Dispose();
}
```

`await` in a `MonoBehaviour` resumes on the main thread, so you can update UI after it; UniTask works too. Never ship an API key inside a game build: quotes, transactions and order status work without one, and checkout sessions, invoices and deposit addresses belong on your backend.

## Does it work with IL2CPP and WebGL?

- **IL2CPP and code stripping:** the package ships a `link.xml` that preserves the `LayerHop` assembly, so the JSON models survive stripping at any level. There is no `System.Reflection.Emit`, no `dynamic` and no runtime code generation; every model is a plain class with a parameterless constructor and explicit `[JsonProperty]` names. The language level is C# 9.
- **WebGL:** browsers only allow `UnityWebRequest`, so `HttpClient`'s default handler does not work there. Pass `new HttpClient(handler)` with a small `HttpMessageHandler` that forwards to `UnityWebRequest`; the [repository README](https://github.com/layerhop/layerhop-csharp#webgl) has a copy-paste version. Every other platform works with the default handler.

## Which methods are available?

| Area | Methods |
|---|---|
| Assets | `HealthAsync` · `GetChainsAsync` · `GetTokensAsync(chain)` · `GetPricesAsync(ids)` |
| Swaps | `QuoteAsync` · `BuildTxAsync` (→ `AsEvm()` / `AsSvm()`) · `SimulateAsync` · `SubmitAsync` · `ReportSubmittedAsync` · `GetOrderStatusAsync` · `ListOrdersAsync` · `WatchOrder` / `WatchOrderAsync` |
| Checkout | `CreateCheckoutSessionAsync` · `GetCheckoutSessionAsync` · `ListCheckoutSessionsAsync` · `ExpireCheckoutSessionAsync` · `QuoteCheckoutSessionAsync` |
| Products | `CreateCheckoutProductAsync` · `GetCheckoutProductAsync` · `ListCheckoutProductsAsync` · `UpdateCheckoutProductAsync` · `StartProductCheckoutAsync` |
| Refunds | `CreateRefundAsync` · `ListRefundsAsync` |
| Invoices | `CreateInvoiceAsync` · `GetInvoiceAsync` · `ListInvoicesAsync` · `UpdateInvoiceAsync` · `SendInvoiceAsync` · `VoidInvoiceAsync` · `GetPublicInvoiceAsync` · `PayInvoiceAsync` |
| Deposit addresses | `CreateDepositAddressAsync` · `GetDepositAddressAsync` · `ListDepositAddressesAsync` · `RefreshDepositAddressAsync` · `DisableDepositAddressAsync` · `SimulateDepositAsync` |
| Subscriptions | `CreateSubscriptionAsync` · `GetSubscriptionAsync` · `ListSubscriptionsAsync` · `UpdateSubscriptionAsync` · `CancelSubscriptionAsync` · `PauseSubscriptionAsync` · `ResumeSubscriptionAsync` · `ListSubscriptionInvoicesAsync` |
| Limit orders (Solana, beta) | `CreateLimitOrderAsync` · `SubmitLimitOrderAsync` · `GetLimitOrderAsync` · `ListLimitOrdersAsync` · `CancelLimitOrderAsync` · `SimulateLimitOrderAsync` |
| Misc | `GetServiceStatusAsync` · `TriggerWebhookEventAsync` |

`Update*Params` send only the properties you set; setting a nullable text field to `""` clears it (sends `null`).

## How do I take payments with checkout?

Run this on your server with an API key ([Checkout guide](/guides/checkout)):

```csharp
var lh = new LayerHopClient(new LayerHopOptions { ApiKey = Environment.GetEnvironmentVariable("LAYERHOP_API_KEY") });
var session = await lh.CreateCheckoutSessionAsync(new CreateCheckoutParams
{
    Amount = "49000000", SettleChain = "base", SettleToken = "USDC",   // 49 USDC on Base
    Recipient = "0xYourPayoutAddress", Reference = "order_123",
    SuccessUrl = "https://shop.example.com/thanks",
});
// Redirect the customer to session.Url; fulfil on the checkout.session.completed webhook.
```

## How do I verify webhooks?

Verify the `LayerHop-Signature` header against the **raw** body before parsing it ([Webhooks guide](/guides/webhooks)). ASP.NET Core minimal API:

```csharp
app.MapPost("/webhooks/layerhop", async (HttpRequest req) =>
{
    using var reader = new StreamReader(req.Body);
    var raw = await reader.ReadToEndAsync();
    try
    {
        var evt = Webhooks.ConstructEvent(secret, raw, req.Headers[Webhooks.SignatureHeader]);
        if (evt.Type == WebhookEventTypes.CheckoutSessionCompleted) Fulfil(evt.Data.Session!.Reference);
        return Results.Ok();
    }
    catch (LayerHopWebhookSignatureException) { return Results.BadRequest(); }
});
```

`Webhooks.VerifySignature(secret, body, header)` returns a `bool` (5-minute tolerance by default, several `v1` values accepted, constant-time comparison). `Webhooks.Sign(secret, body, timestamp)` builds a header for your own tests.

## How are errors reported?

Every non-2xx response throws `LayerHopApiException` with a stable `Code` (`QUOTE_EXPIRED`, `NO_ROUTE`…, see [Errors](/errors)), the HTTP `Status`, the `RequestId` to quote to support, and `DocsUrl`. Network failures throw `HttpRequestException`.

## How do I use test mode?

Use an `lh_test_…` key and every call is served by the sandbox: live-priced quotes, placeholder transactions (`tx.Sandbox == true`, never broadcast them) and orders that fill in about 10 seconds. See [Test mode](/guides/test-mode).
